diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..c4d620a4d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "env": { "browser": true, "es2021": true, "jquery": true }, + "ignorePatterns": ["tests/", "dist/", "node_modules/", "coverage/"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "overrides": [], + "parser": "@typescript-eslint/parser", + "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "warn", + { "argsIgnorePattern": "^_" } + ], + "no-empty": ["error", { "allowEmptyCatch": true }], + "no-constant-condition": "off", + "@typescript-eslint/no-empty-function": "off" + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..c6cb591ca --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.toml merge=union \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..2667c91ea --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ +## Abstract + + + +--- + +## Testing +To test this PR, it's suggested to attempt these user flows, or variations of these: +- Step 1 +- Step 2 +- Step 3 + +If any errors are found, the PR works unexpectedly, or you have viable suggestions to improve the UX or functionality of the PR, let me know! + +--- + + + +## What does this PR address? + + +## What features or improvements were added? + + +## How does this benefit users? + + diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..9b7c5fb90 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,68 @@ +name: Build and release container image + +on: + push: + branches: + - "master" + paths-ignore: + - "*.md" + - LICENSE +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install npm/nodejs + uses: actions/setup-node@v4 + - name: Install packages + run: npm install + - name: Build + run: npm run build + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to Github Packages + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v3 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Set tag date-SHA + id: tag + run: echo "tag=$(date +'%Y-%m-%d')-$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV + + - name: Build image and push to GitHub Container Registry + uses: docker/build-push-action@v2 + with: + context: . + file: docker/Dockerfile + platforms: linux/amd64 + tags: | + ghcr.io/pivx-labs/mypivxwallet:latest + ghcr.io/pivx-labs/mypivxwallet:${{ env.tag }} + ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: release=1 + push: true + + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 000000000..eb7d988d2 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,44 @@ +name: Lint + +on: + # Trigger the workflow on push or pull request, + # but only for the main branch + push: + branches: + - master + paths-ignore: + - "*.md" + - LICENSE + - "*.yaml" + pull_request: + branches: + - master + paths-ignore: + - "*.md" + - LICENSE + - "*.yaml" + + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Install Node.js dependencies + run: npm ci + + - name: Run ESlint + run: npm run lint + + - name: Run prettier + run: npx prettier -l . + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ee744429b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +dist/ +node_modules/ +locale/lang-tools/env +__pycache__ +coverage/ +coverage.data +chain_params.json \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..34659f236 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +node_modules/ +index.template.html +*.css +*.yaml +LICENSE +*.md +package-lock.json +dist/ +coverage/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..27512b6a8 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "singleQuote": true +} diff --git a/LICENSE b/LICENSE index 9f9ee0c99..f7a6ba252 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,8 @@ MIT License Copyright (c) 2020 Luke Larsen Copyright (c) 2020 DogeCash -Copyright (c) 2021 StakeCube +Copyright (c) 2021 ZENZO Ecosystem +Copyright (c) 2021 JSKitty Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README.md b/README.md index 9114633ac..d1516e0fb 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,60 @@ -# StakeCube Web3 -## JS-based web 3.0 wallet for SCC +

+ Send, Stake and Receive with PIVX's most universal wallet. +

-### Installation -To use this web wallet locally click the clone or download button, then chose download as a zip. Unzip the file. Once it is unzipped, open the index.html file in your favorite **_MODERN_** browser. In order to generate new address you must change the debug setting to false, This will generate secure keys by way of window.crypto. There are some cases where this may not work properly make sure you are using a modern browser and that window.crypto works with your browser. Otherwise the generation may not be secure. -### USE +

+ Production (Stable) | Bleeding-Edge (Unstable) +

-#### Key Generation -**_IF YOU ARE IN DEBUG MODE (top right it will say DEBUG) MAKE SURE TO DEACTIVATE DEBUG BEFORE GENERATING KEYS AS IT WILL GENERATE THE SAME KEY OVER AND OVER AND IT IS NOT SECURE._** +--- -The current setup allows for users to generate one private key and one public key. This is not a HD Wallet (Hierarchical deterministic Wallet) and because of that you must remember to back up every private key you generate. There is no one master. Losing any of the private keys you generate could result in the loss of funds. +
-#### Transaction -##### Simple Transactions -**Warning** _in the current state do not use this if you have to have more then 1000 input transactions. In that case it would be better to import your wallet to a software wallet or wait for an update. A small transaction was recently sent using this so it does work, but be cautious as this is still in beta_ + -Simple transactions require you to have networking enabled (cycle the toggle if its not on) in order to connect to a explorer. This is required because simple transactions do all of the heavy lifting for you. -To run a simple transaction go to the transaction tab, then click load transactions (make sure that you have imported or generated a wallet otherwise it won't work.). Then simple put in the wallet address you want to send the coins to and the amount, everything else will be calculated for you (for example, the change address and fees). You will then see the whole signed transaction displayed. You can check that this transaction is what you want by taking the signed transaction and putting in into a software wallet with the command decoderawtransaction. If you feel comfortable with it feel free to send it via explorer or by pressing the button on the site. +## Be your own Bank -##### Advanced Transaction -*The current setup only allows one input transaction and two output transactions if you need more inputs use the simple transaction for now. This will be changed in future updates.* +MPW is a completely [non-custodial](https://www.bitcoin.com/get-started/custodial-non-custodial-bitcoin-wallets/), client-side system, giving you absolute control over your funds, data, bandwidth and privacy... all of which are essential to easily-attainable **self-sovereignty**. -Advanced transactions do not require network access you can create the whole transaction then send the signed transaction on any node/wallet/explorer it takes a little knowledge of how transactions work in bitcoin to understand how to use the create transaction page. I will briefly go over what needs to be done, if you are unsure I recommend doing more research and testing with small amounts in order to not lose funds. How this works is it takes the inputs from the previous transaction (the one that funded the wallet.) and it make a new transaction that funds other wallets instead. Here is how you do this. We are going to be using one of my transaction in order to understand how this works. You can follow along here: https://explorer.dogec.io/tx/f52fad9c89a5a71532632679dc6cef84e6f7be949925d9190d054457052a61ef Under the raw transactions section you to put the top Transaction ID (txid) where it says Trx Hash, In our example it would be "f52fad9c89a5a71532632679dc6cef84e6f7be949925d9190d054457052a61ef". The next step would be to figure out which part of the transaction funded your public key, this is put into the index field. you can find this based on the vout under the vin section. In this example it would be 1. For the script field you need to put in the hex scriptPubKey of that VOUT with the same value under the VOUT section in Raw Transaction. In our example that would be 76a9142a8248f72e7ca9250f837b6cec46aedd6cf1edb288ac . Now the easy part under outputs you need to put in the address you want to send coins to and a change address. The change address is used for any extra coins currently associated with the account that you don't want to go to fees in most cases this would be your public key. In our example I have 1 DOGEC in my public address I wish to send 0.99 DOGEC to my friend at the address of DQJ24v6oFsobif8MQ6JFuFk6vefGAUQ6f2 . Then I set the change address( which is just my public address) D91rzgEmTyUcPEMPBLLPHVoKjSzwUreeoy with the amount 0.009 (Any money in this transaction not allocated will be used as fees and lost!) which means that the fee for this transaction is 0.001 . Under WIF key you put you private key in WIF (Wallet Import Format) which if you used the keypair generator it already is. You can see the end result of my transaction here https://explorer.dogec.io/tx/c445a56c5236a6665f88d3fda012e84778588b9a923f3e13d77927313070b14e +
-#### NETWORK DATA TAB -This show users what the explorer see in association with the public key +--- -#### SETTINGS TAB -##### Explorer -_Note for devs if you want this to connect to your explorer you must set the CORS header to all, otherwise local users won't be able to connect to your explorer_ +
-This is where you can change the explorer this currently is only set up for explorer.dogec.io which is the main current explorer. It is best to currently not mess with this setting as it will be developed more in the future. + -##### Toggles -###### Debug Mode -Debug mode sets some things mainly for testing do not use this if you are using this as a user. It will make wallet generation insecure and some other problems if you are meaning to use the site normally. +## Universal and Portable -###### Networking mode -This turns on and off the networking functions of the script. If you truly want privacy and security run this on a offline computer but this should be reasonably secure. With this turned off the script doesn't have access to any networking parts meaning anything that connects to a explorer or outside server doesn't work. +MPW is completely universal and portable, at both a user-experience level & protocol-level, MPW is interopable with much of the functionality within PIVX, while also being portable enough to run on almost **any device in the world**. -#### BETA **_PROCEED WITH CAUTION, DO STORE LARGE AMOUNTS OF FUNDS_** +
+--- + +
+ + + +## Don't trust, Verify! + +MPW is completely free, open-source software ([FOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software)), with absolute transparency in security, features, down to every letter of code.
+Alongside total codebase availability, MPW allows you to totally bunker-down, with the ability to customize your experience in accordance to your [principles](https://en.wikipedia.org/wiki/Free_and_open-source_software#Four_essential_freedoms_of_Free_Software) & security desires. + +
+ +--- + +
+ + + +## By the Community, for the Community + +MPW is built with love by [PIVX Labs](https://github.com/PIVX-Labs), a micro-DAO of [awesome people](https://github.com/PIVX-Labs/MyPIVXWallet/graphs/contributors) that build both FOSS and Proprietary software for the PIVX community to enjoy. + +The mission of PIVX Labs is to accelerate the adoption & growth of PIVX as a currency, using awesomeness. Join the [PIVX Labs Discord](https://discord.gg/v57eCP4MMx) to meet us! + +
+ +--- diff --git a/assets/bootstrap-4.5.0/css/bootstrap.min.css b/assets/bootstrap-4.5.0/css/bootstrap.min.css deleted file mode 100644 index 7d2a868f5..000000000 --- a/assets/bootstrap-4.5.0/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.5.0 (https://getbootstrap.com/) - * Copyright 2011-2020 The Bootstrap Authors - * Copyright 2011-2020 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item{display:-ms-flexbox;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/assets/bootstrap-4.5.0/css/bootstrap.min.css.map b/assets/bootstrap-4.5.0/css/bootstrap.min.css.map deleted file mode 100644 index e6bc16cbf..000000000 --- a/assets/bootstrap-4.5.0/css/bootstrap.min.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","bootstrap.css","../../scss/mixins/_hover.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/mixins/_border-radius.scss","../../scss/_code.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/mixins/_breakpoints.scss","../../scss/mixins/_grid-framework.scss","../../scss/_tables.scss","../../scss/mixins/_table-row.scss","../../scss/_forms.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_forms.scss","../../scss/mixins/_gradients.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/mixins/_nav-divider.scss","../../scss/_button-group.scss","../../scss/_input-group.scss","../../scss/_custom-forms.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/mixins/_badge.scss","../../scss/_jumbotron.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_media.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/utilities/_align.scss","../../scss/mixins/_background-variant.scss","../../scss/utilities/_background.scss","../../scss/utilities/_borders.scss","../../scss/utilities/_display.scss","../../scss/utilities/_embed.scss","../../scss/utilities/_flex.scss","../../scss/utilities/_float.scss","../../scss/utilities/_interactions.scss","../../scss/utilities/_overflow.scss","../../scss/utilities/_position.scss","../../scss/utilities/_screenreaders.scss","../../scss/mixins/_screen-reader.scss","../../scss/utilities/_shadows.scss","../../scss/utilities/_sizing.scss","../../scss/utilities/_spacing.scss","../../scss/utilities/_stretched-link.scss","../../scss/utilities/_text.scss","../../scss/mixins/_text-truncate.scss","../../scss/mixins/_text-emphasis.scss","../../scss/mixins/_text-hide.scss","../../scss/utilities/_visibility.scss","../../scss/_print.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v4.5.0 (https://getbootstrap.com/)\n * Copyright 2011-2020 The Bootstrap Authors\n * Copyright 2011-2020 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"code\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"input-group\";\n@import \"custom-forms\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"jumbotron\";\n@import \"alert\";\n@import \"progress\";\n@import \"media\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"utilities\";\n@import \"print\";\n","// Do not forget to update getting-started/theming.md!\n:root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$color}: #{$value};\n }\n\n @each $bp, $value in $grid-breakpoints {\n --breakpoint-#{$bp}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --font-family-sans-serif: #{inspect($font-family-sans-serif)};\n --font-family-monospace: #{inspect($font-family-monospace)};\n}\n","// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -webkit-tap-highlight-color: rgba($black, 0); // 5\n}\n\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\n// TODO: remove in v5\n// stylelint-disable-next-line selector-list-comma-newline-after\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use\n// the `inherit` value on things like `` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n @include font-size($font-size-base);\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Future-proof rule: in browsers that support :focus-visible, suppress the focus outline\n// on elements that programmatically receive focus but wouldn't normally show a visible\n// focus outline. In general, this would mean that the outline is only applied if the\n// interaction that led to the element receiving programmatic focus was a keyboard interaction,\n// or the browser has somehow determined that the user is primarily a keyboard user and/or\n// wants focus outlines to always be presented.\n//\n// See https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible\n// and https://developer.paciellogroup.com/blog/2018/03/focus-visible-and-backwards-compatibility/\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable-next-line selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Remove the bottom border in Firefox 39-.\n// 5. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-original-title] { // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 4\n text-decoration-skip-ink: none; // 5\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n\nsmall {\n @include font-size(80%); // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n @include font-size(75%);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n\n @include hover() {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]) {\n color: inherit;\n text-decoration: none;\n\n @include hover() {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-monospace;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n // Disable auto-hiding scrollbar in IE & legacy Edge to avoid overlap,\n // making it impossible to interact with the content\n -ms-overflow-style: scrollbar;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg {\n // Workaround for the SVG overflow bug in IE10/11 is still required.\n // See https://github.com/twbs/bootstrap/issues/26878\n overflow: hidden;\n vertical-align: middle;\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $table-caption-color;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `` alignment by inheriting from the ``, or the\n // closest parent with a set `text-align`.\n text-align: inherit;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: $label-margin-bottom;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n // stylelint-disable-next-line property-blacklist\n border-radius: 0;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// Set the cursor for non-` -

- -
- - -
-
-
-
-
-
-
-
-
-
-
-

Welcome to the StakeCube Web wallet!

-
-

A wallet for everyone!

-
- This web wallet is designed to be able to run on any computer with a modern browser, from a Raspberry Pi - to gaming desktop. - This wallet can do it! It has also been designed to generate wallets and create transactions without a - network connection. - Make sure when you run this wallet to run it from a trusted source such as stakecubecoin.net / stakecube.net or download it from - the Github and run it on your computer. - We tryed to make the user interface as friendly as possible but in the event that you don't understand - something feel free to check out the tutorials at the github - or create a issue on that github and we will get to it as fast as possible. -
-

Generate Paper wallets

-
- This web wallet functions much like a paper wallet generator, but with features most normal wallets - have. You can also generate transactions offline! - Head over to the wallet tab and generate your first wallet. Make sure to keep the key secure! otherwise - you may lose all your coins. -
-
-
-

Generate SCC wallet

- -
- -
-
-
- - -
-
- -
-
- -
- -
-
-
-
- -
-
-
-
-
-
-
-
Create Simple Transactions
- - -
-
-
-
-
Create Manual Transactions
- -
-
-
-
- - -
- -
-
- - -
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/index.template.html b/index.template.html new file mode 100644 index 000000000..fe6381a0f --- /dev/null +++ b/index.template.html @@ -0,0 +1,634 @@ + + + + + + + + + My PIVX Wallet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + +
+ + + + + + +
+
+
+
+ +
+

+ My PIVX Wallet is +
Loading
+

+
+ +
+ +
+
+
+
+ Vote on Governance +

Proposals

+

From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote!

+
+ + +
+
+ Monthly Budget + +
+ - PIV +
+ - +
+
+
+ Budget Allocated + +
+ - PIV +
+ - +
+
+
+ Next Treasury Payout +
+
+
+ Budget Allocated + +
+ - PIV +
+ - +
+
+
+ + +
+ +
+ + + + + + + + + +
Status Name Payment Votes Vote
+
+ +
+
+

Contested Proposals

+

These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal.

+
+ + + + + + + + + +
STATUS NAME PAYMENT VOTES VOTE
+
+
+ +
+
+

4 Masternodes Configured

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatusIP AddressLast Seen
ENABLED127.0.0.1:1234527/02/24 + + +
MISSING127.0.0.1:1234527/02/24Btns
PRE_ENABLED127.0.0.1:1234527/02/24Btns
+
+
+
+ +
+ +
+ + +
+ + Reward History + + 0.00 tPIV + + +
+ + + + + + + + + + + +
TimeIDAmount
+
+
+
+ +
+
+

+ Control your +

+

Masternode

+

From this tab you can create and access one or more masternodes

+
+ +
+
+

+
+ + + + +
+ +
+ +
+ +
+
+

Status

+

+
+
+

+

+
+
+

+ Last Seen +

+

+
+
+ +
+ + +
+
+ +
+
+
+
    +
  • Wallet
  • +
  • Display
  • +
+ +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + + +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ + +

This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users!

+
+ + +
+
+ + +
+ +
+ +
+ +
+ +
+ + +

Price data provided by Labs Oracle

+ +
+ + (-) +
+ +
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ + diff --git a/locale/cnr/translation.toml b/locale/cnr/translation.toml new file mode 100644 index 000000000..90d2c3514 --- /dev/null +++ b/locale/cnr/translation.toml @@ -0,0 +1,335 @@ +amount = "Količina" # Amount +staking = "Staking" # Staking +wallet = "Novčanik" # Wallet +display = "Prikaži" # Display +activity = "Aktivnost" # Activity +yes = "Da" # Yes +no = "Ne" # No +navDashboard = "Kontrolna tabla" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Glavni čvor" # Masternode +navGovernance = "Vladanje" # Governance +navSettings = "Podešavanja" # Settings +footerBuiltWithPivxLabs = "Napravljeno sa 💜 od strane PIVX Labs" # Built with 💜 by PIVX Labs +loading = "Učitavanje" # Loading +loadingTitle = "Moj PIVX Novčanik je" # My PIVX Wallet is +dashboardTitle = "Kontrolna tabla" # Dashboard +dCardOneTitle = "Napravi" # Create a +dCardOneSubTitle = "Novi Novčanik" # New Wallet +dCardOneDesc = "Napravi novi PIVX novčanik, koji nudi najbezbjedniju sigurnosnu kopiju i sigurnosne metode." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Napravi Novi Novčanik" # Create A New Wallet +dCardTwoTitle = "Napravi novi" # Create a new +dCardTwoSubTitle = "Personalizovani Novčanik" # Vanity Wallet +dCardTwoDesc = "Napravi novčanik sa personalizovanim prefiksom, za ovo može biti potrebno puno vremena!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Napravi Personalizovani Novčanik" # Create A Vanity Wallet +dCardThreeTitle = "Pristupi tvom" # Access your +dCardThreeSubTitle = "Ledger Novčaniku" # Ledger Wallet +dCardThreeDesc = "Koristi svoj Ledger Hardver novčanik sa MPW - ovim poznatim interfejsom." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Pristupi svom Ledger novčaniku" # Access my Ledger +dCardFourTitle = "Idi do" # Go to +dCardFourSubTitle = "Mog novčanika" # My Wallet +dCardFourDesc = "Uvezi PIVX novčanik koristeći Privatan ključ, xpriv, ili Seed frazu." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Uvezi Novčanik" # Import Wallet +dCardFourButtonA = "Pristupi Mom Novčaniku" # Access My Wallet +vanityPrefixNote = "Podsetnik: adrese će uvijek početi sa:" # Note: addresses will always start with: +vanityPrefixInput = "Adresni prefiks" # Address Prefix +thisIsYourSeed = "Ovo je Vaša seed fraza:" # This is your seed phrase: +writeDownSeed = "Zapišite je negdje. Vidjećete ovo samo jednom!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Svako sa njenom kopijom će moći da pristupi svim Vašim finansijskim sredstvima." # Anyone with a copy of it can access all of your funds. +doNotShare = "NE dijelite je sa sa bilo kim." # Do NOT share it with anyone. +digitalStoreNotAdvised = "NIJE preporučeno da se ovo skladišti digitalno." # It is NOT advised to store this digitally. +optionalPassphrase = "Opcionalna Lozinka (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Zapisao/la sam svoju seed frazu." # I have written down my seed phrase +importSeedValid = "Seed fraza je validna!" # Seed Phrase is valid! +importSeedError = "Seed fraza nije validna!" # Seed Phrase is invalid! +importSeedErrorSize = "Seed fraza mora biti 12 ili 13 riječi duga!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Seed fraza sadrži nepravilnosti u pisanju!Pažljivo provjerite svoj unos!" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Izgleda da seed fraza nije validna,ali korisnik nije obratio pažnju na upozorenje." # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Počinjemo" # Getting Started +secureYourWallet = "Zaštitite svoj novčanik" # Secure your wallet +unlockWallet = "Otključajte Novčanik" # Unlock wallet +lockWallet = "Zaključajte novčanik" # Lock wallet +encryptWallet = "Enkriptujte novčanik" # Encrypt wallet +encryptPasswordCurrent = "Trenutna lozinka" # Current Password +encryptPasswordFirst = "Unesite lozinku" # Enter Password +encryptPasswordSecond = "Ponovo unesite lozinku" # Re-enter Password +encrypt = "Enkriptujte" # Encrypt +changePassword = "Promijenite lozinku" # Change Password +balanceBreakdown = "Izvod stanja" # Balance Breakdown +viewOnExplorer = "Pogledajte na Explorer-u" # View on Explorer +export = "Izvezi" # Export +refreshAddress = "Osveži adresu" # Refresh address +redeemOrCreateCode = "Unovči ili Napravi Kod" # Redeem or Create Code +address = "Adresa" # Address +receivingAddress = "Primajuća adresa" # Receiving address +sendAmountCoinsMax = "MAKSIMUM" # MAX +paymentRequestMessage = "Opis (od trgovca)" # Description (from the merchant) +send = "Pošalji" # Send +receive = "Primi" # Receive +contacts = "Kontakti" # Contacts +name = "Ime" # Name +username = "Korisničko ime" # Username +addressOrXPub = "Adresa ili XPub" # Address or XPub +back = "Nayad" # Back +chooseAContact = "Izaberi Kontakt" # Choose a Contact +createContact = "Napravi Kontakt" # Create Contact +encryptFirstForContacts = "Kada pritisnete \"{button}\" u Kontrolnoj tabli, možete napraviti Kontakt da bi mogli lakše da primate PIV!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Podijeli Kontakt URL" # Share Contact URL +setupYourContact = "Podesite svoj Kontakt" # Setup your Contact +receiveWithContact = "Primajte koristeći jednostavan Kontakt na osnovu Korisničkog imena" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Samo sa svojim najpuzdanijim poznanicima dijelite svoj Kontakt (porodica, prijatelji)" # Only share your Contact with trusted people (family, friends) +changeTo = "Promijeni u" # Change to +contact = "Kontakt" # Contact +xpub = "XPub" # XPub +addContactTitle = "Dodaj {strName} u Kontakte" # Add {strName} to Contacts +addContactSubtext = "Čim ih dodate, moći ćete slati transakcije ka {strName} uz pomoć njihovog imena (bilo kucanjem ili klikom), bez potrebe za adresama, lako i jednostavno." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Provjerite da li je ovo stvarno \"{strName}\", ne prihvatajte Kontakt zahtjeve od nepoznatih izvora!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Promijenite \"{strName}\" Kontakt" # Change "{strName}" Contact +newName = "Novo Ime" # New Name +removeContactTitle = "Uklonite {strName}?" # Remove {strName}? +removeContactSubtext = "Da li ste sigurni da želite ukloniti {strName} iz Vaših Kontakata?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Možete ih uvijek ponovo dodati bilo kada u budućnosti." # You can add them again any time in the future. +privateKey = "Privatan Ključ" # Private Key +viewPrivateKey = "Prikažite Privatan Ključ?" # View Private Key? +privateWarning1 = "Potrudite se da Vam niko ne može vidjeti ekran." # Make sure no one can see your screen. +privateWarning2 = "Bilo ko sa ovim ključem Vam može ukrasti sva sredstva." # Anyone with this key can steal your funds. +viewKey = "Prikaži ključ" # View key +pivxPromos = "je decentralizovan sistem za poklon kodove, vrijedni PIV-a" # is a decentralised system for gift codes worth PIV +redeemInput = "Unesite svoj 'PIVX Promos' kod" # Enter your 'PIVX Promos' code +createName = "Promo Ime (Opcionalno)" # Promo Name (Optional) +createAmount = "Promo Količina" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "(Opcionalno) Adresa vlasnika" # (Optional) Owner Address +rewardHistory = "Istorija nagrada" # Reward History +loadMore = "Učitajte više" # Load more +mnControlYour = "Kontrolišite svoj" # Control your +mnSubtext = "Iz ovog tab-a možete kreirati i pristupiti jednom ili više glavnih čvorova." # From this tab you can create and access one or more masternodes +govSubtext = "Iz ovog taba možete pregledati predloge i, ako imate glavni čvor, biti deo DAO-a i glasati!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Mjesečni Budžet" # Monthly Budget +govAllocBudget = "Alociran Budžet" # Allocated Budget +govNextPayout = "Sledeća Trezorna Isplata" # Next Treasury Payout +govTableStatus = "STATUS" # STATUS +govTableName = "IME" # NAME +govTablePayment = "PLAĆANJE" # PAYMENT +govTableVotes = "GLASOVI" # VOTES +govTableVote = "GLASAJ" # VOTE +contestedProposalsTitle = "Sporni Predlozi" # Contested Proposals +contestedProposalsDesc = "Ovo su predlozi koji su dobili pretežan broj negativnih glasova, što ih čini verovatnim spamom ili visoko spornim prijedlozima." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Izaberite prikazanu valutu:" # Choose a display currency: +priceProvidedBy = "Podaci o ceni pruženi su od strane" # Price data provided by +settingsDecimals = "Decimali stanja:" # Balance Decimals: +settingsExplorer = "Izaberite explorer:" # Choose an explorer: +settingsLanguage = "Izaberite Jezik:" # Choose a Language: +settingsPivxNode = "Izaberite PIVX čvor:" # Choose a PIVX node: +settingsAutoSelectNet = "Automatski izaberite Explorere i Čvorove" # Auto-select Explorers and Nodes +settingsAnalytics = "Izaberite nivo vašeg doprinosa analitici:" # Choose your analytics contribution level: +settingsToggleDebug = "Režim za otklanjanje grešaka" # Debug Mode +settingsToggleTestnet = "Režim testne mreže" # Testnet Mode +settingsToggleAdvancedMode = "Napredni režim" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Ovo otključava dublje funkcionalnosti i prilagodljivost, ali može biti preopterećujuće i potencijalno opasno za neiskusne korisnike!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Vaš {network} novčanik nije sačuvan!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Vaš {network} nalog je u opasnosti!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Ako pređete na {network} pre nego što ga sačuvate, izgubićete nalog!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Da li ste zaista sigurni?" # Are you really sure? +transparencyReport = "Izveštaj o transparentnosti" # Transparency Report +hit = "Ping koji označava učitavanje aplikacije, ne salju se nikakvi jedinstveni podatci." # A ping indicating an app load, no unique data is sent. +time_to_sync = "Vreme u sekundama koje je trajalo da se MPW poslednji put sinhronizuje." # The time in seconds it took for MPW to last synchronise. +transaction = "Ping koji označava Tx, ne salju se nikakvi jedinstveni podatci, ali se može zaključiti na osnovu vremena na lancu." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Isključeno" # Disabled +analyticMinimal = "Minimalno" # Minimal +analyticBalanced = "Balansirano" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Oporavak naloga nije uspijeo" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Došlo je do greške prilikom oporavka vašeg naloga.
Molimo vas da ponovo uvezete svoj novčanik koristeći sledeći ključ:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Vreme" # Time +description = "Opis" # Description +activityBlockReward = "Nagrada bloka" # Block Reward +activitySentTo = "Poslato {r}" # Sent to {r} +activitySelf = "sopstveno" # self +activityShieldedAddress = "Zaštićena adresa" # Shielded address +activityDelegatedTo = "Delegirano {r}" # Delegated to {r} +activityUndelegated = "Nedelegirano" # Undelegated +activityUnknown = "Nepoznat Tx" # Unknown Tx +password = "Lozinka" # Password +walletUnlock = "Otključajte svoj novčanik" # Unlock your wallet +walletPassword = "Lozinka novčanika" # Wallet password +walletUnlockCreateMN = "Otključajte da biste kreirali svoj glavni čvor!" # Unlock to create your Masternode! +walletUnlockMNStart = "Otključajte da biste započeli svoj glavni čvor!" # Unlock to start your Masternode! +walletUnlockProposal = "Otključajte da biste kreirali predlog!" # Unlock to create a proposal! +walletUnlockPromo = "Otključajte da biste finalizirali svoj Promo kod!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Otključajte da biste poslali transakciju!" # Unlock to send your transaction! +walletUnlockStake = "Otključajte da biste stake-ovali vaš" # Unlock to stake your +walletUnlockUnstake = "Otključajte da biste unstake-ovali iz" # Unlock to unstake your +changelogTitle = "Šta je novo u" # What's New in +popupSetColdAddr = "Postavite svoju adresu za Cold Staking" # Set your Cold Staking address +popupCurrentAddress = "Trenutna adresa:" # Current address: +popupColdStakeNote = "Adresa za Cold Staking ulaže novac umjesto vas, ne može trošiti novac, pa je čak sigurno koristiti Cold Staking adresu nepoznatih osoba!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Primjer:" # Example: +popupWalletLock = "Želite li zaključati svoj novčanik?" # Do you want to lock your wallet? +popupWalletWipe = "Želite li obrisati privatne podatke svog novčanika?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Moraćete uneti svoju lozinku da biste pristupili svojim sredstvima" # You will need to enter your password to access your funds +popupWalletWipeNote = "Izgubićete pristup svojim sredstvima ako niste napravili rezervnu kopiju svog privatnog ključa ili seed fraze" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Neočekivana seed fraza" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "Seed fraza je ili nevažeća ili nije generisana od strane MPW.
Da li i dalje želite nastaviti?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Kreirajte predlog" # Create Proposal +popupCreateProposalCost = "Trošak" # Cost +popupProposalName = "Ime predloga" # Proposal Name +popupProposalAddress = "Adresa predloga (Opcionalno)" # Proposal Address (Optional) +popupProposalDuration = "Trajanje u ciklusima" # Duration in cycles +popupProposalPerCycle = "po ciklusu" # per cycle +popupProposalEncryptFirst = "Morate pritisnuti „{button}“ pre nego što možete kreirati predloge!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Hash glasa:" # Vote Hash: +popupProposalFinalisedNote = "Čestitamo na pokretanju vašeg predloga!
Vlasnici glavnog čvora mogu koristiti vaš Hash glasa da glasaju iz novčanika koji nisu MPW, pa se pobrinite da ga dodate na svoj forumski post, ako je to primenljivo!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Srećno na vašem putovanju kroz DAO, PIViancu!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Molimo vas da potvrdite da je ovo adresa koju vidite na svom" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Potvrđivanje..." # Confirming... +proposalFinalisationRemaining = "preostalo" # remaining +proposalFinalisationExpired = "Predlog je istekao" # Proposal Expired +proposalFinalisationReady = "Spremno za slanje" # Ready to submit +proposalPassing = "Prolazi" # Passing +proposalFailing = "Ne prolazi" # Failing +proposalTooYoung = "Premlad" # Too Young +proposalFunded = "Finansiran" # Funded +proposalNotFunded = "Nije finansiran" # Not Funded +proposalPaymentsRemaining = "preostalih rata
od" # installment(s) remaining
of +proposalPaymentTotal = "ukupno" # total +proposalNetYes = "Neto Da" # Net Yes +popupConfirm = "Potvrdi" # Confirm +popupClose = "Zatvori" # Close +popupCancel = "Otkaži" # Cancel +chartPublicAvailable = "Javno dostupno" # Public Available +timeDays = "Dana" # Days +timeHours = "Sati" # Hours +timeMinutes = "Minuta" # Minutes +timeSeconds = "Sekundi" # Seconds +unhandledException = "Neočekivani izuzetak." # Unhandled exception. +syncStatusHistoryProgress = "Sinhronizacija istorijskih blokova {current} od {total}" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Vaš novčanik se sinhronizuje!
Moći ćete ga potpuno koristiti nakon završetka." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Završena sinhronizacija!
Vaš novčanik je spreman za upotrebu!" # Sync Finished!
Your wallet is ready to use! +activityReceivedWith = "Primljeno sa {s}" # Received with {s} +accountDeleted = "Vaš nalog je uspešno obrisan!" # Your account has been successfully deleted! +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +chartImmatureBalance = "" # Immature balance +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Interna greška, molimo pokušajte ponovo kasnije" # Internal error, please try again later +FAILED_TO_IMPORT = "Neuspešan uvoz! Neispravna lozinka" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = "Neuspešan uvoz Hardverskog Novčanika." # Failed to import Hardware Wallet. +TESTNET_ENCRYPTION_DISABLED = "Režim testne mreže je UKLJUČEN!
Enkripcija novčanika je onemogućena" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Ta lozinka je malo kratka!
Koristite najmanje {MIN_PASS_LENGTH} karaktera." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Vaše lozinke se ne podudaraju!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Vi ste osigurani! 🔐
Svaka čast, Oklopljeni PIVianu!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Pogrešna lozinka!" # Incorrect password! +INVALID_AMOUNT = "Neispravan iznos!
" # Invalid amount!
+TX_SENT = "Transakcija je poslata!" # Transaction sent! +TX_FAILED = "Neuspješna transakcija!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "nije validan primalac plaćanja" # is not a valid payment receiver +UNSUPPORTED_CHARACTER = "Karakter '{char}' nije podržan u adresama! (Nije kompatibilan sa Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Ovaj pretraživač ne podržava Web Workers (višenitni JS), nažalost ne možete generisati Personalizovane novčanike!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Neispravna PIVX adresa!
{address}" # Invalid PIVX address!
{address} +VALIDATE_AMOUNT_LOW = "
Minimalni iznos je {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} decimalno prekoračenje" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Staking adresa je postavljena!
Sada možete unstake-ovati!" # Staking Address set!
Now go ahead and unstake! +CONFIRM_UNSTAKE_H_WALLET = "Potvrdite vaše unstake-ovanje
Potvrdite TX na svom {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Potvrdite vašu transakciju
Potvrdite TX na svom {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Staking adresa je postavljena!
Sada možete započeti staking!" # Staking Address set!
Now go ahead and stake! +STAKE_ADDR_SET = "Cold adresa je postavljena!
Budući stake-ovi će koristiti ovu adresu." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Neispravna Cold stake adresa!" # Invalid Cold Staking address! +STAKE_NOT_SEND = "Ovdje koristite Stake ekran, a ne ekran za slanje!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Neispravna PIVX adresa!
Loša dužina ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Neispravna PIVX adresa!
Loš prefiks {address} (Treba početi sa {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Ne možete poslati 'ništa'!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Sačuvajte svoj novčanik!
Kontrolna tabla ➜ Osigurajte svoj novčanik" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Molimo vas da ŠIFRUJETE i/ili NAPRAVITE REZERVNU KOPJU vaših ključeva pre nego što odete, ili biste ih mogli izgubiti!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Ovaj uređaj nema kameru!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger nije podržan za Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Neuspela sinhronizacija! Molimo pokušajte ponovo kasnije.
Možete pokušati ponovno povezivanje putem Podešavanja." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Vaš glavni čvor još uvek nije omogućen!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Glas je poslat!" # Vote submitted! +VOTED_ALREADY = "Već ste glasali za ovaj predlog! Molimo sačekajte 1 sat" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Nije uspjelo provjeravanje potpisa, molimo provjerite privatni ključ svog glavnog čvora" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Glavni čvor je kreiran!
Sačekajte 15 potvrda da biste nastavili" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Pristupite glavnom čvoru pre glasanja!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Vaš glavni čvor je van mreže, pokušaćemo da ga pokrenemo" # Your masternode is offline, we will try to start it +MN_STARTED = "Glavni čvor je pokrenut!" # Masternode started! +MN_RESTARTED = "Glavni čvor je restartovan!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Glavni čvor je pokrenut!
Uskoro će biti dostupan na mreži" # Masternode started!
It'll be online soon +MN_START_FAILED = "Glavni čvor je pokrenut!" # Masternode started! +MN_RESTART_FAILED = "Glavni čvor je ponovo pokrenut!" # Masternode restarted! +MN_DESTROYED = "Glavni čvor je uništen!
Vaše kovanice sada mogu biti potrošene." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Status vašeg glavnog čvora je" # Your masternode status is +MN_STATE = "Vaš glavni čvor je u {state} stanju" # Your masternode is in {state} state +MN_BAD_IP = "IP adresa nije validna!" # The IP address is invalid! +MN_BAD_PRIVKEY = "Privatni ključ nije validan" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Potrebno vam je još {amount} {ticker} da biste kreirali glavni čvor!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Imate dovoljno sredstava za glavni kluč, ali nemate važeći UTXO kolateral od {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Ovo nije pogodan UTXO za glavni ključ" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Nije moguće povezivanje sa RPC čvorom!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Morate pritisnuti dugme „{button}“ pre nego što možete koristiti kontakte!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Potrebno je unijeti ime!" # A name is required! +CONTACTS_NAME_TOO_LONG = "To ime je predugo!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Ne možete dodati sebe kao kontakt!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Kontakt već postoji!
Već ste sačuvali ovaj kontakt" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Ime kontakta već postoji!
Ovo može biti potencijalni pokušaj prevaranta, oprez!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Kontakt već postoji!
Kontakt je već nazvan kao „{strNewName}“!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Kontakt već postoji, ali pod drugim imenom!
Imate {newName} sačuvano kao {oldName} u svojim kontaktima" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Ovo nije QR kod za kontakt!" # This isn't a Contact QR! +CONTACTS_ADDED = "Novi kontakt dodat!
{strName} je dodat, jupiii!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Nemate kontakte!" # You have no contacts! +PROPOSAL_FINALISED = "Predlog je pokrenut!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "Predlog još uvijek nije potvrđen" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "Predlog je istekao. Kreirajte novi." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Nije uspjelo završavanje predloga." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Kreirajte ili uvezite svoj novčanik da biste nastavili" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Nema dovoljno sredstava za kreiranje predloga." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "Predlog nije validan. Greška:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Predlog je kreiran!
Sačekajte potvrde, zatim finalizujte svoj predlog!" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Minimalni iznos je {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Vaš uređaj može kreirati samo {quantity} kodova odjednom!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Nemate dovoljno {ticker} da biste kreirali taj kod!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Već ste kreirali taj kod!" # You've already created that code! +SWITCHED_EXPLORERS = "Promijenili ste Explorer!
Sada koristite {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Promijenili ste čvor!
Sada koristite {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Promijenili ste nivo analitike!
Sada je {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Promijenili ste režim sinhronizacije!
Sada koristite sinhronizaciju {sync}" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Nemoguća je promijena režima testne mreže!
Novčanik je već učitan" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Režim van mreže je aktivan!
Molimo onemogućite režim van mreže za automatske transakcije" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Molimo {unlock} vaš novčanik pre slanja transakcija!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Firefox ovo ne podržava!
Nažalost, Firefox ne podržava hardverske novčanike" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Hardverski novčanik je spreman!
Molimo vas da držite svoj {hardwareWallet} priključen, otključan i u aplikaciji PIVX" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Potvrdite uvoz na vašem Ledger-u" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Nema dostupnog uređaja
Nije moguće pronaći hardverski novčanik; molimo vas da ga priključite i otključate!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "Operativni sistem je odbio pristup Da li ste dodali udev pravila?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "Operativni sistem je odbio pristup Molimo provjerite postavke vašeg operativnog sistema." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Izgubljena veza sa {hardwareWallet}
Izgleda da je {hardwareWallet} isključen tokom operacije, ups!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} čeka
Molimo otključajte svoj {hardwareWallet} ili završite trenutni zahtev" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Potvrdi glas" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Da li ste sigurni? Potrebno je 60 minuta da promenite glas" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Potvrdite vašu transakciju" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Vaš privatni ključ glavnog čvora" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Sačuvajte ovaj privatni ključ i kopirajte ga u konfiguraciju vašeg VPS-a
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Verifikujte vašu adresu" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Neuspešno oporavljanje vašeg glavnog čvora. Molimo vas da ga ponovo uvezete." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Neuspešno oporavljanje vašeg naloga. Molimo vas da ga ponovo uvezete." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "Aplikacija je instalirana!" # App Installed! +CONFIRM_POPUP_DELETE_ACCOUNT = "Ovo će izbrisati sve vaše podatke, uključujući glavne čvorove, kontakte i privatne ključeve!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Da li ste sigurni?" # Are you sure? +WALLET_NOT_SYNCED = "Molimo pokušajte ponovo kada novčanik završi sinhronizaciju!" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "Novčanik je uspešno zaključan!" # Wallet successfully Locked! +WALLET_UNLOCKED = "Novčanik je uspešno otključan!" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/de/translation.toml b/locale/de/translation.toml new file mode 100644 index 000000000..6c4c87d87 --- /dev/null +++ b/locale/de/translation.toml @@ -0,0 +1,335 @@ +amount = "Menge" # Amount +staking = "Staking" # Staking +wallet = "Geldbörse" # Wallet +display = "Anzeige" # Display +activity = "Aktivität" # Activity +yes = "Ja" # Yes +no = "Nein" # No +navDashboard = "Übersicht" # Dashboard +navStake = "Staking" # Stake +navMasternode = "Masternodes" # Masternode +navGovernance = "Regieren" # Governance +navSettings = "Einstellungen" # Settings +footerBuiltWithPivxLabs = "Mit 💜 erstellt - PIVX Labs 🇩🇪" # Built with 💜 by PIVX Labs +loading = "...lädt..." # Loading +loadingTitle = "Meine PIVX Geldbörse" # My PIVX Wallet is +dashboardTitle = "Übersicht" # Dashboard +dCardOneTitle = "Erstelle eine" # Create a +dCardOneSubTitle = "Neue Geldbörse" # New Wallet +dCardOneDesc = "Erstelle eine neue PIVX Geldbörse, unter verwendung neuester Sicherheits- & Backupmethoden." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Erstelle eine neue Geldbörse" # Create A New Wallet +dCardTwoTitle = "Erstelle eine neue" # Create a new +dCardTwoSubTitle = "Vanity Geldbörse" # Vanity Wallet +dCardTwoDesc = "Erstelle eine Geldbörse mit einem individuellen Präfix, dies kann lange dauern!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Erstelle eine Vanity Geldbörse" # Create A Vanity Wallet +dCardThreeTitle = "Öffne deine" # Access your +dCardThreeSubTitle = "Hardware Geldbörse" # Ledger Wallet +dCardThreeDesc = "Benutze deine Ledger Hardware Geldbörse mit MPW als benutzerfreundliche Schnittstelle." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Öffne meinen Ledger" # Access my Ledger +dCardFourTitle = "Gehe zu" # Go to +dCardFourSubTitle = "Meine Geldbörse" # My Wallet +dCardFourDesc = "Importiere eine PIVX Geldbörse mittels Private Key, xpriv oder einer Seed Phrase" # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Importiere Geldbörse" # Import Wallet +dCardFourButtonA = "Öffne meine Geldbörse" # Access My Wallet +vanityPrefixNote = "Hinweis: Adressen starten immer mit:" # Note: addresses will always start with: +vanityPrefixInput = "Adress-Prefix" # Address Prefix +thisIsYourSeed = "Dies ist deine Seed Phrase" # This is your seed phrase: +writeDownSeed = "Notiere diese an einem sicheren Ort - Sie wird nur einmal angezeigt!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Jeder mit einer Kopie der Phrase hat Zugriff auf deine Brieftasche." # Anyone with a copy of it can access all of your funds. +doNotShare = "Teile Sie unter keinen Umständen mit Dritten." # Do NOT share it with anyone. +digitalStoreNotAdvised = "Es wird empfohlen diese nicht digital zu sichern" # It is NOT advised to store this digitally. +optionalPassphrase = "Optionale Pass Phrase (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Ich bestätige, dass ich die Seed Phrase notiert habe" # I have written down my seed phrase +importSeedValid = "Seed Phrase ist gültig!" # Seed Phrase is valid! +importSeedError = "Seed Phrase ist ungültig!" # Seed Phrase is invalid! +importSeedErrorSize = "Eine Seed Phrase sollte zwischen 12 und 24 Zeichen lang sein!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Die Seed Phrase enthält fehlerhafte Eingaben! Überprüfe deine Eingaben sorgfältig" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Die Seed Phrase erscheint ungültig, die Warnung wurde durch den Benutzer übergangen." # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Erste Schritte" # Getting Started +secureYourWallet = "Verschlüssel deine Geldbörse" # Secure your wallet +unlockWallet = "Entsperre deine Geldbörse" # Unlock wallet +lockWallet = "Sperre deine Geldbörse" # Lock wallet +encryptWallet = "Verschlüssel deine Geldbörse" # Encrypt wallet +encryptPasswordCurrent = "Aktuelles Passwort" # Current Password +encryptPasswordFirst = "Gib dein Passwort ein" # Enter Password +encryptPasswordSecond = "Wiederhole dein Passwort" # Re-enter Password +encrypt = "Verschlüsseln" # Encrypt +changePassword = "Passwort ändern" # Change Password +balanceBreakdown = "Aufgeschlüsselter Kontostand" # Balance Breakdown +viewOnExplorer = "Zeige im Explorer" # View on Explorer +export = "Exportieren" # Export +refreshAddress = "Aktualisiere Adresse" # Refresh address +redeemOrCreateCode = "Erstelle Codes, oder löse Codes ein" # Redeem or Create Code +address = "Adresse" # Address +receivingAddress = "Empfänger Adresse" # Receiving address +sendAmountCoinsMax = "Maximal" # MAX +paymentRequestMessage = "Beschreibung (des Händlers)" # Description (from the merchant) +send = "Senden" # Send +receive = "Erhalten" # Receive +contacts = "Kontakte" # Contacts +name = "Name" # Name +username = "Benutzername" # Username +addressOrXPub = "Adresse oder XPub" # Address or XPub +back = "Zurück" # Back +chooseAContact = "Wähle einen Kontakt" # Choose a Contact +createContact = "Erstelle einen Kontakt" # Create Contact +encryptFirstForContacts = "Nachdem du auf \"{button}\" in der Übersicht gedrückt hast, kannst du Kontakte erstellen um das Senden und Empfangen von PIV einfacher zu machen." # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Teile deine Kontakt-URL" # Share Contact URL +setupYourContact = "Erstelle deinen Kontakt" # Setup your Contact +receiveWithContact = "Empfangen mittels einfachen Benutzernamens" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Teile deine Kontakte nur mit vertrauenswürdigen Personen (Familie, Freunde)" # Only share your Contact with trusted people (family, friends) +changeTo = "Ändere zu" # Change to +contact = "Kontakt" # Contact +xpub = "XPub" # XPub +addContactTitle = "Füge {strName} zu Kontakten zu" # Add {strName} to Contacts +addContactSubtext = "Einmal hinzugefügt kannst du Transaktionen zu {strName} über den Kontakt (eingeben oder anklicken) erledigen. Keine komplexen Adressen mehr - einfach einfach!" # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Bitte prüfe ob die Echtheit von \"{strName}\". Niemals Kontakt-Anfragen von unbekannten annehmen!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Ändere Kontakt \"{strName}\"" # Change "{strName}" Contact +newName = "Neuer Name" # New Name +removeContactTitle = "Entferne {strName}?" # Remove {strName}? +removeContactSubtext = "Willst du wirklich {strName} aus deinen Kontakten entfernen?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Du kannst den Kontakt später zu jeder Zeit wieder hinzufügen." # You can add them again any time in the future. +privateKey = "Privater Schlüssel" # Private Key +viewPrivateKey = "Zeige privaten Schlüssel" # View Private Key? +privateWarning1 = "Stelle sicher, dass niemand deinen Bildschirm einsehen kann" # Make sure no one can see your screen. +privateWarning2 = "Jeder mit diesem Schlüssel kann deine Geldbörse bedienen." # Anyone with this key can steal your funds. +viewKey = "Zeige Schlüssel" # View key +pivxPromos = "ist ein dezentrales System für PIVX Geschenk-Gutscheine" # is a decentralised system for gift codes worth PIV +redeemInput = "Gib deinen PIVX Promos Code ein" # Enter your 'PIVX Promos' code +createName = "Promo Name (Optional)" # Promo Name (Optional) +createAmount = "Promo Anzahl" # Promo Amount +stake = "Delegieren" # Stake +stakeUnstake = "Abberufen" # Unstake +ownerAddress = "(Optional) Adresse des Besitzers" # (Optional) Owner Address +rewardHistory = "Vergütungshistorie" # Reward History +loadMore = "Zeige mehr" # Load more +mnControlYour = "Verwalte deine" # Control your +mnSubtext = "Auf diesem Reiter kannst du eine oder mehrere Masternodes erstellen und verwalten" # From this tab you can create and access one or more masternodes +govSubtext = "Auf diesem Reiter kannst du aktuelle Anträge prüfen und, sofern du eine Masternode besitzt, teil der DAO sein und Abstimmen!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Monatliches Budget" # Monthly Budget +govAllocBudget = "Verteiltes Budget" # Allocated Budget +govNextPayout = "Nächste Auszahlung des Budgets" # Next Treasury Payout +govTableStatus = "Status" # STATUS +govTableName = "Name" # NAME +govTablePayment = "Modalitäten" # PAYMENT +govTableVotes = "Stimmen" # VOTES +govTableVote = "Stimme" # VOTE +contestedProposalsDesc = "Dies sind Anträge, welche aufgrund der erdrückend negativen Abstimmung wahrscheinlich SPAM oder aber zumindest fragwürdig sind." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Wähle die angezeigte Währung" # Choose a display currency: +priceProvidedBy = "Preisinformationen von" # Price data provided by +settingsDecimals = "Nachkommastellen" # Balance Decimals: +settingsExplorer = "Wähle einen Explorer:" # Choose an explorer: +settingsLanguage = "Wähle eine Sprache:" # Choose a Language: +settingsPivxNode = "Wähle einen PIVX Knoten:" # Choose a PIVX node: +settingsAutoSelectNet = "Wähle Explorer und Knoten automatisch" # Auto-select Explorers and Nodes +settingsAnalytics = "Wähle die verwendeten Analysedaten dieser Sitzung:" # Choose your analytics contribution level: +settingsToggleDebug = "Debug Modus" # Debug Mode +settingsToggleTestnet = "Testnet Modus" # Testnet Mode +settingsToggleAdvancedMode = "Erweiterte Ansicht" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Dies aktiviert tiefere Funktionen und Anpassungen, welche für unerfahrene Nutzer potentiell gefährlich sein können." # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +transparencyReport = "Transparenz-Bericht" # Transparency Report +hit = "Ein Ping, welcher das laden der APP darstellt. Keine spezifischen Daten werden gesendet." # A ping indicating an app load, no unique data is sent. +time_to_sync = "Die Zeit in Sekunden, die MPW für den letzten Abgleich gebraucht hat" # The time in seconds it took for MPW to last synchronise. +transaction = "Ein Ping, welcher eine Transaktion darstellt. Keine spezifischen werden gesendet, könnten aber durch Informationen der Blockchain geschlussfolgert werden." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Deaktiviert" # Disabled +analyticMinimal = "Minimal" # Minimal +analyticBalanced = "Ausgewogen" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Wiederherstellen des Accouts fehlgeschlagen" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Ein Fehler ist während der Wiederherstellung aufgetreten.
Bitte importiere deine Geldbörse mit dem folgenden Schlüssel erneut:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Zeit" # Time +description = "Beschreibung" # Description +activityBlockReward = "Block-Erlös" # Block Reward +activitySentTo = "Gesendet an {r}" # Sent to {r} +activityShieldedAddress = "Geschützte Adresse" # Shielded address +activityDelegatedTo = "Delegiert an {r}" # Delegated to {r} +activityUndelegated = "Abberufen" # Undelegated +activityUnknown = "Unbekannte Trasaktion" # Unknown Tx +password = "Passwort" # Password +walletUnlock = "Entsperre deine Geldbörse" # Unlock your wallet +walletPassword = "Password der Geldbörse" # Wallet password +walletUnlockCreateMN = "Entsperren um Masternode zu erstellen!" # Unlock to create your Masternode! +walletUnlockMNStart = "Entsperren um Masternode zu starten!" # Unlock to start your Masternode! +walletUnlockProposal = "Entsperre um einen Antrag zu erstellen!" # Unlock to create a proposal! +walletUnlockPromo = "Entsperre um die Promo Codes zu finalisieren!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Entsperre um die Transaktion abzusenden!" # Unlock to send your transaction! +walletUnlockStake = "Entsperre um zu Staken" # Unlock to stake your +walletUnlockUnstake = "Entsperre um abzuberufen" # Unlock to unstake your +changelogTitle = "Was gibt es neues bei" # What's New in +popupSetColdAddr = "Gib eine kalte Staking Adresse an" # Set your Cold Staking address +popupCurrentAddress = "Aktuelle Adresse" # Current address: +popupColdStakeNote = "Eine kalte Staking Adresse delegiert Münzen für dich. Sie kann nicht für Transaktionen verwendet werden, so kannst du auch fremde kalte Adressen verwenden!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Beispiel" # Example: +popupWalletLock = "Möchtest du deine Geldbörse sperren?" # Do you want to lock your wallet? +popupWalletWipe = "Möchtest du die persönlichen Daten deiner Geldbörse löschen?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Du benötigst anschließend dein Passwort um auf deine Gelder zuzugreifen" # You will need to enter your password to access your funds +popupWalletWipeNote = "Du wirst den Zugriff auf deine Gelder verlieren, wenn du deinen privaten Schlüssel oder die Seed Phrase nicht gesichert hast!" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Unerwartete Seed Phrase" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "Die Seed Phrase ist entweder ungültig oder wurde nicht durch MPW erstellt.
Willst du trotzdem fortfahren?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Erstelle Antrag" # Create Proposal +popupCreateProposalCost = "Kosten" # Cost +popupProposalName = "Name des Antrags" # Proposal Name +popupProposalAddress = "Adresse des Antrags (Optional)" # Proposal Address (Optional) +popupProposalDuration = "Dauer in Zyklen" # Duration in cycles +popupProposalPerCycle = "pro Zyklus" # per cycle +popupProposalEncryptFirst = "Du musst den \"{button}\" drücken bevor du einen Antrag erstellen kannst!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Hash zum Wählen" # Vote Hash: +popupProposalFinalisedNote = "Glückwünsche zum einreichen des Antrags!
Besitzer einer Masternote können nun den Wahl-Hash benutzen, um aus ihren Geldbörsen heraus (auch andere als MPW) zu wählen. Es ist darum sinnvoll einen Beitrag dazu im Form zun hinterlassen - wenn Anwendbar" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Viel Glück bei deiner Beteiligung an der DAO, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Bitte bestätige die Adresse auf deinem" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Bestätige..." # Confirming... +proposalFinalisationRemaining = "Verbleibend" # remaining +proposalFinalisationExpired = "Antrag ausgelaufen" # Proposal Expired +proposalFinalisationReady = "Bereit zum Einreichen" # Ready to submit +proposalPassing = "Bestehend" # Passing +proposalFailing = "Scheiternd" # Failing +proposalTooYoung = "noch zu Jung" # Too Young +proposalFunded = "Finanziert" # Funded +proposalNotFunded = "nicht Finanziert" # Not Funded +proposalPaymentsRemaining = "Ausstehende Transaktion(en)
" # installment(s) remaining
of +proposalPaymentTotal = "Insgesamt" # total +proposalNetYes = "Netto Ja" # Net Yes +popupConfirm = "Bestätigen" # Confirm +popupClose = "Schließen" # Close +popupCancel = "Abbrechen" # Cancel +chartPublicAvailable = "Öffentlich verfügbar" # Public Available +timeDays = "Tage" # Days +timeHours = "Stunden" # Hours +timeMinutes = "Minuten" # Minutes +timeSeconds = "Sekunden" # Seconds +unhandledException = "Unerwarteter Sonderfall" # Unhandled exception. +syncStatusHistoryProgress = "Synchronisiere vergangene Blöcke - {current} von {total}." # Syncing History Chunks {current} of {total} +syncStatusStarting = "Deine Geldbörse synchronisiert!
Volle Funktionalität ist erst anschließend verfügbar." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Synchronisation beendet!
Deine Geldbörse ist für die Benutzung bereit!" # Sync Finished!
Your wallet is ready to use! +contestedProposalsTitle = "Umstrittene Anträge" # Contested Proposals +netSwitchUnsavedWarningTitle = "Deine {network} Geldbörse ist nicht gespeichert!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Dein {network} Konto ist in gefahr!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Wenn du jetzt zu {network} wechselst, bevor du speicherst, verlierst du das aktuelle Konto!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Bist du wirklich sicher?" # Are you really sure? +accountDeleted = "Deine Geldbörse wurde erfolgreich gelöscht" # Your account has been successfully deleted! +activitySelf = "Eigenüberweisung" # self +activityReceivedWith = "Erhalten über {s}" # Received with {s} +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Interner Fehler, bitte versuche es später erneut" # Internal error, please try again later +FAILED_TO_IMPORT = "Import fehlgeschlagen! Falsches Passwort" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = "Importieren der Hardware Geldbörse fehlgeschlagen" # Failed to import Hardware Wallet. +UNSUPPORTED_CHARACTER = "Das Zeichen {char} ist nicht erlaubt in der Adresse! (Nicht Base58 kompatibel)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Dieser Browser unterstützt keine Web Worker (Multi-Threaded JS), leider kannst du keine Vanity Adresse generieren!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Ungültige PIVX Adresse
{address}" # Invalid PIVX address!
{address} +TESTNET_ENCRYPTION_DISABLED = "Testnet Modus ist AN!
Verschlüsselung ist deaktiviert" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Dieses Passwort ist ein wenig zuu kurz!
Es sollte mindestens {MIN_PASS_LENGTH} Zeichen lang sein." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Die Passwörter stimmen nicht überein!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Passwort gesetzt! 🔐
Sehr gut, du bist nun ein geschützter PIVian!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Falsches Passwort" # Incorrect password! +INVALID_AMOUNT = "Ungültige Menge!
" # Invalid amount!
+TX_SENT = "Transaktion gesendet!" # Transaction sent! +TX_FAILED = "Transaktion fehlgeschlagen!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "ist kein gültiger Empfänger" # is not a valid payment receiver +VALIDATE_AMOUNT_LOW = "
Minimale Menge ist {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} Nachkommastellen überschritten" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Staking Adresse gesetzt
Gehe nun über zum Abberufen!" # Staking Address set!
Now go ahead and unstake! +STAKE_ADDR_SET = "Kalte Adresse gesetzt!
Zukünftige Delegierungen benutzen diese Adresse." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Ungültige kalte Adresse!" # Invalid Cold Staking address! +CONFIRM_UNSTAKE_H_WALLET = "Bestätige die Abberufung
Überprüfe die Transaktion auf deinem {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Bestätige die Transaktion
Überprüfe die Transaktion auf deinem {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Staking Adresse gesetzt!
Gehe nun über zum Delegieren!" # Staking Address set!
Now go ahead and stake! +STAKE_NOT_SEND = "Benutze bitte den Stake Reiter, nicht den Senden Reiter!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Ungültige PIVX Adresse!
Falsche Länge ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Ungültige PIVX Adresse!
Falscher Prefixx {address} (Sollte eigentlich mit {addressPrefix} beginnen)" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Zu senden Mengen kann nicht 0 sein!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Sichere deine Geldbörse!
Übersicht ➜ Sichere deine Geldbörse" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Bitter verschlüssel und/oder Sichere deine Schlüssel bevor du die Sitzung beendest, oder du verlierst den Zugriff!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Dieses Gerät hat keine Kamera" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Der Ledger wird nicht unterstützt für kalte Adressen" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Abgleich fehlgeschlagen! Bitte versuche es später erneut.
Alternativ kannst du einen neuen Versuch über die Einstellungen starten." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Die Masternode ist noch nicht aktiviert!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Stimme übermittelt" # Vote submitted! +VOTED_ALREADY = "Du hast bereits für diesen Antrag abgestimmt! Bitte warte eine Stunde" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Verifikation der Signatur fehlgeschlagen, bitte überprüfe den privaten Schlüssel deiner Masternode" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode erstellt!
Warte für 15 Bestätigungen um die Konfiguration abzuschließen" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Wähle eine Masternode, bevor du abstimmst!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Die Masternode ist Offline, wir versuchen sie zu starten" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternode gestartet!" # Masternode started! +MN_RESTARTED = "Masternode neu gestartet!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternode gestartet!
Sie sollte bald online sein" # Masternode started!
It'll be online soon +MN_START_FAILED = "Masternode Start fehlgeschlagen!" # Masternode started! +MN_RESTART_FAILED = "Masternode Neustart fehlgeschlagen!" # Masternode restarted! +MN_DESTROYED = "Masternode destroyed!
Your coins are now spendable." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Der Status der Masternode ist" # Your masternode status is +MN_STATE = "Die Masternode hat den Status {state}" # Your masternode is in {state} state +MN_BAD_IP = "Die IP-Adresse ist ungültig!" # The IP address is invalid! +MN_BAD_PRIVKEY = "Der private Schlüssel ist nicht korrekt!" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Du benötigst {amount} mehr {ticker} um eine Masternode zu erstellen!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Du hast zwar genug Guthaben für eine Masternode, aber keine gültige UTXO über {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Dies ist keine gültige UTXO für eine Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Keine Verbindung zum RPC-Knoten möglich!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Du musst erst \"{button}\" drücken bevor du Kontakt hinzufügen kannst!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Ein Name ist bnötigt!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Der Name ist zu lang!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Du kannst dich nicht selbst als Kontakt hinzufügen!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Kontakt existiert bereits!
Er ist bereits gespeichert" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Ein Kontakt mit diesem Namen existiert bereits!Dies könnte ein Phishing-Versuch sein, sei Vorsichtig!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Der Kontakt existiert bereits!
Ein Kontakt heißt bereits \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Der Kontakt existiert bereits, aber unter einem anderen Namen!
{newName} existiert bereits als {oldName}." # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Dies ist kein Kontakt-QR Code!" # This isn't a Contact QR! +CONTACTS_ADDED = "Neuer Kontakt hinzugefügt!
{strName} ist jetzt einer deiner Kontakte, Juhuu!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Du hast noch keine Kontakte!" # You have no contacts! +PROPOSAL_FINALISED = "Antrag finalisiert!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "Der Antrag wurde noch nicht bestätigt." # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "Der Antrag ist ausgelaufen. Erstelle einen neuen." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Finalisieren des Antrags fehlgeschlagen." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Erstelle eine oder importiere deine Geldbörse um fortzufahren" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Nicht genug Mittel um einen Antrag zu erstellen." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "Der Antrag ist Ungültig. Error:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Antrag eingereicht!
Warte 6 Bestätigungen um zu finalisieren." # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Minimale Menge ist {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Dein Gerät kann nur {quantity} auf einmal erstellen!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Du hast nicht genug {ticker} um den Code zu erstellen!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "DU hast diesen Code bereits erstellt!" # You've already created that code! +SWITCHED_EXPLORERS = "Explorer gewechselt!
In Benutzung: {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Knoten gewechselt!
In Benutzung: {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Analysedaten-Level geändert!
Jetzt: {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Modus zum Abgleich geändert!
Aktuell: {sync} Abgleich" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Ein wechsel in den Testnet Modus ist unmöglich!
Es ist bereits eine Geldbörse geladen" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Der offline Modus ist aktiv!
Bitte deaktivere den offline Modus für automatische Transaktionen" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Bitte {unlock} deine Geldbörse bevor du Transaktionen tätigst!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Firefox unterstütz diese Funktion nicht!
Leider unterstützt Firefox keine Hardware-Geldbörsen" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Hardware-Geldbörse bereit!
Bitte lass dein {hardwareWallet} eingesteckt, entsperrt und in der PIVX app aktiviert" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Bestätige den Import auf deinem Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Kein Gerät verfügbar
Es wurde keine Hardware-Geldbörse gefunden, bitte stecke es ein und entsperre es!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "Das Betriebssystem verweigert den Zugriff Wurden die udev Regeln angepasst?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "Das Betriebssystem verweigert den Zugriff Bitte überprüfe deine Betriebssystem-Einstellungen" # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Verbindung zum {hardwareWallet} verloren
So wie es scheint, wurde das {hardwareWalletProductionName} im Prozess abgezogen, oops!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} wartet
Bitte entsperre dein {hardwareWalletProductionName} oder beende die aktuelle Anfrage" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Bestätige die Wahl" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Bist du dir sicher? Es braucht 60 Minuten bis du deine Stimme ändern kannst" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Bestätige deine Transaktion" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Der private Schlüssel der Masternode" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Sichere diesen Schlüssel und kopiere selbigen in die Konfiguration der Masternode
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Überprüfe die Adresse" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Wiederherstellen der Masternode fehlgeschlagen. Bitte importiere sie erneut." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Wiederherstellen deines Accounts fehlgeschlagen. Bitte importiere ihn erneut." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "Anwendung installiert!" # App Installed! +CONFIRM_POPUP_DELETE_ACCOUNT = "Dies wird alle deine Daten löschen. Eingeschlossen Masternode, Kontakte und private Schlüssel!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Bist du wirklich sicher?" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/en/translation.toml b/locale/en/translation.toml new file mode 100644 index 000000000..f66bbbc81 --- /dev/null +++ b/locale/en/translation.toml @@ -0,0 +1,335 @@ +amount = "Amount" # Amount +staking = "Staking" # Staking +wallet = "Wallet" # Wallet +display = "Display" # Display +activity = "Activity" # Activity +yes = "Yes" # Yes +no = "No" # No +navDashboard = "Dashboard" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Governance" # Governance +navSettings = "Settings" # Settings +footerBuiltWithPivxLabs = "Built with 💜 by PIVX Labs" # Built with 💜 by PIVX Labs +loading = "Loading" # Loading +loadingTitle = "My PIVX Wallet is" # My PIVX Wallet is +dashboardTitle = "Dashboard" # Dashboard +dCardOneTitle = "Create a" # Create a +dCardOneSubTitle = "New Wallet" # New Wallet +dCardOneDesc = "Create a new PIVX wallet, offering the most secure backup & security methods." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Create Wallet" # Create Wallet +dCardTwoTitle = "Create a new" # Create a new +dCardTwoSubTitle = "Vanity Wallet" # Vanity Wallet +dCardTwoDesc = "Create a wallet with a custom prefix, this can take a long time!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Create Vanity" # Create Vanity +dCardThreeTitle = "Access your" # Access your +dCardThreeSubTitle = "Ledger Wallet" # Ledger Wallet +dCardThreeDesc = "Use your Ledger Hardware wallet with MPW's familiar interface." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Access Ledger" # Access Ledger +dCardFourTitle = "Go to" # Go to +dCardFourSubTitle = "My Wallet" # My Wallet +dCardFourDesc = "Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Import" # Import +dCardFourButtonA = "Access Wallet" # Access Wallet +vanityPrefixNote = "Note: addresses will always start with:" # Note: addresses will always start with: +vanityPrefixInput = "Address Prefix" # Address Prefix +thisIsYourSeed = "This is your seed phrase:" # This is your seed phrase: +writeDownSeed = "Write it down somewhere. You'll only see this once!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Anyone with a copy of it can access all of your funds." # Anyone with a copy of it can access all of your funds. +doNotShare = "Do NOT share it with anyone." # Do NOT share it with anyone. +digitalStoreNotAdvised = "It is NOT advised to store this digitally." # It is NOT advised to store this digitally. +optionalPassphrase = "Optional Passphrase (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "I have written down my seed phrase" # I have written down my seed phrase +importSeedValid = "Seed Phrase is valid!" # Seed Phrase is valid! +importSeedError = "Seed Phrase is invalid!" # Seed Phrase is invalid! +importSeedErrorSize = "A Seed Phrase should be 12 or 24 words long!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Seed Phrase contains typing errors! Check your input carefully" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Seed Phrase appears invalid, but the warning was skipped by the user" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Getting Started" # Getting Started +secureYourWallet = "Secure your wallet" # Secure your wallet +unlockWallet = "Unlock wallet" # Unlock wallet +lockWallet = "Lock wallet" # Lock wallet +encryptWallet = "Encrypt wallet" # Encrypt wallet +encryptPasswordCurrent = "Current Password" # Current Password +encryptPasswordFirst = "Enter Password" # Enter Password +encryptPasswordSecond = "Re-enter Password" # Re-enter Password +encrypt = "Encrypt" # Encrypt +changePassword = "Change Password" # Change Password +balanceBreakdown = "Balance Breakdown" # Balance Breakdown +viewOnExplorer = "View on Explorer" # View on Explorer +export = "Export" # Export +refreshAddress = "Refresh address" # Refresh address +redeemOrCreateCode = "Redeem or Create Code" # Redeem or Create Code +address = "Address" # Address +receivingAddress = "Receiving address" # Receiving address +sendAmountCoinsMax = "MAX" # MAX +paymentRequestMessage = "Description (from the merchant)" # Description (from the merchant) +send = "Send" # Send +receive = "Receive" # Receive +contacts = "Contacts" # Contacts +name = "Name" # Name +username = "Username" # Username +addressOrXPub = "Address or XPub" # Address or XPub +back = "Back" # Back +chooseAContact = "Choose a Contact" # Choose a Contact +createContact = "Create Contact" # Create Contact +encryptFirstForContacts = "Once you hit \"{button}\" in the Dashboard, you can create a Contact to make receiving PIV easier!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Share Contact URL" # Share Contact URL +setupYourContact = "Setup your Contact" # Setup your Contact +receiveWithContact = "Receive using a simple username-based Contact" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Only share your Contact with trusted people (family, friends)" # Only share your Contact with trusted people (family, friends) +changeTo = "Change to" # Change to +contact = "Contact" # Contact +xpub = "XPub" # XPub +addContactTitle = "Add {strName} to Contacts" # Add {strName} to Contacts +addContactSubtext = "Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Ensure that this is the real \"{strName}\", do not accept Contact requests from unknown sources!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Change \"{strName}\" Contact" # Change "{strName}" Contact +newName = "New Name" # New Name +removeContactTitle = "Remove {strName}?" # Remove {strName}? +removeContactSubtext = "Are you sure you wish to remove {strName} from your Contacts?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "You can add them again any time in the future." # You can add them again any time in the future. +privateKey = "Private Key" # Private Key +viewPrivateKey = "View Private Key?" # View Private Key? +privateWarning1 = "Make sure no one can see your screen." # Make sure no one can see your screen. +privateWarning2 = "Anyone with this key can steal your funds." # Anyone with this key can steal your funds. +viewKey = "View key" # View key +pivxPromos = "is a decentralised system for gift codes worth PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Enter your 'PIVX Promos' code" # Enter your 'PIVX Promos' code +createName = "Promo Name (Optional)" # Promo Name (Optional) +createAmount = "Promo Amount" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "(Optional) Owner Address" # (Optional) Owner Address +rewardHistory = "Reward History" # Reward History +loadMore = "Load more" # Load more +mnControlYour = "Control your" # Control your +mnSubtext = "From this tab you can create and access one or more masternodes" # From this tab you can create and access one or more masternodes +govSubtext = "From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Monthly Budget" # Monthly Budget +govAllocBudget = "Allocated Budget" # Allocated Budget +govNextPayout = "Next Treasury Payout" # Next Treasury Payout +govTableStatus = "STATUS" # STATUS +govTableName = "NAME" # NAME +govTablePayment = "PAYMENT" # PAYMENT +govTableVotes = "VOTES" # VOTES +govTableVote = "VOTE" # VOTE +contestedProposalsTitle = "Contested Proposals" # Contested Proposals +contestedProposalsDesc = "These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Choose a display currency:" # Choose a display currency: +priceProvidedBy = "Price data provided by" # Price data provided by +settingsDecimals = "Balance Decimals:" # Balance Decimals: +settingsExplorer = "Choose an explorer:" # Choose an explorer: +settingsLanguage = "Choose a Language:" # Choose a Language: +settingsPivxNode = "Choose a PIVX node:" # Choose a PIVX node: +settingsAutoSelectNet = "Auto-select Explorers and Nodes" # Auto-select Explorers and Nodes +settingsAnalytics = "Choose your analytics contribution level:" # Choose your analytics contribution level: +settingsToggleDebug = "Debug Mode" # Debug Mode +settingsToggleTestnet = "Testnet Mode" # Testnet Mode +settingsToggleAdvancedMode = "Advanced Mode" # Advanced Mode +settingsToggleAdvancedModeSubtext = "This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Your {network} wallet isn't saved!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Your {network} account is at risk!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "If you switch to {network} before saving it, you'll lose the account!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Are you really sure?" # Are you really sure? +transparencyReport = "Transparency Report" # Transparency Report +hit = "A ping indicating an app load, no unique data is sent." # A ping indicating an app load, no unique data is sent. +time_to_sync = "The time in seconds it took for MPW to last synchronise." # The time in seconds it took for MPW to last synchronise. +transaction = "A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Disabled" # Disabled +analyticMinimal = "Minimal" # Minimal +analyticBalanced = "Balanced" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Failed to recover account" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "There was an error recovering your account.
Please reimport your wallet using the following key:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Time" # Time +description = "Description" # Description +activityBlockReward = "Block Reward" # Block Reward +activitySentTo = "Sent to {r}" # Sent to {r} +activitySelf = "self" # self +activityShieldedAddress = "Shielded address" # Shielded address +activityDelegatedTo = "Delegated to {r}" # Delegated to {r} +activityUndelegated = "Undelegated" # Undelegated +activityUnknown = "Unknown Tx" # Unknown Tx +password = "Password" # Password +walletUnlock = "Unlock your wallet" # Unlock your wallet +walletPassword = "Wallet password" # Wallet password +walletUnlockCreateMN = "Unlock to create your Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Unlock to start your Masternode!" # Unlock to start your Masternode! +walletUnlockProposal = "Unlock to create a proposal!" # Unlock to create a proposal! +walletUnlockPromo = "Unlock to finalise your Promo Code!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Unlock to send your transaction!" # Unlock to send your transaction! +walletUnlockStake = "Unlock to stake your" # Unlock to stake your +walletUnlockUnstake = "Unlock to unstake your" # Unlock to unstake your +changelogTitle = "What's New in" # What's New in +popupSetColdAddr = "Set your Cold Staking address" # Set your Cold Staking address +popupCurrentAddress = "Current address:" # Current address: +popupColdStakeNote = "A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Example:" # Example: +popupWalletLock = "Do you want to lock your wallet?" # Do you want to lock your wallet? +popupWalletWipe = "Do you want to wipe your wallet private data?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "You will need to enter your password to access your funds" # You will need to enter your password to access your funds +popupWalletWipeNote = "You will lose access to your funds if you haven't backed up your private key or seed phrase" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Unexpected Seed Phrase" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Create Proposal" # Create Proposal +popupCreateProposalCost = "Cost" # Cost +popupProposalName = "Proposal Name" # Proposal Name +popupProposalAddress = "Proposal Address (Optional)" # Proposal Address (Optional) +popupProposalDuration = "Duration in cycles" # Duration in cycles +popupProposalPerCycle = "per cycle" # per cycle +popupProposalEncryptFirst = "You need to hit \"{button}\" before you can create proposals!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Vote Hash:" # Vote Hash: +popupProposalFinalisedNote = "Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Good luck on your journey through the DAO, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Please confirm this is the address you see on your" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Confirming..." # Confirming... +proposalFinalisationRemaining = "remaining" # remaining +proposalFinalisationExpired = "Proposal Expired" # Proposal Expired +proposalFinalisationReady = "Ready to submit" # Ready to submit +proposalPassing = "Passing" # Passing +proposalFailing = "Failing" # Failing +proposalTooYoung = "Too Young" # Too Young +proposalFunded = "Funded" # Funded +proposalNotFunded = "Not Funded" # Not Funded +proposalPaymentsRemaining = "installment(s) remaining
of" # installment(s) remaining
of +proposalPaymentTotal = "total" # total +proposalNetYes = "Net Yes" # Net Yes +popupConfirm = "Confirm" # Confirm +popupClose = "Close" # Close +popupCancel = "Cancel" # Cancel +chartPublicAvailable = "Public Available" # Public Available +timeDays = "Days" # Days +timeHours = "Hours" # Hours +timeMinutes = "Minutes" # Minutes +timeSeconds = "Seconds" # Seconds +unhandledException = "Unhandled exception." # Unhandled exception. +syncStatusHistoryProgress = "Syncing History Chunks {current} of {total}" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Your wallet is syncing!
You'll be able to use it fully once this is complete." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Sync Finished!
Your wallet is ready to use!" # Sync Finished!
Your wallet is ready to use! +activityReceivedWith = "Received with {s}" # Received with {s} +accountDeleted = "Your account has been successfully deleted!" # Your account has been successfully deleted! +chartImmatureBalance = "Immature balance" # Immature balance +syncLoadingSaplingProver = "Loading SHIELD parameters..." # Loading SHIELD parameters... +syncShieldProgress = "Loading shielded blocks {current} of {total}" # Loading shielded blocks {current} of {total} +useShieldInputs = "Use shield inputs" # Use shield inputs +newShieldAddress = "Get new shield address" # Get new shield address +shieldAddress = "Shield address" # Shield address +cantShieldToExc = "This address does not support shield transfers" # This address does not support shield transfers +settingsToggleAutoLockWallet = "Auto Lock the Wallet" # Auto Lock the Wallet +saveWalletFile = "Save Wallet File" # Save Wallet File +proposalOverBudget = "Over Budget" # Over Budget +badSaplingRoot = "There was an error while syncing. Resyncing from scratch (Bad sapling root)" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "Creating SHIELD transaction..." # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Internal error, please try again later" # Internal error, please try again later +FAILED_TO_IMPORT = "Failed to import! Invalid password" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = " Failed to import Hardware Wallet." # Failed to import Hardware Wallet. +TESTNET_ENCRYPTION_DISABLED = "Testnet Mode is ON!
Wallet encryption disabled" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "That password is a little short!
Use at least {MIN_PASS_LENGTH} characters." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Your passwords don't match!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "You're Secured! 🔐
Nice stuff, Armoured PIVian!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Incorrect password!" # Incorrect password! +INVALID_AMOUNT = "Invalid amount!
" # Invalid amount!
+TX_SENT = "Transaction sent!" # Transaction sent! +TX_FAILED = "Transaction Failed!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "is not a valid payment receiver" # is not a valid payment receiver +UNSUPPORTED_CHARACTER = "The character '{char}' is unsupported in addresses! (Not Base58 compatible)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Invalid PIVX address!
{address}" # Invalid PIVX address!
{address} +VALIDATE_AMOUNT_LOW = "
Minimum amount is {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} decimal limit exceeded" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Staking Address set!
Now go ahead and unstake!" # Staking Address set!
Now go ahead and unstake! +CONFIRM_UNSTAKE_H_WALLET = "Confirm your Unstake
Confirm the TX on your {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Confirm your transaction
Confirm the TX on your {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Staking Address set!
Now go ahead and stake!" # Staking Address set!
Now go ahead and stake! +STAKE_ADDR_SET = "Cold Address set!
Future stakes will use this address." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Invalid Cold Staking address!" # Invalid Cold Staking address! +STAKE_NOT_SEND = "Here, use the Stake screen, not the Send screen!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Invalid PIVX address!
Bad length ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "You can't send 'nothing'!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Save your wallet!
Dashboard ➜ Secure your wallet" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "This device has no camera!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger is not supported for Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Your masternode is not enabled yet!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Vote submitted!" # Vote submitted! +VOTED_ALREADY = "You already voted for this proposal! Please wait 1 hour" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Failed to verify signature, please check your masternode's private key" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode Created!
Wait 15 confirmations to proceed further" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Access a masternode before voting!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Your masternode is offline, we will try to start it" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternode started!" # Masternode started! +MN_RESTARTED = "Masternode restarted!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternode started!
It'll be online soon" # Masternode started!
It'll be online soon +MN_START_FAILED = "Masternode started!" # Masternode started! +MN_RESTART_FAILED = "Masternode restarted!" # Masternode restarted! +MN_DESTROYED = "Masternode destroyed!
Your coins are now spendable." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Your masternode status is" # Your masternode status is +MN_STATE = "Your masternode is in {state} state" # Your masternode is in {state} state +MN_BAD_IP = "The IP address is invalid!" # The IP address is invalid! +MN_BAD_PRIVKEY = "The private key is invalid" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "You need {amount} more {ticker} to create a Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "This is not a suitable UTXO for a Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Unable to connect to RPC node!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "You need to hit \"{button}\" before you can use Contacts!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "A name is required!" # A name is required! +CONTACTS_NAME_TOO_LONG = "That name is too long!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "You cannot add yourself as a Contact!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Contact already exists!
You already saved this contact" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Contact name already exists!
This could potentially be a phishing attempt, beware!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Contact already exists!
A contact is already called \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "This isn't a Contact QR!" # This isn't a Contact QR! +CONTACTS_ADDED = "New Contact added!
{strName} has been added, hurray!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "You have no contacts!" # You have no contacts! +PROPOSAL_FINALISED = "Proposal Launched!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "The proposal hasn't confirmed yet" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "The proposal has expired. Create a new one." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Failed to finalize proposal." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Create or import your wallet to continue" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Not enough funds to create a proposal." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "Proposal is invalid. Error:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Proposal Created!
Wait for confirmations, then finalise your proposal!" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Minimum amount is {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Your device can only create {quantity} codes at a time!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "You don't have enough {ticker} to create that code!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "You've already created that code!" # You've already created that code! +SWITCHED_EXPLORERS = "Switched explorer!
Now using {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Switched node!
Now using {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Switched analytics level!
Now {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Switched sync mode!
Now using {sync} sync" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Unable to switch Testnet Mode!
A wallet is already loaded" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Offline Mode is active!
Please disable Offline Mode for automatic transactions" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Please {unlock} your wallet before sending transactions!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Confirm the import on your Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "No device available
Couldn't find a hardware wallet; please plug it in and unlock!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "The OS denied access Did you add the udev rules?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "The OS denied access Please check your Operating System settings." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Confirm Vote" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Are you sure? It takes 60 minutes to change vote" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Confirm your transaction" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Your Masternode Private Key" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Save this private key and copy it to your VPS config
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Verify your address" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Failed to recover your masternode. Please reimport it." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Failed to recover your account. Please reimport it." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "App Installed!" # App Installed! +CONFIRM_POPUP_DELETE_ACCOUNT = "This will delete all your data, including masternodes contacts and private keys!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Are you sure?" # Are you sure? +WALLET_NOT_SYNCED = "Please try again when wallet finishes syncing!" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "Wallet successfully Locked!" # Wallet successfully Locked! +WALLET_UNLOCKED = "Wallet successfully Unlocked!" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "Confirm this transaction matches the one on your {hardwareWallet}" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "You will send {value} {ticker} to
{address}
" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "Balance is too small! Missing {sats} sats!" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "Shield is not enabled in this wallet!" # Shield is not enabled in this wallet! diff --git a/locale/es-mx/translation.toml b/locale/es-mx/translation.toml new file mode 100644 index 000000000..8799397c0 --- /dev/null +++ b/locale/es-mx/translation.toml @@ -0,0 +1,335 @@ +amount = "Cantidad" # Amount +staking = "Staking" # Staking +wallet = "Wallet" # Wallet +display = "Visualizar" # Display +activity = "Actividad" # Activity +yes = "Si" # Yes +no = "No" # No +navDashboard = "Panel" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Gobernanza" # Governance +navSettings = "Ajustes" # Settings +footerBuiltWithPivxLabs = "Hecho con 💜 por PIVX Labs 🇲🇽" # Built with 💜 by PIVX Labs +loading = "Cargando" # Loading +loadingTitle = "My PIVX Wallet está" # My PIVX Wallet is +dashboardTitle = "Panel" # Dashboard +dCardOneTitle = "crea una" # Create a +dCardOneSubTitle = "Nueva Wallet" # New Wallet +dCardOneDesc = "Crear una nueva PIVX Wallet, ofreciendo los métodos de respaldo y seguridad más seguros." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Crear una nueva PIVX Wallet" # Create A New Wallet +dCardTwoTitle = "Crear una nueva" # Create a new +dCardTwoSubTitle = "Vanity Wallet" # Vanity Wallet +dCardTwoDesc = "Crear una wallet con un prefijo personalizado, ¡Esto puede llevar mucho tiempo!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Crear una Vanity Wallet" # Create A Vanity Wallet +dCardThreeTitle = "Accede a tu" # Access your +dCardThreeSubTitle = "Wallet de Hardware" # Ledger Wallet +dCardThreeDesc = "Utiliza tu wallet Ledger Hardware con en la interfaz de MPW." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Acceder a mi Ledger" # Access my Ledger +dCardFourTitle = "Ir a" # Go to +dCardFourSubTitle = "Mi Wallet" # My Wallet +dCardFourDesc = "Importar una wallet de PIVX usando una llave privada, xpriv o frase de semilla." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Importar Wallet" # Import Wallet +dCardFourButtonA = "Acceder a mi Wallet" # Access My Wallet +vanityPrefixNote = "Nota: las direcciones siempre comenzarán con:" # Note: addresses will always start with: +vanityPrefixInput = "Prefijo de direcciones" # Address Prefix +thisIsYourSeed = "Esta es tu frase de semilla:" # This is your seed phrase: +writeDownSeed = "Escríbelo en alguna parte. ¡Solo verás esto una vez!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Cualquiera que tenga una copia puede acceder a todos tus fondos." # Anyone with a copy of it can access all of your funds. +doNotShare = "NO lo compartas con nadie." # Do NOT share it with anyone. +digitalStoreNotAdvised = "NO se recomienda almacenar esto digitalmente." # It is NOT advised to store this digitally. +optionalPassphrase = "Frase de contraseña opcional (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "He escrito mi frase semilla" # I have written down my seed phrase +importSeedValid = "¡La frase de semilla es válida!" # Seed Phrase is valid! +importSeedError = "¡La frase de semilla no es válida!" # Seed Phrase is invalid! +importSeedErrorSize = "¡Una frase de semilla debe tener entre 12 y 24 palabras!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "¡La frase de semilla contiene errores tipográficos! Revisa atentamente tu escritura" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "La frase de semilla parece ser no válida, pero el usuario omitió la advertencia" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Primeros pasos" # Getting Started +secureYourWallet = "Asegura tu wallet" # Secure your wallet +unlockWallet = "Desbloquear wallet" # Unlock wallet +lockWallet = "Bloquear wallet" # Lock wallet +encryptWallet = "Encriptar wallet" # Encrypt wallet +encryptPasswordCurrent = "Contraseña actual" # Current Password +encryptPasswordFirst = "Introduce la contraseña" # Enter Password +encryptPasswordSecond = "Volver a introducir contraseña" # Re-enter Password +encrypt = "Encriptar" # Encrypt +changePassword = "Cambiar contraseña" # Change Password +balanceBreakdown = "Desglose del Balance" # Balance Breakdown +viewOnExplorer = "Ver en el Explorador" # View on Explorer +export = "Exportar" # Export +refreshAddress = "Actualizar la dirección" # Refresh address +redeemOrCreateCode = "Canjear o Crear un Código" # Redeem or Create Code +address = "Dirección" # Address +receivingAddress = "Dirección receptora" # Receiving address +sendAmountCoinsMax = "MÁXIMO" # MAX +paymentRequestMessage = "Descripción (de la tienda)" # Description (from the merchant) +send = "Enviar" # Send +receive = "Recibir" # Receive +contacts = "Contactos" # Contacts +name = "Nombre" # Name +username = "Nombre de usuario" # Username +addressOrXPub = "Dirección o XPub" # Address or XPub +back = "Regresar" # Back +chooseAContact = "Elegir un contacto" # Choose a Contact +createContact = "Crear un contacto" # Create Contact +encryptFirstForContacts = "Una vez que pulses \"{button}\" en el Panel de control, podrás crear un Contacto para facilitar la recepción de PIV." # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Compartir URL de contacto" # Share Contact URL +setupYourContact = "Configurar el contacto" # Setup your Contact +receiveWithContact = "Recibir usando un contacto simple basado en el nombre de usuario" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Comparte tus Contactos sólamente con personas de confianza (familia, amigos, etc.)" # Only share your Contact with trusted people (family, friends) +changeTo = "Cambiar a" # Change to +contact = "Contacto" # Contact +xpub = "XPub" # XPub +addContactTitle = "Añadir a {strName} a los Contactos" # Add {strName} to Contacts +addContactSubtext = "Una vez añadido, podrás enviar transacciones a {strName} por su nombre (escribiendo o haciendo click), no más direcciones, fácil y sencillo." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Asegúrate de que se trata del verdadero \"{strName}\", ¡No aceptes solicitudes de personas desconocidas!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "" # Change "{strName}" Contact +newName = "" # New Name +removeContactTitle = "¿Eliminar a {strName}?" # Remove {strName}? +removeContactSubtext = "¿Estás seguro de que deseas eliminar a {strName} de tus Contactos?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Puedes volver a añadirle en cualquier otro momento." # You can add them again any time in the future. +privateKey = "Llave Privada" # Private Key +viewPrivateKey = "¿Ver la Llave privada?" # View Private Key? +privateWarning1 = "Asegúrate de que nadie pueda ver tu pantalla." # Make sure no one can see your screen. +privateWarning2 = "Cualquiera con esta clave puede robar tus fondos." # Anyone with this key can steal your funds. +viewKey = "Ver la llave" # View key +pivxPromos = "es un sistema descentralizado de códigos de regalo comprados con PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Introduce tu código de PIVX Promos" # Enter your 'PIVX Promos' code +createName = "Nombre de la Promoción (Opcional)" # Promo Name (Optional) +createAmount = "Monto de la Promo" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "" # (Optional) Owner Address +rewardHistory = "Historial de recompensas" # Reward History +loadMore = "Cargar más" # Load more +mnControlYour = "Controla tu" # Control your +mnSubtext = "Desde esta pestaña puedes crear y acceder a uno o varios masternodes" # From this tab you can create and access one or more masternodes +govSubtext = "Desde esta pestaña puedes consultar las propuestas y, si tienes un masternode, ¡ser parte de la DAO y votar!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Presupuesto Mensual" # Monthly Budget +govAllocBudget = "Presupuesto Asignado" # Allocated Budget +govNextPayout = "Próximo Pago de la Tesorería" # Next Treasury Payout +govTableStatus = "ESTATUS" # STATUS +govTableName = "NOMBRE" # NAME +govTablePayment = "PAGOS" # PAYMENT +govTableVotes = "VOTOS" # VOTES +govTableVote = "VOTA" # VOTE +contestedProposalsDesc = "Estas son propuestas que han recibido una cantidad abrumadora de votos negativos, por lo que es probable que se trate de spam o de una propuesta muy cuestionable." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Elije una divisa de visualización:" # Choose a display currency: +priceProvidedBy = "Datos de precios proporcionados por" # Price data provided by +settingsDecimals = "Decimales en el saldo:" # Balance Decimals: +settingsExplorer = "Elige un explorador:" # Choose an explorer: +settingsLanguage = "Elige un idioma:" # Choose a Language: +settingsPivxNode = "Elige un nodo PIVX:" # Choose a PIVX node: +settingsAutoSelectNet = "Selección automática de exploradores y nodos" # Auto-select Explorers and Nodes +settingsAnalytics = "Elige tu nivel de contribución a las analíticas:" # Choose your analytics contribution level: +settingsToggleDebug = "Modo de depuración" # Debug Mode +settingsToggleTestnet = "Modo Testnet" # Testnet Mode +settingsToggleAdvancedMode = "Modo Avanzado" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Esto desbloquea una mayor funcionalidad y personalización, pero puede resultar abrumador y potencialmente peligroso para los usuarios inexpertos." # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "¡Tu wallet {network} no está guardada!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "¡Tu cuenta {network} está en riesgo!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Si cambias a la {network} antes de guardarla, ¡perderás la cuenta!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "¿Estás seguro?" # Are you really sure? +transparencyReport = "Informe de Transparencia" # Transparency Report +hit = "Un ping que indica la carga de la aplicación, ningún dato se ha enviado." # A ping indicating an app load, no unique data is sent. +time_to_sync = "El tiempo en segundos que tardó MPW en sincronizarse por última vez." # The time in seconds it took for MPW to last synchronise. +transaction = "Un ping que indica un Tx, ningún dato se ha enviado, pero puede inferirse del tiempo en la cadena." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Deshabilitado" # Disabled +analyticMinimal = "Mínimo" # Minimal +analyticBalanced = "Balanceado" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Error al recuperar la cuenta" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Se produjo un error al recuperar tu cuenta.
Vuelva a importar su wallet usando la siguiente clave:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Hora" # Time +description = "Descripción" # Description +activityBlockReward = "Recompensa del Bloque" # Block Reward +activitySentTo = "Enviar a {r}" # Sent to {r} +activitySelf = "ti mismo" # self +activityShieldedAddress = "Dirección Shielded" # Shielded address +activityDelegatedTo = "Delegado a {r}" # Delegated to {r} +activityUndelegated = "No delegado" # Undelegated +activityUnknown = "Tx desconocido" # Unknown Tx +password = "Contraseña" # Password +walletUnlock = "Desbloquea tu wallet" # Unlock your wallet +walletPassword = "Contraseña de la Wallet" # Wallet password +walletUnlockCreateMN = "¡Desbloquea para crear tu Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "¡Desbloquea para iniciar tu Masternode!" # Unlock to start your Masternode! +walletUnlockProposal = "¡Desbloquea para crear una propuesta!" # Unlock to create a proposal! +walletUnlockPromo = "¡Desbloquea para finalizar tu Código Promocional!" # Unlock to finalise your Promo Code! +walletUnlockTx = "¡Desbloquea para enviar tu transacción!" # Unlock to send your transaction! +walletUnlockStake = "Desbloquea para hacer stake tus" # Unlock to stake your +walletUnlockUnstake = "Desbloquea para dejar de hacer stake tus" # Unlock to unstake your +changelogTitle = "Novedades en" # What's New in +popupSetColdAddr = "Configura tu dirección de Cold Staking" # Set your Cold Staking address +popupCurrentAddress = "Dirección actual" # Current address: +popupColdStakeNote = "¡Una dirección de Cold Staking hace stake de monedas en tu nombre, pero no puede gastar las monedas, por lo que es seguro utilizar la Dirección Cold Staking de un desconocido!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Ejemplo:" # Example: +popupWalletLock = "¿Quieres bloquear tu wallet?" # Do you want to lock your wallet? +popupWalletWipe = "¿Quieres eliminar los datos privados de tu wallet?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Deberás introducir tu contraseña para acceder a tus fondos" # You will need to enter your password to access your funds +popupWalletWipeNote = "Perderás el acceso a tus fondos si no haz hecho una copia de seguridad de tu llave privada o frase de semilla" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Frase de Semilla Inesperada" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "La frase de semilla no es válida o no ha sido generada por MPW.
¿Aún así deseas continuar?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Crear una Propuesta" # Create Proposal +popupCreateProposalCost = "Costo" # Cost +popupProposalName = "Nombre de la Propuesta" # Proposal Name +popupProposalAddress = "" # Proposal Address (Optional) +popupProposalDuration = "Duración en Ciclos" # Duration in cycles +popupProposalPerCycle = "por ciclo" # per cycle +popupProposalEncryptFirst = "" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Hash de Votación:" # Vote Hash: +popupProposalFinalisedNote = "¡Felicitaciones por el lanzamiento de tu propuesta!
¡Los propietarios de Masternode pueden utilizar tu Hash de Votación para votar desde wallets que no sean MPW, así que asegúrate de añadirlo a tu mensaje en el foro, si aplica!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "¡Buena suerte en tu viaje por la DAO, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Por favor, confirma que esta es la dirección que ve en tu" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Confirmando..." # Confirming... +proposalFinalisationRemaining = "restante" # remaining +proposalFinalisationExpired = "Propuesta Expirada" # Proposal Expired +proposalFinalisationReady = "Listo para enviarla" # Ready to submit +proposalPassing = "Pasando" # Passing +proposalFailing = "Fracasando" # Failing +proposalTooYoung = "Demasiado joven" # Too Young +proposalFunded = "Financiada" # Funded +proposalNotFunded = "No financiada" # Not Funded +proposalPaymentsRemaining = "plazo(s) restante(s)
de" # installment(s) remaining
of +proposalPaymentTotal = "total" # total +proposalNetYes = "Sí de la Red" # Net Yes +popupConfirm = "Confirmar" # Confirm +popupClose = "Cerrar" # Close +popupCancel = "Cancelar" # Cancel +chartPublicAvailable = "Disponible al Público" # Public Available +timeDays = "Días" # Days +timeHours = "Horas" # Hours +timeMinutes = "Minutos" # Minutes +timeSeconds = "Segundos" # Seconds +unhandledException = "Excepción sin resolver." # Unhandled exception. +syncStatusHistoryProgress = "" # Syncing History Chunks {current} of {total} +syncStatusStarting = "" # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "" # Sync Finished!
Your wallet is ready to use! +contestedProposalsTitle = "" # Contested Proposals +accountDeleted = "" # Your account has been successfully deleted! +activityReceivedWith = "" # Received with {s} +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Error interno, vuelve a intentarlo más tarde" # Internal error, please try again later +FAILED_TO_IMPORT = "¡No se ha podido importar! Contraseña inválida" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = "" # Failed to import Hardware Wallet. +UNSUPPORTED_CHARACTER = "¡El carácter {char} no está soportado en las direcciones! (No compatible con Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Este navegador no soporta Web Workers (multi-threaded JS), ¡lamentablemente no puedes generar wallets Vanity!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "¡Dirección PIVX no válida!
{address}" # Invalid PIVX address!
{address} +TESTNET_ENCRYPTION_DISABLED = "¡El modo Testnet está activado!
Encriptado de wallet deshabilitado" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "¡La contraseña es un poco corta!
Utilice al menos {MIN_PASS_LENGTH} caracteres." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "¡Tus contraseñas no coinciden!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "¡Estás asegurado! 🔐
¡Buen trabajo, eres un PIVian precavido!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "¡Contraseña incorrecta!" # Incorrect password! +INVALID_AMOUNT = "¡Cantidad no válida!
" # Invalid amount!
+TX_SENT = "¡Transacción enviada!" # Transaction sent! +TX_FAILED = "¡Transacción fallida!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "no es un receptor de pago válido" # is not a valid payment receiver +VALIDATE_AMOUNT_LOW = "¡La cantidad mínima es {minimum Amount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} límite decimal excedido" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "¡Dirección de Staking establecida!
¡Ahora puedes dejar de hacer stake!" # Staking Address set!
Now go ahead and unstake! +STAKE_ADDR_SET = "¡Dirección de Cold Staking establecida!
El Staking futuro usarán esta dirección." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "¡Dirección de Cold Staking no válida!" # Invalid Cold Staking address! +CONFIRM_UNSTAKE_H_WALLET = "Confirma tu retiro de Staking
Confirma el TX en tu {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Confirma tu transacción
Confirma el TX en tu {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "¡Dirección de Staking establecida!
¡Ahora puedes hacer stake!" # Staking Address set!
Now go ahead and stake! +STAKE_NOT_SEND = "¡Aquí, usa la pantalla Stake, no la pantalla Enviar!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "¡Dirección PIVX no válida!
Longitud incorrecta ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "¡Dirección PIVX no válida!
Prefijo {address} incorrecto (debe comenzar con {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "¡No puedes enviar 'cero'!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "¡Guarda su wallet!
Panel de control ➜ Asegura tu wallet" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "¡Por favor ENCRIPTA y/o HAGA UNA COPIA DE SEGURIDAD de tus llaves antes de irte, o puedes perderlas!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "¡Este dispositivo no tiene cámara!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger no es compatible con Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "¡Error al sincronizar! Vuelve a intentarlo más tarde.
Puedes intentar volver a conectarte a través de los Ajustes." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "¡Tu masternode aún no está habilitado!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "¡Voto enviado!" # Vote submitted! +VOTED_ALREADY = "¡Ya votaste por esta propuesta! Por favor espera 1 hora" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "No se pudo verificar la firma, verifica la llave privada de tu masternode" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "¡Masternode creado!
Espere 15 confirmaciones para continuar" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "¡Accede a un masternode antes de votar!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Tu masternode está fuera de línea, intentaremos iniciarlo" # Your masternode is offline, we will try to start it +MN_STARTED = "¡Masternode iniciado!" # Masternode started! +MN_RESTARTED = "¡Masternode reiniciado!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "¡Masternode iniciado!
Estará en línea muy pronto" # Masternode started!
It'll be online soon +MN_START_FAILED = "¡El inicio del Masternode ha fallado!" # Masternode started! +MN_RESTART_FAILED = "¡El reiniciado del Masternode ha fallado!" # Masternode restarted! +MN_DESTROYED = "¡Masternode destruido!
Tus monedas ahora se pueden gastar." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "El estado de tu masternode es" # Your masternode status is +MN_STATE = "Tu masternode está {state}" # Your masternode is in {state} state +MN_BAD_IP = "¡La dirección IP no es válida!" # The IP address is invalid! +MN_BAD_PRIVKEY = "" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "¡Necesitas {amount} más {ticker} para crear un Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Tienes suficiente saldo para un Masternode, pero no tienes una garantía UTXO válida de {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Este no es un UTXO adecuado para un Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "¡No se puede conectar al nodo RPC!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "¡Debes presionar \"{button}\" antes de poder usar Contactos!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "¡Se requiere un nombre!" # A name is required! +CONTACTS_NAME_TOO_LONG = "¡El nombre es demasiado largo!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "¡No puedes agregarte a ti mismo como contacto!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "¡El contacto ya existe!
Ya haz guardado este contacto" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "¡El nombre del contacto ya existe!
Esto podría ser un intento de phishing, ¡cuidado!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "¡El contacto ya existe!
¡Un contacto ya se llama \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "¡El contacto ya existe, pero con un nombre diferente!
Tienes {newName} guardado como {oldName} en tus contactos" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "¡Este no es un QR de contacto!" # This isn't a Contact QR! +CONTACTS_ADDED = "¡Nuevo contacto añadido!
Se ha añadido {strName}, ¡bravo!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "¡No tienes contactos!" # You have no contacts! +SWITCHED_EXPLORERS = "¡El Explorador a cambiado!
Ahora se usa {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "¡El Nodo a cambiado!
Ahora se usa {nodo}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "¡El Nivel de análisis a cambiado!
Ahora se usa {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "¡El Modo de sincronización a cambiado!
Ahora se usa la sincronización {sync}" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "¡No se puede cambiar a modo Testnet!
Ya hay una wallet cargada" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "¡El modo sin conexión está activo!
Desactiva el modo sin conexión para transacciones automáticas" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "¡Por favor {unlock} tu wallet antes de enviar transacciones!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "¡Firefox no admite esto!
Desafortunadamente, Firefox no admite wallets de hardware" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "¡Listo para la wallet de hardware!
Manten tu {hardwareWallet} conectada, desbloqueada y en la aplicación PIVX." # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Confirma la importación en tu Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "No hay ningún dispositivo disponible
No se pudo encontrar una wallet de hardware; ¡Conéctala y desbloquéala!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "" # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Se perdió la conexión con {hardwareWallet}
Parece que {hardwareWalletProductionName} se desconectó en mitad de la operación, ¡ups!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} está esperando
Desbloquea tu {hardwareWalletProductionName} o finaliza este mensaje" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Confirmar Voto" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "¿Está seguro? Se necesitan 60 minutos para cambiar el voto." # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Confirma tu transacción" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Tu llave privada de Masternode" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Guarda esta llave privada y cópiala en la configuración de tu VPS
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Verifica tu direccion" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "No se pudo recuperar tu masternode. Por favor vuelve a importarlo." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "No se pudo recuperar tu cuenta. Por favor vuelva a importarlo." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "¡Aplicación instalada!" # App Installed! +PROPOSAL_FINALISED = "" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "" # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "" # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "" # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "" # Proposal is invalid. Error: +PROPOSAL_CREATED = "" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "" # You've already created that code! +CONFIRM_POPUP_DELETE_ACCOUNT = "" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/fr/translation.toml b/locale/fr/translation.toml new file mode 100644 index 000000000..3ebe8c866 --- /dev/null +++ b/locale/fr/translation.toml @@ -0,0 +1,335 @@ +amount = "Montant" # Amount +staking = "Staking" # Staking +wallet = "Portefeuille" # Wallet +display = "Afficher" # Display +activity = "Activité" # Activity +yes = "Oui" # Yes +no = "Non" # No +navDashboard = "Tableau de Bord" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Gouvernance" # Governance +navSettings = "Paramètres" # Settings +footerBuiltWithPivxLabs = "Construit avec 💜 par PIVX Labs 🇫🇷" # Built with 💜 by PIVX Labs +loading = "Chargement" # Loading +loadingTitle = "Mon portefeuille PIVX" # My PIVX Wallet is +dashboardTitle = "Tableau de bord" # Dashboard +dCardOneTitle = "Créer le" # Create a +dCardOneSubTitle = "Nouveau portefeuille" # New Wallet +dCardOneDesc = "Créer un nouveau portefeuille PIVX, qui offre les méthodes de sauvegarde et de sécurité les plus sûres." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Créer un nouveau portefeuille" # Create A New Wallet +dCardTwoTitle = "Créer un nouveau" # Create a new +dCardTwoSubTitle = "Portefeuille Vanité" # Vanity Wallet +dCardTwoDesc = "Créer un portefeuille avec un préfixe personnalisé, cela peut prendre beaucoup de temps !" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Créer un portefeuille Vanity" # Create A Vanity Wallet +dCardThreeTitle = "Accédez à votre" # Access your +dCardThreeSubTitle = "Portefeuille de hardware" # Ledger Wallet +dCardThreeDesc = "Utilisez votre portefeuille Ledger Hardware avec l'interface familière de l'application MPW." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Accéder à mon Ledger" # Access my Ledger +dCardFourTitle = "Aller à" # Go to +dCardFourSubTitle = "Mon portefeuille" # My Wallet +dCardFourDesc = "Importer un portefeuille PIVX à l'aide d'une clé privée, d'un xpriv ou d'une phrase de démarrage." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Importer le portefeuille" # Import Wallet +dCardFourButtonA = "Accéder à mon portefeuille" # Access My Wallet +vanityPrefixNote = "Remarque : les adresses commencent toujours par :" # Note: addresses will always start with: +vanityPrefixInput = "Adresse Préfixe" # Address Prefix +thisIsYourSeed = "Voici votre phrase d'introduction:" # This is your seed phrase: +writeDownSeed = "Notez-le à un seul endroit. Vous ne verrez que cela une fois!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Toute personne disposant d'une copie peut accéder à à tousvotre fonds" # Anyone with a copy of it can access all of your funds. +doNotShare = "Ne le partagez avec personne." # Do NOT share it with anyone. +digitalStoreNotAdvised = "NON il est conseillé de les stocker sous forme numérique." # It is NOT advised to store this digitally. +optionalPassphrase = "Phrase mot de passe Facultatif (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "J'ai écrit ma phrase d'introduction" # I have written down my seed phrase +importSeedValid = "La phrase source est valide !" # Seed Phrase is valid! +importSeedError = "La phrase source n'est pas valide !" # Seed Phrase is invalid! +importSeedErrorSize = "Une phrase clé doit comporter entre 12 et 24 mots !" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Seed Phrase contient des fautes de frappe ! Vérifiez soigneusement votre saisie" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "La phrase de semence semble invalide, mais l'utilisateur n'a pas tenu compte de l'avertissement." # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Démarrer" # Getting Started +secureYourWallet = "Protégez votre portefeuille" # Secure your wallet +unlockWallet = "Déverrouiller le portefeuille" # Unlock wallet +lockWallet = "Fermeture du portefeuille" # Lock wallet +encryptWallet = "Cryptage du portefeuille" # Encrypt wallet +encryptPasswordCurrent = "Mot de passe actuel" # Current Password +encryptPasswordFirst = "Entrer le mot de passe" # Enter Password +encryptPasswordSecond = "Réintroduire le mot de passe" # Re-enter Password +encrypt = "Crypter" # Encrypt +changePassword = "Changer le mot de passe" # Change Password +balanceBreakdown = "Composition de la balance" # Balance Breakdown +viewOnExplorer = "Voir dans l'explorateur" # View on Explorer +export = "Exporter" # Export +refreshAddress = "Mise à jour de l'Adresse" # Refresh address +redeemOrCreateCode = "Ouvrir ou Créer un Code" # Redeem or Create Code +address = "Adresse" # Address +receivingAddress = "Adresse de réception" # Receiving address +sendAmountCoinsMax = "Max" # MAX +paymentRequestMessage = "Description (de l'opérateur)" # Description (from the merchant) +send = "Envoyer" # Send +receive = "Recevoir" # Receive +contacts = "Contacts" # Contacts +name = "Nom" # Name +username = "Nom d'utilisateur" # Username +addressOrXPub = "Adresse ou XPub" # Address or XPub +back = "Retour" # Back +chooseAContact = "Sélectionner un contact" # Choose a Contact +createContact = "Création d'un contact" # Create Contact +encryptFirstForContacts = "Une fois que vous avez touché \"{button}\" dans le tableau de bord, vous pouvez créer un contact pour faciliter la réception des PIV !" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Partager l'URL du contact" # Share Contact URL +setupYourContact = "Configurez votre contact" # Setup your Contact +receiveWithContact = "Recevoir par un simple contact basé sur le nom d'utilisateur" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Seul partager votre contact avec des personnes de confiance (famille, amis)" # Only share your Contact with trusted people (family, friends) +changeTo = "Modifier pour" # Change to +contact = "Contact" # Contact +xpub = "XPub" # XPub +addContactTitle = "Ajouter {strName} aux contacts" # Add {strName} to Contacts +addContactSubtext = "Une fois ajouté, vous pourrez envoyer des transactions à {strName} par leur nom (en tapant ou en cliquant), plus d'adresses, c'est facile." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "S'assurer qu'il s'agit bien d'un produit authentique \"{strName}\", n'acceptez pas de demandes de contact provenant de sources inconnues ! " # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Changer \"{strName}\" Contact" # Change "{strName}" Contact +newName = "Nouvelle dénomination" # New Name +removeContactTitle = "Supprimer {strName}?" # Remove {strName}? +removeContactSubtext = "Êtes-vous sûr de vouloir supprimer {strName} de vos contacts ?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Vous pouvez les ajouter à tout moment à l'avenir." # You can add them again any time in the future. +privateKey = "Clé privée" # Private Key +viewPrivateKey = "Montrer la clé privée ?" # View Private Key? +privateWarning1 = "Assurez-vous que personne ne regarde votre écran." # Make sure no one can see your screen. +privateWarning2 = "Toute personne possédant cette clé peut voler vos fonds" # Anyone with this key can steal your funds. +viewKey = "Voir la clé" # View key +pivxPromos = "est un système décentralisé de codes cadeaux d'une valeur de PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Introduisez votre code'PIVX Promos" # Enter your 'PIVX Promos' code +createName = "Nom de la promotion (facultatif)" # Promo Name (Optional) +createAmount = "Valeur promotionnelle" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "(Optionnel) Adresse du propriétaire" # (Optional) Owner Address +rewardHistory = "Historique des récompenses" # Reward History +loadMore = "Chargez plus" # Load more +mnControlYour = "Contrôlez votre" # Control your +mnSubtext = "À partir de ce guide, vous pouvez créer et accéder à un ou plusieurs masternodes." # From this tab you can create and access one or more masternodes +govSubtext = "Dans cet onglet, vous pouvez consulter les propositions et, si vous disposez d'un masternode, participer au DAO et voter!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Budget Mensuel" # Monthly Budget +govAllocBudget = "Budget Attribué" # Allocated Budget +govNextPayout = "Prochain paiement du Trésor" # Next Treasury Payout +govTableStatus = "ÉTAT" # STATUS +govTableName = "NOM" # NAME +govTablePayment = "PAIEMENT" # PAYMENT +govTableVotes = "VOTES" # VOTES +govTableVote = "VOTE" # VOTE +contestedProposalsDesc = " Il s'agit des propositions qui ont reçu un nombre écrasant de votes négatifs, ce qui en fait probablement des spams ou des propositions très contestables." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Choisissez une devise d'affichage :" # Choose a display currency: +priceProvidedBy = "Les prix sont fournis par" # Price data provided by +settingsDecimals = "Solde Décimales :" # Balance Decimals: +settingsExplorer = "Choisissez un explorateur :" # Choose an explorer: +settingsLanguage = "Choisissez une langue :" # Choose a Language: +settingsPivxNode = "Choisir un node PIVX :" # Choose a PIVX node: +settingsAutoSelectNet = "Sélection Automatique des Explorateurs et des Nodes" # Auto-select Explorers and Nodes +settingsAnalytics = "Choisissez votre niveau d'analyse :" # Choose your analytics contribution level: +settingsToggleDebug = "Mode de débogage" # Debug Mode +settingsToggleTestnet = "Mode testnet" # Testnet Mode +settingsToggleAdvancedMode = "Mode avancé" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Cela permet d'accéder à des fonctionnalités et à une personnalisation plus poussées, mais peut s'avérer difficile et potentiellement dangereux pour les utilisateurs inexpérimentés !" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Votre {network} portefeuille n'est pas sauvé !" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Votre {network} compte est en danger !" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Si vous changez de compte {network} avant de l'avoir sauvegardé, vous perdrez le compte! " # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Êtes-vous vraiment sûr ?" # Are you really sure? +transparencyReport = "Rapport de transparence" # Transparency Report +hit = "Un ping indiquant le chargement d'une application, aucune donnée unique n'est envoyée." # A ping indicating an app load, no unique data is sent. +time_to_sync = "Le temps en secondes que le MPW a pris pour se synchroniser pour la dernière fois." # The time in seconds it took for MPW to last synchronise. +transaction = "Un ping indiquant un Tx, aucune donnée unique n'est envoyée mais peut être déduite de l'heure sur le réseau." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Désactivé" # Disabled +analyticMinimal = "Minimum" # Minimal +analyticBalanced = "Solde" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Échec de la récupération du compte" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Une erreur s'est produite lors de la récupération de votre compte.
Veuillez réimporter votre portefeuille en utilisant la clé suivante :" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Heure" # Time +description = "Description" # Description +activityBlockReward = "Récompense en bloc" # Block Reward +activitySentTo = "Envoyé à {r}" # Sent to {r} +activitySelf = "soi-même" # self +activityShieldedAddress = "Adresse protégée" # Shielded address +activityDelegatedTo = "Délégué à {r}" # Delegated to {r} +activityUndelegated = "Non délégué" # Undelegated +activityUnknown = "Transaction inconnu" # Unknown Tx +password = "Mot de passe" # Password +walletUnlock = "Déverrouillez votre portefeuille" # Unlock your wallet +walletPassword = "Mot de passe du portefeuille" # Wallet password +walletUnlockCreateMN = "Débloquez pour créer votre Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Débloquez pour démarrer votre Masternode!" # Unlock to start your Masternode! +walletUnlockProposal = "Débloquez pour créer une proposition! Débloquez pour démarrer votre Masternode !" # Unlock to create a proposal! +walletUnlockPromo = "Déverrouillez pour finaliser votre code promo!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Déverrouillez pour envoyer votre transaction!" # Unlock to send your transaction! +walletUnlockStake = "Déverrouillez pour Staker votre" # Unlock to stake your +walletUnlockUnstake = "Débloquer pour Unstake votre" # Unlock to unstake your +changelogTitle = "Quoi de neuf en" # What's New in +popupSetColdAddr = "Définissez votre adresse de Cold Staking" # Set your Cold Staking address +popupCurrentAddress = "Adresse actuelle :" # Current address: +popupColdStakeNote = "Une Cold Address mise des pièces en votre nom, mais ne peut pas en dépenser. Il est donc possible d'utiliser Cold Address d'un inconnu en toute sécurité !" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Exemple :" # Example: +popupWalletLock = "Voulez-vous verrouiller votre portefeuille ?" # Do you want to lock your wallet? +popupWalletWipe = "Voulez-vous effacer les données privées de votre portefeuille ?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Vous devrez saisir votre mot de passe pour accéder à vos fonds." # You will need to enter your password to access your funds +popupWalletWipeNote = "Vous perdrez l'accès à vos fonds si vous n'avez pas sauvegardé votre clé privée ou votre phrase de base." # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Phrase semence inattendue" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "La phrase de semence est soit invalide, soit n'a pas été généré par MPW.
Voulez-vous toujours continuer ?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Créer une proposition" # Create Proposal +popupCreateProposalCost = "Coût" # Cost +popupProposalName = "Nom de la proposition" # Proposal Name +popupProposalAddress = "Adresse de la proposition (optionnel)" # Proposal Address (Optional) +popupProposalDuration = "Durée en cycles" # Duration in cycles +popupProposalPerCycle = "par cycle" # per cycle +popupProposalEncryptFirst = "Vous devez appuyer sur \"{button}\" avant de pouvoir créer des propositions !" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Vote Hash :" # Vote Hash: +popupProposalFinalisedNote = "Félicitations pour le lancement de votre proposition!
Les propriétaires de Masternodes peuvent utiliser votre Hash de vote pour voter à partir de portefeuilles autres que MPW, alors assurez-vous d'ajouter ceci à votre message sur le forum, le cas échéant!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Bonne chance dans votre voyage à travers le DAO, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Veuillez confirmer qu'il s'agit bien de l'adresse figurant sur votre" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Confirmant..." # Confirming... +proposalFinalisationRemaining = "restants" # remaining +proposalFinalisationExpired = "Proposition expirée" # Proposal Expired +proposalFinalisationReady = "Prêt à soumettre" # Ready to submit +proposalPassing = "Passant" # Passing +proposalFailing = "Échouant" # Failing +proposalTooYoung = "Trop jeune" # Too Young +proposalFunded = "Financée" # Funded +proposalNotFunded = "Non financée" # Not Funded +proposalPaymentsRemaining = "l'installation(s) restants
de" # installment(s) remaining
of +proposalPaymentTotal = "totale" # total +proposalNetYes = "Net Oui" # Net Yes +popupConfirm = "Confirmer" # Confirm +popupClose = "Fermer" # Close +popupCancel = "Annuler" # Cancel +chartPublicAvailable = "Disponible au public" # Public Available +timeDays = "Jours" # Days +timeHours = "Heures" # Hours +timeMinutes = "Minutes" # Minutes +timeSeconds = "Secondes" # Seconds +unhandledException = "Exception non traitée." # Unhandled exception. +syncStatusHistoryProgress = "Syncing History Chunks {current} of {total}" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Votre portefeuille est en cours de synchronisationxa0!
Vous pourrez l'utiliser pleinement une fois cette opération terminée." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Synchronisation terminéexa0!
Votre portefeuille est prêt à être utiliséxa0!" # Sync Finished!
Your wallet is ready to use! +contestedProposalsTitle = "Propositions contestées" # Contested Proposals +accountDeleted = "" # Your account has been successfully deleted! +activityReceivedWith = "Reçu avec {s}" # Received with {s} +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Erreur interne, veuillez réessayer plus tard" # Internal error, please try again later +FAILED_TO_IMPORT = "Échec de l'importation ! Mot de passe invalide" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = "Erreur d'importation du Hardware Wallet." # Failed to import Hardware Wallet. +UNSUPPORTED_CHARACTER = "Le caractère {char} n'est pas pris en charge dans les adresses ! (Non compatible avec Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Ce navigateur ne prend pas en charge Web Workers (JS multi-threaded), Malheureusement, il n'est pas possible de générer des portefeuilles Vanity!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Adresse PIVX non valide !
{address}" # Invalid PIVX address!
{address} +TESTNET_ENCRYPTION_DISABLED = "Modo Testnet activé !
Cryptage du portefeuille désactivé" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Ce mot de passe est un peu court !
Utiliser au moins {MIN_PASS_LENGTH} caractères." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Vos mots de passe ne correspondent pas!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Vous êtes protégé ! 🔐
Bravo, PIVian blindé !" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Mot de passe incorrect!" # Incorrect password! +INVALID_AMOUNT = "Valeur non valide!
" # Invalid amount!
+TX_SENT = "Transaction envoyée!" # Transaction sent! +TX_FAILED = "Échec de la Transaction!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "n'est pas un récepteur de paiement valide" # is not a valid payment receiver +VALIDATE_AMOUNT_LOW = "
La valeur minimale est de {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} limite décimale dépassée" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Adresse de Staking défini!
Poursuivre avec le unstake!" # Staking Address set!
Now go ahead and unstake! +STAKE_ADDR_SET = "Adresse du Cold Staking défini!
Cette adresse sera utilisée à l'avenir pour la réalisation de Stake.." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "L'adresse du Cold Staking n'est pas valide !" # Invalid Cold Staking address! +CONFIRM_UNSTAKE_H_WALLET = "Confirmer votre Unstake
Confirmez le TX dans votre {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Confirmez votre transaction
Confirmez le TX dans votre {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Adresse de Staking définie!
>Poursuivre avec la stake" # Staking Address set!
Now go ahead and stake! +STAKE_NOT_SEND = "Dans ce cas, utilisez l'écran Stake, et non l'écran d'envoi!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Adresse PIVX non valide!
Préfixe non valide {address} (Vous devez commencer par{addressPrefix})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Adresse PIVX non valide!
Préfixe non valide {address} (Vous devez commencer par{addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Vous ne pouvez pas envoyer 'rien" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Gardez votre portefeuille!
Tableau de bord ➜ Définir le mot de passe" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Cryptez et/ou sauvegardez vos clés avant de partir, car vous risquez de les perdre!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Cet appareil n'est pas équipé d'une caméra!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger n'est pas compatible avec Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Échec de la synchronisation! Réessayer plus tard.
Vous pouvez essayer de vous reconnecter via Paramètres." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Votre masternode n'est pas encore activé!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Vote envoyé!" # Vote submitted! +VOTED_ALREADY = "Vous avez déjà voté sur cette proposition ! Attendre 1 heure" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Échec de la vérification de la signature, vérifiez la clé privée de votre masternode maître." # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode créé!
Attendre 15 confirmations pour continuer" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Accéder à un masternode avant de voter!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Votre masternode est hors ligne, essayons de le démarrer" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternode a débuté!" # Masternode started! +MN_RESTARTED = "Masternode redémarré!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternode initié!
Il sera bientôt en ligne" # Masternode started!
It'll be online soon +MN_START_FAILED = "Masternode initié !" # Masternode started! +MN_RESTART_FAILED = "Masternode redémarré!" # Masternode restarted! +MN_DESTROYED = "Masternode détruit!
Vous pouvez maintenant dépenser vos pièces." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "L'état de votre masternode est" # Your masternode status is +MN_STATE = "L'état de votre masternode est {state}" # Your masternode is in {state} state +MN_BAD_IP = "L'adresse IP n'est pas valide!" # The IP address is invalid! +MN_BAD_PRIVKEY = "La clé privée n'est pas valide" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Vous avez besoin {amount} plus {ticker} pour créer un Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Vous disposez d'un solde suffisant pour un Masternode, mais pas d'UTXO comme garantie valide de {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Il ne s'agit pas d'une UTXO appropriée pour un Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Impossible de se connecter au nœud RPC!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Vous devez appuyer sur \"{button}\" avant de pouvoir utiliser les contacts !" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Un nom est nécessaire !" # A name is required! +CONTACTS_NAME_TOO_LONG = "Le nom est trop long !" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Vous ne pouvez pas vous ajouter comme contact !" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Le contact existe déjà!
Vous avez déjà enregistré ce contact" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Le nom du contact existe déjà!
Il pourrait s'agir d'une tentative d'hameçonnage, attention!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Le contact existe déjà!
Un contact est déjà appelé \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Le contact existe déjà, mais sous un nom différent!
Vous avez {newName} sauvegardé comme {oldName} dans vos contacts" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Ce n'est pas un QR de contact!" # This isn't a Contact QR! +CONTACTS_ADDED = "Nouveau contact ajouté!
{strName} a été ajouté, bravo!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Vous n'avez pas de contacts!" # You have no contacts! +SWITCHED_EXPLORERS = "Explorateur échangé!
En utilisant maintenant le {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Nœud commuté!
L'utilisation de la {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Niveau d'analyse modifié!
Il est maintenant {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Changement de mode de synchronisation !
Utiliser la synchronisation maintenant {sync}" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Il n'est pas possible de passer en mode Testnet !
Un portefeuille est déjà chargé." # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Le mode hors-ligne est actif !
Veuillez désactiver le mode hors connexion pour les transactions automatiques" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "S'il vous plaît, {unlock} votre portefeuille avant d'envoyer des transactions !" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "O Firefox ne supporte pas cela !
Malheureusement, Firefox ne prend pas en charge les portefeuilles hardware" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Portefeuille de Hardware prêt!
Gardez votre {hardwareWallet} connecté, déverrouillé et dans l'application PIVX" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Confirmez l'importation dans votre Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Aucun dispositif disponible
Il n'a pas été possible de trouver un portefeuille de hardware; brancher et déverrouiller!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "Le système d'exploitation a refusé l'accès Avez-vous ajouté les règles udevxa0?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "Le système d'exploitation refuse l'accès Veuillez vérifier les paramètres de votre système d'exploitation." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Perte de connexion avec le {hardwareWallet}
Oops! Il semble que {hardwareWalletProductionName} a été déconnecté au milieu de l'opération." # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} est en mode veille
Veuillez débloquer le vôtre {hardwareWalletProductionName} ou compléter l'introduction actuelle" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = "b> {hardwareWallet}

{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Confirmer le vote" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Êtes-vous sûr ? Il faut 60 minutes pour changer de vote" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Confirmez votre transaction" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Clé privée de votre Masternode" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Enregistrez cette clé privée et copiez-la dans votre configuration VPS.
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Vérifiez votre adresse" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Échec de la récupération de votre masternode. S'il vous plaît, le réimporter." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Échec de la récupération de votre compte. S'il vous plaît, le réimporter." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "Application installée!" # App Installed! +PROPOSAL_FINALISED = "Proposal Launched!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "La proposition n'a pas encore été confirmée" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "La proposition a expiré. Créez-en un nouveau." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Échec de la finalisation de la proposition." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Créez ou importez votre portefeuille pour continuer" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Pas assez de fonds pour créer une proposition." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "La proposition n'est pas valide. Erreur:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Proposition crééexa0!
Attendez les confirmations, puis finalisez votre propositionxa0!" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Le montant minimum est de {min}xa0{ticker}xa0!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Votre appareil ne peut créer que {quantité} de codes à la fois !" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Vous n'avez pas assez de {ticker} pour créer ce code !" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Vous avez déjà créé ce code !" # You've already created that code! +CONFIRM_POPUP_DELETE_ACCOUNT = "Cette opération supprimera toutes vos données, y compris les contacts des masternodes et les clés privées !" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Êtes-vous sûr ?" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/hi/translation.toml b/locale/hi/translation.toml new file mode 100644 index 000000000..b111339ea --- /dev/null +++ b/locale/hi/translation.toml @@ -0,0 +1,335 @@ +amount = "अमाउन्ट" # Amount +staking = "स्टेकिंग" # Staking +wallet = "वॉलेट" # Wallet +display = "डिस्प्ले" # Display +activity = "एक्टिविटी" # Activity +yes = "हाँ" # Yes +no = "नहीं" # No +navDashboard = "डैशबोर्ड" # Dashboard +navStake = "स्टेक" # Stake +navMasternode = "मास्टरनोड" # Masternode +navGovernance = "गवर्नेंस" # Governance +navSettings = "सेटिंग्स" # Settings +footerBuiltWithPivxLabs = "PIVX लैब्स द्वारा 💜 के साथ निर्मित" # Built with 💜 by PIVX Labs +loading = "लोड हो रहा है" # Loading +loadingTitle = "मेरा PIVX वॉलेट है" # My PIVX Wallet is +dashboardTitle = "डैशबोर्ड" # Dashboard +dCardOneTitle = "बनाएं" # Create a +dCardOneSubTitle = "नया वॉलेट" # New Wallet +dCardOneDesc = "एक नया PIVX वॉलेट बनाएं, जो सबसे सुरक्षित बैकअप और सुरक्षा विधियों की पेशकश करता है।" # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "एक नया वॉलेट बनाएं" # Create A New Wallet +dCardTwoTitle = "नया बनाएं" # Create a new +dCardTwoSubTitle = "वैनिटी वॉलेट" # Vanity Wallet +dCardTwoDesc = "कस्टम प्रीफिक्स के साथ एक वॉलेट बनाएं, इसमें लंबा समय लग सकता है!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "एक वैनिटी वॉलेट बनाएं" # Create A Vanity Wallet +dCardThreeTitle = "एक्सेस करें" # Access your +dCardThreeSubTitle = "लेजर वॉलेट" # Ledger Wallet +dCardThreeDesc = "MPW के परिचित इंटरफ़ेस के साथ अपने लेजर हार्डवेयर वॉलेट का उपयोग करें।" # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "मेरा लेजर खोलें" # Access my Ledger +dCardFourTitle = "गो टू" # Go to +dCardFourSubTitle = "मेरा वॉलेट" # My Wallet +dCardFourDesc = "एक प्राइवेट की, xpriv, या सीड फ्रेज का उपयोग करके PIVX वॉलेट इम्पोर्ट करें।" # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "इम्पोर्ट वॉलेट" # Import Wallet +dCardFourButtonA = "मेरा वॉलेट एक्सेस करें" # Access My Wallet +vanityPrefixNote = "नोट: एड्रेसिस हमेशा से शुरू होंगे:" # Note: addresses will always start with: +vanityPrefixInput = "एड्रेस प्रीफिक्स" # Address Prefix +thisIsYourSeed = "यह आपका सीड फ्रेज है:" # This is your seed phrase: +writeDownSeed = "इसे कहीं लिख लें। आप इसे केवल देख पाएंगे एक बार!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "जिसके पास इसकी एक प्रति हो, वह आपके सभी फंड्स तक पहुंच सकता है।" # Anyone with a copy of it can access all of your funds. +doNotShare = "इसे किसी के साथ भी साझा न करें।" # Do NOT share it with anyone. +digitalStoreNotAdvised = "इसे डिजिटल रूप में स्टोर करना सुझावित नहीं है।" # It is NOT advised to store this digitally. +optionalPassphrase = "वैकल्पिक पासफ़्रेज़ (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "मैंने अपनी सीड फ्रेज लिख ली है" # I have written down my seed phrase +importSeedValid = "सीड फ्रेज मान्य है!" # Seed Phrase is valid! +importSeedError = "सीड फ्रेज अमान्य है!" # Seed Phrase is invalid! +importSeedErrorSize = "एक सीड फ्रेज 12 या 24 शब्दों का होना चाहिए!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "सीड फ्रेज में टाइपिंग की गलतियाँ हैं! कृपया अपनी इनपुट को ध्यान से जांचें" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "सीड फ्रेज अमान्य प्रतीत होता है, लेकिन उपयोगकर्ता द्वारा चेतावनी को अनदेखा कर दिया गया" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "शुरू करना" # Getting Started +secureYourWallet = "अपने वॉलेट को सुरक्षित करें" # Secure your wallet +unlockWallet = "अनलॉक वॉलेट" # Unlock wallet +lockWallet = "लॉक वॉलेट" # Lock wallet +syncStatusHistoryProgress = "सिंकिंग हिस्ट्री चंक्स {current} ऑफ {total}" # Syncing History Chunks {current} of {total} +syncLoadingSaplingProver = "SHIELD पैरामीटर लोड हो रहे हैं..." # Loading SHIELD parameters... +syncShieldProgress = "लोडिंग शील्डेड ब्लॉक्स {current} ऑफ {total}" # Loading shielded blocks {current} of {total} +syncStatusStarting = "आपका वॉलेट सिंक हो रहा है!
यह पूरा होने के बाद आप इसे पूरी तरह से उपयोग कर पाएंगे।" # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "सिंक पूरा हुआ!
आपका वॉलेट उपयोग के लिए तैयार है!" # Sync Finished!
Your wallet is ready to use! +encryptWallet = "एन्क्रिप्ट वॉलेट" # Encrypt wallet +encryptPasswordCurrent = "करेंट पासवर्ड" # Current Password +encryptPasswordFirst = "एंटर पासवर्ड" # Enter Password +encryptPasswordSecond = "री-एंटर पासवर्ड" # Re-enter Password +encrypt = "एन्क्रिप्ट" # Encrypt +changePassword = "चेंज पासवर्ड" # Change Password +balanceBreakdown = "बैलेंस ब्रेकडाउन" # Balance Breakdown +viewOnExplorer = "व्यू ऑन एक्सप्लोरर" # View on Explorer +export = "एक्सपोर्ट" # Export +refreshAddress = "रिफ्रेश एड्रेस" # Refresh address +redeemOrCreateCode = "रिडीम या क्रिएट कोड" # Redeem or Create Code +address = "एड्रेस" # Address +receivingAddress = "रीसिविंग एड्रेस" # Receiving address +sendAmountCoinsMax = "मैक्स" # MAX +paymentRequestMessage = "विवरण (व्यापारी से)" # Description (from the merchant) +send = "भेजें" # Send +receive = "प्राप्त करें" # Receive +contacts = "संपर्क" # Contacts +name = "नाम" # Name +username = "उपयोगकर्ता नाम" # Username +addressOrXPub = "एड्रेस या XPub" # Address or XPub +back = "बैक" # Back +chooseAContact = "एक संपर्क चुनें" # Choose a Contact +createContact = "संपर्क बनाएं" # Create Contact +encryptFirstForContacts = "जब आप डैशबोर्ड में \"{button}\" पर क्लिक करते हैं, तो आप PIV प्राप्त करना आसान बनाने के लिए एक संपर्क बना सकते हैं!" # Once you hit \"{button}\" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "शेयर कांटेक्ट URL" # Share Contact URL +setupYourContact = "अपने संपर्क को सेटअप करें" # Setup your Contact +receiveWithContact = "सिंपल यूजरनेम-बेस्ड कांटेक्ट का उपयोग करके प्राप्त करें" # Receive using a simple username-based Contact +onlyShareContactPrivately = "केवल अपने कांटेक्ट को विश्वसनीय लोगों (परिवार, दोस्तों) के साथ साझा करें" # Only share your Contact with trusted people (family, friends) +changeTo = "चेंज टू" # Change to +contact = "संपर्क" # Contact +xpub = "XPub" # XPub +addContactTitle = "{strName} को संपर्क में जोड़ें" # Add {strName} to Contacts +addContactSubtext = "एक बार जोड़े जाने पर आप {strName} को उनके नाम से (टाइपिंग या क्लिकिंग के माध्यम से) ट्रांजैक्शन भेज सकेंगे, अब और एड्रेसिस की ज़रूरत नहीं, सब कुछ आसानी से।" # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "सुनिश्चित करें कि यह असली \"{strName}\" है, अज्ञात स्रोतों से संपर्क अनुरोध स्वीकार न करें!" # Ensure that this is the real \"{strName}\", do not accept Contact requests from unknown sources! +editContactTitle = "\"{strName}\" संपर्क बदलें" # Change \"{strName}\" Contact +newName = "नया नाम" # New Name +removeContactTitle = "{strName} को हटाएं?" # Remove {strName}? +removeContactSubtext = "क्या आप वाकई अपने संपर्क से {strName} को हटाना चाहते हैं?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "आप उन्हें भविष्य में कभी भी फिर से जोड़ सकते हैं|" # You can add them again any time in the future. +privateKey = "प्राइवेट की" # Private Key +viewPrivateKey = "व्यू प्राइवेट की?" # View Private Key? +privateWarning1 = "सुनिश्चित करें कि कोई आपकी स्क्रीन नहीं देख सकता।" # Make sure no one can see your screen. +privateWarning2 = "इस की के साथ कोई भी आपके फंड्स चुरा सकता है।" # Anyone with this key can steal your funds. +viewKey = "व्यू की" # View key +saveWalletFile = "सेव वॉलेट फाइल" # Save Wallet File +pivxPromos = "PIV के गिफ्ट कोड के लिए एक विकेन्द्रीकृत प्रणाली है" # is a decentralised system for gift codes worth PIV +redeemInput = "अपना 'PIVX प्रमोज़' कोड दर्ज करें" # Enter your 'PIVX Promos' code +createName = "प्रमो नाम (वैकल्पिक)" # Promo Name (Optional) +createAmount = "प्रोमो अमाउन्ट" # Promo Amount +stake = "स्टेक" # Stake +stakeUnstake = "अनस्टेक" # Unstake +ownerAddress = "(वैकल्पिक) ओनर अड्रेस" # (Optional) Owner Address +rewardHistory = "रिवार्ड हिस्ट्री" # Reward History +loadMore = "लोड मोर" # Load more +mnControlYour = "नियंत्रण करें" # Control your +mnSubtext = "इस टैब से आप एक या अधिक मास्टरनोड्स बना सकते हैं और एक्सेस कर सकते हैं।" # From this tab you can create and access one or more masternodes +govSubtext = "इस टैब से आप प्रस्तावों को देख सकते हैं और यदि आपके पास एक मास्टरनोड है, तो आप DAO का हिस्सा बन सकते हैं और वोट कर सकते हैं!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "मासिक बजट" # Monthly Budget +govAllocBudget = "आवंटित बजट" # Allocated Budget +govNextPayout = "अगला ट्रेजरी पेआउट" # Next Treasury Payout +govTableStatus = "स्टेटस" # STATUS +govTableName = "नाम" # NAME +govTablePayment = "पेमेंट" # PAYMENT +govTableVotes = "वोट्स" # VOTES +govTableVote = "वोट" # VOTE +contestedProposalsTitle = "विवादित प्रस्ताव" # Contested Proposals +contestedProposalsDesc = "ये प्रस्ताव हैं जिन्हें भारी मात्रा में डाउनवोट मिले हैं, जिससे यह संभावित रूप से स्पैम या अत्यधिक विवादित प्रस्ताव हो सकता है।" # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "एक प्रदर्शन मुद्रा चुनें:" # Choose a display currency: +priceProvidedBy = "प्राइस डेटा प्रदान किया गया" # Price data provided by +settingsDecimals = "बैलेंस डेसिमल्स:" # Balance Decimals: +settingsExplorer = "एक एक्सप्लोरर चुनें:" # Choose an explorer: +settingsLanguage = "भाषा चुनें:" # Choose a Language: +settingsPivxNode = "एक PIVX नोड चुनें:" # Choose a PIVX node: +settingsAutoSelectNet = "ऑटो-एक्सप्लोरर और नोड्स का चयन करें" # Auto-select Explorers and Nodes +settingsAnalytics = "अपने एनालिटिक्स योगदान स्तर को चुनें:" # Choose your analytics contribution level: +settingsToggleDebug = "डीबग मोड" # Debug Mode +settingsToggleTestnet = "टेस्टनेट मोड" # Testnet Mode +settingsToggleAdvancedMode = "एडवांस्ड मोड" # Advanced Mode +settingsToggleAdvancedModeSubtext = "यह गहरी कार्यक्षमता और अनुकूलन को अनलॉक करता है, लेकिन यह अनुभवहीन उपयोगकर्ताओं के लिए भारी और संभावित रूप से खतरनाक हो सकता है!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +settingsToggleAutoLockWallet = "ऑटो वॉलेट लॉक करें" # Auto Lock the Wallet +netSwitchUnsavedWarningTitle = "आपका {network} वॉलेट सेव नहीं है!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "आपका {network} खाता जोखिम में है!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "अगर आप {network} पर स्विच करते हैं बिना इसे सेव किए, तो आप खाता खो देंगे!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "क्या आप वास्तव में सुनिश्चित हैं?" # Are you really sure? +transparencyReport = "पारदर्शिता रिपोर्ट" # Transparency Report +hit = "एक पिंग जो ऐप लोड को इंगित करता है, कोई विशिष्ट डेटा नहीं भेजा जाता।" # A ping indicating an app load, no unique data is sent. +time_to_sync = "MPW को आखिरी बार समन्वयित होने में लगे सेकंड।" # The time in seconds it took for MPW to last synchronise. +transaction = "एक पिंग जो Tx को इंगित करता है, कोई विशिष्ट डेटा नहीं भेजा जाता है, लेकिन ऑन-चेन समय से अनुमानित किया जा सकता है।" # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "डिसेबल्ड" # Disabled +analyticMinimal = "मिनिमल" # Minimal +analyticBalanced = "बैलेंस्ड" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "खाता पुनर्प्राप्ति विफल" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "आपके खाते को पुनर्प्राप्त करने में एक त्रुटि थी।
कृपया निम्नलिखित की का उपयोग करके अपने वॉलेट को फिर से रिइम्पोर्ट करें:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "आईडी" # ID +time = "समय" # Time +description = "विवरण" # Description +accountDeleted = "आपका खाता सफलतापूर्वक हटा दिया गया है!" # Your account has been successfully deleted! +activityBlockReward = "ब्लॉक रिवार्ड" # Block Reward +activitySentTo = "भेजा गया {r} को" # Sent to {r} +activitySelf = "स्वयं" # self +activityShieldedAddress = "शिल्डेड एड्रेस" # Shielded address +activityReceivedWith = "प्राप्त {s} के साथ" # Received with {s} +activityDelegatedTo = "सौंपा गया {r} को" # Delegated to {r} +activityUndelegated = "असौंपा गया" # Undelegated +activityUnknown = "अज्ञात Tx" # Unknown Tx +password = "पासवर्ड" # Password +walletUnlock = "अनलॉक करें अपना वॉलेट" # Unlock your wallet +walletPassword = "वॉलेट पासवर्ड" # Wallet password +walletUnlockCreateMN = "अपना मास्टरनोड बनाने के लिए अनलॉक करें!" # Unlock to create your Masternode! +walletUnlockMNStart = "अपना मास्टरनोड शुरू करने के लिए अनलॉक करें!" # Unlock to start your Masternode! +walletUnlockProposal = "एक प्रपोजल बनाने के लिए अनलॉक करें!" # Unlock to create a proposal! +walletUnlockPromo = "अपने प्रोमो कोड को अंतिम रूप देने के लिए अनलॉक करें!" # Unlock to finalise your Promo Code! +walletUnlockTx = "अपना ट्रांजेक्शन भेजने के लिए अनलॉक करें!" # Unlock to send your transaction! +walletUnlockStake = "अपने स्टेक को अनलॉक करें" # Unlock to stake your +walletUnlockUnstake = "अपने अनस्टेक को अनलॉक करें" # Unlock to unstake your +changelogTitle = "क्या नया है" # What's New in +popupSetColdAddr = "अपना कोल्ड स्टेकिंग एड्रेस सेट करें" # Set your Cold Staking address +popupCurrentAddress = "वर्तमान एड्रेस:" # Current address: +popupColdStakeNote = "एक कोल्ड एड्रेस आपकी ओर से सिक्के स्टेक करता है, यह सिक्के खर्च नहीं कर सकता, इसलिए किसी अजनबी के कोल्ड एड्रेस का उपयोग करना भी सुरक्षित है!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "उदाहरण:" # Example: +popupWalletLock = "क्या आप अपने वॉलेट को लॉक करना चाहते हैं?" # Do you want to lock your wallet? +popupWalletWipe = "क्या आप अपने वॉलेट के प्राइवेट डेटा को मिटाना चाहते हैं?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "आपको अपने फंड्स तक पहुँचने के लिए अपना पासवर्ड दर्ज करना होगा" # You will need to enter your password to access your funds +popupWalletWipeNote = "यदि आपने अपने प्राइवेट की या सीड फ्रेज का बैकअप नहीं लिया है, तो आप अपने फंड्स तक पहुँच खो देंगे" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "अप्रत्याशित सीड फ्रेज" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "सीड फ्रेज या तो अमान्य है या MPW द्वारा उत्पन्न नहीं की गई थी।
क्या आप अभी भी आगे बढ़ना चाहते हैं?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "क्रिएट प्रपोजल" # Create Proposal +popupCreateProposalCost = "कोस्ट" # Cost +popupProposalName = "प्रपोजल नाम" # Proposal Name +popupProposalAddress = "प्रपोजल ऐड्रेस (वैकल्पिक)" # Proposal Address (Optional) +popupProposalDuration = "चक्रों में अवधि" # Duration in cycles +popupProposalPerCycle = "प्रति चक्र" # per cycle +popupProposalEncryptFirst = "आपको प्रपोजल बनाने से पहले \"{button}\" पर क्लिक करना होगा!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "वोट हैश:" # Vote Hash: +popupProposalFinalisedNote = "आपके प्रपोजल को लॉन्च करने पर बधाई!
मास्टरनोड ओनर आपके वोट हैश का उपयोग MPW के अलावा अन्य वॉलेट्स से वोट देने के लिए कर सकते हैं, इसलिए यदि लागू हो तो इसे अपने फोरम पोस्ट में शामिल करना सुनिश्चित करें!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "DAO के माध्यम से अपने यात्रा में शुभकामनाएँ, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "कृपया कन्फर्म करें कि यह वही एड्रेस है जो आप देख रहे हैं" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "कन्फर्मिंग..." # Confirming... +proposalFinalisationRemaining = "रिमेइनिंग" # remaining +proposalFinalisationExpired = "प्रपोजल एक्सपायर्ड" # Proposal Expired +proposalFinalisationReady = "रेडी टू सबमिट" # Ready to submit +proposalPassing = "पासिंग" # Passing +proposalFailing = "फेलिंग" # Failing +proposalTooYoung = "टू यंग" # Too Young +proposalFunded = "फंडेड" # Funded +proposalNotFunded = "नॉट फंडेड" # Not Funded +proposalOverBudget = "ओवर बजट" # Over Budget +proposalPaymentsRemaining = "इंस्टॉलमेंट(s) रिमेनिंग
ऑफ" # installment(s) remaining
of +proposalPaymentTotal = "कुल" # total +proposalNetYes = "नेट हाँ" # Net Yes +popupConfirm = "कन्फ़र्म" # Confirm +popupClose = "क्लोज" # Close +popupCancel = "कैंसल" # Cancel +chartPublicAvailable = "पब्लिक अवेलेबल" # Public Available +chartImmatureBalance = "इम्मैच्योर बैलेंस" # Immature balance +timeDays = "दिन" # Days +timeHours = "घंटे" # Hours +timeMinutes = "मिनट" # Minutes +timeSeconds = "सेकंड" # Seconds +unhandledException = "अनहैंडल्ड एक्सेप्शन।" # Unhandled exception. +useShieldInputs = "यूज़ शील्ड इनपुट्स" # Use shield inputs +newShieldAddress = "गेट न्यू शील्ड एड्रेस" # Get new shield address +shieldAddress = "शील्ड एड्रेस" # Shield address +cantShieldToExc = "यह एड्रेस शील्ड ट्रांसफर का समर्थन नहीं करता" # This address does not support shield transfers +badSaplingRoot = "सिंक करते समय एक त्रुटि हुई। रिसिंकिंग कर रहे हैं स्क्रैच से(खराब सैपलिंग रूट)" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "क्रिएटिंग शील्ड ट्रांजेक्शन..." # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "आंतरिक त्रुटि, कृपया कुछ समय बाद पुनः प्रयास करें" # Internal error, please try again later +FAILED_TO_IMPORT = "इम्पोर्ट में विफल! अवैध पासवर्ड" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = " हार्डवेयर वॉलेट इम्पोर्ट में विफल।" # Failed to import Hardware Wallet. +TESTNET_ENCRYPTION_DISABLED = "टेस्टनेट मोड चालू है!
वॉलेट एन्क्रिप्शन निष्क्रिय" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "वह पासवर्ड थोड़ी छोटी है!
कम से कम {MIN_PASS_LENGTH} अक्षर का उपयोग करें।" # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "आपके पासवर्ड मेल नहीं खाते!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "आप सुरक्षित हैं! 🔐
गुड स्टफ, आर्मर्ड PIVian!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "गलत पासवर्ड!" # Incorrect password! +INVALID_AMOUNT = "अवैध अमाउन्ट!" # Invalid amount!
+TX_SENT = "ट्रांजेक्शन सेंट!" # Transaction sent! +TX_FAILED = "ट्रांजेक्शन फेल्ड!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "एक मान्य पेमेंट रिसीवर नहीं है" # is not a valid payment receiver +UNSUPPORTED_CHARACTER = "वर्ण '{char}' एड्रेसिस में समर्थित नहीं है! (Base58 संगत नहीं)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "इस ब्राउज़र में वेब वर्कर्स (मल्टी-थ्रेडेड JS) का समर्थन नहीं है, दुर्भाग्यवश आप वैनीटी वॉलेट नहीं बना सकते!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "अवैध PIVX अमाउन्ट!" # Invalid PIVX address!
{address} +VALIDATE_AMOUNT_LOW = "
न्यूनतम अमाउन्ट {minimumAmount} {coinTicker} है!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} दशमलव सीमा से अधिक" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "स्टेकिंग एड्रेस सेट हो गया!
अब आगे बढ़ें और अनस्टेक करें!" # Staking Address set!
Now go ahead and unstake! +CONFIRM_UNSTAKE_H_WALLET = "अपने अनस्टेक की कन्फर्म करें
अपने {strHardwareName} पर TX कन्फर्म करें" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "अपने ट्रांजैक्शन कन्फर्म करें
अपने {strHardwareName} पर TX कन्फर्म करें" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "स्टेकिंग एड्रेस सेट हो गया!
अब आगे बढ़ें और स्टेक करें!" # Staking Address set!
Now go ahead and stake! +STAKE_ADDR_SET = "कोल्ड एड्रेस सेट हो गया!
भविष्य की स्टेकिंग के लिए इस एड्रेस का उपयोग होगा।" # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "अवैध कोल्ड स्टेकिंग एड्रेस" # Invalid Cold Staking address! +STAKE_NOT_SEND = "यहाँ, स्टेक स्क्रीन का उपयोग करें, न कि सेंड स्क्रीन!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "अवैध PIVX एड्रेस!
खराब लंबाई ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "अवैध PIVX एड्रेस!
खराब प्रीफिक्स {address} (शुरू होना चाहिए {addressPrefix} से)" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "आप 'कुछ भी' नहीं भेज सकते!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "अपने वॉलेट को सेव करें!
डैशबोर्ड ➜ अपने वॉलेट को सुरक्षित करें" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "कृपया छोड़ने से पहले अपने कीज़ को एन्क्रिप्ट और/या बैकअप करें, अन्यथा आप उन्हें खो सकते हैं!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "इस डिवाइस में कोई कैमरा नहीं है!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "लेजर को कोल्ड स्टेकिंग के लिए समर्थन नहीं है" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "सिंक्रोनाइज़ेशन विफल!
कृपया बाद में पुनः प्रयास करें।
आप सेटिंग्स के माध्यम से पुनः कनेक्ट करने का प्रयास कर सकते हैं।" # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "आपका मास्टरनोड अभी सक्षम नहीं है!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "वोट सबमिट कर दिया गया!" # Vote submitted! +VOTED_ALREADY = "आपने इस प्रस्ताव के लिए पहले ही वोट दे दिया है! कृपया 1 घंटा प्रतीक्षा करें" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "सिग्नेचर सत्यापित करने में विफल, कृपया अपने मास्टरनोड की प्राइवेट की की जांच करें" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "मास्टरनोड बनाया गया!

आगे बढ़ने के लिए 15 कन्फर्मेशन प्रतीक्षा करें" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "वोट देने से पहले एक मास्टरनोड को एक्सेस करें" # Access a masternode before voting! +MN_OFFLINE_STARTING = "आपका मास्टरनोड ऑफ़लाइन है, हम इसे शुरू करने की कोशिश करेंगे" # Your masternode is offline, we will try to start it +MN_STARTED = "मास्टरनोड शुरू हो गया!" # Masternode started! +MN_RESTARTED = "मास्टरनोड पुनः शुरू हो गया!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "मास्टरनोड शुरू हो गया!
यह जल्द ही ऑनलाइन होगा" # Masternode started!
It'll be online soon +MN_START_FAILED = "मास्टरनोड शुरू नहीं हो सका!" # Masternode started! +MN_RESTART_FAILED = "मास्टरनोड पुनः शुरू नहीं हो सका!" # Masternode restarted! +MN_DESTROYED = "मास्टरनोड नष्ट हो गया!
आपके सिक्के अब खर्च करने योग्य हैं।" # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "आपका मास्टरनोड स्टेटस है" # Your masternode status is +MN_STATE = "आपका मास्टरनोड {state} स्स्टेट में है" # Your masternode is in {state} state +MN_BAD_IP = "IP एड्रेस अमान्य है!" # The IP address is invalid! +MN_BAD_PRIVKEY = "प्राइवेट की अमान्य है" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "आपको एक मास्टरनोड बनाने के लिए {amount} और {ticker} की आवश्यकता है!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "आपके पास मास्टरनोड के लिए पर्याप्त बैलेंस है, लेकिन {amount} {ticker} का कोई मान्य कोलेटरल UTXO नहीं है" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "यह मास्टरनोड के लिए उपयुक्त UTXO नहीं है" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "RPC नोड से कनेक्ट नहीं हो पा रहा है!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "आपको संपर्कों का उपयोग करने से पहले \"{button}\" पर क्लिक करना होगा!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "नाम आवश्यक है!" # A name is required! +CONTACTS_NAME_TOO_LONG = "वह नाम बहुत लंबा है!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "आप खुद को संपर्क के रूप में जोड़ नहीं सकते!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "संपर्क पहले से मौजूद है!
आपने पहले ही इस संपर्क को सेव किया है" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "संपर्क का नाम पहले से मौजूद है!
यह संभावित रूप से एक फ़िशिंग प्रयास हो सकता है, सतर्क रहें!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "संपर्क पहले से मौजूद है!
एक संपर्क पहले से ही \"{strNewName}\" के नाम से है!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "संपर्क पहले से मौजूद है, लेकिन अलग नाम के तहत!
आपके संपर्कों में {newName} को {oldName} नाम से सेव है" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "यह संपर्क QR नहीं है!" # This isn't a Contact QR! +CONTACTS_ADDED = "नया संपर्क जोड़ा गया!
{strName} जोड़ दिया गया, हुर्रे!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "आपके पास कोई संपर्क नहीं है!" # You have no contacts! +PROPOSAL_FINALISED = "प्रोपोज़ल लॉन्च हो गया!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "प्रोपोज़ल अभी तक कन्फर्म नहीं हुआ है" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "प्रोपोज़ल समाप्त हो गया है। एक नया बनाएं।" # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "प्रोपोज़ल को अंतिम रूप देने में विफल।" # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "जारी रखने के लिए अपना वॉलेट बनाएं या इम्पोर्ट करें" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "प्रोपोज़ल बनाने के लिए पर्याप्त फंड्स नहीं है।" # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "प्रोपोज़ल अमान्य है। त्रुटि:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "प्रोपोज़ल बन गया!
कन्फर्मेशन की प्रतीक्षा करें, फिर अपने प्रोपोज़ल को अंतिम रूप दें!" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "न्यूनतम अमाउन्ट {min} {ticker} है!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "आपका डिवाइस एक बार में केवल {quantity} कोड बना सकता है!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "आपके पास उस कोड को बनाने के लिए पर्याप्त {ticker} नहीं है!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "आपने पहले ही वह कोड बना लिया है!" # You've already created that code! +SWITCHED_EXPLORERS = "एक्सप्लोरर बदला गया!
अब उपयोग कर रहे हैं {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "नोड बदला गया!
अब उपयोग कर रहे हैं {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "एनालिटिक्स स्तर बदला गया!
अब {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "सिंक मोड बदला गया!
अब उपयोग कर रहे हैं {sync} सिंक" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "टेस्टनेट मोड बदलने में असमर्थ!
एक वॉलेट पहले से लोड है" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "ऑफ़लाइन मोड सक्रिय है!
स्वचालित ट्रांजैक्शन के लिए ऑफ़लाइन मोड को अक्षम करें" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "कृपया {unlock} करें अपने वॉलेट को ट्रांजैक्शन भेजने से पहले!" # Please {unlock} your wallet before sending transactions! +WALLET_HARDWARE_WALLET = "हार्डवेयर वॉलेट तैयार!
कृपया अपने {hardwareWallet} को प्लग इन, अनलॉक, और PIVX ऐप में रखें" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "अपने लेजर पर इम्पोर्ट कन्फर्म करें" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "कोई डिवाइस उपलब्ध नहीं
हार्डवेयर वॉलेट नहीं मिल सका; कृपया इसे प्लग इन और अनलॉक करें!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "OS ने एक्सेस अस्वीकृत कर दिया क्या आपने udev नियम जोड़े हैं?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "OS ने एक्सेस अस्वीकृत कर दिया कृपया अपने ऑपरेटिंग सिस्टम सेटिंग्स की जांच करें।" # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "{hardwareWallet} से कनेक्शन खो गया
ऐसा लगता है कि {hardwareWallet} ऑपरेशन के बीच में अनप्लग हो गया!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} इंतजार कर रहा है
कृपया अपने {hardwareWallet} को अनलॉक करें या इसका वर्तमान प्रॉम्प्ट पूरा करें" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +WALLET_NOT_SYNCED = "कृपया पुनः प्रयास करें जब वॉलेट सिंक करना समाप्त हो जाए!" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "वॉलेट सफलतापूर्वक लॉक किया गया!" # Wallet successfully Locked! +WALLET_UNLOCKED = "वॉलेट सफलतापूर्वक अनलॉक किया गया!" # Wallet successfully Unlocked! +CONFIRM_POPUP_VOTE = "कन्फर्म वोट" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "क्या आप सुनिश्चित हैं? वोट बदलने में 60 मिनट लगते हैं" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "आपना ट्रांजैक्शन कन्फर्म करें" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "आपका मास्टरनोड प्राइवेट की" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
इस प्राइवेट की को सेव करें और अपने VPS कॉन्फ़िग में कॉपी करें
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "आपना एड्रेस कन्फर्म करें" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "आपका मास्टरनोड पुनर्प्राप्त करने में विफल। कृपया इसे रिइम्पोर्ट करें" # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "आपका खाता पुनर्प्राप्त करने में विफल। कृपया इसे रिइम्पोर्ट करें" # Failed to recover your account. Please reimport it. +APP_INSTALLED = "ऐप स्थापित हो गया!" # App Installed! +WALLET_FIREFOX_UNSUPPORTED = "फायरफॉक्स इसका समर्थन नहीं करता!
दुर्भाग्यवश, फायरफॉक्स हार्डवेयर वॉलेट का समर्थन नहीं करता है" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +CONFIRM_POPUP_DELETE_ACCOUNT = "यह आपके सभी डेटा को हटा देगा, जिसमें मास्टरनोड्स, संपर्क और प्राइवेट की शामिल हैं!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "क्या आप सुनिश्चित हैं?" # Are you sure? +CONFIRM_LEDGER_TX = "कन्फर्म करें कि यह ट्रांजैक्शन आपके {hardwareWallet} पर जोड़े गए ट्रांजैक्शन से मेल खाता है" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "आप {value} {ticker} को
{address}
भेजेंगे" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "बैलेंस बहुत कम है! {sats} सैट्स की कमी है!" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "इस वॉलेट में शील्ड सक्षम नहीं है!" # Shield is not enabled in this wallet! \ No newline at end of file diff --git a/locale/it/translation.toml b/locale/it/translation.toml new file mode 100644 index 000000000..4af5510b4 --- /dev/null +++ b/locale/it/translation.toml @@ -0,0 +1,335 @@ +amount = "Quantità" # Amount +staking = "Staking" # Staking +wallet = "Wallet" # Wallet +display = "Display" # Display +activity = "Attività" # Activity +yes = "Sì" # Yes +no = "No" # No +navDashboard = "Dashboard" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Governance" # Governance +navSettings = "Impostazioni" # Settings +footerBuiltWithPivxLabs = "Costruito col 💜 da PIVX Labs 🇮🇹" # Built with 💜 by PIVX Labs +loading = "Caricamento" # Loading +loadingTitle = "Il mio Wallet PIVX" # My PIVX Wallet is +dashboardTitle = "Dashboard" # Dashboard +dCardOneTitle = "Crea un " # Create a +dCardOneSubTitle = "Nuovo Wallet" # New Wallet +dCardOneDesc = "Crea un nuovo Wallet PIVX, con metodi di sicurezza & di backup più sicuri." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Crea un Nuovo Wallet" # Create A New Wallet +dCardTwoTitle = "Crea un nuovo " # Create a new +dCardTwoSubTitle = "Wallet Vanity" # Vanity Wallet +dCardTwoDesc = "Crea un wallet con un prefisso personalizzato, potrebbe impiegarci molto tempo!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Crea un Wallet vanity" # Create A Vanity Wallet +dCardThreeTitle = "Accedi al tuo " # Access your +dCardThreeSubTitle = "Hardware wallet" # Ledger Wallet +dCardThreeDesc = "Utilizza il tuo wallet Ledger con la solita interfaccia di MPW." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Accedi al mio Ledger" # Access my Ledger +dCardFourTitle = "Vai al " # Go to +dCardFourSubTitle = "Mio Wallet" # My Wallet +dCardFourDesc = "Importa un wallet PIVX usando una Chiave Privata, xpriv, o una Seed Phrase." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Importa Wallet" # Import Wallet +dCardFourButtonA = "Accedi al mio Wallet" # Access My Wallet +thisIsYourSeed = "Questa è la tua seed phrase:" # This is your seed phrase: +writeDownSeed = "Scrivila da qualche parte. La vedrai solo una volta!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Chiunque ne ha una copia può accedere a tutti i tuoi fondi" # Anyone with a copy of it can access all of your funds. +doNotShare = "NON condividerla con nessuno." # Do NOT share it with anyone. +digitalStoreNotAdvised = "Si consiglia di NON non salvarla online" # It is NOT advised to store this digitally. +optionalPassphrase = "Passphrase opzionale" # Optional Passphrase (BIP39) +writtenDown = "Ho segnato la mia seed phrase" # I have written down my seed phrase +gettingStarted = "Iniziamo" # Getting Started +secureYourWallet = "Proteggi il tuo wallet" # Secure your wallet +unlockWallet = "Sblocca il wallet" # Unlock wallet +lockWallet = "Blocca wallet" # Lock wallet +encryptWallet = "Cripta il wallet" # Encrypt wallet +encryptPasswordCurrent = "Password attuale" # Current Password +encryptPasswordFirst = "Inserisci Password" # Enter Password +encryptPasswordSecond = "Reinserisci Password" # Re-enter Password +encrypt = "Cripta" # Encrypt +changePassword = "Cambia Password" # Change Password +balanceBreakdown = "Scomposizione del saldo" # Balance Breakdown +viewOnExplorer = "Visualizza su Explorer" # View on Explorer +export = "Esporta" # Export +refreshAddress = "Aggiorna indirizzo" # Refresh address +redeemOrCreateCode = "Riscatta o crea codice" # Redeem or Create Code +address = "Indirizzo" # Address +receivingAddress = "Indirizzo di ricevimento" # Receiving address +sendAmountCoinsMax = "MAX" # MAX +paymentRequestMessage = "Descrizione (dal commerciante)" # Description (from the merchant) +send = "Invia" # Send +receive = "Ricevi" # Receive +contacts = "Contatti" # Contacts +name = "Nome" # Name +username = "Nome Utente" # Username +addressOrXPub = "Indirizzo o XPub" # Address or XPub +back = "Indietro" # Back +chooseAContact = "Scegli un contatto" # Choose a Contact +createContact = "Crea un contatto" # Create Contact +encryptFirstForContacts = "Dopo aver premuto \"{button}\" nella Dashboard, puoi creare un contatto per facilitare la ricezione dei PIV!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Condividi contatto URL" # Share Contact URL +setupYourContact = "Imposta il tuo contatto" # Setup your Contact +receiveWithContact = "Ricevi utilizzando un semplice contatto basato sul nome utente" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Condividi il tuo contatto solamente con persone fidate (familiari, amici)" # Only share your Contact with trusted people (family, friends) +changeTo = "Cambia in " # Change to +contact = "Contatto" # Contact +xpub = "XPub" # XPub +addContactTitle = "Aggiungi {strName} ai contatti" # Add {strName} to Contacts +addContactSubtext = "Una volta aggiunto, sarai in grado di inviare transazioni a {strName} con il suo nome (digitando o facendo clic), niente più indirizzi, carino e facile." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Assicurati che questo sia il vero \"{strName}\", non accettare richieste di contatto da fonti sconosciute!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Cambia contatto \"{strName}\"." # Change "{strName}" Contact +newName = "Nuovo Nome " # New Name +removeContactTitle = "Rimuovi {strName}?" # Remove {strName}? +removeContactSubtext = "Sei sicuro di voler cancellare {strName} dai tuoi contatti?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Puoi aggiungerlo di nuovo in qualsiasi momento in futuro." # You can add them again any time in the future. +privateKey = "Chiave Privata" # Private Key +viewPrivateKey = "Visualizzare la chiave privata?" # View Private Key? +privateWarning1 = "Assicurati che nessuno possa vedere il tuo schermo." # Make sure no one can see your screen. +privateWarning2 = "Chiunque abbia questa chiave può rubare i tuoi fondi." # Anyone with this key can steal your funds. +viewKey = "Viasualizza chiave" # View key +pivxPromos = "è un sistema decentralizzato per codici regalo con valore PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Inserisci il tuo codice 'PIVX Promos'" # Enter your 'PIVX Promos' code +createName = "Nome della Promo (Facoltativo)" # Promo Name (Optional) +createAmount = "Valore della Promo" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "(Opzionale) Address del proprietario" # (Optional) Owner Address +rewardHistory = "Cronologia delle ricompense" # Reward History +loadMore = "Carica altro" # Load more +mnControlYour = "Controlla il tuo" # Control your +mnSubtext = "Da questa scheda puoi creare e accedere a uno o più masternode." # From this tab you can create and access one or more masternodes +govSubtext = "Da questa scheda puoi controllare le proposte, se hai un masternode puoi far parte della DAO e votare!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Budget Mensile" # Monthly Budget +govAllocBudget = "Budget Assegnato" # Allocated Budget +govNextPayout = "Prossimo pagamento della Tesoriera" # Next Treasury Payout +govTableStatus = "STATO" # STATUS +govTableName = "NOME" # NAME +govTablePayment = "PAGAMENTO" # PAYMENT +govTableVotes = "VOTI" # VOTES +govTableVote = "VOTAZIONE" # VOTE +contestedProposalsDesc = "Si tratta di proposte che hanno ricevuto tanti voti negativi, rendendole probabilmente spam o altamente contestabili." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Scegli una valuta da visualizzare:" # Choose a display currency: +priceProvidedBy = "Dati sui prezzi forniti da" # Price data provided by +settingsDecimals = "Cifre decimali del saldo:" # Balance Decimals: +settingsExplorer = "Scegli un explorer:" # Choose an explorer: +settingsLanguage = "Scegli una lingua" # Choose a Language: +settingsPivxNode = "Scegli un nodo PIVX" # Choose a PIVX node: +settingsAutoSelectNet = "Auto selezione dell'Explorer e del nodo " # Auto-select Explorers and Nodes +settingsAnalytics = "Scegli il tuo livello di contributo analitico:" # Choose your analytics contribution level: +settingsToggleDebug = "Modalità Debug" # Debug Mode +settingsToggleTestnet = "Modalità testnet" # Testnet Mode +netSwitchUnsavedWarningTitle = "Il tuo wallet {network} non è salvato!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Il tuo account {network} è a rischio" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Se passi a {network} prima di salvarlo, perderai l'account!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Ne sei veramente sicuro?" # Are you really sure? +transparencyReport = "Rapporto sulla trasparenza" # Transparency Report +hit = "Un ping che indica il caricamento dell'app, non vengono inviati dati univoci." # A ping indicating an app load, no unique data is sent. +time_to_sync = "Il tempo in secondi impiegato da MPW per l'ultima sincronizzazione." # The time in seconds it took for MPW to last synchronise. +transaction = "Un ping che indica un Tx, non vengono inviati dati univoci, ma possono essere dedotti dal tempo in chain." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Disattivato" # Disabled +analyticMinimal = "Minimo" # Minimal +analyticBalanced = "Saldo" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Impossibile recuperare l'account" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Si è verificato un errore durante il recupero del tuo account. Reimporta il tuo wallet utilizzando la seguente chiave:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Tempo" # Time +description = "Descrizione" # Description +activityBlockReward = "Ricompensa del blocco" # Block Reward +activitySentTo = "Inviato a {r}" # Sent to {r} +activitySelf = "te stesso" # self +activityShieldedAddress = "Indirizzo protetto" # Shielded address +activityDelegatedTo = "Delegato a {r}" # Delegated to {r} +activityUndelegated = "Non delegato" # Undelegated +activityUnknown = "Tx sconosciuta" # Unknown Tx +password = "Password" # Password +walletUnlock = "Sblocca il tuo wallet" # Unlock your wallet +walletPassword = "Password del wallet" # Wallet password +walletUnlockCreateMN = "Sblocca per creare il tuo Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Sblocca per avviare il tuo Masternode! " # Unlock to start your Masternode! +walletUnlockProposal = "Sblocca per creare la tua proposta!" # Unlock to create a proposal! +walletUnlockPromo = "Sblocca per finalizzare il tuo Promo Code!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Sblocca per inviare la tua transazione! " # Unlock to send your transaction! +walletUnlockStake = "Sblocca per mettere in stake i tuoi" # Unlock to stake your +walletUnlockUnstake = "Sblocca per togliere dallo stake i tuoi" # Unlock to unstake your +changelogTitle = "Novità nella " # What's New in +popupSetColdAddr = "Configura il tuo indirizzo di Cold Staking" # Set your Cold Staking address +popupCurrentAddress = "Indirizzo attuale:" # Current address: +popupColdStakeNote = "Con un Cold Address puoi mettere in stake i tuoi PIVs anche usando l'address di uno sconosciuto, che non potrà spenderli!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Esempio:" # Example: +popupWalletLock = "Vuoi bloccare il tuo wallet?" # Do you want to lock your wallet? +popupWalletWipe = "Vuoi cancellare i dati privati \u200b\u200bdel tuo wallet?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Dovrai inserire la tua password per accedere ai tuoi fondi" # You will need to enter your password to access your funds +popupWalletWipeNote = "Perderai l'accesso ai tuoi fondi se non hai eseguito il backup della chiave privata o della seed phrase" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Seed Phrase inaspettata" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "La frase seed non è valida o non è stata generata da MPW.
Vuoi comunque procedere?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Crea proposta" # Create Proposal +popupCreateProposalCost = "Costo" # Cost +popupProposalName = "Nome della proposta" # Proposal Name +popupProposalAddress = "Address della proposta (Opzionale)" # Proposal Address (Optional) +popupProposalDuration = "Durata in cicli" # Duration in cycles +popupProposalPerCycle = "per ciclo" # per cycle +popupProposalEncryptFirst = "Devi cliccare \"{button}\" prima di poter creare proposte!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Hash del voto:" # Vote Hash: +popupProposalFinalisedNote = "Congratulazioni per aver pubblicato la tua proposta!
I proprietari di Masternode possono utilizzare il tuo Hash di voto per votare da portafogli diversi da MPW, quindi assicurati di aggiungerlo al tuo post sul forum, se applicabile!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Buona fortuna per il tuo viaggio attraverso la DAO, PIVian" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Conferma che questo è l'indirizzo che vedi sul tuo" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Confermando..." # Confirming... +proposalFinalisationRemaining = "rimanenti" # remaining +proposalFinalisationExpired = "Proposta scaduta" # Proposal Expired +proposalFinalisationReady = "Pronto per l'invio" # Ready to submit +proposalPassing = "Passata" # Passing +proposalFailing = "Fallita" # Failing +proposalTooYoung = "Troppo recente" # Too Young +proposalFunded = "Finanziata" # Funded +proposalNotFunded = "Non finanziata" # Not Funded +proposalPaymentsRemaining = "rata/e rimanenti
di" # installment(s) remaining
of +proposalPaymentTotal = "totale" # total +proposalNetYes = "Netto si" # Net Yes +popupConfirm = "Conferma" # Confirm +popupClose = "Chiusa" # Close +popupCancel = "Annulla" # Cancel +chartPublicAvailable = "Disponibile pubblicamente" # Public Available +timeDays = "Giorni" # Days +timeHours = "Ore" # Hours +timeMinutes = "Minuti" # Minutes +timeSeconds = "Secondi" # Seconds +unhandledException = "Eccezione non gestita" # Unhandled exception. +vanityPrefixNote = "Nota: Gli indirizzi inizieranno sempre con:" # Note: addresses will always start with: +vanityPrefixInput = "Prefisso dell'indirizzo" # Address Prefix +importSeedValid = "La seed phrase è valida!" # Seed Phrase is valid! +importSeedError = "La seed phrase non è valida!" # Seed Phrase is invalid! +importSeedErrorSize = "La seed phrase dovrebbe essere lunga 12 o 24 parole!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "La seed phrase contiene errori di battitura! Controllala con attenzione." # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "La seed phrase non sembra valida, ma l'avvertenza è stata ignorata dall'utente" # Seed Phrase appears invalid, but the warning was skipped by the user +syncStatusHistoryProgress = "Sincronizzando Transazioni (Pagina {current} di {total})" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Il tuo wallet sta sincronizzando!
Lo potrai usare quando avrà finito." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Sincronizzazione finita!Il tuo wallet è pronto per essere usato!" # Sync Finished!
Your wallet is ready to use! +contestedProposalsTitle = "Proposte Contestate" # Contested Proposals +settingsToggleAdvancedMode = "Modalità Avanzata" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Sblocca funzionalità e personalizzazioni più avanzate, ma potrebbe essere troppo confusionale e potenzialmente pericoloso per utenti inesperti." # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +accountDeleted = "Il tuo account è stato eliminato con successo!" # Your account has been successfully deleted! +activityReceivedWith = "Ricevuto con {s}" # Received with {s} +chartImmatureBalance = "Saldo immaturo" # Immature balance +syncLoadingSaplingProver = "Caricamento del Sapling Prover" # Loading SHIELD parameters... +syncShieldProgress = "Caricamento blocchi shield {current}/{total}" # Loading shielded blocks {current} of {total} +useShieldInputs = "Usa input shield" # Use shield inputs +newShieldAddress = "Genera un nuovo indirizzo shield" # Get new shield address +shieldAddress = "Indirizzo shield" # Shield address +cantShieldToExc = "Questo indirizzo non supporta i trasferimenti shield" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Errore interno, rirova più tardi" # Internal error, please try again later +FAILED_TO_IMPORT = "Impossibile importare! Password non valida" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = "Impossibile importare il wallet Hardware!" # Failed to import Hardware Wallet. +UNSUPPORTED_CHARACTER = "Il carattere '{char}' non è supportato negli indirizzi! (Non compatibile con Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Questo browser non supporta i Web Worker (JS multi-thread), sfortunatamente non puoi generare portafogli Vanity!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Indirizzo PIVX non valido!
{address}" # Invalid PIVX address!
{address} +TESTNET_ENCRYPTION_DISABLED = "La modalità Testnet è attiva!
Crittografia del wallet disabilitata" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "La password è un po' corta.
Utilizza almeno {MIN_PASS_LENGTH} caratteri." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "La password non corrisponde" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Sei protetto! 🔐
Bel lavoro, PIVian corazzato!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Password sbagliata" # Incorrect password! +INVALID_AMOUNT = "Importo non valido!
" # Invalid amount!
+TX_SENT = "Transazione inviata!" # Transaction sent! +TX_FAILED = "Transazione fallita!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "Il destinatario del pagamento non è valido" # is not a valid payment receiver +VALIDATE_AMOUNT_LOW = "L'importo minimo è di {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} limite decimale superato" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Indirizzo di staking impostato!
Ora vai avanti e annulla lo staking!" # Staking Address set!
Now go ahead and unstake! +STAKE_ADDR_SET = "Cold Address impostato!
Gli stake futuri utilizzeranno questo indirizzo." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "L'indirizzo del Cold Staking non è valido" # Invalid Cold Staking address! +CONFIRM_UNSTAKE_H_WALLET = "Conferma il tuo Unstake
Conferma la TX sul tuo {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Conferma la tua transazione
Conferma la TX sul tuo {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Indirizzo di staking impostato!
Ora vai avanti e inizia lo staking!" # Staking Address set!
Now go ahead and stake! +STAKE_NOT_SEND = "In questo caso, utilizza la schermata Stake, NON la schermata Invia!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Indirizzo PIVX non valido!
Lunghezza errata ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Indirizzo PIVX non valido!
Prefisso {address} errato (dovrebbe iniziare con {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Non puoi inviare 'niente'!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Salva il tuo wallet!
Dashboard ➜ Proteggi il tuo wallet" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Ti preghiamo di crittografare e/o eseguire il backup delle tue chiavi prima di chiudere, altrimenti potresti perderle!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Questo dispositivo non ha una fotocamera!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Il Ledger non è supportato per il Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Impossibile sincronizzare! Riprova più tardi.
Puoi provare a riconnetterti tramite le Impostazioni." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Il tuo masternode non è ancora abilitato!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Voto inviato!" # Vote submitted! +VOTED_ALREADY = "Hai già votato per questa proposta! Si prega di attendere 1 ora" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Impossibile verificare la firma, controlla la chiave privata del tuo masternode" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode creato!
Attendere 15 conferme per procedere ulteriormente" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Accedi a un masternode prima di votare!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Il tuo masternode è offline, proviamo ad avviarlo" # Your masternode is offline, we will try to start it +MN_STARTED = "Il Masternode è stato avviato!" # Masternode started! +MN_RESTARTED = "Il Masternode è stato riavviato" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Il Masternode è stato avviato!
Sarà presto online" # Masternode started!
It'll be online soon +MN_START_FAILED = "Il Masternode è stato avviato!" # Masternode started! +MN_RESTART_FAILED = "Masternode riavviato!" # Masternode restarted! +MN_DESTROYED = "Il distrutto!
Le tue monete ora sono spendibili." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Lo stato del tuo Masternode è " # Your masternode status is +MN_STATE = "Il tuo masternode è {state}" # Your masternode is in {state} state +MN_BAD_IP = "L'indirizzo IP non è valido!" # The IP address is invalid! +MN_BAD_PRIVKEY = "La chiave privata è invalida!" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Hai bisogno di {amount} altri {ticker} per creare un Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Hai un saldo sufficiente per un Masternode, ma nessun UTXO collaterale valido di {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Questo UTXO non è adatto per un Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Impossibile connettersi al nodo RPC!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Devi premere \"{button}\" prima di poter utilizzare i Contatti!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Nome richiesto!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Questo nome è troppo lungo!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Non puoi aggiungerti come contatto!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Il contatto esiste già!
Hai già salvato questo contatto" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Il nome del contatto esiste già!
Potrebbe trattarsi di un tentativo di phishing, attenzione!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Il contatto esiste già!
Un contatto si chiama già \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Il contatto esiste già, ma con un nome diverso!
Hai {newName} salvato come {oldName} nei tuoi contatti" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Questo non è un Contatto QR!" # This isn't a Contact QR! +CONTACTS_ADDED = "Nuovo contatto aggiunto!
{strName} è stato aggiunto, evviva!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Non hai contatti!" # You have no contacts! +SWITCHED_EXPLORERS = "Explorer cambiato!
Stai utilizzando {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Nodo cambiato!
Stai usando {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Livello di analisi cambiato!

Ora {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Modalità di sincronizzazione cambiata!
Ora stai utilizzando la sincronizzazione {sync}" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Impossibile cambiare la modalità Testnet!
Un wallet è già caricato" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "La modalità offline è attiva!
Disattiva la modalità offline per le transazioni automatiche" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Per favore, {unlock} il tuo wallet prima di compiere transazioni di invio!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Firefox non lo supporta!
, Firefox sfortunatamnete non supporta portafogli hardware " # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Wallet hardware pronto!
Tieni il tuo {hardwareWallet} collegato, sbloccato e nell'app PIVX" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Conferma l'importo sulla tua Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Nessun dispositivo disponibile
Impossibile trovare un wallet hardware; per favore collegalo e sbloccalo!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = " Il Sistema operativo ha negato l'accesso Hai aggiunto le regole udev?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = " Il Sistema Operativo ha negato l'accesso Perfavore, controlla le impostazioni del tuo sistema operativo." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Connessione a {hardwareWallet} persa
Sembra che {hardwareWalletProductionName} sia stato scollegato durante l'operazione, ops!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} è in attesa
Sblocca il tuo {hardwareWalletProductionName} o completa la richiesta corrente" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = "b> {hardwareWallet}

{errore}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Conferma voto" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Ne sei sicuro? Dovrai aspettare 60 minuti per cambiare il tuo voto" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Conferma la transazione" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Chiave privata del tuo Masternode" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Salva questa chiave privata e copiala nella configurazione VPS
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Verifica il tuo indirizzo" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Impossibile recuperare il tuo masternode. Per favore reimportalo." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Impossibile recuperare il tuo account. Per favore reimportalo." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "Applicazione installata!" # App Installed! +PROPOSAL_FINALISED = "Proposta Creata!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "La proposta non è ancora stata confermata." # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "La proposta è scaduta. Creane una nuova." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Errore nella finalizzazione della proposta." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Crea o importa il tuo wallet per continuare." # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Non ci sono abbastanza fondi per creare una proposta." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "La proposta è invalida. Errore:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Proposta Creata!
Aspetta le conferme, poi finalizzala." # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "L'importo minimo è di {min} {ticker}" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Il tuo dispositivo può creare solo {quantity} codici alla volta!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Non hai abbastanza {ticker} per create quel codice!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Hai già creato quel codice!" # You've already created that code! +CONFIRM_POPUP_DELETE_ACCOUNT = "Questo cancellerà tutti i tuoi data, inclusi i masternode, i contatti e le chiavi private!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Sei sicuro?" # Are you sure? +WALLET_NOT_SYNCED = "Per favore, prova di nuovo quando il wallet a finito di sincronizzarsi!" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "Wallet bloccato con successo!" # Wallet successfully Locked! +WALLET_UNLOCKED = "Wallet sbloccato con successo!" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "Conferma che questa transazione combacia quella nel tuo {hardwareWallet}" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "Manderai {value} {ticker} a
{address}
" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "Il bilancio non è sufficiente! Mancano {sats} sats!" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "Lo Shield non è abilitato nel wallet!" # Shield is not enabled in this wallet! diff --git a/locale/lang-tools/add_string.py b/locale/lang-tools/add_string.py new file mode 100755 index 000000000..ae481e2eb --- /dev/null +++ b/locale/lang-tools/add_string.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import argparse +import toml +import sys +from lang_common import default_template_path, default_locale_path +from merge import unmerge + +def copy_template_internal(res, template): + for key in template: + if key not in res and key != 'ALERTS': + res[key] = '' + +def copy_template(file_path, template_path): + template = toml.load(template_path) + locale = unmerge(file_path) + # Skip files that are merged + if 'info' in locale and locale['info']['merged']: + return + copy_template_internal(locale, template) + if 'ALERTS' not in locale: + locale['ALERTS'] = {} + copy_template_internal(locale['ALERTS'], template['ALERTS']) + with open(file_path, 'w') as f: + toml.dump(locale, f) + +def main(): + parser = argparse.ArgumentParser( + description='Sync a locale file with the template' + ); + parser.add_argument('file', help='File to sync') + parser.add_argument('--template-path', '-t', help='Template path', default=default_template_path(sys.argv[0])) + args = parser.parse_args() + copy_template(args.file, args.template_path) + +if __name__ == '__main__': + main() diff --git a/locale/lang-tools/comment_langs.py b/locale/lang-tools/comment_langs.py new file mode 100755 index 000000000..acb0cf278 --- /dev/null +++ b/locale/lang-tools/comment_langs.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import argparse +import sys +from os import path +from glob import glob +from lang_common import default_template_path, default_locale_path +import toml + +def comment_file(locale_file, template_path): + if path.abspath(locale_file) == path.abspath(template_path): + return + with open(locale_file, 'r+') as locale_file: + lines = locale_file.readlines() + locale_file.seek(0) + locale_file.truncate() + template = toml.load(template_path) + for line in lines: + # Only get variable lines, and skip already commented ones + if '=' in line and '#' not in line: + # Get the name of the key + key_name = line.split(' ')[0] + if key_name in template: + locale_file.write(line.strip() + ' # {}\n'.format(template[key_name])) + elif key_name in template['ALERTS']: + locale_file.write(line.strip() + ' # {}\n'.format(template['ALERTS'][key_name])) + else: + locale_file.write(line) + else: + locale_file.write(line) + +def comment_files(locale_dir, template_path): + if path.isdir(locale_dir): + for locale_file in glob(locale_dir + '/*/*.toml'): + comment_file(locale_file, template_path) + else: + comment_file(locale_dir, template_path) + +def main(): + parser = argparse.ArgumentParser( + description='Comment locale files with another language strings' + ) + parser.add_argument('locale_dir', help='Locale directory or file to comment', default=default_locale_path(sys.argv[0])) + parser.add_argument('--template-path', '-t', help='Template path', default=default_template_path(sys.argv[0])) + + args = parser.parse_args() + comment_files(args.locale_dir, args.template_path) + +if __name__ == '__main__': + main() diff --git a/locale/lang-tools/lang_common.py b/locale/lang-tools/lang_common.py new file mode 100644 index 000000000..e2e191913 --- /dev/null +++ b/locale/lang-tools/lang_common.py @@ -0,0 +1,9 @@ +import os + +def default_template_path(script_path): + # We need to make sure we take the template based on the script path, + # Not where the user called the script + return os.path.dirname(script_path) + '/../template/translation.toml' + +def default_locale_path(script_path): + return os.path.dirname(script_path) + '/../' diff --git a/locale/lang-tools/merge.py b/locale/lang-tools/merge.py new file mode 100755 index 000000000..655b4743e --- /dev/null +++ b/locale/lang-tools/merge.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import argparse +import toml +import os + +## Unmerges a file +def unmerge(path): + p = path.split('/') + if '-' not in p[-2]: + return toml.load(path) + p[-2] = p[-2].split('-')[0] + parent_path = '/'.join(p) + if not os.path.exists(parent_path): + return toml.load(path) + parent = toml.load(parent_path) + child = toml.load(path) + del parent['info'] + alerts = parent['ALERTS'] + alerts.update(child['ALERTS']) + parent.update(child) + parent['ALERTS'] = alerts + return parent + +def merge_internal(obj1, obj2, res): + for (k1, k2) in zip(obj1.copy(), obj2.copy()): + if k1 != k2: + raise ValueError('Files are out of order. \nKey1 {} != Key2 {}'.format(k1, k2)) + if k1 == 'ALERTS': + continue + if obj1[k1] == "" or obj2[k2] == "": + continue + if obj1[k1] == obj2[k2]: + res[k1] = obj1[k1] + del obj1[k1] + del obj2[k1] + else: + if k1 in res: + del res[k1] + + +def merge(filename1, filename2, output_path): + f1 = unmerge(filename1) + f2 = unmerge(filename2) + try: + merged = toml.load(output_path) + except: + merged = {} + if 'ALERTS' not in merged: + merged['ALERTS'] = {} + + merge_internal(f1, f2, merged) + merge_internal(f1['ALERTS'], f2['ALERTS'], merged['ALERTS']) + if 'info' not in merged: + merged['info'] = {} + merged['info']['merged'] = True + files = [(filename1, f1), (filename2, f2), (output_path, merged)] + for (path, obj) in files: + dirPath = os.path.dirname(path) + if not os.path.exists(dirPath): + os.makedirs(dirPath) + with open(path, 'w') as f: + toml.dump(obj, f) + + +# Merge two languages +def main(): + parser = argparse.ArgumentParser( + description='Merge two languages' + ) + parser.add_argument('filename1', help='First file to merge') + parser.add_argument('filename2', help='Second file to merge') + parser.add_argument('output_path', help='Where to store the output') + args = parser.parse_args() + merge(args.filename1, args.filename2, args.output_path) + + +if __name__ == '__main__': + main() diff --git a/locale/lang-tools/requirements.txt b/locale/lang-tools/requirements.txt new file mode 100644 index 000000000..b77f536ed --- /dev/null +++ b/locale/lang-tools/requirements.txt @@ -0,0 +1 @@ +toml==0.10.2 diff --git a/locale/lang-tools/update_translations.py b/locale/lang-tools/update_translations.py new file mode 100755 index 000000000..6c0c9a969 --- /dev/null +++ b/locale/lang-tools/update_translations.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import argparse +import sys +from glob import glob +from lang_common import default_locale_path, default_template_path +from add_string import copy_template +from comment_langs import comment_file +from merge import merge + +def tame(template_path, locale_path, comment, do_merge, sync): + if sync: + for path in glob(locale_path + '/*/*.toml'): + if 'template' not in path: + copy_template(path, template_path) + if do_merge: + path_dict = {} + for path in glob(locale_path + '/*/*.toml'): + bn = path.split('/')[-2] + if '-' in bn: + lang = bn.split('-') + path_dict[lang[0]] = path_dict.get(lang[0], []) + [path] + for lang in path_dict: + paths = path_dict[lang] + # TODO: add merging support to more than 2 languages + if len(paths) == 2: + split_path = paths[0].split('/') + parent_path = '/'.join(split_path[:-2] + [split_path[-2].split('-')[0]] + [split_path[-1]]) + merge(paths[0], paths[1], parent_path) + if comment: + for path in glob(locale_path + '/*/*.toml'): + if 'template' not in path: + comment_file(path, template_path) +def main(): + parser = argparse.ArgumentParser( + description='Sync, merge and comment all locale files with the template' + ); + parser.add_argument('--template-path', '-t', help='Template path', default=default_template_path(sys.argv[0])) + parser.add_argument('--locale-path', '-l', help='Directory where the locale files are stored', default=default_locale_path(sys.argv[0])) + parser.add_argument('--no-comment', help='Don\'t comment the file', action='store_true') + parser.add_argument('--no-merge', help='Skip merging', action='store_true') + parser.add_argument('--no-sync', help='Skip syncing with the template', action='store_true') + args = parser.parse_args() + tame(args.template_path, args.locale_path, not args.no_comment, not args.no_merge, not args.no_sync) + +if __name__ == '__main__': + main() diff --git a/locale/nl/translation.toml b/locale/nl/translation.toml new file mode 100644 index 000000000..e70dc7836 --- /dev/null +++ b/locale/nl/translation.toml @@ -0,0 +1,335 @@ +amount = "Bedrag" # Amount +staking = "Staken" # Staking +wallet = "Portemonnee" # Wallet +display = "Weergave" # Display +activity = "Activiteit" # Activity +yes = "Ja" # Yes +no = "Nee" # No +navDashboard = "Dashboard" # Dashboard +navStake = "Staken" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Bestuur" # Governance +navSettings = "Instellingen" # Settings +footerBuiltWithPivxLabs = "Gemaakt met 💜 door PIVX Labs" # Built with 💜 by PIVX Labs +loading = "Bezig met laden" # Loading +loadingTitle = "Mijn PIVX-portemonnee is" # My PIVX Wallet is +dashboardTitle = "Dashboard" # Dashboard +dCardOneTitle = "Maak een" # Create a +dCardOneSubTitle = "Nieuwe Portemonnee" # New Wallet +dCardOneDesc = "Maak een nieuwe PIVX-portemonnee aan, met de meest veilige back-up- en beveiligingsmethoden." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Maak een nieuwe portemonnee aan" # Create A New Wallet +dCardTwoTitle = "Maak een nieuwe" # Create a new +dCardTwoSubTitle = "Vanity Portemonnee" # Vanity Wallet +dCardTwoDesc = "Maak een portemonnee met een aangepast voorvoegsel, dit kan lang duren!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Maak een Vanity-portemonnee aan" # Create A Vanity Wallet +dCardThreeTitle = "Toegang tot uw" # Access your +dCardThreeSubTitle = "Ledger Portemonnee" # Ledger Wallet +dCardThreeDesc = "Gebruik uw Ledger Hardware-portemonnee met de vertrouwde interface van MPW." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Toegang tot mijn Ledger" # Access my Ledger +dCardFourTitle = "Ga naar" # Go to +dCardFourSubTitle = "Mijn Portemonnee" # My Wallet +dCardFourDesc = "Importeer een PIVX-portemonnee met een privésleutel, xpriv, of seedzin." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Importeer Portemonnee" # Import Wallet +dCardFourButtonA = "Ga naar Mijn Portemonnee" # Access My Wallet +vanityPrefixNote = "Opmerking: adressen zullen altijd beginnen met:" # Note: addresses will always start with: +vanityPrefixInput = "Voorvoegsel adres" # Address Prefix +thisIsYourSeed = "Dit is uw seedzin:" # This is your seed phrase: +writeDownSeed = "Schrijf het ergens op. U ziet dit maar één keer!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Iedereen die er een kopie van heeft, kan al uw geld bekijken." # Anyone with a copy of it can access all of your funds. +doNotShare = "Deel het niet met iemand." # Do NOT share it with anyone. +digitalStoreNotAdvised = "Het is NIET aan te raden om dit digitaal op te slaan." # It is NOT advised to store this digitally. +optionalPassphrase = "Optionele wachtzin (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Ik heb mijn seedzin opgeschreven" # I have written down my seed phrase +importSeedValid = "Seedzin is geldig!" # Seed Phrase is valid! +importSeedError = "Seedzin is ongeldig!" # Seed Phrase is invalid! +importSeedErrorSize = "Een seedzin moet 12 of 24 woorden lang zijn!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Seedzin bevat typfouten! Controleer uw invoer zorgvuldig" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Seedzin lijkt ongeldig, maar de waarschuwing is door de gebruiker overgeslagen" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Aan de slag" # Getting Started +secureYourWallet = "Beveilig uw portemonnee" # Secure your wallet +unlockWallet = "Ontgrendel portemonnee" # Unlock wallet +lockWallet = "Vergrendel portemonnee" # Lock wallet +encryptWallet = "Versleutel portemonnee" # Encrypt wallet +encryptPasswordCurrent = "Huidig wachtwoord" # Current Password +encryptPasswordFirst = "Voer wachtwoord in" # Enter Password +encryptPasswordSecond = "Voer wachtwoord opnieuw in" # Re-enter Password +encrypt = "Versleutel" # Encrypt +changePassword = "Verander wachtwoord" # Change Password +balanceBreakdown = "Saldo-overzicht" # Balance Breakdown +viewOnExplorer = "Bekijk op verkenner" # View on Explorer +export = "Exporteren" # Export +refreshAddress = "Adres vernieuwen" # Refresh address +redeemOrCreateCode = "Code inwisselen of aanmaken" # Redeem or Create Code +address = "Adres" # Address +receivingAddress = "Ontvangend adres" # Receiving address +sendAmountCoinsMax = "MAX" # MAX +paymentRequestMessage = "Omschrijving (van de handelaar)" # Description (from the merchant) +send = "Verstuur" # Send +receive = "Ontvangen" # Receive +contacts = "Contacten" # Contacts +name = "Naam" # Name +username = "Gebruikersnaam" # Username +addressOrXPub = "Adres of XPub" # Address or XPub +back = "Terug" # Back +chooseAContact = "Kies een Contact" # Choose a Contact +createContact = "Contact Aanmaken" # Create Contact +encryptFirstForContacts = "Zodra je \"{button}\" in het Dashboard selecteert, kun je een contact aanmaken om het ontvangen van PIV gemakkelijker te maken!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Deel Contact URL" # Share Contact URL +setupYourContact = "Stel je contact in" # Setup your Contact +receiveWithContact = "Ontvangen met behulp van een eenvoudige gebruikersnaam-gebaseerd contact" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Alleen deel je contact met vertrouwde personen (familie, vrienden)" # Only share your Contact with trusted people (family, friends) +changeTo = "Verander naar" # Change to +contact = "Contact" # Contact +xpub = "XPub" # XPub +addContactTitle = "Voeg {strName} toe aan Contacten" # Add {strName} to Contacts +addContactSubtext = "Zodra toegevoegd, kun je transacties naar {strName} sturen met behulp van hun naam (door te typen of te klikken), geen adressen meer, lekker makkelijk." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Zorg ervoor dat dit de echte \"{strName}\" is, accepteer geen contactverzoeken van onbekende bronnen!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Verander \"{strName}\" Contact" # Change "{strName}" Contact +newName = "Nieuwe Naam" # New Name +removeContactTitle = "Verwijder {strName}?" # Remove {strName}? +removeContactSubtext = "Weet je zeker dat je {strName} wilt verwijderen uit je contacten?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Je kunt ze op elk moment in de toekomst opnieuw toevoegen." # You can add them again any time in the future. +privateKey = "Privésleutel" # Private Key +viewPrivateKey = "Privésleutel bekijken?" # View Private Key? +privateWarning1 = "Zorg ervoor dat niemand je scherm kan zien." # Make sure no one can see your screen. +privateWarning2 = "Iedereen met deze sleutel kan je geld stelen." # Anyone with this key can steal your funds. +viewKey = "Sleutel bekijken" # View key +pivxPromos = "is een gedecentraliseerd systeem voor cadeaucodes ter waarde van PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Voer uw \"PIVX Promos\" code in" # Enter your 'PIVX Promos' code +createName = "Naam Promo (Optioneel)" # Promo Name (Optional) +createAmount = "Promo Bedrag" # Promo Amount +stake = "Staken" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "(Optioneel) Eigenaar Adres" # (Optional) Owner Address +rewardHistory = "Beloningsgeschiedenis" # Reward History +loadMore = "Meer laden" # Load more +mnControlYour = "Beheer uw" # Control your +mnSubtext = "Vanuit dit tabblad kunt u één of meer masternodes maken en openen" # From this tab you can create and access one or more masternodes +govSubtext = "Vanuit dit tabblad kunt u de voorstellen controleren en, als u een masternode heeft, deel uitmaken van de DAO en stemmen!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Maandelijks Budget" # Monthly Budget +govAllocBudget = "Toegewezen Budget" # Allocated Budget +govNextPayout = "Volgende Treasury Uitbetaling" # Next Treasury Payout +govTableStatus = "STATUS" # STATUS +govTableName = "NAAM" # NAME +govTablePayment = "BETALING" # PAYMENT +govTableVotes = "STEMMEN" # VOTES +govTableVote = "STEM" # VOTE +contestedProposalsTitle = "Omstreden Voorstellen" # Contested Proposals +contestedProposalsDesc = "Dit zijn voorstellen die een overweldigend aantal negatieve stemmen hebben gekregen, waardoor het waarschijnlijk spam is of een zeer betwistbaar voorstel." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Kies een weergavevaluta:" # Choose a display currency: +priceProvidedBy = "Prijsgegevens verstrekt door" # Price data provided by +settingsDecimals = "Balans Decimals:" # Balance Decimals: +settingsExplorer = "Kies een verkenner:" # Choose an explorer: +settingsLanguage = "Kies een taal:" # Choose a Language: +settingsPivxNode = "Kies een PIVX-knooppunt:" # Choose a PIVX node: +settingsAutoSelectNet = "Automatisch selecteren van verkenner en knooppunten" # Auto-select Explorers and Nodes +settingsAnalytics = "Kies uw niveau van bijdrage aan analytics:" # Choose your analytics contribution level: +settingsToggleDebug = "Debugmodus" # Debug Mode +settingsToggleTestnet = "Testnet-modus" # Testnet Mode +settingsToggleAdvancedMode = "Geavanceerde modus" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Dit ontgrendelt diepere functionaliteit en aanpassing, maar kan overweldigend zijn en mogelijk gevaarlijk zijn voor onervaren gebruikers!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Uw {network} portemonnee is niet opgeslagen!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Uw {network} account loopt gevaar!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Als u naar {network} overschakelt voordat u het opslaat, verliest u de rekening!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Weet u het zeker?" # Are you really sure? +transparencyReport = "Transparantierapport" # Transparency Report +hit = "Een ping die wijst op een app-lading, er worden geen unieke gegevens verzonden." # A ping indicating an app load, no unique data is sent. +time_to_sync = "De tijd in seconden die MPW nodig had om voor het laatst te synchroniseren." # The time in seconds it took for MPW to last synchronise. +transaction = "Een ping die duidt op een transactie, er worden geen unieke gegevens verzonden, maar het kan worden afgeleid uit de on-chain tijd." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Uitgeschakeld" # Disabled +analyticMinimal = "Minimaal" # Minimal +analyticBalanced = "Evenwichtig" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Account herstellen mislukt" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Er is een fout opgetreden bij het herstellen van uw account.
Importeer alstublieft uw portemonnee opnieuw met de volgende sleutel:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Tijd" # Time +description = "Beschrijving" # Description +activityBlockReward = "Blokbeloning" # Block Reward +activitySentTo = "Verzonden naar {r}" # Sent to {r} +activitySelf = "zelf" # self +activityShieldedAddress = "Afgeschermd adres" # Shielded address +activityDelegatedTo = "Gedelegeerd aan {r}" # Delegated to {r} +activityUndelegated = "Gedelegeerd ongedaan gemaakt" # Undelegated +activityUnknown = "Onbekende transactie" # Unknown Tx +password = "Wachtwoord" # Password +walletUnlock = "Ontgrendel je portemonnee" # Unlock your wallet +walletPassword = "Portemonneewachtwoord" # Wallet password +walletUnlockCreateMN = "Ontgrendel om je masternode te maken!" # Unlock to create your Masternode! +walletUnlockMNStart = "Ontgrendel om je masternode te starten!" # Unlock to start your Masternode! +walletUnlockProposal = "Ontgrendel om een voorstel te maken!" # Unlock to create a proposal! +walletUnlockPromo = "Ontgrendel om je promotiecode te finaliseren!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Ontgrendel om je transactie te verzenden!" # Unlock to send your transaction! +walletUnlockStake = "Ontgrendel om je inzet te plaatsen voor" # Unlock to stake your +walletUnlockUnstake = "Ontgrendel om je inzet ongedaan te maken voor" # Unlock to unstake your +changelogTitle = "Wat is er nieuw in" # What's New in +popupSetColdAddr = "Stel je Cold Staking-adres in" # Set your Cold Staking address +popupCurrentAddress = "Huidig adres:" # Current address: +popupColdStakeNote = "Een Cold Address zet munten namens jou in, het kan geen munten uitgeven, dus het is zelfs veilig om het Cold Address van een vreemde te gebruiken!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Voorbeeld:" # Example: +popupWalletLock = "Wil je je portemonnee vergrendelen?" # Do you want to lock your wallet? +popupWalletWipe = "Wil je je privégegevens van de portemonnee wissen?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Je moet je wachtwoord invoeren om toegang te krijgen tot je geld" # You will need to enter your password to access your funds +popupWalletWipeNote = "Je verliest de toegang tot je geld als je je privésleutel of seedzin niet hebt back-upped" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Onverwachte seedzin" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "De seedzin is ofwel ongeldig of is niet gegenereerd door MPW.
Wil je nog steeds doorgaan?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Maak een voorstel" # Create Proposal +popupCreateProposalCost = "Kosten" # Cost +popupProposalName = "Voorstelnaam" # Proposal Name +popupProposalAddress = "Voorsteladres (Optioneel)" # Proposal Address (Optional) +popupProposalDuration = "Duur in cycli" # Duration in cycles +popupProposalPerCycle = "per cyclus" # per cycle +popupProposalEncryptFirst = "Je moet \"{button}\" indrukken voordat je voorstellen kunt maken!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Stemhash:" # Vote Hash: +popupProposalFinalisedNote = "Gefeliciteerd met het lanceren van je voorstel!
Masternode-eigenaren kunnen je Stemhash gebruiken om te stemmen vanuit portefeuilles anders dan MPW, zorg er dus voor dat je deze toevoegt aan je forum post, indien van toepassing!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Veel succes op je reis door de DAO, PIViaan!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Bevestig alstublieft dat dit het adres is dat je ziet op je" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Bevestigen..." # Confirming... +proposalFinalisationRemaining = "resterend" # remaining +proposalFinalisationExpired = "Voorstel verlopen" # Proposal Expired +proposalFinalisationReady = "Klaar om in te dienen" # Ready to submit +proposalPassing = "Slaagt" # Passing +proposalFailing = "Mislukt" # Failing +proposalTooYoung = "Te jong" # Too Young +proposalFunded = "Gefinancierd" # Funded +proposalNotFunded = "Niet gefinancierd" # Not Funded +proposalPaymentsRemaining = "termijn(en) resterend
van" # installment(s) remaining
of +proposalPaymentTotal = "totaal" # total +proposalNetYes = "Netto Ja" # Net Yes +popupConfirm = "Bevestigen" # Confirm +popupClose = "Sluiten" # Close +popupCancel = "Annuleren" # Cancel +chartPublicAvailable = "Openbaar beschikbaar" # Public Available +timeDays = "Dagen" # Days +timeHours = "Uren" # Hours +timeMinutes = "Minuten" # Minutes +timeSeconds = "Seconden" # Seconds +unhandledException = "Onbehandelde uitzondering." # Unhandled exception. +syncStatusHistoryProgress = "" # Syncing History Chunks {current} of {total} +syncStatusStarting = "" # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "" # Sync Finished!
Your wallet is ready to use! +accountDeleted = "" # Your account has been successfully deleted! +activityReceivedWith = "" # Received with {s} +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Interne fout, probeer het later opnieuw" # Internal error, please try again later +FAILED_TO_IMPORT = "Importeren mislukt! Ongeldig wachtwoord" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = " Hardware Wallet importeren mislukt ." # Failed to import Hardware Wallet. +TESTNET_ENCRYPTION_DISABLED = "Testnet-modus is AAN!
Portemonneeversleuteling uitgeschakeld" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Dat wachtwoord is een beetje kort!
Gebruik minstens {MIN_PASS_LENGTH} tekens." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Je wachtwoorden komen niet overeen!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Je bent beveiligd! 🔐
Goed gedaan, bepantserde PIVian!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Onjuist wachtwoord!" # Incorrect password! +INVALID_AMOUNT = "Ongeldig bedrag!
" # Invalid amount!
+TX_SENT = "Transactie verstuurd!" # Transaction sent! +TX_FAILED = "Transactie mislukt!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "is geen geldige betalingsontvanger" # is not a valid payment receiver +UNSUPPORTED_CHARACTER = "Het teken '{char}' wordt niet ondersteund in adressen! (Niet compatibel met Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Deze browser ondersteunt geen web workers (multi-threaded JS), helaas kun je geen Vanity wallets genereren!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Ongeldig PIVX-adres!
{address}" # Invalid PIVX address!
{address} +VALIDATE_AMOUNT_LOW = "
Minimale bedrag is {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} decimaallimiet overschreden" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Stakingsadres ingesteld!
Ga nu verder en maak de staking ongedaan!" # Staking Address set!
Now go ahead and unstake! +CONFIRM_UNSTAKE_H_WALLET = "Bevestig je ontkoppeling
Bevestig de transactie op je {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Bevestig je transactie
Bevestig de transactie op je {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Stakingsadres ingesteld!
Ga nu verder en begin met staken!" # Staking Address set!
Now go ahead and stake! +STAKE_ADDR_SET = "Cold-adres ingesteld!
Toekomstige inzetten zullen dit adres gebruiken." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Ongeldig Cold Staking-adres!" # Invalid Cold Staking address! +STAKE_NOT_SEND = "Gebruik hier het Stake scherm, niet het Verzend scherm!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Ongeldig PIVX-adres!
Slechte lengte ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Ongeldig PIVX-adres!
Ongeldige prefix {address} (Moet beginnen met {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Je kunt 'niets' niet versturen!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Sla je portemonnee op!
Dashboard ➜ Beveilig je portemonnee" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Versleutel en/of BACK-UP je sleutels voordat je weggaat, anders kan je ze verliezen!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Dit apparaat heeft geen camera!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger wordt niet ondersteund voor Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Synchronisatie mislukt! Probeer het later opnieuw.
U kunt opnieuw proberen verbinding te maken via de Instellingen." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Je masternode is nog niet ingeschakeld!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Stem ingediend!" # Vote submitted! +VOTED_ALREADY = "Je hebt al gestemd voor dit voorstel! Wacht alsjeblieft 1 uur" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Handtekening kan niet worden geverifieerd, controleer de privésleutel van je masternode" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode aangemaakt!
Wacht 15 bevestigingen om verder te gaan" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Toegang tot een masternode vóór het stemmen!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Je masternode is offline, we zullen proberen deze te starten" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternode gestart!" # Masternode started! +MN_RESTARTED = "Masternode opnieuw gestart!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternode gestart!
Hij zal binnenkort online zijn" # Masternode started!
It'll be online soon +MN_START_FAILED = "Masternode gestart!" # Masternode started! +MN_RESTART_FAILED = "Masternode opnieuw gestart!" # Masternode restarted! +MN_DESTROYED = "Masternode vernietigd!
Je munten zijn nu uitgeefbaar." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "De status van je masternode is" # Your masternode status is +MN_STATE = "Je masternode is in de staat {state}" # Your masternode is in {state} state +MN_BAD_IP = "Het IP-adres is ongeldig!" # The IP address is invalid! +MN_BAD_PRIVKEY = "De privésleutel is ongeldig" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Je hebt {amount} meer {ticker} nodig om een Masternode te maken!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Je hebt genoeg saldo voor een Masternode, maar geen geldige onderpand-UTXO van {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Dit is geen geschikte UTXO voor een Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Kan geen verbinding maken met RPC-node!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Je moet eerst op \"{button}\" drukken voordat je Contacten kunt gebruiken!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Een naam is verplicht!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Die naam is te lang!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Je kunt jezelf niet toevoegen als contact!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Contact bestaat al!
Je hebt dit contact al opgeslagen" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Contactnaam bestaat al!
Dit kan potentieel een phishing-poging zijn, wees voorzichtig!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Contact bestaat al!
Een contact heet al \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Contact bestaat al, maar onder een andere naam!
Je hebt {newName} opgeslagen als {oldName} in je contacten" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Dit is geen Contact QR!" # This isn't a Contact QR! +CONTACTS_ADDED = "Nieuw contact toegevoegd!
{strName} is toegevoegd, hoera!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Je hebt geen contacten!" # You have no contacts! +PROPOSAL_FINALISED = "Voorstel gelanceerd!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "Het voorstel is nog niet bevestigd" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "Het voorstel is verlopen. Maak een nieuwe aan." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Mislukt om het voorstel af te ronden." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Maak of importeer je portemonnee om door te gaan" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Niet genoeg geld om een voorstel te maken." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "Voorstel is ongeldig. Fout:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Voorstel aangemaakt!
Wacht op bevestigingen en rond dan je voorstel af!" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Minimale bedrag is {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Je apparaat kan slechts {quantity} codes tegelijk maken!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Je hebt niet genoeg {ticker} om die code te maken!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Je hebt die code al gemaakt!" # You've already created that code! +SWITCHED_EXPLORERS = "Explorer gewisseld!
Gebruikt nu {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Node gewisseld!
Gebruikt nu {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Analytics niveau gewisseld!
Nu niveau {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Synchronisatiemodus gewisseld!
Gebruikt nu {sync} synchronisatie" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Kan de Testnet-modus niet inschakelen!
Een portemonnee is al geladen" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Offline modus is actief!
Schakel Offline modus uit voor automatische transacties" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Ontgrendel je portemonnee {unlock} voordat je transacties verstuurt!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Firefox ondersteunt dit niet!
Helaas ondersteunt Firefox geen hardware wallets" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Hardware wallet klaar!
Houd je {hardwareWallet} aangesloten, ontgrendeld en in de PIVX-app" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Bevestig de import op je Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Geen apparaat beschikbaar
Kon geen hardware wallet vinden; sluit deze aan en ontgrendel!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "De OS heeft toegang geweigerd Heb je de udev-regels toegevoegd?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "De OS heeft toegang geweigerd Controleer je instellingen van het besturingssysteem." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Verbinding verbroken met {hardwareWallet}
Het lijkt erop dat de {hardwareWallet} tijdens de bewerking is losgekoppeld, oeps!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} wacht
Ontgrendel je {hardwareWallet} of voltooi de huidige prompt" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Stem bevestigen" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Weet je het zeker? Het duurt 60 minuten om je stem te veranderen" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Bevestig je transactie" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Je Masternode privésleutel" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Bewaar deze privésleutel en kopieer deze naar je VPS-configuratie
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Verifieer je adres" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Het herstellen van je masternode is mislukt. Importeer het opnieuw." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Het herstellen van je account is mislukt. Importeer het opnieuw." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "App geïnstalleerd!" # App Installed! +CONFIRM_POPUP_DELETE_ACCOUNT = "" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/ph/translation.toml b/locale/ph/translation.toml new file mode 100644 index 000000000..36d2dba1f --- /dev/null +++ b/locale/ph/translation.toml @@ -0,0 +1,335 @@ +amount = "Halaga" # Amount +staking = "Staking" # Staking +wallet = "Wallet" # Wallet +display = "Display" # Display +activity = "Aktibidad" # Activity +yes = "Oo" # Yes +no = "Hindi" # No +navDashboard = "Dashboard" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Pamamahala" # Governance +navSettings = "Settings" # Settings +footerBuiltWithPivxLabs = "Binuo nang may 💜 ng PIVX Labs 🇵🇭" # Built with 💜 by PIVX Labs +loading = "Loading" # Loading +loadingTitle = "My PIVX wallet ay" # My PIVX Wallet is +dashboardTitle = "Dashboard" # Dashboard +dCardOneTitle = "Gumawa ng" # Create a +dCardOneSubTitle = "Bagong wallet" # New Wallet +dCardOneDesc = "Gumawa ng bagong PIVX wallet, ito'y nag aalok ng pinaka-ligtas na backup at mga paraang pang seguridad" # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Gumawa ng Bagong Wallet" # Create A New Wallet +dCardTwoTitle = "Gumawa ng bagong" # Create a new +dCardTwoSubTitle = "Vanity Wallet" # Vanity Wallet +dCardTwoDesc = "Gumawa ng wallet na may custom prefix, ito ay maaaring tumagal ng mga ilang oras!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Gumawa ng Vanity Wallet" # Create A Vanity Wallet +dCardThreeTitle = "I-access ang iyong" # Access your +dCardThreeSubTitle = "Hardware Wallet" # Ledger Wallet +dCardThreeDesc = "Gamitin ang iyong Ledger Hardware Wallet na pamilyar sa interface ng MPW" # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "I-access ang aking Ledger" # Access my Ledger +dCardFourTitle = "Pumunta sa" # Go to +dCardFourSubTitle = "Aking Wallet" # My Wallet +dCardFourDesc = "I-import ang iyong PIVX wallet gamit ang Private Key, xpriv, o Seed Phrase" # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "I-import ang Wallet" # Import Wallet +dCardFourButtonA = "I-access ang aking Wallet" # Access My Wallet +vanityPrefixNote = "Tandaan: Ang mga address ay laging naguumpisa sa:" # Note: addresses will always start with: +vanityPrefixInput = "Prefix ng Address" # Address Prefix +thisIsYourSeed = "Ito ang iyong seed phrase:" # This is your seed phrase: +writeDownSeed = "Isulat ito kahit saan. Makikita mo lang ito ng isang beses!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Sino man ang may kopya nito ay pwedeng i-access lahat ng iyong mga pondo" # Anyone with a copy of it can access all of your funds. +doNotShare = "WAG mo itong ibibigay kahit kanino" # Do NOT share it with anyone. +digitalStoreNotAdvised = "Ito ay HINDI payo upang itago ito digitally" # It is NOT advised to store this digitally. +optionalPassphrase = "Optional Passphrase (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Isinulat ko na ang aking seed phrase" # I have written down my seed phrase +importSeedValid = "Ang Seed Phrase ay wasto!" # Seed Phrase is valid! +importSeedError = "Ang Seed Phrase ay hindi wasto!" # Seed Phrase is invalid! +importSeedErrorSize = "Ang Seed Phrase dapat ay 12 o 24 na mga salita ang haba!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Ang Seed Phrase ay naglalaman ng mga error sa pag-type! Suriin itong mabuti sa iyong input" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Ang Seed Phrase ay lumalabas na hindi wasto, ngunit ang babala ay nilaktawan ng user" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Magsimula" # Getting Started +secureYourWallet = "I-secure ang iyong wallet" # Secure your wallet +unlockWallet = "Buksan ang Wallet" # Unlock wallet +lockWallet = "Isara ang wallet" # Lock wallet +encryptWallet = "Encrypt wallet" # Encrypt wallet +encryptPasswordCurrent = "Kasalukuyang Password" # Current Password +encryptPasswordFirst = "Ilagay ang Password" # Enter Password +encryptPasswordSecond = "Ilagay ulit ang Password" # Re-enter Password +encrypt = "Encrypt" # Encrypt +changePassword = "Mag palit ng Password" # Change Password +balanceBreakdown = "Kabuoang Balanse" # Balance Breakdown +viewOnExplorer = "Tignan sa Explorer" # View on Explorer +export = "Export" # Export +refreshAddress = "Refresh address" # Refresh address +redeemOrCreateCode = "I-redeem o Gumawa ng Code" # Redeem or Create Code +address = "Address" # Address +receivingAddress = "Address ng tatangap" # Receiving address +sendAmountCoinsMax = "MAX" # MAX +paymentRequestMessage = "Description (galing sa merchant)" # Description (from the merchant) +send = "Ipadala" # Send +receive = "Tumanggap" # Receive +contacts = "Mga Contact" # Contacts +name = "Pangalan" # Name +username = "Username" # Username +addressOrXPub = "Address o XPub" # Address or XPub +back = "Bumalik" # Back +chooseAContact = "Pumili ng Contact" # Choose a Contact +createContact = "Lumikha ng Contact" # Create Contact +encryptFirstForContacts = "Sa sandaling mapindot mo ang \"{button}\" sa Dashboard, maaari kang lumikha ng Contact upang gawing mas madali ang pagtanggap ng PIV!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Magbahagi ng Contact URL" # Share Contact URL +setupYourContact = "I-set up ang iyong Contact" # Setup your Contact +receiveWithContact = "Tumanggap gamit ang simpleng Contact na nakabase sa username" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Pinakamabuting ibahagi ang iyong contact sa mga pinagkakatiwalaang tao (pamilya, kaibigan)" # Only share your Contact with trusted people (family, friends) +changeTo = "Baguhin sa" # Change to +contact = "Contact" # Contact +xpub = "XPub" # XPub +addContactTitle = "Idagdag si {strName} sa mga Contact" # Add {strName} to Contacts +addContactSubtext = "Kapag naidagdag na, maaari ka ng makapag padala ng transaksyon sa {strName} sa pamamagitan ng kanilang pangalan (kahit alin sa pagta-type o pag-click), wala nang mga address, maganda at madali. " # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Siguraduhin na ito ay tunay na \"{strName}\", Wag tumanggap ng mga Contact request galing sa mga hindi kilalang pinagmulan! " # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Baguhin ang \"{strName}\" Contact" # Change "{strName}" Contact +newName = "Bagong Pangalan" # New Name +removeContactTitle = "Tanggalin si {strName}?" # Remove {strName}? +removeContactSubtext = "Sigurado na ba ang hiling mo na tanggalin si {strName} sa iyong mga Contact?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Maaari mong maidagdag ang mga ito kahit anong oras sa hinaharap" # You can add them again any time in the future. +privateKey = "Private Key" # Private Key +viewPrivateKey = "Tignan ang Private Key?" # View Private Key? +privateWarning1 = "Siguraduhing walang nakakakita ng iyong screen" # Make sure no one can see your screen. +privateWarning2 = "Sino man ang may hawak ng key na ito ay pwedeng nakawin ang ng iyong pondo" # Anyone with this key can steal your funds. +viewKey = "View key" # View key +pivxPromos = "ay isang desentralisadong sistema para sa gift code na nag kakahalaga ng PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Ipasok and iyong 'PIVX Promos'code" # Enter your 'PIVX Promos' code +createName = "Promo Name" # Promo Name (Optional) +createAmount = "Halaga ng Promo" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +ownerAddress = "(Optional) May-ari ng Address" # (Optional) Owner Address +rewardHistory = "History ng iyong Reward" # Reward History +loadMore = "Load more" # Load more +mnControlYour = "Kontrolin ang iyong" # Control your +mnSubtext = "Ikaw ay makakagawa at makaka access ng isa o higit pang masternodes mula sa tab na ito " # From this tab you can create and access one or more masternodes +govSubtext = "Maaari mong makita ang mga proposals sa tab na ito at kung meron kang masternode,maaari kang maging parte ng DAO at bumoto!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Buwanang Budget" # Monthly Budget +govAllocBudget = "Nakalaang Budget" # Allocated Budget +govNextPayout = "Susunod na Treasury Payout" # Next Treasury Payout +govTableStatus = "Status" # STATUS +govTableName = "Pangalan" # NAME +govTablePayment = "Kabayaran" # PAYMENT +govTableVotes = "Mga Boto" # VOTES +govTableVote = "Boto" # VOTE +contestedProposalsDesc = "Ito ang mga proposals na nakatanggap ng pinaka madaming downvote,na nagmumukang spam o isang highly contestable proposal." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Pumili ng display currency:" # Choose a display currency: +priceProvidedBy = "Datos ng presyo na ibinigay ng" # Price data provided by +settingsDecimals = "Balance Decimals:" # Balance Decimals: +settingsExplorer = "Pumili ng explorer:" # Choose an explorer: +settingsLanguage = "Pumili ng Wika:" # Choose a Language: +settingsPivxNode = "Pumili ng PIVX node:" # Choose a PIVX node: +settingsAutoSelectNet = "Automatikong pagpili ng Explorers at Nodes" # Auto-select Explorers and Nodes +settingsAnalytics = "Pumili ng iyong analytics contribution level:" # Choose your analytics contribution level: +settingsToggleDebug = "Debug Mode" # Debug Mode +settingsToggleTestnet = "Testnet Mode" # Testnet Mode +settingsToggleAdvancedMode = "Advance Mode" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Ito ay nagbubukas ng mas malalim na functionality at pagpapasadya ngunit napapalaki at maaaring mapanganib sa mga user na wala pang karanasan!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Ang iyong {network} wallet ay hind na-save!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Ang iyong {network} account ay nanganganib!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Kung ikaw ay mag papalit ng {network} bago mo ito i-save, mawawalan ka ng account!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Sigurado ka na ba talaga dito?" # Are you really sure? +transparencyReport = "Transparency Report" # Transparency Report +hit = "Ang ping na nagpapahiwatig ng pag load ng app, walang unique na data ang naipadala." # A ping indicating an app load, no unique data is sent. +time_to_sync = "Ang bawat segundo sa oras na tumatagal upang muling mag synchronize sa MPW." # The time in seconds it took for MPW to last synchronise. +transaction = "Ang ping na nag papahiwating ng isang TX, walang unique na data ang naipadala, pero maaari itong inferred mula sa oras ng on-chain." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Disabled" # Disabled +analyticMinimal = "Minimal" # Minimal +analyticBalanced = "Balanced" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Nabigong marecover ang account" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "May mali sa pag recover ng iyong account. Pakiusap i-reimport ang iyong wallet gamit ang mga sumusunod na key:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Oras" # Time +description = "Description" # Description +activityBlockReward = "Block Reward" # Block Reward +activitySentTo = "Naipadala sa {r}" # Sent to {r} +activitySelf = "sarili" # self +activityShieldedAddress = "Shielded address" # Shielded address +activityDelegatedTo = "Delegated to {r}" # Delegated to {r} +activityUndelegated = "Undeligated" # Undelegated +activityUnknown = "Unknown Tx" # Unknown Tx +password = "Password" # Password +walletUnlock = "Buksan ang iyong wallet" # Unlock your wallet +walletPassword = "Wallet password" # Wallet password +walletUnlockCreateMN = "Buksan upang makagawa ng iyong Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Buksan para maumpisahan ang iyong Masternode!" # Unlock to start your Masternode! +walletUnlockProposal = "Buksan para makagawa ng proposal!" # Unlock to create a proposal! +walletUnlockPromo = "Buksan para i-finalize ang iyong promo code!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Buksan upang i-send ang iyong transaksyon!" # Unlock to send your transaction! +walletUnlockStake = "Buksan upang i-stake ang iyong" # Unlock to stake your +walletUnlockUnstake = "Buksan upang i-unstake ang iyong" # Unlock to unstake your +changelogTitle = "Anong bago sa" # What's New in +popupSetColdAddr = "I-set ang iyong Cold Staking Address" # Set your Cold Staking address +popupCurrentAddress = "Kasalukuyang Address:" # Current address: +popupColdStakeNote = "Ang Cold Address stakes coins sa iyong ngalan, ay hindi makakagastos ng coins, kaya mas ligtas itong gumamit ng stranger's cold address!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Halimbawa" # Example: +popupWalletLock = "Gusto mo bang i-lock ang iyong wallet?" # Do you want to lock your wallet? +popupWalletWipe = "Gusto mo bang burahin lahat ng pribadong data sa iyong wallet?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Kailangan mong i-enter ang iyong password upang ma access ang iyong pondo" # You will need to enter your password to access your funds +popupWalletWipeNote = "Maaari mong hindi ma access ang iyong pondo kung hindi mo na back up ang iyong private key or seed phrase" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Unexpected Seed Phrase" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "Ang seed phrase na ito ay pwedeng hindi wasto o hindi ito generated ng MPW.
Gusto mo bang tumuloy?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Gumawa ng Proposal" # Create Proposal +popupCreateProposalCost = "Gastos" # Cost +popupProposalAddress = "Address ng Proposal (Optional)" # Proposal Address (Optional) +popupProposalDuration = "Duration in cycles" # Duration in cycles +popupProposalPerCycle = "per cycles" # per cycle +popupProposalEncryptFirst = "Kailangan mong pindutin ang \"{button}\" bago ka gumawa ng proposals!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Vote Hash:" # Vote Hash: +popupProposalFinalisedNote = "Binabati kita sa paglulunsad ng iyong proposal!
Ang mga may-ari ng Masternode ay pwedeng gumamit ng Vote Hash upang bumoto mula sa mga wallet maliban sa MPW, kaya siguraduhin idagdag ito sa iyong forum post, kung naaangkop!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Nawa'y ang mabuting kapalaran ay sumasaiyo sa iyong paglalakbay sa DAO. PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Pakiusap kumpirmahin na ito ang address na nakikita mo sa iyong" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Kinukumpirma..." # Confirming... +proposalFinalisationRemaining = "natitira" # remaining +proposalFinalisationExpired = "Proposal expired" # Proposal Expired +proposalFinalisationReady = "Handa ng ipasa" # Ready to submit +proposalPassing = "Passing" # Passing +proposalFailing = "Failing" # Failing +proposalTooYoung = "Too Young" # Too Young +proposalFunded = "Funded" # Funded +proposalNotFunded = "Not Funded" # Not Funded +proposalPaymentsRemaining = "installment(s) remaining
of" # installment(s) remaining
of +proposalPaymentTotal = "Total" # total +proposalNetYes = "Net Yes" # Net Yes +popupConfirm = "Kumpirmahin" # Confirm +popupClose = "Isara" # Close +popupCancel = "Kanselahin" # Cancel +chartPublicAvailable = "Public Available" # Public Available +timeDays = "Mga araw" # Days +timeHours = "Mga oras" # Hours +timeMinutes = "Mga minuto" # Minutes +timeSeconds = "Mga segundo" # Seconds +unhandledException = "Unhandled exception." # Unhandled exception. +syncStatusHistoryProgress = "" # Syncing History Chunks {current} of {total} +syncStatusStarting = "" # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "" # Sync Finished!
Your wallet is ready to use! +contestedProposalsTitle = "" # Contested Proposals +accountDeleted = "" # Your account has been successfully deleted! +activityReceivedWith = "" # Received with {s} +popupProposalName = "" # Proposal Name +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Internal error, Pakiusap uliting muli" # Internal error, please try again later +FAILED_TO_IMPORT = "Nabigong mag import! Ang password na ito ay hindi wasto" # Failed to import! Invalid password +UNSUPPORTED_CHARACTER = "Ang karakter na ito ‘{char}’ay hindi supportado sa mga address! (Not Base58 compatible)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Hindi supportado ng browser na ito ang Web Workers (multi-threaded JS)sa kasamaang palad ay hindi ka makakagawa ng Vanity wallets!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Ang PIVX address na ito ay hindi wasto!
{address}" # Invalid PIVX address!
{address} +TESTNET_ENCRYPTION_DISABLED = "Ang Testnet Mode ay bukas na!

Wallet encryption disabled" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Ang password ay medyo maiksi!
Kahit papaano gumamit ng{MIN_PASS_LENGHT} mga karakter." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Hindi tumugma ang iyong password!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Ikaw ay ligtas! 🔐
Magandang bagay, Armoured PIVian!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Mali ang iyong password" # Incorrect password! +INVALID_AMOUNT = "Ang halaga ay hindi wasto!
" # Invalid amount!
+TX_SENT = "Ang transaksyon ay naipadala!" # Transaction sent! +TX_FAILED = "Nabigo ang Transaksyon" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "ay hindi wastong tanggapan ng bayad " # is not a valid payment receiver +VALIDATE_AMOUNT_LOW = "
Ang pinakamababang halaga ay {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} Ang limitasyon sa decimal ay lumagpas" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Nakatakda na ang Staking address!

Pumunta ka na ngayon at mag unstake" # Staking Address set!
Now go ahead and unstake! +STAKE_ADDR_SET = "Nakatakda na ang Cold Address!

Gagamitin ang address na to sa mga susunod na stakes" # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Ang Cold Staking address ay hindi wasto!" # Invalid Cold Staking address! +CONFIRM_UNSTAKE_H_WALLET = "Kumpirmahin ang iyong unstake
Kumpirmahin ang TX sa iyong {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Kumpirmahin ang iyong transaksyon

Kumpirmahin ang TX sa iyong {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Nakatakda na ang staking address!
Pumunta ka na ngayon at mag stake" # Staking Address set!
Now go ahead and stake! +STAKE_NOT_SEND = "Dito, gamitin mo angStake screen, hindi ang screen sa pagdala" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Hindi wasto ang PIVX address!
Bad lenght({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Hindi wasto ang PIVX address!
Bad prefix{address} (Ito ay dapat nag sisimula sa{addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Hindi ka pwedeng magpadala ng 'wala'!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "I-save ang iyong wallet!
Dashboard ➜ I-secure ang iyong wallet" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Pakiusap i-ENCRYPT at/o i-BACKUP ang iyong keys bago umalis, o mawawala mo ang mga ito!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Ang device na ito ay walang camera!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ang ledger ay hindi suportado para sa Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Nabigong i-synchronize! Pakiusap ulitin itong muli.
Maaari mo itong i-konektang muli via the Settings. " # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Ang iyong masternode ay hindi pa gumagana!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Naipasa na ang iyong Boto!" # Vote submitted! +VOTED_ALREADY = "Ikaw ay bumoto na sa proposal na ito! Pakiusap maghintay ng 1 oras" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Ang signature ay nabigong i-verify, Pakiusap suriin mo ang private key ng iyong masternode's" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Nagawa na ang Masternode!
'Mag antay ng 15 na kumpirmasyon upang mag patuloy" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "I-access ang iyong Masternode bago bumoto!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Ang iyong Masternode ay offline, Ito ay susubukan naming simulan" # Your masternode is offline, we will try to start it +MN_STARTED = "Nagsimula na ang Masternode!" # Masternode started! +MN_RESTARTED = "Na-restart na ang Masternode!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Nagsimula na ang Masternode!
Malapit itong mag online" # Masternode started!
It'll be online soon +MN_START_FAILED = "Nagsimula na ang Masternode!" # Masternode started! +MN_RESTART_FAILED = "Na-restart na ang Masternode!" # Masternode restarted! +MN_DESTROYED = "Nasira ang Masternode!
Ang iyong coins ay pwede nang magastos." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Ang kalagayan ng iyong Masternode ay" # Your masternode status is +MN_STATE = "Ang iyong masternode ay nasa {state} kalagayan" # Your masternode is in {state} state +MN_BAD_IP = "Ang IP address ay hindi wasto!" # The IP address is invalid! +MN_BAD_PRIVKEY = "" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Kailangan mo ng {amount} pang {ticker} upang makagawa ng Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Ikaw ay may sapat na balanse para sa iyong Masternode, subalit walang wastong collateral UTXO ng {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Ang UTXO na ito ay hindi angkop para sa Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Hindi maka-konekta sa RPC node!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Kailangan mong pindutin ang \"{button}\" bago mo magamit ang mga Contact" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Kailangan ng pangalan!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Ang pangalan na ito ay masyadong mahaba!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Hindi mo maaaring maidagdag ang iyong sarili bilang Contact!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Mayroon na ang Contact na ito!
Na i-save mo na ang contact na ito" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Mayroon na ang Contact na ito!
Maaari itong isang phishing attempt, mag ingat!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Mayroon na ang Contact na ito!
Natawagan na ang Contact na ito \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Mayroon na ang Contact na ito, ngunit sa ibang pangalan!
Mayroon kang {newName} bilang {oldName} sa iyong mga contact" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Ito ay hindi isang Contact QR! " # This isn't a Contact QR! +CONTACTS_ADDED = "Naidagdag na ang bagong Contact!
{strName} ay naidagdag na, mabuhay!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Ikaw ay walang mga contact" # You have no contacts! +SWITCHED_EXPLORERS = "Nagpalit ng explorer!Ang gamit ngayon ay {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Nagpalit ng node!Ang gamit ngayon ay {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Nagpalit ng analytics level!
Ngayon {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Nagpalit ng sync mode!
Ang gamit ngayon ay {sync} sync" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Hindi makapag palit sa Testnet Mode!

Nakapag load na ang wallet" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Aktibo ang Offline Mode!
Pakiusap wag paganahin ang Offline Mode para sa automatikong transaksyon" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Pakiusap {unlock} ang iyong wallet bago magpadala ng transaksyon!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = " Hindi ito supportado ng Firefox!
Sa kasamaang palad, hindi supportado ng Firefox ang mga hardware wallet" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Handa na ang Hardware wallet!
Pakiusap panatilihin mo na ang {hardwareWallet} ay naka plugged in, naka bukas, at nasa PIVX app " # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Kumpirmahin ang import sa iyong Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Walang pwedeng magamit na device
Hindi makahanap ng hardware wallet; pakiusap i-plug in ito at buksan!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "Tinanggihan ng OS ang pag-access Dinagdag mo ba ang mga panuntunan sa udev?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "Tinanggihan ng OS ang pag-access Pakiusap tignan ang settings ng iyong Operating System." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Nawala ang koneksyon sa {hardwareWallet}
Parang ang {hardwareWalletProductionName} ay na-unplug sa kalagitnaan ng operasyon, oops!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} ay naghihintay
Pakiusap buksan ang iyong {hardwareWalletProductionName} o tapusin ang kasalukuyang prompt" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = "{hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Kumpirmahin ang boto" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Ikaw ba ay sigurado? Ito ay tatagal n 60 minuto upang mapalitan ang iyong boto" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Kumpirmahin ang iyong transakyon" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Ang iyong Masternode Private Key" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Itago ang private key at kopyahin at kopyahin ito sa iyong VPS config
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "I-verify ang iyong address" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Nabigong marecover ang iyong masternode. Pakiusap i-reimport ito." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Nabigong marecover ang iyong account. Pakiusap i-reimport ito." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "App Installed!" # App Installed! +FAILED_TO_IMPORT_HARDWARE = "" # Failed to import Hardware Wallet. +PROPOSAL_FINALISED = "" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "" # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "" # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "" # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "" # Proposal is invalid. Error: +PROPOSAL_CREATED = "" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "" # You've already created that code! +CONFIRM_POPUP_DELETE_ACCOUNT = "" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/pl/translation.toml b/locale/pl/translation.toml new file mode 100644 index 000000000..82bfc7344 --- /dev/null +++ b/locale/pl/translation.toml @@ -0,0 +1,335 @@ +amount = "Kwota" # Amount +staking = "Staking" # Staking +wallet = "Portfel" # Wallet +display = "Wyświetlanie" # Display +activity = "Aktywność" # Activity +yes = "Tak" # Yes +no = "Nie" # No +navDashboard = "Panel" # Dashboard +navStake = "Stakuj" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Zarządzanie" # Governance +navSettings = "Ustawienia" # Settings +footerBuiltWithPivxLabs = "Zbudowane z 💜 przez PIVX Labs" # Built with 💜 by PIVX Labs +loading = "Ładowanie" # Loading +loadingTitle = "Portfel PIVX " # My PIVX Wallet is +dashboardTitle = "Panel" # Dashboard +dCardOneTitle = "Stwórz" # Create a +dCardOneSubTitle = "Nowy Portfel" # New Wallet +dCardOneDesc = "Stwórz nowy portfel PIVX, oferując najbardziej bezpieczne metody kopii zapasowej i zabezpieczeń." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Stwórz Nowy Portfel" # Create A New Wallet +dCardTwoTitle = "Stwórz Nowy" # Create a new +dCardTwoSubTitle = "Portfel Vanity" # Vanity Wallet +dCardTwoDesc = "Stwórz portfel z własnym prefiksem, to może zająć dużo czasu!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Stwórz Portfel Vanity" # Create A Vanity Wallet +dCardThreeTitle = "Dostęp do" # Access your +dCardThreeSubTitle = "Portfela Ledger" # Ledger Wallet +dCardThreeDesc = "Korzystaj z portfela sprzętowego Ledger z interfejsem MPW" # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Dostęp do mojego portfela Ledger" # Access my Ledger +dCardFourTitle = "Przejdź do" # Go to +dCardFourSubTitle = "Mójego Portfela" # My Wallet +dCardFourDesc = "Importuj portfel PIVX przy użyciu Klucza Prywatnego, xpriv lub Frazy Odzyskiwania (Seed Phrase)." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Importuj Portfel" # Import Wallet +dCardFourButtonA = "Dostęp do Mojego Portfela" # Access My Wallet +vanityPrefixNote = "Uwaga: Adresy zawsze będą zaczynać się od:" # Note: addresses will always start with: +vanityPrefixInput = "Prefiks Adresu" # Address Prefix +thisIsYourSeed = "To jest twój ciąg słów odzyskiwania (Seed Phrase):" # This is your seed phrase: +writeDownSeed = "Zapisz to gdzieś. Zobaczysz to tylko raz!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Ktoś z kopią może uzyskać dostęp do wszystkich Twoich środków." # Anyone with a copy of it can access all of your funds. +doNotShare = "NIE udostępniaj tego nikomu." # Do NOT share it with anyone. +digitalStoreNotAdvised = "NIE jest zalecane przechowywanie tego cyfrowo." # It is NOT advised to store this digitally. +optionalPassphrase = "Opcjonalny kod frazowy (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Zapisałem(am) mój ciąg słów odzyskiwania" # I have written down my seed phrase +importSeedValid = "Ciąg słów odzyskiwania (Seed Phrase) jest poprawny!" # Seed Phrase is valid! +importSeedError = "Ciąg słów odzyskiwania (Seed Phrase) jest nieprawidłowy!" # Seed Phrase is invalid! +importSeedErrorSize = "Ciąg słów odzyskiwania (Seed Phrase) powinien mieć długość 12 lub 24 słów!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Ciąg słów odzyskiwania (Seed Phrase) zawiera błędy! Sprawdź ostrożnie poprawność wprowadzonych danych." # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Ciąg słów odzyskiwania (Seed Phrase) wydaje się być nieprawidłowy, ale ostrzeżenie zostało pominięte przez użytkownika" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Rozpocznij" # Getting Started +secureYourWallet = "Zabezpiecz swój portfel" # Secure your wallet +unlockWallet = "Odblokuj portfel" # Unlock wallet +lockWallet = "Zablokuj portfel" # Lock wallet +encryptWallet = "Zaszyfruj portfel" # Encrypt wallet +encryptPasswordCurrent = "Obecne hasło" # Current Password +encryptPasswordFirst = "Wprowadź hasło" # Enter Password +encryptPasswordSecond = "Powtórz hasło" # Re-enter Password +encrypt = "Zaszyfruj" # Encrypt +changePassword = "Zmień hasło" # Change Password +balanceBreakdown = "Podział salda" # Balance Breakdown +viewOnExplorer = "Przeglądaj w Eksploratorze" # View on Explorer +export = "Eksportuj" # Export +refreshAddress = "Odśwież adres" # Refresh address +redeemOrCreateCode = "Wymień lub Utwórz Kod" # Redeem or Create Code +address = "Adres" # Address +receivingAddress = "Adres odbiorcy" # Receiving address +sendAmountCoinsMax = "MAX" # MAX +paymentRequestMessage = "Opis (od sprzedawcy)" # Description (from the merchant) +send = "Wyślij" # Send +receive = "Odbierz" # Receive +contacts = "Kontakty" # Contacts +name = "Imię" # Name +username = "Nazwa użytkownika" # Username +addressOrXPub = "Adres lub XPub" # Address or XPub +back = "Wróć" # Back +chooseAContact = "Wybierz kontakt" # Choose a Contact +createContact = "Stwórz kontakt" # Create Contact +encryptFirstForContacts = "Po naciśnięciu przycisku \"{button}\" na Pulpicie, możesz utworzyć kontakt, co ułatwi odbieranie PIV!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Udostępnij adres URL kontaktu" # Share Contact URL +setupYourContact = "Skonfiguruj swój kontakt" # Setup your Contact +receiveWithContact = "Odbieraj, korzystając z kontaktu opartego na nazwie użytkownika" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Tylko Udostępnij swój kontakt zaufanym osobom (rodzinie, przyjaciołom)" # Only share your Contact with trusted people (family, friends) +changeTo = "Zmień na" # Change to +contact = "Kontakt" # Contact +xpub = "XPub" # XPub +addContactTitle = "Dodaj {strName} do Kontaktów" # Add {strName} to Contacts +addContactSubtext = "Po dodaniu będziesz mógł/mogła wysyłać transakcje do {strName} używając ich nazwy (poprzez pisanie lub klikanie), bez potrzeby podawania adresów, łatwe i wygodne." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Upewnij się, że to rzeczywiście prawdziwe \"{strName}\", nie akceptuj zapytań kontaktu od nieznanych źródeł!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Zmień kontakt \"{strName}\" " # Change "{strName}" Contact +newName = "Nowa nazwa" # New Name +removeContactTitle = "Usunąć {strName}?" # Remove {strName}? +removeContactSubtext = "Czy na pewno chcesz usunąć {strName} z kontaktów?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Możesz dodać go/nią ponownie w dowolnym momencie w przyszłości." # You can add them again any time in the future. +privateKey = "Prywatny Klucz" # Private Key +viewPrivateKey = "Zobacz Klucz?" # View Private Key? +privateWarning1 = "Upewnij się, że nikt nie może zobaczyć twojego ekranu." # Make sure no one can see your screen. +privateWarning2 = "Ktokolwiek z tym kluczem może ukraść Twoje środki." # Anyone with this key can steal your funds. +viewKey = "Zobacz klucz" # View key +pivxPromos = "To zdecentralizowany system dla kodów podarunkowych o wartości PIV." # is a decentralised system for gift codes worth PIV +redeemInput = "Wprowadź 'PIVX Promo kod" # Enter your 'PIVX Promos' code +createName = "Nazwa Promo (opcjonalne)" # Promo Name (Optional) +createAmount = "Kwota Promo" # Promo Amount +stake = "Stakuj" # Stake +stakeUnstake = "Zakończ" # Unstake +ownerAddress = "(Opcjonalne) Adres Właściciela" # (Optional) Owner Address +rewardHistory = "Historia Nagród" # Reward History +loadMore = "Załaduj więcej" # Load more +mnControlYour = "Kontroluj swoje" # Control your +mnSubtext = "W tej zakładce możesz stworzyć i uzyskać dostęp do jednego lub więcej masternodów." # From this tab you can create and access one or more masternodes +govSubtext = "W tej zakładce możesz sprawdzić propozycje , oraz czy masz masternody, bądź częścią DAO i glosuj!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Miesięczny Budżet" # Monthly Budget +govAllocBudget = "Przydzielony Budżet" # Allocated Budget +govNextPayout = "Kolejna Wypłata ze Skarbca" # Next Treasury Payout +govTableStatus = "STATUS" # STATUS +govTableName = "NAZWA" # NAME +govTablePayment = "PŁATNOŚĆ" # PAYMENT +govTableVotes = "GŁOSY" # VOTES +govTableVote = "GŁOS" # VOTE +contestedProposalsTitle = "Kwestionowane Propozycje" # Contested Proposals +contestedProposalsDesc = "To są propozycje, które otrzymały przytłaczającą liczbę głosów negatywnych, co wskazuje na spam lub bardzo kontrowersyjną propozycję." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Wybierz wyświetlaną walutę:" # Choose a display currency: +priceProvidedBy = "Informacje dotyczące cen dostarczane jest przez" # Price data provided by +settingsDecimals = "Ustawienie dziesiętności salda:" # Balance Decimals: +settingsExplorer = "Wybierz eksplorator:" # Choose an explorer: +settingsLanguage = "Wybierz Język:" # Choose a Language: +settingsPivxNode = "Wybierz node PIVX:" # Choose a PIVX node: +settingsAutoSelectNet = "Automatyczny wybór Eksploratorów i Node'ów" # Auto-select Explorers and Nodes +settingsAnalytics = "Wybierz swój poziom wkładu analitycznego:" # Choose your analytics contribution level: +settingsToggleDebug = "Tryb Debugowania" # Debug Mode +settingsToggleTestnet = "Tryb Testnet" # Testnet Mode +settingsToggleAdvancedMode = "Tryb Zaawansowany" # Advanced Mode +settingsToggleAdvancedModeSubtext = "To odblokowuje dostęp do bardziej zaawansowanych funkcji i dostosowań, jednak może być przytłaczające i potencjalnie niebezpieczne dla mniej doświadczonych użytkowników!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Twój {network} portfel nie jest zapisany!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Twoje konto {network} jest zagrożone!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Jeśli przełączysz się na {network} przed zapisaniem, stracisz konto!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Czy jesteś naprawdę pewien?" # Are you really sure? +transparencyReport = "Raport o Transparentności" # Transparency Report +hit = "Ping informujący o załadowaniu aplikacji, bez wysyłania unikalnych danych." # A ping indicating an app load, no unique data is sent. +time_to_sync = "Czas w sekundach, który był potrzebny, aby MPW zsynchronizował się." # The time in seconds it took for MPW to last synchronise. +transaction = "Ping informujący o transakcji, bez przesyłania unikalnych danych, ale możliwe do wnioskowania na podstawie czasu na łańcuchu bloków." # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Wyłączone" # Disabled +analyticMinimal = "Minimalne" # Minimal +analyticBalanced = "Zrównoważony" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Nie udało się odzyskać konta" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Wystąpił błąd podczas odzyskiwania twojego konta.
Proszę ponownie zaimportuj swój portfel, używając poniższego klucza:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Czas" # Time +description = "Opis" # Description +activityBlockReward = "Nagroda za Blok" # Block Reward +activitySentTo = "Wysłane do {r}" # Sent to {r} +activitySelf = "siebie" # self +activityShieldedAddress = "Adres chroniony" # Shielded address +activityDelegatedTo = "Delegowane do {r}" # Delegated to {r} +activityUndelegated = "Oddelegowano" # Undelegated +activityUnknown = "Nieznana Tx" # Unknown Tx +password = "Hasło" # Password +walletUnlock = "Odblokuj portfel" # Unlock your wallet +walletPassword = "Hasło portfela" # Wallet password +walletUnlockCreateMN = "Odblokuj, aby stworzyć Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Odblokuj, aby uruchomić Masternode!" # Unlock to start your Masternode! +walletUnlockProposal = "Odblokuj, aby stworzyć propozycję!" # Unlock to create a proposal! +walletUnlockPromo = "Odblokuj, aby zakończyć proces swojego Kodu Promocyjnego!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Odblokuj, aby wysłać transakcję!" # Unlock to send your transaction! +walletUnlockStake = "Odblokuj, aby stake'owac" # Unlock to stake your +walletUnlockUnstake = "Odblokuj, aby wycofać swoje środki.(Unstake)" # Unlock to unstake your +changelogTitle = "Co nowego w" # What's New in +popupSetColdAddr = "Ustaw Cold Staking adres" # Set your Cold Staking address +popupCurrentAddress = "Aktualny adres:" # Current address: +popupColdStakeNote = "Cold adres stakuje monety w Twoim imieniu, nie może wydawać monet, więc korzystanie z Cold adresu nieznajomego jest bezpieczne!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Na Przykład:" # Example: +popupWalletLock = "Czy chcesz zablokować swój portfel?" # Do you want to lock your wallet? +popupWalletWipe = "Czy chcesz wyczyścić prywatne dane swojego portfela?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Aby uzyskać dostęp do swoich środków, konieczne będzie podanie hasła" # You will need to enter your password to access your funds +popupWalletWipeNote = "Stracisz dostęp do swoich środków, jeśli nie zrobiłeś kopii zapasowej swojego klucza prywatnego lub frazy odzyskiwania. (Seed Phrase)" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Nieznana Fraza Odzyskiwania (Seed Phrase)" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "Fraza początkowa jest nieprawidłowa lub nie została wygenerowana przez MPW.
Czy chcesz kontynuować?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Utwórz Propozycję" # Create Proposal +popupCreateProposalCost = "Koszt" # Cost +popupProposalName = "Nazwa Propozycji" # Proposal Name +popupProposalAddress = "Adres Propozycji (Opcjonalnie)" # Proposal Address (Optional) +popupProposalDuration = "Czas trwania w cyklach" # Duration in cycles +popupProposalPerCycle = "na cykl" # per cycle +popupProposalEncryptFirst = "Zanim będziesz mógł tworzyć propozycje, musisz kliknąć \"{button}\"" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Hash Głosowania:" # Vote Hash: +popupProposalFinalisedNote = "Gratulujemy uruchomienia propozycji!
Właściciele Masternode mogą używać swojego Hashu Głosowania do głosowania z portfeli innych niż MPW, więc upewnij się, że dodałeś to do swojego posta na forum, jeśli dotyczy!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Powodzenia w podróży przez DAO, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Potwierdź, że jest to adres widoczny na Twoim koncie" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Potwierdzam..." # Confirming... +proposalFinalisationRemaining = "pozostało" # remaining +proposalFinalisationExpired = "Propozycja Wygasła" # Proposal Expired +proposalFinalisationReady = "Gotowe do przesłania" # Ready to submit +proposalPassing = "Zatwierdzony" # Passing +proposalFailing = "Nie zatwierdzony" # Failing +proposalTooYoung = "Zbyt nowy" # Too Young +proposalFunded = "Sfinansowany" # Funded +proposalNotFunded = "Nie sfinansowany" # Not Funded +proposalPaymentsRemaining = "rata(-y) pozostała(-e)
do spłaty" # installment(s) remaining
of +proposalPaymentTotal = "łącznie" # total +proposalNetYes = "Na Tak" # Net Yes +popupConfirm = "Potwierdź" # Confirm +popupClose = "Zamknij" # Close +popupCancel = "Anuluj" # Cancel +chartPublicAvailable = "Dostępne Publicznie" # Public Available +timeDays = "Dni" # Days +timeHours = "Godzin" # Hours +timeMinutes = "Minut" # Minutes +timeSeconds = "Sekund" # Seconds +unhandledException = "Nieobsługiwany wyjątek." # Unhandled exception. +syncStatusHistoryProgress = "Synchronizacja fragmentów historii {current} z {total}" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Twój portfel jest w trakcie synchronizacji!
Po jej zakończeniu będziesz mógł w pełni z niego korzystać." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Synchronizacja Zakończona!
Twój portfel jest gotowy do użycia!" # Sync Finished!
Your wallet is ready to use! +activityReceivedWith = "Otrzymano z {s}" # Received with {s} +accountDeleted = "Twoje konto zostało pomyślnie usunięte!" # Your account has been successfully deleted! +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +chartImmatureBalance = "" # Immature balance +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Błąd wewnętrzny, spróbuj ponownie później" # Internal error, please try again later +FAILED_TO_IMPORT = "Nieprawidłowe hasło Nie udało się zaimportować!" # Failed to import! Invalid password +FAILED_TO_IMPORT_HARDWARE = "Import portfela sprzętowego (Hardware Wallet) nie powiódł się." # Failed to import Hardware Wallet. +TESTNET_ENCRYPTION_DISABLED = "Tryb testnet jest włączony!
Szyfrowanie portfela wyłączone" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "To hasło jest za krótkie!
Użyj co najmniej {MIN_PASS_LENGTH} znaków." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Hasła nie są zgodne!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Jesteś zabezpieczony! 🔐
Dobra robota, Pancerny PIVianinie!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Nieprawidłowe hasło!" # Incorrect password! +INVALID_AMOUNT = "Nieprawidłowa kwota!
" # Invalid amount!
+TX_SENT = "Transakcja wysłana!" # Transaction sent! +TX_FAILED = "Transakcja Nieudana!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "nie jest prawidłowym odbiorcą" # is not a valid payment receiver +UNSUPPORTED_CHARACTER = "Znak '{char}' nie jest obsługiwany w adresach! (Nie jest kompatybilny z Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Ta przeglądarka nie obsługuje Web Workers (wielowątkowego JS), niestety nie można generować portfeli Vanity!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Nieprawidłowy adres PIVX!
{address}" # Invalid PIVX address!
{address} +VALIDATE_AMOUNT_LOW = "
Kwota minimalna to {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} Przekroczono limit dziesiętny" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Adres Stakingu ustawiony!
A teraz śmiało,idź i zrób Unstake" # Staking Address set!
Now go ahead and unstake! +CONFIRM_UNSTAKE_H_WALLET = "Potwierdź swój Unstake
Potwierdź TX na urządzeniu {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Potwierdź transakcję
Potwierdź TX na urządzeniu {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Staking Adres ustawiony!
Teraz idź i Stake'uj!" # Staking Address set!
Now go ahead and stake! +STAKE_ADDR_SET = "Cold Adress ustawiony
Ten adres będzie używany w przyszłości." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Nieprawidłowy adres Cold Stakingu!" # Invalid Cold Staking address! +STAKE_NOT_SEND = "Tu użyj okna Stake, a nie okna Wysyłania!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Nieprawidłowy adres PIVX!
Niewłaściwa długość ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Nieprawidłowy adres PIVX!
Zły prefiks {address} (Powinien zaczynać się od {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Nie możesz wysłać 'niczego'!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Chroń swój portfel!
Panel ➜ Zabezpiecz swój portfel" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Proszę ZASZYFRUJ i/lub ZRÓB KOPIĘ ZAPASOWĄ swoich kluczy przed opuszczeniem, aby ich nie stracić" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "To urządzenie nie ma kamery!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger nie jest obsługiwany w przypadku cold stakingu" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Synchronizacja nie powiodła się! Spróbuj ponownie później.
Możesz spróbować ponownie połączyć się za pomocą Ustawień." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Twój masternode nie jest jeszcze włączony!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Głos oddany!" # Vote submitted! +VOTED_ALREADY = "Oddałeś już głos na tę propozycję! Poczekaj 1 godzinę" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Weryfikacja nie powiodła się, sprawdź klucz prywatny masternode" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode stworzony!
Poczekaj 15 potwierdzeń, aby kontynuować" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Zaloguj się do masternode'u przed oddaniem głosu!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Twój masternode jest offline, spróbujemy go uruchomić" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternode uruchomiony!" # Masternode started! +MN_RESTARTED = "Masternode zrestartowany!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternode uruchomiony!
Wkrótce pojawi się online" # Masternode started!
It'll be online soon +MN_START_FAILED = "Masternode uruchomiony!" # Masternode started! +MN_RESTART_FAILED = "Masternode zrestartowany!" # Masternode restarted! +MN_DESTROYED = "Masternode zniszczony!
Możesz teraz wydać swoje monety" # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Status twojego masternode'u to" # Your masternode status is +MN_STATE = "Twój masternode jest w stanie {state}" # Your masternode is in {state} state +MN_BAD_IP = "Adres IP jest nieprawidłowy!" # The IP address is invalid! +MN_BAD_PRIVKEY = "Klucz prywatny jest nieprawidłowy" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Potrzebujesz {amount} więcej {ticker} aby utworzyć Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Masz wystarczające saldo aby stworzyć Masternode'a, ale nie masz ważnego zabezpieczenia UTXO w wysokości {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "To nie jest odpowiednie UTXO dla Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Nie można połączyć się z RPC node!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Musisz nacisnąć \"{button}\" zanim będziesz mógł użyć Kontaktów!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Nazwa jest wymagana!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Ta nazwa jest za długa!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Nie możesz dodać siebie jako kontaktu!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Kontakt już istnieje!
Zapisałeś już ten kontakt" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Nazwa kontaktu już istnieje!
To może być próba phishingu, uważaj!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Kontakt już istnieje!
Kontakt ma już nazwę \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Kontakt już istnieje, ale pod inną nazwą!
Nowa nazwa {newName} została zapisana jako {oldName} W kontaktach" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "To nie jest kontaktowy QR!" # This isn't a Contact QR! +CONTACTS_ADDED = "Dodano nowy kontakt!
{strName} został dodany, hurra!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Nie masz kontaktów!" # You have no contacts! +PROPOSAL_FINALISED = "Propozycja rozpoczęta!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "Propozycja nie została jeszcze potwierdzona" # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "Propozycja wygasła. Utwórz nową." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Nie udało się sfinalizować propozycji." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Utwórz lub zaimportuj portfel, aby kontynuować" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Brak wystarczających środków na stworzenie wniosku." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "Propozycja jest nieprawidłowa. Błąd:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Utworzono propozycję!
Poczekaj na potwierdzenie, a następnie sfinalizuj swoją propozycję!" # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Minimalna kwota to {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Twoje urządzenie może utworzyć {quantity} kodów jednocześnie!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Nie masz wystarczającej ilości {ticker}, aby utworzyć ten kod!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Już utworzyłeś ten kod!" # You've already created that code! +SWITCHED_EXPLORERS = "Zmieniony eksplorator!
Obecnie używany: {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Zmieniono node!
Obecnie używany: {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Przełączono poziom analityczny!
Obecny poziom {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Przełączono tryb synchronizacji!
NTeraz używana jest {sync} synchronizacja" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Nie można przełączyć trybu Testnet!
portfel jest już załadowany" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Tryb offline jest aktywny!
Wyłącz tryb Offline dla automatycznych transakcji" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Proszę {unlock} swój portfel przed wysłaniem transakcji!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Firefox nie obsługuje tego!
Niestety, Firefox nie obsługuje portfeli sprzętowych." # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Portfel sprzętowy (Hardware Waller) gotowy!
Portfel {hardwareWallet} powinien być podłączony, odblokowany i znajdować się w aplikacji PIVX." # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Potwierdź import na swoim Ledgerze." # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Brak dostępnego urządzenia
Nie można znaleźć portfela sprzętowego; podłącz go i odblokuj!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_UDEV = "System operacyjny odmówił dostępu Czy dodałeś reguły udev?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "System operacyjny odmówił dostępu Proszę sprawdzić ustawienia systemu operacyjnego." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Utracono połączenie z {hardwareWallet}
Wydaje się, że {hardwareWallet} został odłączony w trakcie operacji, ups!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} czeka
Proszę odblokuj swój {hardwareWallet} lub zakończ bieżące polecenie." # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Potwierdź Głos" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Jesteś pewny? Zmiana głosu zajmuje 60 minut" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Potwierdź transakcję" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Klucz prywatny Masternode" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Zapisz ten klucz prywatny i skopiuj go do konfiguracji VPS
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Sprawdź swój adres" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Nie udało się odzyskać Masternode. Zaimportuj go ponownie." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Nie udało się odzyskać konta. Zaimportuj je ponownie." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "Aplikacja zainstalowana!" # App Installed! +CONFIRM_POPUP_DELETE_ACCOUNT = "Spowoduje to usunięcie wszystkich danych, w tym kontaktów masternodów i kluczy prywatnych!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Na pewno?" # Are you sure? +WALLET_NOT_SYNCED = "Spróbuj ponownie, gdy portfel zakończy synchronizację!" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "Portfel pomyślnie zablokowany!" # Wallet successfully Locked! +WALLET_UNLOCKED = "Portfel pomyślnie odblokowany!" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/pt-br/translation.toml b/locale/pt-br/translation.toml new file mode 100644 index 000000000..b1ec19379 --- /dev/null +++ b/locale/pt-br/translation.toml @@ -0,0 +1,134 @@ +amount = "Quantidade" # Amount +footerBuiltWithPivxLabs = "Construido com 💜 por PIVX Labs 🇧🇷" # Built with 💜 by PIVX Labs +loading = "Carregando" # Loading +dCardOneTitle = "Criar" # Create a +dCardTwoDesc = "Criar uma carteira com um prefixo personalizado, isso pode levar bastante tempo!" # Create a wallet with a custom prefix, this can take a long time! +dCardThreeTitle = "Acesse a sua" # Access your +dCardThreeDesc = "Use a sua carteira de Hardware Ledger com a interface familiar do MPW." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Acessar a minha Ledger" # Access my Ledger +dCardFourDesc = "Importar uma carteira PIVX usando uma chave privada, xpriv ou Seed Phrase." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonA = "Acessar a Minha Carteira" # Access My Wallet +vanityPrefixNote = "Anotação: Endereços sempre começaram com:" # Note: addresses will always start with: +vanityPrefixInput = "Prefixo de endereço" # Address Prefix +thisIsYourSeed = "Esta é a sua Seed Phrase:" # This is your seed phrase: +writeDownSeed = "Escreva em algum lugar. Você só ira ver isso uma vez!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Qualquer pessoa com uma cópia pode acessar todos os seus fundos" # Anyone with a copy of it can access all of your funds. +digitalStoreNotAdvised = "NÃO é aconselhável armazená-la digitalmente." # It is NOT advised to store this digitally. +optionalPassphrase = "Frasse-Passe Opcional (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Eu escrevi a minha Seed Phrase" # I have written down my seed phrase +importSeedValid = "Seed Phrase é válida!" # Seed Phrase is valid! +importSeedError = "Seed Phrase é inválida!" # Seed Phrase is invalid! +importSeedErrorSize = "A Seed Phrase deve ter 12 ou 24 palavras!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Seed Phrase contem erros de digitação! Verifique a cuidadosamente" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Seed Phrase registra como inválida, mas o aviso foi pulado pelo usuário" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Começar" # Getting Started +lockWallet = "Bloquear a carteira" # Lock wallet +encryptPasswordCurrent = "Senha Atual" # Current Password +encryptPasswordSecond = "Digite a senha novamente" # Re-enter Password +changePassword = "Mudar Senha" # Change Password +sendAmountCoinsMax = "Máximo" # MAX +contacts = "Contatos" # Contacts +username = "Nome de usuário" # Username +chooseAContact = "Escolher um contato" # Choose a Contact +createContact = "Criar Contato" # Create Contact +encryptFirstForContacts = "Quando você pressionar \"{button}\" no dashboard você poderá criar um contato para receber PIV mais facilmente! " # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Compartilhar URL do contato" # Share Contact URL +setupYourContact = "Configurar o seu contato" # Setup your Contact +receiveWithContact = "Receber utilizando um contato baseado em nome de usuário simples" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Somente compartilhe o seu contato com pessoas confiáveis (família e amigos)" # Only share your Contact with trusted people (family, friends) +changeTo = "Mudar para" # Change to +contact = "Contato" # Contact +xpub = "XPub" # XPub +addContactTitle = "Adicionar {strName} aos contatos " # Add {strName} to Contacts +addContactSubtext = "Quando adicionado você será capaz de efetuar transações com {strName} pelo nome deles (digitando ou clicando), sem necessidade de endereços, simples e fácil" # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Garanta que este seja o verdadeiro \"{strName}\", não aceite pedidos de contatos de fontes desconhecidas!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Mudar o contato de \"{strName}\"" # Change "{strName}" Contact +removeContactSubtext = "Você tem certeza que deseja remover {strName} dos seus contatos?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Você pode adiciona-los de volta a qualquer momento" # You can add them again any time in the future. +privateWarning1 = "Certifique-se de que ninguém veja a sua tela." # Make sure no one can see your screen. +pivxPromos = "é um sistema descentralizado de códigos de presente com valor em PIV" # is a decentralised system for gift codes worth PIV +ownerAddress = "(Opcional) Endereço do dono" # (Optional) Owner Address +mnSubtext = "A partir deste guia, você pode criar e acessar um ou mais masternodes" # From this tab you can create and access one or more masternodes +govNextPayout = "Próximo Pagamento da Tesouraria" # Next Treasury Payout +contestedProposalsDesc = "Estas são as propostas que receberam uma quantidade massiva de votos negativos, sendo provavelmente spam ou uma proposta altamente contestável." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +priceProvidedBy = "Dados de preço fornecidos por" # Price data provided by +settingsDecimals = "Decimais do saldo:" # Balance Decimals: +settingsToggleAdvancedModeSubtext = "Isto desbloqueia mais funcionalidades e customização, mas pode ser complicado e possivelmente perigoso para novos usuários!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Sua carteira de {network} não foi salva!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Sua conta de {network} está em risco!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Se você mudar para {network} antes de salvar você perderá a a conta!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Você tem certeza?" # Are you really sure? +hit = "\"Um ping para indicar o carregamento de uma aplicação, nenhum dado exclusivo é enviado.\"" # A ping indicating an app load, no unique data is sent. +time_to_sync = "\"O tempo em segundos que o MPW levou para sincronizar pela última vez.\"" # The time in seconds it took for MPW to last synchronise. +ID = "ID" # ID +walletUnlockStake = "Desbloquear para realizar Stake" # Unlock to stake your +walletUnlockUnstake = "Desbloquear para realizar Unstake" # Unlock to unstake your +popupSetColdAddr = "Defina o seu endereço de Cold Staking" # Set your Cold Staking address +popupColdStakeNote = "Um Endereço de Cold Staking faz staking de moedas em seu nome, não pode gastar moedas, então é seguro até usar o Cold Address de um estranho!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupWalletWipeNote = "Você perderá o acesso aos seus fundos se não tiver feito o backup de sua chave privada ou Seed Phrase" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Seed Phrase Inesperada" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "A Seed Phrase é inválida ou não foi gerada pelo MPW.
Você ainda quer continuar" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupProposalAddress = "Endereço da proposta (Opcional)" # Proposal Address (Optional) +popupProposalEncryptFirst = "Você precisa pressionar \"{button}\" antes que possa criar propostas! " # You need to hit "{button}" before you can create proposals! +proposalTooYoung = "Muito Nova" # Too Young +proposalNetYes = "Votos Sim" # Net Yes +syncStatusHistoryProgress = "Sincronizando chunks de histórico" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Sua carteira está sincronizando!
Você poderá usá-la completamente quando isso for feito" # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = " Sincronização Terminada!
Sua carteira está pronta para ser utilizada" # Sync Finished!
Your wallet is ready to use! +accountDeleted = "Sua conta foi deletada com successo" # Your account has been successfully deleted! +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +STAKE_ADDR_SET = "Endereço de Cold Staking definido!
Ao fazer Stake no futuro este endereço irá ser usado." # Cold Address set!
Future stakes will use this address. +STAKE_NOT_SEND = "Aqui, use a tela de Stake, não a tela de Envio!" # Here, use the Stake screen, not the Send screen! +SAVE_WALLET_PLEASE = "Salve a sua carteira!
Painel ➜ Proteja a sua carteira" # Save your wallet!
Dashboard ➜ Secure your wallet +NO_CAMERAS = "Este dispositivo não tem câmera!" # This device has no camera! +MN_ACCESS_BEFORE_VOTE = "Acesse um masternode antes de votar!" # Access a masternode before voting! +MN_DESTROYED = "Masternode destruído!
Já pode gastar suas moedas." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "O status do seu masternode é" # Your masternode status is +MN_STATE = "O status do seu masternode é {state}" # Your masternode is in {state} state +MN_ENOUGH_BUT_NO_COLLAT = "Você tem saldo suficiente para um Masternode, mas nenhum UTXO válido como garantia de {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +CONTACTS_ENCRYPT_FIRST = "Você precisa pressionar \"{button}\" antes que possa utilizar os contatos!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "Um nome é necessário!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Esse nome é muito comprido!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Você não pode adicionar a si mesmo como contato" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "O contato já existe!
você já salvou este contato" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "O nome do contato já existe!
Isso pode ser uma tentativa de phising, tome cuidado!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "O contato já existe!
Um contato já é chamado \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "O contato já exite porém com um nome differente!
Você tem {newName} salvo como {oldName} nos seus contatos" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Isto não é um código QR de contato!" # This isn't a Contact QR! +CONTACTS_ADDED = "Novo contato adicionado!
{strName} foi adicionado, Viva!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Você não tem contatos!" # You have no contacts! +PROMO_NOT_ENOUGH = "Você não tem {ticker} suficiente para criar esse código!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Você já criou esse código!" # You've already created that code! +SWITCHED_EXPLORERS = "Explorador trocado!
Agora será utilizado o {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Nó trocado!
Agora será utilizado o {node}" # Switched node!
Now using {node} +SWITCHED_SYNC = "Modo de sincronização alternado!
Agora sera utilizada a sincronização {sync}" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Não é possível alternar o modo Testnet!
Uma carteira está aberta." # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_FIREFOX_UNSUPPORTED = "O Firefox não tem suporte para isto!
Infelizmente, o Firefox não suporta carteiras de hardware" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_UDEV = " O Sistema operacional recusou o acesso Você adicionou as regras de udev?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = " O Sistema operacional recusou o acesso Por favor cheque as configurações do seu sistema operacional" # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Conexão perdida com a {hardwareWallet}
Oops! Parece que a {hardwareWalletProductionName} foi desconectada no meio da operação." # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} está em modo de espera
Por favor desbloqueie a sua {hardwareWalletProductionName} ou conclua o prompt atual" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +CONFIRM_POPUP_VOTE_HTML = "Tem certeza? Demora 60 minutos para mudar o voto" # Are you sure? It takes 60 minutes to change vote +FAILED_TO_IMPORT_HARDWARE = " Falha para importar a carteira de hardware" # Failed to import Hardware Wallet. +CONFIRM_POPUP_DELETE_ACCOUNT = "Isso ira deletar toda a sua informação, incluindo masternodes, contatos e chaves privadas!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Você tem certerza?" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/pt-pt/translation.toml b/locale/pt-pt/translation.toml new file mode 100644 index 000000000..ea6d00b9a --- /dev/null +++ b/locale/pt-pt/translation.toml @@ -0,0 +1,134 @@ +amount = "Quantia" # Amount +footerBuiltWithPivxLabs = "Construido com 💜 por PIVX Labs 🇵🇹" # Built with 💜 by PIVX Labs +loading = "Carregar" # Loading +dCardOneTitle = "Criar a" # Create a +dCardTwoDesc = "Criar uma carteira com um prefixo personalizado, isso pode levar muito tempo!" # Create a wallet with a custom prefix, this can take a long time! +dCardThreeTitle = "Aceda à sua" # Access your +dCardThreeDesc = "Use a sua carteira Ledger Hardware com a interface familiar do MPW." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Aceder à minha Ledger" # Access my Ledger +dCardFourDesc = "Importar uma carteira PIVX usando uma chave privada, xpriv ou Frase Inicial." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonA = "Aceder à Minha Carteira" # Access My Wallet +vanityPrefixNote = "Nota: os endereços começarão sempre por" # Note: addresses will always start with: +vanityPrefixInput = "Prefixo do endereço" # Address Prefix +thisIsYourSeed = "Esta é a sua frase inicial:" # This is your seed phrase: +writeDownSeed = "Escreva num lugar. Você só vai ver isso uma vez!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Qualquer pessoa com uma cópia pode aceder a todos os seus fundos" # Anyone with a copy of it can access all of your funds. +digitalStoreNotAdvised = "NÃO é aconselhável armazená-lo digitalmente." # It is NOT advised to store this digitally. +optionalPassphrase = "Frase Senha Opcional (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "Eu escrevi a minha frase-inicial" # I have written down my seed phrase +importSeedValid = "A frase-semente é válida!" # Seed Phrase is valid! +importSeedError = "A frase-semente é válida!" # Seed Phrase is invalid! +importSeedErrorSize = "Uma frase-semente deve ter 12 ou 24 palavras!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "A frase-semente contém erros de digitação! Verifique cuidadosamente a sua introdução" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "A frase-semente parece inválida, mas o aviso foi ignorado pelo utilizador" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "A Começar" # Getting Started +lockWallet = "Fechar a carteira" # Lock wallet +encryptPasswordCurrent = "Palavra-passe atual" # Current Password +encryptPasswordSecond = "Digite novamente a senha" # Re-enter Password +changePassword = "Alterar a palavra-passe" # Change Password +sendAmountCoinsMax = "MAX" # MAX +contacts = "Contactos" # Contacts +username = "Nome de utilizador" # Username +chooseAContact = "Escolha um contacto" # Choose a Contact +createContact = "Criar contacto" # Create Contact +encryptFirstForContacts = "Depois de clicar no \"{button}\" no Painel de Controlo, pode criar um Contacto para facilitar a receção de PIV!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Partilhar URL de contacto" # Share Contact URL +setupYourContact = "Configurar o seu contacto" # Setup your Contact +receiveWithContact = "Receber através de um simples contacto baseado no nome de utilizador" # Receive using a simple username-based Contact +onlyShareContactPrivately = " partilhe o seu contacto com pessoas de confiança (família, amigos)" # Only share your Contact with trusted people (family, friends) +changeTo = "Alterar para" # Change to +contact = "Contacto" # Contact +xpub = " XPub" # XPub +addContactTitle = "Adicionar {strName} aos contactos" # Add {strName} to Contacts +addContactSubtext = "Uma vez adicionado, será capaz de enviar transacções para {strName} pelo seu nome (quer digitando, quer clicando), sem mais endereços, fácil e agradável." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Certifica-te de que este é o verdadeiro \"{strName}\", não aceites pedidos de contacto de fontes desconhecidas!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Alterar \"{strName}\" Contacto" # Change "{strName}" Contact +removeContactSubtext = "Tem a certeza de que pretende remover {strName} dos seus Contactos?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Pode voltar a adicioná-los em qualquer altura no futuro." # You can add them again any time in the future. +privateWarning1 = "Certifique-se de que ninguém está a ver o seu ecrã." # Make sure no one can see your screen. +pivxPromos = "é um sistema descentralizado de códigos de presente no valor de PIV" # is a decentralised system for gift codes worth PIV +ownerAddress = "(Facultativo) Endereço do proprietário" # (Optional) Owner Address +mnSubtext = "A partir deste guia, você pode criar e aceder a um ou mais masternodes" # From this tab you can create and access one or more masternodes +govNextPayout = "Próximo Pagamento da Tesoraria" # Next Treasury Payout +contestedProposalsDesc = "Estas são as propostas que receberam uma quantidade esmagadora de votos negativos, tornando-as provavelmente spam ou uma proposta altamente contestável." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +priceProvidedBy = "Dados sobre preços fornecidos por" # Price data provided by +settingsDecimals = "Balanço de decimais:" # Balance Decimals: +settingsToggleAdvancedModeSubtext = "Isto desbloqueia uma funcionalidade e personalização mais profundas, mas pode ser demasiado complicado e potencialmente perigoso para utilizadores inexperientes!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "A tua carteira não está guardada!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "A tua carteira não está guardada!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "Se mudares para {network} antes de a guardares, perdes a conta!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Tens mesmo a certeza?" # Are you really sure? +hit = "\"Um ping a indicar o carregamento de uma aplicação, nenhum dado exclusivo é enviado.\"" # A ping indicating an app load, no unique data is sent. +time_to_sync = "\"O tempo em segundos que o MPW levou a sincronizar pela última vez.\"" # The time in seconds it took for MPW to last synchronise. +ID = "BI" # ID +walletUnlockStake = "Desbloquear o seu para skate" # Unlock to stake your +walletUnlockUnstake = "Desbloquear para retirar o seu" # Unlock to unstake your +popupSetColdAddr = "Defina o seu endereço de Frio Staking" # Set your Cold Staking address +popupColdStakeNote = "Um Endereço de aposta moedas em seu nome, não pode gastar moedas, então é até seguro usar o Cold Address de um estranho!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupWalletWipeNote = "Você perderá o acesso aos seus fundos se não tiver feito o backup de sua chave privada ou frase inicial" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Frase inicial Inesperada" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "A frase inicial é inválida ou não foi gerada pelo MPW.
Você ainda quer continuar" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupProposalAddress = "Endereço da proposta (facultativo)" # Proposal Address (Optional) +popupProposalEncryptFirst = "É necessário premir o botão \"{button}\" antes de poder criar propostas!" # You need to hit "{button}" before you can create proposals! +proposalTooYoung = "Demasiado Jovem" # Too Young +proposalNetYes = "Sim Líquido" # Net Yes +syncStatusHistoryProgress = "Sincronização de fracções do histórico {atual} de {total}" # Syncing History Chunks {current} of {total} +syncStatusStarting = "Sua carteira está sincronizando!
Você poderá usá-la totalmente quando isso for concluído." # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "Sincronização concluída!
Sua carteira está pronta para uso!" # Sync Finished!
Your wallet is ready to use! +accountDeleted = "Sua conta foi excluída com sucesso!" # Your account has been successfully deleted! +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "" # Save Wallet File +proposalOverBudget = "" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +STAKE_ADDR_SET = "Endereço de Cold Staking definido!
Ao fazer Stake no futuro irá ser usado este endereço." # Cold Address set!
Future stakes will use this address. +STAKE_NOT_SEND = "Aqui, use o ecrã de Stake, não o ecrã de Envio!" # Here, use the Stake screen, not the Send screen! +SAVE_WALLET_PLEASE = "Guarde a sua carteira!
Painel ➜ Proteja a sua carteira" # Save your wallet!
Dashboard ➜ Secure your wallet +NO_CAMERAS = "Este dispositivo não tem câmara!" # This device has no camera! +MN_ACCESS_BEFORE_VOTE = "Aceda a um masternode antes de votar!" # Access a masternode before voting! +MN_DESTROYED = "Masternode destruído!
Já pode despender das suas moedas." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "O estado do seu masternode é" # Your masternode status is +MN_STATE = "O estado do seu masternode é {state}" # Your masternode is in {state} state +MN_ENOUGH_BUT_NO_COLLAT = "Você tem saldo suficiente para um Masternode, mas nenhum UTXO como garantia válido de {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +CONTACTS_ENCRYPT_FIRST = " É necessário carregar em \"{button}\" antes de poder utilizar Contactos!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "/É necessário um nome!" # A name is required! +CONTACTS_NAME_TOO_LONG = "Esse nome é demasiado longo!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Não se pode adicionar a si próprio como contacto!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = " O contacto já existe!

Já guardou este contacto" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "#O nome do contacto já existe!
Pode tratar-se de uma tentativa de phishing. Cuidado!" +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "#O contacto já existe!
Já existe um contacto com o nome \"{strNewName}\"!" +CONTACTS_KEY_ALREADY_EXISTS = " #O contacto já existe, mas com um nome diferente!
Tem {newName} guardado como {oldName} nos seus contactos" +CONTACTS_NOT_A_CONTACT_QR = "Isto não é um QR de contacto!" # This isn't a Contact QR! +CONTACTS_ADDED = "#Novo contacto adicionado!
{strName} foi adicionado, viva!" +CONTACTS_YOU_HAVE_NONE = " Não tem contactos!" # You have no contacts! +PROMO_NOT_ENOUGH = "Não tem {ticker} suficiente para criar esse código!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "Já criou esse código!" # You've already created that code! +SWITCHED_EXPLORERS = "Explorador trocado!
A usar agora o {explorerName}" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Nó trocado!
A usar o {node}" # Switched node!
Now using {node} +SWITCHED_SYNC = "Modo de sincronização alternado!
A usar agora a sincronização {sync}" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Não é possível alternar o modo Testnet!
Já está carregada uma carteira." # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_FIREFOX_UNSUPPORTED = "O Firefox não suporta isto!
Infelizmente, o Firefox não suporta carteiras de hardware" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_UDEV = "O SO negou acesso Você adicionou as regras do udev?" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "O sistema operativo negou o acesso Verifique as definições do seu sistema operativo." # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_CONNECTION_LOST = "Conexão perdida com a {hardwareWallet}
Oops! Parece que a {hardwareWalletProductionName} foi desconectado no meio da operação." # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_BUSY = "{hardwareWallet} está em modo de espera
Por favor desbloqueie a sua {hardwareWalletProductionName} ou conclua a introdução atual" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +CONFIRM_POPUP_VOTE_HTML = "Tem a certeza? Demora 60 minutos para mudar de voto" # Are you sure? It takes 60 minutes to change vote +FAILED_TO_IMPORT_HARDWARE = "Falha ao importar a carteira de hardware." # Failed to import Hardware Wallet. +CONFIRM_POPUP_DELETE_ACCOUNT = "Isso excluirá todos os seus dados, incluindo contatos de masternodes e chaves privadas!" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "Tens a certeza?" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/locale/pt/translation.toml b/locale/pt/translation.toml new file mode 100644 index 000000000..aee71e9e3 --- /dev/null +++ b/locale/pt/translation.toml @@ -0,0 +1,206 @@ +staking = "Staking" # Staking +wallet = "Carteira" # Wallet +display = "Mostrar" # Display +activity = "Atividade" # Activity +yes = "Sim" # Yes +no = "Não" # No +navDashboard = "Painel" # Dashboard +navStake = "Stake" # Stake +navMasternode = "Masternode" # Masternode +navGovernance = "Governança" # Governance +navSettings = "Configurações" # Settings +loadingTitle = "A Minha Carteira PIVX" # My PIVX Wallet is +dashboardTitle = "Painel" # Dashboard +dCardOneSubTitle = "Nova Carteira" # New Wallet +dCardOneDesc = "Crie uma carteira nova PIVX, oferecendo os métodos de backup e segurança mais seguros." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Crie uma Carteira Nova" # Create A New Wallet +dCardTwoTitle = "Criar Nova" # Create a new +dCardTwoSubTitle = "Carteira Vanity" # Vanity Wallet +dCardTwoButton = "Criar uma Carteira Vanity" # Create A Vanity Wallet +dCardThreeSubTitle = "Carteira de hardware" # Ledger Wallet +dCardFourTitle = "Vá para" # Go to +dCardFourSubTitle = "A minha Carteira" # My Wallet +dCardFourButtonI = "Importar Carteira" # Import Wallet +doNotShare = "NÃO a compartilhe com ninguém." # Do NOT share it with anyone. +secureYourWallet = "Proteja a sua carteira" # Secure your wallet +unlockWallet = "Desbloquear a carteira" # Unlock wallet +encryptWallet = "Criptografar carteira" # Encrypt wallet +encryptPasswordFirst = "Digite a senha" # Enter Password +encrypt = "Criptografar" # Encrypt +balanceBreakdown = "Composição do Saldo" # Balance Breakdown +viewOnExplorer = "Ver no Explorador " # View on Explorer +export = "Exportar" # Export +refreshAddress = "Atualizar endereço" # Refresh address +redeemOrCreateCode = "Resgatar ou Criar Código" # Redeem or Create Code +address = "Endereço" # Address +receivingAddress = "Endereço de recepção" # Receiving address +paymentRequestMessage = "Descrição (do comerciante)" # Description (from the merchant) +send = "Enviar" # Send +receive = "Receber" # Receive +name = "Nome" # Name +addressOrXPub = "Endereço ou XPub" # Address or XPub +back = "Voltar" # Back +newName = "Novo nome" # New Name +removeContactTitle = "Remover {strName}?" # Remove {strName}? +privateKey = "Chave privada" # Private Key +viewPrivateKey = "Mostrar a chave privada?" # View Private Key? +privateWarning2 = "Qualquer pessoa com esta chave pode roubar os seus fundos" # Anyone with this key can steal your funds. +viewKey = "Ver a chave" # View key +redeemInput = "Digite o seu código 'PIVX Promos'" # Enter your 'PIVX Promos' code +createName = "Nome da promoção (opcional)" # Promo Name (Optional) +createAmount = "Valor promocional" # Promo Amount +stake = "Stake" # Stake +stakeUnstake = "Unstake" # Unstake +rewardHistory = "Histórico de recompensas" # Reward History +loadMore = "Carregar mais" # Load more +mnControlYour = "Controle o seu" # Control your +govSubtext = "Nesta aba você pode conferir as propostas e, se tiver um masternode, fazer parte do DAO e votar!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Orçamento mensal" # Monthly Budget +govAllocBudget = "Orçamento Alocado" # Allocated Budget +govTableStatus = "ESTADO" # STATUS +govTableName = "NOME" # NAME +govTablePayment = "PAGAMENTO" # PAYMENT +govTableVotes = "VOTOS" # VOTES +govTableVote = "VOTO" # VOTE +contestedProposalsTitle = "Propostas Contestadas" # Contested Proposals +settingsCurrency = "Escolha uma moeda de exibição:" # Choose a display currency: +settingsExplorer = "Escolha um explorador:" # Choose an explorer: +settingsLanguage = "Escolha um Idioma:" # Choose a Language: +settingsPivxNode = "Escolha um node PIVX:" # Choose a PIVX node: +settingsAutoSelectNet = "Seleção automática de Exploradores e Nodes" # Auto-select Explorers and Nodes +settingsAnalytics = "Escolha o seu nível de contribuição analítica:" # Choose your analytics contribution level: +settingsToggleDebug = "Modo de depuração" # Debug Mode +settingsToggleTestnet = "Modo Testnet" # Testnet Mode +settingsToggleAdvancedMode = "Modo avançado" # Advanced Mode +transparencyReport = "\"Relatório de Transparência\"" # Transparency Report +transaction = "\"Um ping indicando uma Tx, nenhum dado exclusivo é enviado, mas pode ser inferido a partir do tempo na rede.\"" # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Desativado" # Disabled +analyticMinimal = "Mínimo" # Minimal +analyticBalanced = "Saldo" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Falha ao recuperar conta" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "Ocorreu um erro ao recuperar a sua conta.
Por favor reimporte sua carteira usando a seguinte chave:" # There was an error recovering your account.
Please reimport your wallet using the following key: +time = "Tempo" # Time +description = "Descrição" # Description +activityBlockReward = "Bloco Recompensa" # Block Reward +activitySentTo = "Enviado para {r}" # Sent to {r} +activitySelf = "si mesmo" # self +activityShieldedAddress = "Endereço Protegido" # Shielded address +activityDelegatedTo = "Delegado a {r}" # Delegated to {r} +activityUndelegated = "Não é Delegado" # Undelegated +activityUnknown = "Tx desconhecido" # Unknown Tx +password = "Senha" # Password +walletUnlock = "Desbloquear a sua carteira" # Unlock your wallet +walletPassword = "Senha da Carteira" # Wallet password +walletUnlockCreateMN = "Desbloquear para criar o seu Masternode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Desbloquear para iniciar o seu Masternode" # Unlock to start your Masternode! +walletUnlockProposal = "Desbloquear para criar uma proposta" # Unlock to create a proposal! +walletUnlockPromo = "Desbloquear para finalizar o seu código promocional" # Unlock to finalise your Promo Code! +walletUnlockTx = "Desbloquear para enviar a sua transação" # Unlock to send your transaction! +changelogTitle = "O que há de Novo em" # What's New in +popupCurrentAddress = "Endereço atual" # Current address: +popupExample = "Exemplo:" # Example: +popupWalletLock = "Você quer bloquear a sua carteira" # Do you want to lock your wallet? +popupWalletWipe = "Deseja limpar os dados privados da sua carteira" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Você precisará digitar sua senha para acessar seus fundos" # You will need to enter your password to access your funds +popupCreateProposal = "Criar Proposta" # Create Proposal +popupCreateProposalCost = "Custo" # Cost +popupProposalName = "Título da Proposta" # Proposal Name +popupProposalDuration = "Duração em ciclos" # Duration in cycles +popupProposalPerCycle = "por ciclo" # per cycle +popupProposalVoteHash = "Votação de Hash:" # Vote Hash: +popupProposalFinalisedNote = "Parabéns pelo lançamento da sua proposta!
Os proprietários do Masternode podem usar a sua votação de hash em outras carteiras que não sejam MPW, então certifique-se de adicionar isso à sua publicação no fórum, se aplicável!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Boa sorte na sua jornada pelo DAO, PIVian!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Confirme se este é o seu endereço que você vê" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "A Confirmar" # Confirming... +proposalFinalisationRemaining = "restante" # remaining +proposalFinalisationExpired = "Proposta Expirada" # Proposal Expired +proposalFinalisationReady = "Pronto para enviar" # Ready to submit +proposalPassing = "Passagem" # Passing +proposalFailing = "Falha" # Failing +proposalFunded = "Financiado/a" # Funded +proposalNotFunded = "Não financiado/a" # Not Funded +proposalPaymentsRemaining = "parcela(s) restante(s)
de" # installment(s) remaining
of +proposalPaymentTotal = "Total" # total +popupConfirm = "Confirme" # Confirm +popupClose = "Fechar" # Close +popupCancel = "Cancelar" # Cancel +chartPublicAvailable = "Público disponível" # Public Available +timeDays = "Dias" # Days +timeHours = "Horas" # Hours +timeMinutes = "Minutos" # Minutes +timeSeconds = "Segundos" # Seconds +unhandledException = "Exceção não tratada" # Unhandled exception. +activityReceivedWith = "Recebido com {s}" # Received with {s} + +[ALERTS] +INTERNAL_ERROR = "Erro interno, por favor tente novamente mais tarde" # Internal error, please try again later +FAILED_TO_IMPORT = "Falha ao importar! Senha inválida" # Failed to import! Invalid password +UNSUPPORTED_CHARACTER = "O caracter {char} não é suportado em endereços! (Não é compatível com Base58)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Este navegador não suporta Web Workers (JS multi-threaded), infelizmente você não pode gerar carteiras Vanity!" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Endereço PIVX inválido!
{address}" # Invalid PIVX address!
{address} +TESTNET_ENCRYPTION_DISABLED = "Modo Testnet ativado!
Encriptação da carteira desativada" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Esta senha é um pouco curta!
Use pelo menos {MIN_PASS_LENGTH} caracteres." # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "As suas senhas não correspondem!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Você está protegido! 🔐
Muito bem, PIVian blindado!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Senha incorreta!" # Incorrect password! +INVALID_AMOUNT = "Valor inválido!
" # Invalid amount!
+TX_SENT = "Transação enviada!" # Transaction sent! +TX_FAILED = "Falha na transação!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "não é um receptor de pagamento válido" # is not a valid payment receiver +VALIDATE_AMOUNT_LOW = "
O valor mínimo é {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} limite decimal excedido" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Endereço de Staking definido!
Prossiga com o unstake!" # Staking Address set!
Now go ahead and unstake! +CONFIRM_UNSTAKE_H_WALLET = "Confirme o seu Unstake
Confirme a TX no seu {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Confirme a sua transação
Confirme a TX no seu {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Endereço de Staking definido!
>Prossiga com o stake" # Staking Address set!
Now go ahead and stake! +STAKE_ADDR_BAD = "Endereço de Cold Staking inválido!" # Invalid Cold Staking address! +BAD_ADDR_LENGTH = "Endereço PIVX inválido!
Comprimento incorreto ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Endereço PIVX inválido!
Prefixo inválido {address} (Deve começar com {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "Você não pode enviar 'nada'!" # You can't send 'nothing'! +BACKUP_OR_ENCRYPT_WALLET = "Criptografe e/ou faça backup das suas chaves antes de sair, ou você pode perdê-las!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +STAKING_LEDGER_NO_SUPPORT = "A Ledger não é compatível com Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Falha ao sincronizar! Tente novamente mais tarde.
Pode tentar reconectar através das Configurações." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "O seu masternode ainda não está ativado!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Voto enviado!" # Vote submitted! +VOTED_ALREADY = "Você já votou nesta proposta! Aguarde 1 hora" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Falha ao verificar a assinatura, verifique a chave privada do seu masternode" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternode criado!
Aguarde 15 confirmações para prosseguir" # Masternode Created!
Wait 15 confirmations to proceed further +MN_OFFLINE_STARTING = "O seu masternode está offline, vamos tentar iniciá-lo" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternode iniciado!" # Masternode started! +MN_RESTARTED = "Masternode reiniciado!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternode iniciado!
Em breve estará online" # Masternode started!
It'll be online soon +MN_START_FAILED = "Masternode iniciado!" # Masternode started! +MN_RESTART_FAILED = "Masternode reiniciado!" # Masternode restarted! +MN_BAD_IP = "O endereço IP é inválido!" # The IP address is invalid! +MN_BAD_PRIVKEY = "A chave privada é inválida" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Você precisa de {amount} mais {ticker} para criar um Masternode!" # You need {amount} more {ticker} to create a Masternode! +MN_COLLAT_NOT_SUITABLE = "Este não é um UTXO adequado para um Masternode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Não é possível conectar ao nó RPC!" # Unable to connect to RPC node! +PROPOSAL_FINALISED = "Proposta finalizada!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "A proposta ainda não foi confirmada." # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "A proposta expirou. Crie uma nova." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Falha ao finalizar a proposta." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Crie ou importe a sua carteira para continuar" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Não há fundos suficientes para criar uma proposta." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "A proposta é inválida. Erro:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Proposta criada!
Aguarde 6 confirmações para finalizar." # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "O valor mínimo é {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "O seu dispositivo só pode criar {quantity} códigos de cada vez!" # Your device can only create {quantity} codes at a time! +SWITCHED_ANALYTICS = "Nível de análise alterado!
Agora é {level}" # Switched analytics level!
Now {level} +WALLET_OFFLINE_AUTOMATIC = "O modo offline está ativo!
Por favor desabilite o Modo Offline para transações automáticas" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Por favor, {unlock} a sua carteira antes de enviar transações!" # Please {unlock} your wallet before sending transactions! +WALLET_HARDWARE_WALLET = "Carteira de hardware pronta!
Mantenha a sua {hardwareWallet} conectada, desbloqueada e na aplicação PIVX" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Confirme a importação na sua Ledger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "Nenhum dispositivo disponível
Não foi possível encontrar uma carteira de hardware; conecte-a e desbloqueie-a!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Confirmar Voto" # Confirm Vote +CONFIRM_POPUP_TRANSACTION = "Confirme a sua transação" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "A chave privada do seu Masternode" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Guarde esta chave privada e copie-a para a sua configuração no VPS
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Verifique o seu endereço" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Falha ao recuperar o seu masternode. Por favor, reimporte-o." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Falha ao recuperar a sua conta. Por favor, reimporte-a." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "Aplicação instalada!" # App Installed! + +[info] +merged = true diff --git a/locale/template/translation.toml b/locale/template/translation.toml new file mode 100644 index 000000000..cd777e5b4 --- /dev/null +++ b/locale/template/translation.toml @@ -0,0 +1,359 @@ +# This document is to be used as a template as all the base code is in English +# Basic HTML tags are allowed such as etc. All data is sanitized https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML + +# When updating the template you must also update the en version. If you don't and a new language with a new setting is used, it +# will NOT translate back after switching back to en + +# How to create a new language: +# 1) Create a new folder in locale and copy the template, add the locale prefix (en, uwu, etc) to the start of the variable +# (var translation -> var en_translation) then edit the template with your new languages words. +# 2) Go into the i18n.js and find `arrActiveLangs` and add the prefix to that array. ['en'] -> ['en', 'uwu'] +# 3) Go into the i18n.js page and find `translatableLanguages` add your prefix and variable name +# var translatableLanguages = { +# "en": en_translation +# } +# Turns into: +# var translatableLanguages = { +# "en": en_translation +# "uwu": uwu_translation +# } +# 4) Submit a push request to the github + +# NOTE: If a section does NOT need translating, leave it empty. +# NOTE: Variables that MPW insert are denoted by brackets {}, for example, {button}, do NOT translate variables, but place them where it makes the most sense. + +amount = "Amount" +staking = "Staking" +wallet = "Wallet" +display = "Display" +activity = "Activity" +yes = "Yes" +no = "No" +navDashboard = "Dashboard" +navStake = "Stake" +navMasternode = "Masternode" +navGovernance = "Governance" +navSettings = "Settings" +footerBuiltWithPivxLabs = "Built with 💜 by PIVX Labs" +loading = "Loading" +loadingTitle = "My PIVX Wallet is" +dashboardTitle = "Dashboard" +dCardOneTitle = "Create a" +dCardOneSubTitle = "New Wallet" +dCardOneDesc = "Create a new PIVX wallet, offering the most secure backup & security methods." +dCardOneButton = "Create A New Wallet" +dCardTwoTitle = "Create a new" +dCardTwoSubTitle = "Vanity Wallet" +dCardTwoDesc = "Create a wallet with a custom prefix, this can take a long time!" +dCardTwoButton = "Create A Vanity Wallet" +dCardThreeTitle = "Access your" +dCardThreeSubTitle = "Ledger Wallet" +dCardThreeDesc = "Use your Ledger Hardware wallet with MPW's familiar interface." +dCardThreeButton = "Access my Ledger" +dCardFourTitle = "Go to" +dCardFourSubTitle = "My Wallet" +dCardFourDesc = "Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase." +dCardFourButtonI = "Import Wallet" +dCardFourButtonA = "Access My Wallet" +vanityPrefixNote = "Note: addresses will always start with:" +vanityPrefixInput = "Address Prefix" +thisIsYourSeed = "This is your seed phrase:" +writeDownSeed = "Write it down somewhere. You'll only see this once!" +doNotShareWarning = "Anyone with a copy of it can access all of your funds." +doNotShare = "Do NOT share it with anyone." +digitalStoreNotAdvised = "It is NOT advised to store this digitally." +optionalPassphrase = "Optional Passphrase (BIP39)" +writtenDown = "I have written down my seed phrase" +importSeedValid = "Seed Phrase is valid!" +importSeedError = "Seed Phrase is invalid!" +importSeedErrorSize = "A Seed Phrase should be 12 or 24 words long!" +importSeedErrorTypo = "Seed Phrase contains typing errors! Check your input carefully" +importSeedErrorSkip = "Seed Phrase appears invalid, but the warning was skipped by the user" +gettingStarted = "Getting Started" +secureYourWallet = "Secure your wallet" +unlockWallet = "Unlock wallet" +lockWallet = "Lock wallet" +syncStatusHistoryProgress = "Syncing History Chunks {current} of {total}" +syncLoadingSaplingProver = "Loading SHIELD parameters..." +syncShieldProgress = "Loading shielded blocks {current} of {total}" +syncStatusStarting = "Your wallet is syncing!
You'll be able to use it fully once this is complete." +syncStatusFinished = "Sync Finished!
Your wallet is ready to use!" +encryptWallet = "Encrypt wallet" +encryptPasswordCurrent = "Current Password" +encryptPasswordFirst = "Enter Password" +encryptPasswordSecond = "Re-enter Password" +encrypt = "Encrypt" +changePassword = "Change Password" +balanceBreakdown = "Balance Breakdown" +viewOnExplorer = "View on Explorer" +export = "Export" +refreshAddress = "Refresh address" +redeemOrCreateCode = "Redeem or Create Code" +address = "Address" +receivingAddress = "Receiving address" +sendAmountCoinsMax = "MAX" +paymentRequestMessage = "Description (from the merchant)" +send = "Send" +receive = "Receive" +contacts = "Contacts" +name = "Name" +username = "Username" +addressOrXPub = "Address or XPub" +back = "Back" +chooseAContact = "Choose a Contact" +createContact = "Create Contact" +encryptFirstForContacts = "Once you hit \"{button}\" in the Dashboard, you can create a Contact to make receiving PIV easier!" +shareContactURL = "Share Contact URL" +setupYourContact = "Setup your Contact" +receiveWithContact = "Receive using a simple username-based Contact" +onlyShareContactPrivately = "Only share your Contact with trusted people (family, friends)" +changeTo = "Change to" +contact = "Contact" +xpub = "XPub" +addContactTitle = "Add {strName} to Contacts" +addContactSubtext = "Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy." +addContactWarning = "Ensure that this is the real \"{strName}\", do not accept Contact requests from unknown sources!" +editContactTitle = "Change \"{strName}\" Contact" +newName = "New Name" +removeContactTitle = "Remove {strName}?" +removeContactSubtext = "Are you sure you wish to remove {strName} from your Contacts?" +removeContactNote = "You can add them again any time in the future." +privateKey = "Private Key" +viewPrivateKey = "View Private Key?" +privateWarning1 = "Make sure no one can see your screen." +privateWarning2 = "Anyone with this key can steal your funds." +viewKey = "View key" +saveWalletFile = "Save Wallet File" +pivxPromos = "is a decentralised system for gift codes worth PIV" +redeemInput = "Enter your 'PIVX Promos' code" +createName = "Promo Name (Optional)" +createAmount = "Promo Amount" +stake = "Stake" +stakeUnstake = "Unstake" +ownerAddress = "(Optional) Owner Address" +rewardHistory = "Reward History" +loadMore = "Load more" +mnControlYour = "Control your" +mnSubtext = "From this tab you can create and access one or more masternodes" +govSubtext = "From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote!" +govMonthlyBudget = "Monthly Budget" +govAllocBudget = "Allocated Budget" +govNextPayout = "Next Treasury Payout" +govTableStatus = "STATUS" +govTableName = "NAME" +govTablePayment = "PAYMENT" +govTableVotes = "VOTES" +govTableVote = "VOTE" +contestedProposalsTitle = "Contested Proposals" +contestedProposalsDesc = "These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal." +settingsCurrency = "Choose a display currency:" +priceProvidedBy = "Price data provided by" +settingsDecimals = "Balance Decimals:" +settingsExplorer = "Choose an explorer:" +settingsLanguage = "Choose a Language:" +settingsPivxNode = "Choose a PIVX node:" +settingsAutoSelectNet = "Auto-select Explorers and Nodes" +settingsAnalytics = "Choose your analytics contribution level:" +settingsToggleDebug = "Debug Mode" +settingsToggleTestnet = "Testnet Mode" +settingsToggleAdvancedMode = "Advanced Mode" +settingsToggleAdvancedModeSubtext = "This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users!" +settingsToggleAutoLockWallet = "Auto Lock the Wallet" +netSwitchUnsavedWarningTitle = "Your {network} wallet isn't saved!" +netSwitchUnsavedWarningSubtitle = "Your {network} account is at risk!" +netSwitchUnsavedWarningSubtext = "If you switch to {network} before saving it, you'll lose the account!" +netSwitchUnsavedWarningConfirmation = "Are you really sure?" +transparencyReport = "Transparency Report" +hit = "A ping indicating an app load, no unique data is sent." +time_to_sync = "The time in seconds it took for MPW to last synchronise." +transaction = "A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time." +analyticDisabled = "Disabled" +analyticMinimal = "Minimal" +analyticBalanced = "Balanced" +MIGRATION_ACCOUNT_FAILURE_TITLE = "Failed to recover account" +MIGRATION_ACCOUNT_FAILURE_HTML = "There was an error recovering your account.
Please reimport your wallet using the following key:" +ID = "ID" +time = "Time" +description = "Description" +accountDeleted = 'Your account has been successfully deleted!' +activityBlockReward = "Block Reward" +activitySentTo = "Sent to {r}" +activitySelf = "self" +activityShieldedAddress = "Shielded address" +activityReceivedWith = "Received with {s}" +activityDelegatedTo = "Delegated to {r}" +activityUndelegated = "Undelegated" +activityUnknown = "Unknown Tx" +password = "Password" +walletUnlock = "Unlock your wallet" +walletPassword = "Wallet password" +walletUnlockCreateMN = "Unlock to create your Masternode!" +walletUnlockMNStart = "Unlock to start your Masternode!" +walletUnlockProposal = "Unlock to create a proposal!" +walletUnlockPromo = "Unlock to finalise your Promo Code!" +walletUnlockTx = "Unlock to send your transaction!" +walletUnlockStake = "Unlock to stake your" +walletUnlockUnstake = "Unlock to unstake your" +changelogTitle = "What's New in" +popupSetColdAddr = "Set your Cold Staking address" +popupCurrentAddress = "Current address:" +popupColdStakeNote = "A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address!" +popupExample = "Example:" +popupWalletLock = "Do you want to lock your wallet?" +popupWalletWipe = "Do you want to wipe your wallet private data?" +popupWalletLockNote = "You will need to enter your password to access your funds" +popupWalletWipeNote = "You will lose access to your funds if you haven't backed up your private key or seed phrase" +popupSeedPhraseBad = "Unexpected Seed Phrase" +popupSeedPhraseBadNote = "The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed?" +popupCreateProposal = "Create Proposal" +popupCreateProposalCost = "Cost" +popupProposalName = "Proposal Name" +popupProposalAddress = "Proposal Address (Optional)" +popupProposalDuration = "Duration in cycles" +popupProposalPerCycle = "per cycle" +popupProposalEncryptFirst = "You need to hit \"{button}\" before you can create proposals!" +popupProposalVoteHash = "Vote Hash:" +popupProposalFinalisedNote = "Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable!" +popupProposalFinalisedSignoff = "Good luck on your journey through the DAO, PIVian!" +popupHardwareAddrCheck = "Please confirm this is the address you see on your" +proposalFinalisationConfirming = "Confirming..." +proposalFinalisationRemaining = "remaining" +proposalFinalisationExpired = "Proposal Expired" +proposalFinalisationReady = "Ready to submit" +proposalPassing = "Passing" +proposalFailing = "Failing" +proposalTooYoung = "Too Young" +proposalFunded = "Funded" +proposalNotFunded = "Not Funded" +proposalOverBudget = "Over Budget" +proposalPaymentsRemaining = "installment(s) remaining
of" +proposalPaymentTotal = "total" +proposalNetYes = "Net Yes" +popupConfirm = "Confirm" +popupClose = "Close" +popupCancel = "Cancel" +chartPublicAvailable = "Public Available" +chartImmatureBalance = "Immature balance" +timeDays = "Days" +timeHours = "Hours" +timeMinutes = "Minutes" +timeSeconds = "Seconds" +unhandledException = "Unhandled exception." +useShieldInputs = "Use shield inputs" +newShieldAddress = "Get new shield address" +shieldAddress = "Shield address" +cantShieldToExc = "This address does not support shield transfers" +badSaplingRoot = "There was an error while syncing. Resyncing from scratch (Bad sapling root)" +creatingShieldTransaction = "Creating SHIELD transaction..." + +[ALERTS] +INTERNAL_ERROR = "Internal error, please try again later" +FAILED_TO_IMPORT = "Failed to import! Invalid password" +FAILED_TO_IMPORT_HARDWARE = " Failed to import Hardware Wallet." +TESTNET_ENCRYPTION_DISABLED = "Testnet Mode is ON!
Wallet encryption disabled" +PASSWORD_TOO_SMALL = "That password is a little short!
Use at least {MIN_PASS_LENGTH} characters." +PASSWORD_DOESNT_MATCH = "Your passwords don't match!" +NEW_PASSWORD_SUCCESS = "You're Secured! 🔐
Nice stuff, Armoured PIVian!" +INCORRECT_PASSWORD = "Incorrect password!" +INVALID_AMOUNT = "Invalid amount!
" +TX_SENT = "Transaction sent!" +TX_FAILED = "Transaction Failed!" +QR_SCANNER_BAD_RECEIVER = "is not a valid payment receiver" +UNSUPPORTED_CHARACTER = "The character '{char}' is unsupported in addresses! (Not Base58 compatible)" +UNSUPPORTED_WEBWORKERS = "This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets!" +INVALID_ADDRESS = "Invalid PIVX address!
{address}" +VALIDATE_AMOUNT_LOW = "
Minimum amount is {minimumAmount} {coinTicker}!" +VALIDATE_AMOUNT_DECIMAL = "{coinDecimal} decimal limit exceeded" +SUCCESS_STAKING_ADDR = "Staking Address set!
Now go ahead and unstake!" +CONFIRM_UNSTAKE_H_WALLET = "Confirm your Unstake
Confirm the TX on your {strHardwareName}" +CONFIRM_TRANSACTION_H_WALLET = "Confirm your transaction
Confirm the TX on your {strHardwareName}" +SUCCESS_STAKING_ADDR_SET = "Staking Address set!
Now go ahead and stake!" +STAKE_ADDR_SET = "Cold Address set!
Future stakes will use this address." +STAKE_ADDR_BAD = "Invalid Cold Staking address!" +STAKE_NOT_SEND = "Here, use the Stake screen, not the Send screen!" +BAD_ADDR_LENGTH = "Invalid PIVX address!
Bad length ({addressLength})" +BAD_ADDR_PREFIX = "Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix})" +SENT_NOTHING = "You can't send 'nothing'!" +SAVE_WALLET_PLEASE = "Save your wallet!
Dashboard ➜ Secure your wallet" +BACKUP_OR_ENCRYPT_WALLET = "Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them!" +NO_CAMERAS = "This device has no camera!" +STAKING_LEDGER_NO_SUPPORT = "Ledger is not supported for Cold Staking" +CONNECTION_FAILED = "Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings." +MN_NOT_ENABLED = "Your masternode is not enabled yet!" +VOTE_SUBMITTED = "Vote submitted!" +VOTED_ALREADY = "You already voted for this proposal! Please wait 1 hour" +VOTE_SIG_BAD = "Failed to verify signature, please check your masternode's private key" +MN_CREATED_WAIT_CONFS = "Masternode Created!
Wait 15 confirmations to proceed further" +MN_ACCESS_BEFORE_VOTE = "Access a masternode before voting!" +MN_OFFLINE_STARTING = "Your masternode is offline, we will try to start it" +MN_STARTED = "Masternode started!" +MN_RESTARTED = "Masternode restarted!" +MN_STARTED_ONLINE_SOON = "Masternode started!
It'll be online soon" +MN_START_FAILED = "Masternode started!" +MN_RESTART_FAILED = "Masternode restarted!" +MN_DESTROYED = "Masternode destroyed!
Your coins are now spendable." +MN_STATUS_IS = "Your masternode status is" +MN_STATE = "Your masternode is in {state} state" +MN_BAD_IP = "The IP address is invalid!" +MN_BAD_PRIVKEY = "The private key is invalid" +MN_NOT_ENOUGH_COLLAT = "You need {amount} more {ticker} to create a Masternode!" +MN_ENOUGH_BUT_NO_COLLAT = "You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker}" +MN_COLLAT_NOT_SUITABLE = "This is not a suitable UTXO for a Masternode" +MN_CANT_CONNECT = "Unable to connect to RPC node!" +CONTACTS_ENCRYPT_FIRST = "You need to hit \"{button}\" before you can use Contacts!" +CONTACTS_NAME_REQUIRED = "A name is required!" +CONTACTS_NAME_TOO_LONG = "That name is too long!" +CONTACTS_CANNOT_ADD_YOURSELF = "You cannot add yourself as a Contact!" +CONTACTS_ALREADY_EXISTS = "Contact already exists!
You already saved this contact" +CONTACTS_NAME_ALREADY_EXISTS = "Contact name already exists!
This could potentially be a phishing attempt, beware!" +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Contact already exists!
A contact is already called \"{strNewName}\"!" +CONTACTS_KEY_ALREADY_EXISTS = "Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts" +CONTACTS_NOT_A_CONTACT_QR = "This isn't a Contact QR!" +CONTACTS_ADDED = "New Contact added!
{strName} has been added, hurray!" +CONTACTS_YOU_HAVE_NONE = "You have no contacts!" +PROPOSAL_FINALISED = "Proposal Launched!" +PROPOSAL_UNCONFIRMED = "The proposal hasn't confirmed yet" +PROPOSAL_EXPIRED = "The proposal has expired. Create a new one." +PROPOSAL_FINALISE_FAIL = "Failed to finalize proposal." +PROPOSAL_IMPORT_FIRST = "Create or import your wallet to continue" +PROPOSAL_NOT_ENOUGH_FUNDS = "Not enough funds to create a proposal." +PROPOSAL_INVALID_ERROR = "Proposal is invalid. Error:" +PROPOSAL_CREATED = "Proposal Created!
Wait for confirmations, then finalise your proposal!" +PROMO_MIN = "Minimum amount is {min} {ticker}!" +PROMO_MAX_QUANTITY = "Your device can only create {quantity} codes at a time!" +PROMO_NOT_ENOUGH = "You don't have enough {ticker} to create that code!" +PROMO_ALREADY_CREATED = "You've already created that code!" +SWITCHED_EXPLORERS = "Switched explorer!
Now using {explorerName}" +SWITCHED_NODE = "Switched node!
Now using {node}" +SWITCHED_ANALYTICS = "Switched analytics level!
Now {level}" +SWITCHED_SYNC = "Switched sync mode!
Now using {sync} sync" +UNABLE_SWITCH_TESTNET = "Unable to switch Testnet Mode!
A wallet is already loaded" +WALLET_OFFLINE_AUTOMATIC = "Offline Mode is active!
Please disable Offline Mode for automatic transactions" +WALLET_UNLOCK_IMPORT = "Please {unlock} your wallet before sending transactions!" +WALLET_HARDWARE_WALLET = "Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app" +WALLET_CONFIRM_L = "Confirm the import on your Ledger" +WALLET_NO_HARDWARE = "No device available
Couldn't find a hardware wallet; please plug it in and unlock!" +WALLET_HARDWARE_UDEV = "The OS denied access Did you add the udev rules?" +WALLET_HARDWARE_NO_ACCESS = "The OS denied access Please check your Operating System settings." +WALLET_HARDWARE_CONNECTION_LOST = "Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops!" +WALLET_HARDWARE_BUSY = "{hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt" +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" +WALLET_NOT_SYNCED = "Please try again when wallet finishes syncing!" +WALLET_LOCKED = "Wallet successfully Locked!" +WALLET_UNLOCKED = "Wallet successfully Unlocked!" +CONFIRM_POPUP_VOTE = "Confirm Vote" +CONFIRM_POPUP_VOTE_HTML = "Are you sure? It takes 60 minutes to change vote" +CONFIRM_POPUP_TRANSACTION = "Confirm your transaction" +CONFIRM_POPUP_MN_P_KEY = "Your Masternode Private Key" +CONFIRM_POPUP_MN_P_KEY_HTML = "
Save this private key and copy it to your VPS config
" +CONFIRM_POPUP_VERIFY_ADDR = "Verify your address" +MIGRATION_MASTERNODE_FAILURE = "Failed to recover your masternode. Please reimport it." +MIGRATION_ACCOUNT_FAILURE = "Failed to recover your account. Please reimport it." +APP_INSTALLED = "App Installed!" +WALLET_FIREFOX_UNSUPPORTED ="Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets" +CONFIRM_POPUP_DELETE_ACCOUNT = 'This will delete all your data, including masternodes contacts and private keys!' +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = 'Are you sure?' +CONFIRM_LEDGER_TX = 'Confirm this transaction matches the one on your {hardwareWallet}' +CONFIRM_LEDGER_TX_OUT = 'You will send {value} {ticker} to
{address}
' +MISSING_FUNDS = 'Balance is too small! Missing {sats} sats!' +MISSING_SHIELD = "Shield is not enabled in this wallet!" \ No newline at end of file diff --git a/locale/uwu/translation.toml b/locale/uwu/translation.toml new file mode 100644 index 000000000..2ec8dde52 --- /dev/null +++ b/locale/uwu/translation.toml @@ -0,0 +1,335 @@ +amount = "ameownt" # Amount +staking = "" # Staking +wallet = "Wawwet" # Wallet +display = "Dispway" # Display +activity = "Actwivity" # Activity +yes = "Yas" # Yes +no = "Nu" # No +navDashboard = "Dashbowed" # Dashboard +navStake = "Stakin'" # Stake +navMasternode = "Masternowode" # Masternode +navGovernance = "Gubernance" # Governance +navSettings = "Configgy" # Settings +footerBuiltWithPivxLabs = "Built with wuv by PIVX Wabs 💜" # Built with 💜 by PIVX Labs +loading = "Loading" # Loading +loadingTitle = "My PIVX Wawwet is" # My PIVX Wallet is +dashboardTitle = "Dashbowod" # Dashboard +dCardOneTitle = "Cweate a" # Create a +dCardOneSubTitle = "New Wawwet!" # New Wallet +dCardOneDesc = "Cweate a new PIVX wawwet, offewing da most secuwur backup & securrrity methods." # Create a new PIVX wallet, offering the most secure backup & security methods. +dCardOneButton = "Cweate A New Wawwet" # Create A New Wallet +dCardTwoTitle = "Cweate a new" # Create a new +dCardTwoSubTitle = "Vanity Wawwet" # Vanity Wallet +dCardTwoDesc = "Cweate a wawwet wiv a custom pwefix, dis can take a long twime!" # Create a wallet with a custom prefix, this can take a long time! +dCardTwoButton = "Cweate A Vanity Wawwet" # Create A Vanity Wallet +dCardThreeTitle = "Access yowour" # Access your +dCardThreeSubTitle = "Hawdware Wawwet" # Ledger Wallet +dCardThreeDesc = "Use ur Ledger Hardware wawwet wiv MPW's famiwiar intwerface." # Use your Ledger Hardware wallet with MPW's familiar interface. +dCardThreeButton = "Access my Ledger" # Access my Ledger +dCardFourTitle = "Go to" # Go to +dCardFourSubTitle = "My Wawwet" # My Wallet +dCardFourDesc = "Impowt a PIVX wawwet using a Pwivate Key, xpriv, or Seed Phrase." # Import a PIVX wallet using a Private Key, xpriv, or Seed Phrase. +dCardFourButtonI = "Impowt Wawwet" # Import Wallet +dCardFourButtonA = "Access My Wawwet" # Access My Wallet +vanityPrefixNote = "Note: addwesses will always start wif:" # Note: addresses will always start with: +vanityPrefixInput = "Addwess Prefix" # Address Prefix +thisIsYourSeed = "This is ur seed phrase:" # This is your seed phrase: +writeDownSeed = "Write it down, baka! You'll only see this once!" # Write it down somewhere. You'll only see this once! +doNotShareWarning = "Anyone with a copy of it can access all of ur funds." # Anyone with a copy of it can access all of your funds. +doNotShare = "Do NOT share it with anyuwu." # Do NOT share it with anyone. +digitalStoreNotAdvised = "It is NAWT advised to store this digitally." # It is NOT advised to store this digitally. +optionalPassphrase = "Optional Passphwase (BIP39)" # Optional Passphrase (BIP39) +writtenDown = "I haz written down my seed phrase" # I have written down my seed phrase +importSeedValid = "Seed Phwase iz valid!" # Seed Phrase is valid! +importSeedError = "Seed Phwase iz invalid!" # Seed Phrase is invalid! +importSeedErrorSize = "A Seed Phwase shwould be 12 or 24 words long!" # A Seed Phrase should be 12 or 24 words long! +importSeedErrorTypo = "Seed Phwase contains typing ewrrors! Check ur input carefully" # Seed Phrase contains typing errors! Check your input carefully +importSeedErrorSkip = "Seed Phwase appears invalid, but da warning was skipped by da user" # Seed Phrase appears invalid, but the warning was skipped by the user +gettingStarted = "Getting Stwarted" # Getting Started +secureYourWallet = "Secure ur wawwet" # Secure your wallet +unlockWallet = "Unlock wawwet" # Unlock wallet +lockWallet = "Lock wawwet" # Lock wallet +encryptWallet = "Encwypt wawwet" # Encrypt wallet +encryptPasswordCurrent = "Cuwwent Password" # Current Password +encryptPasswordFirst = "Entwer Password" # Enter Password +encryptPasswordSecond = "Re-entwer Password" # Re-enter Password +encrypt = "Encwypt" # Encrypt +changePassword = "Change Passwoword" # Change Password +balanceBreakdown = "Bwalance Bweakdown" # Balance Breakdown +viewOnExplorer = "View on Expwower" # View on Explorer +export = "Expwort" # Export +refreshAddress = "Refwesh address" # Refresh address +redeemOrCreateCode = "Redeem or Cweate Cowode" # Redeem or Create Code +address = "Addwess" # Address +receivingAddress = "Receivwing addwess" # Receiving address +sendAmountCoinsMax = "MAX♡" # MAX +paymentRequestMessage = "Deswiption (fwom da Mewrchant)" # Description (from the merchant) +send = "Send" # Send +receive = "Receive" # Receive +contacts = "Contactz" # Contacts +name = "Name" # Name +username = "Username" # Username +addressOrXPub = "Addwess or XPubby" # Address or XPub +back = "Backu" # Back +chooseAContact = "Chowose a Contact" # Choose a Contact +createContact = "Cweate Contactu" # Create Contact +encryptFirstForContacts = "Once yew hit \"{button}\" in the Dashboard, yew can cweate a Contact to make receiving PIV easier!" # Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier! +shareContactURL = "Share Fren URL" # Share Contact URL +setupYourContact = "Setup ur Contact" # Setup your Contact +receiveWithContact = "Receive using a simp-le username-based Contact" # Receive using a simple username-based Contact +onlyShareContactPrivately = "Only share ur Contact with trusted peeps (family, friends)" # Only share your Contact with trusted people (family, friends) +changeTo = "Change tew" # Change to +contact = "Contactu" # Contact +xpub = "XPubby" # XPub +addContactTitle = "Add {strName} tew Contacts" # Add {strName} to Contacts +addContactSubtext = "Once added you'll be ablwe tew send transactions tew {strName} by their name (either typing, or clicking), no more addwesses, nice 'n OwO." # Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy. +addContactWarning = "Ensure dat dis is da real \"{strName}\", do not accept Contact requests fwom unknown sources!" # Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources! +editContactTitle = "Change \"{strName}\" Contactu" # Change "{strName}" Contact +newName = "Neww Name" # New Name +removeContactTitle = "Unfren {strName}?" # Remove {strName}? +removeContactSubtext = "Are u sure u wish to remove {strName} from your Fren list?" # Are you sure you wish to remove {strName} from your Contacts? +removeContactNote = "Yew can add dem again any time in the future, but... :(" # You can add them again any time in the future. +privateKey = "Pwivate Key" # Private Key +viewPrivateKey = "View Pwivate Key?" # View Private Key? +privateWarning1 = "Make sure no one can see ur scween." # Make sure no one can see your screen. +privateWarning2 = "Anyone with dis key can steal ur funds." # Anyone with this key can steal your funds. +viewKey = "View the secret sauce" # View key +pivxPromos = "is a decentralised system for gifty cowodes worth PIV" # is a decentralised system for gift codes worth PIV +redeemInput = "Enter ur 'PIVX Promos' cowode" # Enter your 'PIVX Promos' code +createName = "Pwomo Name (Optional)" # Promo Name (Optional) +createAmount = "Pwomo Ameownt" # Promo Amount +stake = "" # Stake +stakeUnstake = "" # Unstake +ownerAddress = "(Optional) Owner Addwess" # (Optional) Owner Address +rewardHistory = "Rewawrd Histowy" # Reward History +loadMore = "Lowoad Mowore" # Load more +mnControlYour = "Contwol ur" # Control your +mnSubtext = "Fwom dis tab you can cweate and access one or more masternowodes" # From this tab you can create and access one or more masternodes +govSubtext = "Fwom dis tab yew can check the pwoposals and, if you have a masternowode, be a part of the DAO and vwote!" # From this tab you can check the proposals and, if you have a masternode, be a part of the DAO and vote! +govMonthlyBudget = "Monthly Budgey" # Monthly Budget +govAllocBudget = "Allocated Budgey" # Allocated Budget +govNextPayout = "Next Moneybags Rain" # Next Treasury Payout +govTableStatus = "STATUS" # STATUS +govTableName = "NAME" # NAME +govTablePayment = "PAYMENT" # PAYMENT +govTableVotes = "VOWOTES" # VOTES +govTableVote = "VOWOTE" # VOTE +contestedProposalsTitle = "Contwested Pwoposals" # Contested Proposals +contestedProposalsDesc = "Dees are pwoposals dat received an overwhelming ameownt of downwotes, making it likely spam or a highly contwestable pwoposal." # These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal. +settingsCurrency = "Chowose a dispway cuwwency:" # Choose a display currency: +priceProvidedBy = "Pwice data pwovided by" # Price data provided by +settingsDecimals = "Balance Decimawls:" # Balance Decimals: +settingsExplorer = "Chowose an expwower:" # Choose an explorer: +settingsLanguage = "Chowose a Languwuage:" # Choose a Language: +settingsAnalytics = "Chowose your anawytics contwibution wevel:" # Choose your analytics contribution level: +settingsPivxNode = "Chowose a PIVX nowode pwease:" # Choose a PIVX node: +settingsAutoSelectNet = "Auto-select Expwowers and Nowodes" # Auto-select Explorers and Nodes +settingsToggleDebug = "Debug Mowode" # Debug Mode +settingsToggleTestnet = "Testnet Mowode" # Testnet Mode +settingsToggleAdvancedMode = "Advwanced Mowode" # Advanced Mode +settingsToggleAdvancedModeSubtext = "Dis unlocks deeper fwunctionality and cuwustomisatwion, but may be oveuhwhelming and potentially dangerwus for unexperienced bakas!" # This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! +netSwitchUnsavedWarningTitle = "Ur {network} wawwet isn't saved!" # Your {network} wallet isn't saved! +netSwitchUnsavedWarningSubtitle = "Ur {network} account could get fucky-wuckied!" # Your {network} account is at risk! +netSwitchUnsavedWarningSubtext = "If u switch to {network} befwore saving it, you'll lose the account!" # If you switch to {network} before saving it, you'll lose the account! +netSwitchUnsavedWarningConfirmation = "Are u reaaaaaaaaally sure?" # Are you really sure? +transparencyReport = "Twanspawency Repawt" # Transparency Report +hit = "A ping indicating an app load, no unique data is sent.♡" # A ping indicating an app load, no unique data is sent. +time_to_sync = "The time in seconds it took for MPW to last synchronise.♡" # The time in seconds it took for MPW to last synchronise. +transaction = "A ping indicating a Tx, no unique data is sent, but may be infewwed from on-chain time.♡" # A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time. +analyticDisabled = "Disabwed" # Disabled +analyticMinimal = "Minimuwul" # Minimal +analyticBalanced = "Bawanced" # Balanced +MIGRATION_ACCOUNT_FAILURE_TITLE = "Failed to recover accowount" # Failed to recover account +MIGRATION_ACCOUNT_FAILURE_HTML = "There was an error recovering ur accowount.
Pwease reimport ur wawwet using the following key:" # There was an error recovering your account.
Please reimport your wallet using the following key: +ID = "ID" # ID +time = "Time" # Time +description = "Descwiption" # Description +activityBlockReward = "Bwock Rewawrd" # Block Reward +activitySentTo = "Sentu to {r}" # Sent to {r} +activitySelf = "selfu" # self +activityDelegatedTo = "Delegwated to {r}" # Delegated to {r} +activityUndelegated = "Undelegwated" # Undelegated +activityUnknown = "Unknown Tx" # Unknown Tx +password = "Password" # Password +walletUnlock = "Unlock ur wawwet" # Unlock your wallet +walletPassword = "Wawwet password" # Wallet password +walletUnlockCreateMN = "Unlock to cweate ur Masternowode!" # Unlock to create your Masternode! +walletUnlockMNStart = "Unlock to start ur Masternowode!" # Unlock to start your Masternode! +walletUnlockProposal = "Unlock to cweate a pwoposal!" # Unlock to create a proposal! +walletUnlockPromo = "Unlock to finalise ur Pwomo Cowode!" # Unlock to finalise your Promo Code! +walletUnlockTx = "Unlock to send ur twansaction!" # Unlock to send your transaction! +walletUnlockStake = "Unlock to stake ur" # Unlock to stake your +walletUnlockUnstake = "Unlock to unstake ur" # Unlock to unstake your +changelogTitle = "What's Newu in" # What's New in +popupSetColdAddr = "Set ur Cold Staking addwess" # Set your Cold Staking address +popupCurrentAddress = "Current addwess:" # Current address: +popupColdStakeNote = "A Cold Addwess stakes coins on ur behalf, it cannot spend coins, so it's even safe to uwuse a stwanger's Cold Addwess!" # A Cold Address stakes coins on your behalf, it cannot spend coins, so it's even safe to use a stranger's Cold Address! +popupExample = "Examplez:" # Example: +popupWalletLock = "Do yew want to lock ur wawwet?" # Do you want to lock your wallet? +popupWalletWipe = "Do yew want to wipe ur wawwet pwivate data?" # Do you want to wipe your wallet private data? +popupWalletLockNote = "Yew will need to enter ur password to access ur funds" # You will need to enter your password to access your funds +popupWalletWipeNote = "Yew will lose access to ur funds if yew haven't backed up ur pwivate key or seed phwase" # You will lose access to your funds if you haven't backed up your private key or seed phrase +popupSeedPhraseBad = "Unexpectwed Seed Phwase" # Unexpected Seed Phrase +popupSeedPhraseBadNote = "The seed phwase iz either invalid, or was nawt generated by MPW.
Do yew still want to pwroceed?" # The seed phrase is either invalid or was not generated by MPW.
Do you still want to proceed? +popupCreateProposal = "Cweate Pwoposal" # Create Proposal +popupCreateProposalCost = "Cost" # Cost +popupProposalName = "Pwoposal Name" # Proposal Name +popupProposalAddress = "Pwoposal Addwess (Optional)" # Proposal Address (Optional) +popupProposalDuration = "Duwration in cycles" # Duration in cycles +popupProposalPerCycle = "per cycle" # per cycle +popupProposalEncryptFirst = "Yew need tew hit \"{button}\" before yew can cweate pwoposals!" # You need to hit "{button}" before you can create proposals! +popupProposalVoteHash = "Vowote Hash:" # Vote Hash: +popupProposalFinalisedNote = "Congratsu on launching ur pwoposal DAO senpai!
Masternowode owners can use ur Vowote Hash to vowote from wawwets other than MPW, so make sure to add dis to ur forum post, if applicable!" # Congratulations on launching your proposal!
Masternode owners can use your Vote Hash to vote from wallets other than MPW, so make sure to add this to your forum post, if applicable! +popupProposalFinalisedSignoff = "Good lucky on ur journey through the DAO, Senpai!" # Good luck on your journey through the DAO, PIVian! +popupHardwareAddrCheck = "Pwease confwirm dis is the addwess you see on ur" # Please confirm this is the address you see on your +proposalFinalisationConfirming = "Confwirming..." # Confirming... +proposalFinalisationRemaining = "left" # remaining +proposalFinalisationExpired = "Pwoposal Expired" # Proposal Expired +proposalFinalisationReady = "Ready tew submit" # Ready to submit +proposalPassing = "Passing, YAY!" # Passing +proposalFailing = "Failing, NAY!" # Failing +proposalTooYoung = "Too Young, BAKA!" # Too Young +proposalFunded = "Funded!" # Funded +proposalNotFunded = "No Monies!" # Not Funded +proposalPaymentsRemaining = "payment(s) remainingz
of" # installment(s) remaining
of +proposalPaymentTotal = "totalz" # total +proposalNetYes = "Net Yasss'es" # Net Yes +popupConfirm = "Confirm-u" # Confirm +popupClose = "Close-u" # Close +popupCancel = "Cancel-u" # Cancel +chartPublicAvailable = "Public Avaiwable" # Public Available +timeDays = "Dayz" # Days +timeHours = "Hourz" # Hours +timeMinutes = "Minutez" # Minutes +timeSeconds = "Secondz" # Seconds +unhandledException = "Unhandled exception." # Unhandled exception. +syncStatusHistoryProgress = "" # Syncing History Chunks {current} of {total} +syncStatusStarting = "" # Your wallet is syncing!
You'll be able to use it fully once this is complete. +syncStatusFinished = "" # Sync Finished!
Your wallet is ready to use! +accountDeleted = "" # Your account has been successfully deleted! +activityShieldedAddress = "" # Shielded address +activityReceivedWith = "" # Received with {s} +chartImmatureBalance = "" # Immature balance +syncLoadingSaplingProver = "" # Loading SHIELD parameters... +syncShieldProgress = "" # Loading shielded blocks {current} of {total} +useShieldInputs = "" # Use shield inputs +newShieldAddress = "" # Get new shield address +shieldAddress = "" # Shield address +cantShieldToExc = "" # This address does not support shield transfers +settingsToggleAutoLockWallet = "" # Auto Lock the Wallet +saveWalletFile = "Save Wawwet File" # Save Wallet File +proposalOverBudget = "Over Budgey" # Over Budget +badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) +creatingShieldTransaction = "" # Creating SHIELD transaction... + +[ALERTS] +INTERNAL_ERROR = "Internal error, pwease try again later" # Internal error, please try again later +FAILED_TO_IMPORT = "Faiwed to impawt! Invawed password! Baka!" # Failed to import! Invalid password +TESTNET_ENCRYPTION_DISABLED = "Testnet Mowode in ON!
Wawwet encwyption disabwed" # Testnet Mode is ON!
Wallet encryption disabled +PASSWORD_TOO_SMALL = "Dat password is a wittle short!
Pwease use at least {MIN_PASS_LENGTH} chawacters!" # That password is a little short!
Use at least {MIN_PASS_LENGTH} characters. +PASSWORD_DOESNT_MATCH = "Yowour passwords dun match!! baka!!" # Your passwords don't match! +NEW_PASSWORD_SUCCESS = "Yowou're Secuwed!
Good Job, PIVX Pogchamp!" # You're Secured! 🔐
Nice stuff, Armoured PIVian! +INCORRECT_PASSWORD = "Incowwect password!" # Incorrect password! +INVALID_AMOUNT = "Invawed ameownt
" # Invalid amount!
+TX_SENT = "Twansaction sentu!" # Transaction sent! +TX_FAILED = "Twansaction Failed!" # Transaction Failed! +QR_SCANNER_BAD_RECEIVER = "is not a valid payment receiver" # is not a valid payment receiver +UNSUPPORTED_CHARACTER = "The chawacter '{char}' is unsupurrted in addwesses! (Not Base58 compatible)" # The character '{char}' is unsupported in addresses! (Not Base58 compatible) +UNSUPPORTED_WEBWORKERS = "Dis bwowser doesn't suppurrt web workers" # This browser doesn't support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! +INVALID_ADDRESS = "Invawed PIVX addwess baka
{address} " # Invalid PIVX address!
{address} +VALIDATE_AMOUNT_LOW = "
Minimum ameownt is {minimumAmount} {coinTicker}!" #
Minimum amount is {minimumAmount} {coinTicker}! +VALIDATE_AMOUNT_DECIMAL = " decimal wimit exceeded. Yowouve gone too fawr!" # {coinDecimal} decimal limit exceeded +SUCCESS_STAKING_ADDR = "Staking Addwess set!
Now go ahead and unstake senpai❣" # Staking Address set!
Now go ahead and unstake! +STAKE_ADDR_SET = "Cold Addwess set!
Future stakes will use dis addwess." # Cold Address set!
Future stakes will use this address. +STAKE_ADDR_BAD = "Invawid Cold Staking addwess!" # Invalid Cold Staking address! +CONFIRM_UNSTAKE_H_WALLET = " Confirm yowour Unstake
Confirm da TX on yowour {strHardwareName}" # Confirm your Unstake
Confirm the TX on your {strHardwareName} +CONFIRM_TRANSACTION_H_WALLET = "Confirm yowoure twansaction
Confirm da TX on yowour {strHardwareName}" # Confirm your transaction
Confirm the TX on your {strHardwareName} +SUCCESS_STAKING_ADDR_SET = "Staking Addwess set!~
Now go ahead and stake senpai❣" # Staking Address set!
Now go ahead and stake! +STAKE_NOT_SEND = "Here senpai❣ use da Stake scween, Not da send scween, baka!" # Here, use the Stake screen, not the Send screen! +BAD_ADDR_LENGTH = "Invawed PIVX addwess!
Bad wength ({addressLength})" # Invalid PIVX address!
Bad length ({addressLength}) +BAD_ADDR_PREFIX = "Invawed PIVX addwess! Baka!
Bad pwefix {address} (Should start with {addressPrefix})" # Invalid PIVX address!
Bad prefix {address} (Should start with {addressPrefix}) +SENT_NOTHING = "You can't send nothing!! Baka!" # You can't send 'nothing'! +SAVE_WALLET_PLEASE = "Save yowour wawwet!
Dashbowed ➜ Secure your wallet" # Save your wallet!
Dashboard ➜ Secure your wallet +BACKUP_OR_ENCRYPT_WALLET = "Pwease ENCWYPT and/or BACKUP yowour keys befowe weaving, or you may lose dem! Oh noes!" # Please ENCRYPT and/or BACKUP your keys before leaving, or you may lose them! +NO_CAMERAS = "Dis device has nwo camwera!" # This device has no camera! +STAKING_LEDGER_NO_SUPPORT = "Ledger is not supported for Cold Staking" # Ledger is not supported for Cold Staking +CONNECTION_FAILED = "Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings." # Failed to synchronize! Please try again later.
You can attempt re-connect via the Settings. +MN_NOT_ENABLED = "Ur masternowode is nawt enabled yet!" # Your masternode is not enabled yet! +VOTE_SUBMITTED = "Vowote submitted!" # Vote submitted! +VOTED_ALREADY = "Yew already vowoted for dis pwoposal! Pwease wait 1 hour" # You already voted for this proposal! Please wait 1 hour +VOTE_SIG_BAD = "Failed to vewify signature, pwease check ur masternowode's pwivate key" # Failed to verify signature, please check your masternode's private key +MN_CREATED_WAIT_CONFS = "Masternowode Created!
Wait 15 confwirmations to pwoceed further" # Masternode Created!
Wait 15 confirmations to proceed further +MN_ACCESS_BEFORE_VOTE = "Access a masternowode befwore vowoting!" # Access a masternode before voting! +MN_OFFLINE_STARTING = "Ur masternowode is offline, we'll try to start it" # Your masternode is offline, we will try to start it +MN_STARTED = "Masternowode started!" # Masternode started! +MN_RESTARTED = "Masternowode restarted!" # Masternode restarted! +MN_STARTED_ONLINE_SOON = "Masternowode started!
It'll be online soon" # Masternode started!
It'll be online soon +MN_START_FAILED = "Failed to start masternowode!" # Masternode started! +MN_RESTART_FAILED = "Failed to restart masternowode!" # Masternode restarted! +MN_DESTROYED = "Masternowode destrwoyed!
Ur coins are now spendable." # Masternode destroyed!
Your coins are now spendable. +MN_STATUS_IS = "Ur masternowode status is" # Your masternode status is +MN_STATE = "Ur masternowode is in {state} state" # Your masternode is in {state} state +MN_BAD_IP = "The IP addwess is invalid!" # The IP address is invalid! +MN_BAD_PRIVKEY = "" # The private key is invalid +MN_NOT_ENOUGH_COLLAT = "Yew need {amount} more {ticker} to create a Masternowode!" # You need {amount} more {ticker} to create a Masternode! +MN_ENOUGH_BUT_NO_COLLAT = "Yew haz enough balance for a Masternowode, but nu valid collateral UTXO of {amount} {ticker}" # You have enough balance for a Masternode, but no valid collateral UTXO of {amount} {ticker} +MN_COLLAT_NOT_SUITABLE = "Dis is not a suitable UTXO for a Masternowode" # This is not a suitable UTXO for a Masternode +MN_CANT_CONNECT = "Unable to connect to RPC nowode!" # Unable to connect to RPC node! +CONTACTS_ENCRYPT_FIRST = "Yew need tew hit \"{button}\" before yew can use Contacts!" # You need to hit "{button}" before you can use Contacts! +CONTACTS_NAME_REQUIRED = "A name iz required!" # A name is required! +CONTACTS_NAME_TOO_LONG = "That name iz teww long!" # That name is too long! +CONTACTS_CANNOT_ADD_YOURSELF = "Yew cannot add urself as a Contact!" # You cannot add yourself as a Contact! +CONTACTS_ALREADY_EXISTS = "Contact already exists!
Yew already saved dis contact" # Contact already exists!
You already saved this contact +CONTACTS_NAME_ALREADY_EXISTS = "Contact name already exists!
Dis could potentially be a phishing attempt, beware!" # Contact name already exists!
This could potentially be a phishing attempt, beware! +CONTACTS_EDIT_NAME_ALREADY_EXISTS = "Contact already exists!
A contact iz already cawlled \"{strNewName}\"!" # Contact already exists!
A contact is already called "{strNewName}"! +CONTACTS_KEY_ALREADY_EXISTS = "Contact already exists, buh under a different name!
Yew have {newName} saved as {oldName} in ur contacts" # Contact already exists, but under a different name!
You have {newName} saved as {oldName} in your contacts +CONTACTS_NOT_A_CONTACT_QR = "Dis isn't a Contact QR, baka!" # This isn't a Contact QR! +CONTACTS_ADDED = "New Contact added!
{strName} haz been added, hurray!" # New Contact added!
{strName} has been added, hurray! +CONTACTS_YOU_HAVE_NONE = "Yew have no contacts! Lonely!" # You have no contacts! +PROPOSAL_FINALISED = "Pwoposal finalized!" # Proposal Launched! +PROPOSAL_UNCONFIRMED = "Da pwoposal hasn't been confirmed yet." # The proposal hasn't confirmed yet +PROPOSAL_EXPIRED = "Da pwoposal has expired. Cweate a new one." # The proposal has expired. Create a new one. +PROPOSAL_FINALISE_FAIL = "Failed to finalize pwoposal." # Failed to finalize proposal. +PROPOSAL_IMPORT_FIRST = "Cweate or impwort ur wawwet to continue" # Create or import your wallet to continue +PROPOSAL_NOT_ENOUGH_FUNDS = "Not enough funds to cweate a pwoposal." # Not enough funds to create a proposal. +PROPOSAL_INVALID_ERROR = "Pwoposal is invalid. Error:" # Proposal is invalid. Error: +PROPOSAL_CREATED = "Pwoposal cweated!
Wait 6 confwirmations to finalise." # Proposal Created!
Wait for confirmations, then finalise your proposal! +PROMO_MIN = "Minimum ameownt is {min} {ticker}!" # Minimum amount is {min} {ticker}! +PROMO_MAX_QUANTITY = "Ur device can only cweate {quantity} cowodes at a time!" # Your device can only create {quantity} codes at a time! +PROMO_NOT_ENOUGH = "Yew don't have enough {ticker} to cweate that code!" # You don't have enough {ticker} to create that code! +PROMO_ALREADY_CREATED = "You've already created that code!" # You've already created that code! +SWITCHED_EXPLORERS = "Switched expwower!
Nowo using {explorerName}✿" # Switched explorer!
Now using {explorerName} +SWITCHED_NODE = "Switched nowode!✿
Nowo using {node}" # Switched node!
Now using {node} +SWITCHED_ANALYTICS = "Switched anawytics wevel!
Nowo {level}" # Switched analytics level!
Now {level} +SWITCHED_SYNC = "Switched sync mowode!✿
Nowo using {sync} sync" # Switched sync mode!
Now using {sync} sync +UNABLE_SWITCH_TESTNET = "Unable to switch Testnet Mowode!
A wawwet is alweady woaded✿" # Unable to switch Testnet Mode!
A wallet is already loaded +WALLET_OFFLINE_AUTOMATIC = "Offwine Mowode is active!
Pwease disabwe Offwine Mowode for automatic twansactions" # Offline Mode is active!
Please disable Offline Mode for automatic transactions +WALLET_UNLOCK_IMPORT = "Pwease {unlock} your wawwet befowore sending twansactions!" # Please {unlock} your wallet before sending transactions! +WALLET_FIREFOX_UNSUPPORTED = "Oh noes!Firefox senpai doesn't suppurrt this!
Unfortunatewy, Firefox senpai does not suppurrt hawdware wawwets" # Firefox doesn't support this!
Unfortunately, Firefox does not support hardware wallets +WALLET_HARDWARE_WALLET = "Hawdware wawwet ready!
Pwease keep your {hardwareWallet} pwugged in, unwocked, and in da PIVX app ♥" # Hardware wallet ready!
Please keep your {hardwareWallet} plugged in, unlocked, and in the PIVX app +WALLET_CONFIRM_L = "Confiwm da impowot on your Wedger" # Confirm the import on your Ledger +WALLET_NO_HARDWARE = "No device avaiwable ☹
Couldn't find a hawdware wawwet; pwease pwug it in and unwock!" # No device available
Couldn't find a hardware wallet; please plug it in and unlock! +WALLET_HARDWARE_CONNECTION_LOST = "Wost connection to da {hardwareWallet}
It seems da {hardwareWalletProductionName} was unpwugged mid-opewation, oops!!" # Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops! +WALLET_HARDWARE_UDEV = "Onii-chan noticed the OS denied access~ OwO Did you add the udev rules? UwU" # The OS denied access Did you add the udev rules? +WALLET_HARDWARE_NO_ACCESS = "Nyaa~ The OS denied access, nya~ Please purr-retty please check your Operating System settings, nya~ UwU" # The OS denied access Please check your Operating System settings. +WALLET_HARDWARE_BUSY = "{hardwareWallet} is waiting!
Pwease unwock yowour {hardwareWalletProductionName} or finish it's cuwwent pwompt" # {hardwareWallet} is waiting
Please unlock your {hardwareWallet} or finish it's current prompt +WALLET_HARDWARE_ERROR = " {hardwareWallet}
{error}" # {hardwareWallet}
{error} +CONFIRM_POPUP_VOTE = "Confiwm Vowote!" # Confirm Vote +CONFIRM_POPUP_VOTE_HTML = "Are you suuure? It takes 60 minutes to change yowour vowote" # Are you sure? It takes 60 minutes to change vote +CONFIRM_POPUP_TRANSACTION = "Confiwm yowour twansaction" # Confirm your transaction +CONFIRM_POPUP_MN_P_KEY = "Yowour Masternode Pwivate Key" # Your Masternode Private Key +CONFIRM_POPUP_MN_P_KEY_HTML = "
Save dis pwivate key and copy it to yowour VPS config
" #
Save this private key and copy it to your VPS config
+CONFIRM_POPUP_VERIFY_ADDR = "Vewify yowour addwess" # Verify your address +MIGRATION_MASTERNODE_FAILURE = "Failed to recover ur masternowode. Pwease reimport it." # Failed to recover your masternode. Please reimport it. +MIGRATION_ACCOUNT_FAILURE = "Failed to recover ur account. Pwease reimport it." # Failed to recover your account. Please reimport it. +APP_INSTALLED = "App Instawlled!" # App Installed! +FAILED_TO_IMPORT_HARDWARE = "" # Failed to import Hardware Wallet. +CONFIRM_POPUP_DELETE_ACCOUNT = "" # This will delete all your data, including masternodes contacts and private keys! +CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = "" # Are you sure? +WALLET_NOT_SYNCED = "" # Please try again when wallet finishes syncing! +WALLET_LOCKED = "" # Wallet successfully Locked! +WALLET_UNLOCKED = "" # Wallet successfully Unlocked! +CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardwareWallet} +CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
+MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats! +MISSING_SHIELD = "" # Shield is not enabled in this wallet! diff --git a/manifest.json b/manifest.json new file mode 100644 index 000000000..4cded8df2 --- /dev/null +++ b/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "My PIVX Wallet", + "short_name": "MPW", + "start_url": ".", + "description": "Send, Stake and Receive with PIVX's most universal wallet. ", + "orientation": "portrait", + "display": "standalone", + "icons": [ + { + "src": "512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "192.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "background_color": "#17012c", + "theme_color": "#470e75" +} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..fa8126793 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,5 @@ +[[headers]] + for = "/*" + [headers.values] + Cross-Origin-Embedder-Policy = "require-corp" + Cross-Origin-Opener-Policy = "same-origin" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..b2063c2b7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11350 @@ +{ + "name": "mypivxwallet", + "version": "1.5.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mypivxwallet", + "version": "1.5.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@fontsource/chivo": "^4.5.11", + "@fontsource/montserrat": "^4.5.14", + "@fortawesome/fontawesome-free": "^6.2.1", + "@ledgerhq/hw-app-btc": "^9.1.1", + "@ledgerhq/hw-transport-webusb": "^6.27.9", + "@noble/hashes": "^1.1.5", + "@noble/secp256k1": "^1.7.0", + "@popperjs/core": "^2.11.6", + "base32": "^0.0.7", + "bech32": "^2.0.0", + "biginteger": "^1.0.3", + "bip39": "^3.0.4", + "bootstrap-4": "^4.0.0", + "bs58": "^5.0.0", + "buffer": "^6.0.3", + "chart.js": "^4.2.1", + "country-locale-map": "^1.8.15", + "create-xpub": "^2.1.0", + "hdkey": "^2.0.1", + "idb": "^7.1.1", + "ip-address": "^8.1.0", + "jdenticon": "^3.2.0", + "jquery": "^3.6.3", + "pinia": "^2.1.7", + "pivx-promos": "^0.2.0", + "pivx-shield": "^1.1.3", + "pivx-shield-rust": "^1.1.3", + "pivx-shield-rust-multicore": "^1.1.3", + "qr-scanner": "^1.4.2", + "qrcode-generator": "^1.4.4", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@fluent/langneg": "^0.7.0", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "@typescript-eslint/parser": "^5.48.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitest/coverage-istanbul": "^1.0.2", + "@vue/preload-webpack-plugin": "^2.0.0", + "@vue/test-utils": "^2.4.2", + "copy-webpack-plugin": "^11.0.0", + "cross-env": "^7.0.3", + "css-loader": "^6.7.3", + "css-minimizer-webpack-plugin": "^4.2.2", + "eslint": "^8.31.0", + "fake-indexeddb": "^5.0.1", + "file-loader": "^6.2.0", + "gh-pages": "^5.0.0", + "happy-dom": "^12.10.3", + "html-loader": "^4.2.0", + "html-webpack-plugin": "^5.5.0", + "mini-css-extract-plugin": "^2.7.2", + "node-polyfill-webpack-plugin": "^2.0.1", + "prettier": "^2.8.1", + "raw-loader": "^4.0.2", + "resource-loader": "^4.0.0-rc4", + "toml": "^3.0.0", + "vitest": "^1.0.2", + "vue-loader": "^17.2.2", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1", + "webpack-dev-server": "^4.11.1", + "webpack-merge": "^5.8.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fluent/langneg": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@fluent/langneg/-/langneg-0.7.0.tgz", + "integrity": "sha512-StAM0vgsD1QK+nFikaKs9Rxe3JGNipiXrpmemNGwM4gWERBXPe9gjzsBoKjgBgq1Vyiy+xy/C652QIWY+MPyYw==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@fontsource/chivo": { + "version": "4.5.11", + "resolved": "https://registry.npmjs.org/@fontsource/chivo/-/chivo-4.5.11.tgz", + "integrity": "sha512-fWGRS4RW5xkN0obUcyFhYrZPSKdnTfCR3FIKduG3uCYAV+Ana/f3ObyDO3UHDC5sRLxHTAOSFJewocgpZmZRXQ==" + }, + "node_modules/@fontsource/montserrat": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/@fontsource/montserrat/-/montserrat-4.5.14.tgz", + "integrity": "sha512-fTvrteVzuFUePhr4QYBGoK8G/YHLJ3IhF1HhKg0AxcFvZajJT7rM7ULdmKLSd2PkX44R3aaFZq1zDbmjbGGI+w==" + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz", + "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@ledgerhq/devices": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.2.2.tgz", + "integrity": "sha512-SKahGA4p0mZ3ovypOJ2wa5mUvUkArE3HBrwWKYf+cRs+t/Licp3OJfhj+DHIxP3AfyH2xR6CFFWECYHeKwGsDQ==", + "dependencies": { + "@ledgerhq/errors": "^6.16.3", + "@ledgerhq/logs": "^6.12.0", + "rxjs": "^7.8.1", + "semver": "^7.3.5" + } + }, + "node_modules/@ledgerhq/errors": { + "version": "6.16.3", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.16.3.tgz", + "integrity": "sha512-3w7/SJVXOPa9mpzyll7VKoKnGwDD3BzWgN1Nom8byR40DiQvOKjHX+kKQausCedTHVNBn9euzPCNsftZ9+mxfw==" + }, + "node_modules/@ledgerhq/hw-app-btc": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-btc/-/hw-app-btc-9.1.3.tgz", + "integrity": "sha512-f/eohEQ+Ha8iJ3ix0a42qa8NafeEWQdNhe0Q85xBTwVdzyu2W3kLwVfShqn/ELIy/lZffSNqYQKyHp8EnO4s8w==", + "dependencies": { + "@ledgerhq/hw-transport": "^6.28.0", + "@ledgerhq/logs": "^6.10.1", + "bip32-path": "^0.4.2", + "bitcoinjs-lib": "^5.2.0", + "bs58": "^4.0.1", + "bs58check": "^2.1.2", + "invariant": "^2.2.4", + "ripemd160": "2", + "semver": "^7.3.5", + "sha.js": "2", + "tiny-secp256k1": "1.1.6", + "varuint-bitcoin": "1.1.2" + } + }, + "node_modules/@ledgerhq/hw-app-btc/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@ledgerhq/hw-app-btc/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@ledgerhq/hw-transport": { + "version": "6.30.5", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.30.5.tgz", + "integrity": "sha512-JMl//7BgPBvWxrWyMu82jj6JEYtsQyOyhYtonWNgtxn6KUZWht3gU4gxmLpeIRr+DiS7e50mW7m3GA+EudZmmA==", + "dependencies": { + "@ledgerhq/devices": "^8.2.2", + "@ledgerhq/errors": "^6.16.3", + "@ledgerhq/logs": "^6.12.0", + "events": "^3.3.0" + } + }, + "node_modules/@ledgerhq/hw-transport-webusb": { + "version": "6.28.5", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.28.5.tgz", + "integrity": "sha512-93mye+T/deqwYfJV89iTqoLxJY6zXhT57vLTWUkae0s1S9n7Lj53KNWCOGVqdlq9DMY2qGrtnAw3V0xtfgsLXw==", + "dependencies": { + "@ledgerhq/devices": "^8.2.2", + "@ledgerhq/errors": "^6.16.3", + "@ledgerhq/hw-transport": "^6.30.5", + "@ledgerhq/logs": "^6.12.0" + } + }, + "node_modules/@ledgerhq/logs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.12.0.tgz", + "integrity": "sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA==" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", + "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/coverage-istanbul": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-1.5.0.tgz", + "integrity": "sha512-mEbVTIAPKhMkszO0lwOwWiG8Cvkj7rdMgdmCNUDnmcSZYUWGIqM8+4O1bcQ1WMHkejpcwvED5oU6ZFm3syVb6A==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-instrument": "^6.0.1", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "test-exclude": "^6.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.5.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.5.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.23.tgz", + "integrity": "sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==", + "dependencies": { + "@babel/parser": "^7.24.1", + "@vue/shared": "3.4.23", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.23.tgz", + "integrity": "sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==", + "dependencies": { + "@vue/compiler-core": "3.4.23", + "@vue/shared": "3.4.23" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.23.tgz", + "integrity": "sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==", + "dependencies": { + "@babel/parser": "^7.24.1", + "@vue/compiler-core": "3.4.23", + "@vue/compiler-dom": "3.4.23", + "@vue/compiler-ssr": "3.4.23", + "@vue/shared": "3.4.23", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.8", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.23.tgz", + "integrity": "sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==", + "dependencies": { + "@vue/compiler-dom": "3.4.23", + "@vue/shared": "3.4.23" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", + "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==" + }, + "node_modules/@vue/preload-webpack-plugin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-2.0.0.tgz", + "integrity": "sha512-RoorRB50WehYbsiWu497q8egZBYlrvOo9KBUG41uth4O023Cbs+7POLm9uw2CAiViBAIhvpw1Y4w4i+MZxOfXw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "html-webpack-plugin": "^5.0.0 || ^4.5.1", + "webpack": "^5.20.0 || ^4.1.0" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.23.tgz", + "integrity": "sha512-GlXR9PL+23fQ3IqnbSQ8OQKLodjqCyoCrmdLKZk3BP7jN6prWheAfU7a3mrltewTkoBm+N7qMEb372VHIkQRMQ==", + "dependencies": { + "@vue/shared": "3.4.23" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.23.tgz", + "integrity": "sha512-FeQ9MZEXoFzFkFiw9MQQ/FWs3srvrP+SjDKSeRIiQHIhtkzoj0X4rWQlRNHbGuSwLra6pMyjAttwixNMjc/xLw==", + "dependencies": { + "@vue/reactivity": "3.4.23", + "@vue/shared": "3.4.23" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.23.tgz", + "integrity": "sha512-RXJFwwykZWBkMiTPSLEWU3kgVLNAfActBfWFlZd0y79FTUxexogd0PLG4HH2LfOktjRxV47Nulygh0JFXe5f9A==", + "dependencies": { + "@vue/runtime-core": "3.4.23", + "@vue/shared": "3.4.23", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.23.tgz", + "integrity": "sha512-LDwGHtnIzvKFNS8dPJ1SSU5Gvm36p2ck8wCZc52fc3k/IfjKcwCyrWEf0Yag/2wTFUBXrqizfhK9c/mC367dXQ==", + "dependencies": { + "@vue/compiler-ssr": "3.4.23", + "@vue/shared": "3.4.23" + }, + "peerDependencies": { + "vue": "3.4.23" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.23.tgz", + "integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz", + "integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==", + "dev": true, + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/base32": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/base32/-/base32-0.0.7.tgz", + "integrity": "sha512-ire9Jmh+BsUk4Idu0wu6aKeJJr/2j28Mlu0qqJBd1SyOGxW/VRotkfwAv3/KE/entVlNwCefs9bxi7kBeCIxTQ==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "base32": "bin/base32.js" + }, + "engines": { + "node": ">0.10" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/biginteger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/biginteger/-/biginteger-1.0.3.tgz", + "integrity": "sha512-gmTfQyNVreGeTlSrZiD7mAg52B0e20NWTJdt9zESPcUce/SEdP0UT/GGal6OZxNELiues4+XuyJzyAlkuO9nNg==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "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-path": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/bip32-path/-/bip32-path-0.4.2.tgz", + "integrity": "sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ==" + }, + "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/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bitcoin-ops": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", + "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" + }, + "node_modules/bitcoinjs-lib": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz", + "integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==", + "dependencies": { + "bech32": "^1.1.2", + "bip174": "^2.0.1", + "bip32": "^2.0.4", + "bip66": "^1.1.0", + "bitcoin-ops": "^1.4.0", + "bs58check": "^2.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.3", + "merkle-lib": "^2.0.10", + "pushdata-bitcoin": "^1.0.1", + "randombytes": "^2.0.1", + "tiny-secp256k1": "^1.1.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.0.4", + "wif": "^2.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib/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/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/bootstrap": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, + "node_modules/bootstrap-4": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bootstrap-4/-/bootstrap-4-4.0.0.tgz", + "integrity": "sha512-vvGfZmLoBF2/l5CHMVIY1BBwdWVKD7qbxBWiB5aLZ99a5Hm5+zVho1fhtxhTar2lpjmQ8LrDnEM5Q/OOA7qSKg==", + "dependencies": { + "bootstrap": "^4.0.0-alpha.6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-sign/node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/bs58check/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bs58check/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001610", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", + "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/canvas-renderer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz", + "integrity": "sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/cctx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cctx/-/cctx-1.0.1.tgz", + "integrity": "sha512-AcWAyflX8kGUaYMyvCLzRpcOe4MyMYioPzqoQ8AIrNFSQS2ddKLW46fIKc4wiTPai6b/Pebx2acBc1vPjGWGpA==" + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/comlink": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz", + "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/country-locale-map": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/country-locale-map/-/country-locale-map-1.9.4.tgz", + "integrity": "sha512-mRg2rrNfZBgJ09pfHloaFHRcVoKJ8FR/Q0wInYdA9+oBwA+rb6z9m7wtEXcmS2tbD65ZY68mjLTneq589RkTMw==", + "dependencies": { + "cctx": "^1.0.1", + "fuzzball": "^2.1.2", + "nodemon": "^3.0.2" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "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/create-xpub": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/create-xpub/-/create-xpub-2.1.0.tgz", + "integrity": "sha512-Ngq2RBf90N3G7MfhaMGghdtKLSbJQFYAkWRpVXRRoxr1vLL2ArJo6RibcyAxKNz9N51tudljfobX6SCCcuXV1A==", + "dependencies": { + "bs58check": "^2.1.2", + "hash.js": "^1.1.7", + "ow": "^0.8.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz", + "integrity": "sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==", + "dev": true, + "dependencies": { + "cssnano": "^5.1.8", + "jest-worker": "^29.1.2", + "postcss": "^8.4.17", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/default-gateway/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-gateway/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/default-gateway/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domain-browser": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz", + "integrity": "sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.737", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.737.tgz", + "integrity": "sha512-QvLTxaLHKdy5YxvixAw/FfHq2eWLUL9KvsPjp0aHK1gI5d3EDuDgITkvj0nFO2c6zUY3ZqVAJQiBYyQP9tQpfw==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "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/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz", + "integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fake-indexeddb": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-5.0.2.tgz", + "integrity": "sha512-cB507r5T3D55DfclY01GLkninZLfU7HXV/mhVRTnTRm5k2u+fY7Fof2dBkr80p5t7G7dlA/G5dI87QiMdPpMCQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-2.0.2.tgz", + "integrity": "sha512-lO3ttPjHZRfjMcxWKb1j1eDhTFsu4meeR3lnMcnBFhk6RuLhvEiuALu2TlfL310ph4lCYYwgF/ElIjdP739tdg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzball": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fuzzball/-/fuzzball-2.1.2.tgz", + "integrity": "sha512-wVBw/a73M3luaX6ZHt9vIoEKT/rLqBkzdBRhQzWw/IQyIt0qnqc0IAJDCkX3CLgj2tRIUAfgDUT8G6YuMpmNXg==", + "dependencies": { + "heap": ">=0.2.0", + "setimmediate": "^1.0.5", + "string.fromcodepoint": "^0.2.1", + "string.prototype.codepointat": "^0.2.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gh-pages": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-5.0.0.tgz", + "integrity": "sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ==", + "dev": true, + "dependencies": { + "async": "^3.2.4", + "commander": "^2.18.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gh-pages/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/happy-dom": { + "version": "12.10.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-12.10.3.tgz", + "integrity": "sha512-JzUXOh0wdNGY54oKng5hliuBkq/+aT1V3YpTM+lrN/GoLQTANZsMaIvmHiHe612rauHvPJnDZkZ+5GZR++1Abg==", + "dev": true, + "dependencies": { + "css.escape": "^1.5.1", + "entities": "^4.5.0", + "iconv-lite": "^0.6.3", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hdkey": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-2.1.0.tgz", + "integrity": "sha512-i9Wzi0Dy49bNS4tXXeGeu0vIcn86xXdPQUpEYg+SO1YiO8HtomjmmRMaRyqL0r59QfcD4PfVbSF3qmsWFwAemA==", + "dependencies": { + "bs58check": "^2.1.2", + "ripemd160": "^2.0.2", + "safe-buffer": "^5.1.1", + "secp256k1": "^4.0.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-loader": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-4.2.0.tgz", + "integrity": "sha512-OxCHD3yt+qwqng2vvcaPApCEvbx+nXWu+v69TYHx1FO8bffHn/JjHtE3TTQZmHjwvnJe4xxzuecetDVBrQR1Zg==", + "dev": true, + "dependencies": { + "html-minifier-terser": "^7.0.0", + "parse5": "^7.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "dev": true, + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", + "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jdenticon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.2.0.tgz", + "integrity": "sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==", + "dependencies": { + "canvas-renderer": "~2.2.0" + }, + "bin": { + "jdenticon": "bin/jdenticon.js" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/magicast": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/merkle-lib": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz", + "integrity": "sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "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": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-polyfill-webpack-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-2.0.1.tgz", + "integrity": "sha512-ZUMiCnZkP1LF0Th2caY6J/eKKoA0TefpoVa68m/LQU1I/mE8rGt4fNYGgNuCcK+aG8P8P43nbeJ2RqJMOL/Y1A==", + "dev": true, + "dependencies": { + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "console-browserify": "^1.2.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.12.0", + "domain-browser": "^4.22.0", + "events": "^3.3.0", + "filter-obj": "^2.0.2", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "punycode": "^2.1.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^4.0.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.3.0", + "timers-browserify": "^2.0.12", + "tty-browserify": "^0.0.1", + "type-fest": "^2.14.0", + "url": "^0.11.0", + "util": "^0.12.4", + "vm-browserify": "^1.1.2" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "webpack": ">=5" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nodemon": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, + "node_modules/ow": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.8.0.tgz", + "integrity": "sha512-hYgYZNcRfIZ2JppSTqh6mxdU1zkUXsGlwy4eBsRG91R6CiZk7cB+AfHl+SVKBdynQvAnNHNfu0ZrtJN1jj7Mow==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dev": true, + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-asn1/node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-uri": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/parse-uri/-/parse-uri-1.0.9.tgz", + "integrity": "sha512-YZfRHHkEZa6qTfPF/xgZ1ErQYCABfud/Vcqp1Q1GNa7RKwv6Oe0YaxXfQQMnQsGdNTo3fwaT0GbVEX7dMAr7tw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pivx-promos": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/pivx-promos/-/pivx-promos-0.2.0.tgz", + "integrity": "sha512-1s/omzaJ5jqJim+bK6AKg0bb1uerraUTJt59KoDckzWXgExOfdh8sZgAy4GTyowoR+vJz0u8j9r5GwmFqz8IwA==" + }, + "node_modules/pivx-shield": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pivx-shield/-/pivx-shield-1.1.4.tgz", + "integrity": "sha512-aCSQG+4A6O3pDTnSpq2bPuR9EJOfbT6W+LLdaKuIO8e6OWLeXPhfiscGrQxystppev/9tn6Qoy7de2eJ9AWX2Q==", + "dependencies": { + "bs58": "^5.0.0", + "comlink": "^4.4.1", + "pivx-shield-rust": "^1.1.0", + "pivx-shield-rust-multicore": "^1.1.0", + "uuid": "^9.0.0", + "wasm-feature-detect": "^1.5.1" + } + }, + "node_modules/pivx-shield-rust": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pivx-shield-rust/-/pivx-shield-rust-1.1.3.tgz", + "integrity": "sha512-M/kM7M/H5rbgcEYf8DgV2cbvvYrmxLYzqTG026GQPifUOkR3c98S1L9O2jJN4unYpD5QYak6P2Bbc7AoxWfcHQ==" + }, + "node_modules/pivx-shield-rust-multicore": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pivx-shield-rust-multicore/-/pivx-shield-rust-multicore-1.1.3.tgz", + "integrity": "sha512-ijcr6xp6Bas0TMMTfSrCH05KkHU3t0jWlQpdWC2gtqGdPiu3/21xN/tPijnJ/fBvWPN9vWo2wrK8PwsYo8YoTQ==" + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pushdata-bitcoin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", + "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", + "dependencies": { + "bitcoin-ops": "^1.3.0" + } + }, + "node_modules/qr-scanner": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/qr-scanner/-/qr-scanner-1.4.2.tgz", + "integrity": "sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==", + "dependencies": { + "@types/offscreencanvas": "^2019.6.4" + } + }, + "node_modules/qrcode-generator": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", + "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==" + }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "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", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/raw-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resource-loader": { + "version": "4.0.0-rc4", + "resolved": "https://registry.npmjs.org/resource-loader/-/resource-loader-4.0.0-rc4.tgz", + "integrity": "sha512-DuGS7xVDnL6rlDj0gU103N8CGGbEPkwqrIoZlL22/rGb97azFHgeb3DG53INW1MZCiAv9MUZfuk9ZxCIMBCM6g==", + "dev": true, + "dependencies": { + "parse-uri": "^1.0.0", + "type-signals": "^1.0.3" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rollup": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.fromcodepoint": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz", + "integrity": "sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==" + }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", + "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "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/tiny-secp256k1/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/tinybench": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz", + "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "dev": true + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/touch/node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/touch/node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-signals": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/type-signals/-/type-signals-1.1.0.tgz", + "integrity": "sha512-JzmQSmRNTIcQofZdYW9RHxAP8rrcq/N5I2fBy+V43oOqikOvsCTYlOIIX40Qpmr5GiRmYrptghIog84E1yz1Zg==", + "dev": true + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "devOptional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", + "integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/vue": { + "version": "3.4.23", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.23.tgz", + "integrity": "sha512-X1y6yyGJ28LMUBJ0k/qIeKHstGd+BlWQEOT40x3auJFTmpIhpbKLgN7EFsqalnJXq1Km5ybDEsp6BhuWKciUDg==", + "dependencies": { + "@vue/compiler-dom": "3.4.23", + "@vue/compiler-sfc": "3.4.23", + "@vue/runtime-dom": "3.4.23", + "@vue/server-renderer": "3.4.23", + "@vue/shared": "3.4.23" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.13.tgz", + "integrity": "sha512-xNO5B7DstNWETnoYflLkVgh8dK8h2ZDgxY1M2O0zrqGeBNq5yAZ8a10yCS9+HnixouNGYNX+ggU9MQQq86HTpg==", + "dev": true + }, + "node_modules/vue-loader": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", + "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz", + "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.6.1.tgz", + "integrity": "sha512-R1i9ED8UlLu/foILNB1ck9XS63vdtqU/tP1MCugVekETp/ySCrBZRk5I/zI67cI1wlQYeSonNm1PLjDHZDNg6g==" + }, + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.16.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..9281e702f --- /dev/null +++ b/package.json @@ -0,0 +1,103 @@ +{ + "name": "mypivxwallet", + "version": "1.5.1", + "description": "Wallet for PIVX", + "main": "scripts/index.js", + "type": "module", + "scripts": { + "postinstall": "node postinstall.cjs", + "build": "webpack --config webpack.prod.js", + "buildDev": "webpack --config webpack.dev.js", + "watch": "webpack --watch", + "dev": "webpack serve --config webpack.dev.js", + "predeploy": "npm run build", + "deploy": "gh-pages -d dist", + "prettier": "prettier --write --cache .", + "lint": "eslint .", + "test": "vitest", + "coverage": "vitest --coverage" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/PIVX-Labs/MyPIVXWallet.git" + }, + "keywords": [ + "crypto", + "PIVX", + "wallet" + ], + "author": "PIVX Labs", + "license": "MIT", + "bugs": { + "url": "https://github.com/PIVX-Labs/MyPIVXWallet/issues" + }, + "homepage": "https://github.com/PIVX-Labs/MyPIVXWallet#readme", + "devDependencies": { + "@fluent/langneg": "^0.7.0", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "@typescript-eslint/parser": "^5.48.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitest/coverage-istanbul": "^1.0.2", + "@vue/preload-webpack-plugin": "^2.0.0", + "@vue/test-utils": "^2.4.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.3", + "css-minimizer-webpack-plugin": "^4.2.2", + "eslint": "^8.31.0", + "fake-indexeddb": "^5.0.1", + "file-loader": "^6.2.0", + "gh-pages": "^5.0.0", + "happy-dom": "^12.10.3", + "html-loader": "^4.2.0", + "html-webpack-plugin": "^5.5.0", + "mini-css-extract-plugin": "^2.7.2", + "node-polyfill-webpack-plugin": "^2.0.1", + "prettier": "^2.8.1", + "raw-loader": "^4.0.2", + "resource-loader": "^4.0.0-rc4", + "toml": "^3.0.0", + "vitest": "^1.0.2", + "vue-loader": "^17.2.2", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1", + "webpack-dev-server": "^4.11.1", + "webpack-merge": "^5.8.0" + }, + "dependencies": { + "@fontsource/chivo": "^4.5.11", + "@fontsource/montserrat": "^4.5.14", + "@fortawesome/fontawesome-free": "^6.2.1", + "@ledgerhq/hw-app-btc": "^9.1.1", + "@ledgerhq/hw-transport-webusb": "^6.27.9", + "@noble/hashes": "^1.1.5", + "@noble/secp256k1": "^1.7.0", + "@popperjs/core": "^2.11.6", + "base32": "^0.0.7", + "bech32": "^2.0.0", + "biginteger": "^1.0.3", + "bip39": "^3.0.4", + "bootstrap-4": "^4.0.0", + "bs58": "^5.0.0", + "buffer": "^6.0.3", + "chart.js": "^4.2.1", + "country-locale-map": "^1.8.15", + "create-xpub": "^2.1.0", + "hdkey": "^2.0.1", + "idb": "^7.1.1", + "ip-address": "^8.1.0", + "jdenticon": "^3.2.0", + "jquery": "^3.6.3", + "pinia": "^2.1.7", + "pivx-promos": "^0.2.0", + "pivx-shield": "^1.1.3", + "pivx-shield-rust": "^1.1.3", + "pivx-shield-rust-multicore": "^1.1.3", + "qr-scanner": "^1.4.2", + "qrcode-generator": "^1.4.4", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "alias": { + "@ledgerhq/devices": "@ledgerhq/devices/lib-es" + } +} diff --git a/postinstall.cjs b/postinstall.cjs new file mode 100644 index 000000000..2b99ca7d1 --- /dev/null +++ b/postinstall.cjs @@ -0,0 +1,27 @@ +const { execSync } = require('child_process'); + +// Determine the operating system type +const os = require('os'); +const osType = os.platform(); + +// Define the command to execute based on the OS +let command; +if (osType.match(/aix|darwin|freebsd|linux|openbsd|sunos|android/)) { + command = + 'test -f chain_params.json || cp chain_params.prod.json chain_params.json'; +} else if (osType.match(/win32/)) { + command = + 'if not exist chain_params.json copy chain_params.prod.json chain_params.json'; +} else { + console.error('Unsupported operating system'); + process.exit(1); +} + +// Execute the command +try { + execSync(command, { stdio: 'inherit' }); + console.log('Postinstall script executed successfully'); +} catch (error) { + console.error('Error executing postinstall script:', error); + process.exit(1); +} diff --git a/scripts/BottomPopup.vue b/scripts/BottomPopup.vue new file mode 100644 index 000000000..7726e7e1a --- /dev/null +++ b/scripts/BottomPopup.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/scripts/Loadingbar.vue b/scripts/Loadingbar.vue new file mode 100644 index 000000000..bb69a8917 --- /dev/null +++ b/scripts/Loadingbar.vue @@ -0,0 +1,19 @@ + + + diff --git a/scripts/Modal.vue b/scripts/Modal.vue new file mode 100644 index 000000000..d59c10402 --- /dev/null +++ b/scripts/Modal.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/scripts/__mocks__/global.js b/scripts/__mocks__/global.js new file mode 100644 index 000000000..67b053520 --- /dev/null +++ b/scripts/__mocks__/global.js @@ -0,0 +1 @@ +export const blockCount = 1504903; diff --git a/scripts/__mocks__/network.js b/scripts/__mocks__/network.js new file mode 100644 index 000000000..40e04d755 --- /dev/null +++ b/scripts/__mocks__/network.js @@ -0,0 +1,162 @@ +import { vi } from 'vitest'; +import { Transaction } from '../transaction.js'; + +export const getNetwork = vi.fn(() => { + return globalNetwork; +}); + +class TestNetwork { + // initial blockHeight + #blockHeight = 1504903; + getBlockCount = vi.fn(() => { + return this.#blockHeight; + }); + /** + * map blockHeight -> block + * @type {Map} + */ + #mapBlocks = new Map(); + /** + * The next block that will be added to the blockchain + * @type{TestBlock} + */ + #nextBlock; + + constructor() { + // Mint 5 empty blocks in the past because wallet will ask for them when calling getLatestBlocks + this.#blockHeight -= 5; + this.#nextBlock = new TestBlock(this.#blockHeight + 1); + for (let i = 0; i < 5; i++) { + this.mintBlock(); + } + } + + get enabled() { + return true; + } + + getLatestTxs = vi.fn( + /** + * @param {import('../wallet.js').Wallet}wallet + * @returns {Promise} + */ + async (wallet) => { + if (wallet.isSynced) { + throw new Error('Wallet already synced!'); + } + if ( + wallet.getKeyToExport() === 'DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC' + ) { + // 1) Legacy mainnet wallet + // tx_1 provides a spendable balance of 0.1 * 10^8 satoshi + const tx_1 = + '010000000138f9c8ac1b4c6ad54e487206daad1fd12bae510dd70fbd2dc928f617ef6b4a47010000006a47304402200d01891ac5dc6d25452cadbe7ba7edd98143631cc2922da45dde94919593b222022066ef14c01c165a1530e2acf74bcd8648d7151b555fa0bfd0222c33e983091678012102033a004cb71693e809c45e1e491bc797654fa0c012be9dd46401ce8368beb705ffffffff02d02c5d05000000001976a91463fa54dad2e215ec21c54ff45a195d49b570b97988ac80969800000000001976a914f49b25384b79685227be5418f779b98a6be4c73888ac00000000'; + await wallet.addTransaction(Transaction.fromHex(tx_1)); + } else if ( + wallet.getKeyToExport() === + 'xpub6DVaPT3irDwUth7Y6Ff137FgA9jjvsmA2CHD7g2vjvvuMiNSUJRs9F8jSoPpXpc1s7ohR93dNAuzR5T2oPZFDTn7G2mHKYtpwtk7krNZmnV' + ) { + // 2) HD mainnet wallet + // tx_1 provides a spendable balance of 1 * 10^8 satoshi to the address with path m/44'/119'/0'/0/0 + const tx_1 = + '0100000001458de14f4f4fecfdeebfef09fb16e761bbd15029f37bec0a63b86808cbb8a512010000006b483045022100ef4f4364aea7604d749aaff7a2609e3a51a12f49500b7910b34ced0d0837e1db022012d153d96ebcb94e9b905a609c0ea97cdc99ae961be2848e0e8f2f695379c21201210371eca6799221b82cbba9e880a8a5a0f47d811f3ff5cad346931406ab0a0469eeffffffff0200e1f505000000001976a9148952bf31104625a7b3e6fcf4c79b35c6849ef74d88ac905cfb2f010000001976a9144e8d2fcf6d909c62597e4defd1c26d50842d73df88ac00000000'; + await wallet.addTransaction(Transaction.fromHex(tx_1)); + } else { + console.warn( + 'getLatestTxs did not find any txs this wallet! ' + + wallet.getKeyToExport() + ); + } + } + ); + + sendTransaction = vi.fn( + /** + * @param {string}txHex - transaction hex + */ + (txHex) => { + this.#nextBlock.addTransaction(txHex, this.#blockHeight + 1); + return true; + } + ); + getBlock = vi.fn( + /** + * + * @param{number} blockHeight + * @param{boolean} skipCoinstake + */ + (blockHeight, skipCoinstake = false) => { + if (!this.#mapBlocks.has(blockHeight)) { + throw new Error('Requested block does not exist!'); + } + return this.#mapBlocks.get(blockHeight); + } + ); + + mintBlock() { + const nextBlockHeight = this.#blockHeight + 1; + if (this.#nextBlock.blockHeight !== nextBlockHeight) { + throw new Error('Block heights do not match!'); + } + if (this.#mapBlocks.has(nextBlockHeight)) { + throw new Error('Block already minted!'); + } + this.#mapBlocks.set(nextBlockHeight, structuredClone(this.#nextBlock)); + this.#blockHeight = nextBlockHeight; + this.#nextBlock.reset(); + this.#nextBlock.blockHeight = this.#blockHeight + 1; + } +} + +/** + * Dummy implementation of a blockchain bock + */ +class TestBlock { + /** + * list of transactions in the block + * @type {TestTransaction[]} + */ + txs = []; + blockHeight = -1; + constructor(blockHeight) { + this.blockHeight = blockHeight; + } + /** + * list of transactions in the block + * @param {string} txHex - transaction hex + * @param {number} blockHeight - blockHeight of the transaction + */ + addTransaction(txHex, blockHeight) { + // Sanity check + if (blockHeight != this.blockHeight) { + throw new Error('Transaction and block have a different height!'); + } + this.txs.push(new TestTransaction(txHex, blockHeight)); + } + reset() { + this.blockHeight = -1; + this.txs = []; + } +} + +/** + * Dummy implementation of a blockchain transaction + */ +class TestTransaction { + /** + * @type {string} - hex of the transaction + */ + hex = ''; + blockTime = -1; + blockHeight = -1; + constructor(hex, blockHeight) { + this.hex = hex; + this.blockHeight = blockHeight; + } +} + +let globalNetwork = new TestNetwork(); + +export function resetNetwork() { + globalNetwork = new TestNetwork(); +} diff --git a/scripts/accounts.js b/scripts/accounts.js new file mode 100644 index 000000000..09c9bcd38 --- /dev/null +++ b/scripts/accounts.js @@ -0,0 +1,69 @@ +/** + * A local Account, containing sensitive user-data + */ +export class Account { + /** + * Create an Account. + * @param {Object} accountData - The account data. + * @param {String} accountData.publicKey - The public key. + * @param {String} [accountData.encWif] - The encrypted WIF. + * @param {Array} [accountData.localProposals] - The local proposals. + * @param {Array} [accountData.contacts] - The Contacts saved in this account. + * @param {String} [accountData.name] - The Contact Name of the account. + * @param {String} [accountData.coldAddress] - The Cold Address that this account delegates to. + * @param {String} [accountData.shieldData] - Shield data necessary to load shielding + * @param {String} [accountData.encExtsk] - Encrypted extended spending key + */ + constructor(accountData) { + // Keys take the Constructor as priority, but if missing, default to their "Type" in empty form for type-safety + this.publicKey = accountData?.publicKey || ''; + this.encWif = accountData?.encWif || ''; + this.localProposals = accountData?.localProposals || []; + this.contacts = accountData?.contacts || []; + this.name = accountData?.name || ''; + this.coldAddress = accountData?.coldAddress || ''; + this.shieldData = accountData?.shieldData || ''; + this.encExtsk = accountData?.encExtsk || ''; + } + + /** @type {String} The public key. */ + publicKey = ''; + + /** @type {String} The encrypted WIF. */ + encWif = ''; + + /** @type {Array} The local proposals. */ + localProposals = []; + + /** @type {Array} The Contacts saved in this account. */ + contacts = []; + + /** @type {String} The Contact Name of the account. */ + name = ''; + + /** @type {String} The Cold Address that this account delegates to. */ + coldAddress = ''; + + /** @type {String} Shield data necessary to load shielding */ + shieldData = ''; + + /** @type {String} Encrypted extended spending key*/ + encExtsk = ''; + + /** + * Search for a Contact in this account, by specific properties + * @param {Object} settings + * @param {string?} settings.name - The Name of the contact to search for + * @param {string?} settings.pubkey - The Pubkey of the contact to search for + * @returns {import('./contact-book.js').Contact?} - A Contact, if found + */ + getContactBy({ name, pubkey }) { + // Get by Name + if (name) return this.contacts.find((a) => a.label === name); + // Get by Pubkey + if (pubkey) return this.contacts.find((a) => a.pubkey === pubkey); + + // Nothing found + return null; + } +} diff --git a/scripts/aes-gcm.js b/scripts/aes-gcm.js new file mode 100644 index 000000000..830c15be0 --- /dev/null +++ b/scripts/aes-gcm.js @@ -0,0 +1,102 @@ +const buff_to_base64 = (buff) => btoa(String.fromCharCode.apply(null, buff)); + +const base64_to_buf = (b64) => + Uint8Array.from(atob(b64), (c) => c.charCodeAt(null)); + +const enc = new TextEncoder(); +const dec = new TextDecoder(); + +/** + * @param {String} data - The data you want to encrypt + * @param {String} strPassword - The password used to encrypt + * @returns {Promise} Encrypt data or false if the process failed + */ +export async function encrypt(data, strPassword) { + if (!strPassword) return false; + return await encryptData(data, strPassword); +} + +/** + * @param {String} data - The data you want to decrypt + * @param {String} strPassword - The password used to decrypt + * @returns {Promise} Decrypted data or false if the process failed + */ +export async function decrypt(data, strPassword) { + if (!strPassword) return false; + return (await decryptData(data, strPassword)) || false; +} + +const getPasswordKey = (password) => + window.crypto.subtle.importKey( + 'raw', + enc.encode(password), + 'PBKDF2', + false, + ['deriveKey'] + ); + +const deriveKey = (passwordKey, salt, keyUsage) => + window.crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt, + iterations: 250000, + hash: 'SHA-256', + }, + passwordKey, + { name: 'AES-GCM', length: 256 }, + false, + keyUsage + ); + +async function encryptData(secretData, password) { + try { + const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const iv = window.crypto.getRandomValues(new Uint8Array(12)); + const passwordKey = await getPasswordKey(password); + const aesKey = await deriveKey(passwordKey, salt, ['encrypt']); + const encryptedContent = await window.crypto.subtle.encrypt( + { + name: 'AES-GCM', + iv: iv, + }, + aesKey, + enc.encode(secretData) + ); + + const encryptedContentArr = new Uint8Array(encryptedContent); + let buff = new Uint8Array( + salt.byteLength + iv.byteLength + encryptedContentArr.byteLength + ); + buff.set(salt, 0); + buff.set(iv, salt.byteLength); + buff.set(encryptedContentArr, salt.byteLength + iv.byteLength); + return buff_to_base64(buff); + } catch (e) { + console.log(`Error - ${e}`); + return ''; + } +} + +async function decryptData(encryptedData, password) { + try { + const encryptedDataBuff = base64_to_buf(encryptedData); + const salt = encryptedDataBuff.slice(0, 16); + const iv = encryptedDataBuff.slice(16, 16 + 12); + const data = encryptedDataBuff.slice(16 + 12); + const passwordKey = await getPasswordKey(password); + const aesKey = await deriveKey(passwordKey, salt, ['decrypt']); + const decryptedContent = await window.crypto.subtle.decrypt( + { + name: 'AES-GCM', + iv: iv, + }, + aesKey, + data + ); + return dec.decode(decryptedContent); + } catch (e) { + console.log(`Error - ${e}`); + return ''; + } +} diff --git a/scripts/bitTrx.js b/scripts/bitTrx.js deleted file mode 100644 index 76b733588..000000000 --- a/scripts/bitTrx.js +++ /dev/null @@ -1,503 +0,0 @@ -(function () { - - var bitjs = window.bitjs = function () { }; - - /* public vars */ - bitjs.pub = 0x7D; - bitjs.priv = 0xFD; - bitjs.compressed = true; - - /* provide a privkey and return an WIF */ - bitjs.privkey2wif = function(h) { - var r = Crypto.util.hexToBytes(h); - - if (bitjs.compressed==true) { - r.push(0x01); - } - - r.unshift(bitjs.priv); - var hash = Crypto.SHA256(Crypto.SHA256(r, {asBytes: true}), {asBytes: true}); - var checksum = hash.slice(0, 4); - - return B58.encode(r.concat(checksum)); - } - - /* convert a wif key back to a private key */ - bitjs.wif2privkey = function(wif) { - var compressed = false; - var decode = B58.decode(wif); - var key = decode.slice(0, decode.length-4); - key = key.slice(1, key.length); - if (key.length >=33 && key[key.length-1] == 0x01) { - key = key.slice(0, key.length-1); - compressed = true; - } - return {'privkey': Crypto.util.bytesToHex(key), 'compressed':compressed}; - } - - /* convert a wif to a pubkey */ - bitjs.wif2pubkey = function(wif) { - var compressed = bitjs.compressed; - var r = bitjs.wif2privkey(wif); - bitjs.compressed = r['compressed']; - var pubkey = bitjs.newPubkey(r['privkey']); - bitjs.compressed = compressed; - return {'pubkey':pubkey,'compressed':r['compressed']}; - } - - /* convert a wif to a address */ - bitjs.wif2address = function(wif) { - var r = bitjs.wif2pubkey(wif); - return {'address':bitjs.pubkey2address(r['pubkey']), 'compressed':r['compressed']}; - } - - /* generate a public key from a private key */ - bitjs.newPubkey = function(hash) { - var privateKeyBigInt = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(hash)); - var curve = EllipticCurve.getSECCurveByName("secp256k1"); - - var curvePt = curve.getG().multiply(privateKeyBigInt); - var x = curvePt.getX().toBigInteger(); - var y = curvePt.getY().toBigInteger(); - - var publicKeyBytes = EllipticCurve.integerToBytes(x, 32); - publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y,32)); - publicKeyBytes.unshift(0x04); - - if (bitjs.compressed==true) { - var publicKeyBytesCompressed = EllipticCurve.integerToBytes(x,32) - if (y.isEven()) { - publicKeyBytesCompressed.unshift(0x02) - } else { - publicKeyBytesCompressed.unshift(0x03) - } - return Crypto.util.bytesToHex(publicKeyBytesCompressed); - } else { - return Crypto.util.bytesToHex(publicKeyBytes); - } - } - - /* provide a public key and return address */ - bitjs.pubkey2address = function(h, byte) { - var r = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(h), {asBytes: true})); - r.unshift(byte || bitjs.pub); - var hash = Crypto.SHA256(Crypto.SHA256(r, {asBytes: true}), {asBytes: true}); - var checksum = hash.slice(0, 4); - return B58.encode(r.concat(checksum)); - } - - bitjs.transaction = function() { - var btrx = {}; - btrx.version = 2; - btrx.inputs = []; - btrx.outputs = []; - btrx.locktime = 0; - - btrx.addinput = function(txid, index, script, sequence) { - var o = {}; - o.outpoint = {'hash': txid, 'index': index}; - //o.script = []; Signature and Public Key should be added after singning - o.script = Crypto.util.hexToBytes(script); //push previous output pubkey script - o.sequence = sequence || ((btrx.locktime==0) ? 4294967295 : 0); - return this.inputs.push(o); - } - - btrx.addoutput = function(address, value) { - var o = {}; - var buf = []; - var addrDecoded = btrx.addressDecode(address); - o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10); - buf.push(118); // OP_DUP - buf.push(169); // OP_HASH160 - buf.push(addrDecoded.length); - buf = buf.concat(addrDecoded); // address in bytes - buf.push(136); // OP_EQUALVERIFY - buf.push(172); // OP_CHECKSIG - o.script = buf; - return this.outputs.push(o); - } - - // Only standard addresses - btrx.addressDecode = function(address) { - var bytes = B58.decode(address); - var front = bytes.slice(0, bytes.length-4); - var back = bytes.slice(bytes.length-4); - var checksum = Crypto.SHA256(Crypto.SHA256(front, {asBytes: true}), {asBytes: true}).slice(0, 4); - if (checksum + "" == back + "") { - return front.slice(1); - } - } - - /* generate the transaction hash to sign from a transaction input */ - btrx.transactionHash = function(index, sigHashType) { - - var clone = bitjs.clone(this); - var shType = sigHashType || 1; - - /* black out all other ins, except this one */ - for (var i = 0; i < clone.inputs.length; i++) { - if (index!=i) { - clone.inputs[i].script = []; - } - } - - - if ((clone.inputs) && clone.inputs[index]) { - - /* SIGHASH : For more info on sig hashs see https://en.bitcoin.it/wiki/OP_CHECKSIG - and https://bitcoin.org/en/developer-guide#signature-hash-type */ - - if (shType == 1) { - //SIGHASH_ALL 0x01 - - } else if (shType == 2) { - //SIGHASH_NONE 0x02 - clone.outputs = []; - for (var i = 0; i < clone.inputs.length; i++) { - if (index!=i) { - clone.inputs[i].sequence = 0; - } - } - - } else if (shType == 3) { - - //SIGHASH_SINGLE 0x03 - clone.outputs.length = index + 1; - - for(var i = 0; i < index; i++) { - clone.outputs[i].value = -1; - clone.outputs[i].script = []; - } - - for (var i = 0; i < clone.inputs.length; i++) { - if (index!=i) { - clone.inputs[i].sequence = 0; - } - } - - } else if (shType >= 128) { - //SIGHASH_ANYONECANPAY 0x80 - clone.inputs = [clone.inputs[index]]; - - if (shType==129) { - // SIGHASH_ALL + SIGHASH_ANYONECANPAY - - } else if (shType==130) { - // SIGHASH_NONE + SIGHASH_ANYONECANPAY - clone.outputs = []; - - } else if (shType==131) { - // SIGHASH_SINGLE + SIGHASH_ANYONECANPAY - clone.outputs.length = index + 1; - for(var i = 0; i < index; i++) { - clone.outputs[i].value = -1; - clone.outputs[i].script = []; - } - } - } - - var buffer = Crypto.util.hexToBytes(clone.serialize()); - buffer = buffer.concat(bitjs.numToBytes(parseInt(shType), 4)); - var hash = Crypto.SHA256(buffer, {asBytes: true}); - var r = Crypto.util.bytesToHex(Crypto.SHA256(hash, {asBytes: true})); - return r; - } else { - return false; - } - } - - /* generate a signature from a transaction hash */ - btrx.transactionSig = function(index, wif, sigHashType, txhash) { - - function serializeSig(r, s) { - var rBa = r.toByteArraySigned(); - var sBa = s.toByteArraySigned(); - - var sequence = []; - sequence.push(0x02); // INTEGER - sequence.push(rBa.length); - sequence = sequence.concat(rBa); - - sequence.push(0x02); // INTEGER - sequence.push(sBa.length); - sequence = sequence.concat(sBa); - - sequence.unshift(sequence.length); - sequence.unshift(0x30); // SEQUENCE - - return sequence; - } - - var shType = sigHashType || 1; - var hash = txhash || Crypto.util.hexToBytes(this.transactionHash(index, shType)); - - if (hash) { - var curve = EllipticCurve.getSECCurveByName("secp256k1"); - var key = bitjs.wif2privkey(wif); - var priv = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(key['privkey'])); - var n = curve.getN(); - var e = BigInteger.fromByteArrayUnsigned(hash); - var badrs = 0 - do { - var k = this.deterministicK(wif, hash, badrs); - var G = curve.getG(); - var Q = G.multiply(k); - var r = Q.getX().toBigInteger().mod(n); - var s = k.modInverse(n).multiply(e.add(priv.multiply(r))).mod(n); - badrs++ - } while (r.compareTo(BigInteger.ZERO) <= 0 || s.compareTo(BigInteger.ZERO) <= 0); - - // Force lower s values per BIP62 - var halfn = n.shiftRight(1); - if (s.compareTo(halfn) > 0) { - s = n.subtract(s); - }; - - var sig = serializeSig(r, s); - sig.push(parseInt(shType, 10)); - - return Crypto.util.bytesToHex(sig); - } else { - return false; - } - } - - // https://tools.ietf.org/html/rfc6979#section-3.2 - btrx.deterministicK = function(wif, hash, badrs) { - // if r or s were invalid when this function was used in signing, - // we do not want to actually compute r, s here for efficiency, so, - // we can increment badrs. explained at end of RFC 6979 section 3.2 - - // wif is b58check encoded wif privkey. - // hash is byte array of transaction digest. - // badrs is used only if the k resulted in bad r or s. - - // some necessary things out of the way for clarity. - badrs = badrs || 0; - var key = bitjs.wif2privkey(wif); - var x = Crypto.util.hexToBytes(key['privkey']) - var curve = EllipticCurve.getSECCurveByName("secp256k1"); - var N = curve.getN(); - - // Step: a - // hash is a byteArray of the message digest. so h1 == hash in our case - - // Step: b - var v = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; - - // Step: c - var k = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // Step: d - k = Crypto.HMAC(Crypto.SHA256, v.concat([0]).concat(x).concat(hash), k, { asBytes: true }); - - // Step: e - v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true }); - - // Step: f - k = Crypto.HMAC(Crypto.SHA256, v.concat([1]).concat(x).concat(hash), k, { asBytes: true }); - - // Step: g - v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true }); - - // Step: h1 - var T = []; - - // Step: h2 (since we know tlen = qlen, just copy v to T.) - v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true }); - T = v; - - // Step: h3 - var KBigInt = BigInteger.fromByteArrayUnsigned(T); - - // loop if KBigInt is not in the range of [1, N-1] or if badrs needs incrementing. - var i = 0 - while (KBigInt.compareTo(N) >= 0 || KBigInt.compareTo(BigInteger.ZERO) <= 0 || i < badrs) { - k = Crypto.HMAC(Crypto.SHA256, v.concat([0]), k, { asBytes: true }); - v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true }); - v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true }); - T = v; - KBigInt = BigInteger.fromByteArrayUnsigned(T); - i++ - }; - - return KBigInt; - }; - - /* sign a "standard" input */ - btrx.signinput = function(index, wif, sigHashType) { - var key = bitjs.wif2pubkey(wif); - var shType = sigHashType || 1; - var signature = this.transactionSig(index, wif, shType); - var buf = []; - var sigBytes = Crypto.util.hexToBytes(signature); - buf.push(sigBytes.length); - buf = buf.concat(sigBytes); - var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']); - buf.push(pubKeyBytes.length); - buf = buf.concat(pubKeyBytes); - this.inputs[index].script = buf; - return true; - } - - /* sign inputs */ - btrx.sign = function(wif, sigHashType) { - var shType = sigHashType || 1; - for (var i = 0; i < this.inputs.length; i++) { - this.signinput(i, wif, shType); - } - return this.serialize(); - } - - - /* serialize a transaction */ - btrx.serialize = function() { - var buffer = []; - buffer = buffer.concat(bitjs.numToBytes(parseInt(this.version),4)); - - buffer = buffer.concat(bitjs.numToVarInt(this.inputs.length)); - for (var i = 0; i < this.inputs.length; i++) { - var txin = this.inputs[i]; - buffer = buffer.concat(Crypto.util.hexToBytes(txin.outpoint.hash).reverse()); - buffer = buffer.concat(bitjs.numToBytes(parseInt(txin.outpoint.index),4)); - var scriptBytes = txin.script; - buffer = buffer.concat(bitjs.numToVarInt(scriptBytes.length)); - buffer = buffer.concat(scriptBytes); - buffer = buffer.concat(bitjs.numToBytes(parseInt(txin.sequence),4)); - } - buffer = buffer.concat(bitjs.numToVarInt(this.outputs.length)); - - for (var i = 0; i < this.outputs.length; i++) { - var txout = this.outputs[i]; - buffer = buffer.concat(bitjs.numToBytes(txout.value,8)); - var scriptBytes = txout.script; - buffer = buffer.concat(bitjs.numToVarInt(scriptBytes.length)); - buffer = buffer.concat(scriptBytes); - } - - buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime),4)); - return Crypto.util.bytesToHex(buffer); - } - - - return btrx; - - } - - bitjs.numToBytes = function(num,bytes) { - if (typeof bytes === "undefined") bytes = 8; - if (bytes == 0) { - return []; - } else if (num == -1) { - return Crypto.util.hexToBytes("ffffffffffffffff"); - } else { - return [num % 256].concat(bitjs.numToBytes(Math.floor(num / 256),bytes-1)); - } - } - - bitjs.numToByteArray = function(num) { - if (num <= 256) { - return [num]; - } else { - return [num % 256].concat(bitjs.numToByteArray(Math.floor(num / 256))); - } - } - - bitjs.numToVarInt = function(num) { - if (num < 253) { - return [num]; - } else if (num < 65536) { - return [253].concat(bitjs.numToBytes(num,2)); - } else if (num < 4294967296) { - return [254].concat(bitjs.numToBytes(num,4)); - } else { - return [255].concat(bitjs.numToBytes(num,8)); - } - } - - bitjs.bytesToNum = function(bytes) { - if (bytes.length == 0) return 0; - else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1)); - } - - /* clone an object */ - bitjs.clone = function(obj) { - if (obj == null || typeof(obj) != 'object') return obj; - var temp = new obj.constructor(); - - for(var key in obj) { - if (obj.hasOwnProperty(key)) { - temp[key] = bitjs.clone(obj[key]); - } - } - return temp; - } - - var B58 = bitjs.Base58 = { - alphabet: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", - validRegex: /^[1-9A-HJ-NP-Za-km-z]+$/, - base: BigInteger.valueOf(58), - - /** - * Convert a byte array to a base58-encoded string. - * - * Written by Mike Hearn for BitcoinJ. - * Copyright (c) 2011 Google Inc. - * - * Ported to JavaScript by Stefan Thomas. - */ - encode: function (input) { - var bi = BigInteger.fromByteArrayUnsigned(input); - var chars = []; - - while (bi.compareTo(B58.base) >= 0) { - var mod = bi.mod(B58.base); - chars.unshift(B58.alphabet[mod.intValue()]); - bi = bi.subtract(mod).divide(B58.base); - } - chars.unshift(B58.alphabet[bi.intValue()]); - - // Convert leading zeros too. - for (var i = 0; i < input.length; i++) { - if (input[i] == 0x00) { - chars.unshift(B58.alphabet[0]); - } else break; - } - - return chars.join(''); - }, - - /** - * Convert a base58-encoded string to a byte array. - * - * Written by Mike Hearn for BitcoinJ. - * Copyright (c) 2011 Google Inc. - * - * Ported to JavaScript by Stefan Thomas. - */ - decode: function (input) { - var bi = BigInteger.valueOf(0); - var leadingZerosNum = 0; - for (var i = input.length - 1; i >= 0; i--) { - var alphaIndex = B58.alphabet.indexOf(input[i]); - if (alphaIndex < 0) { - throw "Invalid character"; - } - bi = bi.add(BigInteger.valueOf(alphaIndex) - .multiply(B58.base.pow(input.length - 1 - i))); - - // This counts leading zero bytes - if (input[i] == "1") leadingZerosNum++; - else leadingZerosNum = 0; - } - var bytes = bi.toByteArrayUnsigned(); - - // Add leading zeros - while (leadingZerosNum-- > 0) bytes.unshift(0); - - return bytes; - } - } - return bitjs; - -})(); diff --git a/scripts/chain_params.js b/scripts/chain_params.js new file mode 100644 index 000000000..329c4c076 --- /dev/null +++ b/scripts/chain_params.js @@ -0,0 +1,32 @@ +import { reactive } from 'vue'; +import chainParams from '../chain_params.json'; + +// In most BTC-derived coins, the below parameters can be found in the 'src/chainparams.cpp' Mainnet configuration. +// These below params share the same names as the CPP params, so finding and editing these is easy-peasy! +// <[network_byte] [32_byte_payload] [0x01] [4_byte_checksum]> +export const PRIVKEY_BYTE_LENGTH = 38; + +export const COIN_DECIMALS = 8; +export const COIN = 10 ** 8; + +/** The maximum gap (absence of transactions within a range of derived addresses) before an account search ends */ +export const MAX_ACCOUNT_GAP = 20; + +/** The batch size of Shield block synchronisation */ +export const SHIELD_BATCH_SYNC_SIZE = 32; + +/** Transaction Sapling Version */ +export const SAPLING_TX_VERSION = 3; + +/* Internal tweaking parameters */ +// A new encryption password must be 'at least' this long. +export const MIN_PASS_LENGTH = 6; + +/** BIP21 coin prefix */ +export const BIP21_PREFIX = 'pivx'; + +/* chainparams */ +export const cChainParams = reactive({ + current: chainParams.main, + ...chainParams, +}); diff --git a/scripts/changelog.js b/scripts/changelog.js new file mode 100644 index 000000000..5905e9982 --- /dev/null +++ b/scripts/changelog.js @@ -0,0 +1,73 @@ +import { doms } from './global.js'; +import { translation } from './i18n.js'; +import { confirmPopup, sanitizeHTML } from './misc.js'; + +// ESLint error-skipping for webpack-injected globals +/* global VERSION */ +/* global CHANGELOG */ + +/** + * Check for recent local app updates + */ +export function checkForUpgrades() { + // Check if the last used version doesn't match the current version. + // Note: it's intended to skip if `lastVersion` is null, as this stops the popups for NEW users. + const lastVersion = localStorage.getItem('version'); + if (lastVersion && lastVersion !== VERSION.split('-')[0]) { + // Old user's first time on this update; display the changelog + renderChangelog(); + } + // Update the footer with our version + doms.domVersion.innerText = `v${VERSION}`; + + // Update the last-used app version + localStorage.setItem('version', VERSION.split('-')[0]); +} + +/** + * Render the Changelog from app versioning data, displaying it to the user + */ +export function renderChangelog() { + let strHTML = ''; + + // Loop every line of the markdown + for (const rawLine of CHANGELOG.split(/\r?\n/)) { + // Skip empty lines with linebreaks + if (!rawLine.trim()) { + strHTML += '

'; + continue; + } + + // Parse the element type and the line content + const type = rawLine[0]; + const line = rawLine.substring(1).trim(); + + switch (type) { + case '#': + // `#` is a header, for titles like "New Features" or "Bug Fixes" + strHTML += `

${sanitizeHTML(line)}

`; + break; + case '-': + // `-` is a list element, for each 'New Feature' or 'Bug Fix' to be listed with + strHTML += `

${sanitizeHTML( + line + )}

`; + break; + default: + // If no element was recognised, it's just a plaintext line + strHTML += `

${sanitizeHTML(type + line)}

`; + break; + } + } + + // Enclose the Changelog in a body with a Changelog class + const strFinalHTML = `
${strHTML}
`; + + confirmPopup({ + title: translation.changelogTitle + '
' + VERSION + '?', + html: strFinalHTML, + resolvePromise: false, + hideConfirm: true, + centerButtons: true, + }); +} diff --git a/scripts/charting.js b/scripts/charting.js new file mode 100644 index 000000000..a78968ce4 --- /dev/null +++ b/scripts/charting.js @@ -0,0 +1,247 @@ +import { + ArcElement, + Chart, + Colors, + DoughnutController, + Legend, + LinearScale, + Tooltip, +} from 'chart.js'; +import { cChainParams, COIN } from './chain_params.js'; +import { doms } from './global.js'; +import { Database } from './database.js'; +import { translation } from './i18n.js'; +import { wallet } from './wallet.js'; +import { COutpoint } from './transaction.js'; +import { beautifyNumber } from './misc.js'; + +Chart.register( + Colors, + DoughnutController, + ArcElement, + Legend, + Tooltip, + LinearScale +); + +/** + * The wallet breakdown modal chart + * @type {Chart} + */ +let chartWalletBreakdown = null; + +/** + * An element generated from the wallet for the purpose of charting or tables + * @typedef {object} WalletDatasetPoint + * @property {string} type + * @property {number} balance + * @property {string} colour + */ + +/** + * Generate an array of pie/doughnut charting data from the wallet's totals + * @returns {Promise>} - The charting data + */ +async function getWalletDataset() { + const arrBreakdown = []; + + // Public (Available) + const spendable_bal = wallet.balance; + if (spendable_bal > 0) { + arrBreakdown.push({ + type: translation.chartPublicAvailable, + balance: spendable_bal / COIN, + colour: '#C898F5', + }); + } + + // Shielded (Available spendable) + const shield_spendable = await wallet.getShieldBalance(); + if (shield_spendable > 0) { + arrBreakdown.push({ + type: 'Shield Available', + balance: shield_spendable / COIN, + colour: '#9131EA', + }); + } + + // Shielded (Pending i.e still unspendable) + const shield_pending = await wallet.getPendingShieldBalance(); + if (shield_pending > 0) { + arrBreakdown.push({ + type: 'Shield Pending', + balance: shield_pending / COIN, + colour: '#5e169c', + }); + } + + const immature_bal = wallet.immatureBalance; + if (immature_bal > 0) { + arrBreakdown.push({ + type: translation.chartImmatureBalance, + balance: immature_bal / COIN, + colour: '#4A1399', + }); + } + // Staking (Locked) + const spendable_cold_bal = wallet.coldBalance; + if (spendable_cold_bal > 0) { + arrBreakdown.push({ + type: 'Staking', + balance: spendable_cold_bal / COIN, + colour: '#721DEA', + }); + } + + const masternode = await (await Database.getInstance()).getMasternode(); + + // Masternode (Locked) + if (masternode) { + if ( + wallet.isCoinLocked( + new COutpoint({ + txid: masternode.collateralTxId, + n: masternode.outidx, + }) + ) + ) { + arrBreakdown.push({ + type: 'Masternode', + balance: cChainParams.current.collateralInSats / COIN, + colour: 'rgba(19, 13, 30, 1)', + }); + } + } + return arrBreakdown; +} + +/** + * Create the initial Wallet Breakdown chart configuration and UI rendering + */ +export async function generateWalletBreakdown(arrBreakdown) { + // Render the PIVX logo in the centre of the "Wallet Doughnut" + const image = new Image(); + const svg = (await import('../assets/icons/image-pivx-logo.svg')).default; + const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' })); + image.src = url; + const logo_plugin = { + id: 'centreLogo', + beforeDraw: (chart) => { + const ctx = chart.ctx; + const { top, left, width, height } = chart.chartArea; + const x = left + width / 2 - (image.width - 30) / 2; + const y = top + height / 2 - (image.height - 30) / 2; + ctx.globalAlpha = 1; + ctx.drawImage(image, x, y, image.width - 30, image.height - 30); + ctx.globalAlpha = 1; + }, + }; + + // Initialise the chart + chartWalletBreakdown = new Chart(doms.domWalletBreakdownCanvas, { + type: 'doughnut', + data: { + labels: arrBreakdown.map((data) => data.type), + datasets: [ + { + label: cChainParams.current.TICKER, + data: arrBreakdown.map((data) => data.balance), + }, + ], + }, + plugins: [logo_plugin], + options: { + borderWidth: 0, + backgroundColor: arrBreakdown.map((data) => data.colour), + radius: '85%', + cutout: '60%', + animation: { + duration: 500, + }, + plugins: { + legend: { + display: false, + labels: { + color: '#FFFFFF', + font: { + size: 16, + }, + }, + }, + }, + }, + }); + + let breakdownLegendStr = ''; + for (let i = 0; i < arrBreakdown.length; i++) { + breakdownLegendStr += `
+
+
+ ${beautifyNumber( + arrBreakdown[i]['balance'].toFixed(2), + '13px' + )} ${ + cChainParams.current.TICKER + } + ${ + arrBreakdown[i]['type'] + } +
+
`; + } + + doms.domWalletBreakdownLegend.innerHTML = breakdownLegendStr; + + // Set an interval internally to refresh the chart in real-time + chartWalletBreakdown.interval = setInterval(renderWalletBreakdown, 2500); +} + +/** + * Render the wallet breakdown chart, or create it if not initialised + */ +export async function renderWalletBreakdown() { + // Only if the modal is open, to save performance and prevent rendering when it's not visible + if (!doms.domModalWalletBreakdown.style.display === 'block') return; + + // Update the chart data with the new dataset + const arrBreakdown = await getWalletDataset(); + + // If no chart exists, create it + if (!chartWalletBreakdown) + return await generateWalletBreakdown(arrBreakdown); + + // Update the chart + chartWalletBreakdown.data.labels = arrBreakdown.map((data) => data.type); + chartWalletBreakdown.data.datasets[0].data = arrBreakdown.map( + (data) => data.balance + ); + chartWalletBreakdown.data.datasets[0].backgroundColor = arrBreakdown.map( + (data) => data.colour + ); + chartWalletBreakdown.update(); + + // Update the wallet breakdown + let breakdownLegendStr = ''; + for (let i = 0; i < arrBreakdown.length; i++) { + breakdownLegendStr += `
+
+
+ ${beautifyNumber( + arrBreakdown[i]['balance'].toFixed(2), + '13px' + )} ${ + cChainParams.current.TICKER + } + ${ + arrBreakdown[i]['type'] + } +
+
`; + } + + doms.domWalletBreakdownLegend.innerHTML = breakdownLegendStr; +} diff --git a/scripts/composables/use_settings.js b/scripts/composables/use_settings.js new file mode 100644 index 000000000..01c7c852a --- /dev/null +++ b/scripts/composables/use_settings.js @@ -0,0 +1,24 @@ +import { getEventEmitter } from '../event_bus.js'; +import { ref } from 'vue'; +import { nDisplayDecimals, fAdvancedMode } from '../settings.js'; + +export function useSettings() { + const advancedMode = ref(fAdvancedMode); + const displayDecimals = ref(0); + const autoLockWallet = ref(false); + + getEventEmitter().on('advanced-mode', (fAdvancedMode) => { + advancedMode.value = fAdvancedMode; + }); + getEventEmitter().on('balance-update', async () => { + displayDecimals.value = nDisplayDecimals; + }); + getEventEmitter().on('auto-lock-wallet', (fAutoLockWallet) => { + autoLockWallet.value = fAutoLockWallet; + }); + return { + advancedMode, + displayDecimals, + autoLockWallet, + }; +} diff --git a/scripts/composables/use_wallet.js b/scripts/composables/use_wallet.js new file mode 100644 index 000000000..9e9d4aa0f --- /dev/null +++ b/scripts/composables/use_wallet.js @@ -0,0 +1,152 @@ +import { getEventEmitter } from '../event_bus.js'; +import { hasEncryptedWallet, wallet } from '../wallet.js'; +import { ref, watch } from 'vue'; +import { strCurrency } from '../settings.js'; +import { cOracle } from '../prices.js'; +import { ledgerSignTransaction } from '../ledger.js'; +import { defineStore } from 'pinia'; +import { lockableFunction } from '../lock.js'; + +/** + * This is the middle ground between vue and the wallet class + * It makes sure that everything is up to date and provides + * a reactive interface to it + */ +export const useWallet = defineStore('wallet', () => { + // Eventually we want to create a new wallet + // For now we'll just import the existing one + // const wallet = new Wallet(); + + const publicMode = ref(true); + watch(publicMode, (publicMode) => { + if (publicMode) { + document.getElementById('navbar').classList.toggle('active'); + document.getElementById('page-container-light').style.opacity = '1'; + } else { + document.getElementById('navbar').classList.toggle('active'); + document.getElementById('page-container-light').style.opacity = '0'; + } + }); + + const isImported = ref(wallet.isLoaded()); + const isViewOnly = ref(wallet.isViewOnly()); + const isSynced = ref(wallet.isSynced); + const getKeyToBackup = async () => await wallet.getKeyToBackup(); + const isEncrypted = ref(true); + const loadFromDisk = () => wallet.loadFromDisk(); + const hasShield = ref(wallet.hasShield()); + + const setMasterKey = async ({ mk, extsk }) => { + wallet.setMasterKey({ mk, extsk }); + isImported.value = wallet.isLoaded(); + isHardwareWallet.value = wallet.isHardwareWallet(); + isHD.value = wallet.isHD(); + isViewOnly.value = wallet.isViewOnly(); + isEncrypted.value = await hasEncryptedWallet(); + isSynced.value = wallet.isSynced; + }; + const setExtsk = async (extsk) => { + await wallet.setExtsk(extsk); + }; + const setShield = (shield) => { + wallet.setShield(shield); + hasShield.value = wallet.hasShield(); + }; + const getAddress = () => wallet.getAddress(); + const isHardwareWallet = ref(wallet.isHardwareWallet()); + const isHD = ref(wallet.isHD()); + const checkDecryptPassword = async (passwd) => + await wallet.checkDecryptPassword(passwd); + + hasEncryptedWallet().then((r) => { + isEncrypted.value = r; + }); + + const encrypt = async (passwd) => { + const res = await wallet.encrypt(passwd); + isEncrypted.value = await hasEncryptedWallet(); + return res; + }; + const balance = ref(0); + const shieldBalance = ref(0); + const coldBalance = ref(0); + const pendingShieldBalance = ref(0); + const immatureBalance = ref(0); + const currency = ref('USD'); + const price = ref(0.0); + const sync = async () => { + await wallet.sync(); + balance.value = wallet.balance; + shieldBalance.value = await wallet.getShieldBalance(); + pendingShieldBalance.value = await wallet.getPendingShieldBalance(); + isSynced.value = wallet.isSynced; + }; + getEventEmitter().on('shield-loaded-from-disk', () => { + hasShield.value = wallet.hasShield(); + }); + const createAndSendTransaction = lockableFunction( + async (network, address, value, opts) => { + const tx = wallet.createTransaction(address, value, opts); + if (wallet.isHardwareWallet()) { + await ledgerSignTransaction(wallet, tx); + } else { + await wallet.sign(tx); + } + const res = await network.sendTransaction(tx.serialize()); + if (res) { + await wallet.addTransaction(tx); + } else { + wallet.discardTransaction(tx); + } + return res; + } + ); + const isCreatingTransaction = () => createAndSendTransaction.isLocked(); + + getEventEmitter().on('toggle-network', async () => { + isEncrypted.value = await hasEncryptedWallet(); + }); + + getEventEmitter().on('balance-update', async () => { + balance.value = wallet.balance; + immatureBalance.value = wallet.immatureBalance; + currency.value = strCurrency.toUpperCase(); + shieldBalance.value = await wallet.getShieldBalance(); + pendingShieldBalance.value = await wallet.getPendingShieldBalance(); + coldBalance.value = wallet.coldBalance; + price.value = cOracle.getCachedPrice(strCurrency); + }); + + return { + publicMode, + isImported, + isViewOnly, + isEncrypted, + isSynced, + getKeyToBackup, + setMasterKey, + setExtsk, + setShield, + isHardwareWallet, + checkDecryptPassword, + encrypt, + getAddress, + wipePrivateData: () => { + wallet.wipePrivateData(); + isViewOnly.value = wallet.isViewOnly(); + }, + isCreatingTransaction, + isHD, + balance, + hasShield, + shieldBalance, + pendingShieldBalance, + immatureBalance, + currency, + price, + sync, + createAndSendTransaction, + loadFromDisk, + coldBalance, + }; +}); diff --git a/scripts/contacts-book.js b/scripts/contacts-book.js new file mode 100644 index 000000000..cbb5e7b20 --- /dev/null +++ b/scripts/contacts-book.js @@ -0,0 +1,1058 @@ +import { Buffer } from 'buffer'; +import { Database } from './database.js'; +import { doms, toClipboard } from './global.js'; +import { ALERTS, tr, translation } from './i18n.js'; +import { + confirmPopup, + createAlert, + createQR, + getImageFile, + isValidPIVXAddress, + isXPub, + sanitizeHTML, +} from './misc.js'; +import { scanQRCode } from './scanner.js'; +import { wallet, hasEncryptedWallet } from './wallet.js'; +import { useWallet } from './composables/use_wallet.js'; +import pIconCopy from '../assets/icons/icon-copy.svg'; +import pIconCamera from '../assets/icons/icon-camera.svg'; +import pIconBin from '../assets/icons/icon-bin.svg'; + +/** + * Represents an Account contact + */ +export class Contact { + /** + * Creates a new Account contact + * @param {Object} options - The contact options + * @param {string} options.label - The label of the contact + * @param {string} options.icon - The optional icon of the contact (base64) + * @param {string} options.pubkey - The Master public key of the contact + * @param {number} options.date - The date (unix timestamp) of the contact being saved + */ + constructor({ label, icon, pubkey, date }) { + this.label = label; + this.icon = icon; + this.pubkey = pubkey; + this.date = date; + } + + /** The label of the Contact + * @type {string} + */ + label; + + /** The optional icon of the Contact (base64) + * @type {string} + */ + icon; + + /** The Master public key of the Contact + * @type {string} + */ + pubkey; + + /** The date (unix timestamp) of the Contact being saved + * @type {number} + */ + date; +} + +/** + * Add a Contact to an Account's contact list + * @param {import('./accounts.js').Account} account - The account to add the Contact to + * @param {Contact} contact - The contact object + */ +export async function addContact(account, contact) { + // TODO: once multi-account is added, ensure this function adds the contact to the correct account (by pubkey) + const cDB = await Database.getInstance(); + + // Push contact in to the account + account.contacts.push(contact); + + // Save to the DB + await cDB.updateAccount(account); +} + +/** + * Remove a Contact from an Account's contact list + * @param {import('./accounts.js').Account} account - The account to remove the Contact from + * @param {string} pubkey - The contact pubkey + */ +export async function removeContact(account, pubkey) { + // TODO: once multi-account is added, ensure this function adds the contact to the correct account (by pubkey) + const cDB = await Database.getInstance(); + + // Find the contact by index, if it exists; splice it away + const nIndex = account.contacts.findIndex((a) => a.pubkey === pubkey); + if (nIndex > -1) { + // Splice out the contact, and save to DB + account.contacts.splice(nIndex, 1); + await cDB.updateAccount(account, true); + } +} + +/** + * Render an Account's contact list + * @param {import('./accounts.js').Account} account + * @param {boolean} fPrompt - If this is a Contact Selection prompt + */ +export async function renderContacts(account, fPrompt = false) { + let strHTML = ''; + let i = 0; + + // For non-prompts: we allow the user to Add, Edit or Delete their contacts + if (!fPrompt) { + // Lastly, inject the "Add Account" UI to the table + strHTML += ` +
+ + + + +
+
+ +
+
+ +
+
+
+ `; + + // Render an editable Contacts Table + strHTML += `
+
`; + + for (const cContact of account.contacts || []) { + const strPubkey = isXPub(cContact.pubkey) + ? cContact.pubkey.slice(0, 32) + '…' + : cContact.pubkey; + strHTML += ` +
+
+ +
+
+ ${sanitizeHTML( + cContact.label + )} + + + ${sanitizeHTML(strPubkey).substring(0, 17)}... + + +
+
+ ${pIconBin} +
+
+ `; + i++; + } + + strHTML += `
`; + + doms.domContactsTable.innerHTML = strHTML; + } else { + // For prompts: the user must click an address (or cancel), and cannot add, edit or delete contacts + strHTML += `
+
`; + for (const cContact of account.contacts || []) { + const strPubkey = isXPub(cContact.pubkey) + ? cContact.pubkey.slice(0, 32) + '…' + : cContact.pubkey; + strHTML += ` +
+
+ +
+
+ ${sanitizeHTML( + sanitizeHTML(cContact.label) + )} + + + ${sanitizeHTML(strPubkey).substring(0, 23)}... + + +
+
+ `; + i++; + } + strHTML += `
`; + + // Add the final "Back" button + strHTML += ` +
+ +
+ `; + + // Finish the display + strHTML += `
`; + + // Prepare the Contact list Prompt + const cPrompt = getUserContactClick(); + + // Hook the Contact Prompt to the Popup UI, which resolves when the user has interacted with the Contact Prompt + return await confirmPopup({ + title: translation.chooseAContact, + html: strHTML, + resolvePromise: cPrompt(), + purpleModal: true, + textLeft: true, + noPadding: true, + maxHeight: 450, + }); + } +} + +/** + * Creates and returns a function that returns a promise for a click event. + * + * The promise will resolve with the Contact Name of whichever button is clicked first. + * + * Once a button is clicked, all remaining listeners are removed. + */ +function getUserContactClick() { + // Specify the function to return + return function () { + // Note that the return type is a Promise, this will wait on the click + return new Promise((resolve, _reject) => { + // Wait a bit for the DOM to fully render, then setup the handler functions + attach them to the Contact Buttons via Event Listeners + setTimeout(() => { + // The function to handle the click + function handleClick(event) { + // If this is the Exit button (a -1 index), just silently quit + if (event.target.id.endsWith('-1')) return resolve(''); + + // Splice the 'Contact Index' from the button clicked + const nIndex = event.target.id.match(/([0-9]+)$/)[0]; + // Fetch the associated Contact Name from the table + // TODO: maybe don't rely on the table, and just fetch the Contact Index from the DB Contacts? + const strName = document.getElementById( + `contactsName${nIndex}` + ).innerText; + // Resolve the promise with the Contact Name of the button that was clicked first + resolve(strName); + // Remove all the remaining click listeners + removeRemainingListeners(); + } + + // The function to iterate over the buttons and remove their listeners + function removeRemainingListeners() { + let i = -1; + let button; + // This iteration removes the listener from each button + // eslint-disable-next-line no-cond-assign + while ( + (button = document.getElementById( + `contactsSelector${i}` + )) + ) { + button.removeEventListener('click', handleClick); + i++; + } + } + + // Attach a click listener to each `contactsSelector` button + let i = -1; + let button; + // eslint-disable-next-line no-cond-assign + while ( + (button = document.getElementById(`contactsSelector${i}`)) + ) { + button.addEventListener('click', handleClick, { + once: true, + }); + i++; + } + }, 500); // Waits 500ms to ensure the all the elements have been added to the DOM (yeah, not the most elegant, but cannot think of a better solution yet) + }); + }; +} + +/** + * A function that uses the Prompt system to ask the user for a contact + */ +export async function promptForContact() { + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + if (!cAccount || (cAccount.contacts && cAccount.contacts.length === 0)) + return createAlert('warning', ALERTS.CONTACTS_YOU_HAVE_NONE, 2500); + return renderContacts(cAccount, true); +} + +/** + * A GUI button wrapper that fills an Input with a user-selected Contact + * @param {HTMLInputElement} domInput - The input box to fill with a selected Contact Address + */ +export async function guiSelectContact(domInput) { + // Fill the 'Input box' with a user-chosen Contact + domInput.value = (await promptForContact()) || ''; + + // Run the validity checker for double-safety + await guiCheckRecipientInput({ target: domInput }); +} + +/** + * A GUI wrapper that renders the current Account's contacts list + */ +export async function guiRenderContacts() { + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + if (!cAccount || !cAccount.contacts) { + return createAlert( + 'warning', + tr(ALERTS.CONTACTS_ENCRYPT_FIRST, [ + { button: translation.secureYourWallet }, + ]), + 3500 + ); + } + + return renderContacts(cAccount); +} + +/** + * Set the current Account's Contact name + * @param {import('./accounts.js').Account} account - The account to add the new Name to + * @param {String} name - The name to set + */ +export async function setAccountContactName(account, name) { + const cDB = await Database.getInstance(); + + // Set the name + account.name = name; + + // Save name to the DB + await cDB.updateAccount(account); +} + +async function renderContactModal() { + // Fetch Contact info from the current Account + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // Check that a local Contact name was set + if (cAccount?.name) { + // Derive our Public Key + let strPubkey = ''; + + // If HD: use xpub, otherwise we'll fallback to our single address + strPubkey = await wallet.getKeyToExport(); + + // Construct the Contact Share URI + const strContactURI = await localContactToURI(cAccount, strPubkey); + + // Render Copy Button + doms.domModalQrLabel.innerHTML = `${translation.shareContactURL}${pIconCopy}`; + + // We'll render a short informational text, alongside a QR below for Contact scanning + doms.domModalQR.innerHTML = ` + +
+ `; + const domQR = document.getElementById('receiveModalEmbeddedQR'); + createQR(strContactURI, domQR, 10); + domQR.firstChild.style.width = '100%'; + domQR.firstChild.style.height = 'auto'; + domQR.firstChild.classList.add('no-antialias'); + document.getElementById('clipboard').value = strPubkey; + } else { + // Get our current wallet address + const strAddress = wallet.getCurrentAddress(); + + // Update the QR Label (we'll show the address here for now, user can set Contact "Name" optionally later) + doms.domModalQrLabel.innerHTML = + strAddress + + ` + ${pIconCopy} + `; + + // Update the QR section + if (await hasEncryptedWallet()) { + doms.domModalQR.innerHTML = ` + ${translation.setupYourContact} +

${translation.receiveWithContact}

+ + `; + } else { + doms.domModalQR.innerHTML = ` +
+ ${translation.secureYourWallet} +

${tr(translation.encryptFirstForContacts, [ + { button: translation.secureYourWallet }, + ])}

+
+ `; + } + } +} + +function renderAddress(strAddress) { + try { + createQR('pivx:' + strAddress, doms.domModalQR); + doms.domModalQR.firstChild.style.width = '100%'; + doms.domModalQR.firstChild.style.height = 'auto'; + doms.domModalQR.firstChild.classList.add('no-antialias'); + } catch (e) { + doms.domModalQR.hidden = true; + } + doms.domModalQrLabel.innerHTML = + // SanitzeHTML shouldn't be necessary, but let's keep it just in case + sanitizeHTML(strAddress) + + `${pIconCopy}`; + document.getElementById('clipboard').value = strAddress; +} + +/** + * Render the Receive Modal with either our Contact or Address + * @param {boolean} fContact - `true` to render our Contact, `false` to render our current Address + */ +export async function guiRenderReceiveModal( + cReceiveType = RECEIVE_TYPES.CONTACT +) { + doms.domModalQR.hidden = false; + switch (cReceiveType) { + case RECEIVE_TYPES.CONTACT: + await renderContactModal(); + break; + case RECEIVE_TYPES.ADDRESS: + renderAddress(wallet.getCurrentAddress()); + break; + case RECEIVE_TYPES.SHIELD: + renderAddress(await wallet.getNewShieldAddress()); + break; + case RECEIVE_TYPES.XPUB: { + // Get our current wallet XPub + const strXPub = wallet.getXPub(); + + // Update the QR Label (we'll show the address here for now, user can set Contact "Name" optionally later) + doms.domModalQrLabel.innerHTML = + sanitizeHTML(strXPub) + + `${pIconCopy}`; + + // We'll render a short informational text, alongside a QR below for Contact scanning + doms.domModalQR.innerHTML = ` + +
+ `; + + // Update the QR section + const domQR = document.getElementById('receiveModalEmbeddedQR'); + createQR(strXPub, domQR, 10); + domQR.firstChild.style.width = '100%'; + domQR.firstChild.style.height = 'auto'; + domQR.firstChild.classList.add('no-antialias'); + document.getElementById('clipboard').value = strXPub; + + break; + } + } +} + +/** + * A GUI wrapper to re-render the current Receive Modal configuration + */ +export async function guiRenderCurrentReceiveModal() { + return guiToggleReceiveType(cReceiveType); +} + +/** + * An enum of Receive Types (i.e: receive by Contact, Address, XPub) + */ +export const RECEIVE_TYPES = { + CONTACT: 0, + ADDRESS: 1, + SHIELD: 2, + XPUB: 3, + MAX_RECEIVE: 4, +}; + +/** The current Receive Type used by Receive UIs */ +export let cReceiveType = RECEIVE_TYPES.CONTACT; + +/** + * Helper function for guiToggleReceiveType + */ +function findNextAvailableType(startType, availableTypes) { + do { + startType = (startType + 1) % RECEIVE_TYPES.MAX_RECEIVE; + } while (!availableTypes.includes(startType)); + return startType; +} + +/** + * Cycles through the Receive Types with each run + * @param {number?} nForceType - Optionally force the Receive Type + */ +export async function guiToggleReceiveType(nForceType = null) { + const walletUse = useWallet(); + + // Figure out which Types can be used with this wallet + const availableTypes = [RECEIVE_TYPES.CONTACT]; + + // Show only addresses according to public/private mode + if (walletUse.publicMode) { + availableTypes.push(RECEIVE_TYPES.ADDRESS); + + if (wallet.isHD()) { + availableTypes.push(RECEIVE_TYPES.XPUB); + } + } else { + if (wallet.hasShield()) { + availableTypes.push(RECEIVE_TYPES.SHIELD); + } + } + + // Loop back to the first if we hit the end + cReceiveType = + nForceType !== null + ? nForceType + : findNextAvailableType(cReceiveType, availableTypes); + + // If type is not found, then default to contact + if (!availableTypes.includes(cReceiveType)) { + cReceiveType = availableTypes[0]; + } + + // Convert the *next* Type to text (also runs through i18n system) + const nNextType = findNextAvailableType(cReceiveType, availableTypes); + let strNextType = ''; + switch (nNextType) { + case RECEIVE_TYPES.CONTACT: + strNextType = translation.contact; + break; + case RECEIVE_TYPES.ADDRESS: + strNextType = translation.address; + break; + case RECEIVE_TYPES.SHIELD: + strNextType = translation.shieldAddress; + break; + case RECEIVE_TYPES.XPUB: + strNextType = translation.xpub; + break; + } + + // Render the new UI + doms.domModalQrReceiveTypeBtn.innerText = + translation.changeTo + ' ' + strNextType; + guiRenderReceiveModal(cReceiveType); + + // Return the new Receive Type index + return cReceiveType; +} + +/** A GUI wrapper that adds a contact to the current Account's contacts list */ +export async function guiAddContact() { + const strName = document.getElementById('contactsNameInput').value.trim(); + const strAddr = document + .getElementById('contactsAddressInput') + .value.trim(); + + // Verify the name + if (strName.length < 1) + return createAlert('warning', ALERTS.CONTACTS_NAME_REQUIRED, 2500); + if (strName.length > 32) + return createAlert('warning', ALERTS.CONTACTS_NAME_TOO_LONG, 2500); + + // Verify the address + if (!isValidPIVXAddress(strAddr)) + return createAlert( + 'warning', + tr(ALERTS.INVALID_ADDRESS, [{ address: strAddr }]), + 3000 + ); + // Ensure we're not adding our own XPub + if (isXPub(strAddr)) { + if (wallet.isHD()) { + // Compare the XPub against our own + const fOurs = strAddr === wallet.getXPub(); + if (fOurs) { + createAlert( + 'warning', + ALERTS.CONTACTS_CANNOT_ADD_YOURSELF, + 3500 + ); + return false; + } + } + } else { + // Ensure we're not adding (one of) our own address(es) + if (wallet.isOwnAddress(strAddr)) { + createAlert('warning', ALERTS.CONTACTS_CANNOT_ADD_YOURSELF, 3500); + return false; + } + } + + // Fetch the current Account + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // Check this Contact isn't already saved, either fully or partially + const cContactByName = cAccount.getContactBy({ name: strName }); + const cContactByPubkey = cAccount.getContactBy({ pubkey: strAddr }); + + // If both Name and Key are saved, then they just tried re-adding the same Contact twice + if (cContactByName && cContactByPubkey) { + createAlert('warning', ALERTS.CONTACTS_ALREADY_EXISTS, 3000); + return true; + } + + // If the Name is saved, but not key, then this *could* be a kind of Username-based phishing attempt + if (cContactByName && !cContactByPubkey) { + createAlert('warning', ALERTS.CONTACTS_NAME_ALREADY_EXISTS, 4000); + return true; + } + + // If the Key is saved, but not the name: perhaps the Contact changed their name? + if (!cContactByName && cContactByPubkey) { + createAlert( + 'warning', + tr(ALERTS.CONTACTS_KEY_ALREADY_EXISTS, [ + { newName: strName }, + { oldName: cContactByPubkey.label }, + ]), + 7500 + ); + return true; + } + + // Add the Contact to it + await addContact(cAccount, { + label: strName, + pubkey: strAddr, + date: Date.now(), + }); + + // Render the new list + return renderContacts(cAccount); +} + +/** + * Prompt the user to add a new Contact, safely checking for duplicates + * @param {String} strName - The Name of the Contact + * @param {String} strPubkey - The Public Key of the Contact + * @param {boolean} fDuplicateNotif - Notify the user if the incoming Contact is a duplicate + * @returns {Promise} - `true` if contact was added, `false` if not + */ +export async function guiAddContactPrompt( + strName, + strPubkey, + fDuplicateNotif = true +) { + // Verify the name + if (strName.length < 1) + return createAlert('warning', ALERTS.CONTACTS_NAME_REQUIRED, 2500); + if (strName.length > 32) + return createAlert('warning', ALERTS.CONTACTS_NAME_TOO_LONG, 2500); + + // Verify the address + if (!isValidPIVXAddress(strPubkey)) + return createAlert( + 'warning', + tr(ALERTS.INVALID_ADDRESS, [{ address: strPubkey }]), + 4000 + ); + + // Ensure we're not adding our own XPub + if (isXPub(strPubkey)) { + if (wallet.isHD()) { + // Compare the XPub against our own + const fOurs = strPubkey === (await wallet.getXPub()); + if (fOurs) { + createAlert( + 'warning', + ALERTS.CONTACTS_CANNOT_ADD_YOURSELF, + 3500 + ); + return false; + } + } + } else { + // Ensure we're not adding (one of) our own address(es) + if (wallet.isOwnAddress(strPubkey)) { + createAlert('warning', ALERTS.CONTACTS_CANNOT_ADD_YOURSELF, 3500); + return false; + } + } + + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // Check this Contact isn't already saved, either fully or partially + const cContactByName = cAccount.getContactBy({ name: strName }); + const cContactByPubkey = cAccount.getContactBy({ pubkey: strPubkey }); + + // If both Name and Key are saved, then they just tried re-adding the same Contact twice + if (cContactByName && cContactByPubkey) { + if (fDuplicateNotif) + createAlert('warning', ALERTS.CONTACTS_ALREADY_EXISTS, 3000); + return true; + } + + // If the Name is saved, but not key, then this *could* be a kind of Username-based phishing attempt + if (cContactByName && !cContactByPubkey) { + if (fDuplicateNotif) + createAlert('warning', ALERTS.CONTACTS_NAME_ALREADY_EXISTS, 4000); + return true; + } + + // If the Key is saved, but not the name: perhaps the Contact changed their name? + if (!cContactByName && cContactByPubkey) { + if (fDuplicateNotif) + createAlert( + 'warning', + tr(ALERTS.CONTACTS_KEY_ALREADY_EXISTS, [ + { newName: strName }, + { oldName: cContactByPubkey.label }, + ]), + 7500 + ); + return true; + } + + // Render an 'Add to Contacts' UI + const strHTML = ` +

+ ${tr(translation.addContactSubtext, [{ strName: strName }])} +
+
+ ${tr(translation.addContactWarning, [ + { strName: strName }, + ])} +

+ `; + + // Hook the Contact Prompt to the Popup UI, which resolves when the user has interacted with the Contact Prompt + const fAdd = await confirmPopup({ + title: tr(translation.addContactTitle, [{ strName: strName }]), + html: strHTML, + }); + + // If accepted, then we add to contacts! + if (fAdd) { + // Add the Contact to the current account + await addContact(cAccount, { + label: strName, + pubkey: strPubkey, + date: Date.now(), + }); + + // Notify + createAlert( + 'success', + tr(ALERTS.CONTACTS_ADDED, [{ strName: strName }]), + 3000 + ); + } + + // Return if the user accepted or declined + return fAdd; +} + +/** + * Prompt the user to edit a contact by it's original name + * + * The new name will be taken from the internal prompt input + * @param {number} nIndex - The DB index of the Contact to edit + * @returns {Promise} - `true` if contact was edited, `false` if not + */ +export async function guiEditContactNamePrompt(nIndex) { + // Fetch the desired Contact to edit + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + const cContact = cAccount.contacts[nIndex]; + + // Render an 'Add to Contacts' UI + const strHTML = ` + + `; + + // Hook the Contact Prompt to the Popup UI, which resolves when the user has interacted with the Contact Prompt + const fContinue = await confirmPopup({ + title: tr(translation.editContactTitle, [ + { strName: sanitizeHTML(cContact.label) }, + ]), + html: strHTML, + }); + if (!fContinue) return false; + + // Verify the name + const strNewName = document.getElementById('contactsNewNameInput').value; + if (strNewName.length < 1) { + createAlert('warning', ALERTS.CONTACTS_NAME_REQUIRED, 2500); + return false; + } + if (strNewName.length > 32) { + createAlert('warning', ALERTS.CONTACTS_NAME_TOO_LONG, 2500); + return false; + } + + // Check this new Name isn't already saved + const cContactByNewName = cAccount.getContactBy({ name: strNewName }); + if (cContactByNewName) { + createAlert( + 'warning', + tr(ALERTS.CONTACTS_EDIT_NAME_ALREADY_EXISTS, [ + { strNewName: strNewName }, + ]), + 4500 + ); + return false; + } + + // Edit it (since it's a pointer to the Account's Contacts) + cContact.label = strNewName; + + // Commit to DB + await cDB.updateAccount(cAccount); + + // Re-render the Contacts UI + await renderContacts(cAccount); + + // Return if the user accepted or declined + return true; +} + +/** + * Prompt the user to add an image to a contact by it's DB index + * + * The new image will be taken from the internal system prompt + * @param {number} nIndex - The DB index of the Contact to edit + * @returns {Promise} - `true` if contact was edited, `false` if not + */ +export async function guiAddContactImage(nIndex) { + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + const cContact = cAccount.contacts[nIndex]; + + // Prompt for the image + const strImage = await getImageFile(); + if (!strImage) return false; + + // Fetch the original contact, edit it (since it's a pointer to the Account's Contacts) + cContact.icon = strImage; + + // Commit to DB + await cDB.updateAccount(cAccount); + + // Re-render the Contacts UI + await renderContacts(cAccount); + + // Return that the edit was successful + return true; +} + +/** + * A GUI wrapper to open a QR Scanner prompt for Contact imports + * @returns {boolean} - `true` if contact was added, `false` if not + */ +export async function guiAddContactQRPrompt() { + const cScan = await scanQRCode(); + + // Empty (i.e: rejected or no camera) can just silently exit + if (!cScan) return false; + + // MPW Contact Request URI + if (cScan?.data?.includes('addcontact=')) { + // Parse as URL Params + const cURL = new URL(cScan.data); + const urlParams = new URLSearchParams(cURL.search); + const strURI = urlParams.get('addcontact'); + + // Sanity check the URI + if (strURI?.includes(':')) { + // Split to 'name' and 'pubkey' + const arrParts = strURI.split(':'); + + // Convert Name from HEX to UTF-8 + const strName = Buffer.from(arrParts[0], 'hex').toString('utf8'); + const strPubkey = arrParts[1]; + + // Prompt the user to add the Contact + const fAdded = await guiAddContactPrompt( + sanitizeHTML(strName), + strPubkey + ); + + // Re-render the list + await guiRenderContacts(); + + // Return the status + return fAdded; + } + } else { + createAlert('warning', ALERTS.CONTACTS_NOT_A_CONTACT_QR, 2500); + return false; + } +} + +/** A GUI wrapper that removes a contact from the current Account's contacts list */ +export async function guiRemoveContact(index) { + // Fetch the current Account + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // Fetch the Contact + const cContact = cAccount.contacts[index]; + + // Confirm the deletion + const fConfirmed = await confirmPopup({ + title: tr(translation.removeContactTitle, [ + { strName: sanitizeHTML(cContact.label) }, + ]), + html: ` +

+ ${tr(translation.removeContactSubtext, [ + { strName: sanitizeHTML(cContact.label) }, + ])} +
+
+ ${translation.removeContactNote} +

+ `, + }); + if (!fConfirmed) return; + + // Remove the Contact from it + await removeContact(cAccount, cAccount.contacts[index].pubkey); + + // Render the new list + return renderContacts(cAccount); +} + +/** A GUI wrapper that sets the name of the current Account */ +export async function guiSetAccountName(strDOM) { + const domInput = document.getElementById(strDOM); + + // Verify the name + const strNewName = domInput.value.trim(); + if (strNewName.length < 1) { + createAlert('warning', ALERTS.CONTACTS_NAME_REQUIRED, 2500); + return false; + } + if (strNewName.length > 32) { + createAlert('warning', ALERTS.CONTACTS_NAME_TOO_LONG, 2500); + return false; + } + + // Fetch the current Account + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // Set the account's name + await setAccountContactName(cAccount, strNewName); + + // Render the new Receive Modal + await guiRenderReceiveModal(); +} + +/** + * Get the address color based on the validity of an address/contact + * @param {string} address + */ +export async function getAddressColor(address) { + // If the value is empty, we don't do any checks and simply reset the colourcoding + if (!address) { + return ''; + } + + // Fetch the current Account + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // Check if this is a Contact + const cContact = cAccount?.getContactBy({ + name: address, + pubkey: address, + }); + if (cContact) { + // Yep, nice! + return 'green'; + } + if (isValidPIVXAddress(address)) { + // Yep! + return 'green'; + } else { + // We give up: this appears to be nonsense + return '#b20000'; + } +} + +/** + * Checks the input from the recipient field + * + * This function should be connected to an `input` as it's `onchange` function + * @param {InputEvent} event - The change event from the input + */ +export async function guiCheckRecipientInput(event) { + const strInput = event.target.value.trim(); + event.target.style.color = await getAddressColor(strInput); +} + +/** + * Search for a Name of a Contact from a given Account and Address + * @param {Account} cAccount - The Account to search for the Contact + * @param {string} address - The address to search for a Contact with + * @returns {string} - The Name of the address Contact, or just the address if none is found + */ +export function getNameOrAddress(cAccount, address) { + return ( + cAccount?.contacts?.find((a) => a.pubkey === address)?.label || address + ); +} + +/** + * Convert the current Account's Contact to a Share URI + * @param {Account?} account - An optional Account to construct the Contact URI from, if omitted, the current DB account is used + * @param {string?} pubkey - An optional Master Public Key to attach to the Contact URI + */ +export async function localContactToURI(account, pubkey) { + // Fetch the current Account + const cDB = await Database.getInstance(); + const cAccount = account || (await cDB.getAccount()); + + // Use the given pubkey; but if none is passed, we'll derive our loaded Public Key + let strPubkey = pubkey || ''; + + // If HD: use xpub, otherwise we'll fallback to our single address + if (!strPubkey) strPubkey = await wallet.getKeyToExport(); + + // Construct the Contact URI Root + const strURL = window.location.origin + window.location.pathname; + + // Convert our Name and Pubkey to HEX + const strHexName = Buffer.from(cAccount.name).toString('hex'); + + // Encode in our URI and return + const strEncodedURI = encodeURIComponent(strHexName + ':' + strPubkey); + return `${strURL}?addcontact=${strEncodedURI}`; +} + +/** + * A GUI wrapper for the Contact URI and Clipboard functions + * @param {InputEvent} event - The DOM element calling the copy function + */ +export async function localContactToClipboard(event) { + return toClipboard(await localContactToURI(), event.target); +} diff --git a/scripts/dashboard/AccessWallet.vue b/scripts/dashboard/AccessWallet.vue new file mode 100644 index 000000000..c69cd2409 --- /dev/null +++ b/scripts/dashboard/AccessWallet.vue @@ -0,0 +1,116 @@ + + + diff --git a/scripts/dashboard/Activity.vue b/scripts/dashboard/Activity.vue new file mode 100644 index 000000000..25ed32bad --- /dev/null +++ b/scripts/dashboard/Activity.vue @@ -0,0 +1,421 @@ + + + diff --git a/scripts/dashboard/CreateWallet.vue b/scripts/dashboard/CreateWallet.vue new file mode 100644 index 000000000..f53479be1 --- /dev/null +++ b/scripts/dashboard/CreateWallet.vue @@ -0,0 +1,117 @@ + + + diff --git a/scripts/dashboard/Dashboard.vue b/scripts/dashboard/Dashboard.vue new file mode 100644 index 000000000..5284f167e --- /dev/null +++ b/scripts/dashboard/Dashboard.vue @@ -0,0 +1,914 @@ + + + diff --git a/scripts/dashboard/ExportPrivKey.vue b/scripts/dashboard/ExportPrivKey.vue new file mode 100644 index 000000000..36cabef81 --- /dev/null +++ b/scripts/dashboard/ExportPrivKey.vue @@ -0,0 +1,83 @@ + + + diff --git a/scripts/dashboard/GenKeyWarning.vue b/scripts/dashboard/GenKeyWarning.vue new file mode 100644 index 000000000..626432638 --- /dev/null +++ b/scripts/dashboard/GenKeyWarning.vue @@ -0,0 +1,152 @@ + + + diff --git a/scripts/dashboard/Login.vue b/scripts/dashboard/Login.vue new file mode 100644 index 000000000..098b404a1 --- /dev/null +++ b/scripts/dashboard/Login.vue @@ -0,0 +1,71 @@ + + + diff --git a/scripts/dashboard/RestoreWallet.vue b/scripts/dashboard/RestoreWallet.vue new file mode 100644 index 000000000..2bbfd3a5d --- /dev/null +++ b/scripts/dashboard/RestoreWallet.vue @@ -0,0 +1,84 @@ + + diff --git a/scripts/dashboard/TransferMenu.vue b/scripts/dashboard/TransferMenu.vue new file mode 100644 index 000000000..ed3eb48e7 --- /dev/null +++ b/scripts/dashboard/TransferMenu.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/scripts/dashboard/VanityGen.vue b/scripts/dashboard/VanityGen.vue new file mode 100644 index 000000000..b566719cf --- /dev/null +++ b/scripts/dashboard/VanityGen.vue @@ -0,0 +1,188 @@ + + + + diff --git a/scripts/dashboard/WalletBalance.vue b/scripts/dashboard/WalletBalance.vue new file mode 100644 index 000000000..4333335f1 --- /dev/null +++ b/scripts/dashboard/WalletBalance.vue @@ -0,0 +1,602 @@ + + + diff --git a/scripts/dashboard/WalletButtons.vue b/scripts/dashboard/WalletButtons.vue new file mode 100644 index 000000000..5a3faeb3b --- /dev/null +++ b/scripts/dashboard/WalletButtons.vue @@ -0,0 +1,69 @@ + + + diff --git a/scripts/database.js b/scripts/database.js new file mode 100644 index 000000000..a153f4dbc --- /dev/null +++ b/scripts/database.js @@ -0,0 +1,524 @@ +import { openDB } from 'idb'; +import Masternode from './masternode.js'; +import { Settings } from './settings.js'; +import { cChainParams } from './chain_params.js'; +import { + confirmPopup, + sanitizeHTML, + createAlert, + isSameType, + isEmpty, +} from './misc.js'; +import { PromoWallet } from './promos.js'; +import { ALERTS, translation } from './i18n.js'; +import { Account } from './accounts.js'; +import { COutpoint, CTxIn, CTxOut, Transaction } from './transaction.js'; + +export class Database { + /** + * The current version of the DB - increasing this will prompt the Upgrade process for clients with an older version + * Version 1 = Add index DB (#121) + * Version 2 = Promos Integration (#124) + * Version 3 = TX Database (#235) + * Version 4 = Tx Refactor (#284) + * Version 5 = Tx shield data (#295) + * @type{Number} + */ + static version = 5; + + /** + * @type{import('idb').IDBPDatabase} + */ + #db; + + constructor({ db }) { + this.#db = db; + } + + close() { + this.#db.close(); + this.#db = null; + } + + /** + * Add masternode to the database + * @param {Masternode} masternode + * @param {Masterkey} _masterKey - Masterkey associated to the masternode. Currently unused + */ + async addMasternode(masternode, _masterKey) { + const store = this.#db + .transaction('masternodes', 'readwrite') + .objectStore('masternodes'); + // For now the key is 'masternode' since we don't support multiple masternodes + await store.put(masternode, 'masternode'); + } + /** + * Removes a masternode + * @param {Masterkey} _masterKey - Masterkey associated to the masternode. Currently unused + */ + async removeMasternode(_masterKey) { + const store = this.#db + .transaction('masternodes', 'readwrite') + .objectStore('masternodes'); + await store.delete('masternode'); + } + + /** + * Store a tx inside the database + * @param {Transaction} tx + */ + async storeTx(tx) { + const store = this.#db + .transaction('txs', 'readwrite') + .objectStore('txs'); + await store.put(tx.__original, tx.txid); + } + + /** + * Remove a tx from the database + * @param {String} txid - transaction id + */ + async removeTx(txid) { + const store = this.#db + .transaction('txs', 'readwrite') + .objectStore('txs'); + await store.delete(txid); + } + + /** + * Add Promo Code to the database for tracking and management + * @param {PromoWallet} promo + */ + async addPromo(promo) { + const store = this.#db + .transaction('promos', 'readwrite') + .objectStore('promos'); + // The plaintext code is our key, since codes are unique and deterministic anyway + await store.put(promo, promo.code); + } + /** + * Removes a Promo Code from the Promo management system + * @param {string} promoCode - the promo code to remove + */ + async removePromo(promoCode) { + const store = this.#db + .transaction('promos', 'readwrite') + .objectStore('promos'); + await store.delete(promoCode); + } + + /** + * Adds an account to the database + * + * This will also apply missing Account keys from the Account class automatically, and check high-level type safety. + * @param {Account} account - The Account to add + */ + async addAccount(account) { + // Critical: Ensure the input is an Account instance + if (!(account instanceof Account)) { + console.error( + '---- addAccount() called with invalid input, input dump below ----' + ); + console.error(account); + console.error('---- end of account dump ----'); + createAlert( + 'warning', + 'Account Creation Error
Logs were dumped in your Browser Console
Please submit these privately to PIVX Labs Developers!' + ); + throw new Error( + 'addAccount was called with with an invalid account' + ); + } + + // Create an empty DB Account + const cDBAccount = new Account(); + + // We'll overlay the `account` keys atop the `DB Account` keys: + // Note: Since the Account constructor defaults all properties to type-safe defaults, we can already assume `cDBAccount` is safe. + // Note: Since `addAccount` could be called with *anything*, we must apply the same type-safety on it's input. + for (const strKey of Object.keys(cDBAccount)) { + // Ensure the Type is correct for the Key against the Account class + if (!isSameType(account[strKey], cDBAccount[strKey])) { + console.error( + 'DB: addAccount() key "' + + strKey + + '" does NOT match the correct class type, likely data mismatch, please report!' + ); + continue; + } + + // Overlay the 'new' keys on top of the DB keys + cDBAccount[strKey] = account[strKey]; + } + + const store = this.#db + .transaction('accounts', 'readwrite') + .objectStore('accounts'); + + // Check this account isn't already added (by pubkey once multi-account) + if (await store.get('account')) + throw new Error( + 'DB: Ran addAccount() when account already exists!' + ); + + // When the account system is going to be added, the key is gonna be the publicKey + await store.put(cDBAccount, 'account'); + } + + /** + * Update specified keys for an Account in the DB. + * + * This will also apply new Account keys from MPW updates automatically, and check high-level type safety. + * + * --- + * + * To allow "deleting/clearing/resetting" keys, for example, when removing Proposals or Contacts, toggle `allowDeletion`. + * + * **Do NOT toggle unless otherwise necessary**, to avoid overwriting keys from code errors or misuse. + * @param {Account} account - The Account to update, with new data inside + * @param {boolean} allowDeletion - Allow setting keys to an "empty" state (`""`, `[]`, `{}`) + */ + async updateAccount(account, allowDeletion = false) { + // Critical: Ensure the input is an Account instance + if (!(account instanceof Account)) { + console.error( + '---- updateAccount() called with invalid input, input dump below ----' + ); + console.error(account); + console.error('---- end of account dump ----'); + createAlert( + 'warning', + 'DB Update Error
Your wallet is safe, logs were dumped in your Browser Console
Please submit these privately to PIVX Labs Developers!' + ); + throw new Error( + 'addAccount was called with with an invalid account' + ); + } + + // Fetch the DB account + const cDBAccount = await this.getAccount(); + + // If none exists; we should throw an error, as there's no reason for MPW to call `updateAccount` before an account was added using `addAccount` + // Note: This is mainly to force "good standards" in which we don't lazily use `updateAccount` to create NEW accounts. + if (!cDBAccount) { + console.error( + '---- updateAccount() called without an account existing, input dump below ----' + ); + console.error(account); + console.error('---- end of input dump ----'); + createAlert( + 'warning', + 'DB Update Error
Logs were dumped in your Browser Console
Please submit these privately to PIVX Labs Developers!' + ); + throw new Error( + "updateAccount was called, but the account doesn't exist" + ); + } + + // We'll overlay the `account` keys atop the `DB Account` keys: + // Note: Since `getAccount` already checks type-safety, we can already assume `cDBAccount` is safe. + // Note: Since `updateAccount` could be called with *anything*, we must apply the same type-safety on it's input. + for (const strKey of Object.keys(cDBAccount)) { + // Ensure the Type is correct for the Key against the Account class + if (!isSameType(account[strKey], cDBAccount[strKey])) { + console.error( + 'DB: updateAccount() key "' + + strKey + + '" does NOT match the correct class type, likely data mismatch, please report!' + ); + continue; + } + + // Ensure the 'updated' key (which may not exist) is NOT a default or EMPTY value + // Note: this can be overriden manually when erasing data such as Contacts, Local Proposals, etc. + if (!allowDeletion && isEmpty(account[strKey])) continue; + + // Overlay the 'new' keys on top of the DB keys + cDBAccount[strKey] = account[strKey]; + } + + const store = this.#db + .transaction('accounts', 'readwrite') + .objectStore('accounts'); + // When the account system is going to be added, the key is gonna be the publicKey + await store.put(cDBAccount, 'account'); + } + + /** + * Removes an account from the database + * @param {Object} o + * @param {String} o.publicKey - Public key associated to the account. + */ + async removeAccount({ publicKey: _publicKey }) { + const store = this.#db + .transaction('accounts', 'readwrite') + .objectStore('accounts'); + // When the account system is going to be added, the key is gonna be the publicKey + await store.delete('account'); + } + + /** + * Gets an account from the database. + * + * This also will apply new keys from MPW updates automatically, and check high-level type safety. + * @returns {Promise} + */ + async getAccount() { + const store = this.#db + .transaction('accounts', 'readonly') + .objectStore('accounts'); + const cDBAccount = await store.get('account'); + + // If there's no DB Account, we'll return null early + if (!cDBAccount) return null; + + // We'll generate an Account Class for up-to-date keys, then layer the 'new' type-checked properties on it one-by-one + const cAccount = new Account(); + for (const strKey of Object.keys(cAccount)) { + // If the key is missing: this is fine, `cAccount` will auto-fill it with the default blank Account Class type and value + if (!Object.prototype.hasOwnProperty.call(cDBAccount, strKey)) + continue; + + // Ensure the Type is correct for the Key against the Account class (with instanceof to also check Class validity) + if (!isSameType(cDBAccount[strKey], cAccount[strKey])) { + console.error( + 'DB: getAccount() key "' + + strKey + + '" does NOT match the correct class type, likely bad data saved, please report!' + ); + continue; + } + + // Overlay the 'DB' keys on top of the Class Instance keys + cAccount[strKey] = cDBAccount[strKey]; + } + + // Return the Account Class + return cAccount; + } + + /** + * @returns {Promise} the masternode stored in the db + */ + async getMasternode(_masterKey) { + const store = this.#db + .transaction('masternodes', 'readonly') + .objectStore('masternodes'); + const mnData = await store.get('masternode'); + return !mnData ? null : new Masternode(mnData); + } + + /** + * @returns {Promise>} all Promo Codes stored in the db + */ + async getAllPromos() { + const store = this.#db + .transaction('promos', 'readonly') + .objectStore('promos'); + // Convert all promo objects in to their Class and return them as a new array + return (await store.getAll()).map((promo) => new PromoWallet(promo)); + } + + /** + * Get all txs from the database + * @returns {Promise} + */ + async getTxs() { + const store = this.#db + .transaction('txs', 'readonly') + .objectStore('txs'); + return (await store.getAll()) + .map((tx) => { + const vin = tx.vin.map( + (x) => + new CTxIn({ + outpoint: new COutpoint({ + txid: x.outpoint.txid, + n: x.outpoint.n, + }), + scriptSig: x.scriptSig, + sequence: x.sequence, + }) + ); + const vout = tx.vout.map( + (x) => + new CTxOut({ + script: x.script, + value: x.value, + }) + ); + return new Transaction({ + version: tx.version, + blockHeight: tx.blockHeight, + blockTime: tx.blockTime, + vin: vin, + vout: vout, + valueBalance: tx.valueBalance, + shieldSpend: tx.shieldSpend, + shieldOutput: tx.shieldOutput, + bindingSig: tx.bindingSig, + lockTime: tx.lockTime, + }); + }) + .sort((a, b) => a.blockHeight - b.blockHeight); + } + /** + * Remove all txs from db + */ + async removeAllTxs() { + const store = this.#db + .transaction('txs', 'readwrite') + .objectStore('txs'); + await store.clear(); + } + + /** + * @returns {Promise} + */ + async getSettings() { + const store = this.#db + .transaction('settings', 'readonly') + .objectStore('settings'); + return new Settings(await store.get('settings')); + } + + /** + * @param {Settings} settings - settings to use + * @returns {Promise} + */ + async setSettings(settings) { + const oldSettings = await this.getSettings(); + const store = this.#db + .transaction('settings', 'readwrite') + .objectStore('settings'); + await store.put( + { + ...oldSettings, + ...settings, + }, + 'settings' + ); + } + + /** + * Migrates from local storage + */ + async #migrateLocalStorage() { + if (localStorage.length === 0) return; + const settings = new Settings({ + analytics: localStorage.analytics, + explorer: localStorage.explorer, + node: localStorage.node, + translation: localStorage.translation, + displayCurrency: localStorage.displayCurrency, + }); + await this.setSettings(settings); + + if (localStorage.masternode) { + try { + const masternode = JSON.parse(localStorage.masternode); + await this.addMasternode(masternode); + } catch (e) { + console.error(e); + createAlert('warning', ALERTS.MIGRATION_MASTERNODE_FAILURE); + } + } + + if (localStorage.encwif || localStorage.publicKey) { + try { + const localProposals = JSON.parse( + localStorage.localProposals || '[]' + ); + + // Update and format the old Account data + const cAccount = new Account({ + publicKey: localStorage.publicKey, + encWif: localStorage.encwif, + localProposals: localProposals, + }); + + // Migrate the old Account data to the new DB + await this.addAccount(cAccount); + } catch (e) { + console.error(e); + createAlert('warning', ALERTS.MIGRATION_ACCOUNT_FAILURE); + if (localStorage.encwif) { + await confirmPopup({ + title: translation.MIGRATION_ACCOUNT_FAILURE_TITLE, + html: `${ + translation.MIGRATION_ACCOUNT_FAILURE_HTML + } ${sanitizeHTML( + localStorage.encwif + )} `, + }); + } + } + } + } + + static async create(name) { + let migrate = false; + const database = new Database({ db: null }); + const db = await openDB(`MPW-${name}`, Database.version, { + upgrade: (db, oldVersion) => { + console.log( + 'DB: Upgrading from ' + + oldVersion + + ' to ' + + Database.version + ); + if (oldVersion == 0) { + db.createObjectStore('masternodes'); + db.createObjectStore('accounts'); + db.createObjectStore('settings'); + migrate = true; + } + + // The introduction of PIVXPromos (safely added during { + // Another instance is waiting to upgrade, and we're preventing it + // Close the database and refresh the page + // (This would only happen if the user opened another window after MPW got an update) + database.close(); + alert('New update received!'); + window.location.reload(); + }, + }); + database.#db = db; + if (migrate) { + await database.#migrateLocalStorage(); + } + return database; + } + + /** + * Map name->instnace + * @type{Map} + */ + static #instances = new Map(); + + /** + * @return {Promise} the default database instance + */ + static async getInstance() { + const name = cChainParams.current.name; + const instance = this.#instances.get(name); + if (!instance || !instance.#db) { + this.#instances.set(name, await Database.create(name)); + } + + return this.#instances.get(name); + } +} diff --git a/scripts/debug.js b/scripts/debug.js new file mode 100644 index 000000000..86422d5db --- /dev/null +++ b/scripts/debug.js @@ -0,0 +1,93 @@ +import { debug } from './settings.js'; +import debugParams from '../debug_config.json'; + +/** + * Execute a given function if we are in debug mode + * @param {Function} func - a function we want to execute + * @param {...any} args - The arguments to pass to the function + */ +function debugEval(func, ...args) { + if (debug) { + func(...args); + } +} + +/** + * Execute a given function if we are in debug mode and the provided topic is in the filtered list + * @param {Function} func - a function we want to execute + * @param {DebugTopic} topic - the topic of the debug + * @param {...any} args - The arguments to pass to the function + */ +function debugEvalFilter(func, topic, ...args) { + if (topic.value & enabledDebug) { + debugEval(func, ...args); + } +} +/** + * call console.log if we are in debug mode + * @param {DebugTopic} topic - topic of the debug + * @param args - arguments to print + */ +export function debugLog(topic, ...args) { + debugEvalFilter(console.log, topic, topic.name, ...args); +} + +/** + * call console.warn if we are in debug mode + * @param {DebugTopic} topic - topic of the debug + * @param args - arguments to print + */ +export function debugWarn(topic, ...args) { + debugEvalFilter(console.warn, topic, topic.name, ...args); +} + +/** + * call console.error if we are in debug mode + * @param {DebugTopic} topic - topic of the debug + * @param args - arguments to print + */ +export function debugError(topic, ...args) { + debugEvalFilter(console.error, topic, topic.name, ...args); +} + +/** + * Start a timer if we are in debug + * @param {DebugTopic} topic - topic of the debug + * @param {String} label - the label of the timer + */ +export function debugTimerStart(topic, label) { + debugEvalFilter(console.time, topic, topic.name + ' ' + label); +} + +/** + * End a timer if we are in debug mode + * @param {DebugTopic} topic - topic of the debug + * @param {String} label - the label of the timer + */ +export function debugTimerEnd(topic, label) { + debugEvalFilter(console.timeEnd, topic, topic.name + ' ' + label); +} + +class DebugTopic { + constructor(name, value) { + this.name = name; + this.value = value; + } +} +export const DebugTopics = { + NET: new DebugTopic('[NET]', 1 << 0), + GLOBAL: new DebugTopic('[GLOBAL]', 1 << 1), + WALLET: new DebugTopic('[WALLET]', 1 << 2), + MEMPOOL: new DebugTopic('[MEMPOOL]', 1 << 3), +}; + +let enabledDebug = 0; + +export async function loadDebug() { + for (const topic in DebugTopics) { + const index_str = DebugTopics[topic].name.toLowerCase().slice(1, -1); + if (debugParams[index_str]) { + enabledDebug += DebugTopics[topic].value; + } + } +} diff --git a/scripts/encoding.js b/scripts/encoding.js new file mode 100644 index 000000000..44afdaee0 --- /dev/null +++ b/scripts/encoding.js @@ -0,0 +1,307 @@ +import { sha256 } from '@noble/hashes/sha256'; +import { hexToBytes, bytesToHex, dSHA256 } from './utils.js'; +import * as nobleSecp256k1 from '@noble/secp256k1'; +import { ripemd160 } from '@noble/hashes/ripemd160'; +import { cChainParams, PRIVKEY_BYTE_LENGTH } from './chain_params.js'; +import { + pubKeyHashNetworkLen, + writeToUint8, + getSafeRand, + pubPrebaseLen, +} from './utils.js'; + +import bs58 from 'bs58'; +import { bech32 } from 'bech32'; + +/** + * Compress an uncompressed Public Key in byte form + * @param {Array | Uint8Array} pubKeyBytes - The uncompressed public key bytes + * @returns {Array} The compressed public key bytes + */ +export function compressPublicKey(pubKeyBytes) { + if (pubKeyBytes.length != 65) + throw new Error('Attempting to compress an invalid uncompressed key'); + const x = pubKeyBytes.slice(1, 33); + const y = pubKeyBytes.slice(33); + + // Compressed key is [key_parity + 2, x] + return [y[31] % 2 === 0 ? 2 : 3, ...x]; +} + +/** + * Network encode 32 bytes for a private key + * @param {Uint8Array} pkBytes - 32 Bytes + * @returns {Uint8Array} - The network-encoded Private Key bytes + */ +export function encodePrivkeyBytes(pkBytes) { + const pkNetBytes = new Uint8Array(pkBytes.length + 2); + pkNetBytes[0] = cChainParams.current.SECRET_KEY; // Private key prefix (1 byte) + writeToUint8(pkNetBytes, pkBytes, 1); // Private key bytes (32 bytes) + pkNetBytes[pkNetBytes.length - 1] = 1; // Leading digit (1 byte) + return pkNetBytes; +} + +/** + * Generate a new private key OR encode an existing private key from raw bytes + * @param {Uint8Array} pkBytesToEncode - Bytes to encode as a coin private key + * @returns {PrivateKey} - The private key + */ +export function generateOrEncodePrivkey(pkBytesToEncode) { + // Private Key Generation + const pkBytes = pkBytesToEncode || getSafeRand(); + + // Network Encoding + const pkNetBytes = encodePrivkeyBytes(pkBytes); + + // Double SHA-256 hash + const shaObj = dSHA256(pkNetBytes); + + // WIF Checksum + const checksum = shaObj.slice(0, 4); + const keyWithChecksum = new Uint8Array(34 + checksum.length); + writeToUint8(keyWithChecksum, pkNetBytes, 0); + writeToUint8(keyWithChecksum, checksum, 34); + + // Return both the raw bytes and the WIF format + return { pkBytes, strWIF: bs58.encode(keyWithChecksum) }; +} + +/** + * Derive a Secp256k1 network-encoded public key (coin address) from raw private or public key bytes + * @param {Object} options - The object to deconstruct + * @param {String} [options.publicKey] - The hex encoded public key. Can be both compressed or uncompressed + * @param {Array | Uint8Array} [options.pkBytes] - An array of bytes containing the private key + * @param {"ENCODED" | "UNCOMPRESSED_HEX" | "COMPRESSED_HEX"} options.output - Output + * @return {String} the public key with the specified encoding + */ +export function deriveAddress({ pkBytes, publicKey, output = 'ENCODED' }) { + if (!pkBytes && !publicKey) return null; + const compress = output !== 'UNCOMPRESSED_HEX'; + // Public Key Derivation + let pubKeyBytes = publicKey + ? hexToBytes(publicKey) + : nobleSecp256k1.getPublicKey(pkBytes, compress); + + if (output === 'UNCOMPRESSED_HEX') { + if (pubKeyBytes.length !== 65) { + // It's actually possible, but it's probably not something that we'll need + throw new Error("Can't uncompress an already compressed key"); + } + return bytesToHex(pubKeyBytes); + } + + if (pubKeyBytes.length === 65) { + pubKeyBytes = compressPublicKey(pubKeyBytes); + } + + if (pubKeyBytes.length != 33) { + throw new Error('Invalid public key'); + } + + if (output === 'COMPRESSED_HEX') { + return bytesToHex(pubKeyBytes); + } + + // First pubkey SHA-256 hash + const pubKeyHashing = sha256(new Uint8Array(pubKeyBytes)); + + // RIPEMD160 hash + const pubKeyHashRipemd160 = ripemd160(pubKeyHashing); + + // Network Encoding + const pubKeyHashNetwork = new Uint8Array(pubKeyHashNetworkLen); + pubKeyHashNetwork[0] = cChainParams.current.PUBKEY_ADDRESS; + writeToUint8(pubKeyHashNetwork, pubKeyHashRipemd160, 1); + + // Double SHA-256 hash + const pubKeyHashingSF = dSHA256(pubKeyHashNetwork); + + // Checksum + const checksumPubKey = pubKeyHashingSF.slice(0, 4); + + // Public key pre-base58 + const pubKeyPreBase = new Uint8Array(pubPrebaseLen); + writeToUint8(pubKeyPreBase, pubKeyHashNetwork, 0); + writeToUint8(pubKeyPreBase, checksumPubKey, pubKeyHashNetworkLen); + + // Encode as Base58 human-readable network address + return bs58.encode(pubKeyPreBase); +} + +/** + * Verify the integrity of an address + * @param {string} strPubkey - A base58 encoded public key + * @param {number[]} [expectedKey] - The key type to check, defaults to current chain's `PUBKEY_ADDRESS` + * @return {boolean|Error} `true` if valid, `false` if invalid + */ +export function verifyPubkey( + strPubkey, + expectedKey = cChainParams.current.PUBKEY_ADDRESS +) { + // Decode base58 and verify basic integrity + try { + const bDecoded = bs58.decode(strPubkey); + if (bDecoded.length !== 25 && bDecoded.length !== 27) return false; + if (!expectedKey.every((n, i) => n === bDecoded[i])) return false; + + // Sha256d hash the pubkey payload + const bDoubleSHA = dSHA256(bDecoded.slice(0, bDecoded.length - 4)); + + // Verify payload integrity via checksum + const bChecksum = bDoubleSHA.slice(0, 4); + const bChecksumPayload = bDecoded.slice(bDecoded.length - 4); + if (!bChecksum.every((byte, i) => byte === bChecksumPayload[i])) + return false; + + // All is valid! (base58 format, payload and checksum integrity) + return true; + } catch (e) { + // Definitely not valid (likely a bad base58 string) + return false; + } +} + +/** + * Verify that a string has been encoded correctly in bech32 + * @param {String} encodedString - the encoded string we want to validate + * @param {String} expectedPrefix - expected bech32 encoding prefix + */ +export function verifyBech32(address, expectedPrefix) { + try { + const { prefix } = bech32.decode(address); + if (prefix !== expectedPrefix) { + return false; + } + return true; + } catch (e) { + return false; + } +} + +// Verify the integrity of a WIF private key, optionally parsing and returning the key payload +export function verifyWIF( + strWIF = '', + fParseBytes = false, + skipVerification = false +) { + // Convert from Base58 + const bWIF = bs58.decode(strWIF); + + if (!skipVerification) { + // Verify the byte length + if (bWIF.byteLength !== PRIVKEY_BYTE_LENGTH) { + throw Error( + 'Private key length (' + + bWIF.byteLength + + ') is invalid, should be ' + + PRIVKEY_BYTE_LENGTH + + '!' + ); + } + + // Verify the network byte + if (bWIF[0] !== cChainParams.current.SECRET_KEY) { + // Find the network it's trying to use, if any + const cNetwork = Object.keys(cChainParams) + .filter((strNet) => strNet !== 'current') + .map((strNet) => cChainParams[strNet]) + .find((cNet) => cNet.SECRET_KEY === bWIF[0]); + // Give a specific alert based on the byte properties + throw Error( + cNetwork + ? 'This private key is for ' + + (cNetwork.isTestnet ? 'Testnet' : 'Mainnet') + + ', wrong network!' + : 'This private key belongs to another coin, or is corrupted.' + ); + } + + // Perform SHA256d hash of the WIF bytes + const shaHash = dSHA256(bWIF.slice(0, 34)); + + // Verify checksum (comparison by String since JS hates comparing object-like primitives) + const bChecksumWIF = bWIF.slice(bWIF.byteLength - 4); + const bChecksum = shaHash.slice(0, 4); + if (bChecksumWIF.join('') !== bChecksum.join('')) { + throw Error( + 'Private key checksum is invalid, key may be modified, mis-typed, or corrupt.' + ); + } + } + + return fParseBytes ? Uint8Array.from(bWIF.slice(1, 33)) : true; +} + +// A convenient alias to verifyWIF that returns the raw byte payload +export function parseWIF(strWIF, skipVerification = false) { + return verifyWIF(strWIF, true, skipVerification); +} + +/** + * Encodes a number to bytes in little endian order + * @param {BigInt} num - number to encode + * @param {number} bytes - Number of bytes to encode to + * @returns {number[]} - Encoded numbers + */ +export function numToBytes(num, bytes = 8) { + if (bytes == 0) { + return []; + } else if (num == -1n) { + return hexToBytes('ffffffffffffffff'); + } else { + return [Number(num % 256n)].concat(numToBytes(num / 256n, bytes - 1)); + } +} + +/** + * @param {BigInt} num - Number to encode + * @returns {number[]} Number to bytes + */ +export function numToByteArray(num) { + if (num <= 256n) { + return [Number(num)]; + } else { + return [Number(num % 256n)].concat(numToByteArray(num / 256n)); + } +} + +/** + * @param {number[]} bytes + * @returns {BigInt} converted number from bytes + */ +export function bytesToNum(bytes) { + if (bytes.length == 0) return 0n; + else return BigInt(bytes[0]) + 256n * bytesToNum(bytes.slice(1)); +} + +/** + * @param {BigInt} num - Number to encode + * @returns {number[]} Number encoded in bitcoin varint format + */ +export function numToVarInt(num) { + if (num < 253n) { + return [Number(num)]; + } else if (num < 65536n) { + return [253].concat(numToBytes(num, 2)); + } else if (num < 4294967296n) { + return [254].concat(numToBytes(num, 4)); + } else { + return [255].concat(numToBytes(num, 8)); + } +} + +/** + * @returns {{num: BigInt, readBytes: number}} + */ +export function varIntToNum(bytes) { + if (bytes[0] < 253) + return { + num: BigInt(bytes[0]), + readBytes: 1, + }; + const readBytes = 1 + 2 ** (bytes[0] - 252); + return { + num: bytesToNum(bytes.slice(1, readBytes)), + readBytes, + }; +} diff --git a/scripts/event_bus.js b/scripts/event_bus.js new file mode 100644 index 000000000..c02c698d2 --- /dev/null +++ b/scripts/event_bus.js @@ -0,0 +1,11 @@ +import { EventEmitter } from 'events'; + +const eventEmitter = new EventEmitter(); + +/** + * Get the application wide event emitter. + * @returns {EventEmitter} + */ +export function getEventEmitter() { + return eventEmitter; +} diff --git a/scripts/flipdown.js b/scripts/flipdown.js new file mode 100644 index 000000000..05f39dd3a --- /dev/null +++ b/scripts/flipdown.js @@ -0,0 +1,422 @@ +import { translation } from './i18n.js'; + +/** + * @name FlipDown + * @description Flip styled countdown clock + * @author Peter Butcher (PButcher) + * @param {number} uts - Time to count down to as unix timestamp + * @param {string} el - DOM element to attach FlipDown to + * @param {object} opt - Optional configuration settings + **/ +export class FlipDown { + constructor(uts, el = 'flipdown', opt = {}) { + // If uts is not specified + if (typeof uts !== 'number') { + throw new Error( + `FlipDown: Constructor expected unix timestamp, got ${typeof uts} instead.` + ); + } + + // If opt is specified, but not el + if (typeof el === 'object') { + opt = el; + el = 'flipdown'; + } + + // FlipDown version + this.version = '0.3.2'; + + // Initialised? + this.initialised = false; + + // Time at instantiation in seconds + this.now = this._getTime(); + + // UTS to count down to + this.epoch = uts; + + // UTS passed to FlipDown is in the past + this.countdownEnded = false; + + // User defined callback for countdown end + this.hasEndedCallback = null; + + // FlipDown DOM element + this.element = document.getElementById(el); + + // Rotor DOM elements + this.rotors = []; + this.rotorLeafFront = []; + this.rotorLeafRear = []; + this.rotorTops = []; + this.rotorBottoms = []; + + // Interval + this.countdown = null; + + // Number of days remaining + this.daysRemaining = 0; + + // Clock values as numbers + this.clockValues = {}; + + // Clock values as strings + this.clockStrings = {}; + + // Clock values as array + this.clockValuesAsString = []; + this.prevClockValuesAsString = []; + + // Parse options + this.opts = this._parseOptions(opt); + + // Set options + this._setOptions(); + } + + /** + * @name start + * @description Start the countdown + * @author PButcher + **/ + start() { + // Initialise the clock + if (!this.initialised) this._init(); + + // Set up the countdown interval + this.countdown = setInterval(this._tick.bind(this), 1000); + + // Chainable + return this; + } + + /** + * @name ifEnded + * @description Call a function once the countdown ends + * @author PButcher + * @param {function} cb - Callback + **/ + ifEnded(cb) { + this.hasEndedCallback = function () { + cb(); + this.hasEndedCallback = null; + }; + + // Chainable + return this; + } + + /** + * @name _getTime + * @description Get the time in seconds (unix timestamp) + * @author PButcher + **/ + _getTime() { + return new Date().getTime() / 1000; + } + + /** + * @name _hasCountdownEnded + * @description Has the countdown ended? + * @author PButcher + **/ + _hasCountdownEnded() { + // Countdown has ended + if (this.epoch - this.now < 0) { + this.countdownEnded = true; + + // Fire the ifEnded callback once if it was set + if (this.hasEndedCallback != null) { + // Call ifEnded callback + this.hasEndedCallback(); + + // Remove the callback + this.hasEndedCallback = null; + } + + return true; + + // Countdown has not ended + } else { + this.countdownEnded = false; + return false; + } + } + + /** + * @name _parseOptions + * @description Parse any passed options + * @param {object} opt - Optional configuration settings + * @author PButcher + **/ + _parseOptions(opt) { + let headings = [ + translation.timeDays, + translation.timeHours, + translation.timeMinutes, + translation.timeSeconds, + ]; + if (opt.headings && opt.headings.length === 4) { + headings = opt.headings; + } + return { + // Theme + theme: Object.prototype.hasOwnProperty.call(opt, 'theme') + ? opt.theme + : 'dark', + headings, + }; + } + + /** + * @name _setOptions + * @description Set optional configuration settings + * @author PButcher + **/ + _setOptions() { + // Apply theme + this.element.classList.add(`flipdown__theme-${this.opts.theme}`); + } + + /** + * @name _init + * @description Initialise the countdown + * @author PButcher + **/ + _init() { + this.initialised = true; + + // Check whether countdown has ended and calculate how many digits the day counter needs + if (this._hasCountdownEnded()) { + this.daysremaining = 0; + } else { + this.daysremaining = Math.floor( + (this.epoch - this.now) / 86400 + ).toString().length; + } + var dayRotorCount = this.daysremaining <= 2 ? 2 : this.daysremaining; + + // Create and store rotors + for (var i = 0; i < dayRotorCount + 6; i++) { + this.rotors.push(this._createRotor(0)); + } + + // Create day rotor group + var dayRotors = []; + for (var j = 0; j < dayRotorCount; j++) { + dayRotors.push(this.rotors[j]); + } + this.element.appendChild(this._createRotorGroup(dayRotors, 0)); + + // Create other rotor groups + var count = dayRotorCount; + for (var k = 0; k < 3; k++) { + var otherRotors = []; + for (var l = 0; l < 2; l++) { + otherRotors.push(this.rotors[count]); + count++; + } + this.element.appendChild( + this._createRotorGroup(otherRotors, k + 1) + ); + } + + // Store and convert rotor nodelists to arrays + this.rotorLeafFront = Array.prototype.slice.call( + this.element.getElementsByClassName('rotor-leaf-front') + ); + this.rotorLeafRear = Array.prototype.slice.call( + this.element.getElementsByClassName('rotor-leaf-rear') + ); + this.rotorTop = Array.prototype.slice.call( + this.element.getElementsByClassName('rotor-top') + ); + this.rotorBottom = Array.prototype.slice.call( + this.element.getElementsByClassName('rotor-bottom') + ); + + // Set initial values; + this._tick(); + this._updateClockValues(true); + + return this; + } + + /** + * @name _createRotorGroup + * @description Add rotors to the DOM + * @author PButcher + * @param {array} rotors - A set of rotors + **/ + _createRotorGroup(rotors, rotorIndex) { + var rotorGroup = document.createElement('div'); + rotorGroup.className = 'rotor-group'; + var dayRotorGroupHeading = document.createElement('div'); + dayRotorGroupHeading.className = 'rotor-group-heading'; + dayRotorGroupHeading.setAttribute( + 'data-before', + this.opts.headings[rotorIndex] + ); + rotorGroup.appendChild(dayRotorGroupHeading); + appendChildren(rotorGroup, rotors); + return rotorGroup; + } + + /** + * @name _createRotor + * @description Create a rotor DOM element + * @author PButcher + * @param {number} v - Initial rotor value + **/ + _createRotor(v = 0) { + var rotor = document.createElement('div'); + var rotorLeaf = document.createElement('div'); + var rotorLeafRear = document.createElement('figure'); + var rotorLeafFront = document.createElement('figure'); + var rotorTop = document.createElement('div'); + var rotorBottom = document.createElement('div'); + rotor.className = 'rotor'; + rotorLeaf.className = 'rotor-leaf'; + rotorLeafRear.className = 'rotor-leaf-rear'; + rotorLeafFront.className = 'rotor-leaf-front'; + rotorTop.className = 'rotor-top'; + rotorBottom.className = 'rotor-bottom'; + rotorLeafRear.textContent = v; + rotorTop.textContent = v; + rotorBottom.textContent = v; + appendChildren(rotor, [rotorLeaf, rotorTop, rotorBottom]); + appendChildren(rotorLeaf, [rotorLeafRear, rotorLeafFront]); + return rotor; + } + + /** + * @name _tick + * @description Calculate current tick + * @author PButcher + **/ + _tick() { + // Get time now + this.now = this._getTime(); + + // Between now and epoch + var diff = this.epoch - this.now <= 0 ? 0 : this.epoch - this.now; + + // Days remaining + this.clockValues.d = Math.floor(diff / 86400); + diff -= this.clockValues.d * 86400; + + // Hours remaining + this.clockValues.h = Math.floor(diff / 3600); + diff -= this.clockValues.h * 3600; + + // Minutes remaining + this.clockValues.m = Math.floor(diff / 60); + diff -= this.clockValues.m * 60; + + // Seconds remaining + this.clockValues.s = Math.floor(diff); + + // Update clock values + this._updateClockValues(); + + // Has the countdown ended? + this._hasCountdownEnded(); + } + + /** + * @name _updateClockValues + * @description Update the clock face values + * @author PButcher + * @param {boolean} init - True if calling for initialisation + **/ + _updateClockValues(init = false) { + // Build clock value strings + this.clockStrings.d = pad(this.clockValues.d, 2); + this.clockStrings.h = pad(this.clockValues.h, 2); + this.clockStrings.m = pad(this.clockValues.m, 2); + this.clockStrings.s = pad(this.clockValues.s, 2); + + // Concat clock value strings + this.clockValuesAsString = ( + this.clockStrings.d + + this.clockStrings.h + + this.clockStrings.m + + this.clockStrings.s + ).split(''); + + // Update rotor values + // Note that the faces which are initially visible are: + // - rotorLeafFront (top half of current rotor) + // - rotorBottom (bottom half of current rotor) + // Note that the faces which are initially hidden are: + // - rotorTop (top half of next rotor) + // - rotorLeafRear (bottom half of next rotor) + this.rotorLeafFront.forEach((el, i) => { + el.textContent = this.prevClockValuesAsString[i]; + }); + + this.rotorBottom.forEach((el, i) => { + el.textContent = this.prevClockValuesAsString[i]; + }); + + function rotorTopFlip() { + this.rotorTop.forEach((el, i) => { + if (el.textContent != this.clockValuesAsString[i]) { + el.textContent = this.clockValuesAsString[i]; + } + }); + } + + function rotorLeafRearFlip() { + this.rotorLeafRear.forEach((el, i) => { + if (el.textContent != this.clockValuesAsString[i]) { + el.textContent = this.clockValuesAsString[i]; + el.parentElement.classList.add('flipped'); + var flip = setInterval( + function () { + el.parentElement.classList.remove('flipped'); + clearInterval(flip); + }.bind(this), + 500 + ); + } + }); + } + + // Init + if (!init) { + setTimeout(rotorTopFlip.bind(this), 500); + setTimeout(rotorLeafRearFlip.bind(this), 500); + } else { + rotorTopFlip.call(this); + rotorLeafRearFlip.call(this); + } + + // Save a copy of clock values for next tick + this.prevClockValuesAsString = this.clockValuesAsString; + } +} + +/** + * @name pad + * @description Prefix a number with zeroes + * @author PButcher + * @param {string} n - Number to pad + * @param {number} len - Desired length of number + **/ +function pad(n, len) { + n = n.toString(); + return n.length < len ? pad('0' + n, len) : n; +} + +/** + * @name appendChildren + * @description Add multiple children to an element + * @author PButcher + * @param {object} parent - Parent + **/ +function appendChildren(parent, children) { + children.forEach((el) => { + parent.appendChild(el); + }); +} diff --git a/scripts/global.js b/scripts/global.js new file mode 100644 index 000000000..95534e5cd --- /dev/null +++ b/scripts/global.js @@ -0,0 +1,1822 @@ +import { COutpoint } from './transaction.js'; +import { TransactionBuilder } from './transaction_builder.js'; +import Masternode from './masternode.js'; +import { ALERTS, tr, start as i18nStart, translation } from './i18n.js'; +import { wallet, hasEncryptedWallet, Wallet } from './wallet.js'; +import { getNetwork } from './network.js'; +import { + start as settingsStart, + cExplorer, + strCurrency, + fAdvancedMode, +} from './settings.js'; +import { createAndSendTransaction } from './legacy.js'; +import { createAlert, confirmPopup, sanitizeHTML } from './misc.js'; +import { cChainParams, COIN } from './chain_params.js'; +import { sleep } from './utils.js'; +import { registerWorker } from './native.js'; +import { Address6 } from 'ip-address'; +import { getEventEmitter } from './event_bus.js'; +import { Database } from './database.js'; +import { checkForUpgrades } from './changelog.js'; +import { FlipDown } from './flipdown.js'; +import { createApp } from 'vue'; +import Dashboard from './dashboard/Dashboard.vue'; +import { loadDebug, debugLog, DebugTopics } from './debug.js'; +import Stake from './stake/Stake.vue'; +import { createPinia } from 'pinia'; +import { cOracle } from './prices.js'; + +import pIconCopy from '../assets/icons/icon-copy.svg'; +import pIconCheck from '../assets/icons/icon-check.svg'; + +/** A flag showing if base MPW is fully loaded or not */ +export let fIsLoaded = false; + +/** A getter for the flag showing if base MPW is fully loaded or not */ +export function isLoaded() { + return fIsLoaded; +} + +// Block count +export let blockCount = 0; + +export let doms = {}; + +const pinia = createPinia(); + +export const dashboard = createApp(Dashboard).use(pinia).mount('#DashboardTab'); +createApp(Stake).use(pinia).mount('#StakingTab'); + +export async function start() { + doms = { + domNavbarToggler: document.getElementById('navbarToggler'), + domDashboard: document.getElementById('dashboard'), + domStakeTab: document.getElementById('stakeTab'), + domModalQR: document.getElementById('ModalQR'), + domModalQrLabel: document.getElementById('ModalQRLabel'), + domModalQrReceiveTypeBtn: document.getElementById( + 'ModalQRReceiveTypeBtn' + ), + domModalQRReader: document.getElementById('qrReaderModal'), + domQrReaderStream: document.getElementById('qrReaderStream'), + domCloseQrReaderBtn: document.getElementById('closeQrReader'), + domModalWalletBreakdown: document.getElementById( + 'walletBreakdownModal' + ), + domWalletBreakdownCanvas: document.getElementById( + 'walletBreakdownCanvas' + ), + domWalletBreakdownLegend: document.getElementById( + 'walletBreakdownLegend' + ), + domGenHardwareWallet: document.getElementById('generateHardwareWallet'), + //GOVERNANCE ELEMENTS + domGovTab: document.getElementById('governanceTab'), + domGovProposalsTable: document.getElementById('proposalsTable'), + domGovProposalsTableBody: document.getElementById('proposalsTableBody'), + domTotalGovernanceBudget: document.getElementById( + 'totalGovernanceBudget' + ), + domTotalGovernanceBudgetValue: document.getElementById( + 'totalGovernanceBudgetValue' + ), + domAllocatedGovernanceBudget: document.getElementById( + 'allocatedGovernanceBudget' + ), + domAllocatedGovernanceBudgetValue: document.getElementById( + 'allocatedGovernanceBudgetValue' + ), + domAllocatedGovernanceBudget2: document.getElementById( + 'allocatedGovernanceBudget2' + ), + domAllocatedGovernanceBudgetValue2: document.getElementById( + 'allocatedGovernanceBudgetValue2' + ), + domGovProposalsContestedTable: document.getElementById( + 'proposalsContestedTable' + ), + domGovProposalsContestedTableBody: document.getElementById( + 'proposalsContestedTableBody' + ), + //MASTERNODE ELEMENTS + domCreateMasternode: document.getElementById('createMasternode'), + domControlMasternode: document.getElementById('controlMasternode'), + domAccessMasternode: document.getElementById('accessMasternode'), + domMnAccessMasternodeText: document.getElementById( + 'accessMasternodeText' + ), + domMnCreateType: document.getElementById('mnCreateType'), + domMnTextErrors: document.getElementById('mnTextErrors'), + domMnIP: document.getElementById('mnIP'), + domMnTxId: document.getElementById('mnTxId'), + domMnPrivateKey: document.getElementById('mnPrivateKey'), + domMnDashboard: document.getElementById('mnDashboard'), + domMnProtocol: document.getElementById('mnProtocol'), + domMnStatus: document.getElementById('mnStatus'), + domMnNetType: document.getElementById('mnNetType'), + domMnNetIP: document.getElementById('mnNetIP'), + domMnLastSeen: document.getElementById('mnLastSeen'), + + domEncryptWalletLabel: document.getElementById('encryptWalletLabel'), + domEncryptPasswordCurrent: document.getElementById( + 'changePassword-current' + ), + domEncryptPasswordFirst: document.getElementById('newPassword'), + domEncryptPasswordSecond: document.getElementById('newPasswordRetype'), + domAnalyticsDescriptor: document.getElementById('analyticsDescriptor'), + domRedeemTitle: document.getElementById('redeemCodeModalTitle'), + domRedeemCodeUse: document.getElementById('redeemCodeUse'), + domRedeemCodeCreate: document.getElementById('redeemCodeCreate'), + domRedeemCodeGiftIconBox: document.getElementById( + 'redeemCodeGiftIconBox' + ), + domRedeemCodeGiftIcon: document.getElementById('redeemCodeGiftIcon'), + domRedeemCodeETA: document.getElementById('redeemCodeETA'), + domRedeemCodeProgress: document.getElementById('redeemCodeProgress'), + domRedeemCodeInputBox: document.getElementById('redeemCodeInputBox'), + domRedeemCodeInput: document.getElementById('redeemCodeInput'), + domRedeemCodeConfirmBtn: document.getElementById( + 'redeemCodeModalConfirmButton' + ), + domRedeemCodeModeRedeemBtn: document.getElementById( + 'redeemCodeModeRedeem' + ), + domRedeemCodeModeCreateBtn: document.getElementById( + 'redeemCodeModeCreate' + ), + domRedeemCodeCreateInput: document.getElementById( + 'redeemCodeCreateInput' + ), + domRedeemCodeCreateAmountInput: document.getElementById( + 'redeemCodeCreateAmountInput' + ), + domRedeemCodeCreatePendingList: document.getElementById( + 'redeemCodeCreatePendingList' + ), + domPromoTable: document.getElementById('promo-table'), + domContactsTable: document.getElementById('contactsList'), + domConfirmModalDialog: document.getElementById('confirmModalDialog'), + domConfirmModalMain: document.getElementById('confirmModalMain'), + domConfirmModalHeader: document.getElementById('confirmModalHeader'), + domConfirmModalTitle: document.getElementById('confirmModalTitle'), + domConfirmModalContent: document.getElementById('confirmModalContent'), + domConfirmModalButtons: document.getElementById('confirmModalButtons'), + domConfirmModalConfirmButton: document.getElementById( + 'confirmModalConfirmButton' + ), + domConfirmModalCancelButton: document.getElementById( + 'confirmModalCancelButton' + ), + + masternodeLegacyAccessText: + 'Access the masternode linked to this address
Note: the masternode MUST have been already created (however it can be online or offline)
If you want to create a new masternode access with a HD wallet', + masternodeHDAccessText: + "Access your masternodes if you have any! If you don't you can create one", + // Aggregate menu screens and links for faster switching + arrDomScreens: document.getElementsByClassName('tabcontent'), + arrDomScreenLinks: document.getElementsByClassName('tablinks'), + // Alert DOM element + domAlertPos: document.getElementsByClassName('alertPositioning')[0], + domNetwork: document.getElementById('Network'), + domChangePasswordContainer: document.getElementById( + 'changePassword-container' + ), + domLogOutContainer: document.getElementById('logOut-container'), + domDebug: document.getElementById('Debug'), + domTestnet: document.getElementById('Testnet'), + domCurrencySelect: document.getElementById('currency'), + domExplorerSelect: document.getElementById('explorer'), + domNodeSelect: document.getElementById('node'), + domAutoSwitchToggle: document.getElementById('autoSwitchToggler'), + domTranslationSelect: document.getElementById('translation'), + domDisplayDecimalsSlider: document.getElementById('displayDecimals'), + domDisplayDecimalsSliderDisplay: + document.getElementById('sliderDisplay'), + domBlackBack: document.getElementById('blackBack'), + domWalletSettings: document.getElementById('settingsWallet'), + domDisplaySettings: document.getElementById('settingsDisplay'), + domWalletSettingsBtn: document.getElementById('settingsWalletBtn'), + domDisplaySettingsBtn: document.getElementById('settingsDisplayBtn'), + domVersion: document.getElementById('version'), + domFlipdown: document.getElementById('flipdown'), + domTestnetToggler: document.getElementById('testnetToggler'), + domAdvancedModeToggler: document.getElementById('advancedModeToggler'), + domAutoLockModeToggler: document.getElementById('autoLockModeToggler'), + domRedeemCameraBtn: document.getElementById('redeemCameraBtn'), + }; + + // Set Copyright year on footer + document.getElementById('copyrightYear').innerHTML = + new Date().getFullYear(); + + await i18nStart(); + await loadImages(); + + // Enable all Bootstrap Tooltips + $(function () { + $('#displayDecimals').tooltip({ + template: + '', + }); + $('[data-toggle="tooltip"]').tooltip(); + }); + + // Set decimal slider event + const sliderElement = document.getElementById('displayDecimals'); + function handleDecimalSlider() { + setTimeout(() => { + try { + if (window.innerWidth > 991) { + const sliderHalf = Math.round( + document + .getElementById('displayDecimals') + .getBoundingClientRect().width / 2 + ); + const sliderBegin = -sliderHalf + 28; + const stepVal = (sliderHalf * 2) / 8 - 6.45; + const sliderValue = parseInt(sliderElement.value) + 1; + + document.querySelector('.sliderStyle').style.left = `${ + sliderBegin - stepVal + stepVal * sliderValue + }px`; + document.querySelector('.tooltip-inner').innerHTML = + sliderValue - 1; + } + } catch (e) {} + }, 10); + } + sliderElement.addEventListener('input', handleDecimalSlider); + sliderElement.addEventListener('mouseover', handleDecimalSlider); + + // Load debug + await loadDebug(); + + // Register native app service + registerWorker(); + await settingsStart(); + + subscribeToNetworkEvents(); + // Make sure we know the correct number of blocks + await refreshChainData(); + // Load the price manager + cOracle.load(); + + // If allowed by settings: submit a simple 'hit' (app load) to Labs Analytics + getNetwork().submitAnalytics('hit'); + setInterval(() => { + // Refresh blockchain data + refreshChainData(); + + // Fetch the PIVX prices + refreshPriceDisplay(); + }, 15000); + + // Check for recent upgrades, display the changelog + checkForUpgrades(); + + // Update the Encryption UI (If the user has a wallet, then it changes to "Change Password" rather than "Encrypt Wallet") + getEventEmitter().on('wallet-import', async () => { + updateLogOutButton(); + }); + fIsLoaded = true; + + // If we haven't already (due to having no wallet, etc), display the Dashboard + doms.domDashboard.click(); +} + +async function refreshPriceDisplay() { + await cOracle.getPrice(strCurrency); + getEventEmitter().emit('balance-update'); +} + +function subscribeToNetworkEvents() { + getEventEmitter().on('network-toggle', (value) => { + doms.domNetwork.innerHTML = + ''; + }); + + getEventEmitter().on('new-block', (block) => { + debugLog(DebugTopics.GLOBAL, `New block detected! ${block}`); + + // If it's open: update the Governance Dashboard + if (doms.domGovTab.classList.contains('active')) { + updateGovernanceTab(); + } + }); + + getEventEmitter().on('transaction-sent', (success, result) => { + if (success) { + createAlert( + 'success', + `${ALERTS.TX_SENT}
${sanitizeHTML(result)}`, + result ? 1250 + result.length * 50 : 3000 + ); + // If allowed by settings: submit a simple 'tx' ping to Labs Analytics + getNetwork().submitAnalytics('transaction'); + } else { + console.error('Error sending transaction:'); + console.error(result); + createAlert('warning', ALERTS.TX_FAILED, 2500); + } + }); +} + +// WALLET STATE DATA + +let isTestnetLastState = cChainParams.current.isTestnet; + +/** + * @type {FlipDown | null} + */ +let governanceFlipdown = null; + +/** + * Open a UI 'tab' menu, and close all other tabs, intended for frontend use + * @param {Event} evt - The click event target + * @param {string} tabName - The name of the tab to load + */ +export function openTab(evt, tabName) { + // Only allow switching tabs if MPw is loaded + if (!isLoaded()) return; + + // Hide all screens and deactivate link highlights + for (const domScreen of doms.arrDomScreens) + domScreen.style.display = 'none'; + for (const domLink of doms.arrDomScreenLinks) + domLink.classList.remove('active'); + + // Show and activate the given screen + document.getElementById(tabName).style.display = 'block'; + evt.currentTarget.classList.add('active'); + + // Close the navbar if it's not already closed + if (!doms.domNavbarToggler.className.includes('collapsed')) + doms.domNavbarToggler.click(); + + if (tabName === 'Governance') { + updateGovernanceTab(); + } else if (tabName === 'Masternode') { + updateMasternodeTab(); + } +} + +/** + * Return locale settings best for displaying the user-selected currency + * @param {Number} nAmount - The amount in Currency + */ +export function optimiseCurrencyLocale(nAmount) { + // Allow manipulating the value, if necessary + let nValue = nAmount; + + // Find the best fitting native-locale + const cLocale = Intl.supportedValuesOf('currency').includes( + strCurrency.toUpperCase() + ) + ? { + style: 'currency', + currency: strCurrency, + currencyDisplay: 'narrowSymbol', + } + : { maximumFractionDigits: 8, minimumFractionDigits: 8 }; + + // Catch display edge-cases; like Satoshis having decimals. + switch (strCurrency) { + case 'sats': + nValue = Math.round(nValue); + cLocale.maximumFractionDigits = 0; + cLocale.minimumFractionDigits = 0; + } + + // Return display-optimised Value and Locale pair. + return { nValue, cLocale }; +} + +/** + * Open the Explorer in a new tab for the current wallet, or a specific address + * @param {string?} strAddress - Optional address to open, if void, the master key is used + */ +export async function openExplorer(strAddress = '') { + if (wallet.isLoaded() && wallet.isHD() && !strAddress) { + const xpub = wallet.getXPub(); + window.open(cExplorer.url + '/xpub/' + xpub, '_blank'); + } else { + const address = strAddress || wallet.getAddress(); + window.open(cExplorer.url + '/address/' + address, '_blank'); + } +} + +async function loadImages() { + const images = [ + ['mpw-main-logo', import('../assets/new_logo.png')], + ['plus-icon', import('../assets/icons/icon-plus.svg')], + ['plus-icon2', import('../assets/icons/icon-plus.svg')], + ['plus-icon3', import('../assets/icons/icon-plus.svg')], + ['del-wallet-icon', import('../assets/icons/icon-bin.svg')], + ['change-pwd-icon', import('../assets/icons/icon-key.svg')], + ]; + + const promises = images.map(([id, path]) => + (async () => { + try { + if ((await path).default.includes(' { + caller.innerHTML = pIconCopy; + caller.style.cursor = 'pointer'; + }, 1000); +} + +export async function govVote(hash, voteCode) { + if ( + (await confirmPopup({ + title: ALERTS.CONFIRM_POPUP_VOTE, + html: ALERTS.CONFIRM_POPUP_VOTE_HTML, + })) == true + ) { + const database = await Database.getInstance(); + const cMasternode = await database.getMasternode(); + if (cMasternode) { + if ((await cMasternode.getStatus()) !== 'ENABLED') { + createAlert('warning', ALERTS.MN_NOT_ENABLED, 6000); + return; + } + const result = await cMasternode.vote(hash.toString(), voteCode); //1 yes 2 no + if (result.includes('Voted successfully')) { + //good vote + cMasternode.storeVote(hash.toString(), voteCode); + await updateGovernanceTab(); + createAlert('success', ALERTS.VOTE_SUBMITTED, 6000); + } else if (result.includes('Error voting :')) { + //If you already voted return an alert + createAlert('warning', ALERTS.VOTED_ALREADY, 6000); + } else if (result.includes('Failure to verify signature.')) { + //wrong masternode private key + createAlert('warning', ALERTS.VOTE_SIG_BAD, 6000); + } else { + //this could be everything + console.error(result); + createAlert('warning', ALERTS.INTERNAL_ERROR, 6000); + } + } else { + createAlert('warning', ALERTS.MN_ACCESS_BEFORE_VOTE, 6000); + } + } +} + +/** + * Start a Masternode via a signed network broadcast + * @param {boolean} fRestart - Whether this is a Restart or a first Start + */ +export async function startMasternode(fRestart = false) { + const database = await Database.getInstance(); + const cMasternode = await database.getMasternode(wallet.getMasterKey()); + if (cMasternode) { + if ( + wallet.isViewOnly() && + !(await restoreWallet(translation.walletUnlockMNStart)) + ) + return; + if (await cMasternode.start()) { + const strMsg = fRestart ? ALERTS.MN_RESTARTED : ALERTS.MN_STARTED; + createAlert('success', strMsg, 4000); + } else { + const strMsg = fRestart + ? ALERTS.MN_RESTART_FAILED + : ALERTS.MN_START_FAILED; + createAlert('warning', strMsg, 4000); + } + } +} + +export async function destroyMasternode() { + const database = await Database.getInstance(); + const cMasternode = await database.getMasternode(wallet.getMasterKey()); + if (cMasternode) { + // Unlock the coin and update the balance + wallet.unlockCoin( + new COutpoint({ + txid: cMasternode.collateralTxId, + n: cMasternode.outidx, + }) + ); + + database.removeMasternode(wallet.getMasterKey()); + createAlert('success', ALERTS.MN_DESTROYED, 5000); + updateMasternodeTab(); + } +} + +/** + * Takes an ip address and adds the port. + * If it's a tor address, ip.onion:port will be used (e.g. expyuzz4wqqyqhjn.onion:12345) + * If it's an IPv4 address, ip:port will be used, (e.g. 127.0.0.1:12345) + * If it's an IPv6 address, [ip]:port will be used, (e.g. [::1]:12345) + * @param {String} ip - Ip address with or without port + * @returns {String} + */ +function parseIpAddress(ip) { + // IPv4 or tor without port + if (ip.match(/\d+\.\d+\.\d+\.\d+/) || ip.match(/\w+\.onion/)) { + return `${ip}:${cChainParams.current.MASTERNODE_PORT}`; + } + + // IPv4 or tor with port + if (ip.match(/\d+\.\d+\.\d+\.\d+:\d+/) || ip.match(/\w+\.onion:\d+/)) { + return ip; + } + + // IPv6 without port + if (Address6.isValid(ip)) { + return `[${ip}]:${cChainParams.current.MASTERNODE_PORT}`; + } + + const groups = /\[(.*)\]:\d+/.exec(ip); + if (groups !== null && groups.length > 1) { + // IPv6 with port + if (Address6.isValid(groups[1])) { + return ip; + } + } + + // If we haven't returned yet, the address was invalid. + return null; +} + +export async function importMasternode() { + const mnPrivKey = doms.domMnPrivateKey.value; + const address = parseIpAddress(doms.domMnIP.value); + if (!address) { + createAlert('warning', ALERTS.MN_BAD_IP, 5000); + return; + } + if (!mnPrivKey) { + createAlert('warning', ALERTS.MN_BAD_PRIVKEY, 5000); + return; + } + + let collateralTxId; + let outidx; + let collateralPrivKeyPath; + doms.domMnIP.value = ''; + doms.domMnPrivateKey.value = ''; + + if (!wallet.isHD()) { + // Find the first UTXO matching the expected collateral size + const cCollaUTXO = wallet.getMasternodeUTXOs()[0]; + const balance = wallet.balance; + // If there's no valid UTXO, exit with a contextual message + if (!cCollaUTXO) { + if (balance < cChainParams.current.collateralInSats) { + // Not enough balance to create an MN UTXO + const amount = + (cChainParams.current.collateralInSats - balance) / COIN; + const ticker = cChainParams.current.TICKER; + createAlert( + 'warning', + tr(ALERTS.MN_NOT_ENOUGH_COLLAT, [ + { amount: amount }, + { ticker: ticker }, + ]), + 10000 + ); + } else { + // Balance is capable of a masternode, just needs to be created + // TODO: this UX flow is weird, is it even possible? perhaps we can re-design this entire function accordingly + const amount = cChainParams.current.collateralInSats / COIN; + const ticker = cChainParams.current.TICKER; + createAlert( + 'warning', + tr(ALERTS.MN_ENOUGH_BUT_NO_COLLAT, [ + { amount }, + { ticker }, + ]), + 10000 + ); + } + return; + } + + collateralTxId = cCollaUTXO.outpoint.txid; + outidx = cCollaUTXO.outpoint.n; + collateralPrivKeyPath = 'legacy'; + } else { + const path = doms.domMnTxId.value; + let masterUtxo; + const utxos = wallet.getMasternodeUTXOs(); + for (const u of utxos) { + if ( + u.value === cChainParams.current.collateralInSats && + wallet.getPath(u.script) === path + ) { + masterUtxo = u; + } + } + + // sanity check: + if (masterUtxo.value !== cChainParams.current.collateralInSats) { + return createAlert('warning', ALERTS.MN_COLLAT_NOT_SUITABLE, 10000); + } + collateralTxId = masterUtxo.outpoint.txid; + outidx = masterUtxo.outpoint.n; + collateralPrivKeyPath = path; + } + doms.domMnTxId.value = ''; + + const cMasternode = new Masternode({ + walletPrivateKeyPath: collateralPrivKeyPath, + mnPrivateKey: mnPrivKey, + collateralTxId: collateralTxId, + outidx: outidx, + addr: address, + }); + + await refreshMasternodeData(cMasternode, true); + await updateMasternodeTab(); +} + +export async function accessOrImportWallet() { + // Hide and Reset the Vanity address input + + // Show Import button, hide access button + doms.domImportWallet.style.display = 'block'; + setTimeout(() => { + doms.domPrivKey.style.opacity = '1'; + }, 100); + doms.domAccessWalletBtn.style.display = 'none'; + + // If we have a local wallet, display the decryption prompt + // This is no longer being used, as the user will be put in view-only + // mode when logging in, however if the user locked the wallet before + // #52 there would be no way to recover the public key without getting + // The password from the user + if (await hasEncryptedWallet()) { + doms.domPrivKey.placeholder = translation.encryptPasswordFirst; + doms.domImportWalletText.innerText = translation.unlockWallet; + doms.domPrivKey.focus(); + } +} + +/** Update the log out button to match the current wallet state */ +export function updateLogOutButton() { + doms.domLogOutContainer.style.display = wallet.isLoaded() + ? 'block' + : 'none'; +} + +/** + * Sweep an address to our own wallet, spending all it's UTXOs without change + * @param {Array} arrUTXOs - The UTXOs belonging to the address to sweep + * @param {import('./masterkey.js').LegacyMasterKey} sweepingMasterKey - The address to sweep from + * @param {number} nFixedFee - An optional fixed satoshi fee + * @returns {Promise} - TXID on success, false or error on failure + */ +export async function sweepAddress(arrUTXOs, sweepingMasterKey, nFixedFee) { + const txBuilder = TransactionBuilder.create().addUTXOs(arrUTXOs); + + const outputValue = txBuilder.valueIn - (nFixedFee || txBuilder.getFee()); + const [address] = wallet.getNewAddress(1); + const tx = txBuilder + .addOutput({ + address, + value: outputValue, + }) + .build(); + + // Sign using the given Master Key, then broadcast the sweep, returning the TXID (or a failure) + const sweepingWallet = new Wallet({ nAccount: 0 }); + sweepingWallet.setMasterKey({ mk: sweepingMasterKey }); + + await sweepingWallet.sign(tx); + return await getNetwork().sendTransaction(tx.serialize()); +} + +export function toggleDropDown(id) { + const domID = document.getElementById(id); + domID.style.display = domID.style.display === 'block' ? 'none' : 'block'; +} + +export function isMasternodeUTXO(cUTXO, cMasternode) { + if (cMasternode?.collateralTxId) { + const { collateralTxId, outidx } = cMasternode; + return collateralTxId === cUTXO.id && cUTXO.vout === outidx; + } else { + return false; + } +} + +/** + * Prompt the user in the GUI to unlock their wallet + * @param {string} strReason - An optional reason for the unlock + * @returns {Promise} - If the unlock was successful or rejected + */ +export async function restoreWallet(strReason = '') { + // TODO: This needs to be vueified quite a bit + // This will be done after #225 since it's already + // way bigger than I would have liked + return await dashboard.restoreWallet(strReason); +} + +/** A lock to prevent rendering the Governance Dashboard multiple times */ +let fRenderingGovernance = false; + +/** + * Fetch Governance data and re-render the Governance UI + */ +export async function updateGovernanceTab() { + if (fRenderingGovernance) return; + fRenderingGovernance = true; + + // Setup the Superblock countdown (if not already done), no need to block thread with await, either. + if (!isTestnetLastState == cChainParams.current.isTestnet) { + // Reset flipdown + governanceFlipdown = null; + doms.domFlipdown.innerHTML = ''; + } + + // Update governance counter when testnet/mainnet has been switched + if (!governanceFlipdown && blockCount > 0) { + Masternode.getNextSuperblock().then((nSuperblock) => { + // The estimated time to the superblock (using the block target and remaining blocks) + const nTimestamp = + Date.now() / 1000 + (nSuperblock - blockCount) * 60; + governanceFlipdown = new FlipDown(nTimestamp).start(); + }); + isTestnetLastState = cChainParams.current.isTestnet; + } + + // Fetch all proposals from the network + const arrProposals = await Masternode.getProposals({ + fAllowFinished: false, + }); + + /* Sort proposals into two categories + - Standard (Proposal is either new with <100 votes, or has a healthy vote count) + - Contested (When a proposal may be considered spam, malicious, or simply highly contestable) + */ + const arrStandard = arrProposals.filter( + (a) => a.Yeas + a.Nays < 100 || a.Ratio > 0.25 + ); + const arrContested = arrProposals.filter( + (a) => a.Yeas + a.Nays >= 100 && a.Ratio <= 0.25 + ); + + // Render Proposals + await Promise.all([ + renderProposals(arrStandard, false), + renderProposals(arrContested, true), + ]); + + // Remove lock + fRenderingGovernance = false; +} + +/** + * @typedef {Object} ProposalCache + * @property {number} nSubmissionHeight - The submission height of the proposal. + * @property {string} txid - The transaction ID of the proposal (string). + * @property {boolean} fFetching - Indicates whether the proposal is currently being fetched or not. + */ + +/** + * An array of Proposal Finalisation caches + * @type {Array} + */ +const arrProposalFinalisationCache = []; + +/** + * Asynchronously wait for a Proposal Tx to confirm, then cache the height. + * + * Do NOT await unless you want to lock the thread for a long time. + * @param {ProposalCache} cProposalCache - The proposal cache to wait for + * @returns {Promise} Returns `true` once the block height is cached + */ +async function waitForSubmissionBlockHeight(cProposalCache) { + let nHeight = null; + + // Wait in a permanent throttled loop until we successfully fetch the block + const cNet = getNetwork(); + while (true) { + // If a proposal is already fetching, then consequtive calls will be rejected + cProposalCache.fFetching = true; + + // Attempt to fetch the submission Tx (may not exist yet!) + let cTx = null; + try { + cTx = await cNet.getTxInfo(cProposalCache.txid); + } catch (_) {} + + if (!cTx || !cTx.blockHeight) { + // Didn't get the TX, throttle the thread by sleeping for a bit, then try again. + await sleep(30000); + } else { + nHeight = cTx.blockHeight; + break; + } + } + + // Update the proposal finalisation cache + cProposalCache.nSubmissionHeight = nHeight; + + return true; +} + +/** + * Create a Status String for a proposal's finalisation status + * @param {ProposalCache} cPropCache - The proposal cache to check + * @returns {string} The string status, for display purposes + */ +function getProposalFinalisationStatus(cPropCache) { + // Confirmations left until finalisation, by network consensus + const nConfsLeft = + cPropCache.nSubmissionHeight + + cChainParams.current.proposalFeeConfirmRequirement - + blockCount; + + if (cPropCache.nSubmissionHeight === 0 || blockCount === 0) { + return translation.proposalFinalisationConfirming; + } else if (nConfsLeft > 0) { + return ( + nConfsLeft + + ' block' + + (nConfsLeft === 1 ? '' : 's') + + ' ' + + translation.proposalFinalisationRemaining + ); + } else if (Math.abs(nConfsLeft) >= cChainParams.current.budgetCycleBlocks) { + return translation.proposalFinalisationExpired; + } else { + return translation.proposalFinalisationReady; + } +} + +/** + * + * @param {Object} cProposal - A local proposal to add to the cache tracker + * @returns {ProposalCache} - The finalisation cache object pointer of the local proposal + */ +function addProposalToFinalisationCache(cProposal) { + // If it exists, return the existing cache + /** @type ProposalCache */ + let cPropCache = arrProposalFinalisationCache.find( + (a) => a.txid === cProposal.mpw.txid + ); + if (cPropCache) return cPropCache; + + // Create a new cache + cPropCache = { + nSubmissionHeight: 0, + txid: cProposal.mpw.txid, + fFetching: false, + }; + arrProposalFinalisationCache.push(cPropCache); + + // Return the object 'pointer' in the array for further updating + return cPropCache; +} + +/** + * Render Governance proposal objects to a given Proposal category + * @param {Array} arrProposals - The proposals to render + * @param {boolean} fContested - The proposal category + */ +async function renderProposals(arrProposals, fContested) { + // Set the total budget + doms.domTotalGovernanceBudget.innerText = ( + cChainParams.current.maxPayment / COIN + ).toLocaleString('en-gb'); + + // Update total budget in user's currency + const nPrice = cOracle.getCachedPrice(strCurrency); + const nCurrencyValue = (cChainParams.current.maxPayment / COIN) * nPrice; + const { nValue, cLocale } = optimiseCurrencyLocale(nCurrencyValue); + doms.domTotalGovernanceBudgetValue.innerHTML = + nValue.toLocaleString('en-gb', cLocale) + + ' ' + + strCurrency.toUpperCase() + + ''; + + // Select the table based on the proposal category + const domTable = fContested + ? doms.domGovProposalsContestedTableBody + : doms.domGovProposalsTableBody; + + // Render the proposals in the relevent table + const database = await Database.getInstance(); + const cMasternode = await database.getMasternode(); + + if (!fContested) { + const localProposals = + (await database.getAccount())?.localProposals?.map((p) => { + return { + Name: p.name, + URL: p.url, + PaymentAddress: p.address, + MonthlyPayment: p.monthlyPayment / COIN, + RemainingPaymentCount: p.nPayments, + TotalPayment: p.nPayments * (p.monthlyPayment / COIN), + Yeas: 0, + Nays: 0, + local: true, + Ratio: 0, + IsEstablished: false, + mpw: p, + }; + }) || []; + arrProposals = localProposals.concat(arrProposals); + } + arrProposals = await Promise.all( + arrProposals.map(async (p) => { + return { + YourVote: + cMasternode && p.Hash + ? await cMasternode.getVote(p.Name, p.Hash) + : null, + ...p, + }; + }) + ); + + // Fetch the Masternode count for proposal status calculations + const cMasternodes = await Masternode.getMasternodeCount(); + + let totalAllocatedAmount = 0; + + // Wipe the current table and start rendering proposals + let i = 0; + domTable.innerHTML = ''; + for (const cProposal of arrProposals) { + const domRow = domTable.insertRow(); + + const domStatus = domRow.insertCell(); + domStatus.classList.add('governStatusCol'); + if (!fContested) { + domStatus.setAttribute( + 'onclick', + `if(document.getElementById('governMob${i}').classList.contains('d-none')) { document.getElementById('governMob${i}').classList.remove('d-none'); } else { document.getElementById('governMob${i}').classList.add('d-none'); }` + ); + } else { + domStatus.setAttribute( + 'onclick', + `if(document.getElementById('governMobCon${i}').classList.contains('d-none')) { document.getElementById('governMobCon${i}').classList.remove('d-none'); } else { document.getElementById('governMobCon${i}').classList.add('d-none'); }` + ); + } + + // Add border radius to last row + if (arrProposals.length - 1 == i) { + domStatus.classList.add('bblr-7p'); + } + + // Net Yes calculation + const { Yeas, Nays } = cProposal; + const nNetYes = Yeas - Nays; + const nNetYesPercent = (nNetYes / cMasternodes.enabled) * 100; + + // Proposal Status calculation + const nRequiredVotes = cMasternodes.enabled / 10; + const nMonthlyPayment = parseInt(cProposal.MonthlyPayment); + + // Initial state is assumed to be "Not enough votes" + let strStatus = translation.proposalFailing; + let strFundingStatus = translation.proposalNotFunded; + let strColourClass = 'No'; + + // Proposal Status calculations + if (nNetYes < nRequiredVotes) { + // Scenario 1: Not enough votes, default scenario + } else if (!cProposal.IsEstablished) { + // Scenario 2: Enough votes, but not established + strFundingStatus = translation.proposalTooYoung; + } else if ( + nMonthlyPayment + totalAllocatedAmount > + cChainParams.current.maxPayment / COIN + ) { + // Scenario 3: Enough votes, and established, but over-allocating the budget + strStatus = translation.proposalPassing; + strFundingStatus = translation.proposalOverBudget; + strColourClass = 'OverAllocated'; + } else { + // Scenario 4: Enough votes, and established + strStatus = translation.proposalPassing; + strFundingStatus = translation.proposalFunded; + strColourClass = 'Yes'; + + // Allocate this with the budget + totalAllocatedAmount += nMonthlyPayment; + } + + // Funding Status and allocation calculations + if (cProposal.local) { + // Check the finalisation cache + const cPropCache = addProposalToFinalisationCache(cProposal); + if (!cPropCache.fFetching) { + waitForSubmissionBlockHeight(cPropCache).then( + updateGovernanceTab + ); + } + const strLocalStatus = getProposalFinalisationStatus(cPropCache); + const finalizeButton = document.createElement('button'); + finalizeButton.className = 'pivx-button-small '; + finalizeButton.innerHTML = ''; + + if ( + strLocalStatus === translation.proposalFinalisationReady || + strLocalStatus === translation.proposalFinalisationExpired + ) { + finalizeButton.addEventListener('click', async () => { + const result = await Masternode.finalizeProposal( + cProposal.mpw + ); + + const deleteProposal = async () => { + // Fetch Account + const account = await database.getAccount(); + + // Find index of Account local proposal to remove + const nProposalIndex = account.localProposals.findIndex( + (p) => p.txid === cProposal.mpw.txid + ); + + // If found, remove the proposal and update the account with the modified localProposals array + if (nProposalIndex > -1) { + // Remove our proposal from it + account.localProposals.splice(nProposalIndex, 1); + + // Update the DB + await database.updateAccount(account, true); + } + }; + + if (result.ok) { + deleteProposal(); + // Create a prompt showing the finalisation success, vote hash, and further details + confirmPopup({ + title: translation.PROPOSAL_FINALISED + ' 🚀', + html: `

${ + translation.popupProposalFinalisedNote + }

${ + translation.popupProposalVoteHash + }
${sanitizeHTML( + result.hash + )}

${ + translation.popupProposalFinalisedSignoff + } 👋

`, + hideConfirm: true, + }); + updateGovernanceTab(); + } else { + if (result.err === 'unconfirmed') { + createAlert( + 'warning', + ALERTS.PROPOSAL_UNCONFIRMED, + 5000 + ); + } else if (result.err === 'invalid') { + createAlert( + 'warning', + ALERTS.PROPOSAL_EXPIRED, + 5000 + ); + deleteProposal(); + updateGovernanceTab(); + } else { + createAlert( + 'warning', + ALERTS.PROPOSAL_FINALISE_FAIL + ); + } + } + }); + } else { + finalizeButton.style.opacity = 0.5; + finalizeButton.style.cursor = 'default'; + } + + domStatus.innerHTML = ` + + ${strLocalStatus}
+
+ + + `; + domStatus.appendChild(finalizeButton); + } else { + domStatus.innerHTML = ` + + ${strStatus}
+ (${strFundingStatus})
+
+ + ${nNetYesPercent.toFixed(1)}%
+ ${translation.proposalNetYes} +
+ + + `; + } + + // Name, Payment Address and URL hyperlink + const domNameAndURL = domRow.insertCell(); + domNameAndURL.style = 'vertical-align: middle;'; + + // IMPORTANT: Sanitise all of our HTML or a rogue server or malicious proposal could perform a cross-site scripting attack + domNameAndURL.innerHTML = `${sanitizeHTML( + cProposal.Name + )}
+ ${sanitizeHTML( + cProposal.PaymentAddress.slice(0, 10) + '...' + )}`; + + // Convert proposal amount to user's currency + const nProposalValue = nMonthlyPayment * nPrice; + const { nValue } = optimiseCurrencyLocale(nProposalValue); + const strProposalCurrency = nValue.toLocaleString('en-gb', cLocale); + + // Payment Schedule and Amounts + const domPayments = domRow.insertCell(); + domPayments.classList.add('for-desktop'); + domPayments.style = 'vertical-align: middle;'; + domPayments.innerHTML = `${sanitizeHTML( + nMonthlyPayment.toLocaleString('en-gb', ',', '.') + )} ${ + cChainParams.current.TICKER + }
+ ${strProposalCurrency} ${strCurrency.toUpperCase()}
+ + ${sanitizeHTML( + cProposal['RemainingPaymentCount'] + )} ${ + translation.proposalPaymentsRemaining + } ${sanitizeHTML( + parseInt(cProposal.TotalPayment).toLocaleString('en-gb', ',', '.') + )} ${cChainParams.current.TICKER} ${ + translation.proposalPaymentTotal + }`; + + // Vote Counts and Consensus Percentages + const domVoteCounters = domRow.insertCell(); + domVoteCounters.classList.add('for-desktop'); + domVoteCounters.style = 'vertical-align: middle;'; + + const nLocalPercent = cProposal.Ratio * 100; + domVoteCounters.innerHTML = `${parseFloat( + nLocalPercent + ).toLocaleString( + 'en-gb', + { minimumFractionDigits: 0, maximumFractionDigits: 1 }, + ',', + '.' + )}%
+
${sanitizeHTML( + Yeas + )}
/ +
${sanitizeHTML( + Nays + )}
+ `; + + // Voting Buttons for Masternode owners (MNOs) + let voteBtn; + if (cProposal.local) { + const domVoteBtns = domRow.insertCell(); + domVoteBtns.classList.add('for-desktop'); + domVoteBtns.style = 'vertical-align: middle;'; + voteBtn = ''; + } else { + let btnYesClass = 'pivx-button-small govYesBtnMob'; + let btnNoClass = + 'pivx-button-outline pivx-button-outline-small govNoBtnMob'; + if (cProposal.YourVote) { + if (cProposal.YourVote === 1) { + btnYesClass += ' pivx-button-big-yes-gov'; + } else { + btnNoClass += ' pivx-button-big-no-gov'; + } + } + + /* + +
+ */ + + const domVoteBtns = domRow.insertCell(); + domVoteBtns.style = + 'padding-top: 30px; vertical-align: middle; display: flex; justify-content: center; align-items: center;'; + const domNoBtn = document.createElement('div'); + domNoBtn.className = btnNoClass; + domNoBtn.style.width = 'fit-content'; + domNoBtn.innerHTML = `${translation.no}`; + domNoBtn.onclick = () => govVote(cProposal.Hash, 2); + + const domYesBtn = document.createElement('button'); + domYesBtn.className = btnYesClass; + domYesBtn.innerText = translation.yes; + domYesBtn.onclick = () => govVote(cProposal.Hash, 1); + + // Add border radius to last row + if (arrProposals.length - 1 == i) { + domVoteBtns.classList.add('bbrr-7p'); + } + + domVoteBtns.classList.add('for-desktop'); + domVoteBtns.appendChild(domNoBtn); + domVoteBtns.appendChild(domYesBtn); + + domNoBtn.setAttribute( + 'onclick', + `MPW.govVote('${cProposal.Hash}', 2)` + ); + domYesBtn.setAttribute( + 'onclick', + `MPW.govVote('${cProposal.Hash}', 1);` + ); + voteBtn = domNoBtn.outerHTML + domYesBtn.outerHTML; + } + + // Create extended row for mobile + const mobileDomRow = domTable.insertRow(); + const mobileExtended = mobileDomRow.insertCell(); + mobileExtended.style = 'vertical-align: middle;'; + if (!fContested) { + mobileExtended.id = `governMob${i}`; + } else { + mobileExtended.id = `governMobCon${i}`; + } + mobileExtended.colSpan = '2'; + mobileExtended.classList.add('text-left'); + mobileExtended.classList.add('d-none'); + mobileExtended.classList.add('for-mobile'); + mobileExtended.innerHTML = ` +
+
+
${translation.govTablePayment} +
+
+ ${sanitizeHTML( + nMonthlyPayment.toLocaleString('en-gb', ',', '.') + )} ${ + cChainParams.current.TICKER + } ${strProposalCurrency} + + ${sanitizeHTML( + cProposal['RemainingPaymentCount'] + )} ${translation.proposalPaymentsRemaining} ${sanitizeHTML( + parseInt(cProposal.TotalPayment).toLocaleString('en-gb', ',', '.') + )} ${cChainParams.current.TICKER} ${ + translation.proposalPaymentTotal + } +
+
+
+
+
+
${translation.govTableVotes} +
+
+ ${parseFloat(nLocalPercent).toLocaleString( + 'en-gb', + { minimumFractionDigits: 0, maximumFractionDigits: 1 }, + ',', + '.' + )}% +
${sanitizeHTML( + Yeas + )}
/ +
${sanitizeHTML( + Nays + )}
+
+
+
+
+
+
${translation.govTableVote} +
+
+ ${voteBtn} +
+
`; + + i++; + } + + // Show allocated budget + if (!fContested) { + const strAlloc = sanitizeHTML( + totalAllocatedAmount.toLocaleString('en-gb') + ); + doms.domAllocatedGovernanceBudget.innerHTML = strAlloc; + doms.domAllocatedGovernanceBudget2.innerHTML = strAlloc; + + // Update allocated budget in user's currency + const nCurrencyValue = totalAllocatedAmount * nPrice; + const { nValue } = optimiseCurrencyLocale(nCurrencyValue); + const strAllocCurrency = + nValue.toLocaleString('en-gb', cLocale) + + ' ' + + strCurrency.toUpperCase() + + ''; + doms.domAllocatedGovernanceBudgetValue.innerHTML = strAllocCurrency; + doms.domAllocatedGovernanceBudgetValue2.innerHTML = strAllocCurrency; + } +} + +export async function updateMasternodeTab() { + //TODO: IN A FUTURE ADD MULTI-MASTERNODE SUPPORT BY SAVING MNs with which you logged in the past. + // Ensure a wallet is loaded + doms.domMnTextErrors.innerHTML = ''; + doms.domAccessMasternode.style.display = 'none'; + doms.domCreateMasternode.style.display = 'none'; + doms.domMnDashboard.style.display = 'none'; + + if (!wallet.isLoaded()) { + doms.domMnTextErrors.innerHTML = + 'Please ' + + ((await hasEncryptedWallet()) ? 'unlock' : 'import') + + ' your COLLATERAL WALLET first.'; + return; + } + + if (!wallet.isSynced) { + doms.domMnTextErrors.innerHTML = + 'Your wallet is empty or still loading, re-open the tab in a few seconds!'; + return; + } + + const database = await Database.getInstance(); + + let cMasternode = await database.getMasternode(); + // If the collateral is missing (spent, or switched wallet) then remove the current MN + if (cMasternode) { + if ( + !wallet.isCoinLocked( + new COutpoint({ + txid: cMasternode.collateralTxId, + n: cMasternode.outidx, + }) + ) + ) { + database.removeMasternode(); + cMasternode = null; + } + } + + doms.domControlMasternode.style.display = cMasternode ? 'block' : 'none'; + + // first case: the wallet is not HD and it is not hardware, so in case the wallet has collateral the user can check its status and do simple stuff like voting + if (!wallet.isHD()) { + doms.domMnAccessMasternodeText.innerHTML = + doms.masternodeLegacyAccessText; + doms.domMnTxId.style.display = 'none'; + // Find the first UTXO matching the expected collateral size + const cCollaUTXO = wallet.getMasternodeUTXOs()[0]; + + const balance = wallet.balance; + if (cMasternode) { + await refreshMasternodeData(cMasternode); + doms.domMnDashboard.style.display = ''; + } else if (cCollaUTXO) { + doms.domMnTxId.style.display = 'none'; + doms.domAccessMasternode.style.display = 'block'; + } else if (balance < cChainParams.current.collateralInSats) { + // The user needs more funds + doms.domMnTextErrors.innerHTML = + 'You need ' + + (cChainParams.current.collateralInSats - balance) / COIN + + ' more ' + + cChainParams.current.TICKER + + ' to create a Masternode!'; + } else { + // The user has the funds, but not an exact collateral, prompt for them to create one + doms.domCreateMasternode.style.display = 'flex'; + doms.domMnTxId.style.display = 'none'; + doms.domMnTxId.innerHTML = ''; + } + } else { + doms.domMnTxId.style.display = 'none'; + doms.domMnTxId.innerHTML = ''; + doms.domMnAccessMasternodeText.innerHTML = doms.masternodeHDAccessText; + + // First UTXO for each address in HD + const mapCollateralPath = new Map(); + + // Aggregate all valid Masternode collaterals into a map of Path <--> Collateral + for (const cUTXO of wallet.getMasternodeUTXOs()) { + mapCollateralPath.set(wallet.getPath(cUTXO.script), cUTXO); + } + const fHasCollateral = mapCollateralPath.size > 0; + // If there's no loaded MN, but valid collaterals, display the configuration screen + if (!cMasternode && fHasCollateral) { + doms.domMnTxId.style.display = 'block'; + doms.domAccessMasternode.style.display = 'block'; + + for (const [key] of mapCollateralPath) { + const option = document.createElement('option'); + option.value = key; + option.innerText = wallet.getAddressFromPath(key); + doms.domMnTxId.appendChild(option); + } + } + + // If there's no collateral found, display the creation UI + if (!fHasCollateral && !cMasternode) + doms.domCreateMasternode.style.display = 'flex'; + + // If we a loaded Masternode, display the Dashboard + if (cMasternode) { + // Refresh the display + refreshMasternodeData(cMasternode); + doms.domMnDashboard.style.display = ''; + } + } +} + +async function refreshMasternodeData(cMasternode, fAlert = false) { + const cMasternodeData = await cMasternode.getFullData(); + + debugLog(DebugTopics.GLOBAL, ' ---- NEW MASTERNODE DATA (Debug Mode) ----'); + debugLog(DebugTopics.GLOBAL, cMasternodeData); + debugLog(DebugTopics.GLOBAL, '---- END MASTERNODE DATA (Debug Mode) ----'); + + // If we have MN data available, update the dashboard + if (cMasternodeData && cMasternodeData.status !== 'MISSING') { + doms.domMnTextErrors.innerHTML = ''; + doms.domMnProtocol.innerText = `(${sanitizeHTML( + cMasternodeData.version + )})`; + doms.domMnStatus.innerText = sanitizeHTML(cMasternodeData.status); + doms.domMnNetType.innerText = sanitizeHTML( + cMasternodeData.network.toUpperCase() + ); + doms.domMnNetIP.innerText = cMasternode.addr; + doms.domMnLastSeen.innerText = new Date( + cMasternodeData.lastseen * 1000 + ).toLocaleTimeString(); + } + + if (cMasternodeData.status === 'MISSING') { + doms.domMnTextErrors.innerHTML = + 'Masternode is currently OFFLINE'; + if ( + !wallet.isViewOnly() || + (await restoreWallet(translation.walletUnlockCreateMN)) + ) { + createAlert('warning', ALERTS.MN_OFFLINE_STARTING, 6000); + // try to start the masternode + const started = await cMasternode.start(); + if (started) { + doms.domMnTextErrors.innerHTML = ALERTS.MN_STARTED; + createAlert('success', ALERTS.MN_STARTED_ONLINE_SOON, 6000); + const database = await Database.getInstance(); + await database.addMasternode(cMasternode); + wallet.lockCoin( + new COutpoint({ + txid: cMasternode.collateralTxId, + n: cMasternode.outidx, + }) + ); + } else { + doms.domMnTextErrors.innerHTML = ALERTS.MN_START_FAILED; + createAlert('warning', ALERTS.MN_START_FAILED, 6000); + } + } + } else if ( + cMasternodeData.status === 'ENABLED' || + cMasternodeData.status === 'PRE_ENABLED' + ) { + if (fAlert) + createAlert( + 'success', + `${ALERTS.MN_STATUS_IS} ${sanitizeHTML( + cMasternodeData.status + )} `, + 6000 + ); + const database = await Database.getInstance(); + await database.addMasternode(cMasternode); + wallet.lockCoin( + new COutpoint({ + txid: cMasternode.collateralTxId, + n: cMasternode.outidx, + }) + ); + } else if (cMasternodeData.status === 'REMOVED') { + const state = cMasternodeData.status; + doms.domMnTextErrors.innerHTML = tr(ALERTS.MN_STATE, [ + { state: state }, + ]); + if (fAlert) + createAlert( + 'warning', + tr(ALERTS.MN_STATE, [{ state: state }]), + 6000 + ); + } else { + // connection problem + doms.domMnTextErrors.innerHTML = ALERTS.MN_CANT_CONNECT; + if (fAlert) createAlert('warning', ALERTS.MN_CANT_CONNECT, 6000); + } + + // Return the data in case the caller needs additional context + return cMasternodeData; +} + +export async function createProposal() { + // Must have a wallet + if (!wallet.isLoaded()) { + return createAlert('warning', ALERTS.PROPOSAL_IMPORT_FIRST, 4500); + } + // Wallet must be encrypted + if (!(await hasEncryptedWallet())) { + return createAlert( + 'warning', + tr(translation.popupProposalEncryptFirst, [ + { button: translation.secureYourWallet }, + ]), + 4500 + ); + } + // Wallet must be unlocked + if ( + wallet.isViewOnly() && + !(await restoreWallet(translation.walletUnlockProposal)) + ) { + return; + } + // Must have enough funds + if (wallet.balance * COIN < cChainParams.current.proposalFee) { + return createAlert('warning', ALERTS.PROPOSAL_NOT_ENOUGH_FUNDS, 4500); + } + + // Create the popup, wait for the user to confirm or cancel + const fConfirmed = await confirmPopup({ + title: `

${translation.popupCreateProposal}

+ ${ + translation.popupCreateProposalCost + } ${cChainParams.current.proposalFee / COIN} ${ + cChainParams.current.TICKER + }`, + html: `
+

Proposal name

+
+ +

URL

+
+ +

Duration in cycles

+
+ +

${ + cChainParams.current.TICKER + } per cycle

+ ${ + !fAdvancedMode ? '
' : '' + } + +

Proposal Address

+ +
`, + wideModal: true, + }); + + // If the user cancelled, then we return + if (!fConfirmed) return; + + const strTitle = document.getElementById('proposalTitle').value.trim(); + const strUrl = document.getElementById('proposalUrl').value.trim(); + const numCycles = parseInt( + document.getElementById('proposalCycles').value.trim() + ); + const numPayment = parseInt( + document.getElementById('proposalPayment').value.trim() + ); + + // If Advanced Mode is enabled and an address is given, use the provided address, otherwise, generate a new one + const strAddress = + document.getElementById('proposalAddress').value.trim() || + wallet.getNewAddress(1)[0]; + const nextSuperblock = await Masternode.getNextSuperblock(); + const proposal = { + name: strTitle, + url: strUrl, + nPayments: numCycles, + start: nextSuperblock, + address: strAddress, + monthlyPayment: numPayment * COIN, + }; + + const isValid = Masternode.isValidProposal(proposal); + if (!isValid.ok) { + createAlert( + 'warning', + `${ALERTS.PROPOSAL_INVALID_ERROR} ${isValid.err}`, + 7500 + ); + return; + } + + const hash = Masternode.createProposalHash(proposal); + const { ok, txid } = await createAndSendTransaction({ + address: hash, + amount: cChainParams.current.proposalFee, + isProposal: true, + }); + if (ok) { + proposal.txid = txid; + const database = await Database.getInstance(); + + // Fetch our Account, add the proposal to it + const account = await database.getAccount(); + account.localProposals.push(proposal); + + // Update the DB + await database.updateAccount(account); + createAlert('success', translation.PROPOSAL_CREATED, 10000); + updateGovernanceTab(); + } +} + +export async function refreshChainData() { + const cNet = getNetwork(); + // If in offline mode: don't sync ANY data or connect to the internet + if (!cNet.enabled) + return console.warn( + 'Offline mode active: For your security, the wallet will avoid ALL internet requests.' + ); + + // Fetch block count + const newBlockCount = await cNet.getBlockCount(); + if (newBlockCount !== blockCount) { + blockCount = newBlockCount; + getEventEmitter().emit('new-block', blockCount); + } +} + +// A safety mechanism enabled if the user attempts to leave without encrypting/saving their keys +export const beforeUnloadListener = (evt) => { + evt.preventDefault(); + // Disable Save your wallet warning on unload + createAlert('warning', ALERTS.SAVE_WALLET_PLEASE, 10000); + // Most browsers ignore this nowadays, but still, keep it 'just incase' + return (evt.returnValue = translation.BACKUP_OR_ENCRYPT_WALLET); +}; + +/** + * @typedef {Object} SettingsDOM - An object that contains the DOM elements for settings pages. + * @property {HTMLElement} btn - The button to switch to this setting type. + * @property {HTMLElement} section - The container for this setting type. + */ + +/** + * Returns a list of all pages and their DOM elements. + * + * This must be a function, since, the DOM elements are `undefined` until + * after the startup sequence. + * + * Types are inferred. + */ +function getSettingsPages() { + return { + /** @type {SettingsDOM} */ + wallet: { + btn: doms.domWalletSettingsBtn, + section: doms.domWalletSettings, + }, + /** @type {SettingsDOM} */ + display: { + btn: doms.domDisplaySettingsBtn, + section: doms.domDisplaySettings, + }, + }; +} + +/** + * Switch between screens in the settings menu + * @param {string} page - The name of the setting page to switch to + */ +export function switchSettings(page) { + const SETTINGS = getSettingsPages(); + const { btn, section } = SETTINGS[page]; + + Object.values(SETTINGS).forEach(({ section, btn }) => { + // Set the slider to the proper location + if (page == 'display') { + doms.domDisplayDecimalsSlider.oninput = function () { + doms.domDisplayDecimalsSliderDisplay.innerHTML = this.value; + //let val = ((((doms.domDisplayDecimalsSlider.offsetWidth - 24) / 9) ) * parseInt(this.value)); + + //doms.domDisplayDecimalsSliderDisplay.style.marginLeft = (val) + 'px'; + }; + + // Triggers the input event + setTimeout( + () => + doms.domDisplayDecimalsSlider.dispatchEvent( + new Event('input') + ), + 10 + ); + } + // Hide all settings sections + section.classList.add('d-none'); + // Make all buttons inactive + btn.classList.remove('active'); + }); + + // Show selected section and make its button active + section.classList.remove('d-none'); + btn.classList.add('active'); +} + +function errorHandler(e) { + const message = `${translation.unhandledException}
${sanitizeHTML( + e.message || e.reason + )}`; + try { + createAlert('warning', message); + } catch (_) { + // Something as gone wrong, so we fall back to the default alert + // This can happen on early errors for example + alert(message); + } +} + +// This code is ran in the vanity gen worker as well! +// In which case, window would be not defined. +// `if (window)` wouldn't work either because +// window is not defined as opposed to undefined +try { + window.addEventListener('error', errorHandler); + window.addEventListener('unhandledrejection', errorHandler); +} catch (_) {} diff --git a/scripts/historical_tx.js b/scripts/historical_tx.js new file mode 100644 index 000000000..92883d9ef --- /dev/null +++ b/scripts/historical_tx.js @@ -0,0 +1,44 @@ +/** + * A historical transaction + */ +export class HistoricalTx { + /** + * @param {HistoricalTxType} type - The type of transaction. + * @param {string} id - The transaction ID. + * @param {Array} receivers - The list of 'output addresses'. + * @param {boolean} shieldedOutputs - If this transaction contains Shield outputs. + * @param {number} time - The block time of the transaction. + * @param {number} blockHeight - The block height of the transaction. + * @param {number} amount - The amount transacted, in coins. + */ + constructor( + type, + id, + receivers, + shieldedOutputs, + time, + blockHeight, + amount + ) { + this.type = type; + this.id = id; + this.receivers = receivers; + this.shieldedOutputs = shieldedOutputs; + this.time = time; + this.blockHeight = blockHeight; + this.amount = amount; + } +} + +/** + * A historical transaction type. + * @enum {number} + */ +export const HistoricalTxType = { + UNKNOWN: 0, + STAKE: 1, + DELEGATION: 2, + UNDELEGATION: 3, + RECEIVED: 4, + SENT: 5, +}; diff --git a/scripts/i18n.js b/scripts/i18n.js new file mode 100644 index 000000000..960de8aef --- /dev/null +++ b/scripts/i18n.js @@ -0,0 +1,211 @@ +import template from '../locale/template/translation.toml'; +import { Database } from './database.js'; +import { fillAnalyticSelect, setTranslation } from './settings.js'; +import { wallet } from './wallet.js'; +import { cReceiveType, guiToggleReceiveType } from './contacts-book.js'; +import { reactive } from 'vue'; +import { negotiateLanguages } from '@fluent/langneg'; + +/** + * @type {translation_template} + */ +export let ALERTS = {}; + +/** + * @type {translation_template} + */ +export const translation = reactive({}); + +const defaultLang = 'en'; + +/** + * @param {string} langName + * @example getParentlanguage('es-ES') === 'es' // true + * @example getParentLanguage('es') === defaultLang // true + * @returns the 'parent' language of a langcode + */ +export function getParentLanguage(langName) { + const strParentCode = langName.includes('-') + ? langName.split('-')[0] + : defaultLang; + // Ensure the code exists + return strParentCode; +} + +/** + * @param {string} code + * @returns {Promise} + */ +async function getLanguage(code) { + try { + return (await import(`../locale/${code}/translation.toml`)).default; + } catch (e) { + return template; + } +} + +async function setTranslationKey(key, langName) { + const lang = await getLanguage(langName); + + if (key === 'ALERTS') { + await setAlertKey(langName); + return; + } + if (lang[key]) { + translation[key] = lang[key]; + } else { + if (langName === defaultLang) { + // If the default language doens't have a string, then it has never been translated + translation[key] = ''; + return; + } + // If there's an empty or missing key, use the parent language + await setTranslationKey(key, getParentLanguage(langName)); + } +} + +/** + * Set the alert key for a given langName + * @param {String} langName - language name + */ +async function setAlertKey(langName) { + const lang = await getLanguage(langName); + translation['ALERTS'] = lang['ALERTS']; + for (const subKey in template['ALERTS']) { + setAlertSubKey(subKey, langName); + } +} + +/** + * Set a given subkey for ALERTS key for a given langName + * @param {String} langName - language name + * @param {String} subKey - ALERT subkey that we want to set + */ +async function setAlertSubKey(subKey, langName) { + const lang = await getLanguage(langName); + const item = lang['ALERTS'][subKey]; + if (item) { + translation['ALERTS'][subKey] = item; + } else { + if (langName === defaultLang) { + //Should not happen but just in case + translation['ALERTS'][subKey] = ''; + return; + } + await setAlertSubKey(subKey, getParentLanguage(langName)); + } +} + +/** + * Takes the language name and sets the translation settings based on the language file + * @param {string} langName + */ +export async function switchTranslation(langName) { + if (langName === 'auto' || !langName) { + langName = negotiateLanguages( + window.navigator.languages, + arrActiveLangs.slice(1).map((l) => l.code), + { + defaultLocale: defaultLang, + } + )[0]; + } + + if (arrActiveLangs.find((lang) => lang.code === langName)) { + // Load every 'active' key of the language, otherwise, we'll default the key to the EN file + for (const strKey of Object.keys(template)) { + await setTranslationKey(strKey, langName); + } + + // Translate static`data-i18n` tags + translateStaticHTML(translation); + + // Translate any dynamic elements necessary + ALERTS = translation['ALERTS']; + fillAnalyticSelect(); + if (wallet.isLoaded()) { + await guiToggleReceiveType(cReceiveType); + } + return true; + } else { + console.log( + 'i18n: The language (' + + langName + + ") is not supported yet, if you'd like to contribute translations (for rewards!) contact us on GitHub or Discord!" + ); + switchTranslation(defaultLang); + return false; + } +} + +/** + * Takes an i18n string that includes `{x}` and replaces that based on what is in the array of objects + * @param {string} message + * @param {Array} variables + * @returns a string with the variables implemented in the string + * + * @example + * //returns "test this" + * tr("test {x}" [{x: "this"}]) + */ +export function tr(message, variables) { + variables.forEach((element) => { + message = message.replaceAll( + '{' + Object.keys(element)[0] + '}', + Object.values(element)[0] + ); + }); + return message; +} + +/** + * Translates all static HTML based on the `data-i18n` tag + * @param {Array} i18nLangs + */ +export function translateStaticHTML(i18nLangs) { + if (!i18nLangs) return; + + document.querySelectorAll('[data-i18n]').forEach(function (element) { + if (!i18nLangs[element.dataset.i18n]) return; + + if (element.dataset.i18n_target) { + element[element.dataset.i18n_target] = + i18nLangs[element.dataset.i18n]; + } else { + switch (element.tagName.toLowerCase()) { + case 'input': + case 'textarea': + element.placeholder = i18nLangs[element.dataset.i18n]; + break; + default: + element.innerHTML = i18nLangs[element.dataset.i18n]; + break; + } + } + }); + ALERTS = translation['ALERTS']; +} + +export const arrActiveLangs = [ + { code: 'auto', display: 'Auto', emoji: '🌐' }, + { code: 'en', display: 'English', emoji: '🇬🇧' }, + { code: 'fr', display: 'French', emoji: '🇫🇷' }, + { code: 'de', display: 'German', emoji: '🇩🇪' }, + { code: 'nl', display: 'Dutch', emoji: '🇳🇱' }, + { code: 'it', display: 'Italian', emoji: '🇮🇹' }, + { code: 'pl', display: 'Polish', emoji: '🇵🇱' }, + { code: 'pt-pt', display: 'Portuguese', emoji: '🇵🇹' }, + { code: 'pt-br', display: 'Brazilian Portuguese', emoji: '🇧🇷' }, + { code: 'cnr', display: 'Montenegrin', emoji: '🇲🇪' }, + { code: 'es-mx', display: 'Mexican Spanish', emoji: '🇲🇽' }, + { code: 'ph', display: 'Filipino', emoji: '🇵🇭' }, + { code: 'hi', display: 'Hindi', emoji: '🇮🇳' }, + { code: 'uwu', display: 'UwU', emoji: '🐈' }, +]; + +export async function start() { + const db = await Database.getInstance(); + const settings = await db.getSettings(); + + await setTranslation(settings?.translation || 'auto'); +} diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 000000000..3ea8e8e8f --- /dev/null +++ b/scripts/index.js @@ -0,0 +1,76 @@ +import 'bootstrap/dist/css/bootstrap.min.css'; +import '@fontsource/chivo/900.css'; +import '@fortawesome/fontawesome-free/css/all.min.css'; +import '../assets/style/style.css'; +import 'bootstrap'; + +// Import all montserrat font weights +import.meta.webpackContext('@fontsource/montserrat/', { + recursive: false, + regExp: /\.css$/, +}); +import './global.js'; +import { getNetwork } from './network.js'; + +// Export global functions to the MPW namespace so we can use them in html +export { + openTab, + accessOrImportWallet, + toClipboard, + restoreWallet, + playMusic, + openExplorer, + doms, + importMasternode, + destroyMasternode, + startMasternode, + toggleDropDown, + createProposal, + switchSettings, + govVote, +} from './global.js'; +export { wallet, getNewAddress } from './wallet.js'; +export { + logOut, + toggleTestnet, + toggleDebug, + toggleAutoSwitch, + toggleAdvancedMode, + toggleAutoLockWallet, + changePassword, +} from './settings.js'; +export { createMasternode } from './legacy.js'; +export { + promoConfirm, + setPromoMode, + sweepPromoCode, + deletePromoCode, + openPromoQRScanner, + promosToCSV, +} from './promos.js'; +export { + guiRenderContacts, + guiAddContact, + guiRemoveContact, + guiSelectContact, + guiToggleReceiveType, + guiSetAccountName, + guiCheckRecipientInput, + guiRenderCurrentReceiveModal, + guiAddContactQRPrompt, + guiEditContactNamePrompt, + guiAddContactImage, + localContactToClipboard, +} from './contacts-book.js'; +export { renderWalletBreakdown } from './charting.js'; +export { hexToBytes, bytesToHex, dSHA256 } from './utils.js'; + +import Masternode from './masternode.js'; +export { renderChangelog } from './changelog.js'; +export { Masternode }; + +export { getNetwork } from './network.js'; +const toggleNetwork = () => getNetwork().toggle(); +export { toggleNetwork }; + +export { FlipDown } from './flipdown.js'; diff --git a/scripts/ledger.js b/scripts/ledger.js new file mode 100644 index 000000000..d151cccef --- /dev/null +++ b/scripts/ledger.js @@ -0,0 +1,230 @@ +import createXpub from 'create-xpub'; +import { ALERTS, tr } from './i18n.js'; +import { confirmPopup, createAlert } from './misc.js'; +import { getNetwork } from './network.js'; +import { Transaction } from './transaction.js'; +import { COIN, cChainParams } from './chain_params.js'; +import { hexToBytes, bytesToHex } from './utils.js'; +import { OP } from './script.js'; + +/** + * @type{import('@ledgerhq/hw-transport-webusb').default} + */ +let transport; +/** + * @type {import('@ledgerhq/hw-app-btc').default?} + */ +export let cHardwareWallet = null; +export let strHardwareName = ''; +/** + * Get hardware wallet keys. + * @param {string} path - bip32 path to the key + * @returns {Promise} + */ +export async function getHardwareWalletKeys(path, xpub = false, verify = true) { + try { + // Check if we haven't setup a connection yet OR the previous connection disconnected + if (!cHardwareWallet || transport._disconnectEmitted) { + const AppBtc = (await import('@ledgerhq/hw-app-btc')).default; + const TransportWebUSB = ( + await import('@ledgerhq/hw-transport-webusb') + ).default; + transport = await TransportWebUSB.create(); + cHardwareWallet = new AppBtc({ transport, currency: 'PIVX' }); + } + + // Update device info and fetch the pubkey + strHardwareName = + transport.device.manufacturerName + + ' ' + + transport.device.productName; + + // Prompt the user in both UIs + if (verify) createAlert('info', ALERTS.WALLET_CONFIRM_L, 3500); + const cPubKey = await cHardwareWallet.getWalletPublicKey(path, { + verify, + format: 'legacy', + }); + + if (xpub) { + return createXpub({ + depth: 3, + childNumber: 2147483648, + chainCode: cPubKey.chainCode, + publicKey: cPubKey.publicKey, + }); + } else { + return cPubKey.publicKey; + } + } catch (e) { + if (e.message.includes('denied by the user')) { + // User denied an operation + return null; + } + + // If there's no device, nudge the user to plug it in. + if (e.message.toLowerCase().includes('no device selected')) { + createAlert('info', ALERTS.WALLET_NO_HARDWARE, 10000); + return null; + } + + // If the device is unplugged, or connection lost through other means (such as spontanious device explosion) + if (e.message.includes("Failed to execute 'transferIn'")) { + createAlert( + 'info', + tr(ALERTS.WALLET_HARDWARE_CONNECTION_LOST, [ + { + hardwareWallet: strHardwareName, + }, + ]), + 10000 + ); + return null; + } + + // If the ledger is busy, just nudge the user. + if (e.message.includes('is busy')) { + createAlert( + 'info', + tr(ALERTS.WALLET_HARDWARE_BUSY, [ + { + hardwareWallet: strHardwareName, + }, + ]), + 7500 + ); + return null; + } + + // This is when the OS denies access to the WebUSB + // It's likely caused by faulty udev rules on linux + if (e instanceof DOMException && e.message.match(/access denied/i)) { + if (navigator.userAgent.toLowerCase().includes('linux')) { + createAlert('warning', ALERTS.WALLET_HARDWARE_UDEV, 5500); + } else { + createAlert('warning', ALERTS.WALLET_HARDWARE_NO_ACCESS, 5500); + } + + console.error(e); + return; + } + + // Check if this is an expected error + if (!e.statusCode || !LEDGER_ERRS.has(e.statusCode)) { + console.error( + 'MISSING LEDGER ERROR-CODE TRANSLATION! - Please report this below error on our GitHub so we can handle it more nicely!' + ); + throw e; + } + + // Translate the error to a user-friendly string (if possible) + createAlert( + 'warning', + tr(ALERTS.WALLET_HARDWARE_ERROR, [ + { + hardwareWallet: strHardwareName, + }, + { + error: LEDGER_ERRS.get(e.statusCode), + }, + ]), + 5500 + ); + + return null; + } +} + +/** + * @param {import('./wallet.js').Wallet} wallet + * @param {import('./transaction.js').Transaction} transaction - tx to sign + */ +export async function ledgerSignTransaction(wallet, transaction) { + const ledgerTx = cHardwareWallet.splitTransaction(transaction.serialize()); + const outputs = transaction.vout.map((o) => { + const { addresses, type } = wallet.getAddressesFromScript(o.script); + if (type !== 'p2pkh') { + throw new Error( + 'Invalid script. Ledger supports p2pkh scripts only' + ); + } + return { + value: o.value, + address: addresses[0], + }; + }); + + const associatedKeysets = []; + const inputs = []; + const isColdStake = []; + for (const input of transaction.vin) { + const { hex } = await getNetwork().getTxInfo(input.outpoint.txid); + const { type } = wallet.getAddressesFromScript(input.scriptSig); + inputs.push([ + cHardwareWallet.splitTransaction(hex, false, false, true), + input.outpoint.n, + ]); + // ScriptSig is the script at this point, since it's not signed + associatedKeysets.push(wallet.getPath(input.scriptSig)); + isColdStake.push(type === 'p2cs'); + } + const outputScriptHex = cHardwareWallet + .serializeTransactionOutputs(ledgerTx) + .toString('hex'); + const hex = await confirmPopup({ + title: ALERTS.CONFIRM_POPUP_TRANSACTION, + html: createTxConfirmation(outputs), + resolvePromise: cHardwareWallet.createPaymentTransaction({ + inputs, + associatedKeysets, + outputScriptHex, + }), + }); + + const signedTx = Transaction.fromHex(hex); + // Update vin with signatures + transaction.vin = signedTx.vin; + for (let i = 0; i < transaction.vin.length; i++) { + const input = transaction.vin[i]; + // if it's a cold stake tx we need to add OP_FALSE + if (isColdStake[i]) { + const bytes = hexToBytes(input.scriptSig); + const sigLength = bytes[0]; + input.scriptSig = bytesToHex([ + bytes[0], + ...bytes.slice(1, sigLength + 1), + OP['FALSE'], + ...bytes.slice(sigLength + 1), + ]); + } + } + return transaction; +} + +function createTxConfirmation(outputs) { + let strHtml = tr(ALERTS.CONFIRM_LEDGER_TX, [ + { hardwareWallet: strHardwareName }, + ]); + for (const { value, address } of outputs) { + const translated = tr(ALERTS.CONFIRM_LEDGER_TX_OUT, [ + { value: value / COIN }, + { ticker: cChainParams.current.TICKER }, + { address }, + ]); + strHtml += `

${translated}`; + } + return strHtml; +} + +// Ledger Hardware wallet constants +export const LEDGER_ERRS = new Map([ + // Ledger error code <--> User-friendly string + [25870, 'Open the PIVX app on your device'], + [25873, 'Open the PIVX app on your device'], + [57408, 'Navigate to the PIVX app on your device'], + [27157, 'Wrong app! Open the PIVX app on your device'], + [27266, 'Wrong app! Open the PIVX app on your device'], + [27904, 'Wrong app! Open the PIVX app on your device'], + [27010, 'Unlock your Ledger, then try again!'], + [27404, 'Unlock your Ledger, then try again!'], +]); diff --git a/scripts/legacy.js b/scripts/legacy.js new file mode 100644 index 000000000..2cb8d4e38 --- /dev/null +++ b/scripts/legacy.js @@ -0,0 +1,125 @@ +// Legacy functions +// To be removed when vue port is done + +import { ALERTS, translation, tr } from './i18n.js'; +import { doms, restoreWallet } from './global.js'; +import { wallet, getNewAddress } from './wallet.js'; +import { cChainParams, COIN, COIN_DECIMALS } from './chain_params.js'; +import { + createAlert, + generateMasternodePrivkey, + confirmPopup, +} from './misc.js'; +import { Database } from './database.js'; +import { getNetwork } from './network.js'; +import { ledgerSignTransaction } from './ledger.js'; + +/** + * @deprecated use the new wallet method instead + */ +export async function createAndSendTransaction({ + address, + amount, + isDelegation = false, + useDelegatedInputs = false, + delegateChange = false, + changeDelegationAddress = null, + isProposal = false, + changeAddress = '', + delegationOwnerAddress, +}) { + const tx = wallet.createTransaction(address, amount, { + isDelegation, + useDelegatedInputs, + delegateChange, + changeDelegationAddress, + isProposal, + changeAddress, + returnAddress: delegationOwnerAddress, + }); + if (!wallet.isHardwareWallet()) await wallet.sign(tx); + else { + await ledgerSignTransaction(wallet, tx); + } + const res = await getNetwork().sendTransaction(tx.serialize()); + if (res) { + await wallet.addTransaction(tx); + return { ok: true, txid: tx.txid }; + } + wallet.discardTransaction(tx); + return { ok: false }; +} + +/** + * @deprecated use the new wallet method instead + */ +export async function createMasternode() { + // Ensure the wallet is unlocked + if ( + wallet.isViewOnly() && + !(await restoreWallet(translation.walletUnlockCreateMN)) + ) + return; + + // Generate the Masternode collateral + const [address] = await getNewAddress({ + verify: wallet.isHardwareWallet(), + nReceiving: 1, + }); + const result = await createAndSendTransaction({ + amount: cChainParams.current.collateralInSats, + address, + }); + if (!result.ok) { + return; + } + + // Generate a Masternode private key if the user wants a self-hosted masternode + const fGeneratePrivkey = doms.domMnCreateType.value === 'VPS'; + if (fGeneratePrivkey) { + await confirmPopup({ + title: ALERTS.CONFIRM_POPUP_MN_P_KEY, + html: + generateMasternodePrivkey() + + ALERTS.CONFIRM_POPUP_MN_P_KEY_HTML, + }); + } + createAlert('success', ALERTS.MN_CREATED_WAIT_CONFS); + // Remove any previous Masternode data, if there were any + const database = await Database.getInstance(); + database.removeMasternode(); +} + +/** + * @deprecated reimplement when the vue port is done + */ +export function validateAmount(nAmountSats, nMinSats = 10000) { + // Validate the amount is a valid number, and meets the minimum (if any) + if (nAmountSats < nMinSats || isNaN(nAmountSats)) { + createAlert( + 'warning', + tr(ALERTS.INVALID_AMOUNT + ALERTS.VALIDATE_AMOUNT_LOW, [ + { minimumAmount: nMinSats / COIN }, + { coinTicker: cChainParams.current.TICKER }, + ]), + 2500 + ); + return false; + } + + // Validate the amount in Satoshi terms meets the coin's native decimal depth + if (!Number.isSafeInteger(nAmountSats)) { + createAlert( + 'warning', + tr( + ALERTS.INVALID_AMOUNT + '
' + ALERTS.VALIDATE_AMOUNT_DECIMAL, + [{ coinDecimal: COIN_DECIMALS }] + ), + 2500 + ); + return false; + } + + // Amount looks valid! + return true; +} diff --git a/scripts/libs/crypto-min.js b/scripts/libs/crypto-min.js deleted file mode 100644 index 66807a01b..000000000 --- a/scripts/libs/crypto-min.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Crypto-JS v2.5.4 - * http://code.google.com/p/crypto-js/ - * (c) 2009-2012 by Jeff Mott. All rights reserved. - * http://code.google.com/p/crypto-js/wiki/License - */ -(typeof Crypto=="undefined"||!Crypto.util)&&function(){var e=window.Crypto={},f=e.util={rotl:function(a,b){return a<>>32-b},rotr:function(a,b){return a<<32-b|a>>>b},endian:function(a){if(a.constructor==Number)return f.rotl(a,8)&16711935|f.rotl(a,24)&4278255360;for(var b=0;b0;a--)b.push(Math.floor(Math.random()*256));return b},bytesToWords:function(a){for(var b=[],c=0,d=0;c>>5]|=(a[c]&255)<< -24-d%32;return b},wordsToBytes:function(a){for(var b=[],c=0;c>>5]>>>24-c%32&255);return b},bytesToHex:function(a){for(var b=[],c=0;c>>4).toString(16)),b.push((a[c]&15).toString(16));return b.join("")},hexToBytes:function(a){for(var b=[],c=0;c>> -6*(3-e)&63)):b.push("=");return b.join("")},base64ToBytes:function(a){for(var a=a.replace(/[^A-Z0-9+\/]/ig,""),b=[],c=0,d=0;c>>6-d*2);return b}},e=e.charenc={};e.UTF8={stringToBytes:function(a){return g.stringToBytes(unescape(encodeURIComponent(a)))},bytesToString:function(a){return decodeURIComponent(escape(g.bytesToString(a)))}}; -var g=e.Binary={stringToBytes:function(a){for(var b=[],c=0;c>>32-a},rotr:function(b,a){return b<<32-a|b>>>a},endian:function(b){if(b.constructor==Number)return k.rotl(b,8)&16711935|k.rotl(b,24)&4278255360;for(var a=0;a0;b--)a.push(Math.floor(Math.random()*256));return a},bytesToWords:function(b){for(var a=[],c=0,e=0;c>>5]|=(b[c]&255)<< -24-e%32;return a},wordsToBytes:function(b){for(var a=[],c=0;c>>5]>>>24-c%32&255);return a},bytesToHex:function(b){for(var a=[],c=0;c>>4).toString(16)),a.push((b[c]&15).toString(16));return a.join("")},hexToBytes:function(b){for(var a=[],c=0;c>> -6*(3-p)&63)):a.push("=");return a.join("")},base64ToBytes:function(b){for(var b=b.replace(/[^A-Z0-9+\/]/ig,""),a=[],c=0,e=0;c>>6-e*2);return a}},d=d.charenc={};d.UTF8={stringToBytes:function(b){return g.stringToBytes(unescape(encodeURIComponent(b)))},bytesToString:function(b){return decodeURIComponent(escape(g.bytesToString(b)))}}; -var g=d.Binary={stringToBytes:function(b){for(var a=[],c=0;c>5]|=128<<24-f%32;e[(f+64>>9<<4)+15]=f;for(t=0;t>>7)^(l<<14|l>>>18)^l>>>3)+(d[h-7]>>>0)+((j<<15|j>>>17)^(j<<13|j>>>19)^j>>>10)+(d[h-16]>>>0));j=f&g^f&m^g&m;var u=(f<<30|f>>>2)^(f<<19|f>>>13)^(f<<10|f>>>22);l=(s>>>0)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))+ -(i&n^~i&o)+c[h]+(d[h]>>>0);j=u+j;s=o;o=n;n=i;i=r+l>>>0;r=m;m=g;g=f;f=l+j>>>0}a[0]+=f;a[1]+=g;a[2]+=m;a[3]+=r;a[4]+=i;a[5]+=n;a[6]+=o;a[7]+=s}return a};e._blocksize=16;e._digestsize=32})(); -(function(){var d=Crypto,k=d.util,g=d.charenc,b=g.UTF8,a=g.Binary;d.HMAC=function(c,e,d,g){e.constructor==String&&(e=b.stringToBytes(e));d.constructor==String&&(d=b.stringToBytes(d));d.length>c._blocksize*4&&(d=c(d,{asBytes:!0}));for(var f=d.slice(0),d=d.slice(0),q=0;q>>32-a},rotr:function(b,a){return b<<32-a|b>>>a},endian:function(b){if(b.constructor==Number)return l.rotl(b,8)&16711935|l.rotl(b,24)&4278255360;for(var a=0;a0;b--)a.push(Math.floor(Math.random()*256));return a},bytesToWords:function(b){for(var a=[],c=0,d=0;c>>5]|=(b[c]&255)<< -24-d%32;return a},wordsToBytes:function(b){for(var a=[],c=0;c>>5]>>>24-c%32&255);return a},bytesToHex:function(b){for(var a=[],c=0;c>>4).toString(16)),a.push((b[c]&15).toString(16));return a.join("")},hexToBytes:function(b){for(var a=[],c=0;c>> -6*(3-q)&63)):a.push("=");return a.join("")},base64ToBytes:function(b){for(var b=b.replace(/[^A-Z0-9+\/]/ig,""),a=[],c=0,d=0;c>>6-d*2);return a}},f=f.charenc={};f.UTF8={stringToBytes:function(b){return i.stringToBytes(unescape(encodeURIComponent(b)))},bytesToString:function(b){return decodeURIComponent(escape(i.bytesToString(b)))}}; -var i=f.Binary={stringToBytes:function(b){for(var a=[],c=0;c>5]|=128<<24-e%32;d[(e+64>>9<<4)+15]=e;for(s=0;s>>7)^(k<<14|k>>>18)^k>>>3)+(f[g-7]>>>0)+((j<<15|j>>>17)^(j<<13|j>>>19)^j>>>10)+(f[g-16]>>>0));j=e&m^e&n^m&n;var t=(e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22);k=(r>>>0)+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+ -(h&o^~h&p)+c[g]+(f[g]>>>0);j=t+j;r=p;p=o;o=h;h=i+k>>>0;i=n;n=m;m=e;e=k+j>>>0}a[0]+=e;a[1]+=m;a[2]+=n;a[3]+=i;a[4]+=h;a[5]+=o;a[6]+=p;a[7]+=r}return a};d._blocksize=16;d._digestsize=32})(); \ No newline at end of file diff --git a/scripts/libs/ellipticcurve.js b/scripts/libs/ellipticcurve.js deleted file mode 100644 index e9fc78448..000000000 --- a/scripts/libs/ellipticcurve.js +++ /dev/null @@ -1,668 +0,0 @@ -/*! -* Basic Javascript Elliptic Curve implementation -* Ported loosely from BouncyCastle's Java EC code -* Only Fp curves implemented for now -* -* Copyright Tom Wu, bitaddress.org BSD License. -* http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE -*/ -(function () { - - // Constructor function of Global EllipticCurve object - var ec = window.EllipticCurve = function () { }; - - - // ---------------- - // ECFieldElementFp constructor - // q instanceof BigInteger - // x instanceof BigInteger - ec.FieldElementFp = function (q, x) { - this.x = x; - // TODO if(x.compareTo(q) >= 0) error - this.q = q; - }; - - ec.FieldElementFp.prototype.equals = function (other) { - if (other == this) return true; - return (this.q.equals(other.q) && this.x.equals(other.x)); - }; - - ec.FieldElementFp.prototype.toBigInteger = function () { - return this.x; - }; - - ec.FieldElementFp.prototype.negate = function () { - return new ec.FieldElementFp(this.q, this.x.negate().mod(this.q)); - }; - - ec.FieldElementFp.prototype.add = function (b) { - return new ec.FieldElementFp(this.q, this.x.add(b.toBigInteger()).mod(this.q)); - }; - - ec.FieldElementFp.prototype.subtract = function (b) { - return new ec.FieldElementFp(this.q, this.x.subtract(b.toBigInteger()).mod(this.q)); - }; - - ec.FieldElementFp.prototype.multiply = function (b) { - return new ec.FieldElementFp(this.q, this.x.multiply(b.toBigInteger()).mod(this.q)); - }; - - ec.FieldElementFp.prototype.square = function () { - return new ec.FieldElementFp(this.q, this.x.square().mod(this.q)); - }; - - ec.FieldElementFp.prototype.divide = function (b) { - return new ec.FieldElementFp(this.q, this.x.multiply(b.toBigInteger().modInverse(this.q)).mod(this.q)); - }; - - ec.FieldElementFp.prototype.getByteLength = function () { - return Math.floor((this.toBigInteger().bitLength() + 7) / 8); - }; - - // D.1.4 91 - /** - * return a sqrt root - the routine verifies that the calculation - * returns the right value - if none exists it returns null. - * - * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) - * Ported to JavaScript by bitaddress.org - */ - ec.FieldElementFp.prototype.sqrt = function () { - if (!this.q.testBit(0)) throw new Error("even value of q"); - - // p mod 4 == 3 - if (this.q.testBit(1)) { - // z = g^(u+1) + p, p = 4u + 3 - var z = new ec.FieldElementFp(this.q, this.x.modPow(this.q.shiftRight(2).add(BigInteger.ONE), this.q)); - return z.square().equals(this) ? z : null; - } - - // p mod 4 == 1 - var qMinusOne = this.q.subtract(BigInteger.ONE); - var legendreExponent = qMinusOne.shiftRight(1); - if (!(this.x.modPow(legendreExponent, this.q).equals(BigInteger.ONE))) return null; - var u = qMinusOne.shiftRight(2); - var k = u.shiftLeft(1).add(BigInteger.ONE); - var Q = this.x; - var fourQ = Q.shiftLeft(2).mod(this.q); - var U, V; - - do { - var rand = new SecureRandom(); - var P; - do { - P = new BigInteger(this.q.bitLength(), rand); - } - while (P.compareTo(this.q) >= 0 || !(P.multiply(P).subtract(fourQ).modPow(legendreExponent, this.q).equals(qMinusOne))); - - var result = ec.FieldElementFp.fastLucasSequence(this.q, P, Q, k); - - U = result[0]; - V = result[1]; - if (V.multiply(V).mod(this.q).equals(fourQ)) { - // Integer division by 2, mod q - if (V.testBit(0)) { - V = V.add(this.q); - } - V = V.shiftRight(1); - return new ec.FieldElementFp(this.q, V); - } - } - while (U.equals(BigInteger.ONE) || U.equals(qMinusOne)); - - return null; - }; - - /* - * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) - * Ported to JavaScript by bitaddress.org - */ - ec.FieldElementFp.fastLucasSequence = function (p, P, Q, k) { - // TODO Research and apply "common-multiplicand multiplication here" - - var n = k.bitLength(); - var s = k.getLowestSetBit(); - var Uh = BigInteger.ONE; - var Vl = BigInteger.TWO; - var Vh = P; - var Ql = BigInteger.ONE; - var Qh = BigInteger.ONE; - - for (var j = n - 1; j >= s + 1; --j) { - Ql = Ql.multiply(Qh).mod(p); - if (k.testBit(j)) { - Qh = Ql.multiply(Q).mod(p); - Uh = Uh.multiply(Vh).mod(p); - Vl = Vh.multiply(Vl).subtract(P.multiply(Ql)).mod(p); - Vh = Vh.multiply(Vh).subtract(Qh.shiftLeft(1)).mod(p); - } - else { - Qh = Ql; - Uh = Uh.multiply(Vl).subtract(Ql).mod(p); - Vh = Vh.multiply(Vl).subtract(P.multiply(Ql)).mod(p); - Vl = Vl.multiply(Vl).subtract(Ql.shiftLeft(1)).mod(p); - } - } - - Ql = Ql.multiply(Qh).mod(p); - Qh = Ql.multiply(Q).mod(p); - Uh = Uh.multiply(Vl).subtract(Ql).mod(p); - Vl = Vh.multiply(Vl).subtract(P.multiply(Ql)).mod(p); - Ql = Ql.multiply(Qh).mod(p); - - for (var j = 1; j <= s; ++j) { - Uh = Uh.multiply(Vl).mod(p); - Vl = Vl.multiply(Vl).subtract(Ql.shiftLeft(1)).mod(p); - Ql = Ql.multiply(Ql).mod(p); - } - - return [Uh, Vl]; - }; - - // ---------------- - // ECPointFp constructor - ec.PointFp = function (curve, x, y, z, compressed) { - this.curve = curve; - this.x = x; - this.y = y; - // Projective coordinates: either zinv == null or z * zinv == 1 - // z and zinv are just BigIntegers, not fieldElements - if (z == null) { - this.z = BigInteger.ONE; - } - else { - this.z = z; - } - this.zinv = null; - // compression flag - this.compressed = !!compressed; - }; - - ec.PointFp.prototype.getX = function () { - if (this.zinv == null) { - this.zinv = this.z.modInverse(this.curve.q); - } - var r = this.x.toBigInteger().multiply(this.zinv); - this.curve.reduce(r); - return this.curve.fromBigInteger(r); - }; - - ec.PointFp.prototype.getY = function () { - if (this.zinv == null) { - this.zinv = this.z.modInverse(this.curve.q); - } - var r = this.y.toBigInteger().multiply(this.zinv); - this.curve.reduce(r); - return this.curve.fromBigInteger(r); - }; - - ec.PointFp.prototype.equals = function (other) { - if (other == this) return true; - if (this.isInfinity()) return other.isInfinity(); - if (other.isInfinity()) return this.isInfinity(); - var u, v; - // u = Y2 * Z1 - Y1 * Z2 - u = other.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(other.z)).mod(this.curve.q); - if (!u.equals(BigInteger.ZERO)) return false; - // v = X2 * Z1 - X1 * Z2 - v = other.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(other.z)).mod(this.curve.q); - return v.equals(BigInteger.ZERO); - }; - - ec.PointFp.prototype.isInfinity = function () { - if ((this.x == null) && (this.y == null)) return true; - return this.z.equals(BigInteger.ZERO) && !this.y.toBigInteger().equals(BigInteger.ZERO); - }; - - ec.PointFp.prototype.negate = function () { - return new ec.PointFp(this.curve, this.x, this.y.negate(), this.z); - }; - - ec.PointFp.prototype.add = function (b) { - if (this.isInfinity()) return b; - if (b.isInfinity()) return this; - - // u = Y2 * Z1 - Y1 * Z2 - var u = b.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(b.z)).mod(this.curve.q); - // v = X2 * Z1 - X1 * Z2 - var v = b.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(b.z)).mod(this.curve.q); - - - if (BigInteger.ZERO.equals(v)) { - if (BigInteger.ZERO.equals(u)) { - return this.twice(); // this == b, so double - } - return this.curve.getInfinity(); // this = -b, so infinity - } - - var THREE = new BigInteger("3"); - var x1 = this.x.toBigInteger(); - var y1 = this.y.toBigInteger(); - var x2 = b.x.toBigInteger(); - var y2 = b.y.toBigInteger(); - - var v2 = v.square(); - var v3 = v2.multiply(v); - var x1v2 = x1.multiply(v2); - var zu2 = u.square().multiply(this.z); - - // x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3) - var x3 = zu2.subtract(x1v2.shiftLeft(1)).multiply(b.z).subtract(v3).multiply(v).mod(this.curve.q); - // y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3 - var y3 = x1v2.multiply(THREE).multiply(u).subtract(y1.multiply(v3)).subtract(zu2.multiply(u)).multiply(b.z).add(u.multiply(v3)).mod(this.curve.q); - // z3 = v^3 * z1 * z2 - var z3 = v3.multiply(this.z).multiply(b.z).mod(this.curve.q); - - return new ec.PointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3); - }; - - ec.PointFp.prototype.twice = function () { - if (this.isInfinity()) return this; - if (this.y.toBigInteger().signum() == 0) return this.curve.getInfinity(); - - // TODO: optimized handling of constants - var THREE = new BigInteger("3"); - var x1 = this.x.toBigInteger(); - var y1 = this.y.toBigInteger(); - - var y1z1 = y1.multiply(this.z); - var y1sqz1 = y1z1.multiply(y1).mod(this.curve.q); - var a = this.curve.a.toBigInteger(); - - // w = 3 * x1^2 + a * z1^2 - var w = x1.square().multiply(THREE); - if (!BigInteger.ZERO.equals(a)) { - w = w.add(this.z.square().multiply(a)); - } - w = w.mod(this.curve.q); - //this.curve.reduce(w); - // x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1) - var x3 = w.square().subtract(x1.shiftLeft(3).multiply(y1sqz1)).shiftLeft(1).multiply(y1z1).mod(this.curve.q); - // y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3 - var y3 = w.multiply(THREE).multiply(x1).subtract(y1sqz1.shiftLeft(1)).shiftLeft(2).multiply(y1sqz1).subtract(w.square().multiply(w)).mod(this.curve.q); - // z3 = 8 * (y1 * z1)^3 - var z3 = y1z1.square().multiply(y1z1).shiftLeft(3).mod(this.curve.q); - - return new ec.PointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3); - }; - - // Simple NAF (Non-Adjacent Form) multiplication algorithm - // TODO: modularize the multiplication algorithm - ec.PointFp.prototype.multiply = function (k) { - if (this.isInfinity()) return this; - if (k.signum() == 0) return this.curve.getInfinity(); - - var e = k; - var h = e.multiply(new BigInteger("3")); - - var neg = this.negate(); - var R = this; - - var i; - for (i = h.bitLength() - 2; i > 0; --i) { - R = R.twice(); - - var hBit = h.testBit(i); - var eBit = e.testBit(i); - - if (hBit != eBit) { - R = R.add(hBit ? this : neg); - } - } - - return R; - }; - - // Compute this*j + x*k (simultaneous multiplication) - ec.PointFp.prototype.multiplyTwo = function (j, x, k) { - var i; - if (j.bitLength() > k.bitLength()) - i = j.bitLength() - 1; - else - i = k.bitLength() - 1; - - var R = this.curve.getInfinity(); - var both = this.add(x); - while (i >= 0) { - R = R.twice(); - if (j.testBit(i)) { - if (k.testBit(i)) { - R = R.add(both); - } - else { - R = R.add(this); - } - } - else { - if (k.testBit(i)) { - R = R.add(x); - } - } - --i; - } - - return R; - }; - - // patched by bitaddress.org and Casascius for use with Bitcoin.ECKey - // patched by coretechs to support compressed public keys - ec.PointFp.prototype.getEncoded = function (compressed) { - var x = this.getX().toBigInteger(); - var y = this.getY().toBigInteger(); - var len = 32; // integerToBytes will zero pad if integer is less than 32 bytes. 32 bytes length is required by the Bitcoin protocol. - var enc = ec.integerToBytes(x, len); - - // when compressed prepend byte depending if y point is even or odd - if (compressed) { - if (y.isEven()) { - enc.unshift(0x02); - } - else { - enc.unshift(0x03); - } - } - else { - enc.unshift(0x04); - enc = enc.concat(ec.integerToBytes(y, len)); // uncompressed public key appends the bytes of the y point - } - return enc; - }; - - ec.PointFp.decodeFrom = function (curve, enc) { - var type = enc[0]; - var dataLen = enc.length - 1; - - // Extract x and y as byte arrays - var xBa = enc.slice(1, 1 + dataLen / 2); - var yBa = enc.slice(1 + dataLen / 2, 1 + dataLen); - - // Prepend zero byte to prevent interpretation as negative integer - xBa.unshift(0); - yBa.unshift(0); - - // Convert to BigIntegers - var x = new BigInteger(xBa); - var y = new BigInteger(yBa); - - // Return point - return new ec.PointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y)); - }; - - ec.PointFp.prototype.add2D = function (b) { - if (this.isInfinity()) return b; - if (b.isInfinity()) return this; - - if (this.x.equals(b.x)) { - if (this.y.equals(b.y)) { - // this = b, i.e. this must be doubled - return this.twice(); - } - // this = -b, i.e. the result is the point at infinity - return this.curve.getInfinity(); - } - - var x_x = b.x.subtract(this.x); - var y_y = b.y.subtract(this.y); - var gamma = y_y.divide(x_x); - - var x3 = gamma.square().subtract(this.x).subtract(b.x); - var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); - - return new ec.PointFp(this.curve, x3, y3); - }; - - ec.PointFp.prototype.twice2D = function () { - if (this.isInfinity()) return this; - if (this.y.toBigInteger().signum() == 0) { - // if y1 == 0, then (x1, y1) == (x1, -y1) - // and hence this = -this and thus 2(x1, y1) == infinity - return this.curve.getInfinity(); - } - - var TWO = this.curve.fromBigInteger(BigInteger.valueOf(2)); - var THREE = this.curve.fromBigInteger(BigInteger.valueOf(3)); - var gamma = this.x.square().multiply(THREE).add(this.curve.a).divide(this.y.multiply(TWO)); - - var x3 = gamma.square().subtract(this.x.multiply(TWO)); - var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); - - return new ec.PointFp(this.curve, x3, y3); - }; - - ec.PointFp.prototype.multiply2D = function (k) { - if (this.isInfinity()) return this; - if (k.signum() == 0) return this.curve.getInfinity(); - - var e = k; - var h = e.multiply(new BigInteger("3")); - - var neg = this.negate(); - var R = this; - - var i; - for (i = h.bitLength() - 2; i > 0; --i) { - R = R.twice(); - - var hBit = h.testBit(i); - var eBit = e.testBit(i); - - if (hBit != eBit) { - R = R.add2D(hBit ? this : neg); - } - } - - return R; - }; - - ec.PointFp.prototype.isOnCurve = function () { - var x = this.getX().toBigInteger(); - var y = this.getY().toBigInteger(); - var a = this.curve.getA().toBigInteger(); - var b = this.curve.getB().toBigInteger(); - var n = this.curve.getQ(); - var lhs = y.multiply(y).mod(n); - var rhs = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(n); - return lhs.equals(rhs); - }; - - ec.PointFp.prototype.toString = function () { - return '(' + this.getX().toBigInteger().toString() + ',' + this.getY().toBigInteger().toString() + ')'; - }; - - /** - * Validate an elliptic curve point. - * - * See SEC 1, section 3.2.2.1: Elliptic Curve Public Key Validation Primitive - */ - ec.PointFp.prototype.validate = function () { - var n = this.curve.getQ(); - - // Check Q != O - if (this.isInfinity()) { - throw new Error("Point is at infinity."); - } - - // Check coordinate bounds - var x = this.getX().toBigInteger(); - var y = this.getY().toBigInteger(); - if (x.compareTo(BigInteger.ONE) < 0 || x.compareTo(n.subtract(BigInteger.ONE)) > 0) { - throw new Error('x coordinate out of bounds'); - } - if (y.compareTo(BigInteger.ONE) < 0 || y.compareTo(n.subtract(BigInteger.ONE)) > 0) { - throw new Error('y coordinate out of bounds'); - } - - // Check y^2 = x^3 + ax + b (mod n) - if (!this.isOnCurve()) { - throw new Error("Point is not on the curve."); - } - - // Check nQ = 0 (Q is a scalar multiple of G) - if (this.multiply(n).isInfinity()) { - // TODO: This check doesn't work - fix. - throw new Error("Point is not a scalar multiple of G."); - } - - return true; - }; - - - - - // ---------------- - // ECCurveFp constructor - ec.CurveFp = function (q, a, b) { - this.q = q; - this.a = this.fromBigInteger(a); - this.b = this.fromBigInteger(b); - this.infinity = new ec.PointFp(this, null, null); - this.reducer = new Barrett(this.q); - } - - ec.CurveFp.prototype.getQ = function () { - return this.q; - }; - - ec.CurveFp.prototype.getA = function () { - return this.a; - }; - - ec.CurveFp.prototype.getB = function () { - return this.b; - }; - - ec.CurveFp.prototype.equals = function (other) { - if (other == this) return true; - return (this.q.equals(other.q) && this.a.equals(other.a) && this.b.equals(other.b)); - }; - - ec.CurveFp.prototype.getInfinity = function () { - return this.infinity; - }; - - ec.CurveFp.prototype.fromBigInteger = function (x) { - return new ec.FieldElementFp(this.q, x); - }; - - ec.CurveFp.prototype.reduce = function (x) { - this.reducer.reduce(x); - }; - - // for now, work with hex strings because they're easier in JS - // compressed support added by bitaddress.org - ec.CurveFp.prototype.decodePointHex = function (s) { - var firstByte = parseInt(s.substr(0, 2), 16); - switch (firstByte) { // first byte - case 0: - return this.infinity; - case 2: // compressed - case 3: // compressed - var yTilde = firstByte & 1; - var xHex = s.substr(2, s.length - 2); - var X1 = new BigInteger(xHex, 16); - return this.decompressPoint(yTilde, X1); - case 4: // uncompressed - case 6: // hybrid - case 7: // hybrid - var len = (s.length - 2) / 2; - var xHex = s.substr(2, len); - var yHex = s.substr(len + 2, len); - - return new ec.PointFp(this, - this.fromBigInteger(new BigInteger(xHex, 16)), - this.fromBigInteger(new BigInteger(yHex, 16))); - - default: // unsupported - return null; - } - }; - - ec.CurveFp.prototype.encodePointHex = function (p) { - if (p.isInfinity()) return "00"; - var xHex = p.getX().toBigInteger().toString(16); - var yHex = p.getY().toBigInteger().toString(16); - var oLen = this.getQ().toString(16).length; - if ((oLen % 2) != 0) oLen++; - while (xHex.length < oLen) { - xHex = "0" + xHex; - } - while (yHex.length < oLen) { - yHex = "0" + yHex; - } - return "04" + xHex + yHex; - }; - - /* - * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) - * Ported to JavaScript by bitaddress.org - * - * Number yTilde - * BigInteger X1 - */ - ec.CurveFp.prototype.decompressPoint = function (yTilde, X1) { - var x = this.fromBigInteger(X1); - var alpha = x.multiply(x.square().add(this.getA())).add(this.getB()); - var beta = alpha.sqrt(); - // if we can't find a sqrt we haven't got a point on the curve - run! - if (beta == null) throw new Error("Invalid point compression"); - var betaValue = beta.toBigInteger(); - var bit0 = betaValue.testBit(0) ? 1 : 0; - if (bit0 != yTilde) { - // Use the other root - beta = this.fromBigInteger(this.getQ().subtract(betaValue)); - } - return new ec.PointFp(this, x, beta, null, true); - }; - - - ec.fromHex = function (s) { return new BigInteger(s, 16); }; - - ec.integerToBytes = function (i, len) { - var bytes = i.toByteArrayUnsigned(); - if (len < bytes.length) { - bytes = bytes.slice(bytes.length - len); - } else while (len > bytes.length) { - bytes.unshift(0); - } - return bytes; - }; - - - // Named EC curves - // ---------------- - // X9ECParameters constructor - ec.X9Parameters = function (curve, g, n, h) { - this.curve = curve; - this.g = g; - this.n = n; - this.h = h; - } - ec.X9Parameters.prototype.getCurve = function () { return this.curve; }; - ec.X9Parameters.prototype.getG = function () { return this.g; }; - ec.X9Parameters.prototype.getN = function () { return this.n; }; - ec.X9Parameters.prototype.getH = function () { return this.h; }; - - // secp256k1 is the Curve used by Bitcoin - ec.secNamedCurves = { - // used by Bitcoin - "secp256k1": function () { - // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 - var p = ec.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"); - var a = BigInteger.ZERO; - var b = ec.fromHex("7"); - var n = ec.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); - var h = BigInteger.ONE; - var curve = new ec.CurveFp(p, a, b); - var G = curve.decodePointHex("04" - + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" - + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"); - return new ec.X9Parameters(curve, G, n, h); - } - }; - - // secp256k1 called by Bitcoin's ECKEY - ec.getSECCurveByName = function (name) { - if (ec.secNamedCurves[name] == undefined) return null; - return ec.secNamedCurves[name](); - } -})(); \ No newline at end of file diff --git a/scripts/libs/jsbn.js b/scripts/libs/jsbn.js deleted file mode 100644 index c0acd4f43..000000000 --- a/scripts/libs/jsbn.js +++ /dev/null @@ -1,1298 +0,0 @@ -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if (!(this instanceof BigInteger)) { - return new BigInteger(a, b, c); - } - - if(a != null) { - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); - } -} - -var proto = BigInteger.prototype; - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} - -// wtf? -BigInteger.prototype.am = am1; -dbits = 26; - -/* -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} -*/ - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var self = this; - - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { self.fromRadix(s,b); return; } - self.t = 0; - self.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - self[self.t++] = x; - else if(sh+k > self.DB) { - self[self.t-1] |= (x&((1<<(self.DB-sh))-1))<>(self.DB-sh)); - } - else - self[self.t-1] |= x<= self.DB) sh -= self.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - self.s = -1; - if(sh > 0) self[self.t-1] |= ((1<<(self.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - var self = this; - if(self.s < 0) return "-"+self.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return self.toRadix(b); - var km = (1< 0) { - if(p < self.DB && (d = self[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (self[i]&((1<>(p+=self.DB-k); - } - else { - d = (self[i]>>(p-=k))&km; - if(p <= 0) { p += self.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return (this.s<0)?-r:r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var self = this; - var bs = n%self.DB; - var cbs = self.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (self[i]>>cbs)|c; - c = (self[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = self.t+ds+1; - r.s = self.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - var self = this; - r.s = self.s; - var ds = Math.floor(n/self.DB); - if(ds >= self.t) { r.t = 0; return; } - var bs = n%self.DB; - var cbs = self.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < self.t; ++i) { - r[i-ds-1] |= (self[i]&bm)<>bs; - } - if(bs > 0) r[self.t-ds-1] |= (self.s&bm)<>= self.DB; - } - if(a.t < self.t) { - c -= a.s; - while(i < self.t) { - c += self[i]; - r[i++] = c&self.DM; - c >>= self.DB; - } - c += self.s; - } - else { - c += self.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&self.DM; - c >>= self.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = self.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var self = this; - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = self.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) self.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = self.s, ms = m.s; - var nsh = self.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>self.F2:0); - var d1 = self.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?self.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -proto.copyTo = bnpCopyTo; -proto.fromInt = bnpFromInt; -proto.fromString = bnpFromString; -proto.clamp = bnpClamp; -proto.dlShiftTo = bnpDLShiftTo; -proto.drShiftTo = bnpDRShiftTo; -proto.lShiftTo = bnpLShiftTo; -proto.rShiftTo = bnpRShiftTo; -proto.subTo = bnpSubTo; -proto.multiplyTo = bnpMultiplyTo; -proto.squareTo = bnpSquareTo; -proto.divRemTo = bnpDivRemTo; -proto.invDigit = bnpInvDigit; -proto.isEven = bnpIsEven; -proto.exp = bnpExp; - -// public -proto.toString = bnToString; -proto.negate = bnNegate; -proto.abs = bnAbs; -proto.compareTo = bnCompareTo; -proto.bitLength = bnBitLength; -proto.mod = bnMod; -proto.modPowInt = bnModPowInt; - -//// jsbn2 - -function nbi() { return new BigInteger(null); } - -// (public) -function bnClone() { var r = nbi(); this.copyTo(r); return r; } - -// (public) return value as integer -function bnIntValue() { - if(this.s < 0) { - if(this.t == 1) return this[0]-this.DV; - else if(this.t == 0) return -1; - } - else if(this.t == 1) return this[0]; - else if(this.t == 0) return 0; - // assumes 16 < DB < 32 - return ((this[1]&((1<<(32-this.DB))-1))<>24; } - -// (public) return value as short (assumes DB>=16) -function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } - -// (protected) return x s.t. r^x < DV -function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } - -// (public) 0 if this == 0, 1 if this > 0 -function bnSigNum() { - if(this.s < 0) return -1; - else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; - else return 1; -} - -// (protected) convert to radix string -function bnpToRadix(b) { - if(b == null) b = 10; - if(this.signum() == 0 || b < 2 || b > 36) return "0"; - var cs = this.chunkSize(b); - var a = Math.pow(b,cs); - var d = nbv(a), y = nbi(), z = nbi(), r = ""; - this.divRemTo(d,y,z); - while(y.signum() > 0) { - r = (a+z.intValue()).toString(b).substr(1) + r; - y.divRemTo(d,y,z); - } - return z.intValue().toString(b) + r; -} - -// (protected) convert from radix string -function bnpFromRadix(s,b) { - var self = this; - self.fromInt(0); - if(b == null) b = 10; - var cs = self.chunkSize(b); - var d = Math.pow(b,cs), mi = false, j = 0, w = 0; - for(var i = 0; i < s.length; ++i) { - var x = intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-" && self.signum() == 0) mi = true; - continue; - } - w = b*w+x; - if(++j >= cs) { - self.dMultiply(d); - self.dAddOffset(w,0); - j = 0; - w = 0; - } - } - if(j > 0) { - self.dMultiply(Math.pow(b,j)); - self.dAddOffset(w,0); - } - if(mi) BigInteger.ZERO.subTo(self,self); -} - -// (protected) alternate constructor -function bnpFromNumber(a,b,c) { - var self = this; - if("number" == typeof b) { - // new BigInteger(int,int,RNG) - if(a < 2) self.fromInt(1); - else { - self.fromNumber(a,c); - if(!self.testBit(a-1)) // force MSB set - self.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,self); - if(self.isEven()) self.dAddOffset(1,0); // force odd - while(!self.isProbablePrime(b)) { - self.dAddOffset(2,0); - if(self.bitLength() > a) self.subTo(BigInteger.ONE.shiftLeft(a-1),self); - } - } - } - else { - // new BigInteger(int,RNG) - var x = new Array(), t = a&7; - x.length = (a>>3)+1; - b.nextBytes(x); - if(t > 0) x[0] &= ((1< 0) { - if(p < self.DB && (d = self[i]>>p) != (self.s&self.DM)>>p) - r[k++] = d|(self.s<<(self.DB-p)); - while(i >= 0) { - if(p < 8) { - d = (self[i]&((1<>(p+=self.DB-8); - } - else { - d = (self[i]>>(p-=8))&0xff; - if(p <= 0) { p += self.DB; --i; } - } - if((d&0x80) != 0) d |= -256; - if(k === 0 && (self.s&0x80) != (d&0x80)) ++k; - if(k > 0 || d != self.s) r[k++] = d; - } - } - return r; -} - -function bnEquals(a) { return(this.compareTo(a)==0); } -function bnMin(a) { return(this.compareTo(a)<0)?this:a; } -function bnMax(a) { return(this.compareTo(a)>0)?this:a; } - -// (protected) r = this op a (bitwise) -function bnpBitwiseTo(a,op,r) { - var self = this; - var i, f, m = Math.min(a.t,self.t); - for(i = 0; i < m; ++i) r[i] = op(self[i],a[i]); - if(a.t < self.t) { - f = a.s&self.DM; - for(i = m; i < self.t; ++i) r[i] = op(self[i],f); - r.t = self.t; - } - else { - f = self.s&self.DM; - for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); - r.t = a.t; - } - r.s = op(self.s,a.s); - r.clamp(); -} - -// (public) this & a -function op_and(x,y) { return x&y; } -function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } - -// (public) this | a -function op_or(x,y) { return x|y; } -function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } - -// (public) this ^ a -function op_xor(x,y) { return x^y; } -function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } - -// (public) this & ~a -function op_andnot(x,y) { return x&~y; } -function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } - -// (public) ~this -function bnNot() { - var r = nbi(); - for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; - r.t = this.t; - r.s = ~this.s; - return r; -} - -// (public) this << n -function bnShiftLeft(n) { - var r = nbi(); - if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); - return r; -} - -// (public) this >> n -function bnShiftRight(n) { - var r = nbi(); - if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); - return r; -} - -// return index of lowest 1-bit in x, x < 2^31 -function lbit(x) { - if(x == 0) return -1; - var r = 0; - if((x&0xffff) == 0) { x >>= 16; r += 16; } - if((x&0xff) == 0) { x >>= 8; r += 8; } - if((x&0xf) == 0) { x >>= 4; r += 4; } - if((x&3) == 0) { x >>= 2; r += 2; } - if((x&1) == 0) ++r; - return r; -} - -// (public) returns index of lowest 1-bit (or -1 if none) -function bnGetLowestSetBit() { - for(var i = 0; i < this.t; ++i) - if(this[i] != 0) return i*this.DB+lbit(this[i]); - if(this.s < 0) return this.t*this.DB; - return -1; -} - -// return number of 1 bits in x -function cbit(x) { - var r = 0; - while(x != 0) { x &= x-1; ++r; } - return r; -} - -// (public) return number of set bits -function bnBitCount() { - var r = 0, x = this.s&this.DM; - for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); - return r; -} - -// (public) true iff nth bit is set -function bnTestBit(n) { - var j = Math.floor(n/this.DB); - if(j >= this.t) return(this.s!=0); - return((this[j]&(1<<(n%this.DB)))!=0); -} - -// (protected) this op (1<>= self.DB; - } - if(a.t < self.t) { - c += a.s; - while(i < self.t) { - c += self[i]; - r[i++] = c&self.DM; - c >>= self.DB; - } - c += self.s; - } - else { - c += self.s; - while(i < a.t) { - c += a[i]; - r[i++] = c&self.DM; - c >>= self.DB; - } - c += a.s; - } - r.s = (c<0)?-1:0; - if(c > 0) r[i++] = c; - else if(c < -1) r[i++] = self.DV+c; - r.t = i; - r.clamp(); -} - -// (public) this + a -function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } - -// (public) this - a -function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } - -// (public) this * a -function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } - -// (public) this^2 -function bnSquare() { var r = nbi(); this.squareTo(r); return r; } - -// (public) this / a -function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } - -// (public) this % a -function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } - -// (public) [this/a,this%a] -function bnDivideAndRemainder(a) { - var q = nbi(), r = nbi(); - this.divRemTo(a,q,r); - return new Array(q,r); -} - -// (protected) this *= n, this >= 0, 1 < n < DV -function bnpDMultiply(n) { - this[this.t] = this.am(0,n-1,this,0,0,this.t); - ++this.t; - this.clamp(); -} - -// (protected) this += n << w words, this >= 0 -function bnpDAddOffset(n,w) { - if(n == 0) return; - while(this.t <= w) this[this.t++] = 0; - this[w] += n; - while(this[w] >= this.DV) { - this[w] -= this.DV; - if(++w >= this.t) this[this.t++] = 0; - ++this[w]; - } -} - -// A "null" reducer -function NullExp() {} -function nNop(x) { return x; } -function nMulTo(x,y,r) { x.multiplyTo(y,r); } -function nSqrTo(x,r) { x.squareTo(r); } - -NullExp.prototype.convert = nNop; -NullExp.prototype.revert = nNop; -NullExp.prototype.mulTo = nMulTo; -NullExp.prototype.sqrTo = nSqrTo; - -// (public) this^e -function bnPow(e) { return this.exp(e,new NullExp()); } - -// (protected) r = lower n words of "this * a", a.t <= n -// "this" should be the larger one if appropriate. -function bnpMultiplyLowerTo(a,n,r) { - var i = Math.min(this.t+a.t,n); - r.s = 0; // assumes a,this >= 0 - r.t = i; - while(i > 0) r[--i] = 0; - var j; - for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); - for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); - r.clamp(); -} - -// (protected) r = "this * a" without lower n words, n > 0 -// "this" should be the larger one if appropriate. -function bnpMultiplyUpperTo(a,n,r) { - --n; - var i = r.t = this.t+a.t-n; - r.s = 0; // assumes a,this >= 0 - while(--i >= 0) r[i] = 0; - for(i = Math.max(n-this.t,0); i < a.t; ++i) - r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); - r.clamp(); - r.drShiftTo(1,r); -} - -// Barrett modular reduction -function Barrett(m) { - // setup Barrett - this.r2 = nbi(); - this.q3 = nbi(); - BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); - this.m = m; -} - -function barrettConvert(x) { - if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); - else if(x.compareTo(this.m) < 0) return x; - else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } -} - -function barrettRevert(x) { return x; } - -// x = x mod m (HAC 14.42) -function barrettReduce(x) { - var self = this; - x.drShiftTo(self.m.t-1,self.r2); - if(x.t > self.m.t+1) { x.t = self.m.t+1; x.clamp(); } - self.mu.multiplyUpperTo(self.r2,self.m.t+1,self.q3); - self.m.multiplyLowerTo(self.q3,self.m.t+1,self.r2); - while(x.compareTo(self.r2) < 0) x.dAddOffset(1,self.m.t+1); - x.subTo(self.r2,x); - while(x.compareTo(self.m) >= 0) x.subTo(self.m,x); -} - -// r = x^2 mod m; x != r -function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = x*y mod m; x,y != r -function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Barrett.prototype.convert = barrettConvert; -Barrett.prototype.revert = barrettRevert; -Barrett.prototype.reduce = barrettReduce; -Barrett.prototype.mulTo = barrettMulTo; -Barrett.prototype.sqrTo = barrettSqrTo; - -// (public) this^e % m (HAC 14.85) -function bnModPow(e,m) { - var i = e.bitLength(), k, r = nbv(1), z; - if(i <= 0) return r; - else if(i < 18) k = 1; - else if(i < 48) k = 3; - else if(i < 144) k = 4; - else if(i < 768) k = 5; - else k = 6; - if(i < 8) - z = new Classic(m); - else if(m.isEven()) - z = new Barrett(m); - else - z = new Montgomery(m); - - // precomputation - var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { - var g2 = nbi(); - z.sqrTo(g[1],g2); - while(n <= km) { - g[n] = nbi(); - z.mulTo(g2,g[n-2],g[n]); - n += 2; - } - } - - var j = e.t-1, w, is1 = true, r2 = nbi(), t; - i = nbits(e[j])-1; - while(j >= 0) { - if(i >= k1) w = (e[j]>>(i-k1))&km; - else { - w = (e[j]&((1<<(i+1))-1))<<(k1-i); - if(j > 0) w |= e[j-1]>>(this.DB+i-k1); - } - - n = k; - while((w&1) == 0) { w >>= 1; --n; } - if((i -= n) < 0) { i += this.DB; --j; } - if(is1) { // ret == 1, don't bother squaring or multiplying it - g[w].copyTo(r); - is1 = false; - } - else { - while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } - if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } - z.mulTo(r2,g[w],r); - } - - while(j >= 0 && (e[j]&(1< 0) { - x.rShiftTo(g,x); - y.rShiftTo(g,y); - } - while(x.signum() > 0) { - if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); - if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); - if(x.compareTo(y) >= 0) { - x.subTo(y,x); - x.rShiftTo(1,x); - } - else { - y.subTo(x,y); - y.rShiftTo(1,y); - } - } - if(g > 0) y.lShiftTo(g,y); - return y; -} - -// (protected) this % n, n < 2^26 -function bnpModInt(n) { - if(n <= 0) return 0; - var d = this.DV%n, r = (this.s<0)?n-1:0; - if(this.t > 0) - if(d == 0) r = this[0]%n; - else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; - return r; -} - -// (public) 1/this % m (HAC 14.61) -function bnModInverse(m) { - var ac = m.isEven(); - if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; - var u = m.clone(), v = this.clone(); - var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); - while(u.signum() != 0) { - while(u.isEven()) { - u.rShiftTo(1,u); - if(ac) { - if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } - a.rShiftTo(1,a); - } - else if(!b.isEven()) b.subTo(m,b); - b.rShiftTo(1,b); - } - while(v.isEven()) { - v.rShiftTo(1,v); - if(ac) { - if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } - c.rShiftTo(1,c); - } - else if(!d.isEven()) d.subTo(m,d); - d.rShiftTo(1,d); - } - if(u.compareTo(v) >= 0) { - u.subTo(v,u); - if(ac) a.subTo(c,a); - b.subTo(d,b); - } - else { - v.subTo(u,v); - if(ac) c.subTo(a,c); - d.subTo(b,d); - } - } - if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; - if(d.compareTo(m) >= 0) return d.subtract(m); - if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; -} - -// protected -proto.chunkSize = bnpChunkSize; -proto.toRadix = bnpToRadix; -proto.fromRadix = bnpFromRadix; -proto.fromNumber = bnpFromNumber; -proto.bitwiseTo = bnpBitwiseTo; -proto.changeBit = bnpChangeBit; -proto.addTo = bnpAddTo; -proto.dMultiply = bnpDMultiply; -proto.dAddOffset = bnpDAddOffset; -proto.multiplyLowerTo = bnpMultiplyLowerTo; -proto.multiplyUpperTo = bnpMultiplyUpperTo; -proto.modInt = bnpModInt; - -// public -proto.clone = bnClone; -proto.intValue = bnIntValue; -proto.byteValue = bnByteValue; -proto.shortValue = bnShortValue; -proto.signum = bnSigNum; -proto.toByteArray = bnToByteArray; -proto.equals = bnEquals; -proto.min = bnMin; -proto.max = bnMax; -proto.and = bnAnd; -proto.or = bnOr; -proto.xor = bnXor; -proto.andNot = bnAndNot; -proto.not = bnNot; -proto.shiftLeft = bnShiftLeft; -proto.shiftRight = bnShiftRight; -proto.getLowestSetBit = bnGetLowestSetBit; -proto.bitCount = bnBitCount; -proto.testBit = bnTestBit; -proto.setBit = bnSetBit; -proto.clearBit = bnClearBit; -proto.flipBit = bnFlipBit; -proto.add = bnAdd; -proto.subtract = bnSubtract; -proto.multiply = bnMultiply; -proto.divide = bnDivide; -proto.remainder = bnRemainder; -proto.divideAndRemainder = bnDivideAndRemainder; -proto.modPow = bnModPow; -proto.modInverse = bnModInverse; -proto.pow = bnPow; -proto.gcd = bnGCD; - -// JSBN-specific extension -proto.square = bnSquare; - -// BigInteger interfaces not implemented in jsbn: - -// BigInteger(int signum, byte[] magnitude) -// double doubleValue() -// float floatValue() -// int hashCode() -// long longValue() -// static BigInteger valueOf(long val) - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); -BigInteger.valueOf = nbv; - - -/// bitcoinjs addons - -/** - * Turns a byte array into a big integer. - * - * This function will interpret a byte array as a big integer in big - * endian notation and ignore leading zeros. - */ -BigInteger.fromByteArrayUnsigned = function(ba) { - - if (!ba.length) { - return new BigInteger.valueOf(0); - } else if (ba[0] & 0x80) { - // Prepend a zero so the BigInteger class doesn't mistake this - // for a negative integer. - return new BigInteger([0].concat(ba)); - } else { - return new BigInteger(ba); - } -}; - -/** - * Parse a signed big integer byte representation. - * - * For details on the format please see BigInteger.toByteArraySigned. - */ -BigInteger.fromByteArraySigned = function(ba) { - // Check for negative value - if (ba[0] & 0x80) { - // Remove sign bit - ba[0] &= 0x7f; - - return BigInteger.fromByteArrayUnsigned(ba).negate(); - } else { - return BigInteger.fromByteArrayUnsigned(ba); - } -}; - -/** - * Returns a byte array representation of the big integer. - * - * This returns the absolute of the contained value in big endian - * form. A value of zero results in an empty array. - */ -BigInteger.prototype.toByteArrayUnsigned = function() { - var ba = this.abs().toByteArray(); - - // Empty array, nothing to do - if (!ba.length) { - return ba; - } - - // remove leading 0 - if (ba[0] === 0) { - ba = ba.slice(1); - } - - // all values must be positive - for (var i=0 ; i 0x00 - * 1 => 0x01 - * -1 => 0x81 - * 127 => 0x7f - * -127 => 0xff - * 128 => 0x0080 - * -128 => 0x8080 - * 255 => 0x00ff - * -255 => 0x80ff - * 16300 => 0x3fac - * -16300 => 0xbfac - * 62300 => 0x00f35c - * -62300 => 0x80f35c -*/ -BigInteger.prototype.toByteArraySigned = function() { - var val = this.toByteArrayUnsigned(); - var neg = this.s < 0; - - // if the first bit is set, we always unshift - // either unshift 0x80 or 0x00 - if (val[0] & 0x80) { - val.unshift((neg) ? 0x80 : 0x00); - } - // if the first bit isn't set, set it if negative - else if (neg) { - val[0] |= 0x80; - } - - return val; -}; \ No newline at end of file diff --git a/scripts/libs/qrcode.js b/scripts/libs/qrcode.js deleted file mode 100644 index 76889b589..000000000 --- a/scripts/libs/qrcode.js +++ /dev/null @@ -1,2297 +0,0 @@ -//--------------------------------------------------------------------- -// -// QR Code Generator for JavaScript -// -// Copyright (c) 2009 Kazuhiko Arase -// -// URL: http://www.d-project.com/ -// -// Licensed under the MIT license: -// http://www.opensource.org/licenses/mit-license.php -// -// The word 'QR Code' is registered trademark of -// DENSO WAVE INCORPORATED -// http://www.denso-wave.com/qrcode/faqpatent-e.html -// -//--------------------------------------------------------------------- - -var qrcode = function() { - - //--------------------------------------------------------------------- - // qrcode - //--------------------------------------------------------------------- - - /** - * qrcode - * @param typeNumber 1 to 40 - * @param errorCorrectionLevel 'L','M','Q','H' - */ - var qrcode = function(typeNumber, errorCorrectionLevel) { - - var PAD0 = 0xEC; - var PAD1 = 0x11; - - var _typeNumber = typeNumber; - var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel]; - var _modules = null; - var _moduleCount = 0; - var _dataCache = null; - var _dataList = []; - - var _this = {}; - - var makeImpl = function(test, maskPattern) { - - _moduleCount = _typeNumber * 4 + 17; - _modules = function(moduleCount) { - var modules = new Array(moduleCount); - for (var row = 0; row < moduleCount; row += 1) { - modules[row] = new Array(moduleCount); - for (var col = 0; col < moduleCount; col += 1) { - modules[row][col] = null; - } - } - return modules; - }(_moduleCount); - - setupPositionProbePattern(0, 0); - setupPositionProbePattern(_moduleCount - 7, 0); - setupPositionProbePattern(0, _moduleCount - 7); - setupPositionAdjustPattern(); - setupTimingPattern(); - setupTypeInfo(test, maskPattern); - - if (_typeNumber >= 7) { - setupTypeNumber(test); - } - - if (_dataCache == null) { - _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList); - } - - mapData(_dataCache, maskPattern); - }; - - var setupPositionProbePattern = function(row, col) { - - for (var r = -1; r <= 7; r += 1) { - - if (row + r <= -1 || _moduleCount <= row + r) continue; - - for (var c = -1; c <= 7; c += 1) { - - if (col + c <= -1 || _moduleCount <= col + c) continue; - - if ( (0 <= r && r <= 6 && (c == 0 || c == 6) ) - || (0 <= c && c <= 6 && (r == 0 || r == 6) ) - || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) { - _modules[row + r][col + c] = true; - } else { - _modules[row + r][col + c] = false; - } - } - } - }; - - var getBestMaskPattern = function() { - - var minLostPoint = 0; - var pattern = 0; - - for (var i = 0; i < 8; i += 1) { - - makeImpl(true, i); - - var lostPoint = QRUtil.getLostPoint(_this); - - if (i == 0 || minLostPoint > lostPoint) { - minLostPoint = lostPoint; - pattern = i; - } - } - - return pattern; - }; - - var setupTimingPattern = function() { - - for (var r = 8; r < _moduleCount - 8; r += 1) { - if (_modules[r][6] != null) { - continue; - } - _modules[r][6] = (r % 2 == 0); - } - - for (var c = 8; c < _moduleCount - 8; c += 1) { - if (_modules[6][c] != null) { - continue; - } - _modules[6][c] = (c % 2 == 0); - } - }; - - var setupPositionAdjustPattern = function() { - - var pos = QRUtil.getPatternPosition(_typeNumber); - - for (var i = 0; i < pos.length; i += 1) { - - for (var j = 0; j < pos.length; j += 1) { - - var row = pos[i]; - var col = pos[j]; - - if (_modules[row][col] != null) { - continue; - } - - for (var r = -2; r <= 2; r += 1) { - - for (var c = -2; c <= 2; c += 1) { - - if (r == -2 || r == 2 || c == -2 || c == 2 - || (r == 0 && c == 0) ) { - _modules[row + r][col + c] = true; - } else { - _modules[row + r][col + c] = false; - } - } - } - } - } - }; - - var setupTypeNumber = function(test) { - - var bits = QRUtil.getBCHTypeNumber(_typeNumber); - - for (var i = 0; i < 18; i += 1) { - var mod = (!test && ( (bits >> i) & 1) == 1); - _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod; - } - - for (var i = 0; i < 18; i += 1) { - var mod = (!test && ( (bits >> i) & 1) == 1); - _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod; - } - }; - - var setupTypeInfo = function(test, maskPattern) { - - var data = (_errorCorrectionLevel << 3) | maskPattern; - var bits = QRUtil.getBCHTypeInfo(data); - - // vertical - for (var i = 0; i < 15; i += 1) { - - var mod = (!test && ( (bits >> i) & 1) == 1); - - if (i < 6) { - _modules[i][8] = mod; - } else if (i < 8) { - _modules[i + 1][8] = mod; - } else { - _modules[_moduleCount - 15 + i][8] = mod; - } - } - - // horizontal - for (var i = 0; i < 15; i += 1) { - - var mod = (!test && ( (bits >> i) & 1) == 1); - - if (i < 8) { - _modules[8][_moduleCount - i - 1] = mod; - } else if (i < 9) { - _modules[8][15 - i - 1 + 1] = mod; - } else { - _modules[8][15 - i - 1] = mod; - } - } - - // fixed module - _modules[_moduleCount - 8][8] = (!test); - }; - - var mapData = function(data, maskPattern) { - - var inc = -1; - var row = _moduleCount - 1; - var bitIndex = 7; - var byteIndex = 0; - var maskFunc = QRUtil.getMaskFunction(maskPattern); - - for (var col = _moduleCount - 1; col > 0; col -= 2) { - - if (col == 6) col -= 1; - - while (true) { - - for (var c = 0; c < 2; c += 1) { - - if (_modules[row][col - c] == null) { - - var dark = false; - - if (byteIndex < data.length) { - dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1); - } - - var mask = maskFunc(row, col - c); - - if (mask) { - dark = !dark; - } - - _modules[row][col - c] = dark; - bitIndex -= 1; - - if (bitIndex == -1) { - byteIndex += 1; - bitIndex = 7; - } - } - } - - row += inc; - - if (row < 0 || _moduleCount <= row) { - row -= inc; - inc = -inc; - break; - } - } - } - }; - - var createBytes = function(buffer, rsBlocks) { - - var offset = 0; - - var maxDcCount = 0; - var maxEcCount = 0; - - var dcdata = new Array(rsBlocks.length); - var ecdata = new Array(rsBlocks.length); - - for (var r = 0; r < rsBlocks.length; r += 1) { - - var dcCount = rsBlocks[r].dataCount; - var ecCount = rsBlocks[r].totalCount - dcCount; - - maxDcCount = Math.max(maxDcCount, dcCount); - maxEcCount = Math.max(maxEcCount, ecCount); - - dcdata[r] = new Array(dcCount); - - for (var i = 0; i < dcdata[r].length; i += 1) { - dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]; - } - offset += dcCount; - - var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); - var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1); - - var modPoly = rawPoly.mod(rsPoly); - ecdata[r] = new Array(rsPoly.getLength() - 1); - for (var i = 0; i < ecdata[r].length; i += 1) { - var modIndex = i + modPoly.getLength() - ecdata[r].length; - ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0; - } - } - - var totalCodeCount = 0; - for (var i = 0; i < rsBlocks.length; i += 1) { - totalCodeCount += rsBlocks[i].totalCount; - } - - var data = new Array(totalCodeCount); - var index = 0; - - for (var i = 0; i < maxDcCount; i += 1) { - for (var r = 0; r < rsBlocks.length; r += 1) { - if (i < dcdata[r].length) { - data[index] = dcdata[r][i]; - index += 1; - } - } - } - - for (var i = 0; i < maxEcCount; i += 1) { - for (var r = 0; r < rsBlocks.length; r += 1) { - if (i < ecdata[r].length) { - data[index] = ecdata[r][i]; - index += 1; - } - } - } - - return data; - }; - - var createData = function(typeNumber, errorCorrectionLevel, dataList) { - - var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel); - - var buffer = qrBitBuffer(); - - for (var i = 0; i < dataList.length; i += 1) { - var data = dataList[i]; - buffer.put(data.getMode(), 4); - buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) ); - data.write(buffer); - } - - // calc num max data. - var totalDataCount = 0; - for (var i = 0; i < rsBlocks.length; i += 1) { - totalDataCount += rsBlocks[i].dataCount; - } - - if (buffer.getLengthInBits() > totalDataCount * 8) { - throw 'code length overflow. (' - + buffer.getLengthInBits() - + '>' - + totalDataCount * 8 - + ')'; - } - - // end code - if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { - buffer.put(0, 4); - } - - // padding - while (buffer.getLengthInBits() % 8 != 0) { - buffer.putBit(false); - } - - // padding - while (true) { - - if (buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(PAD0, 8); - - if (buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(PAD1, 8); - } - - return createBytes(buffer, rsBlocks); - }; - - _this.addData = function(data, mode) { - - mode = mode || 'Byte'; - - var newData = null; - - switch(mode) { - case 'Numeric' : - newData = qrNumber(data); - break; - case 'Alphanumeric' : - newData = qrAlphaNum(data); - break; - case 'Byte' : - newData = qr8BitByte(data); - break; - case 'Kanji' : - newData = qrKanji(data); - break; - default : - throw 'mode:' + mode; - } - - _dataList.push(newData); - _dataCache = null; - }; - - _this.isDark = function(row, col) { - if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) { - throw row + ',' + col; - } - return _modules[row][col]; - }; - - _this.getModuleCount = function() { - return _moduleCount; - }; - - _this.make = function() { - if (_typeNumber < 1) { - var typeNumber = 1; - - for (; typeNumber < 40; typeNumber++) { - var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel); - var buffer = qrBitBuffer(); - - for (var i = 0; i < _dataList.length; i++) { - var data = _dataList[i]; - buffer.put(data.getMode(), 4); - buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) ); - data.write(buffer); - } - - var totalDataCount = 0; - for (var i = 0; i < rsBlocks.length; i++) { - totalDataCount += rsBlocks[i].dataCount; - } - - if (buffer.getLengthInBits() <= totalDataCount * 8) { - break; - } - } - - _typeNumber = typeNumber; - } - - makeImpl(false, getBestMaskPattern() ); - }; - - _this.createTableTag = function(cellSize, margin) { - - cellSize = cellSize || 2; - margin = (typeof margin == 'undefined')? cellSize * 4 : margin; - - var qrHtml = ''; - - qrHtml += ''; - qrHtml += ''; - - for (var r = 0; r < _this.getModuleCount(); r += 1) { - - qrHtml += ''; - - for (var c = 0; c < _this.getModuleCount(); c += 1) { - qrHtml += ''; - } - - qrHtml += ''; - qrHtml += '
'; - } - - qrHtml += '
'; - - return qrHtml; - }; - - _this.createSvgTag = function(cellSize, margin, alt, title) { - - var opts = {}; - if (typeof arguments[0] == 'object') { - // Called by options. - opts = arguments[0]; - // overwrite cellSize and margin. - cellSize = opts.cellSize; - margin = opts.margin; - alt = opts.alt; - title = opts.title; - } - - cellSize = cellSize || 2; - margin = (typeof margin == 'undefined')? cellSize * 4 : margin; - - // Compose alt property surrogate - alt = (typeof alt === 'string') ? {text: alt} : alt || {}; - alt.text = alt.text || null; - alt.id = (alt.text) ? alt.id || 'qrcode-description' : null; - - // Compose title property surrogate - title = (typeof title === 'string') ? {text: title} : title || {}; - title.text = title.text || null; - title.id = (title.text) ? title.id || 'qrcode-title' : null; - - var size = _this.getModuleCount() * cellSize + margin * 2; - var c, mc, r, mr, qrSvg='', rect; - - rect = 'l' + cellSize + ',0 0,' + cellSize + - ' -' + cellSize + ',0 0,-' + cellSize + 'z '; - - qrSvg += '' + - escapeXml(title.text) + '' : ''; - qrSvg += (alt.text) ? '' + - escapeXml(alt.text) + '' : ''; - qrSvg += ''; - qrSvg += ''; - qrSvg += ''; - - return qrSvg; - }; - - _this.createDataURL = function(cellSize, margin) { - - cellSize = cellSize || 2; - margin = (typeof margin == 'undefined')? cellSize * 4 : margin; - - var size = _this.getModuleCount() * cellSize + margin * 2; - var min = margin; - var max = size - margin; - - return createDataURL(size, size, function(x, y) { - if (min <= x && x < max && min <= y && y < max) { - var c = Math.floor( (x - min) / cellSize); - var r = Math.floor( (y - min) / cellSize); - return _this.isDark(r, c)? 0 : 1; - } else { - return 1; - } - } ); - }; - - _this.createImgTag = function(cellSize, margin, alt) { - - cellSize = cellSize || 2; - margin = (typeof margin == 'undefined')? cellSize * 4 : margin; - - var size = _this.getModuleCount() * cellSize + margin * 2; - - var img = ''; - img += '': escaped += '>'; break; - case '&': escaped += '&'; break; - case '"': escaped += '"'; break; - default : escaped += c; break; - } - } - return escaped; - }; - - var _createHalfASCII = function(margin) { - var cellSize = 1; - margin = (typeof margin == 'undefined')? cellSize * 2 : margin; - - var size = _this.getModuleCount() * cellSize + margin * 2; - var min = margin; - var max = size - margin; - - var y, x, r1, r2, p; - - var blocks = { - '██': '█', - '█ ': '▀', - ' █': '▄', - ' ': ' ' - }; - - var blocksLastLineNoMargin = { - '██': '▀', - '█ ': '▀', - ' █': ' ', - ' ': ' ' - }; - - var ascii = ''; - for (y = 0; y < size; y += 2) { - r1 = Math.floor((y - min) / cellSize); - r2 = Math.floor((y + 1 - min) / cellSize); - for (x = 0; x < size; x += 1) { - p = '█'; - - if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) { - p = ' '; - } - - if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) { - p += ' '; - } - else { - p += '█'; - } - - // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square. - ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p]; - } - - ascii += '\n'; - } - - if (size % 2 && margin > 0) { - return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀'); - } - - return ascii.substring(0, ascii.length-1); - }; - - _this.createASCII = function(cellSize, margin) { - cellSize = cellSize || 1; - - if (cellSize < 2) { - return _createHalfASCII(margin); - } - - cellSize -= 1; - margin = (typeof margin == 'undefined')? cellSize * 2 : margin; - - var size = _this.getModuleCount() * cellSize + margin * 2; - var min = margin; - var max = size - margin; - - var y, x, r, p; - - var white = Array(cellSize+1).join('██'); - var black = Array(cellSize+1).join(' '); - - var ascii = ''; - var line = ''; - for (y = 0; y < size; y += 1) { - r = Math.floor( (y - min) / cellSize); - line = ''; - for (x = 0; x < size; x += 1) { - p = 1; - - if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) { - p = 0; - } - - // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square. - line += p ? white : black; - } - - for (r = 0; r < cellSize; r += 1) { - ascii += line + '\n'; - } - } - - return ascii.substring(0, ascii.length-1); - }; - - _this.renderTo2dContext = function(context, cellSize) { - cellSize = cellSize || 2; - var length = _this.getModuleCount(); - for (var row = 0; row < length; row++) { - for (var col = 0; col < length; col++) { - context.fillStyle = _this.isDark(row, col) ? 'black' : 'white'; - context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize); - } - } - } - - return _this; - }; - - //--------------------------------------------------------------------- - // qrcode.stringToBytes - //--------------------------------------------------------------------- - - qrcode.stringToBytesFuncs = { - 'default' : function(s) { - var bytes = []; - for (var i = 0; i < s.length; i += 1) { - var c = s.charCodeAt(i); - bytes.push(c & 0xff); - } - return bytes; - } - }; - - qrcode.stringToBytes = qrcode.stringToBytesFuncs['default']; - - //--------------------------------------------------------------------- - // qrcode.createStringToBytes - //--------------------------------------------------------------------- - - /** - * @param unicodeData base64 string of byte array. - * [16bit Unicode],[16bit Bytes], ... - * @param numChars - */ - qrcode.createStringToBytes = function(unicodeData, numChars) { - - // create conversion map. - - var unicodeMap = function() { - - var bin = base64DecodeInputStream(unicodeData); - var read = function() { - var b = bin.read(); - if (b == -1) throw 'eof'; - return b; - }; - - var count = 0; - var unicodeMap = {}; - while (true) { - var b0 = bin.read(); - if (b0 == -1) break; - var b1 = read(); - var b2 = read(); - var b3 = read(); - var k = String.fromCharCode( (b0 << 8) | b1); - var v = (b2 << 8) | b3; - unicodeMap[k] = v; - count += 1; - } - if (count != numChars) { - throw count + ' != ' + numChars; - } - - return unicodeMap; - }(); - - var unknownChar = '?'.charCodeAt(0); - - return function(s) { - var bytes = []; - for (var i = 0; i < s.length; i += 1) { - var c = s.charCodeAt(i); - if (c < 128) { - bytes.push(c); - } else { - var b = unicodeMap[s.charAt(i)]; - if (typeof b == 'number') { - if ( (b & 0xff) == b) { - // 1byte - bytes.push(b); - } else { - // 2bytes - bytes.push(b >>> 8); - bytes.push(b & 0xff); - } - } else { - bytes.push(unknownChar); - } - } - } - return bytes; - }; - }; - - //--------------------------------------------------------------------- - // QRMode - //--------------------------------------------------------------------- - - var QRMode = { - MODE_NUMBER : 1 << 0, - MODE_ALPHA_NUM : 1 << 1, - MODE_8BIT_BYTE : 1 << 2, - MODE_KANJI : 1 << 3 - }; - - //--------------------------------------------------------------------- - // QRErrorCorrectionLevel - //--------------------------------------------------------------------- - - var QRErrorCorrectionLevel = { - L : 1, - M : 0, - Q : 3, - H : 2 - }; - - //--------------------------------------------------------------------- - // QRMaskPattern - //--------------------------------------------------------------------- - - var QRMaskPattern = { - PATTERN000 : 0, - PATTERN001 : 1, - PATTERN010 : 2, - PATTERN011 : 3, - PATTERN100 : 4, - PATTERN101 : 5, - PATTERN110 : 6, - PATTERN111 : 7 - }; - - //--------------------------------------------------------------------- - // QRUtil - //--------------------------------------------------------------------- - - var QRUtil = function() { - - var PATTERN_POSITION_TABLE = [ - [], - [6, 18], - [6, 22], - [6, 26], - [6, 30], - [6, 34], - [6, 22, 38], - [6, 24, 42], - [6, 26, 46], - [6, 28, 50], - [6, 30, 54], - [6, 32, 58], - [6, 34, 62], - [6, 26, 46, 66], - [6, 26, 48, 70], - [6, 26, 50, 74], - [6, 30, 54, 78], - [6, 30, 56, 82], - [6, 30, 58, 86], - [6, 34, 62, 90], - [6, 28, 50, 72, 94], - [6, 26, 50, 74, 98], - [6, 30, 54, 78, 102], - [6, 28, 54, 80, 106], - [6, 32, 58, 84, 110], - [6, 30, 58, 86, 114], - [6, 34, 62, 90, 118], - [6, 26, 50, 74, 98, 122], - [6, 30, 54, 78, 102, 126], - [6, 26, 52, 78, 104, 130], - [6, 30, 56, 82, 108, 134], - [6, 34, 60, 86, 112, 138], - [6, 30, 58, 86, 114, 142], - [6, 34, 62, 90, 118, 146], - [6, 30, 54, 78, 102, 126, 150], - [6, 24, 50, 76, 102, 128, 154], - [6, 28, 54, 80, 106, 132, 158], - [6, 32, 58, 84, 110, 136, 162], - [6, 26, 54, 82, 110, 138, 166], - [6, 30, 58, 86, 114, 142, 170] - ]; - var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); - var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); - var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); - - var _this = {}; - - var getBCHDigit = function(data) { - var digit = 0; - while (data != 0) { - digit += 1; - data >>>= 1; - } - return digit; - }; - - _this.getBCHTypeInfo = function(data) { - var d = data << 10; - while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { - d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) ); - } - return ( (data << 10) | d) ^ G15_MASK; - }; - - _this.getBCHTypeNumber = function(data) { - var d = data << 12; - while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { - d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) ); - } - return (data << 12) | d; - }; - - _this.getPatternPosition = function(typeNumber) { - return PATTERN_POSITION_TABLE[typeNumber - 1]; - }; - - _this.getMaskFunction = function(maskPattern) { - - switch (maskPattern) { - - case QRMaskPattern.PATTERN000 : - return function(i, j) { return (i + j) % 2 == 0; }; - case QRMaskPattern.PATTERN001 : - return function(i, j) { return i % 2 == 0; }; - case QRMaskPattern.PATTERN010 : - return function(i, j) { return j % 3 == 0; }; - case QRMaskPattern.PATTERN011 : - return function(i, j) { return (i + j) % 3 == 0; }; - case QRMaskPattern.PATTERN100 : - return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; }; - case QRMaskPattern.PATTERN101 : - return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; }; - case QRMaskPattern.PATTERN110 : - return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; }; - case QRMaskPattern.PATTERN111 : - return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; }; - - default : - throw 'bad maskPattern:' + maskPattern; - } - }; - - _this.getErrorCorrectPolynomial = function(errorCorrectLength) { - var a = qrPolynomial([1], 0); - for (var i = 0; i < errorCorrectLength; i += 1) { - a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) ); - } - return a; - }; - - _this.getLengthInBits = function(mode, type) { - - if (1 <= type && type < 10) { - - // 1 - 9 - - switch(mode) { - case QRMode.MODE_NUMBER : return 10; - case QRMode.MODE_ALPHA_NUM : return 9; - case QRMode.MODE_8BIT_BYTE : return 8; - case QRMode.MODE_KANJI : return 8; - default : - throw 'mode:' + mode; - } - - } else if (type < 27) { - - // 10 - 26 - - switch(mode) { - case QRMode.MODE_NUMBER : return 12; - case QRMode.MODE_ALPHA_NUM : return 11; - case QRMode.MODE_8BIT_BYTE : return 16; - case QRMode.MODE_KANJI : return 10; - default : - throw 'mode:' + mode; - } - - } else if (type < 41) { - - // 27 - 40 - - switch(mode) { - case QRMode.MODE_NUMBER : return 14; - case QRMode.MODE_ALPHA_NUM : return 13; - case QRMode.MODE_8BIT_BYTE : return 16; - case QRMode.MODE_KANJI : return 12; - default : - throw 'mode:' + mode; - } - - } else { - throw 'type:' + type; - } - }; - - _this.getLostPoint = function(qrcode) { - - var moduleCount = qrcode.getModuleCount(); - - var lostPoint = 0; - - // LEVEL1 - - for (var row = 0; row < moduleCount; row += 1) { - for (var col = 0; col < moduleCount; col += 1) { - - var sameCount = 0; - var dark = qrcode.isDark(row, col); - - for (var r = -1; r <= 1; r += 1) { - - if (row + r < 0 || moduleCount <= row + r) { - continue; - } - - for (var c = -1; c <= 1; c += 1) { - - if (col + c < 0 || moduleCount <= col + c) { - continue; - } - - if (r == 0 && c == 0) { - continue; - } - - if (dark == qrcode.isDark(row + r, col + c) ) { - sameCount += 1; - } - } - } - - if (sameCount > 5) { - lostPoint += (3 + sameCount - 5); - } - } - }; - - // LEVEL2 - - for (var row = 0; row < moduleCount - 1; row += 1) { - for (var col = 0; col < moduleCount - 1; col += 1) { - var count = 0; - if (qrcode.isDark(row, col) ) count += 1; - if (qrcode.isDark(row + 1, col) ) count += 1; - if (qrcode.isDark(row, col + 1) ) count += 1; - if (qrcode.isDark(row + 1, col + 1) ) count += 1; - if (count == 0 || count == 4) { - lostPoint += 3; - } - } - } - - // LEVEL3 - - for (var row = 0; row < moduleCount; row += 1) { - for (var col = 0; col < moduleCount - 6; col += 1) { - if (qrcode.isDark(row, col) - && !qrcode.isDark(row, col + 1) - && qrcode.isDark(row, col + 2) - && qrcode.isDark(row, col + 3) - && qrcode.isDark(row, col + 4) - && !qrcode.isDark(row, col + 5) - && qrcode.isDark(row, col + 6) ) { - lostPoint += 40; - } - } - } - - for (var col = 0; col < moduleCount; col += 1) { - for (var row = 0; row < moduleCount - 6; row += 1) { - if (qrcode.isDark(row, col) - && !qrcode.isDark(row + 1, col) - && qrcode.isDark(row + 2, col) - && qrcode.isDark(row + 3, col) - && qrcode.isDark(row + 4, col) - && !qrcode.isDark(row + 5, col) - && qrcode.isDark(row + 6, col) ) { - lostPoint += 40; - } - } - } - - // LEVEL4 - - var darkCount = 0; - - for (var col = 0; col < moduleCount; col += 1) { - for (var row = 0; row < moduleCount; row += 1) { - if (qrcode.isDark(row, col) ) { - darkCount += 1; - } - } - } - - var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; - lostPoint += ratio * 10; - - return lostPoint; - }; - - return _this; - }(); - - //--------------------------------------------------------------------- - // QRMath - //--------------------------------------------------------------------- - - var QRMath = function() { - - var EXP_TABLE = new Array(256); - var LOG_TABLE = new Array(256); - - // initialize tables - for (var i = 0; i < 8; i += 1) { - EXP_TABLE[i] = 1 << i; - } - for (var i = 8; i < 256; i += 1) { - EXP_TABLE[i] = EXP_TABLE[i - 4] - ^ EXP_TABLE[i - 5] - ^ EXP_TABLE[i - 6] - ^ EXP_TABLE[i - 8]; - } - for (var i = 0; i < 255; i += 1) { - LOG_TABLE[EXP_TABLE[i] ] = i; - } - - var _this = {}; - - _this.glog = function(n) { - - if (n < 1) { - throw 'glog(' + n + ')'; - } - - return LOG_TABLE[n]; - }; - - _this.gexp = function(n) { - - while (n < 0) { - n += 255; - } - - while (n >= 256) { - n -= 255; - } - - return EXP_TABLE[n]; - }; - - return _this; - }(); - - //--------------------------------------------------------------------- - // qrPolynomial - //--------------------------------------------------------------------- - - function qrPolynomial(num, shift) { - - if (typeof num.length == 'undefined') { - throw num.length + '/' + shift; - } - - var _num = function() { - var offset = 0; - while (offset < num.length && num[offset] == 0) { - offset += 1; - } - var _num = new Array(num.length - offset + shift); - for (var i = 0; i < num.length - offset; i += 1) { - _num[i] = num[i + offset]; - } - return _num; - }(); - - var _this = {}; - - _this.getAt = function(index) { - return _num[index]; - }; - - _this.getLength = function() { - return _num.length; - }; - - _this.multiply = function(e) { - - var num = new Array(_this.getLength() + e.getLength() - 1); - - for (var i = 0; i < _this.getLength(); i += 1) { - for (var j = 0; j < e.getLength(); j += 1) { - num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) ); - } - } - - return qrPolynomial(num, 0); - }; - - _this.mod = function(e) { - - if (_this.getLength() - e.getLength() < 0) { - return _this; - } - - var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) ); - - var num = new Array(_this.getLength() ); - for (var i = 0; i < _this.getLength(); i += 1) { - num[i] = _this.getAt(i); - } - - for (var i = 0; i < e.getLength(); i += 1) { - num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio); - } - - // recursive call - return qrPolynomial(num, 0).mod(e); - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // QRRSBlock - //--------------------------------------------------------------------- - - var QRRSBlock = function() { - - var RS_BLOCK_TABLE = [ - - // L - // M - // Q - // H - - // 1 - [1, 26, 19], - [1, 26, 16], - [1, 26, 13], - [1, 26, 9], - - // 2 - [1, 44, 34], - [1, 44, 28], - [1, 44, 22], - [1, 44, 16], - - // 3 - [1, 70, 55], - [1, 70, 44], - [2, 35, 17], - [2, 35, 13], - - // 4 - [1, 100, 80], - [2, 50, 32], - [2, 50, 24], - [4, 25, 9], - - // 5 - [1, 134, 108], - [2, 67, 43], - [2, 33, 15, 2, 34, 16], - [2, 33, 11, 2, 34, 12], - - // 6 - [2, 86, 68], - [4, 43, 27], - [4, 43, 19], - [4, 43, 15], - - // 7 - [2, 98, 78], - [4, 49, 31], - [2, 32, 14, 4, 33, 15], - [4, 39, 13, 1, 40, 14], - - // 8 - [2, 121, 97], - [2, 60, 38, 2, 61, 39], - [4, 40, 18, 2, 41, 19], - [4, 40, 14, 2, 41, 15], - - // 9 - [2, 146, 116], - [3, 58, 36, 2, 59, 37], - [4, 36, 16, 4, 37, 17], - [4, 36, 12, 4, 37, 13], - - // 10 - [2, 86, 68, 2, 87, 69], - [4, 69, 43, 1, 70, 44], - [6, 43, 19, 2, 44, 20], - [6, 43, 15, 2, 44, 16], - - // 11 - [4, 101, 81], - [1, 80, 50, 4, 81, 51], - [4, 50, 22, 4, 51, 23], - [3, 36, 12, 8, 37, 13], - - // 12 - [2, 116, 92, 2, 117, 93], - [6, 58, 36, 2, 59, 37], - [4, 46, 20, 6, 47, 21], - [7, 42, 14, 4, 43, 15], - - // 13 - [4, 133, 107], - [8, 59, 37, 1, 60, 38], - [8, 44, 20, 4, 45, 21], - [12, 33, 11, 4, 34, 12], - - // 14 - [3, 145, 115, 1, 146, 116], - [4, 64, 40, 5, 65, 41], - [11, 36, 16, 5, 37, 17], - [11, 36, 12, 5, 37, 13], - - // 15 - [5, 109, 87, 1, 110, 88], - [5, 65, 41, 5, 66, 42], - [5, 54, 24, 7, 55, 25], - [11, 36, 12, 7, 37, 13], - - // 16 - [5, 122, 98, 1, 123, 99], - [7, 73, 45, 3, 74, 46], - [15, 43, 19, 2, 44, 20], - [3, 45, 15, 13, 46, 16], - - // 17 - [1, 135, 107, 5, 136, 108], - [10, 74, 46, 1, 75, 47], - [1, 50, 22, 15, 51, 23], - [2, 42, 14, 17, 43, 15], - - // 18 - [5, 150, 120, 1, 151, 121], - [9, 69, 43, 4, 70, 44], - [17, 50, 22, 1, 51, 23], - [2, 42, 14, 19, 43, 15], - - // 19 - [3, 141, 113, 4, 142, 114], - [3, 70, 44, 11, 71, 45], - [17, 47, 21, 4, 48, 22], - [9, 39, 13, 16, 40, 14], - - // 20 - [3, 135, 107, 5, 136, 108], - [3, 67, 41, 13, 68, 42], - [15, 54, 24, 5, 55, 25], - [15, 43, 15, 10, 44, 16], - - // 21 - [4, 144, 116, 4, 145, 117], - [17, 68, 42], - [17, 50, 22, 6, 51, 23], - [19, 46, 16, 6, 47, 17], - - // 22 - [2, 139, 111, 7, 140, 112], - [17, 74, 46], - [7, 54, 24, 16, 55, 25], - [34, 37, 13], - - // 23 - [4, 151, 121, 5, 152, 122], - [4, 75, 47, 14, 76, 48], - [11, 54, 24, 14, 55, 25], - [16, 45, 15, 14, 46, 16], - - // 24 - [6, 147, 117, 4, 148, 118], - [6, 73, 45, 14, 74, 46], - [11, 54, 24, 16, 55, 25], - [30, 46, 16, 2, 47, 17], - - // 25 - [8, 132, 106, 4, 133, 107], - [8, 75, 47, 13, 76, 48], - [7, 54, 24, 22, 55, 25], - [22, 45, 15, 13, 46, 16], - - // 26 - [10, 142, 114, 2, 143, 115], - [19, 74, 46, 4, 75, 47], - [28, 50, 22, 6, 51, 23], - [33, 46, 16, 4, 47, 17], - - // 27 - [8, 152, 122, 4, 153, 123], - [22, 73, 45, 3, 74, 46], - [8, 53, 23, 26, 54, 24], - [12, 45, 15, 28, 46, 16], - - // 28 - [3, 147, 117, 10, 148, 118], - [3, 73, 45, 23, 74, 46], - [4, 54, 24, 31, 55, 25], - [11, 45, 15, 31, 46, 16], - - // 29 - [7, 146, 116, 7, 147, 117], - [21, 73, 45, 7, 74, 46], - [1, 53, 23, 37, 54, 24], - [19, 45, 15, 26, 46, 16], - - // 30 - [5, 145, 115, 10, 146, 116], - [19, 75, 47, 10, 76, 48], - [15, 54, 24, 25, 55, 25], - [23, 45, 15, 25, 46, 16], - - // 31 - [13, 145, 115, 3, 146, 116], - [2, 74, 46, 29, 75, 47], - [42, 54, 24, 1, 55, 25], - [23, 45, 15, 28, 46, 16], - - // 32 - [17, 145, 115], - [10, 74, 46, 23, 75, 47], - [10, 54, 24, 35, 55, 25], - [19, 45, 15, 35, 46, 16], - - // 33 - [17, 145, 115, 1, 146, 116], - [14, 74, 46, 21, 75, 47], - [29, 54, 24, 19, 55, 25], - [11, 45, 15, 46, 46, 16], - - // 34 - [13, 145, 115, 6, 146, 116], - [14, 74, 46, 23, 75, 47], - [44, 54, 24, 7, 55, 25], - [59, 46, 16, 1, 47, 17], - - // 35 - [12, 151, 121, 7, 152, 122], - [12, 75, 47, 26, 76, 48], - [39, 54, 24, 14, 55, 25], - [22, 45, 15, 41, 46, 16], - - // 36 - [6, 151, 121, 14, 152, 122], - [6, 75, 47, 34, 76, 48], - [46, 54, 24, 10, 55, 25], - [2, 45, 15, 64, 46, 16], - - // 37 - [17, 152, 122, 4, 153, 123], - [29, 74, 46, 14, 75, 47], - [49, 54, 24, 10, 55, 25], - [24, 45, 15, 46, 46, 16], - - // 38 - [4, 152, 122, 18, 153, 123], - [13, 74, 46, 32, 75, 47], - [48, 54, 24, 14, 55, 25], - [42, 45, 15, 32, 46, 16], - - // 39 - [20, 147, 117, 4, 148, 118], - [40, 75, 47, 7, 76, 48], - [43, 54, 24, 22, 55, 25], - [10, 45, 15, 67, 46, 16], - - // 40 - [19, 148, 118, 6, 149, 119], - [18, 75, 47, 31, 76, 48], - [34, 54, 24, 34, 55, 25], - [20, 45, 15, 61, 46, 16] - ]; - - var qrRSBlock = function(totalCount, dataCount) { - var _this = {}; - _this.totalCount = totalCount; - _this.dataCount = dataCount; - return _this; - }; - - var _this = {}; - - var getRsBlockTable = function(typeNumber, errorCorrectionLevel) { - - switch(errorCorrectionLevel) { - case QRErrorCorrectionLevel.L : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; - case QRErrorCorrectionLevel.M : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; - case QRErrorCorrectionLevel.Q : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; - case QRErrorCorrectionLevel.H : - return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; - default : - return undefined; - } - }; - - _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) { - - var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel); - - if (typeof rsBlock == 'undefined') { - throw 'bad rs block @ typeNumber:' + typeNumber + - '/errorCorrectionLevel:' + errorCorrectionLevel; - } - - var length = rsBlock.length / 3; - - var list = []; - - for (var i = 0; i < length; i += 1) { - - var count = rsBlock[i * 3 + 0]; - var totalCount = rsBlock[i * 3 + 1]; - var dataCount = rsBlock[i * 3 + 2]; - - for (var j = 0; j < count; j += 1) { - list.push(qrRSBlock(totalCount, dataCount) ); - } - } - - return list; - }; - - return _this; - }(); - - //--------------------------------------------------------------------- - // qrBitBuffer - //--------------------------------------------------------------------- - - var qrBitBuffer = function() { - - var _buffer = []; - var _length = 0; - - var _this = {}; - - _this.getBuffer = function() { - return _buffer; - }; - - _this.getAt = function(index) { - var bufIndex = Math.floor(index / 8); - return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1; - }; - - _this.put = function(num, length) { - for (var i = 0; i < length; i += 1) { - _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1); - } - }; - - _this.getLengthInBits = function() { - return _length; - }; - - _this.putBit = function(bit) { - - var bufIndex = Math.floor(_length / 8); - if (_buffer.length <= bufIndex) { - _buffer.push(0); - } - - if (bit) { - _buffer[bufIndex] |= (0x80 >>> (_length % 8) ); - } - - _length += 1; - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // qrNumber - //--------------------------------------------------------------------- - - var qrNumber = function(data) { - - var _mode = QRMode.MODE_NUMBER; - var _data = data; - - var _this = {}; - - _this.getMode = function() { - return _mode; - }; - - _this.getLength = function(buffer) { - return _data.length; - }; - - _this.write = function(buffer) { - - var data = _data; - - var i = 0; - - while (i + 2 < data.length) { - buffer.put(strToNum(data.substring(i, i + 3) ), 10); - i += 3; - } - - if (i < data.length) { - if (data.length - i == 1) { - buffer.put(strToNum(data.substring(i, i + 1) ), 4); - } else if (data.length - i == 2) { - buffer.put(strToNum(data.substring(i, i + 2) ), 7); - } - } - }; - - var strToNum = function(s) { - var num = 0; - for (var i = 0; i < s.length; i += 1) { - num = num * 10 + chatToNum(s.charAt(i) ); - } - return num; - }; - - var chatToNum = function(c) { - if ('0' <= c && c <= '9') { - return c.charCodeAt(0) - '0'.charCodeAt(0); - } - throw 'illegal char :' + c; - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // qrAlphaNum - //--------------------------------------------------------------------- - - var qrAlphaNum = function(data) { - - var _mode = QRMode.MODE_ALPHA_NUM; - var _data = data; - - var _this = {}; - - _this.getMode = function() { - return _mode; - }; - - _this.getLength = function(buffer) { - return _data.length; - }; - - _this.write = function(buffer) { - - var s = _data; - - var i = 0; - - while (i + 1 < s.length) { - buffer.put( - getCode(s.charAt(i) ) * 45 + - getCode(s.charAt(i + 1) ), 11); - i += 2; - } - - if (i < s.length) { - buffer.put(getCode(s.charAt(i) ), 6); - } - }; - - var getCode = function(c) { - - if ('0' <= c && c <= '9') { - return c.charCodeAt(0) - '0'.charCodeAt(0); - } else if ('A' <= c && c <= 'Z') { - return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10; - } else { - switch (c) { - case ' ' : return 36; - case '$' : return 37; - case '%' : return 38; - case '*' : return 39; - case '+' : return 40; - case '-' : return 41; - case '.' : return 42; - case '/' : return 43; - case ':' : return 44; - default : - throw 'illegal char :' + c; - } - } - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // qr8BitByte - //--------------------------------------------------------------------- - - var qr8BitByte = function(data) { - - var _mode = QRMode.MODE_8BIT_BYTE; - var _data = data; - var _bytes = qrcode.stringToBytes(data); - - var _this = {}; - - _this.getMode = function() { - return _mode; - }; - - _this.getLength = function(buffer) { - return _bytes.length; - }; - - _this.write = function(buffer) { - for (var i = 0; i < _bytes.length; i += 1) { - buffer.put(_bytes[i], 8); - } - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // qrKanji - //--------------------------------------------------------------------- - - var qrKanji = function(data) { - - var _mode = QRMode.MODE_KANJI; - var _data = data; - - var stringToBytes = qrcode.stringToBytesFuncs['SJIS']; - if (!stringToBytes) { - throw 'sjis not supported.'; - } - !function(c, code) { - // self test for sjis support. - var test = stringToBytes(c); - if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) { - throw 'sjis not supported.'; - } - }('\u53cb', 0x9746); - - var _bytes = stringToBytes(data); - - var _this = {}; - - _this.getMode = function() { - return _mode; - }; - - _this.getLength = function(buffer) { - return ~~(_bytes.length / 2); - }; - - _this.write = function(buffer) { - - var data = _bytes; - - var i = 0; - - while (i + 1 < data.length) { - - var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]); - - if (0x8140 <= c && c <= 0x9FFC) { - c -= 0x8140; - } else if (0xE040 <= c && c <= 0xEBBF) { - c -= 0xC140; - } else { - throw 'illegal char at ' + (i + 1) + '/' + c; - } - - c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff); - - buffer.put(c, 13); - - i += 2; - } - - if (i < data.length) { - throw 'illegal char at ' + (i + 1); - } - }; - - return _this; - }; - - //===================================================================== - // GIF Support etc. - // - - //--------------------------------------------------------------------- - // byteArrayOutputStream - //--------------------------------------------------------------------- - - var byteArrayOutputStream = function() { - - var _bytes = []; - - var _this = {}; - - _this.writeByte = function(b) { - _bytes.push(b & 0xff); - }; - - _this.writeShort = function(i) { - _this.writeByte(i); - _this.writeByte(i >>> 8); - }; - - _this.writeBytes = function(b, off, len) { - off = off || 0; - len = len || b.length; - for (var i = 0; i < len; i += 1) { - _this.writeByte(b[i + off]); - } - }; - - _this.writeString = function(s) { - for (var i = 0; i < s.length; i += 1) { - _this.writeByte(s.charCodeAt(i) ); - } - }; - - _this.toByteArray = function() { - return _bytes; - }; - - _this.toString = function() { - var s = ''; - s += '['; - for (var i = 0; i < _bytes.length; i += 1) { - if (i > 0) { - s += ','; - } - s += _bytes[i]; - } - s += ']'; - return s; - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // base64EncodeOutputStream - //--------------------------------------------------------------------- - - var base64EncodeOutputStream = function() { - - var _buffer = 0; - var _buflen = 0; - var _length = 0; - var _base64 = ''; - - var _this = {}; - - var writeEncoded = function(b) { - _base64 += String.fromCharCode(encode(b & 0x3f) ); - }; - - var encode = function(n) { - if (n < 0) { - // error. - } else if (n < 26) { - return 0x41 + n; - } else if (n < 52) { - return 0x61 + (n - 26); - } else if (n < 62) { - return 0x30 + (n - 52); - } else if (n == 62) { - return 0x2b; - } else if (n == 63) { - return 0x2f; - } - throw 'n:' + n; - }; - - _this.writeByte = function(n) { - - _buffer = (_buffer << 8) | (n & 0xff); - _buflen += 8; - _length += 1; - - while (_buflen >= 6) { - writeEncoded(_buffer >>> (_buflen - 6) ); - _buflen -= 6; - } - }; - - _this.flush = function() { - - if (_buflen > 0) { - writeEncoded(_buffer << (6 - _buflen) ); - _buffer = 0; - _buflen = 0; - } - - if (_length % 3 != 0) { - // padding - var padlen = 3 - _length % 3; - for (var i = 0; i < padlen; i += 1) { - _base64 += '='; - } - } - }; - - _this.toString = function() { - return _base64; - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // base64DecodeInputStream - //--------------------------------------------------------------------- - - var base64DecodeInputStream = function(str) { - - var _str = str; - var _pos = 0; - var _buffer = 0; - var _buflen = 0; - - var _this = {}; - - _this.read = function() { - - while (_buflen < 8) { - - if (_pos >= _str.length) { - if (_buflen == 0) { - return -1; - } - throw 'unexpected end of file./' + _buflen; - } - - var c = _str.charAt(_pos); - _pos += 1; - - if (c == '=') { - _buflen = 0; - return -1; - } else if (c.match(/^\s$/) ) { - // ignore if whitespace. - continue; - } - - _buffer = (_buffer << 6) | decode(c.charCodeAt(0) ); - _buflen += 6; - } - - var n = (_buffer >>> (_buflen - 8) ) & 0xff; - _buflen -= 8; - return n; - }; - - var decode = function(c) { - if (0x41 <= c && c <= 0x5a) { - return c - 0x41; - } else if (0x61 <= c && c <= 0x7a) { - return c - 0x61 + 26; - } else if (0x30 <= c && c <= 0x39) { - return c - 0x30 + 52; - } else if (c == 0x2b) { - return 62; - } else if (c == 0x2f) { - return 63; - } else { - throw 'c:' + c; - } - }; - - return _this; - }; - - //--------------------------------------------------------------------- - // gifImage (B/W) - //--------------------------------------------------------------------- - - var gifImage = function(width, height) { - - var _width = width; - var _height = height; - var _data = new Array(width * height); - - var _this = {}; - - _this.setPixel = function(x, y, pixel) { - _data[y * _width + x] = pixel; - }; - - _this.write = function(out) { - - //--------------------------------- - // GIF Signature - - out.writeString('GIF87a'); - - //--------------------------------- - // Screen Descriptor - - out.writeShort(_width); - out.writeShort(_height); - - out.writeByte(0x80); // 2bit - out.writeByte(0); - out.writeByte(0); - - //--------------------------------- - // Global Color Map - - // black - out.writeByte(0x00); - out.writeByte(0x00); - out.writeByte(0x00); - - // white - out.writeByte(0xff); - out.writeByte(0xff); - out.writeByte(0xff); - - //--------------------------------- - // Image Descriptor - - out.writeString(','); - out.writeShort(0); - out.writeShort(0); - out.writeShort(_width); - out.writeShort(_height); - out.writeByte(0); - - //--------------------------------- - // Local Color Map - - //--------------------------------- - // Raster Data - - var lzwMinCodeSize = 2; - var raster = getLZWRaster(lzwMinCodeSize); - - out.writeByte(lzwMinCodeSize); - - var offset = 0; - - while (raster.length - offset > 255) { - out.writeByte(255); - out.writeBytes(raster, offset, 255); - offset += 255; - } - - out.writeByte(raster.length - offset); - out.writeBytes(raster, offset, raster.length - offset); - out.writeByte(0x00); - - //--------------------------------- - // GIF Terminator - out.writeString(';'); - }; - - var bitOutputStream = function(out) { - - var _out = out; - var _bitLength = 0; - var _bitBuffer = 0; - - var _this = {}; - - _this.write = function(data, length) { - - if ( (data >>> length) != 0) { - throw 'length over'; - } - - while (_bitLength + length >= 8) { - _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) ); - length -= (8 - _bitLength); - data >>>= (8 - _bitLength); - _bitBuffer = 0; - _bitLength = 0; - } - - _bitBuffer = (data << _bitLength) | _bitBuffer; - _bitLength = _bitLength + length; - }; - - _this.flush = function() { - if (_bitLength > 0) { - _out.writeByte(_bitBuffer); - } - }; - - return _this; - }; - - var getLZWRaster = function(lzwMinCodeSize) { - - var clearCode = 1 << lzwMinCodeSize; - var endCode = (1 << lzwMinCodeSize) + 1; - var bitLength = lzwMinCodeSize + 1; - - // Setup LZWTable - var table = lzwTable(); - - for (var i = 0; i < clearCode; i += 1) { - table.add(String.fromCharCode(i) ); - } - table.add(String.fromCharCode(clearCode) ); - table.add(String.fromCharCode(endCode) ); - - var byteOut = byteArrayOutputStream(); - var bitOut = bitOutputStream(byteOut); - - // clear code - bitOut.write(clearCode, bitLength); - - var dataIndex = 0; - - var s = String.fromCharCode(_data[dataIndex]); - dataIndex += 1; - - while (dataIndex < _data.length) { - - var c = String.fromCharCode(_data[dataIndex]); - dataIndex += 1; - - if (table.contains(s + c) ) { - - s = s + c; - - } else { - - bitOut.write(table.indexOf(s), bitLength); - - if (table.size() < 0xfff) { - - if (table.size() == (1 << bitLength) ) { - bitLength += 1; - } - - table.add(s + c); - } - - s = c; - } - } - - bitOut.write(table.indexOf(s), bitLength); - - // end code - bitOut.write(endCode, bitLength); - - bitOut.flush(); - - return byteOut.toByteArray(); - }; - - var lzwTable = function() { - - var _map = {}; - var _size = 0; - - var _this = {}; - - _this.add = function(key) { - if (_this.contains(key) ) { - throw 'dup key:' + key; - } - _map[key] = _size; - _size += 1; - }; - - _this.size = function() { - return _size; - }; - - _this.indexOf = function(key) { - return _map[key]; - }; - - _this.contains = function(key) { - return typeof _map[key] != 'undefined'; - }; - - return _this; - }; - - return _this; - }; - - var createDataURL = function(width, height, getPixel) { - var gif = gifImage(width, height); - for (var y = 0; y < height; y += 1) { - for (var x = 0; x < width; x += 1) { - gif.setPixel(x, y, getPixel(x, y) ); - } - } - - var b = byteArrayOutputStream(); - gif.write(b); - - var base64 = base64EncodeOutputStream(); - var bytes = b.toByteArray(); - for (var i = 0; i < bytes.length; i += 1) { - base64.writeByte(bytes[i]); - } - base64.flush(); - - return 'data:image/gif;base64,' + base64; - }; - - //--------------------------------------------------------------------- - // returns qrcode function. - - return qrcode; -}(); - -// multibyte support -!function() { - - qrcode.stringToBytesFuncs['UTF-8'] = function(s) { - // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array - function toUTF8Array(str) { - var utf8 = []; - for (var i=0; i < str.length; i++) { - var charcode = str.charCodeAt(i); - if (charcode < 0x80) utf8.push(charcode); - else if (charcode < 0x800) { - utf8.push(0xc0 | (charcode >> 6), - 0x80 | (charcode & 0x3f)); - } - else if (charcode < 0xd800 || charcode >= 0xe000) { - utf8.push(0xe0 | (charcode >> 12), - 0x80 | ((charcode>>6) & 0x3f), - 0x80 | (charcode & 0x3f)); - } - // surrogate pair - else { - i++; - // UTF-16 encodes 0x10000-0x10FFFF by - // subtracting 0x10000 and splitting the - // 20 bits of 0x0-0xFFFFF into two halves - charcode = 0x10000 + (((charcode & 0x3ff)<<10) - | (str.charCodeAt(i) & 0x3ff)); - utf8.push(0xf0 | (charcode >>18), - 0x80 | ((charcode>>12) & 0x3f), - 0x80 | ((charcode>>6) & 0x3f), - 0x80 | (charcode & 0x3f)); - } - } - return utf8; - } - return toUTF8Array(s); - }; - -}(); - -(function (factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } -}(function () { - return qrcode; -})); diff --git a/scripts/libs/ripemd160.js b/scripts/libs/ripemd160.js deleted file mode 100644 index 53d10c56f..000000000 --- a/scripts/libs/ripemd160.js +++ /dev/null @@ -1,192 +0,0 @@ -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -/** @preserve -(c) 2012 by Cédric Mesnil. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Constants table -var zl = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, - 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, - 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, - 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]; -var zr = [ - 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, - 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, - 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, - 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, - 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]; -var sl = [ - 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, - 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, - 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, - 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, - 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]; -var sr = [ - 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, - 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, - 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, - 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, - 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]; - -var hl = [ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]; -var hr = [ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]; - -var bytesToWords = function (bytes) { - var words = []; - for (var i = 0, b = 0; i < bytes.length; i++, b += 8) { - words[b >>> 5] |= bytes[i] << (24 - b % 32); - } - return words; -}; - -var wordsToBytes = function (words) { - var bytes = []; - for (var b = 0; b < words.length * 32; b += 8) { - bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); - } - return bytes; -}; - -var processBlock = function (H, M, offset) { - - // Swap endian - for (var i = 0; i < 16; i++) { - var offset_i = offset + i; - var M_offset_i = M[offset_i]; - - // Swap - M[offset_i] = ( - (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | - (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) - ); - } - - // Working variables - var al, bl, cl, dl, el; - var ar, br, cr, dr, er; - - ar = al = H[0]; - br = bl = H[1]; - cr = cl = H[2]; - dr = dl = H[3]; - er = el = H[4]; - // Computation - var t; - for (var i = 0; i < 80; i += 1) { - t = (al + M[offset+zl[i]])|0; - if (i<16){ - t += f1(bl,cl,dl) + hl[0]; - } else if (i<32) { - t += f2(bl,cl,dl) + hl[1]; - } else if (i<48) { - t += f3(bl,cl,dl) + hl[2]; - } else if (i<64) { - t += f4(bl,cl,dl) + hl[3]; - } else {// if (i<80) { - t += f5(bl,cl,dl) + hl[4]; - } - t = t|0; - t = rotl(t,sl[i]); - t = (t+el)|0; - al = el; - el = dl; - dl = rotl(cl, 10); - cl = bl; - bl = t; - - t = (ar + M[offset+zr[i]])|0; - if (i<16){ - t += f5(br,cr,dr) + hr[0]; - } else if (i<32) { - t += f4(br,cr,dr) + hr[1]; - } else if (i<48) { - t += f3(br,cr,dr) + hr[2]; - } else if (i<64) { - t += f2(br,cr,dr) + hr[3]; - } else {// if (i<80) { - t += f1(br,cr,dr) + hr[4]; - } - t = t|0; - t = rotl(t,sr[i]) ; - t = (t+er)|0; - ar = er; - er = dr; - dr = rotl(cr, 10); - cr = br; - br = t; - } - // Intermediate hash value - t = (H[1] + cl + dr)|0; - H[1] = (H[2] + dl + er)|0; - H[2] = (H[3] + el + ar)|0; - H[3] = (H[4] + al + br)|0; - H[4] = (H[0] + bl + cr)|0; - H[0] = t; -}; - -function f1(x, y, z) { - return ((x) ^ (y) ^ (z)); -} - -function f2(x, y, z) { - return (((x)&(y)) | ((~x)&(z))); -} - -function f3(x, y, z) { - return (((x) | (~(y))) ^ (z)); -} - -function f4(x, y, z) { - return (((x) & (z)) | ((y)&(~(z)))); -} - -function f5(x, y, z) { - return ((x) ^ ((y) |(~(z)))); -} - -function rotl(x,n) { - return (x<>>(32-n)); -} - -function ripemd160(message) { - var H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]; - - var m = bytesToWords(message); - - var nBitsLeft = message.length * 8; - var nBitsTotal = message.length * 8; - - // Add padding - m[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - m[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( - (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | - (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) - ); - - for (var i=0 ; i>> 24)) & 0x00ff00ff) | - (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); - } - - var digestbytes = wordsToBytes(H); - return digestbytes; -} \ No newline at end of file diff --git a/scripts/libs/script.js b/scripts/libs/script.js deleted file mode 100644 index 0ffe0c3f7..000000000 --- a/scripts/libs/script.js +++ /dev/null @@ -1,17 +0,0 @@ -function createRawTransaction() { - var trx = bitjs.transaction(); - var txid = document.getElementById("prevTrxHash").value; - var index = document.getElementById("index").value; - var script = document.getElementById("script").value; - trx.addinput(txid,index,script); - var address = document.getElementById("address1").value; - var value = document.getElementById("value1").value; - trx.addoutput(address,value); - var address = document.getElementById("address2").value; - var value = document.getElementById("value2").value; - trx.addoutput(address,value); - var wif = document.getElementById("wif").value; - var textArea = document.getElementById("rawTrx"); - textArea.value = trx.sign(wif,1); //SIGHASH_ALL DEFAULT 1 - -} \ No newline at end of file diff --git a/scripts/libs/sha256.js b/scripts/libs/sha256.js deleted file mode 100644 index b0a96e0a4..000000000 --- a/scripts/libs/sha256.js +++ /dev/null @@ -1,28 +0,0 @@ -/** -Copyright (c) 2008-2020 Brian Turek, 1998-2009 Paul Johnston & Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of the the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(t=t||self).jsSHA=r()}(this,(function(){"use strict";var t=function(r,n){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,r){t.__proto__=r}||function(t,r){for(var n in r)r.hasOwnProperty(n)&&(t[n]=r[n])})(r,n)};var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function n(t,r,n,i){var e,o,u,s=r||[0],f=(n=n||0)>>>3,h=-1===i?3:0;for(e=0;e>>2,s.length<=o&&s.push(0),s[o]|=t[e]<<8*(h+i*(u%4));return{value:s,binLen:8*t.length+n}}function i(t,i,e){switch(i){case"UTF8":case"UTF16BE":case"UTF16LE":break;default:throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE")}switch(t){case"HEX":return function(t,r,n){return function(t,r,n,i){var e,o,u,s;if(0!=t.length%2)throw new Error("String of HEX type must be in byte increments");var f=r||[0],h=(n=n||0)>>>3,a=-1===i?3:0;for(e=0;e>>1)+h)>>>2;f.length<=u;)f.push(0);f[u]|=o<<8*(a+i*(s%4))}return{value:f,binLen:4*t.length+n}}(t,r,n,e)};case"TEXT":return function(t,r,n){return function(t,r,n,i,e){var o,u,s,f,h,a,c,w,v=0,E=n||[0],A=(i=i||0)>>>3;if("UTF8"===r)for(c=-1===e?3:0,s=0;s(o=t.charCodeAt(s))?u.push(o):2048>o?(u.push(192|o>>>6),u.push(128|63&o)):55296>o||57344<=o?u.push(224|o>>>12,128|o>>>6&63,128|63&o):(s+=1,o=65536+((1023&o)<<10|1023&t.charCodeAt(s)),u.push(240|o>>>18,128|o>>>12&63,128|o>>>6&63,128|63&o)),f=0;f>>2;E.length<=h;)E.push(0);E[h]|=u[f]<<8*(c+e*(a%4)),v+=1}else for(c=-1===e?2:0,w="UTF16LE"===r&&1!==e||"UTF16LE"!==r&&1===e,s=0;s>>8),h=(a=v+A)>>>2;E.length<=h;)E.push(0);E[h]|=o<<8*(c+e*(a%4)),v+=2}return{value:E,binLen:8*v+i}}(t,i,r,n,e)};case"B64":return function(t,n,i){return function(t,n,i,e){var o,u,s,f,h,a,c=0,w=n||[0],v=(i=i||0)>>>3,E=-1===e?3:0,A=t.indexOf("=");if(-1===t.search(/^[a-zA-Z0-9=+/]+$/))throw new Error("Invalid character in base-64 string");if(t=t.replace(/=/g,""),-1!==A&&A !n.includes("'")) + .join('/') + ); + } else { + child = this._hdKey.derive(path); + } + return deriveAddress({ publicKey: bytesToHex(child.publicKey) }); + } + + wipePrivateData(nAccount) { + if (this._isViewOnly) return; + + this._hdKey = HDKey.fromExtendedKey(this.getKeyToExport(nAccount)); + this._isViewOnly = true; + } + getKeyToExport(nAccount) { + if (this._isViewOnly) return this._hdKey.publicExtendedKey; + // We need the xpub to point at the account level + return this._hdKey.derive( + this.getDerivationPath(nAccount, 0, 0) + .split('/') + .slice(0, 4) + .join('/') + ).publicExtendedKey; + } + + getDerivationPath(nAccount, nReceiving, nIndex) { + return `m/44'/${cChainParams.current.BIP44_TYPE}'/${nAccount}'/${nReceiving}/${nIndex}`; + } +} + +export class HardwareWalletMasterKey extends HdMasterKey { + /** + * Trick to get private constructors + */ + static #initializing = false; + constructor(xpub) { + if (!HardwareWalletMasterKey.#initializing) { + throw new Error( + 'Hardware wallet master keys must be created with create' + ); + } + HardwareWalletMasterKey.#initializing = false; + super({ + xpub, + }); + this._isHardwareWallet = true; + this._isViewOnly = true; + } + + /** + * @param {number} nAccount + * @returns {Promise} + */ + static async create(nAccount = 0) { + const path = this.getDerivationPath(nAccount, 0, 0) + .split('/') + .slice(0, 4) + .join('/'); + const xpub = await getHardwareWalletKeys(path, true, false); + if (!xpub) throw new Error('Failed to get hardware wallet keys.'); + HardwareWalletMasterKey.#initializing = true; + return new HardwareWalletMasterKey(xpub); + } + + async getPublicKey(path, { verify } = {}) { + return deriveAddress({ + publicKey: await getHardwareWalletKeys(path, false, verify), + output: 'COMPRESSED_HEX', + }); + } + + /** + * Verifies that the address is correct by asking the ledger + * directly and then the user. + * This is considerably slower than deriving the key ourselves + * with `getAddress`, but should be used every time we want to be sure + * the address cannot be tampered with + * @param {string} path - bip32 path + * @returns {Promise} address or null if the user rejected the verification + */ + async verifyAddress(path) { + const publicKey = await getHardwareWalletKeys(path); + + return deriveAddress({ publicKey }); + } + + getDerivationPath(nAccount, nReceiving, nIndex) { + return HardwareWalletMasterKey.getDerivationPath( + nAccount, + nReceiving, + nIndex + ); + } + + static getDerivationPath(nAccount, nReceiving, nIndex) { + return `m/44'/${cChainParams.current.BIP44_TYPE_LEDGER}'/${nAccount}'/${nReceiving}/${nIndex}`; + } +} + +export class LegacyMasterKey extends MasterKey { + constructor({ pkBytes, address }) { + super(); + this._isHD = false; + this._isHardwareWallet = false; + this._pkBytes = pkBytes; + this._address = address || super.getAddress(); + this._isViewOnly = !!address; + } + + getAddress() { + return this._address; + } + + getKeyToExport(_nAccount) { + return this._address; + } + + getPrivateKeyBytes(_path) { + if (this.isViewOnly) { + throw new Error( + 'Trying to get private key bytes from a view only key' + ); + } + return this._pkBytes; + } + + get keyToBackup() { + return generateOrEncodePrivkey(this._pkBytes).strWIF; + } + + getxpub(_path) { + throw new Error( + 'Trying to get an extended public key from a legacy address' + ); + } + + wipePrivateData(_nAccount) { + this._pkBytes = null; + this._isViewOnly = true; + } + + /** + * This is a bit of a hack: + * Legacy master keys don't need derivation paths. + * We're going to make one nonetheless, to generalize things a bit + */ + getDerivationPath(_nAccount, _nReceiving, _nIndex) { + return `:)//${cChainParams.current.BIP44_TYPE}'`; + } +} diff --git a/scripts/masternode.js b/scripts/masternode.js new file mode 100644 index 000000000..75d769c60 --- /dev/null +++ b/scripts/masternode.js @@ -0,0 +1,630 @@ +import { cNode, cExplorer } from './settings.js'; +import { cChainParams, COIN } from './chain_params.js'; +import { wallet } from './wallet.js'; +import { parseWIF, deriveAddress } from './encoding.js'; +import { cHardwareWallet } from './ledger.js'; +import { dSHA256, bytesToHex, hexToBytes } from './utils.js'; +import { Buffer } from 'buffer'; +import { Address6 } from 'ip-address'; +import * as nobleSecp256k1 from '@noble/secp256k1'; +import { OP } from './script.js'; +import bs58 from 'bs58'; +import base32 from 'base32'; +import { isStandardAddress } from './misc.js'; + +/** + * Construct a Masternode + * @param {string} [masternode.walletPrivateKeyPath] - BIP39 path pointing to the private key holding the collateral. Optional if not HD + * @param {string} masternode.mnPrivateKey - Masternode private key. Must be uncompressed WIF + * @param {string} masternode.collateralTxId - Must be a UTXO pointing to the collateral + * @param {number} masternode.outidx - The output id of the collateral starting from 0 + * @param {string} masternode.addr - IPV4 address in the form `ip:port` + */ +export default class Masternode { + constructor({ + walletPrivateKeyPath, + mnPrivateKey, + collateralTxId, + outidx, + addr, + } = {}) { + this.walletPrivateKeyPath = walletPrivateKeyPath; + this.mnPrivateKey = mnPrivateKey; + this.collateralTxId = collateralTxId; + this.outidx = outidx; + this.addr = addr; + } + /** + * @type {[string, number]} array of vote hash and corresponding vote for the current session + */ + static sessionVotes = []; + + async _getWalletPrivateKey() { + return await wallet + .getMasterKey() + .getPrivateKey(this.walletPrivateKeyPath); + } + + /** + @return {Promise} The object containing masternode information for this masternode + */ + async getFullData() { + const strURL = `${cNode.url}/listmasternodes?params=${this.collateralTxId}`; + try { + const cMasternodes = (await (await fetch(strURL)).json()).filter( + (m) => m.outidx === this.outidx + ); + if (cMasternodes.length > 0) { + return cMasternodes[0]; + } else { + return { status: 'MISSING' }; + } + } catch (e) { + //this is the unfortunate state in which the node is not reachable + console.error(e); + return 'EXPLORER_DOWN'; + } + } + + /** + @return {Promise} The status of this masternode. + */ + async getStatus() { + const cMasternode = await this.getFullData(); + return cMasternode ? cMasternode.status : 'MISSING'; + } + + /** + * @param {string} ip + * @param {number} port + * @returns {string} hex representation of the IP + port pair + */ + static _decodeIpAddress(ip, port) { + /** + * @type {Array?} + */ + let bytes; + if (ip.endsWith('.onion')) { + const onionBytes = base32 + .decode(ip.slice(0, -6)) + .split('') + .map((c) => c.charCodeAt(0)); + switch (onionBytes.length) { + case 10: + bytes = [0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, ...onionBytes]; + break; + case 35: + bytes = [0x04, 32, ...onionBytes.slice(0, 32)]; + break; + default: + throw new Error('Invalid onion address'); + } + } else { + const address = ip.includes('.') + ? Address6.fromAddress4(ip) + : new Address6(ip); + bytes = address.toUnsignedByteArray(); + } + const res = + bytesToHex([ + ...new Array(Math.max(16 - bytes.length, 0)).fill(0), + ...bytes, + ]) + bytesToHex(Masternode._numToBytes(port, 2, false)); + return res; + } + + static _numToBytes(number, numBytes = 8, littleEndian = true) { + const bytes = []; + for (let i = 0; i < numBytes; i++) { + bytes.push((number / 2 ** (8 * i)) & 0xff); + } + return littleEndian ? bytes : bytes.reverse(); + } + + /** + * @param {Object} message - message to encode + * @param {string} message.vin.txid - transaction id of the collateral + * @param {number} message.vin.idx - output id of the collateral starting from 0 + * @param {string} message.blockHash - latest blockhash + * @param {number} message.sigTime - current time in seconds since UNIX epoch + * @return {Array} Returns the unsigned ping message. It needs to be signed with the MN private key + */ + static getPingSignature({ vin, blockHash, sigTime }) { + const ping = [ + ...hexToBytes(vin.txid).reverse(), + ...Masternode._numToBytes(vin.idx, 4, true), + // Should be tx sequence, but 0xffffff is fine + ...[0, 255, 255, 255, 255], + ...hexToBytes(blockHash).reverse(), + ...Masternode._numToBytes(sigTime, 8, true), + ]; + return dSHA256(ping); + } + + /** + * @param {Object} message - Message to encode + * @param {string} message.walletPrivateKey - private key of the collateral + * @param {string} message.addr - Masternode ipv4 with port + * @param {string} message.mnPrivateKey - private key of masternode + * @param {number} message.sigTime - current time in seconds since UNIX epoch + * @return {string} The message to be signed with the collateral private key. + * it needs to be padded with "\x18DarkNet Signed Message:\n" + Message length + Message + * Then hashed two times with SHA256 + */ + static getToSign({ publicKey, addr, mnPrivateKey, sigTime }) { + let ip, port; + if (addr.includes('.')) { + // IPv4 + [ip, port] = addr.split(':'); + } else { + // IPv6 + [ip, port] = addr.slice(1).split(']'); + port = port.slice(1); + } + + const mnPublicKey = hexToBytes( + deriveAddress({ + pkBytes: parseWIF(mnPrivateKey, true), + output: 'UNCOMPRESSED_HEX', + }) + ); + + const pkt = [ + ...Masternode._numToBytes(1, 4, true), // Message version + ...hexToBytes(Masternode._decodeIpAddress(ip, port)), // Encoded ip + port + ...Masternode._numToBytes(sigTime, 8, true), + ...Masternode._numToBytes(publicKey.length, 1, true), // Collateral public key length + ...publicKey, + ...Masternode._numToBytes(mnPublicKey.length, 1, true), // Masternode public key length + ...mnPublicKey, + ...Masternode._numToBytes( + cChainParams.current.PROTOCOL_VERSION, + 4, + true + ), // Protocol version + ]; + return bytesToHex(dSHA256(pkt).reverse()); + } + + /** + * @return {Promise} The last block hash + */ + static async getLastBlockHash() { + const status = await (await fetch(`${cExplorer.url}/api/`)).json(); + return status.backend.bestBlockHash; + } + + /** + * @return {Promise} The signed message signed with the collateral private key + */ + async getSignedMessage(sigTime) { + const toSign = Masternode.getToSign({ + addr: this.addr, + publicKey: await this.getWalletPublicKey(), + mnPrivateKey: this.mnPrivateKey, + sigTime, + }); + + if (wallet.isHardwareWallet()) { + const { r, s, v } = await cHardwareWallet.signMessage( + this.walletPrivateKeyPath, + bytesToHex(toSign) + ); + return [v + 31, ...hexToBytes(r), ...hexToBytes(s)]; + } else { + const padding = '\x18DarkNet Signed Message:\n' + .split('') + .map((c) => c.charCodeAt(0)); + const walletPrivateKey = await this._getWalletPrivateKey(); + + const message = toSign.split('').map((c) => c.charCodeAt(0)); + const hash = dSHA256( + padding.concat(message.length).concat(message) + ); + const [signature, v] = await nobleSecp256k1.sign( + hash, + parseWIF(walletPrivateKey, true), + { der: false, recovered: true } + ); + return [v + 31, ...signature]; + } + } + /** + * @return {Promise} The signed ping message signed with the masternode private key + */ + async getSignedPingMessage(sigTime, blockHash) { + const toSign = Masternode.getPingSignature({ + vin: { + txid: this.collateralTxId, + idx: this.outidx, + }, + blockHash, + sigTime, + }); + const [signature, v] = await nobleSecp256k1.sign( + toSign, + parseWIF(this.mnPrivateKey, true), + { der: false, recovered: true } + ); + return [v + 27, ...signature]; + } + + async getWalletPublicKey() { + if (wallet.isHardwareWallet()) { + return hexToBytes( + await wallet + .getMasterKey() + .getPublicKey(this.walletPrivateKeyPath) + ); + } else { + const walletPrivateKey = await this._getWalletPrivateKey(); + return hexToBytes( + deriveAddress({ + pkBytes: parseWIF(walletPrivateKey, true), + output: 'COMPRESSED_HEX', + }) + ); + } + } + + /** + * Get the message encoded to hex used to start a masternode + * It uses to two signatures: `getPingSignature()` which is signed + * With the masternode private key, and `getToSign()` which is signed with + * The collateral private key + * @return {Promise} The message used to start a masternode. + */ + async broadcastMessageToHex() { + const sigTime = Math.round(Date.now() / 1000); + const blockHash = await Masternode.getLastBlockHash(); + let ip, port; + if (this.addr.includes('.')) { + // IPv4 + [ip, port] = this.addr.split(':'); + } else { + // IPv6 + [ip, port] = this.addr.slice(1).split(']'); + port = port.slice(1); + } + const walletPublicKey = await this.getWalletPublicKey(); + + const mnPublicKey = hexToBytes( + deriveAddress({ + pkBytes: parseWIF(this.mnPrivateKey, true), + output: 'UNCOMPRESSED_HEX', + compress: false, + }) + ); + + const sigBytes = await this.getSignedMessage(sigTime); + const sigPingBytes = await this.getSignedPingMessage( + sigTime, + blockHash + ); + + const message = [ + ...hexToBytes(this.collateralTxId).reverse(), + ...Masternode._numToBytes(this.outidx, 4, true), + ...Masternode._numToBytes(0, 1, true), // Message version + ...Masternode._numToBytes(0xffffffff, 4, true), + ...hexToBytes(Masternode._decodeIpAddress(ip, port)), + ...Masternode._numToBytes(walletPublicKey.length, 1, true), + ...walletPublicKey, + ...Masternode._numToBytes(mnPublicKey.length, 1, true), + ...mnPublicKey, + ...Masternode._numToBytes(sigBytes.length, 1, true), + ...sigBytes, + ...Masternode._numToBytes(sigTime, 8, true), + ...Masternode._numToBytes( + cChainParams.current.PROTOCOL_VERSION, + 4, + true + ), + ...hexToBytes(this.collateralTxId).reverse(), + ...Masternode._numToBytes(this.outidx, 4, true), + ...Masternode._numToBytes(0, 1, true), + ...Masternode._numToBytes(0xffffffff, 4, true), + ...hexToBytes(blockHash).reverse(), + ...Masternode._numToBytes(sigTime, 8, true), + ...Masternode._numToBytes(sigPingBytes.length, 1, true), + ...sigPingBytes, + ...Masternode._numToBytes(1, 4, true), + ...Masternode._numToBytes(1, 4, true), + ]; + return bytesToHex(message); + } + + /** + * Start the masternode + * @return {Promise} Whether or not the message was relayed successfully. This does not necessarely mean + * starting was successful, but only that the node was able to decode the broadcast. + */ + async start() { + const message = await this.broadcastMessageToHex(); + const url = `${cNode.url}/relaymasternodebroadcast?params=${message}`; + const response = await (await fetch(url)).text(); + return response.includes('Masternode broadcast sent'); + } + + /** + * + * @param {object} options + * @param {bool} options.fAllowFinished - Pass `true` to stop filtering proposals if finished + * @return {Promise} A list of currently active proposal + */ + static async getProposals({ fAllowFinished = false } = {}) { + const url = `${cNode.url}/getbudgetinfo`; + let arrProposals = await (await fetch(url)).json(); + + // Apply optional filters + if (!fAllowFinished) { + arrProposals = arrProposals.filter( + (a) => a.RemainingPaymentCount > 0 + ); + } + return arrProposals; + } + + /** + * @param {string} hash - the hash of the proposal to vote + * @param {number} voteCode - the vote code. "Yes" is 1, "No" is 2 + * @param {number} sigTime - The current time in seconds since UNIX epoch + * @return {Promise} The signed message used to vote + */ + async getSignedVoteMessage(hash, voteCode, sigTime) { + const msg = [ + ...hexToBytes(this.collateralTxId).reverse(), + ...Masternode._numToBytes(this.outidx, 4, true), + // Should be tx sequence, but 0xffffff is fine + ...[0, 255, 255, 255, 255], + ...hexToBytes(hash).reverse(), + ...Masternode._numToBytes(voteCode, 4, true), + ...Masternode._numToBytes(sigTime, 8, true), + ]; + + const [signature, v] = await nobleSecp256k1.sign( + dSHA256(msg), + parseWIF(this.mnPrivateKey, true), + { der: false, recovered: true } + ); + return Buffer.from([v + 27, ...signature]).toString('base64'); + } + /** + * @param {string} proposalName - the name of the proposal you want to get the vote of + * @param {string} hash - the hash of the proposal you want to get the vote of + * @return {Promise} Vote code "Yes" is 1, "No" is 2 + */ + async getVote(proposalName, hash) { + //See if you already voted the proposal in the current session + const index = Masternode.sessionVotes.findIndex( + ([vHash]) => vHash === hash + ); + if (index !== -1) { + //Found it! return the vote + return Masternode.sessionVotes[index][1]; + } + //Haven't voted yet, fetch the result from Duddino's node + const filterString = `.[] | select(.mnId=="`; + const filter = + `${encodeURI(filterString)}` + + `${this.collateralTxId}-${this.outidx}")`; + const url = `${cNode.url}/getbudgetvotes?params=${proposalName}&filter=${filter}`; + try { + const { Vote: vote } = await (await fetch(url)).json(); + return vote === 'YES' ? 1 : 2; + } catch (e) { + //Cannot parse JSON! This means that you did not vote hence return null + return null; + } + } + /** + * Stores a vote for the current session + * @param {string} hash - the hash of the proposal to vote + * @param {number} voteCode - the vote code. "Yes" is 1, "No" is 2 + */ + storeVote(hash, voteCode) { + const newVote = [hash, voteCode]; + const index = Masternode.sessionVotes.findIndex( + ([vHash]) => vHash === hash + ); + if (index !== -1) { + Masternode.sessionVotes[index] = newVote; + } else { + Masternode.sessionVotes.push(newVote); + } + } + /** + * @param {string} hash - the hash of the proposal to vote + * @param {number} voteCode - the vote code. "Yes" is 1, "No" is 2 + * @return {Promise} The response from the node + */ + async vote(hash, voteCode) { + const sigTime = Math.round(Date.now() / 1000); + const signature = await this.getSignedVoteMessage( + hash, + voteCode, + sigTime + ); + const url = `${cNode.url}/mnbudgetrawvote?params=${ + this.collateralTxId + },${this.outidx},${hash},${ + voteCode === 1 ? 'yes' : 'no' + },${sigTime},${encodeURI(signature).replaceAll('+', '%2b')}`; + const text = await (await fetch(url)).text(); + return text; + } + + /** + * Create proposal hash + * @param {Object} options + * @param {String} options.name - Name of the proposal + * @param {String} options.url - Url of the proposal + * @param {Number} options.nPayments - Number of cycles this proposal is gonna last + * @param {Number} options.start - Superblock of when the proposal is going to start + * @param {String} options.address - Base58 encoded PIVX address + * @param {Number} options.monthlyPayment - Payment amount per cycle in satoshi + * @returns {String} hex hash of the proposal + */ + static createProposalHash({ + name, + url, + nPayments, + start, + address, + monthlyPayment, + }) { + const end = + start + (cChainParams.current.budgetCycleBlocks + 1) * nPayments; + const addressBytes = bs58.decode(address); + const scriptBytes = [ + OP.DUP, + OP.HASH160, + addressBytes.length - 5, + ...addressBytes.slice(1, addressBytes.length - 4), + OP.EQUALVERIFY, + OP.CHECKSIG, + ]; + const msg = [ + name.length, + ...name.split('').map((c) => c.charCodeAt(0)), + url.length, + ...url.split('').map((c) => c.charCodeAt(0)), + ...Masternode._numToBytes(start, 4, true), + ...Masternode._numToBytes(end, 4, true), + ...Masternode._numToBytes(monthlyPayment, 8, true), + scriptBytes.length, + ...scriptBytes, + ]; + return bytesToHex(dSHA256(new Uint8Array(msg))); + } + + /** + * Finalize the proposal + * @param {Object} options + * @param {String} options.name - Name of the proposal + * @param {String} options.url - Url of the proposal + * @param {Number} options.nPayments - Number of cycles this proposal is gonna last + * @param {Number} options.start - Superblock of when the proposal is going to start + * @param {String} options.address - Base58 encoded PIVX address + * @param {Number} options.monthlyPayment - Payment amount per cycle in satoshi + * @param {String} options.txid - Transaction id of the proposal fee + * @returns {Promise<{ ok: boolean, err: string | undefined, hash: string | undefined }>} The Vote Hash, if the finalization happened without errors + */ + static async finalizeProposal({ + name, + url, + nPayments, + start, + address, + monthlyPayment, + txid, + }) { + try { + const res = await ( + await fetch( + `${cNode.url}/submitbudget?params=${encodeURI( + name + )},${encodeURI(url)},${nPayments},${start},${encodeURI( + address + )},${monthlyPayment / COIN},${txid}` + ) + ).text(); + + if (/^"[a-f0-9]"$/ && res.length == 64 + 2) { + return { ok: true, hash: res }; + } else if ( + res.includes('is unconfirmed') || + res.includes('requires at least') + ) { + return { ok: false, err: 'unconfirmed' }; + } else if ( + res.includes('invalid budget proposal') || + res.includes('Invalid block start') + ) { + return { ok: false, err: 'invalid' }; + } else { + return { ok: false, err: 'other' }; + } + } catch (e) { + console.error(e); + return { ok: false, err: e }; + } + } + + static async getNextSuperblock() { + return parseInt( + await (await fetch(`${cNode.url}/getnextsuperblock`)).text() + ); + } + + /** + * Fetches the masternode count object, containing each status and network. + * @returns {Promise<{total:number, stable:number, enabled:number, inqueue:number, ipv4:number, ipv6:number, onion:number}>} - The masternode count object + */ + static async getMasternodeCount() { + return await (await fetch(`${cNode.url}/getmasternodecount`)).json(); + } + + /** + * @param {Object} options + * @param {String} options.name - Name of the proposal + * @param {String} options.url - Url of the proposal + * @param {Number} options.nPayments - Number of cycles this proposal is gonna last + * @param {Number} options.start - Superblock of when the proposal is going to start + * @param {String} options.address - Base58 encoded PIVX address + * @param {Number} options.monthlyPayment - Payment amount per cycle in satoshi + * @returns {boolean} If the proposal is valid + */ + static isValidProposal({ + name, + url, + nPayments, + _start, + address, + monthlyPayment, + }) { + const isSafeStr = /^[a-z0-9 .,;\-_/:?@()]+$/i; + if (name.length > 20) { + return { ok: false, err: 'name_length' }; + } + + if (!isSafeStr.test(name)) { + return { ok: false, err: 'invalid_name' }; + } + + if (url.length > 64) { + return { ok: false, err: 'url_length' }; + } + + if (!isSafeStr.test(url)) { + return { ok: false, err: 'invalid_url' }; + } + + if ( + !/^(https?):\/\/[^\s/$.?#][^\s]*[^\s/.]\.[^\s/.][^\s]*[^\s.]$/.test( + url + ) + ) { + return { ok: false, err: 'invalid_url' }; + } + + if ( + nPayments < 1 || + nPayments > cChainParams.current.maxPaymentCycles + ) { + return { ok: false, err: 'invalid_payment_count' }; + } + + if ( + monthlyPayment < 10 * COIN || + monthlyPayment * nPayments > cChainParams.current.maxPayment + ) { + return { ok: false, err: 'invalid_monthly_payment' }; + } + if (!isStandardAddress(address)) { + return { ok: false, err: 'invalid_address' }; + } + + return { ok: true }; + } +} diff --git a/scripts/mempool.js b/scripts/mempool.js new file mode 100644 index 000000000..ddbbd319c --- /dev/null +++ b/scripts/mempool.js @@ -0,0 +1,332 @@ +import { getEventEmitter } from './event_bus.js'; +import { COutpoint, UTXO } from './transaction.js'; + +export const OutpointState = { + OURS: 1 << 0, // This outpoint is ours + + P2PKH: 1 << 1, // This is a P2PKH outpoint + P2CS: 1 << 2, // This is a P2CS outpoint + + SPENT: 1 << 3, // This outpoint has been spent + LOCKED: 1 << 5, // Coins in the LOCK set +}; + +export class Mempool { + /** @type{Map} */ + #outpointStatus = new Map(); + + /** + * Maps txid -> Transaction + * @type{Map} + */ + #txmap = new Map(); + + /** + * Object containing balances of the wallet + */ + #balances = { + balance: new CachableBalance(), + coldBalance: new CachableBalance(), + immatureBalance: new CachableBalance(), + }; + + /** + * Add a transaction to the mempool + * And mark the input as spent. + * @param {import('./transaction.js').Transaction} tx + */ + addTransaction(tx) { + this.#txmap.set(tx.txid, tx); + for (const input of tx.vin) { + this.setSpent(input.outpoint); + } + } + + /** + * @param {COutpoint} outpoint + */ + getOutpointStatus(outpoint) { + return this.#outpointStatus.get(outpoint.toUnique()) ?? 0; + } + + /** + * Sets outpoint status to `status`, overriding the old one + * @param {COutpoint} outpoint + * @param {number} status + */ + setOutpointStatus(outpoint, status) { + this.#outpointStatus.set(outpoint.toUnique(), status); + this.invalidateBalanceCache(); + } + + /** + * Adds `status` to the outpoint status, keeping the old status + * @param {COutpoint} outpoint + * @param {number} status + */ + addOutpointStatus(outpoint, status) { + const oldStatus = this.#outpointStatus.get(outpoint.toUnique()); + this.#outpointStatus.set(outpoint.toUnique(), oldStatus | status); + this.invalidateBalanceCache(); + } + + /** + * Removes `status` to the outpoint status, keeping the old status + * @param {COutpoint} outpoint + * @param {number} status + */ + removeOutpointStatus(outpoint, status) { + const oldStatus = this.#outpointStatus.get(outpoint.toUnique()); + this.#outpointStatus.set(outpoint.toUnique(), oldStatus & ~status); + this.invalidateBalanceCache(); + } + + /** + * Mark an outpoint as spent + * @param {COutpoint} outpoint + */ + setSpent(outpoint) { + this.addOutpointStatus(outpoint, OutpointState.SPENT); + } + + /** + * @param {COutpoint} outpoint + * @returns {boolean} whether or not the outpoint has been marked as spent + */ + isSpent(outpoint) { + return !!(this.getOutpointStatus(outpoint) & OutpointState.SPENT); + } + + /** + * Utility function to get the UTXO from an outpoint + * @param {COutpoint} outpoint + * @returns {UTXO?} + */ + outpointToUTXO(outpoint) { + const tx = this.#txmap.get(outpoint.txid); + if (!tx) return null; + return new UTXO({ + outpoint, + script: tx.vout[outpoint.n].script, + value: tx.vout[outpoint.n].value, + }); + } + + /** + * Get the debit of a transaction in satoshi + * @param {import('./transaction.js').Transaction} tx + */ + getDebit(tx) { + return tx.vin + .filter( + (input) => + this.getOutpointStatus(input.outpoint) & OutpointState.OURS + ) + .map((i) => this.outpointToUTXO(i.outpoint)) + .reduce((acc, u) => acc + (u?.value || 0), 0); + } + + /** + * Get the credit of a transaction in satoshi + * @param {import('./transaction.js').Transaction} tx + */ + getCredit(tx) { + const txid = tx.txid; + + return tx.vout + .filter( + (_, i) => + this.getOutpointStatus( + new COutpoint({ + txid, + n: i, + }) + ) & OutpointState.OURS + ) + .reduce((acc, u) => acc + u?.value ?? 0, 0); + } + + /** + * Loop through the unspent balance of the wallet + * @template T + * @param {number} requirement - Requirement that outpoints must have + * @param {T} initialValue - initial value of the result + * @param {balanceIterator} fn + * @returns {T} + */ + loopSpendableBalance(requirement, initialValue, fn) { + for (const tx of this.#txmap.values()) { + for (const [index, vout] of tx.vout.entries()) { + const status = this.getOutpointStatus( + new COutpoint({ txid: tx.txid, n: index }) + ); + if (status & (OutpointState.SPENT | OutpointState.LOCKED)) { + continue; + } + if ((status & requirement) === requirement) { + initialValue = fn(tx, vout, initialValue); + } + } + } + return initialValue; + } + + /** + * @param {object} o - options + * @param {number} [o.requirement] - A requirement to apply to all UTXOs. For example + * `OutpointState.P2CS` will only return P2CS transactions. + * @param {number} [o.target] - Number of satoshis needed. The method will return early when the value of the UTXOs has been reached, plus a bit to account for change + * By default it's MAX_SAFE_INTEGER + * @param {boolean} [o.includeImmature] - If set to true immature UTXOs will be included + * @param {number} [o.blockCount] - Current number of blocks + * @returns {UTXO[]} a list of unspent transaction outputs + */ + getUTXOs({ + requirement = 0, + includeImmature = false, + target = Number.POSITIVE_INFINITY, + blockCount, + } = {}) { + return this.loopSpendableBalance( + requirement, + { utxos: [], bal: 0 }, + (tx, vout, currentValue) => { + if ( + (!includeImmature && tx.isImmature(blockCount)) || + (currentValue.bal >= (target * 11) / 10 && + currentValue.bal > 0) + ) { + return currentValue; + } + const n = tx.vout.findIndex((element) => element === vout); + currentValue.utxos.push( + new UTXO({ + outpoint: new COutpoint({ txid: tx.txid, n }), + script: vout.script, + value: vout.value, + }) + ); + currentValue.bal += vout.value; + return currentValue; + } + ).utxos; + } + + #balanceInternal(requirement, blockCount, includeImmature = false) { + return this.loopSpendableBalance( + requirement, + 0, + (tx, vout, currentValue) => { + if (!tx.isImmature(blockCount)) { + return currentValue + vout.value; + } else if (includeImmature) { + return currentValue + vout.value; + } + return currentValue; + } + ); + } + + invalidateBalanceCache() { + this.#balances.immatureBalance.invalidate(); + this.#balances.balance.invalidate(); + this.#balances.coldBalance.invalidate(); + this.#emitBalanceUpdate(); + } + + #emittingBalanceUpdate = false; + + #emitBalanceUpdate() { + if (this.#emittingBalanceUpdate) return; + this.#emittingBalanceUpdate = true; + // TODO: This is not ideal, we are limiting the mempool to only emit 1 balance-update per frame, + // but we don't want the mempool to know about animation frames. This is needed during + // sync to avoid spamming balance-updates and slowing down the sync. + // The best course of action is to probably add a loading page/state and avoid + // listening to the balance-update event until the sync is done + requestAnimationFrame(() => { + getEventEmitter().emit('balance-update'); + this.#emittingBalanceUpdate = false; + }); + } + + /** + * @returns {import('./transaction.js').Transaction[]} a list of all transactions + */ + getTransactions() { + return Array.from(this.#txmap.values()); + } + + /** + * @param blockCount - chain height + */ + getBalance(blockCount) { + return this.#balances.balance.getOrUpdateInvalid(() => { + return this.#balanceInternal( + OutpointState.OURS | OutpointState.P2PKH, + blockCount + ); + }); + } + + /** + * @param blockCount - chain height + */ + getColdBalance(blockCount) { + return this.#balances.coldBalance.getOrUpdateInvalid(() => { + return this.#balanceInternal( + OutpointState.OURS | OutpointState.P2CS, + blockCount + ); + }); + } + + /** + * @param blockCount - chain height + */ + getImmatureBalance(blockCount) { + return this.#balances.immatureBalance.getOrUpdateInvalid(() => { + return ( + this.#balanceInternal(OutpointState.OURS, blockCount, true) - + this.getBalance(blockCount) - + this.getColdBalance(blockCount) + ); + }); + } +} + +class CachableBalance { + /** + * @type {number} + * represents a cachable balance + */ + value = -1; + + isValid() { + return this.value != -1; + } + invalidate() { + this.value = -1; + } + + /** + * Return the cached balance if it's valid, or re-compute and return. + * @param {Function} fn - function with which calculate the balance + * @returns {number} cached balance + */ + getOrUpdateInvalid(fn) { + if (!this.isValid()) { + this.value = fn(); + } + return this.value; + } +} + +/** + * @template T + * @typedef {Function} balanceIterator + * @param {import('./transaction.js').Transaction} tx + * @param {CTxOut} vout + * @param {T} currentValue - the current value iterated + * @returns {number} amount + */ diff --git a/scripts/misc.js b/scripts/misc.js new file mode 100644 index 000000000..d1e35af40 --- /dev/null +++ b/scripts/misc.js @@ -0,0 +1,460 @@ +import { translation } from './i18n.js'; +import { doms } from './global.js'; +import qrcode from 'qrcode-generator'; +import bs58 from 'bs58'; +import { BIP21_PREFIX, cChainParams } from './chain_params.js'; +import { dSHA256 } from './utils.js'; +import { verifyPubkey, verifyBech32 } from './encoding.js'; + +// Base58 Encoding Map +export const MAP_B58 = + '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; +export const LEN_B58 = MAP_B58.length; + +/* --- UTILS --- */ +// Cryptographic Random-Gen +export function getSafeRand(nSize = 32) { + return crypto.getRandomValues(new Uint8Array(nSize)); +} + +/** Download contents as a file */ +export function downloadBlob(content, filename, contentType) { + // Create a blob + const blob = new Blob([content], { type: contentType }); + + // Create a link to download it + const pom = document.createElement('a'); + pom.href = URL.createObjectURL(blob); + pom.setAttribute('download', filename); + pom.click(); +} + +/** + * Create a custom GUI Alert popup + * + * ### Do NOT display arbitrary / external errors: + * - The use of `.innerHTML` allows for input styling at this cost. + * @param {'success'|'info'|'warning'} type - The styling type of the alert + * @param {string} message - The message to relay to the user + * @param {number?} timeout - The time in `ms` until the alert expires (Defaults to never expiring) + */ +export function createAlert(type, message, timeout = 0) { + const domAlert = document.createElement('div'); + domAlert.classList.add('notifyWrapper'); + domAlert.classList.add(type); + setTimeout(() => { + domAlert.style.opacity = '1'; + domAlert.style.zIndex = '999999'; + domAlert.classList.add('bounce-ani'); + domAlert.classList.add('bounce'); + }, 100); + + // Colors for types + let typeIcon; + switch (type) { + case 'warning': + typeIcon = 'fa-exclamation'; + break; + case 'info': + typeIcon = 'fa-info'; + break; + default: + // If no valid type is set, default to success + type == 'success'; + typeIcon = 'fa-check'; + break; + } + + // Message + domAlert.innerHTML = ` +
+ +
+
+ ${message} +
`; + domAlert.destroy = () => { + // Fully destroy timers + DOM elements, no memory leaks! + clearTimeout(domAlert.timer); + domAlert.style.opacity = '0'; + setTimeout(() => { + domAlert.remove(); + }, 600); + }; + // On Click: Delete alert from DOM after close animation. + domAlert.addEventListener('click', domAlert.destroy); + // On Timeout: Delete alert from DOM after a period of inactive time. + if (timeout > 0) domAlert.timer = setTimeout(domAlert.destroy, timeout); + doms.domAlertPos.appendChild(domAlert); +} + +/** + * Shows a Confirm popup with custom HTML. + * + * If `resolvePromise` has a value, the popup won't have + * Confirm/Cancel buttons and will wait for the promise to resolve. + * + * Returns the awaited value of `resolvePromise` or `true/false` if the + * user used a Cancel/Confirm button. + * @param {object} options + * @param {string?} options.title - The optional title of the popup + * @param {string} options.html - The HTML of the popup contents + * @param {Promise} options.resolvePromise - A promise to resolve before closing the modal + * @param {boolean?} options.hideConfirm - Whether to hide the Confirm button or not + * @param {boolean?} options.purpleModal - Toggle a Purple modal, or leave as false for White + * @param {boolean?} options.textLeft - Toggle to align modal text to the left, or leave as false for center + * @param {boolean?} options.noPadding - Toggle zero padding, or leave as false for default padding + * @param {number?} options.maxHeight - An optional modal Max Height, omit for default modal max + * @returns {Promise} + */ +export async function confirmPopup({ + title, + html, + resolvePromise, + hideConfirm, + purpleModal, + textLeft, + noPadding, + maxHeight, + centerButtons, + wideModal, +}) { + // If there's a title provided: display the header and text + doms.domConfirmModalHeader.style.display = title ? 'block' : 'none'; + doms.domConfirmModalTitle.innerHTML = title || ''; + + // If there's a promise to resolve, don't display buttons; the modal visibility will be controlled by the promise (f.e: a 'pls wait' screen) + doms.domConfirmModalButtons.style.setProperty( + 'display', + resolvePromise ? 'none' : 'block', + resolvePromise ? 'important' : undefined + ); + $('#confirmModal').modal(resolvePromise ? 'show' : { keyboard: false }); + + // Show or hide the confirm button, and replace 'Cancel' with 'Close' + doms.domConfirmModalConfirmButton.style.display = hideConfirm ? 'none' : ''; + doms.domConfirmModalCancelButton.innerHTML = + '' + + (hideConfirm ? translation.popupClose : translation.popupCancel) + + ''; + + // Set content display + doms.domConfirmModalContent.innerHTML = html; + + // Set text align to left + if (textLeft) { + doms.domConfirmModalContent.classList.remove('center-text'); + } else { + doms.domConfirmModalContent.classList.add('center-text'); + } + + // Use the purple modal + if (purpleModal) { + doms.domConfirmModalMain.classList.add('exportKeysModalColor'); + } else { + doms.domConfirmModalMain.classList.remove('exportKeysModalColor'); + } + + // If modal is wide + if (wideModal) { + doms.domConfirmModalDialog.classList.add('masternodeModalDialog'); + doms.domConfirmModalDialog.classList.add('masternodeModalDialog2'); + + doms.domConfirmModalContent.classList.remove('center-text'); + } + + // Remove padding + if (noPadding) { + doms.domConfirmModalContent.classList.add('px-0'); + doms.domConfirmModalContent.classList.add('pb-0'); + } else { + doms.domConfirmModalContent.classList.remove('px-0'); + doms.domConfirmModalContent.classList.remove('pb-0'); + } + + // Set max-height (removed at `.finally` context) + if (maxHeight) + doms.domConfirmModalDialog.classList.add(`max-w-${maxHeight}`); + + // If there's an input in the prompt, focus the cursor upon it + for (const domElement of doms.domConfirmModalContent.children) { + if (domElement.type === 'text' || domElement.type === 'password') { + domElement.focus(); + break; + } + } + + // Center the buttons + if (centerButtons) { + doms.domConfirmModalButtons.classList.add('centerFlex'); + } + + // Wait for the promise to resolve OR create a new one which resolves upon a modal button click + resolvePromise = + resolvePromise || + new Promise((res, _) => { + doms.domConfirmModalConfirmButton.onclick = () => { + res(true); + }; + doms.domConfirmModalCancelButton.onclick = () => { + res(false); + }; + }); + try { + return await resolvePromise; + } finally { + // We want to hide the modal even if an exception occurs + $('#confirmModal').modal('hide'); + + // Reset any modal settings + doms.domConfirmModalDialog.classList.remove(`max-w-${maxHeight}`); + } +} + +// Generates and sets a QRCode image from a string and dom element +export function createQR(strData = '', domImg, size = 5) { + // QRCode class consists of 'typeNumber' & 'errorCorrectionLevel' + const cQR = qrcode(size, 'L'); + cQR.addData(strData); + cQR.make(); + domImg.innerHTML = cQR.createImgTag(2, 2); + domImg.firstChild.style.borderRadius = '8px'; +} + +/** + * Prompt image selection, and return base64 of an image file. + * @returns {Promise} The base64 string of the selected image file. + */ +export async function getImageFile() { + return new Promise((resolve) => { + let input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = (e) => { + let file = e.target.files[0]; + let reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(file); + }; + input.click(); + }); +} + +/** + * A quick check to see if an address is a Standard (P2PKH) address + * @param {string} strAddress - The address to check + * @returns {boolean} - `true` if a Standard address, `false` if not + */ +export function isStandardAddress(strAddress) { + return verifyPubkey(strAddress); +} + +/** + * A quick check to see if an address is a Cold (P2CS) address + * @param {string} strAddress - The address to check + * @returns {boolean} - `true` if a Cold address, `false` if not + */ +export function isColdAddress(strAddress) { + return verifyPubkey(strAddress, cChainParams.current.STAKING_ADDRESS); +} + +/** + * A quick check to see if an address is an exchange address + * @param {string} strAddress - The address to check + * @returns {boolean} - `true` if an exchange address, `false` if not + */ +export function isExchangeAddress(strAddress) { + return verifyPubkey( + strAddress, + cChainParams.current.EXCHANGE_ADDRESS_PREFIX + ); +} + +/** + * @param {string} strAddress - The address to check + * @returns {boolean} if strAddress is a valid shiled address + */ +export function isShieldAddress(strAddress) { + return verifyBech32(strAddress, cChainParams.current.SHIELD_PREFIX); +} + +/** + * @param {string} strAddress + * @return {boolean} If a straddress is a valid PIVX address, + * i.e. shield, xpub or standard + */ +export function isValidPIVXAddress(strAddress) { + return ( + isStandardAddress(strAddress) || + isColdAddress(strAddress) || + isShieldAddress(strAddress) || + isExchangeAddress(strAddress) + ); +} + +/** + * A quick check to see if a string is an XPub key + * @param {string} strXPub - The XPub to check + * @returns {boolean} - `true` if a valid formatted XPub, `false` if not + */ +export function isXPub(strXPub) { + if (!strXPub.startsWith('xpub')) return false; + + // Attempt to Base58 decode the XPub + try { + // Slice away the `xpub` prefix and decode + const decoded = bs58.decode(strXPub.slice(4)); + + // Then verify the final length too + return decoded.length === 78; + } catch (e) { + return false; + } +} + +/** + * Attempt to safely parse a BIP21 Payment Request + * @param {string} strReq - BIP21 Payment Request string + * @returns {object | false} + */ +export function parseBIP21Request(strReq) { + // Format should match: pivx:addr[?amount=x&label=x] + if (!strReq.includes(BIP21_PREFIX + ':')) return false; + + const [addressPart, optionsPart] = strReq.includes('?') + ? strReq.split('?') + : [strReq, false]; + const strAddress = addressPart.substring(BIP21_PREFIX.length + 1); // remove 'pivx:' prefix + let cOptions = {}; + + // Ensure the address is valid + if ( + // Standard address + !isStandardAddress(strAddress) && + // Shield address + !isShieldAddress(strAddress) + ) { + return false; + } + + if (optionsPart) { + cOptions = Object.fromEntries( + optionsPart + .split('&') + .map((opt) => opt.split('=').map(decodeURIComponent)) + ); + } + + return { address: strAddress, options: cOptions }; +} + +/** + * Generate an encoded private key for masternodes + */ +export function generateMasternodePrivkey() { + // Prefix the network byte with the private key (32 random bytes) + const data = [cChainParams.current.SECRET_KEY, ...getSafeRand(32)]; + + // Compute and concatenate the checksum, then encode the private key as Base58 + return bs58.encode([...data, ...dSHA256(data).slice(0, 4)]); +} + +export function sanitizeHTML(text) { + const element = document.createElement('div'); + element.innerText = text; + return element.innerHTML; +} + +/** + * "Beautifies" a number with HTML, by displaying decimals in a lower opacity + * @param {string} strNumber - The number in String form to beautify + * @param {string?} strDecFontSize - The optional font size to display decimals in + * @returns {string} - A HTML string with beautified number handling + */ +export function beautifyNumber( + strNumber, + strDecFontSize = '', + showFirstNumber = true +) { + if (typeof strNumber === 'number') strNumber = strNumber.toString(); + + // Only run this for numbers with decimals + if (!strNumber.includes('.')) + return parseInt(strNumber).toLocaleString('en-GB'); + + // Split the number in to Full and Decimal parts + const arrNumParts = strNumber.split('.'); + + // Return a HTML that renders the decimal in a lower opacity + const strFontSize = strDecFontSize ? 'font-size: ' + strDecFontSize : ''; + return `${ + showFirstNumber ? parseInt(arrNumParts[0]).toLocaleString('en-GB') : '' + }.${arrNumParts[1]}`; +} + +/** + * Check if a string is valid Base64 encoding + * @param {string} str - String to check + * @returns {boolean} + */ +export function isBase64(str) { + const base64Regex = /^[A-Za-z0-9+/=]+$/; + + // Check if the string contains only Base64 characters: + if (!base64Regex.test(str)) { + return false; + } + + // Check if the length is a multiple of 4 (required for Base64): + if (str.length % 4 !== 0) { + return false; + } + + // Try decoding the Base64 string to check for errors: + try { + atob(str); + } catch (e) { + return false; + } + + // The string is likely Base64-encoded: + return true; +} + +/** + * Checks if two values are of the same type. + * + * @param {any} a - The first value. + * @param {any} b - The second value. + * @returns {boolean} - `true` if the values are of the same type, `false` if not or if there was an error comparing. + */ +export function isSameType(a, b) { + try { + if (a === null || b === null) return a === b; + if (typeof a !== typeof b) return false; + if (typeof a === 'object') + return Object.getPrototypeOf(a) === Object.getPrototypeOf(b); + return true; + } catch (e) { + return false; + } +} + +/** + * Checks if a value is 'empty'. + * + * @param {any} val - The value to check. + * @returns {boolean} - `true` if the value is 'empty', `false` otherwise. + * --- + * Values **considered 'empty'**: `null`, `undefined`, empty strings, empty arrays, empty objects. + * + * Values **NOT considered 'empty'**: Any non-nullish primitive value: numbers (including `0` and `NaN`), `true`, `false`, `Symbol()`, `BigInt()`. + */ +export function isEmpty(val) { + return ( + val == null || + val === '' || + (Array.isArray(val) && val.length === 0) || + (typeof val === 'object' && Object.keys(val).length === 0) + ); +} diff --git a/scripts/native-worker.js b/scripts/native-worker.js new file mode 100644 index 000000000..ab64908b1 --- /dev/null +++ b/scripts/native-worker.js @@ -0,0 +1,60 @@ +// Listen for native worker installs +self.addEventListener('install', function (_event) { + console.log('[ServiceWorker] Install'); + + // Activate the newly installed worker + self.skipWaiting(); +}); + +// Listen for native worker activation +self.addEventListener('activate', (_event) => { + console.log('[ServiceWorker] Activated'); + + // Tell the active service worker to take control of the page immediately. + self.clients.claim(); +}); +self.addEventListener('message', (event) => { + if (event.data) populatePrefetchCache(event.data); +}); + +async function populatePrefetchCache(files) { + const cache = await caches.open('sapling-params-v1'); + for (const file of files) { + const res = await fetch(file, { + priority: 'low', + }); + if (res.ok) await cache.put(file, res); + } +} + +self.addEventListener('fetch', (event) => { + // Let the browser do its default thing + // for non-GET requests. + if (event.request.method !== 'GET') return; + + const cacheRegexps = [ + /sapling-(spend|output)\.params/, + /.wasm$/, + /(pivx-shield|util)/, + ]; + + if (!cacheRegexps.some((r) => r.test(event.request.url))) { + return; + } + + event.respondWith( + (async () => { + // Try to get the response from a cache. + const cache = await caches.open('sapling-params-v1'); + const cachedResponse = await cache.match(event.request.url); + + if (cachedResponse && cachedResponse.ok) { + return cachedResponse; + } + // If we didn't find a match in the cache, use the network. + const response = await fetch(event.request); + await cache.put(event.request.url, response.clone()); + return response; + })() + ); +}); diff --git a/scripts/native.js b/scripts/native.js new file mode 100644 index 000000000..32d1fff65 --- /dev/null +++ b/scripts/native.js @@ -0,0 +1,49 @@ +import { ALERTS } from './i18n.js'; +import { createAlert } from './misc.js'; + +// Register a service worker, if it's supported +export function registerWorker() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register('./native-worker.js') + .then((registration) => { + const sendMessage = (serviceWorker) => { + const files = Array.from( + document.head.querySelectorAll( + 'link[rel="serviceworkprefetch"]' + ) + ).map((l) => l.href); + serviceWorker.postMessage(files); + }; + + if (registration.active) { + sendMessage(registration.active); + } else { + // Wait for the service worker to become active + registration.addEventListener('updatefound', () => { + const newWorker = + registration.installing || registration.waiting; + if (newWorker) { + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'activated') { + sendMessage(newWorker); + } + }); + } + }); + } + }); + + // Listen for device pre-install events, these fire if MPW is capable of being installed on the device + window.addEventListener('beforeinstallprompt', (event) => { + // Prevent the mini-infobar from appearing on mobile. + event.preventDefault(); + }); + + // Listen for successful installs + window.addEventListener('appinstalled', (_event) => { + // Notify! + return createAlert('success', ALERTS.APP_INSTALLED, 2500); + }); + } +} diff --git a/scripts/network.js b/scripts/network.js index bcb963676..ced48a90f 100644 --- a/scripts/network.js +++ b/scripts/network.js @@ -1,149 +1,456 @@ -if (networkEnabled) { - var url = 'https://' + explorer - var githubRepo = 'https://api.github.com/repos/dogecash/dogecash-web-wallet/releases'; - var checkPubKey = function () { - // Create a request variable and assign a new XMLHttpRequest object to it. - var request = new XMLHttpRequest() - // Open a new connection, using the GET request on the URL endpoint - request.open('GET', url + '/api/v1/address/' + publicKeyForNetwork, true) - request.onload = function () { - var data = JSON.parse(this.response) - document.getElementById("balance").innerHTML = data['balance']; - document.getElementById("totalReceived").innerHTML = data['totalReceived']; - document.getElementById("totalSent").innerHTML = data['totalSent']; - var typeNumber = 4; - var errorCorrectionLevel = 'L'; - var qr = qrcode(typeNumber, errorCorrectionLevel); - qr.addData('scc:' + data['addrStr']); - qr.make(); - document.getElementById("addrStrQR").innerHTML = qr.createImgTag(); - document.getElementById("addrStr").innerHTML = data['addrStr']; - //Transactions - document.getElementById("TransactionNumber").innerHTML = data['txApperances']; - if (data['txApperances'] > 0) { - var dataTransactions = JSON.stringify(data['transactions']).replace("[", "").replace("]", "").replace(/"/g, ""); - const splits = dataTransactions.split(',') - var transactionLinks; - for (i = 0; i < splits.length; i++) { - if (i == 0) { - transactionLinks = '' + splits[i] + '
'; - } else { - transactionLinks += '' + splits[i] + '
'; - } +import { cChainParams } from './chain_params.js'; +import { createAlert } from './misc.js'; +import { + debugLog, + debugTimerEnd, + debugTimerStart, + DebugTopics, +} from './debug.js'; +import { sleep } from './utils.js'; +import { getEventEmitter } from './event_bus.js'; +import { + STATS, + cStatKeys, + cAnalyticsLevel, + setExplorer, + fAutoSwitch, +} from './settings.js'; +import { cNode } from './settings.js'; +import { ALERTS, tr, translation } from './i18n.js'; +import { Transaction } from './transaction.js'; + +/** + * @typedef {Object} XPUBAddress + * @property {string} type - Type of address (always 'XPUBAddress' for XPUBInfo classes) + * @property {string} name - PIVX address string + * @property {string} path - BIP44 path of the address derivation + * @property {number} transfers - Number of transfers involving the address + * @property {number} decimals - Decimal places in the amounts (PIVX has 8 decimals) + * @property {string} balance - Current balance of the address (satoshi) + * @property {string} totalReceived - Total ever received by the address (satoshi) + * @property {string} totalSent - Total ever sent from the address (satoshi) + */ + +/** + * @typedef {Object} XPUBInfo + * @property {number} page - Current response page in a paginated data + * @property {number} totalPages - Total pages in the paginated data + * @property {number} itemsOnPage - Number of items on the current page + * @property {string} address - XPUB string of the address + * @property {string} balance - Current balance of the xpub (satoshi) + * @property {string} totalReceived - Total ever received by the xpub (satoshi) + * @property {string} totalSent - Total ever sent from the xpub (satoshi) + * @property {string} unconfirmedBalance - Unconfirmed balance of the xpub (satoshi) + * @property {number} unconfirmedTxs - Number of unconfirmed transactions of the xpub + * @property {number} txs - Total number of transactions of the xpub + * @property {string[]?} txids - Transaction ids involving the xpub + * @property {number?} usedTokens - Number of used token addresses from the xpub + * @property {XPUBAddress[]?} tokens - Array of used token addresses + */ + +/** + * Virtual class rapresenting any network backend + * + */ +export class Network { + constructor() { + if (this.constructor === Network) { + throw new Error('Initializing virtual class'); } - document.getElementById("Transactions").innerHTML = transactionLinks; - } - document.getElementById("NetworkingJson").innerHTML = this.response; - console.log(data) - console.log() - } - // Send request - request.send() - } - var getScriptData = function (txid, index) { - var request = new XMLHttpRequest() - if (amountOfTransactions <= 1000) { - request.open('GET', url + '/api/v2/tx/' + txid, true)//Simple queing fix - } else { - request.open('GET', url + '/api/v2/tx/' + txid, false) - } - request.onload = function (e) { - if (request.readyState === 4) { - if (request.status === 200) { - datar = JSON.parse(this.response) - var script = datar['vout'][index]['hex'] - trx.addinput(txid, index, script) - console.log(trx); + this._enabled = true; + } + + /** + * @param {boolean} value + */ + set enabled(value) { + if (value !== this._enabled) { + getEventEmitter().emit('network-toggle', value); + this._enabled = value; } - } - } - request.send() - } - var getUnspentTransactions = function () { - var request = new XMLHttpRequest() - request.open('GET', url + '/api/v2/utxo/' + publicKeyForNetwork + '?confirmed=true', true) - request.onload = function () { - data = JSON.parse(this.response) - if (JSON.stringify(data) === '[]') { - console.log('No unspent Transactions'); - document.getElementById("errorNotice").innerHTML = '

Error:

It seems there are no unspent inputs associated with your wallet. This means you have no funds! D:
'; - } else { - amountOfTransactions = JSON.stringify(data['length']) - var dataTransactions = JSON.stringify(data['0']['txid']); - if (amountOfTransactions <= 1000) { - for (i = 0; i < amountOfTransactions; i++) { - if (i == 0) { - balance = parseFloat(Number(data[i]['value']) / 100000000); - } else { - balance = parseFloat(balance) + parseFloat(Number(data[i]['value']) / 100000000); + } + + get enabled() { + return this._enabled; + } + + enable() { + this.enabled = true; + } + + disable() { + this.enabled = false; + } + + toggle() { + this.enabled = !this.enabled; + } + + error() { + throw new Error('Error must be implemented'); + } + + getBlockCount() { + throw new Error('getBlockCount must be implemented'); + } + + sendTransaction() { + throw new Error('sendTransaction must be implemented'); + } + + submitAnalytics(_strType, _cData = {}) { + throw new Error('submitAnalytics must be implemented'); + } + + async getTxInfo(_txHash) { + throw new Error('getTxInfo must be implemented'); + } +} + +/** + * + */ +export class ExplorerNetwork extends Network { + /** + * @param {string} strUrl - Url pointing to the blockbook explorer + */ + constructor(strUrl, wallet) { + super(wallet); + /** + * @type{string} + * @public + */ + this.strUrl = strUrl; + } + + error() { + if (this.enabled) { + this.disable(); + createAlert('warning', ALERTS.CONNECTION_FAILED); + } + } + + /** + * Fetch a block from the explorer given the height + * @param {number} blockHeight + * @param {boolean} skipCoinstake - if true coinstake tx will be skipped + * @returns {Promise} the block fetched from explorer + */ + async getBlock(blockHeight, skipCoinstake = false) { + try { + const block = await this.safeFetchFromExplorer( + `/api/v2/block/${blockHeight}` + ); + const newTxs = []; + // This is bad. We're making so many requests + // This is a quick fix to try to be compliant with the blockbook + // API, and not the PIVX extension. + // In the Blockbook API /block doesn't have any chain specific information + // Like hex, shield info or what not. + // We could change /getshieldblocks to /getshieldtxs? + // In addition, always skip the coinbase transaction and in case the coinstake one + // TODO: once v6.0 and shield stake is activated we might need to change this optimization + for (const tx of block.txs.slice(skipCoinstake ? 2 : 1)) { + const r = await fetch( + `${this.strUrl}/api/v2/tx-specific/${tx.txid}` + ); + if (!r.ok) throw new Error('failed'); + const newTx = await r.json(); + newTxs.push(newTx); + } + block.txs = newTxs; + return block; + } catch (e) { + this.error(); + throw e; + } + } + + async getBlockCount() { + try { + const { backend } = await ( + await retryWrapper(fetchBlockbook, `/api/v2/api`) + ).json(); + + return backend.blocks; + } catch (e) { + this.error(); + throw e; + } + } + + /** + * Sometimes blockbook might return internal error, in this case this function will sleep for some times and retry + * @param {string} strCommand - The specific Blockbook api to call + * @param {number} sleepTime - How many milliseconds sleep between two calls. Default value is 20000ms + * @returns {Promise} Explorer result in json + */ + async safeFetchFromExplorer(strCommand, sleepTime = 20000) { + let trials = 0; + const maxTrials = 6; + while (trials < maxTrials) { + trials += 1; + const res = await retryWrapper(fetchBlockbook, strCommand); + if (!res.ok) { + debugLog( + DebugTopics.NET, + 'Blockbook internal error! sleeping for ' + + sleepTime + + ' seconds' + ); + await sleep(sleepTime); + continue; + } + return await res.json(); + } + throw new Error('Cannot safe fetch from explorer!'); + } + + /** + * //TODO: do not take the wallet as parameter but instead something weaker like a public key or address? + * Must be called only for initial wallet sync + * @param {import('./wallet.js').Wallet} wallet - Wallet that we are getting the txs of + * @returns {Promise} + */ + async getLatestTxs(wallet) { + if (wallet.isSynced) { + throw new Error('getLatestTxs must only be for initial sync'); + } + let nStartHeight = Math.max( + ...wallet.getTransactions().map((tx) => tx.blockHeight) + ); + debugTimerStart(DebugTopics.NET, 'getLatestTxsTimer'); + // Form the API call using our wallet information + const strKey = wallet.getKeyToExport(); + const strRoot = `/api/v2/${ + wallet.isHD() ? 'xpub/' : 'address/' + }${strKey}`; + const strCoreParams = `?details=txs&from=${nStartHeight}`; + const probePage = await this.safeFetchFromExplorer( + `${strRoot + strCoreParams}&pageSize=1` + ); + const txNumber = probePage.txs - wallet.getTransactions().length; + // Compute the total pages and iterate through them until we've synced everything + const totalPages = Math.ceil(txNumber / 1000); + for (let i = totalPages; i > 0; i--) { + getEventEmitter().emit( + 'transparent-sync-status-update', + tr(translation.syncStatusHistoryProgress, [ + { current: totalPages - i + 1 }, + { total: totalPages }, + ]), + ((totalPages - i) / totalPages) * 100 < 0 + ? 0 + : ((totalPages - i) / totalPages) * 100, + false + ); + + // Fetch this page of transactions + const iPage = await this.safeFetchFromExplorer( + `${strRoot + strCoreParams}&page=${i}` + ); + + // Update the internal mempool if there's new transactions + // Note: Extra check since Blockbook sucks and removes `.transactions` instead of an empty array if there's no transactions + if (iPage?.transactions?.length > 0) { + for (const tx of iPage.transactions.reverse()) { + const parsed = Transaction.fromHex(tx.hex); + parsed.blockHeight = tx.blockHeight; + parsed.blockTime = tx.blockTime; + await wallet.addTransaction(parsed); + } } - var txid = JSON.stringify(data[i]['txid']).replace(/"/g, ""); - var index = JSON.stringify(data[i]['vout']); - getScriptData(txid, index) - } - } else { - //Temporary message for when there are alot of inputs - //Probably use change all of this to using websockets will work better - document.getElementById("errorNotice").innerHTML = '

Error:

We are sorry but this address has over 1k inputs. In this version we do not support this. Please import your private key to a desktop wallet or wait for an update
'; } - } - console.log('Total Balance:' + balance); - } - request.send() - } - var sendTransaction = function (hex) { - if (typeof hex !== 'undefined') { - document.getElementById("sendIt").style.display = 'none'; - var request = new XMLHttpRequest() - request.open('GET', url + '/api/v2/sendtx/' + hex, true) - request.onload = function () { - data = JSON.parse(this.response) - if (typeof data['result'] !== 'undefined') { - console.log('Transaction sent tx:' + data['result']); - document.getElementById("transactionFinal").innerHTML = ('

Transaction sent tx:' + data['result'] + '

'); - document.getElementById("sendIt").style.display = 'none'; - document.getElementById("loadSimpleTransactions").style.display = 'block'; - document.getElementById("simpleTransactions").style.display = 'none'; - document.getElementById("simpleRawTx").innerHTML = ''; - document.getElementById("HumanReadable").innerHTML = ''; - document.getElementById("address1s").innerHTML = ''; - document.getElementById("value1s").innerHTML = ''; - } else { - console.log('Error sending transaction:' + data['error']['message']); - document.getElementById("transactionFinal").innerHTML = ('

Error sending transaction:' + data + "

"); + + debugLog( + DebugTopics.NET, + 'Fetched latest txs: total number of pages was ', + totalPages + ); + debugTimerEnd(DebugTopics.NET, 'getLatestTxsTimer'); + } + + /** + * @typedef {object} BlockbookUTXO + * @property {string} txid - The TX hash of the output + * @property {number} vout - The Index Position of the output + * @property {string} value - The string-based satoshi value of the output + * @property {number} height - The block height the TX was confirmed in + * @property {number} confirmations - The depth of the TX in the blockchain + */ + + /** + * Fetch UTXOs from the current primary explorer + * @param {string} strAddress - address of which we want UTXOs + * @returns {Promise>} Resolves when it has finished fetching UTXOs + */ + async getUTXOs(strAddress) { + try { + let publicKey = strAddress; + // Fetch UTXOs for the key + const arrUTXOs = await ( + await retryWrapper(fetchBlockbook, `/api/v2/utxo/${publicKey}`) + ).json(); + return arrUTXOs; + } catch (e) { + console.error(e); + this.error(); + } + } + + /** + * Fetch an XPub's basic information + * @param {string} strXPUB - The xpub to fetch info for + * @returns {Promise} - A JSON class of aggregated XPUB info + */ + async getXPubInfo(strXPUB) { + return await ( + await retryWrapper(fetchBlockbook, `/api/v2/xpub/${strXPUB}`) + ).json(); + } + + async sendTransaction(hex) { + try { + const data = await ( + await retryWrapper(fetchBlockbook, '/api/v2/sendtx/', { + method: 'post', + body: hex, + }) + ).json(); + + // Throw and catch if the data is not a TXID + if (!data.result || data.result.length !== 64) throw data; + + console.log('Transaction sent! ' + data.result); + getEventEmitter().emit('transaction-sent', true, data.result); + return data.result; + } catch (e) { + getEventEmitter().emit('transaction-sent', false, e); + return false; } - } - - request.send() - } else { - console.log("hex undefined"); - } - } - var calculatefee = function (bytes) { - var request = new XMLHttpRequest() - request.open('GET', url + '/api/v1/estimatefee/10', false) - request.onload = function () { - data = JSON.parse(this.response) - console.log(data); - console.log('current fee rate' + data['result']); - fee = data['result']; - } - request.send() - } - var versionCheck = function () { - var request = new XMLHttpRequest() - request.open('GET', githubRepo, true) - request.onload = function () { - data = JSON.parse(this.response) - var currentReleaseVersion = (data[0]['tag_name']).replace("V", "") - if (parseFloat(currentReleaseVersion) > parseFloat(wallet_version)) { - console.log("out of date"); - document.getElementById("outdated").style.display = 'block'; - } - } - request.send() - } - //Call a version check if network is enabled: - versionCheck(); - document.getElementById('Network').innerHTML = " Network Enabled "; + } + + async getTxInfo(txHash) { + const req = await retryWrapper(fetchBlockbook, `/api/v2/tx/${txHash}`); + return await req.json(); + } + + /** + * @return {Promise} The list of blocks which have at least one shield transaction + */ + async getShieldBlockList() { + return await (await fetch(`${cNode.url}/getshieldblocks`)).json(); + } + + // PIVX Labs Analytics: if you are a user, you can disable this FULLY via the Settings. + // ... if you're a developer, we ask you to keep these stats to enhance upstream development, + // ... but you are free to completely strip MPW of any analytics, if you wish, no hard feelings. + submitAnalytics(strType, cData = {}) { + if (!this.enabled) return; + + // TODO: rebuild Labs Analytics, submitAnalytics() will be disabled at code-level until this is live again + /* eslint-disable */ + return; + + // Limit analytics here to prevent 'leakage' even if stats are implemented incorrectly or forced + let i = 0, + arrAllowedKeys = []; + for (i; i < cAnalyticsLevel.stats.length; i++) { + const cStat = cAnalyticsLevel.stats[i]; + arrAllowedKeys.push(cStatKeys.find((a) => STATS[a] === cStat)); + } + + // Check if this 'stat type' was granted permissions + if (!arrAllowedKeys.includes(strType)) return false; + + // Format + const cStats = { type: strType, ...cData }; + + // Send to Labs Analytics + const request = new XMLHttpRequest(); + request.open('POST', 'https://scpscan.net/mpw/statistic', true); + request.setRequestHeader('Content-Type', 'application/json'); + request.send(JSON.stringify(cStats)); + return true; + } +} + +let _network = null; + +/** + * Sets the network in use by MPW. + * @param {ExplorerNetwork} network - network to use + */ +export function setNetwork(network) { + _network = network; +} + +/** + * Gets the network in use by MPW. + * @returns {ExplorerNetwork?} Returns the network in use, may be null if MPW hasn't properly loaded yet. + */ +export function getNetwork() { + return _network; +} + +/** + * A Fetch wrapper which uses the current Blockbook Network's base URL + * @param {string} api - The specific Blockbook api to call + * @param {RequestInit} options - The Fetch options + * @returns {Promise} - The unresolved Fetch promise + */ +export function fetchBlockbook(api, options) { + return fetch(_network.strUrl + api, options); +} + +/** + * A wrapper for Blockbook calls which can, in the event of an unresponsive explorer, + * seamlessly attempt the same call on multiple other explorers until success. + * @param {Function} func - The function to re-attempt with + * @param {...any} args - The arguments to pass to the function + */ +async function retryWrapper(func, ...args) { + // Track internal errors from the wrapper + let err; + + // If allowed by the user, Max Tries is ALL MPW-supported explorers, otherwise, restrict to only the current one. + let nMaxTries = cChainParams.current.Explorers.length; + let retries = 0; + + // The explorer index we started at + let nIndex = cChainParams.current.Explorers.findIndex( + (a) => a.url === getNetwork().strUrl + ); + + // Run the call until successful, or all attempts exhausted + while (retries < nMaxTries) { + try { + // Call the passed function with the arguments + const res = await func(...args); + + // If the endpoint is non-OK, assume it's an error + if (!res.ok) throw res; + + // Return the result if successful + return res; + } catch (error) { + err = error; + + // If allowed, switch explorers + if (!fAutoSwitch) throw err; + nIndex = (nIndex + 1) % cChainParams.current.Explorers.length; + const cNewExplorer = cChainParams.current.Explorers[nIndex]; + + // Set the explorer at Network-class level, then as a hacky workaround for the current callback; we + // ... adjust the internal URL to the new explorer. + getNetwork().strUrl = cNewExplorer.url; + setExplorer(cNewExplorer, true); + + // Bump the attempts, and re-try next loop + retries++; + } + } + + // Throw an error so the calling code knows the operation failed + throw err; } diff --git a/scripts/parsed_secret.js b/scripts/parsed_secret.js new file mode 100644 index 000000000..8a68eb342 --- /dev/null +++ b/scripts/parsed_secret.js @@ -0,0 +1,125 @@ +import { cleanAndVerifySeedPhrase } from './wallet.js'; +import { parseWIF, verifyWIF } from './encoding.js'; +import { cChainParams } from './chain_params.js'; +import { LegacyMasterKey, HdMasterKey } from './masterkey.js'; +import { PIVXShield } from 'pivx-shield'; +import { mnemonicToSeed } from 'bip39'; +import { decrypt } from './aes-gcm.js'; +import { isBase64 } from './misc.js'; + +export class ParsedSecret { + /** + * @type {import('../masterkey.js').MasterKey} masterkey - Masterkey object derived from the secret + */ + masterKey; + /** + * @type {PIVXShield} shield - Shield object associated with the secret. Only provided if the secret contains a seed + */ + shield; + constructor(masterKey, shield = null) { + this.masterKey = masterKey; + this.shield = shield; + } + + /** + * Parses whatever the secret is to a MasterKey + * @param {string|number[]|Uint8Array} secret + * @returns {Promise} + */ + static async parse(secret, password = '', advancedMode) { + const rules = [ + { + test: (s) => Array.isArray(s) || s instanceof Uint8Array, + f: (s) => new ParsedSecret(new LegacyMasterKey({ pkBytes: s })), + }, + { + test: (s) => isBase64(s) && s.length >= 128, + f: async (s, p) => ParsedSecret.parse(await decrypt(s, p)), + }, + { + test: (s) => s.startsWith('xprv'), + f: (s) => new ParsedSecret(new HdMasterKey({ xpriv: s })), + }, + { + test: (s) => s.startsWith('xpub'), + f: (s) => new ParsedSecret(new HdMasterKey({ xpub: s })), + }, + { + test: (s) => + cChainParams.current.PUBKEY_PREFIX.includes(s[0]) && + s.length === 34, + f: (s) => new ParsedSecret(new LegacyMasterKey({ address: s })), + }, + { + test: (s) => verifyWIF(s), + f: (s) => ParsedSecret.parse(parseWIF(s)), + }, + { + test: (s) => s.includes(' '), + f: async (s) => { + const { ok, msg, phrase } = await cleanAndVerifySeedPhrase( + s, + advancedMode + ); + if (!ok) throw new Error(msg); + const seed = await mnemonicToSeed(phrase, password); + const pivxShield = await PIVXShield.create({ + seed, + // hardcoded value considering the last checkpoint, this is good both for mainnet and testnet + // TODO: take the wallet creation height in input from users + blockHeight: 4200000, + coinType: cChainParams.current.BIP44_TYPE, + // TODO: Change account index once account system is made + accountIndex: 0, + loadSaplingData: false, + }); + return new ParsedSecret( + new HdMasterKey({ + seed, + }), + pivxShield + ); + }, + }, + { + test: (s) => { + try { + const obj = JSON.parse(s); + return !!obj.mk; + } catch (_) { + return false; + } + }, + f: async (s) => { + const obj = JSON.parse(s); + const mk = (await ParsedSecret.parse(obj.mk)).masterKey; + let shield; + try { + if (obj.shield) + shield = await PIVXShield.create({ + extendedSpendingKey: obj.shield, + blockHeight: 4200000, + coinType: cChainParams.current.BIP44_TYPE, + accountIndex: 0, + loadSaplingData: false, + }); + } catch (_) {} + return new ParsedSecret(mk, shield); + }, + }, + ]; + + for (const rule of rules) { + let test; + try { + test = rule.test(secret, password); + } catch (e) { + test = false; + } + if (test) { + return await rule.f(secret, password); + } + } + return null; + } +} diff --git a/scripts/polyfills/crypto.js b/scripts/polyfills/crypto.js new file mode 100644 index 000000000..61014787b --- /dev/null +++ b/scripts/polyfills/crypto.js @@ -0,0 +1,49 @@ +// Lightweight polyfill for the NodeJS crypto module +import { sha256 } from '@noble/hashes/sha256'; +import { sha512 } from '@noble/hashes/sha512'; +import { sha1 } from '@noble/hashes/sha1'; +import { hmac } from '@noble/hashes/hmac'; +import { randomBytes as nobleRandomBytes } from '@noble/hashes/utils'; +import { Buffer } from 'buffer'; + +class WrappedCreate { + #hash; + + constructor(hash, ...args) { + this.#hash = hash.create(...args); + } + + update(buff) { + this.#hash.update(buff); + return this; + } + + digest() { + return Buffer.from(this.#hash.digest()); + } +} + +export const createHash = (hash, options) => { + if (options) throw new Error('Unfilled polyfill'); + let fun; + switch (hash) { + case 'sha256': + fun = sha256; + break; + case 'sha1': + fun = sha1; + break; + default: + throw new Error('Unfilled polyfill'); + } + return new WrappedCreate(fun); +}; + +export const createHmac = (hash, key) => { + if (hash !== 'sha512') throw new Error('unfilled polyfill'); + return new WrappedCreate(hmac, sha512, key); +}; + +export const randomBytes = (length) => { + return Buffer.from(nobleRandomBytes(length)); +}; diff --git a/scripts/prices.js b/scripts/prices.js new file mode 100644 index 000000000..e442c152b --- /dev/null +++ b/scripts/prices.js @@ -0,0 +1,133 @@ +import { getEventEmitter } from './event_bus.js'; +import { sleep } from './utils.js'; + +/** + * @typedef {Object} Currency + * @property {string} currency - The type of currency + * @property {number} value - The value of the currency + * @property {number} last_updated - The timestamp when this value was last updated + */ + +/** + * Oracle's primary instance. + * + * @todo Allow an array of Oracle instances for better privacy and decentralisation + */ +export const ORACLE_BASE = 'https://pivxla.bz/oracle/api/v1'; + +/** + * An Oracle instance + */ +export class Oracle { + /** + * The currencies cache map + * @type {Map} Map to store currency objects + */ + mapCurrencies = new Map(); + + /** + * A lock-like flag which waits until at least once successful "full fetch" of currencies has occurred. + * This flag massively lowers bandwidth by only fetching the bulk once, falling to per-currency APIs afterwards. + */ + #fLoadedCurrencies = false; + + /** + * Get the cached price in a specific display currency + * @param {string} strCurrency - The Oracle display currency + * @return {Number} + */ + getCachedPrice(strCurrency) { + return this.mapCurrencies.get(strCurrency)?.value || 0; + } + + /** + * Get a cached list of the supported display currencies + * + * **Note:** This is a read-only array, use the {@link mapCurrencies} map to mutate the cache + * @returns {Array} - A list of Oracle-supported display currencies + */ + getCachedCurrencies() { + return Array.from(this.mapCurrencies.values()); + } + + /** + * Get the price in a specific display currency with extremely low bandwidth + * @param {string} strCurrency - The Oracle display currency + * @return {Promise} + */ + async getPrice(strCurrency) { + try { + const cReq = await fetch(`${ORACLE_BASE}/price/${strCurrency}`); + + // If the request fails, we'll try to fallback to cache, otherwise return a safe empty state + if (!cReq.ok) return this.getCachedPrice(strCurrency); + + /** @type {Currency} */ + const cCurrency = await cReq.json(); + + // Update it + this.mapCurrencies.set(strCurrency, cCurrency); + + // And finally return it + return cCurrency.value; + } catch (e) { + console.warn( + 'Oracle: Failed to fetch ' + + strCurrency.toUpperCase() + + ' price!' + ); + console.warn(e); + return this.getCachedPrice(strCurrency); + } + } + + /** + * Get a list of the supported display currencies + * + * This should only be used sparingly due to higher bandwidth, prefer {@link getPrice} if you need fresh data for a single, or select few currencies. + * + * See {@link #fLoadedCurrencies} for more info on Oracle bandwidth saving. + * @returns {Promise>} - A list of Oracle-supported display currencies + */ + async getCurrencies() { + try { + const cReq = await fetch(`${ORACLE_BASE}/currencies`); + + // If the request fails, we'll try to fallback to cache, otherwise return a safe empty state + if (!cReq.ok) return this.getCachedCurrencies(); + + /** @type {Array} */ + const arrCurrencies = await cReq.json(); + + // Loop each currency and update the cache + for (const cCurrency of arrCurrencies) { + this.mapCurrencies.set(cCurrency.currency, cCurrency); + } + + // Now we've loaded all currencies: we'll flag it and use the lower bandwidth price fetches in the future + this.#fLoadedCurrencies = true; + return arrCurrencies; + } catch (e) { + console.warn('Oracle: Failed to fetch currencies!'); + console.warn(e); + return this.getCachedCurrencies(); + } + } + + async load() { + while (!this.#fLoadedCurrencies) { + await this.getCurrencies(); + if (!this.#fLoadedCurrencies) await sleep(5000); + } + // Update any listeners for the full currency list (Settings, etc) + getEventEmitter().emit('currency-loaded', this.mapCurrencies); + // Update the balance to render the price instantly + getEventEmitter().emit('balance-update'); + } +} + +/** + * The user-selected Price Oracle, used for all price data + * @type {Oracle} + */ +export let cOracle = new Oracle(); diff --git a/scripts/promos.js b/scripts/promos.js new file mode 100644 index 000000000..93501e2f0 --- /dev/null +++ b/scripts/promos.js @@ -0,0 +1,717 @@ +import { cChainParams, COIN } from './chain_params.js'; +import { Database } from './database.js'; +import { doms, restoreWallet, sweepAddress } from './global.js'; +import { createAlert, downloadBlob } from './misc.js'; +import { getAlphaNumericRand, arrayToCSV } from './utils.js'; +import { ALERTS, translation, tr } from './i18n.js'; +import { getNetwork } from './network.js'; +import { scanQRCode } from './scanner.js'; +import { createAndSendTransaction } from './legacy.js'; +import { UTXO, COutpoint } from './transaction.js'; +import { wallet } from './wallet.js'; +import { LegacyMasterKey } from './masterkey.js'; +import { deriveAddress } from './encoding.js'; +import { getP2PKHScript } from './script.js'; + +/** The fee in Sats to use for Creating or Redeeming PIVX Promos */ +export const PROMO_FEE = 10000; + +/** + * The global storage for temporary Promo Code wallets, this is used for sweeping funds + * @type {PromoWallet} + */ +export let cPromoWallet = null; + +export class PromoWallet { + /** + * @param {object} data - An object containing the PromoWallet data + * @param {string} data.code - The human-readable Promo Code + * @param {string} data.address - The public key associated with the Promo Code + * @param {Uint8Array} data.pkBytes - The private key bytes derived from the Promo Code + * @param {Date|number} data.time - The Date or timestamp the code was created + * @param {Array} data.utxos - UTXOs associated with the Promo Code + */ + constructor({ code, address, pkBytes, utxos, time }) { + /** @type {string} The human-readable Promo Code */ + this.code = code; + /** @type {string} The public key associated with the Promo Code */ + this.address = address; + /** @type {Uint8Array} The private key bytes derived from the Promo Code */ + this.pkBytes = pkBytes; + /** @type {Array} UTXOs associated with the Promo Code */ + this.utxos = utxos; + /** @type {Date|number} The Date or timestamp the code was created */ + this.time = time instanceof Date ? time : new Date(time); + } + + /** A flag to show if this UTXO has successfully synced UTXOs previously */ + fSynced = false; + + /** A lock to prevent this Promo from synchronisation races */ + fLock = false; + + /** + * Synchronise UTXOs and return the balance of the Promo Code + * @param {boolean} - Whether to use UTXO Cache, or sync from network + * @returns {Promise} - The Promo Wallet balance in sats + */ + async getBalance(fCacheOnly = false) { + // Refresh our UTXO set + if (!fCacheOnly) { + await this.getUTXOs(); + } + + // Return the sum of the set + return this.utxos.reduce((a, b) => a + b.value, 0); + } + + /** + * Synchronise UTXOs and return them + * @param {boolean} - Whether to sync simple UTXOs or full UTXOs + * @returns {Promise>} + */ + async getUTXOs(fFull = false) { + // For shallow syncs, don't allow racing: but Full syncs are allowed to bypass for Tx creation + if (!fFull && this.fLock) return this.utxos; + this.fLock = true; + + // If we don't have it, derive the public key from the promo code's WIF + if (!this.address) { + this.address = deriveAddress({ pkBytes: this.pkBytes }); + } + + // Check for UTXOs on the explorer + const arrSimpleUTXOs = await getNetwork().getUTXOs(this.address); + + // Generate the UTXO with scripts + this.utxos = []; + for (const cUTXO of arrSimpleUTXOs) { + this.utxos.push( + new UTXO({ + outpoint: new COutpoint({ + txid: cUTXO.txid, + n: cUTXO.vout, + }), + script: getP2PKHScript(this.address), + value: parseInt(cUTXO.value), + }) + ); + } + // Unlock, mark as synced and return the UTXO set + this.fLock = false; + this.fSynced = true; + return this.utxos; + } +} + +/** + * The mode of the Promo system: Redeem when true - Create when false. + */ +let fPromoRedeem = true; + +/** + * Sets the mode of the PIVX Promos UI + * @param {boolean} fMode - `true` to redeem, `false` to create + */ +export async function setPromoMode(fMode) { + fPromoRedeem = fMode; + + // Modify the UI to match the mode + if (fPromoRedeem) { + // Swap the buttons + doms.domRedeemCodeModeRedeemBtn.classList.add('active'); + doms.domRedeemCodeModeCreateBtn.classList.remove('active'); + + // Show camera button + doms.domRedeemCameraBtn.classList.remove('d-none'); + + // Show the redeem box, hide create box + doms.domRedeemCodeUse.style.display = ''; + doms.domRedeemCodeCreate.style.display = 'none'; + + // Set the title and confirm button + doms.domRedeemTitle.innerText = 'Redeem Code'; + doms.domRedeemCodeConfirmBtn.innerText = 'Redeem'; + + // Hide table + doms.domPromoTable.classList.add('d-none'); + + // Show smooth table animation + setTimeout(() => { + doms.domPromoTable.style.maxHeight = '0px'; + }, 100); + } else { + // Swap the buttons + doms.domRedeemCodeModeRedeemBtn.classList.remove('active'); + doms.domRedeemCodeModeCreateBtn.classList.add('active'); + + // Hide camera button + doms.domRedeemCameraBtn.classList.add('d-none'); + + // Show the redeem box, hide create box + doms.domRedeemCodeUse.style.display = 'none'; + doms.domRedeemCodeCreate.style.display = ''; + + // Set the title and confirm button + doms.domRedeemTitle.innerText = 'Create Code'; + doms.domRedeemCodeConfirmBtn.innerText = 'Create'; + + // Render saved codes + const cCodes = await renderSavedPromos(); + + // Show animation when promo creation thread has 1 or more items + if (arrPromoCreationThreads.length || cCodes.codes) { + // Refresh the Promo UI + await updatePromoCreationTick(); + + // Show table + doms.domRedeemCodeCreatePendingList.innerHTML = cCodes.html; + doms.domPromoTable.classList.remove('d-none'); + + // Show smooth table animation + setTimeout(() => { + doms.domPromoTable.style.maxHeight = 'min-content'; + }, 100); + } + } +} + +/** + * The GUI handler function for hitting the promo modal 'Confirm' button + */ +export function promoConfirm() { + if (fPromoRedeem) { + redeemPromoCode(doms.domRedeemCodeInput.value); + } else { + // Show table + doms.domPromoTable.classList.remove('d-none'); + + // Show smooth table animation + setTimeout(() => { + doms.domPromoTable.style.maxHeight = 'min-content'; + }, 100); + + createPromoCode( + doms.domRedeemCodeCreateInput.value, + Number(doms.domRedeemCodeCreateAmountInput.value) + ); + } +} + +/** + * A list of promo creation threads, each thread works on a unique code + * @type {Array} + */ +const arrPromoCreationThreads = []; + +/** + * A lock for updating promo-creation related UI and threads + */ +let fPromoIntervalStarted = false; + +/** + * Create a new 'PIVX Promos' code with a webworker + * @param {string} strCode - The Promo Code to create + * @param {number} nAmount - The Promo Code amount in coins + * @param {boolean} fAddRandomness - Whether to append Randomness to the code + */ +export async function createPromoCode(strCode, nAmount, fAddRandomness = true) { + // Determine if we're adding randomness - and if so, if it's appended entropy or full randomness + const strFinalCode = fAddRandomness + ? strCode + ? strCode + '-' + getAlphaNumericRand(5).toUpperCase() + : getAlphaNumericRand(10).toUpperCase() + : strCode; + + // Ensure the amount is sane + const min = 0.01; + if (nAmount < min) { + return createAlert( + 'warning', + tr(ALERTS.PROMO_MIN, [ + { min }, + { ticker: cChainParams.current.TICKER }, + ]) + ); + } + + // Ensure there's no more than half the device's cores used + if (arrPromoCreationThreads.length >= navigator.hardwareConcurrency) + return createAlert( + 'warning', + tr(ALERTS.PROMO_MAX_QUANTITY, [ + { quantity: navigator.hardwareConcurrency }, + ]), + 4000 + ); + + // Ensure the user has enough balance (Code amount + Redeem fee + Blockchain fee buffer) + const nReservedBalance = arrPromoCreationThreads.reduce( + (a, b) => a + b.amount * COIN, + 0 + ); + if (wallet.balance - nReservedBalance < nAmount * COIN + PROMO_FEE * 2) { + return createAlert( + 'warning', + tr(ALERTS.PROMO_NOT_ENOUGH, [ + { ticker: cChainParams.current.TICKER }, + ]), + 4000 + ); + } + + // Ensure the user doesn't create the same code twice + const db = await Database.getInstance(); + const arrCodes = (await db.getAllPromos()).concat(arrPromoCreationThreads); + if (arrCodes.some((a) => a.code === strFinalCode)) { + return createAlert('warning', ALERTS.PROMO_ALREADY_CREATED, 3000); + } + + // Create a new thread + const cThread = { + code: strFinalCode, + amount: nAmount, + thread: new Worker(new URL('./promos_worker.js', import.meta.url)), + txid: '', + update: function (evt) { + if (evt.data.type === 'progress') { + this.progress = evt.data.res.progress; + // If the State HTML is available, render it! + const cElement = document.getElementById('c' + this.code); + if (cElement) { + cElement.innerText = this.progress; + } + } else { + this.key = evt.data.res.bytes; + } + }, + end_state: '', + }; + + // Inject the promo code in to the thread context + cThread.thread.code = strFinalCode; + + // Setup it's internal update function + cThread.thread.onmessage = cThread.update; + + // Start the thread + cThread.thread.postMessage(strFinalCode); + + // Push to the global threads list + arrPromoCreationThreads.push(cThread); + + // Refresh the promo UI + await updatePromoCreationTick(); +} + +export async function deletePromoCode(strCode) { + // Delete any ongoing threads + const nThread = arrPromoCreationThreads.findIndex( + (a) => a.code === strCode + ); + if (nThread >= 0) { + // Terminate the Web Worker + arrPromoCreationThreads[nThread].thread.terminate(); + // Remove the thread from memory + arrPromoCreationThreads.splice(nThread, 1); + } + + // Delete the database entry, if it exists + const db = await Database.getInstance(); + await db.removePromo(strCode); + + // And splice from post-creation memory too, if it exists + const nMemIndex = arrPromoCodes.findIndex( + (cCode) => cCode.code === strCode + ); + if (nMemIndex >= 0) { + arrPromoCodes.splice(nMemIndex, 1); + } + + // Re-render promos + await updatePromoCreationTick(); +} + +/** + * A pair of code quantity and HTML + * @typedef {Object} RenderedPromoPair + * @property {number} codes - The number of codes returned in the response. + * @property {string} html - The HTML string returned in the response. + */ + +/** An in-memory representation of all created Promo Wallets + * @type {Array} + */ +let arrPromoCodes = []; + +/** + * Render locally-saved Promo Codes in the created list + * @type {Promise} - The code count and HTML pair + */ +export async function renderSavedPromos() { + // Begin rendering our list of codes + let strHTML = ''; + + // Finished or 'Saved' codes are hoisted to the top, static + const db = await Database.getInstance(); + const arrCodes = await db.getAllPromos(); + + // Render each code; sorted by Newest First, Oldest Last. + for (const cDiskCode of arrCodes.sort((a, b) => b.time - a.time)) { + // Move on-disk promos to a memory representation for quick state computation + let cCode = arrPromoCodes.find((code) => code.code === cDiskCode.code); + if (!cCode) { + // Push this disk promo to memory + cCode = cDiskCode; + arrPromoCodes.push(cCode); + } + + // Sync only the balance of the code (not full data) + cCode.getUTXOs(false); + const nBal = (await cCode.getBalance(true)) / COIN; + + // A code younger than ~3 minutes without a balance will just say 'confirming', since Blockbook does not return a balance for NEW codes + const fNew = cCode.time.getTime() > Date.now() - 60000 * 3; + + // If this code is allowed to be deleted or not + const fCannotDelete = !cCode.fSynced || fNew || nBal > 0; + + // Status calculation (defaults to 'fNew' condition) + let strStatus = 'Confirming...'; + if (!fNew) { + if (cCode.fSynced) { + strStatus = nBal > 0 ? 'Unclaimed' : 'Claimed'; + } else { + strStatus = 'Syncing'; + } + } + strHTML += ` + + ${ + fCannotDelete + ? '' + : '' + } + + ${ + cCode.code + } + ${ + fNew || !cCode.fSynced + ? '...' + : nBal + ' ' + cChainParams.current.TICKER + } + ${strStatus} + + `; + } + + // Return how many codes were rendered + return { codes: arrCodes.length, html: strHTML }; +} + +/** Export and download all PIVX Promos data in to a CSV format */ +export async function promosToCSV() { + const arrCSV = [ + // Titles + ['Promo Code', 'PIV (Remaining)', 'Funding Address'], + // Content + ]; + + // Push each code in to the CSV + for (const cCode of arrPromoCodes) { + arrCSV.push([ + cCode.code, + (await cCode.getBalance(true)) / COIN, + cCode.address, + ]); + } + + // Encode it + const cCSV = arrayToCSV(arrCSV); + + // Download it + downloadBlob(cCSV, 'promos.csv', 'text/csv;charset=utf-8;'); +} + +/** + * Handle the Promo Workers, Code Rendering, and update or prompt the UI appropriately + * @param {boolean} fRecursive - Whether this call is self-initiated or not + */ +export async function updatePromoCreationTick(fRecursive = false) { + // Begin rendering our list of codes + const cSavedCodes = await renderSavedPromos(); + let strHTML = cSavedCodes.html; + + // Loop all threads, displaying their progress + for (const cThread of arrPromoCreationThreads) { + // Check if the code is derived, if so, fill it with it's balance + if (cThread.thread.key && !cThread.end_state) { + const strAddress = deriveAddress({ pkBytes: cThread.thread.key }); + + // Ensure the wallet is unlocked + if (wallet.isViewOnly()) { + $('#redeemCodeModal').modal('hide'); + if (await restoreWallet(translation.walletUnlockPromo)) { + // Unlocked! Re-show the promo UI and continue + $('#redeemCodeModal').modal('show'); + } else { + // Failed to unlock, so just mark as cancelled + cThread.end_state = 'Cancelled'; + $('#redeemCodeModal').modal('show'); + } + } + + // Send the fill transaction if unlocked + if (!wallet.isViewOnly() || wallet.isHardwareWallet()) { + const res = await createAndSendTransaction({ + address: strAddress, + amount: Math.round(cThread.amount * COIN + PROMO_FEE), + }).catch((_) => { + // Failed to create this code - mark it as errored + cThread.end_state = 'Errored'; + }); + if (res && res.ok) { + cThread.txid = res.txid; + cThread.end_state = 'Done'; + } else { + // If it looks like it was purposefully cancelled, then mark it as such + cThread.end_state = 'Cancelled'; + } + } + } + + // The 'state' is either a percentage to completion, the TXID, or an arbitrary state (error, etc) + let strState = ''; + if (cThread.txid) { + // Complete state + strState = 'Confirming...'; + } else if (cThread.end_state) { + // Errored state (failed to broadcast, etc) + strState = cThread.end_state; + } else { + // Display progress + strState = + '' + + (cThread.thread.progress || 0) + + '%'; + } + + // Render the table row + strHTML = + ` + + + ${cThread.code} + ${cThread.amount} ${cChainParams.current.TICKER} + ${strState} + + ` + strHTML; + } + + // Render the compiled HTML + doms.domRedeemCodeCreatePendingList.innerHTML = strHTML; + + const db = await Database.getInstance(); + for (const cThread of arrPromoCreationThreads) { + if (cThread.end_state === 'Done') { + // Convert to PromoWallet + const cPromo = new PromoWallet({ + code: cThread.code, + address: deriveAddress({ pkBytes: cThread.thread.key }), + pkBytes: cThread.thread.key, + // For storage, UTXOs are not necessary, so are left empty + utxos: [], + time: Date.now(), + }); + + // Save to DB + await db.addPromo(cPromo); + + // Terminate and destroy the thread + cThread.thread.terminate(); + arrPromoCreationThreads.splice( + arrPromoCreationThreads.findIndex( + (a) => a.code === cThread.code + ), + 1 + ); + } + } + + // After the update completes, await another update in one second + if (!fPromoIntervalStarted || fRecursive) { + fPromoIntervalStarted = true; + setTimeout(() => updatePromoCreationTick(true), 1000); + } +} + +/** + * A sweep wrapper that handles the Promo UI after the sweep completes + */ +export async function sweepPromoCode() { + // Only allow clicking if there's a promo code loaded in memory + if (!cPromoWallet) return false; + + // Convert the Promo Wallet in to a LegacyMasterkey + const cSweepMasterkey = new LegacyMasterKey({ + pkBytes: cPromoWallet.pkBytes, + }); + + // Perform sweep + const strTXID = await sweepAddress( + await cPromoWallet.getUTXOs(true), + cSweepMasterkey, + PROMO_FEE + ); + + // Display the promo redeem results, then schedule a reset of the UI + if (strTXID) { + // Coins were redeemed! + const nAmt = ((await cPromoWallet.getBalance(true)) - PROMO_FEE) / COIN; + doms.domRedeemCodeETA.innerHTML = + '

You redeemed ' + + nAmt.toLocaleString('en-GB') + + ' ' + + cChainParams.current.TICKER + + '!'; + resetRedeemPromo(15); + } else { + // Most likely; this TX was claimed very recently and a mempool conflict occurred + doms.domRedeemCodeETA.innerHTML = + '

Oops, this code was valid, but someone may have claimed it seconds earlier!'; + doms.domRedeemCodeGiftIcon.classList.remove('fa-gift'); + doms.domRedeemCodeGiftIcon.classList.remove('fa-solid'); + doms.domRedeemCodeGiftIcon.classList.add('fa-face-frown'); + doms.domRedeemCodeGiftIcon.classList.add('fa-regular'); + resetRedeemPromo(7.5); + } +} + +/** + * Resets the 'Redeem' promo code system back to it's default state + * @param {number} nSeconds - The seconds to wait until the full reset + */ +export function resetRedeemPromo(nSeconds = 5) { + // Nuke the in-memory Promo Wallet + cPromoWallet = null; + + // Reset Promo UI + doms.domRedeemCodeInput.value = ''; + doms.domRedeemCodeGiftIcon.classList.remove('ptr'); + doms.domRedeemCodeGiftIcon.classList.remove('fa-shake'); + + // After the specified seconds, reset the UI fully, and wipe the Promo Wallet + setTimeout(() => { + doms.domRedeemCodeETA.innerHTML = ''; + doms.domRedeemCodeInputBox.style.display = ''; + doms.domRedeemCodeGiftIconBox.style.display = 'none'; + doms.domRedeemCodeGiftIcon.classList.add('fa-gift'); + doms.domRedeemCodeGiftIcon.classList.add('fa-solid'); + doms.domRedeemCodeGiftIcon.classList.remove('fa-face-frown'); + doms.domRedeemCodeGiftIcon.classList.remove('fa-regular'); + doms.domRedeemCodeConfirmBtn.style.display = ''; + }, nSeconds * 1000); +} + +/** + * @type {Worker?} - The thread used for the PIVX Promos redeem process + */ +export let promoThread = null; + +/** + * Derive a 'PIVX Promos' code with a webworker + * @param {string} strCode - The Promo Code to derive + */ +export async function redeemPromoCode(strCode) { + // Ensure a Promo Code is not already being redeemed + if (promoThread) return; + + // Create a new thread + promoThread = new Worker(new URL('./promos_worker.js', import.meta.url)); + + // Hide unnecessary UI components + doms.domRedeemCodeInputBox.style.display = 'none'; + doms.domRedeemCodeConfirmBtn.style.display = 'none'; + + // Display Progress data and Redeem Animations + doms.domRedeemCodeETA.style.display = ''; + doms.domRedeemCodeGiftIconBox.style.display = ''; + doms.domRedeemCodeGiftIcon.classList.add('fa-bounce'); + + // Listen for and report derivation progress + promoThread.onmessage = async (evt) => { + if (evt.data.type === 'progress') { + doms.domRedeemCodeProgress.style.display = ''; + doms.domRedeemCodeETA.innerHTML = + '

' + + evt.data.res.eta.toFixed(0) + + 's remaining to unwrap...

' + + evt.data.res.progress + + '%'; + doms.domRedeemCodeProgress.value = evt.data.res.progress; + } else { + // The finished key! + promoThread.terminate(); + promoThread = null; + + // Pause animations and finish 'unwrapping' by checking the derived Promo Key for a balance + doms.domRedeemCodeGiftIcon.classList.remove('fa-bounce'); + doms.domRedeemCodeProgress.style.display = 'none'; + doms.domRedeemCodeETA.innerHTML = '

Final checks...'; + + // Prepare the global Promo Wallet + cPromoWallet = new PromoWallet({ + code: strCode, + address: '', + pkBytes: evt.data.res.bytes, + utxos: [], + time: 0, + }); + + // Derive the Public Key and synchronise UTXOs from the network + const nBalance = await cPromoWallet.getBalance(); + + // Display if the code is Valid (has coins) or is empty + if (nBalance > 0) { + doms.domRedeemCodeGiftIcon.classList.add('fa-shake'); + doms.domRedeemCodeETA.innerHTML = + '

This code is verified! Tap the gift to open it!'; + doms.domRedeemCodeGiftIcon.classList.add('ptr'); + } else { + doms.domRedeemCodeETA.innerHTML = + '

This code had no balance!'; + doms.domRedeemCodeGiftIcon.classList.remove('fa-gift'); + doms.domRedeemCodeGiftIcon.classList.remove('fa-solid'); + doms.domRedeemCodeGiftIcon.classList.add('fa-face-frown'); + doms.domRedeemCodeGiftIcon.classList.add('fa-regular'); + resetRedeemPromo(); + } + } + }; + + // Send our 'Promo Code' to be derived on a separate thread, allowing a faster and non-blocking derivation + promoThread.postMessage(strCode); +} + +/** + * Prompt a QR scan for a PIVX Promos code + */ +export async function openPromoQRScanner() { + const cScan = await scanQRCode(); + + if (!cScan || !cScan.data) return; + + // Enter the scanned code in to the redeem box + doms.domRedeemCodeInput.value = cScan.data; +} diff --git a/scripts/promos_worker.js b/scripts/promos_worker.js new file mode 100644 index 000000000..6d5909d1d --- /dev/null +++ b/scripts/promos_worker.js @@ -0,0 +1,15 @@ +import { PromoCode } from 'pivx-promos'; + +// Listen for the caller giving the 'Promo Code' to derive +onmessage = async function (evt) { + const code = new PromoCode(evt.data); + + // Setup the progress emitter + code.progressEmitter.on('deriveProgress', (progress) => { + postMessage({ type: 'progress', res: progress }); + }); + + // Begin deriving, returning the finished key when complete + const cWallet = await code.derivePrivateKey(); + postMessage({ type: 'key', res: cWallet }); +}; diff --git a/scripts/scanner.js b/scripts/scanner.js new file mode 100644 index 000000000..61f435a80 --- /dev/null +++ b/scripts/scanner.js @@ -0,0 +1,78 @@ +import QrScanner from 'qr-scanner'; + +import { doms } from './global.js'; +import { ALERTS } from './i18n.js'; +import { createAlert } from './misc.js'; + +/** + * The active QR scanner (if one exists) + * @type {QrScanner} + */ +let scanner = null; + +/** + * Asynchronously prompt a QR code scan, returning the QR string data on resolve or a `false` rejection on cancel or error + * @returns {Promise} - QR String data | false + */ +export async function scanQRCode() { + // Don't create multiple scanners; in case of button spam + if (scanner) return false; + + // Check for Camera support + if (!QrScanner.hasCamera()) { + createAlert('warning', ALERTS.NO_CAMERAS, 3000); + return false; + } + + return await new Promise((resolve, _reject) => { + // Create a scanner + scanner = new QrScanner( + doms.domQrReaderStream, + (res) => { + stopQRScan(); + resolve(res); + }, + { + returnDetailedScanResult: true, + preferredCamera: 'environment', + highlightCodeOutline: true, + highlightScanRegion: true, + } + ); + + // Attempt to start scanning, then display the UI + scanner + .start() + .then(() => { + doms.domModalQRReader.classList.add('show'); + doms.domModalQRReader.style.display = 'block'; + }) + .catch((err) => { + createAlert('warning', err, 2500); + stopQRScan(); + resolve(); + }); + + // If the close button is clicked, shutdown the scanner and destroy it to free memory + doms.domCloseQrReaderBtn.addEventListener('click', () => { + stopQRScan(); + resolve(); + }); + }); +} + +/** + * Cancel an ongoing scan prompt + */ +export function stopQRScan() { + if (!scanner) return; + + // Hide the modal + doms.domModalQRReader.classList.remove('show'); + doms.domModalQRReader.style.display = 'none'; + + // Shutdown the scanner and destroy it to free memory + scanner.stop(); + scanner.destroy(); + scanner = null; +} diff --git a/scripts/script.js b/scripts/script.js new file mode 100644 index 000000000..4e1989a67 --- /dev/null +++ b/scripts/script.js @@ -0,0 +1,246 @@ +import bs58 from 'bs58'; +import { cChainParams } from './chain_params.js'; +import { bytesToHex, dSHA256 } from './utils.js'; + +export const P2PK_START_INDEX = 3; +export const OWNER_START_INDEX = 6; +export const COLD_START_INDEX = 28; + +export const OP = { + // push value + 0: 0x00, + FALSE: 0x00, // ALIAS FOR: 0 + PUSHDATA1: 0x4c, + PUSHDATA2: 0x4d, + PUSHDATA4: 0x4e, + '1NEGATE': 0x4f, + RESERVED: 0x50, + 1: 0x51, + TRUE: 0x51, // ALIAS FOR: 1 + 2: 0x52, + 3: 0x53, + 4: 0x54, + 5: 0x55, + 6: 0x56, + 7: 0x57, + 8: 0x58, + 9: 0x59, + 10: 0x5a, + 11: 0x5b, + 12: 0x5c, + 13: 0x5d, + 14: 0x5e, + 15: 0x5f, + 16: 0x60, + + // control + NOP: 0x61, + VER: 0x62, + IF: 0x63, + NOTIF: 0x64, + VERIF: 0x65, + VERNOTIF: 0x66, + ELSE: 0x67, + ENDIF: 0x68, + VERIFY: 0x69, + RETURN: 0x6a, + + // stack ops + TOALTSTACK: 0x6b, + FROMALTSTACK: 0x6c, + '2DROP': 0x6d, + '2DUP': 0x6e, + '3DUP': 0x6f, + '2OVER': 0x70, + '2ROT': 0x71, + '2SWAP': 0x72, + IFDUP: 0x73, + DEPTH: 0x74, + DROP: 0x75, + DUP: 0x76, + NIP: 0x77, + OVER: 0x78, + PICK: 0x79, + ROLL: 0x7a, + ROT: 0x7b, + SWAP: 0x7c, + TUCK: 0x7d, + + // splice ops + CAT: 0x7e, + SUBSTR: 0x7f, + LEFT: 0x80, + RIGHT: 0x81, + SIZE: 0x82, + + // bit logic + INVERT: 0x83, + AND: 0x84, + OR: 0x85, + XOR: 0x86, + EQUAL: 0x87, + EQUALVERIFY: 0x88, + RESERVED1: 0x89, + RESERVED2: 0x8a, + + // numeric + '1ADD': 0x8b, + '1SUB': 0x8c, + '2MUL': 0x8d, + '2DIV': 0x8e, + NEGATE: 0x8f, + ABS: 0x90, + NOT: 0x91, + '0NOTEQUAL': 0x92, + + ADD: 0x93, + SUB: 0x94, + MUL: 0x95, + DIV: 0x96, + MOD: 0x97, + LSHIFT: 0x98, + RSHIFT: 0x99, + + BOOLAND: 0x9a, + BOOLOR: 0x9b, + NUMEQUAL: 0x9c, + NUMEQUALVERIFY: 0x9d, + NUMNOTEQUAL: 0x9e, + LESSTHAN: 0x9f, + GREATERTHAN: 0xa0, + LESSTHANOREQUAL: 0xa1, + GREATERTHANOREQUAL: 0xa2, + MIN: 0xa3, + MAX: 0xa4, + + WITHIN: 0xa5, + + // crypto + RIPEMD160: 0xa6, + SHA1: 0xa7, + SHA256: 0xa8, + HASH160: 0xa9, + HASH256: 0xaa, + CODESEPARATOR: 0xab, + CHECKSIG: 0xac, + CHECKSIGVERIFY: 0xad, + CHECKMULTISIG: 0xae, + CHECKMULTISIGVERIFY: 0xaf, + + // expansion + NOP1: 0xb0, + NOP2: 0xb1, + CHECKLOCKTIMEVERIFY: 0xb1, // ALIAS FOR: NOP2 + NOP3: 0xb2, + NOP4: 0xb3, + NOP5: 0xb4, + NOP6: 0xb5, + NOP7: 0xb6, + NOP8: 0xb7, + NOP9: 0xb8, + NOP10: 0xb9, + + // zerocoin + ZEROCOINMINT: 0xc1, + ZEROCOINSPEND: 0xc2, + ZEROCOINPUBLICSPEND: 0xc3, + + // cold staking + CHECKCOLDSTAKEVERIFY_LOF: 0xd1, // last output free for masternode/budget payments + CHECKCOLDSTAKEVERIFY: 0xd2, + EXCHANGEADDR: 0xe0, + + INVALIDOPCODE: 0xff, +}; +Object.freeze(OP); + +export function getScriptForBurn(data) { + let cScript = []; + // Check if we're fitting any data into the TX + if (typeof data === 'string' && data.length > 0) { + let bData = new TextEncoder().encode(data); + cScript.push(OP['RETURN']); + cScript.push(OP['PUSHDATA1']); + // Append the byte array length + cScript.push(bData.length); + // Convert from uint8 to array and append the byte array + cScript = cScript.concat(Array.prototype.slice.call(bData)); + } else { + // Empty data, create a simple RETURN script + cScript.push(OP['RETURN']); + } + // Return the burn script + return cScript; +} + +/** + * Is a given script pay to public key hash? + * @param {Uint8Array} dataBytes - script as byte aray + * @returns {Boolean} True if the given script is P2PKH + */ +export function isP2PKH(dataBytes) { + return ( + dataBytes.length >= 25 && + dataBytes[0] == OP['DUP'] && + dataBytes[1] == OP['HASH160'] && + dataBytes[2] == 0x14 && + dataBytes[23] == OP['EQUALVERIFY'] && + dataBytes[24] == OP['CHECKSIG'] + ); +} + +/** + * Is a given script pay to cold stake? + * @param {Uint8Array} dataBytes - script as byte aray + * @returns {Boolean} True if the given script is P2CS + */ +export function isP2CS(dataBytes) { + return ( + dataBytes.length >= 51 && + dataBytes[0] == OP['DUP'] && + dataBytes[1] == OP['HASH160'] && + dataBytes[2] == OP['ROT'] && + dataBytes[3] == OP['IF'] && + (dataBytes[4] == OP['CHECKCOLDSTAKEVERIFY'] || + dataBytes[4] == OP['CHECKCOLDSTAKEVERIFY_LOF']) && + dataBytes[5] == 0x14 && + dataBytes[26] == OP['ELSE'] && + dataBytes[27] == 0x14 && + dataBytes[48] == OP['ENDIF'] && + dataBytes[49] == OP['EQUALVERIFY'] && + dataBytes[50] == OP['CHECKSIG'] + ); +} +/** + * Get address from the corresponding public key hash + * @param {Uint8Array} pkhBytes - public key hash + * @param isColdStake true if the hash corresponds to a cold stake owner address + * @return {String} Base58 encoded address + */ +export function getAddressFromHash(pkhBytes, isColdStake) { + const prefix = isColdStake + ? cChainParams.current.STAKING_ADDRESS + : cChainParams.current.PUBKEY_ADDRESS; + const buffer = new Uint8Array([prefix, ...pkhBytes]); + const checksum = dSHA256(buffer); + return bs58.encode([ + ...Array.from(buffer), + ...Array.from(checksum.slice(0, 4)), + ]); +} +/** + * Generate the P2KH Script from the corresponding public key + * @param {string} pubKey - public key encoded with base58 + * @return {string} Script in HEX + */ +export function getP2PKHScript(pubKey) { + const pkh = Uint8Array.from(bs58.decode(pubKey).slice(1, 21)); + let dataBytes = []; + dataBytes.push(OP['DUP']); + dataBytes.push(OP['HASH160']); + dataBytes.push(0x14); + dataBytes = dataBytes.concat(Array.prototype.slice.call(pkh)); + dataBytes.push(OP['EQUALVERIFY']); + dataBytes.push(OP['CHECKSIG']); + return bytesToHex(dataBytes); +} diff --git a/scripts/settings.js b/scripts/settings.js index 0d0b5bb45..de544c3ce 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -1,39 +1,693 @@ -//Settings Defaults -var debug = false; -var explorer = 'explorer.dogec.io'; -var networkEnabled = true; -//Users need not look below here. -//------------------------------ -var publicKeyForNetwork; -var trx; -var amountOfTransactions; -var balance; -var fee; -var privateKeyForTransactions; -var walletAlreadyMade = 0; -var wallet_version = '1.02'; -var closeTheAlert = '' -function setExplorer() { - explorer = document.getElementById("explorer").value - alert(`${explorer} has been set succesfully`); -} -function toggleDebug() { - if (debug) { - debug = false; - document.getElementById('Debug').innerHTML = ""; - } else { - debug = true; - document.getElementById('Debug').innerHTML = " DEBUG MODE " + closeTheAlert; - } - alert(`Debug set to ${debug}`); -} -function toggleNetwork() { - if (networkEnabled) { - networkEnabled = false; - document.getElementById('Network').innerHTML = ""; - } else { - networkEnabled = true; - document.getElementById('Network').innerHTML = " Network Enabled " + closeTheAlert; - } - alert(`Network set to ${networkEnabled}`); +import { + doms, + updateLogOutButton, + updateGovernanceTab, + dashboard, + refreshChainData, +} from './global.js'; +import { wallet, hasEncryptedWallet } from './wallet.js'; +import { cChainParams } from './chain_params.js'; +import { setNetwork, ExplorerNetwork, getNetwork } from './network.js'; +import { confirmPopup, createAlert } from './misc.js'; +import { + switchTranslation, + ALERTS, + translation, + arrActiveLangs, + tr, +} from './i18n.js'; +import { Database } from './database.js'; +import { getEventEmitter } from './event_bus.js'; +import countries from 'country-locale-map/countries.json'; + +// --- Default Settings +/** A mode that emits verbose console info for internal MPW operations */ +export let debug = false; +/** + * The user-selected display currency from Oracle + * @type {string} + */ +export let strCurrency = getDefaultCurrency(); + +/** + * @returns {string} currency based on settings + */ +function getDefaultCurrency() { + const langCode = navigator.languages[0]?.split('-')?.at(-1) || 'US'; + return ( + countries.find((c) => c.alpha2 === langCode)?.currency?.toLowerCase() || + 'usd' + ); +} + +/** The user-selected explorer, used for most of MPW's data synchronisation */ +export let cExplorer = cChainParams.current.Explorers[0]; +/** The user-selected MPW node, used for alternative blockchain data */ +export let cNode = cChainParams.current.Nodes[0]; +/** A mode which allows MPW to automatically select it's data sources */ +export let fAutoSwitch = true; +/** The decimals to display for the wallet balance */ +export let nDisplayDecimals = 2; +/** A mode which configures MPW towards Advanced users, with low-level feature access and less restrictions (Potentially dangerous) */ +export let fAdvancedMode = false; +/** automatically lock the wallet after any operation that requires unlocking */ +export let fAutoLockWallet = false; + +let transparencyReport; + +export class Settings { + /** + * @type {String} analytics level + */ + analytics; + /** + * @type {String} Explorer url to use + */ + explorer; + /** + * @type {String} Node url to use + */ + node; + /** + * @type {Boolean} The Auto-Switch mode state + */ + autoswitch; + /** + * @type {String} The user's active Cold Staking address + */ + coldAddress; + /** + * @type {String} translation to use + */ + translation; + /** + * @type {String} Currency to display + */ + displayCurrency; + /** + * @type {number} The decimals to display for the wallet balance + */ + displayDecimals; + /** + * @type {boolean} Whether Advanced Mode is enabled or disabled + */ + advancedMode; + /** + * @type {boolean} Whether auto lock feature is enabled or disabled + */ + autoLockWallet; + constructor({ + analytics, + explorer, + node, + autoswitch = true, + translation = '', + displayCurrency = getDefaultCurrency(), + displayDecimals = nDisplayDecimals, + advancedMode = false, + coldAddress = '', + autoLockWallet = false, + } = {}) { + this.analytics = analytics; + this.explorer = explorer; + this.node = node; + this.autoswitch = autoswitch; + this.translation = translation; + this.displayCurrency = displayCurrency; + this.displayDecimals = displayDecimals; + this.advancedMode = advancedMode; + this.autoLockWallet = autoLockWallet; + // DEPRECATED: Read-only below here, for migration only + this.coldAddress = coldAddress; + } +} + +// A list of statistic keys and their descriptions +export let STATS = { + // Stat key // Description of the stat, it's data, and it's purpose + hit: 'A ping indicating an app load, no unique data is sent.', + time_to_sync: 'The time in seconds it took for MPW to last synchronise.', + transaction: + 'A ping indicating a Tx, no unique data is sent, but may be inferred from on-chain time.', +}; + +export const cStatKeys = Object.keys(STATS); + +// A list of Analytics 'levels' at which the user may set depending on their privacy preferences +// NOTE: When changing Level Names, ensure the i18n system is updated to support them too +let arrAnalytics = [ + // Statistic level // Allowed statistics + { name: 'Disabled', stats: [] }, + { name: 'Minimal', stats: [STATS.hit, STATS.time_to_sync] }, + { + name: 'Balanced', + stats: [STATS.hit, STATS.time_to_sync, STATS.transaction], + }, +]; + +export let cAnalyticsLevel = arrAnalytics[2]; + +// Users need not look below here. +// ------------------------------ +// Global Keystore / Wallet Information + +// --- DOM Cache +export async function start() { + //TRANSLATIONS + //to make translations work we need to change it so that we just enable or disable the visibility of the text + doms.domTestnet.style.display = cChainParams.current.isTestnet + ? '' + : 'none'; + doms.domDebug.style.display = debug ? '' : 'none'; + + // Hook up the 'currency' select UI + document.getElementById('currency').onchange = function (evt) { + setCurrency(evt.target.value); + }; + + // Hook up the 'display decimals' slider UI + doms.domDisplayDecimalsSlider.onchange = function (evt) { + setDecimals(Number(evt.target.value)); + }; + + // Hook up the 'explorer' select UI + document.getElementById('explorer').onchange = function (evt) { + setExplorer( + cChainParams.current.Explorers.find( + (a) => a.url === evt.target.value + ) + ); + }; + + // Hook up the 'translation' select UI + document.getElementById('translation').onchange = function (evt) { + setTranslation(evt.target.value); + }; + + // Hook up the 'analytics' select UI + document.getElementById('analytics').onchange = function (evt) { + setAnalytics(arrAnalytics.find((a) => a.name === evt.target.value)); + }; + + await Promise.all([ + fillExplorerSelect(), + fillNodeSelect(), + fillTranslationSelect(), + ]); + + const database = await Database.getInstance(); + + // Fetch settings from Database + const { + analytics: strSettingAnalytics, + autoswitch, + displayCurrency, + displayDecimals, + advancedMode, + autoLockWallet, + // DEPRECATED: Below here are entries that are read-only due to being moved to a different location in the DB + coldAddress, + } = await database.getSettings(); + + // Cold Staking: As of v1.2.1 this was moved to the Account class, if any exists here, we'll migrate it then wipe it + // Note: We also only migrate Mainnet addresses, to keep the migration logic simple + if ( + coldAddress && + coldAddress.startsWith(cChainParams.main.STAKING_PREFIX) + ) { + const cAccount = await database.getAccount(); + // Ensure an account exists (it is possible that a Cold Address was set without a wallet being encrypted) + if (cAccount) { + // We'll add the Cold Address to the account + cAccount.coldAddress = coldAddress; + // Save the changes + await database.updateAccount(cAccount); + // And wipe the old setting + await database.setSettings({ coldAddress: '' }); + } + } + // auto lock wallet + fAutoLockWallet = autoLockWallet; + doms.domAutoLockModeToggler.checked = fAutoLockWallet; + configureAutoLockWallet(); + + // Set any Toggles to their default or DB state + // Network Auto-Switch + fAutoSwitch = autoswitch; + doms.domAutoSwitchToggle.checked = fAutoSwitch; + + // Advanced Mode + fAdvancedMode = advancedMode; + doms.domAdvancedModeToggler.checked = fAdvancedMode; + await configureAdvancedMode(); + + // Set the display currency + strCurrency = doms.domCurrencySelect.value = displayCurrency; + + // Set the display decimals + nDisplayDecimals = displayDecimals; + doms.domDisplayDecimalsSlider.value = nDisplayDecimals; + + // Apply translations to the transparency report + STATS = { + // Stat key // Description of the stat, it's data, and it's purpose + hit: translation.hit, + time_to_sync: translation.time_to_sync, + transaction: translation.transaction, + }; + transparencyReport = translation.transparencyReport; + arrAnalytics = [ + // Statistic level // Allowed statistics + { name: 'Disabled', stats: [] }, + { name: 'Minimal', stats: [STATS.hit, STATS.time_to_sync] }, + { + name: 'Balanced', + stats: [STATS.hit, STATS.time_to_sync, STATS.transaction], + }, + ]; + + // Initialise status icons as their default variables + doms.domNetwork.innerHTML = + ''; + + // Honour the "Do Not Track" header by default + if (!strSettingAnalytics && navigator.doNotTrack === '1') { + // Disabled + setAnalytics(arrAnalytics[0], true); + doms.domAnalyticsDescriptor.innerHTML = + '
Analytics disabled to honour "Do Not Track" browser setting, you may manually enable if desired, though!
'; + } else { + // Load from storage, or use defaults + setAnalytics( + (cAnalyticsLevel = + arrAnalytics.find((a) => a.name === strSettingAnalytics) || + cAnalyticsLevel), + true + ); + } + + // Add each analytics level into the UI selector + fillAnalyticSelect(); + + // Subscribe to events + subscribeToNetworkEvents(); + + // Check if password is encrypted + if (await hasEncryptedWallet()) { + doms.domChangePasswordContainer.classList.remove('d-none'); + } +} + +function subscribeToNetworkEvents() { + getEventEmitter().on('currency-loaded', async (mapCurrencies) => { + await fillCurrencySelect(mapCurrencies); + }); +} + +// --- Settings Functions +export async function setExplorer(explorer, fSilent = false) { + const database = await Database.getInstance(); + database.setSettings({ explorer: explorer.url }); + cExplorer = explorer; + + // Enable networking + notify if allowed + if (getNetwork()) { + getNetwork().strUrl = cExplorer.url; + } else { + const network = new ExplorerNetwork(cExplorer.url, wallet); + setNetwork(network); + } + + // Update the selector UI + doms.domExplorerSelect.value = cExplorer.url; + + if (!fSilent) + createAlert( + 'success', + tr(ALERTS.SWITCHED_EXPLORERS, [{ explorerName: cExplorer.name }]), + 2250 + ); +} + +async function setNode(node, fSilent = false) { + cNode = node; + const database = await Database.getInstance(); + database.setSettings({ node: node.url }); + + // Enable networking + notify if allowed + getNetwork().enable(); + if (!fSilent) + createAlert( + 'success', + tr(ALERTS.SWITCHED_NODE, [{ node: cNode.name }]), + 2250 + ); +} + +//TRANSLATION +/** + * Switches the translation and sets the translation preference to database + * @param {string} strLang + */ +export async function setTranslation(strLang = 'auto') { + await switchTranslation(strLang); + const database = await Database.getInstance(); + await database.setSettings({ translation: strLang }); + doms.domTranslationSelect.value = strLang; +} + +/** + * Sets and saves the display currency setting in runtime and database + * @param {string} currency - The currency string name + */ +async function setCurrency(currency) { + strCurrency = currency; + const database = await Database.getInstance(); + database.setSettings({ displayCurrency: strCurrency }); + // Update the UI to reflect the new currency + getEventEmitter().emit('balance-update'); +} + +/** + * Sets and saves the display decimals setting in runtime and database + * @param {number} decimals - The decimals to set for the display + */ +async function setDecimals(decimals) { + nDisplayDecimals = decimals; + const database = await Database.getInstance(); + database.setSettings({ displayDecimals: nDisplayDecimals }); + // Update the UI to reflect the new decimals + getEventEmitter().emit('balance-update'); +} + +/** + * Fills the translation dropbox on the settings page + */ +async function fillTranslationSelect() { + while (doms.domTranslationSelect.options.length > 0) { + doms.domTranslationSelect.remove(0); + } + + // Add each language into the UI selector + for (const cLang of arrActiveLangs) { + const opt = document.createElement('option'); + opt.innerHTML = `${cLang.emoji} ${cLang.display}`; + opt.value = cLang.code; + doms.domTranslationSelect.appendChild(opt); + } + + const database = await Database.getInstance(); + const { translation: strLang } = await database.getSettings(); + // And update the UI to reflect them (default to English if none) + doms.domTranslationSelect.value = strLang; +} + +/** + * Fills the display currency dropbox on the settings page + */ +async function fillCurrencySelect(mapCurrencies) { + while (doms.domCurrencySelect.options.length > 0) { + doms.domCurrencySelect.remove(0); + } + // Add each data source currency into the UI selector + for (const cCurrency of mapCurrencies.values()) { + const opt = document.createElement('option'); + opt.innerHTML = cCurrency.currency.toUpperCase(); + opt.value = cCurrency.currency; + doms.domCurrencySelect.appendChild(opt); + } + + const database = await Database.getInstance(); + let { displayCurrency } = await database.getSettings(); + if (!mapCurrencies.has(displayCurrency)) { + // Currency not supported; fallback to USD + displayCurrency = 'usd'; + database.setSettings({ displayCurrency }); + } + // And update the UI to reflect them + strCurrency = doms.domCurrencySelect.value = displayCurrency; +} + +/** + * Fills the Analytics Settings UI + */ +export function fillAnalyticSelect() { + const domAnalyticsSelect = document.getElementById('analytics'); + domAnalyticsSelect.innerHTML = ''; + for (const analLevel of arrAnalytics) { + const opt = document.createElement('option'); + // Apply translation to the display HTML + opt.value = analLevel.name; + opt.innerHTML = translation['analytic' + analLevel.name]; + domAnalyticsSelect.appendChild(opt); + } +} + +async function setAnalytics(level, fSilent = false) { + cAnalyticsLevel = level; + const database = await Database.getInstance(); + await database.setSettings({ analytics: level.name }); + + // For total transparency, we'll 'describe' the various analytic keys of this chosen level + let strDesc = '
--- ' + transparencyReport + ' ---

', + i = 0; + const nLongestKeyLen = cStatKeys.reduce((prev, e) => + prev.length >= e.length ? prev : e + ).length; + for (i; i < cAnalyticsLevel.stats.length; i++) { + const cStat = cAnalyticsLevel.stats[i]; + // This formats Stat keys into { $key $(padding) $description } + strDesc += + cStatKeys + .find((a) => STATS[a] === cStat) + .padEnd(nLongestKeyLen, ' ') + + ': ' + + cStat + + '
'; + } + + // Set display + notify if allowed + doms.domAnalyticsDescriptor.innerHTML = + cAnalyticsLevel.name === arrAnalytics[0].name + ? '' + : '
' +
+              strDesc +
+              '
'; + if (!fSilent) + createAlert( + 'success', + tr(ALERTS.SWITCHED_ANALYTICS, [ + { level: translation['analytic' + cAnalyticsLevel.name] }, + ]), + 2250 + ); +} + +/** + * Log out from the current wallet + */ +export async function logOut() { + if (wallet.isSyncing) { + createAlert('warning', `${ALERTS.WALLET_NOT_SYNCED}`, 3000); + return; + } + const fContinue = await confirmPopup({ + title: `${ALERTS.CONFIRM_POPUP_DELETE_ACCOUNT_TITLE}`, + html: ` +
+ + ${tr(translation.netSwitchUnsavedWarningSubtitle, [ + { network: cChainParams.current.name }, + ])}

+ + ${ + ALERTS.CONFIRM_POPUP_DELETE_ACCOUNT + } +
+
+ `, + }); + if (!fContinue) return; + const database = await Database.getInstance(); + await database.removeAccount({ publicKey: null }); + + getEventEmitter().emit('toggle-network'); + updateLogOutButton(); + createAlert('success', translation.accountDeleted, 3000); +} + +/** + * Toggle between Mainnet and Testnet + */ +export async function toggleTestnet() { + if (wallet.isLoaded() && !wallet.isSynced) { + createAlert('warning', `${ALERTS.WALLET_NOT_SYNCED}`, 3000); + doms.domTestnetToggler.checked = cChainParams.current.isTestnet; + return; + } + const cNextNetwork = cChainParams.current.isTestnet + ? cChainParams.main + : cChainParams.testnet; + + // If the current wallet is not saved, we'll ask the user for confirmation, since they'll lose their wallet if they switch with an unsaved wallet! + if (wallet.isLoaded() && !(await hasEncryptedWallet())) { + const fContinue = await confirmPopup({ + title: tr(translation.netSwitchUnsavedWarningTitle, [ + { network: cChainParams.current.name }, + ]), + html: `
+ ${tr(translation.netSwitchUnsavedWarningSubtitle, [ + { network: cChainParams.current.name }, + ])} +
+ ${tr(translation.netSwitchUnsavedWarningSubtext, [ + { network: cNextNetwork.name }, + ])} +
+
+ ${ + translation.netSwitchUnsavedWarningConfirmation + } +
`, + }); + + if (!fContinue) { + // Kick back the "toggle" switch + doms.domTestnetToggler.checked = cChainParams.current.isTestnet; + return; + } + } + + // Update current chain config + cChainParams.current = cNextNetwork; + + // Update UI and static tickers + doms.domTestnet.style.display = cChainParams.current.isTestnet + ? '' + : 'none'; + // Update testnet toggle in settings + doms.domTestnetToggler.checked = cChainParams.current.isTestnet; + await start(); + // Make sure we have the correct number of blocks before loading any wallet + await refreshChainData(); + getEventEmitter().emit('toggle-network'); + await updateGovernanceTab(); +} + +export function toggleDebug() { + debug = !debug; + doms.domDebug.style.display = debug ? '' : 'none'; +} + +/** + * Toggle the Auto-Switch mode at runtime and in DB + */ +export async function toggleAutoSwitch() { + fAutoSwitch = !fAutoSwitch; + + // Update the setting in the DB + const database = await Database.getInstance(); + await database.setSettings({ autoswitch: fAutoSwitch }); +} + +async function fillExplorerSelect() { + cExplorer = cChainParams.current.Explorers[0]; + + while (doms.domExplorerSelect.options.length > 0) { + doms.domExplorerSelect.remove(0); + } + + // Add each trusted explorer into the UI selector + for (const explorer of cChainParams.current.Explorers) { + const opt = document.createElement('option'); + opt.value = explorer.url; + opt.innerHTML = + explorer.name + ' (' + explorer.url.replace('https://', '') + ')'; + doms.domExplorerSelect.appendChild(opt); + } + + // Fetch settings from Database + const database = await Database.getInstance(); + const { explorer: strSettingExplorer } = await database.getSettings(); + + // For any that exist: load them, or use the defaults + await setExplorer( + cChainParams.current.Explorers.find( + (a) => a.url === strSettingExplorer + ) || cExplorer, + true + ); + + // And update the UI to reflect them + doms.domExplorerSelect.value = cExplorer.url; +} + +async function fillNodeSelect() { + cNode = cChainParams.current.Nodes[0]; + + while (doms.domNodeSelect.options.length > 0) { + doms.domNodeSelect.remove(0); + } + + // Add each trusted node into the UI selector + for (const node of cChainParams.current.Nodes) { + const opt = document.createElement('option'); + opt.value = node.url; + opt.innerHTML = + node.name + ' (' + node.url.replace('https://', '') + ')'; + doms.domNodeSelect.appendChild(opt); + } + + // Fetch settings from Database + const database = await Database.getInstance(); + const { node: strSettingNode } = await database.getSettings(); + + // For any that exist: load them, or use the defaults + setNode( + cChainParams.current.Nodes.find((a) => a.url === strSettingNode) || + cNode, + true + ); + + // And update the UI to reflect them + doms.domNodeSelect.value = cNode.url; +} + +/** + * Toggle Advanced Mode at runtime and in DB + */ +export async function toggleAdvancedMode() { + fAdvancedMode = !fAdvancedMode; + + // Configure the app accordingly + await configureAdvancedMode(); + + // Update the setting in the DB + const database = await Database.getInstance(); + await database.setSettings({ advancedMode: fAdvancedMode }); +} + +export async function toggleAutoLockWallet() { + fAutoLockWallet = !fAutoLockWallet; + configureAutoLockWallet(); + // Update the setting in the DB + const database = await Database.getInstance(); + await database.setSettings({ autoLockWallet: fAutoLockWallet }); +} + +/** + * Configure the app functionality and UI for the current mode + */ +async function configureAdvancedMode() { + getEventEmitter().emit('advanced-mode', fAdvancedMode); +} + +function configureAutoLockWallet() { + getEventEmitter().emit('auto-lock-wallet', fAutoLockWallet); +} + +export function changePassword() { + dashboard.changePassword(); } diff --git a/scripts/stake/Stake.vue b/scripts/stake/Stake.vue new file mode 100644 index 000000000..65067f3bb --- /dev/null +++ b/scripts/stake/Stake.vue @@ -0,0 +1,169 @@ + + + diff --git a/scripts/stake/StakeBalance.vue b/scripts/stake/StakeBalance.vue new file mode 100644 index 000000000..2e4ab7e65 --- /dev/null +++ b/scripts/stake/StakeBalance.vue @@ -0,0 +1,251 @@ + + + diff --git a/scripts/stake/StakeInput.vue b/scripts/stake/StakeInput.vue new file mode 100644 index 000000000..8607b4685 --- /dev/null +++ b/scripts/stake/StakeInput.vue @@ -0,0 +1,219 @@ + + + diff --git a/scripts/transaction.js b/scripts/transaction.js new file mode 100644 index 000000000..916979baa --- /dev/null +++ b/scripts/transaction.js @@ -0,0 +1,483 @@ +import { Buffer } from 'buffer'; +import { bytesToNum, numToBytes, numToVarInt, parseWIF } from './encoding.js'; +import { hexToBytes, bytesToHex, dSHA256 } from './utils.js'; +import { OP } from './script.js'; +import { varIntToNum, deriveAddress } from './encoding.js'; +import * as nobleSecp256k1 from '@noble/secp256k1'; +import { cChainParams, SAPLING_TX_VERSION } from './chain_params.js'; + +/** An Unspent Transaction Output, used as Inputs of future transactions */ +export class COutpoint { + /** + * @param {object} COutpoint + * @param {string} COutpoint.txid - Transaction ID + * @param {number} COutpoint.n - Outpoint position in the corresponding transaction + */ + constructor({ txid, n } = {}) { + /** Transaction ID + * @type {string} */ + this.txid = txid; + /** Outpoint position in the corresponding transaction + * @type {number} */ + this.n = n; + } + /** + * Sadly javascript sucks and we cannot directly compare Objects in Sets + * @returns {string} Unique string representation of the COutpoint + */ + toUnique() { + return this.txid + this.n.toString(); + } + + /** + * @param {string} str + */ + static fromUnique(str) { + return new COutpoint({ + txid: str.slice(0, 64), + n: parseInt(str.slice(64)), + }); + } +} + +export class CTxOut { + /** + * @param {object} CTxOut + * @param {string} CTxOut.script - Redeem script, in HEX + * @param {number} CTxOut.value - Value in satoshi + */ + constructor({ script, value } = {}) { + /** Redeem script, in hex + * @type {string} */ + this.script = script; + /** Value in satoshi + * @type {number} */ + this.value = value; + } + + isEmpty() { + return this.value == 0 && (this.script === 'f8' || this.script === ''); + } + + serialize() { + const scriptBytes = hexToBytes(this.script); + return [ + ...numToBytes(BigInt(this.value), 8), + ...numToVarInt(BigInt(scriptBytes.length)), + ...scriptBytes, + ]; + } +} +export class CTxIn { + /** + * @param {Object} CTxIn + * @param {COutpoint} CTxIn.outpoint - Outpoint of the UTXO that the vin spends + * @param {String} CTxIn.scriptSig - Script used to spend the corresponding UTXO, in hex + * @param {number} CTxIn.sequence - Sequence + */ + constructor({ outpoint, scriptSig, sequence = 4294967295 } = {}) { + /** Outpoint of the UTXO that the vin spends + * @type {COutpoint} */ + this.outpoint = outpoint; + /** Script used to spend the corresponding UTXO, in hex + * @type {string} */ + this.scriptSig = scriptSig; + this.sequence = sequence; + } +} +export class Transaction { + /** @type{number} */ + version; + /** @type{number} */ + blockHeight; + /** @type{CTxIn[]}*/ + vin = []; + /** @type{CTxOut[]}*/ + vout = []; + /** @type{number} */ + blockTime; + /** @type{number} */ + lockTime; + /** Cached txid */ + #txid = ''; + + constructor({ + version = 1, + blockHeight = -1, + vin = [], + vout = [], + blockTime = -1, + lockTime = 0, + valueBalance = 0, + shieldSpend = [], + shieldOutput = [], + bindingSig = '', + } = {}) { + this.version = version; + this.blockHeight = blockHeight; + this.vin = vin; + this.vout = vout; + this.blockTime = blockTime; + this.lockTime = lockTime; + this.shieldSpend = shieldSpend; + this.shieldOutput = shieldOutput; + this.bindingSig = bindingSig; + this.valueBalance = valueBalance; + /** Handle to the unproxied tx for when we need to clone it */ + this.__original = this; + return new Proxy(this, { + set(obj, p) { + if (p !== 'blockHeight' && p !== 'blockTime') { + obj.#txid = ''; + } + return Reflect.set(...arguments); + }, + }); + } + + get hasSaplingVersion() { + return this.version >= SAPLING_TX_VERSION; + } + + get txid() { + if (!this.__original.#txid) { + this.__original.#txid = bytesToHex( + dSHA256(hexToBytes(this.serialize())).reverse() + ); + } + return this.__original.#txid; + } + + get hasShieldData() { + return this.bindingSig !== ''; + } + + isConfirmed() { + return this.blockHeight != -1; + } + + isCoinStake() { + return this.vout.length >= 2 && this.vout[0].isEmpty(); + } + + isCoinBase() { + // txid is full of 0s for coinbase inputs + return ( + this.vin.length == 1 && !!this.vin[0].outpoint.txid.match(/^0*$/) + ); + } + + /** + * @param {Transaction} tx - transaction we want to check + * @returns {boolean} + */ + isImmature(blockCount) { + if (this.isCoinStake() || this.isCoinBase()) { + return ( + blockCount - this.blockHeight < + cChainParams.current.coinbaseMaturity + ); + } + return false; + } + + static fromHex(hex) { + const tx = new Transaction(); + return tx.fromHex(hex); + } + + /** + * @param {string} hex - hex encoded transaction + * @returns {Transaction} + */ + fromHex(hex) { + const bytes = hexToBytes(hex); + let offset = 0; + this.version = Number(bytesToNum(bytes.slice(offset, (offset += 4)))); + const { num: vinLength, readBytes } = varIntToNum(bytes.slice(offset)); + offset += readBytes; + this.vin = []; + for (let i = 0; i < Number(vinLength); i++) { + const txid = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const n = Number(bytesToNum(bytes.slice(offset, (offset += 4)))); + const { num: scriptLength, readBytes } = varIntToNum( + bytes.slice(offset) + ); + offset += readBytes; + const script = bytesToHex( + bytes.slice(offset, (offset += Number(scriptLength))) + ); + const sequence = Number( + bytesToNum(bytes.slice(offset, (offset += 4))) + ); + + const input = new CTxIn({ + outpoint: new COutpoint({ + txid, + n, + }), + scriptSig: script, + sequence, + }); + this.vin.push(input); + } + const { num: voutLength, readBytes: readBytesOut } = varIntToNum( + bytes.slice(offset) + ); + offset += readBytesOut; + + this.vout = []; + for (let i = 0; i < voutLength; i++) { + const value = bytesToNum(bytes.slice(offset, (offset += 8))); + const { num: scriptLength, readBytes } = varIntToNum( + bytes.slice(offset) + ); + offset += readBytes; + const script = bytesToHex( + bytes.slice(offset, (offset += Number(scriptLength))) + ); + + this.vout.push( + new CTxOut({ + script, + value: Number(value), + }) + ); + } + + this.lockTime = Number(bytesToNum(bytes.slice(offset, (offset += 4)))); + this.shieldSpend = []; + this.shieldOutput = []; + if (this.hasSaplingVersion) { + const hasShield = bytesToNum(bytes.slice(offset, (offset += 1))); + if (hasShield) { + this.valueBalance = Number( + new BigInt64Array([ + bytesToNum(bytes.slice(offset, (offset += 8))), + ])[0] + ); + + const { num: shieldSpendLen, readBytes } = varIntToNum( + bytes.slice(offset) + ); + offset += readBytes; + for (let i = 0; i < shieldSpendLen; i++) { + const cv = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const anchor = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const nullifier = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const rk = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const proof = bytesToHex( + bytes.slice(offset, (offset += 192)) + ); + const spendAuthSig = bytesToHex( + bytes.slice(offset, (offset += 64)) + ); + + this.shieldSpend.push({ + cv, + anchor, + nullifier, + rk, + proof, + spendAuthSig, + }); + } + const { num: outputLen, readBytes: readOutBytes } = varIntToNum( + bytes.slice(offset) + ); + offset += readOutBytes; + for (let i = 0; i < outputLen; i++) { + const cv = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const cmu = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const ephemeralKey = bytesToHex( + bytes.slice(offset, (offset += 32)).reverse() + ); + const encCiphertext = bytesToHex( + bytes.slice(offset, (offset += 580)) + ); + const outCiphertext = bytesToHex( + bytes.slice(offset, (offset += 80)) + ); + const proof = bytesToHex( + bytes.slice(offset, (offset += 192)) + ); + + this.shieldOutput.push({ + cv, + cmu, + ephemeralKey, + encCiphertext, + outCiphertext, + proof, + }); + } + this.bindingSig = bytesToHex( + bytes.slice(offset, (offset += 64)) + ); + } + } + this.__original.#txid = bytesToHex(dSHA256(hexToBytes(hex)).reverse()); + return this; + } + + serialize() { + let buffer = [ + ...numToBytes(BigInt(this.version), 4), + ...numToVarInt(BigInt(this.vin.length)), + ]; + + for (const input of this.vin) { + const scriptBytes = hexToBytes(input.scriptSig); + buffer = [ + ...buffer, + ...hexToBytes(input.outpoint.txid).reverse(), + ...numToBytes(BigInt(input.outpoint.n), 4), + ...numToVarInt(BigInt(scriptBytes.length)), + ...scriptBytes, + ...numToBytes(BigInt(input.sequence), 4), + ]; + } + + buffer = [...buffer, ...numToVarInt(BigInt(this.vout.length))]; + for (const output of this.vout) { + const scriptBytes = hexToBytes(output.script); + buffer = [ + ...buffer, + ...numToBytes(BigInt(output.value), 8), + ...numToVarInt(BigInt(scriptBytes.length)), + ...scriptBytes, + ]; + } + buffer = [...buffer, ...numToBytes(BigInt(this.lockTime), 4)]; + + if (this.hasSaplingVersion) { + const valueBalance = Buffer.alloc(8); + valueBalance.writeBigInt64LE(BigInt(this.valueBalance)); + buffer = [ + ...buffer, + Number(this.hasShieldData), + ...valueBalance, + ...numToVarInt(BigInt(this.shieldSpend.length)), + ]; + for (const spend of this.shieldSpend) { + buffer = [ + ...buffer, + ...hexToBytes(spend.cv).reverse(), + ...hexToBytes(spend.anchor).reverse(), + ...hexToBytes(spend.nullifier).reverse(), + ...hexToBytes(spend.rk).reverse(), + ...hexToBytes(spend.proof), + ...hexToBytes(spend.spendAuthSig), + ]; + } + buffer = [ + ...buffer, + ...numToVarInt(BigInt(this.shieldOutput.length)), + ]; + for (const output of this.shieldOutput) { + buffer = [ + ...buffer, + ...hexToBytes(output.cv).reverse(), + ...hexToBytes(output.cmu).reverse(), + ...hexToBytes(output.ephemeralKey).reverse(), + ...hexToBytes(output.encCiphertext), + ...hexToBytes(output.outCiphertext), + ...hexToBytes(output.proof), + ]; + } + buffer = [...buffer, ...hexToBytes(this.bindingSig)]; + } + + return bytesToHex(buffer); + } + + /** + * Get the transaction hash of the indexth input + * Using the sighash type SIGHASH_ALL + */ + transactionHash(index) { + if (this.hasSaplingVersion) { + throw new Error('tx version too high, cannot use base tx hash'); + } + const copy = structuredClone(this.__original); + // Black out all inputs + for (let i = 0; i < copy.vin.length; i++) { + if (i != index) copy.vin[i].scriptSig = ''; + } + return bytesToHex( + dSHA256([ + ...hexToBytes(this.serialize.bind(copy)()), + //...hexToBytes(this.serialize()), + ...numToBytes(1n, 4), // SIGHASH_ALL + ]) + ); + } + + /** + * Signs an input using the given wif + * @param {number} index - Which vin to sign + * @param {string} wif - base58 encoded private key + * @param {object} [options] + * @param {boolean} [isColdStake] - Whether or not we're signing a cold stake input + */ + async signInput(index, wif, { isColdStake = false } = {}) { + const pubkeyBytes = hexToBytes( + deriveAddress({ + pkBytes: parseWIF(wif), + output: 'COMPRESSED_HEX', + }) + ); + const txhash = this.transactionHash(index); + let signature = Array.from( + await nobleSecp256k1.sign(txhash, parseWIF(wif), { + canonical: true, + }) + ); + signature.push(1); // SIGHASH_ALL + + this.vin[index].scriptSig = bytesToHex([ + signature.length, + ...signature, + // OP_FALSE to flag the redeeming of the delegation back to the Owner Address + ...(isColdStake ? [OP['FALSE']] : []), + pubkeyBytes.length, + ...pubkeyBytes, + ]); + } +} + +export class UTXO { + /** @type {COutpoint} */ + outpoint; + /** + * @type {string} script in hex + */ + script; + /** + * @type {number} value in satoshi + */ + value; + + /** + * @param {{outpoint: COutpoint, script: string, value: number}} + */ + constructor({ outpoint, script, value }) { + this.outpoint = outpoint; + this.script = script; + this.value = value; + } +} diff --git a/scripts/transaction_builder.js b/scripts/transaction_builder.js new file mode 100644 index 000000000..4c726e82c --- /dev/null +++ b/scripts/transaction_builder.js @@ -0,0 +1,297 @@ +import { Transaction, CTxIn, CTxOut, COutpoint } from './transaction.js'; +import bs58 from 'bs58'; +import { OP } from './script.js'; +import { hexToBytes, bytesToHex, dSHA256 } from './utils.js'; +import { isShieldAddress, isExchangeAddress } from './misc.js'; +import { SAPLING_TX_VERSION } from './chain_params.js'; +/** + * @class Builds a non-signed transaction + */ +export class TransactionBuilder { + #transaction = new Transaction(); + #valueIn = 0; + #valueOut = 0; + + // Part of the tx fee that has been already handled + #handledFee = 0; + MIN_FEE_PER_BYTE = 10; + // This number is larger or equal than the max size of the script sig for a P2CS and P2PKH transaction + SCRIPT_SIG_MAX_SIZE = 108; + + get valueIn() { + return this.#valueIn; + } + + get valueOut() { + return this.#valueOut; + } + + get value() { + return this.#valueIn - this.#valueOut; + } + /** + * See if the given CTxOut is plain dust + * @param{{out:CTxOut, value: number}} + */ + isDust({ out, value }) { + // Dust is a transaction such that its creation costs more than its value + return value < out.serialize().length * this.MIN_FEE_PER_BYTE; + } + + constructor() { + this.#transaction.version = 1; + this.#transaction.blockHeight = -1; // Not yet sent + this.#transaction.blockTime = -1; + this.#transaction.lockTime = 0; + } + + /** + * Utility function to make chaining easier to read. + */ + static create() { + return new TransactionBuilder(); + } + + getFee() { + //TODO: find a cleaner way to add the dummy signature + let scriptSig = []; + for (let vin of this.#transaction.vin) { + scriptSig.push(vin.scriptSig); + // Insert a dummy signature just to compute fees + vin.scriptSig = bytesToHex(Array(this.SCRIPT_SIG_MAX_SIZE).fill(0)); + } + + const fee = + Math.ceil(this.#transaction.serialize().length / 2) * + this.MIN_FEE_PER_BYTE - + this.#handledFee; + // Re-insert whatever was inside before + for (let i = 0; i < scriptSig.length; i++) { + this.#transaction.vin[i].scriptSig = scriptSig[i]; + } + return fee; + } + + /** + * @returns {TransactionBuilder} + */ + #addInput({ txid, n, scriptSig }) { + this.#transaction.vin.push( + new CTxIn({ + outpoint: new COutpoint({ + txid, + n, + }), + scriptSig, + }) + ); + return this; + } + + /** + * Add an unspent transaction output to the inputs + * @param {UTXO} utxo + * @returns {TransactionBuilder} + */ + addUTXO(utxo) { + this.#addInput({ + txid: utxo.outpoint.txid, + n: utxo.outpoint.n, + scriptSig: utxo.script, + }); + this.#valueIn += utxo.value; + return this; + } + + /** + * Add an array of UTXOs to the inputs + * @param {CTxOut[]} utxos + * @returns {TransactionBuilder} + */ + addUTXOs(utxos) { + for (const utxo of utxos) { + this.addUTXO(utxo); + } + return this; + } + + /** + * @param {string} address - Address to decode + * @returns {number[]} Decoded address in bytes + */ + #decodeAddress(address) { + const bytes = bs58.decode(address); + const front = bytes.slice(0, bytes.length - 4); + const back = bytes.slice(bytes.length - 4); + const checksum = dSHA256(front).slice(0, 4); + if (checksum + '' == back + '') { + return Array.from(front.slice(isExchangeAddress(address) ? 3 : 1)); + } + throw new Error('Invalid address'); + } + + /** + * Add a shield output. Automatically called by `addOutput` + * @param {{address: string, value: number}} + * @returns {TransactionBuilder} + */ + #addShieldOutput({ address, value }) { + this.#transaction.version = SAPLING_TX_VERSION; + // We don't know how to create shieldData, so we create + // a dummy object so we can pass it later to the Shield library + // upon signing. + // This is similar to how we temporarely use the UTXO script instead + // of the scriptSig because we don't know how to sign it + this.#transaction.shieldOutput.push({ address, value }); + return this; + } + + /** + * Add an exchange output to the transaction. + * @param{{address:string, value: number}} + */ + #addExchangeOutput({ address, value }) { + const decoded = this.#decodeAddress(address); + const script = [ + OP['EXCHANGEADDR'], + OP['DUP'], + OP['HASH160'], + decoded.length, + ...decoded, + OP['EQUALVERIFY'], + OP['CHECKSIG'], + ]; + this.#addScript({ script, value }); + } + + #addScript({ script, value, subtractFeeFromAmt = false }) { + let out = new CTxOut({ + script: bytesToHex(script), + value, + }); + // if subtractFeeFromAmt has been set do NOT add dust + // We would end up with an UTXO with negative value + if (this.isDust({ out, value }) && subtractFeeFromAmt) { + return; + } + const fee = out.serialize().length * this.MIN_FEE_PER_BYTE; + // We have subtracted fees from the value, mark this fee as handled (don't pay them again) + if (subtractFeeFromAmt) { + out.value -= fee; + this.#handledFee += fee; + } + this.#transaction.vout.push(out); + this.#valueOut += out.value; + } + + /** + * Adds a P2PKH output to the transaction + * @param {{address: string, value: number, isChange: boolean}} + * @returns {TransactionBuilder} + */ + #addP2pkhOutput({ address, value, isChange }) { + const decoded = this.#decodeAddress(address); + const script = [ + OP['DUP'], + OP['HASH160'], + decoded.length, + ...decoded, + OP['EQUALVERIFY'], + OP['CHECKSIG'], + ]; + this.#addScript({ script, value, subtractFeeFromAmt: isChange }); + } + + /** + * Adds an output to the transaction + * @param {{address: string, value: number, isChange: boolean}} + * @returns {TransactionBuilder} + */ + addOutput({ address, value, isChange = false }) { + if (isShieldAddress(address)) { + this.#addShieldOutput({ address, value }); + } else if (isExchangeAddress(address)) { + this.#addExchangeOutput({ address, value }); + } else { + this.#addP2pkhOutput({ address, value, isChange }); + } + + return this; + } + + addOutputs(outputs) { + for (const output of outputs) { + this.addOutput(output); + } + return this; + } + + /** + * Adds a proposal output to the transaction + * @param {{hash: string, value: number}} + * @returns {TransactionBuilder} + */ + addProposalOutput({ hash, value }) { + this.#addScript({ + script: [OP['RETURN'], 32, ...hexToBytes(hash)], + value, + }); + return this; + } + + /** + * Adds a cold stake output to the transaction + * @param {{address: string, addressColdStake: string, value: number}} + * @returns {TransactionBuilder} + */ + addColdStakeOutput({ address, addressColdStake, value, isChange }) { + const decodedAddress = this.#decodeAddress(address); + const decodedAddressColdStake = this.#decodeAddress(addressColdStake); + const script = [ + OP['DUP'], + OP['HASH160'], + OP['ROT'], + OP['IF'], + OP['CHECKCOLDSTAKEVERIFY_LOF'], + decodedAddressColdStake.length, + ...decodedAddressColdStake, + OP['ELSE'], + decodedAddress.length, + ...decodedAddress, + OP['ENDIF'], + OP['EQUALVERIFY'], + OP['CHECKSIG'], + ]; + this.#addScript({ script, value, subtractFeeFromAmt: isChange }); + return this; + } + + // Equally subtract a value from every output of the tx + equallySubtractAmt(value) { + const tx = this.#transaction; + let first = true; + const outputs = tx.vout.length; + if (!outputs || outputs == 0) { + throw new Error('tx has no outputs!'); + } + for (let vout of tx.vout) { + vout.value -= Math.floor(value / outputs); + // The first pays the remainder + if (first) { + vout.value -= value % outputs; + first = false; + } + } + } + + build() { + const tx = this.#transaction; + if (tx && !tx.vin.length) { + // If the tx doesn't have any clear inputs, + // it must be a shield transaction + tx.version = SAPLING_TX_VERSION; + } + this.#transaction = null; + return tx; + } +} diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 000000000..fc0fbad9d --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,139 @@ +import { Buffer } from 'buffer'; +import { sha256 } from '@noble/hashes/sha256'; + +export const pubKeyHashNetworkLen = 21; +export const pubChksum = 4; +export const pubPrebaseLen = pubKeyHashNetworkLen + pubChksum; + +export function hexToBytes(str) { + return Buffer.from(str, 'hex'); +} + +export function bytesToHex(bytes) { + return Buffer.from(bytes).toString('hex'); +} + +/** + * Double SHA256 hash a byte array + * @param {Array} buff - Bytes to hash + * @returns {Uint8Array} Hash buffer + */ +export function dSHA256(buff) { + return sha256(sha256(new Uint8Array(buff))); +} + +/* --- UTILS --- */ +// Cryptographic Random-Gen +export function getSafeRand(nSize = 32) { + return crypto.getRandomValues(new Uint8Array(nSize)); +} + +export const MAP_ALPHANUMERIC = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + +/** + * Generate a random Alpha-Numeric sequence + * @param {number} nSize - The amount of characters to generate + * @returns {string} - A random alphanumeric string of nSize length + */ +export function getAlphaNumericRand(nSize = 32) { + let result = ''; + const randValues = getSafeRand(nSize); + for (const byte of randValues) { + const index = byte % MAP_ALPHANUMERIC.length; + result += MAP_ALPHANUMERIC.charAt(index); + } + return result; +} + +// Writes a sequence of Array-like bytes into a location within a Uint8Array +export function writeToUint8(arr, bytes, pos) { + const arrLen = arr.length; + // Sanity: ensure an overflow cannot occur, if one is detected, somewhere in MPW's state could be corrupted. + if (arrLen - pos - bytes.length < 0) { + const strERR = + 'CRITICAL: Overflow detected (' + + (arrLen - pos - bytes.length) + + '), possible state corruption, backup and refresh advised.'; + throw new Error(strERR); + } + let i = 0; + while (pos < arrLen) arr[pos++] = bytes[i++]; +} + +/** Convert a 2D array into a CSV string */ +export function arrayToCSV(data) { + return data + .map( + (row) => + row + .map(String) // convert every value to String + .map((v) => v.replaceAll('"', '""')) // escape double colons + .map((v) => `"${v}"`) // quote it + .join(',') // comma-separated + ) + .join('\r\n'); // rows starting on new lines +} + +/** + * Start a batch of promises, processing them concurrently up to `batchSize`. + * This does *not* run them in parallel. Only 1 CPU core is used + * @template T + * @param {(number)=>Promise} promiseFactory - Function that spawns promises based + * on a number. 0 is the first, length-1 is the last one. + * @param {number} length - How many promises to spawn + * @param {number} batchSize - How many promises to spawn at a time + * @returns {Promise} array of the return value of the promise. + * It's guaranteed to be sorted, i.e. it will contain + * [ await promsieFactory(0), await promisefactory(1), ... ] + * If the promises depend on each other, then behavior is undefined + */ +export async function startBatch( + promiseFactory, + length, + batchSize, + retryTime = 10000 +) { + if (length == 0) { + return; + } + return new Promise((res) => { + const running = []; + let i = 0; + const startNext = async (current) => { + let result; + try { + result = await promiseFactory(current); + } catch (e) { + // Try again later + await sleep(retryTime); + return await startNext(current); + } + i++; + if (i < length) { + running.push(startNext(i)); + } else { + (async () => res(await Promise.all(running)))(); + } + return result; + }; + // Start fisrt batchsize promises + for (i = 0; i < batchSize && i < length; i++) { + running.push(startNext(i)); + } + --i; + }); +} + +/** + * An artificial sleep function to pause code execution + * + * @param {Number} ms - The milliseconds to sleep + * + * @example + * // Pause an asynchronous script for 1 second + * await sleep(1000); + */ +export function sleep(ms) { + return new Promise((res, _) => setTimeout(res, ms)); +} diff --git a/scripts/vanitygen_worker.js b/scripts/vanitygen_worker.js new file mode 100644 index 000000000..ab41cbe0c --- /dev/null +++ b/scripts/vanitygen_worker.js @@ -0,0 +1,19 @@ +import { cChainParams } from './chain_params.js'; +import { deriveAddress } from './encoding.js'; +import { getSafeRand } from './utils.js'; + +/** + * @param {MessageEvent<'main'|'testnet'>} event + */ +onmessage = (event) => { + while (true) { + // Ensure we're using the correct network + cChainParams.current = + cChainParams[event.data === 'mainnet' ? 'main' : 'testnet']; + const cKeypair = {}; + cKeypair.priv = getSafeRand(); + + cKeypair.pub = deriveAddress({ pkBytes: cKeypair.priv }); + postMessage(cKeypair); + } +}; diff --git a/scripts/wallet.js b/scripts/wallet.js index dd4088d7e..8ae61b8ca 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -1,292 +1,1357 @@ -//ByteToHexString Convertions -function byteToHexString(uint8arr) { - if (!uint8arr) { - return ''; - } - var hexStr = ''; - for (var i = 0; i < uint8arr.length; i++) { - var hex = (uint8arr[i] & 0xff).toString(16); - hex = (hex.length === 1) ? '0' + hex : hex; - hexStr += hex; - } - return hexStr.toUpperCase(); -} -function hexStringToByte(str) { - if (!str) { - return new Uint8Array(); - } - var a = []; - for (var i = 0, len = str.length; i < len; i += 2) { - a.push(parseInt(str.substr(i, 2), 16)); - } - return new Uint8Array(a); -} +import { validateMnemonic } from 'bip39'; +import { decrypt } from './aes-gcm.js'; +import { parseWIF } from './encoding.js'; +import { beforeUnloadListener, blockCount } from './global.js'; +import { getNetwork } from './network.js'; +import { MAX_ACCOUNT_GAP, SHIELD_BATCH_SYNC_SIZE } from './chain_params.js'; +import { HistoricalTx, HistoricalTxType } from './historical_tx.js'; +import { COutpoint, Transaction } from './transaction.js'; +import { confirmPopup, createAlert, isShieldAddress } from './misc.js'; +import { cChainParams } from './chain_params.js'; +import { COIN } from './chain_params.js'; +import { ALERTS, tr, translation } from './i18n.js'; +import { encrypt } from './aes-gcm.js'; +import { Database } from './database.js'; +import { RECEIVE_TYPES } from './contacts-book.js'; +import { Account } from './accounts.js'; +import { fAdvancedMode } from './settings.js'; +import { bytesToHex, hexToBytes, sleep, startBatch } from './utils.js'; +import { strHardwareName } from './ledger.js'; +import { OutpointState, Mempool } from './mempool.js'; +import { getEventEmitter } from './event_bus.js'; +import { lockableFunction } from './lock.js'; -var MAP = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";//B58 Encoding Map -//B58 Encoding -var to_b58 = function ( - B, //Uint8Array raw byte input - A //Base58 characters (i.e. "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") -) { - var d = [], //the array for storing the stream of base58 digits - s = "", //the result string variable that will be returned - i, //the iterator variable for the byte input - j, //the iterator variable for the base58 digit array (d) - c, //the carry amount variable that is used to overflow from the current base58 digit to the next base58 digit - n; //a temporary placeholder variable for the current base58 digit - for (i in B) { //loop through each byte in the input stream - j = 0, //reset the base58 digit iterator - c = B[i]; //set the initial carry amount equal to the current byte amount - s += c || s.length ^ i ? "" : 1; //prepend the result string with a "1" (0 in base58) if the byte stream is zero and non-zero bytes haven't been seen yet (to ensure correct decode length) - while (j in d || c) { //start looping through the digits until there are no more digits and no carry amount - n = d[j]; //set the placeholder for the current base58 digit - n = n ? n * 256 + c : c; //shift the current base58 one byte and add the carry amount (or just add the carry amount if this is a new digit) - c = n / 58 | 0; //find the new carry amount (floored integer of current digit divided by 58) - d[j] = n % 58; //reset the current base58 digit to the remainder (the carry amount will pass on the overflow) - j++ //iterate to the next base58 digit - } - } - while (j--) //since the base58 digits are backwards, loop through them in reverse order - s += A[d[j]]; //lookup the character associated with each base58 digit - return s //return the final base58 string +import { + isP2CS, + isP2PKH, + getAddressFromHash, + COLD_START_INDEX, + P2PK_START_INDEX, + OWNER_START_INDEX, +} from './script.js'; +import { PIVXShield } from 'pivx-shield'; +import { guiToggleReceiveType } from './contacts-book.js'; +import { TransactionBuilder } from './transaction_builder.js'; + +/** + * Class Wallet, at the moment it is just a "realization" of Masterkey with a given nAccount + * it also remembers which addresses we generated. + * in future PRs this class will manage balance, UTXOs, masternode etc... + */ +export class Wallet { + /** + * We are using two chains: The external chain, and the internal one (i.e. change addresses) + * See https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki for more info + * (Change paragraph) + */ + static chains = 2; + /** + * @type {import('./masterkey.js').MasterKey?} + */ + #masterKey; + /** + * @type {import('pivx-shield').PIVXShield?} + */ + #shield = null; + /** + * @type {number} + */ + #nAccount; + + /** + * Map bip48 change -> Loaded index + * Number of loaded indexes, loaded means that they are in the ownAddresses map + * @type {Map} + */ + #loadedIndexes = new Map(); + /** + * Map bip48 change -> Highest used index + * Highest index used, where used means that the corresponding address is on chain (for example in a tx) + * @type {Map} + */ + #highestUsedIndices = new Map(); + /** + * @type {Map} + */ + #addressIndices = new Map(); + /** + * Map our own address -> Path + * @type {Map} + */ + #ownAddresses = new Map(); + /** + * Map public key hash -> Address + * @type {Map} + */ + #knownPKH = new Map(); + + /** + * @type {Mempool} + */ + #mempool; + + #isSynced = false; + /** + * The height of the last processed block in the wallet + * @type {number} + */ + #lastProcessedBlock = 0; + constructor({ nAccount, masterKey, shield, mempool = new Mempool() }) { + this.#nAccount = nAccount; + this.#mempool = mempool; + this.#masterKey = masterKey; + this.#shield = shield; + for (let i = 0; i < Wallet.chains; i++) { + this.#highestUsedIndices.set(i, 0); + this.#loadedIndexes.set(i, 0); + } + this.subscribeToNetworkEvents(); + } + + /** + * Check whether a given outpoint is locked + * @param {import('./transaction.js').COutpoint} opt + * @return {boolean} true if opt is locked, false otherwise + */ + isCoinLocked(opt) { + return !!(this.#mempool.getOutpointStatus(opt) & OutpointState.LOCKED); + } + + /** + * Lock a given Outpoint + * @param {import('./transaction.js').COutpoint} opt + */ + lockCoin(opt) { + this.#mempool.addOutpointStatus(opt, OutpointState.LOCKED); + } + + /** + * Unlock a given Outpoint + * @param {import('./transaction.js').COutpoint} opt + */ + unlockCoin(opt) { + this.#mempool.removeOutpointStatus(opt, OutpointState.LOCKED); + } + + /** + * Get master key + * @deprecated use the wallet functions instead + */ + getMasterKey() { + return this.#masterKey; + } + + /** + * Gets the Cold Staking Address for the current wallet, while considering user settings and network automatically. + * @return {Promise} Cold Address + */ + async getColdStakingAddress() { + // Check if we have an Account with custom Cold Staking settings + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + + // If there's an account with a Cold Address, return it, otherwise return the default + return ( + cAccount?.coldAddress || + cChainParams.current.defaultColdStakingAddress + ); + } + + get nAccount() { + return this.#nAccount; + } + + get isSynced() { + return this.#isSynced; + } + + get isSyncing() { + return this.sync.isLocked(); + } + + wipePrivateData() { + this.#masterKey.wipePrivateData(this.#nAccount); + if (this.#shield) { + this.#shield.extsk = null; + } + } + + isViewOnly() { + if (!this.#masterKey) return false; + return this.#masterKey.isViewOnly; + } + + isHD() { + if (!this.#masterKey) return false; + return this.#masterKey.isHD; + } + + async hasWalletUnlocked(fIncludeNetwork = false) { + if (fIncludeNetwork && !getNetwork().enabled) + return createAlert( + 'warning', + ALERTS.WALLET_OFFLINE_AUTOMATIC, + 5500 + ); + if (!this.isLoaded()) { + return createAlert( + 'warning', + tr(ALERTS.WALLET_UNLOCK_IMPORT, [ + { + unlock: (await hasEncryptedWallet()) + ? 'unlock ' + : 'import/create', + }, + ]), + 3500 + ); + } else { + return true; + } + } + + /** + * Set or replace the active Master Key with a new Master Key + * @param {object} o - Object to be destructured + * @param {import('./masterkey.js').MasterKey} o.mk - The new Master Key + * @param {number} [o.nAccount] - The account number + * @param {string} [o.extsk] - The extended spending key + */ + async setMasterKey({ mk, nAccount = 0, extsk }) { + const isNewAcc = + mk?.getKeyToExport(nAccount) !== + this.#masterKey?.getKeyToExport(this.#nAccount); + this.#masterKey = mk; + this.#nAccount = nAccount; + if (extsk) await this.setExtsk(extsk); + if (isNewAcc) { + this.reset(); + for (let i = 0; i < Wallet.chains; i++) this.loadAddresses(i); + } + } + + /** + * Set the extended spending key of a shield object + * @param {String} extsk encoded extended spending key + */ + async setExtsk(extsk) { + await this.#shield.loadExtendedSpendingKey(extsk); + } + + /** + * This should really be provided with the constructor, + * This will be done once `Dashboard.vue` is the owner of the wallet + * @param {import('pivx-shield').PIVXShield} shield object to set + */ + setShield(shield) { + this.#shield = shield; + } + + hasShield() { + return !!this.#shield; + } + + /** + * Reset the wallet, indexes address map and so on + */ + reset() { + this.#highestUsedIndices = new Map(); + this.#loadedIndexes = new Map(); + this.#ownAddresses = new Map(); + this.#isSynced = false; + this.#shield = null; + this.#addressIndices = new Map(); + for (let i = 0; i < Wallet.chains; i++) { + this.#highestUsedIndices.set(i, 0); + this.#loadedIndexes.set(i, 0); + this.#addressIndices.set(i, 0); + } + this.#mempool = new Mempool(); + this.#lastProcessedBlock = 0; + } + + /** + * Derive the current address (by internal index) + * @return {string} Address + * + */ + getCurrentAddress() { + return this.getAddress(0, this.#addressIndices.get(0)); + } + + /** + * Derive a generic address (given nReceiving and nIndex) + * @return {string} Address + */ + getAddress(nReceiving = 0, nIndex = 0) { + const path = this.getDerivationPath(nReceiving, nIndex); + return this.#masterKey.getAddress(path); + } + + /** + * Derive a generic address (given the full path) + * @return {string} Address + */ + getAddressFromPath(path) { + return this.#masterKey.getAddress(path); + } + + /** + * Derive xpub (given nReceiving and nIndex) + * @return {string} Address + */ + getXPub(nReceiving = 0, nIndex = 0) { + // Get our current wallet XPub + const derivationPath = this.getDerivationPath(nReceiving, nIndex) + .split('/') + .slice(0, 4) + .join('/'); + return this.#masterKey.getxpub(derivationPath); + } + + /** + * Derive xpub (given nReceiving and nIndex) + * @return {boolean} Return true if a masterKey has been loaded in the wallet + */ + isLoaded() { + return !!this.#masterKey; + } + + /** + * Check if the current encrypted keyToBackup can be decrypted with the given password + * @param {string} strPassword + * @return {Promise} + */ + async checkDecryptPassword(strPassword) { + // Check if there's any encrypted WIF available + const database = await Database.getInstance(); + const { encWif: strEncWIF } = await database.getAccount(); + if (!strEncWIF || strEncWIF.length < 1) return false; + + const strDecWIF = await decrypt(strEncWIF, strPassword); + return !!strDecWIF; + } + + /** + * Encrypt the keyToBackup with a given password + * @param {string} strPassword + * @returns {Promise} + */ + async encrypt(strPassword) { + // Encrypt the wallet WIF with AES-GCM and a user-chosen password - suitable for browser storage + let strEncWIF = await encrypt(this.#getKeyToEncrypt(), strPassword); + let strEncExtsk = ''; + let shieldData = ''; + if (this.#shield) { + strEncExtsk = await encrypt(this.#shield.extsk, strPassword); + shieldData = this.#shield.save(); + } + if (!strEncWIF) return false; + + // Prepare to Add/Update an account in the DB + const cAccount = new Account({ + publicKey: this.getKeyToExport(), + encWif: strEncWIF, + encExtsk: strEncExtsk, + shieldData: shieldData, + }); + + // Incase of a "Change Password", we check if an Account already exists + const database = await Database.getInstance(); + if (await database.getAccount()) { + // Update the existing Account (new encWif) in the DB + await database.updateAccount(cAccount); + } else { + // Add the new Account to the DB + await database.addAccount(cAccount); + } + + // Remove the exit blocker, we can annoy the user less knowing the key is safe in their database! + removeEventListener('beforeunload', beforeUnloadListener, { + capture: true, + }); + return true; + } + + /** + * @return {[string, string]} Address and its BIP32 derivation path + */ + getNewAddress(nReceiving = 0) { + const last = this.#highestUsedIndices.get(nReceiving); + this.#addressIndices.set( + nReceiving, + (this.#addressIndices.get(nReceiving) > last + ? this.#addressIndices.get(nReceiving) + : last) + 1 + ); + if (this.#addressIndices.get(nReceiving) - last > MAX_ACCOUNT_GAP) { + // If the user creates more than ${MAX_ACCOUNT_GAP} empty wallets we will not be able to sync them! + this.#addressIndices.set(nReceiving, last); + } + const path = this.getDerivationPath( + nReceiving, + this.#addressIndices.get(nReceiving) + ); + const address = this.getAddress( + nReceiving, + this.#addressIndices.get(nReceiving) + ); + return [address, path]; + } + + /** + * @returns {Promsie} new shield address + */ + async getNewShieldAddress() { + return await this.#shield.getNewAddress(); + } + + isHardwareWallet() { + return this.#masterKey?.isHardwareWallet === true; + } + + /** + * Check if the vout is owned and in case update highestUsedIdex + * @param {CTxOut} vout + */ + updateHighestUsedIndex(vout) { + const dataBytes = hexToBytes(vout.script); + const iStart = isP2PKH(dataBytes) ? P2PK_START_INDEX : COLD_START_INDEX; + const address = this.getAddressFromHashCache( + bytesToHex(dataBytes.slice(iStart, iStart + 20)), + false + ); + const path = this.isOwnAddress(address); + if (path) { + const nReceiving = parseInt(path.split('/')[4]); + this.#highestUsedIndices.set( + nReceiving, + Math.max( + parseInt(path.split('/')[5]), + this.#highestUsedIndices.get(nReceiving) + ) + ); + if ( + this.#highestUsedIndices.get(nReceiving) + MAX_ACCOUNT_GAP >= + this.#loadedIndexes.get(nReceiving) + ) { + this.loadAddresses(nReceiving); + } + } + } + + /** + * Load MAX_ACCOUNT_GAP inside #ownAddresses map. + * @param {number} chain - Chain to load + */ + loadAddresses(chain) { + if (this.isHD()) { + const start = this.#loadedIndexes.get(chain); + const end = start + MAX_ACCOUNT_GAP; + for (let i = start; i <= end; i++) { + const path = this.getDerivationPath(chain, i); + const address = this.#masterKey.getAddress(path); + this.#ownAddresses.set(address, path); + } + + this.#loadedIndexes.set(chain, end); + } else { + this.#ownAddresses.set(this.getKeyToExport(), ':)'); + } + } + + /** + * @param {string} address - address to check + * @return {string?} BIP32 path or null if it's not your address + */ + isOwnAddress(address) { + const path = this.#ownAddresses.get(address) ?? null; + return path; + } + + /** + * @return {String} BIP32 path or null if it's not your address + */ + getDerivationPath(nReceiving = 0, nIndex = 0) { + return this.#masterKey.getDerivationPath( + this.#nAccount, + nReceiving, + nIndex + ); + } + + getKeyToExport() { + return this.#masterKey?.getKeyToExport(this.#nAccount); + } + + /** + * @returns key to backup. May be encrypted + */ + async getKeyToBackup() { + if (await hasEncryptedWallet()) { + const account = await (await Database.getInstance()).getAccount(); + return account.encWif; + } + return this.#getKeyToEncrypt(); + } + + /** + * @returns key to encrypt + */ + #getKeyToEncrypt() { + return JSON.stringify({ + mk: this.getMasterKey()?.keyToBackup, + shield: this.#shield?.extsk, + }); + } + + //Get path from a script + getPath(script) { + const dataBytes = hexToBytes(script); + // At the moment we support only P2PKH and P2CS + const iStart = isP2PKH(dataBytes) ? P2PK_START_INDEX : COLD_START_INDEX; + const address = this.getAddressFromHashCache( + bytesToHex(dataBytes.slice(iStart, iStart + 20)), + false + ); + return this.isOwnAddress(address); + } + + /** + * Get the outpoint state based on the script. + * This functions only tells us the type of the script and if it's ours + * It doesn't know about LOCK, IMMATURE or SPENT statuses, for that + * it's necessary to interrogate the mempool + */ + getScriptType(script) { + const { type, addresses } = this.getAddressesFromScript(script); + let status = 0; + const isOurs = addresses.some((s) => this.isOwnAddress(s)); + if (isOurs) status |= OutpointState.OURS; + if (type === 'p2pkh') status |= OutpointState.P2PKH; + if (type === 'p2cs') { + status |= OutpointState.P2CS; + } + return status; + } + + /** + * Get addresses from a script + * @returns {{ type: 'p2pkh'|'p2cs'|'unknown', addresses: string[] }} + */ + getAddressesFromScript(script) { + const dataBytes = hexToBytes(script); + if (isP2PKH(dataBytes)) { + const address = this.getAddressFromHashCache( + bytesToHex( + dataBytes.slice(P2PK_START_INDEX, P2PK_START_INDEX + 20) + ), + false + ); + return { + type: 'p2pkh', + addresses: [address], + }; + } else if (isP2CS(dataBytes)) { + const addresses = []; + for (let i = 0; i < 2; i++) { + const iStart = i == 0 ? OWNER_START_INDEX : COLD_START_INDEX; + addresses.push( + this.getAddressFromHashCache( + bytesToHex(dataBytes.slice(iStart, iStart + 20)), + iStart === OWNER_START_INDEX + ) + ); + } + return { type: 'p2cs', addresses }; + } else { + return { type: 'unknown', addresses: [] }; + } + } + + // Avoid calculating over and over the same getAddressFromHash by saving the result in a map + getAddressFromHashCache(pkh_hex, isColdStake) { + if (!this.#knownPKH.has(pkh_hex)) { + this.#knownPKH.set( + pkh_hex, + getAddressFromHash(hexToBytes(pkh_hex), isColdStake) + ); + } + return this.#knownPKH.get(pkh_hex); + } + + /** + * Return true if the transaction contains undelegations regarding the given wallet + * @param {import('./transaction.js').Transaction} tx + */ + checkForUndelegations(tx) { + for (const vin of tx.vin) { + const status = this.#mempool.getOutpointStatus(vin.outpoint); + if (status & OutpointState.P2CS) { + return true; + } + } + return false; + } + + /** + * Return true if the transaction contains delegations regarding the given wallet + * @param {import('./transaction.js').Transaction} tx + */ + checkForDelegations(tx) { + const txid = tx.txid; + for (let i = 0; i < tx.vout.length; i++) { + const outpoint = new COutpoint({ + txid, + n: i, + }); + if ( + this.#mempool.getOutpointStatus(outpoint) & OutpointState.P2CS + ) { + return true; + } + } + return false; + } + + /** + * Return the output addresses for a given transaction + * @param {import('./transaction.js').Transaction} tx + */ + getOutAddress(tx) { + return tx.vout.reduce( + (acc, vout) => [ + ...acc, + ...this.getAddressesFromScript(vout.script).addresses, + ], + [] + ); + } + + /** + * Convert a list of Blockbook transactions to HistoricalTxs + * @param {import('./transaction.js').Transaction[]} arrTXs - An array of the Blockbook TXs + * @returns {Array} - A new array of `HistoricalTx`-formatted transactions + */ + // TODO: add shield data to txs + toHistoricalTXs(arrTXs) { + let histTXs = []; + for (const tx of arrTXs) { + // The total 'delta' or change in balance, from the Tx's sums + let nAmount = + (this.#mempool.getCredit(tx) - this.#mempool.getDebit(tx)) / + COIN; + + // The receiver addresses, if any + let arrReceivers = this.getOutAddress(tx); + + const getFilteredCredit = (filter) => { + return tx.vout + .filter((_, i) => { + const status = this.#mempool.getOutpointStatus( + new COutpoint({ + txid: tx.txid, + n: i, + }) + ); + return status & filter && status & OutpointState.OURS; + }) + .reduce((acc, o) => acc + o.value, 0); + }; + + // Figure out the type, based on the Tx's properties + let type = HistoricalTxType.UNKNOWN; + if (tx.isCoinStake()) { + type = HistoricalTxType.STAKE; + } else if (this.checkForUndelegations(tx)) { + type = HistoricalTxType.UNDELEGATION; + nAmount = getFilteredCredit(OutpointState.P2PKH) / COIN; + } else if (this.checkForDelegations(tx)) { + type = HistoricalTxType.DELEGATION; + arrReceivers = arrReceivers.filter((addr) => { + return addr[0] === cChainParams.current.STAKING_PREFIX; + }); + nAmount = getFilteredCredit(OutpointState.P2CS) / COIN; + } else if (nAmount > 0) { + type = HistoricalTxType.RECEIVED; + } else if (nAmount < 0) { + type = HistoricalTxType.SENT; + } + + histTXs.push( + new HistoricalTx( + type, + tx.txid, + arrReceivers, + false, + tx.blockTime, + tx.blockHeight, + Math.abs(nAmount) + ) + ); + } + return histTXs; + } + sync = lockableFunction(async () => { + if (this.#isSynced) { + throw new Error('Attempting to sync when already synced'); + } + await this.loadFromDisk(); + await this.loadShieldFromDisk(); + // Let's set the last processed block 5 blocks behind the actual chain tip + // This is just to be sure since blockbook (as we know) + // usually does not return txs of the actual last block. + this.#lastProcessedBlock = blockCount - 5; + await this.#transparentSync(); + if (this.hasShield()) { + await this.#syncShield(); + } + this.#isSynced = true; + // Update both activities post sync + getEventEmitter().emit('new-tx'); + }); + + async #transparentSync() { + if (!this.isLoaded() || this.#isSynced) return; + const cNet = getNetwork(); + await cNet.getLatestTxs(this); + getEventEmitter().emit('transparent-sync-status-update', '', '', true); + } + + /** + * Initial block and prover sync for the shield object + */ + async #syncShield() { + if (!this.#shield || this.#isSynced) { + return; + } + const cNet = getNetwork(); + try { + const blockHeights = (await cNet.getShieldBlockList()).filter( + (b) => b > this.#shield.getLastSyncedBlock() + ); + const batchSize = SHIELD_BATCH_SYNC_SIZE; + let handled = 0; + const blocks = []; + let syncing = false; + await startBatch( + async (i) => { + let block; + block = await cNet.getBlock(blockHeights[i], true); + blocks[i] = block; + // We need to process blocks monotically + // When we get a block, start from the first unhandled + // One and handle as many as possible + for (let j = handled; blocks[j]; j = handled) { + if (syncing) break; + syncing = true; + handled++; + await this.#shield.handleBlock(blocks[j]); + // Delete so we don't have to hold all blocks in memory + // until we finish syncing + delete blocks[j]; + syncing = false; + } + + getEventEmitter().emit( + 'shield-sync-status-update', + handled - 1, + blockHeights.length, + false + ); + }, + blockHeights.length, + batchSize + ); + getEventEmitter().emit('shield-sync-status-update', 0, 0, true); + } catch (e) { + console.error(e); + } + + // At this point it should be safe to assume that shield is ready to use + await this.saveShieldOnDisk(); + const networkSaplingRoot = ( + await getNetwork().getBlock(this.#shield.getLastSyncedBlock()) + ).finalsaplingroot; + if (networkSaplingRoot) + await this.#checkShieldSaplingRoot(networkSaplingRoot); + this.#isSynced = true; + } + + /** + * @todo this needs to take the `vin` as input, + * But currently we don't have any way of getting the UTXO + * out of the vin. This will happen after the mempool refactor, + * But for now we can just recalculate the UTXOs + * @param {number} target - Number of satoshis needed. See Mempool.getUTXOs + */ + #getUTXOsForShield(target = Number.POSITIVE_INFINITY) { + return this.#mempool + .getUTXOs({ + requirement: OutpointState.P2PKH | OutpointState.OURS, + target, + blockCount, + }) + .map((u) => { + return { + vout: u.outpoint.n, + amount: u.value, + private_key: parseWIF( + this.#masterKey.getPrivateKey(this.getPath(u.script)) + ), + script: hexToBytes(u.script), + txid: u.outpoint.txid, + }; + }); + } + + subscribeToNetworkEvents() { + getEventEmitter().on('new-block', async (block) => { + if (this.#isSynced) { + // Invalidate the balance cache to keep immature balance updated + this.#mempool.invalidateBalanceCache(); + await this.getLatestBlocks(block); + getEventEmitter().emit('new-tx'); + } + }); + } + getLatestBlocks = lockableFunction( + /** + * Update the shield object with the latest blocks + * @param{number} blockCount - block count + */ + async (blockCount) => { + const cNet = getNetwork(); + let block; + // Don't ask for the exact last block that arrived, + // since it takes around 1 minute for blockbook to make it API available + for ( + let blockHeight = this.#lastProcessedBlock + 1; + blockHeight < blockCount; + blockHeight++ + ) { + try { + block = await cNet.getBlock(blockHeight); + if (block.txs) { + if ( + this.hasShield() && + blockHeight > this.#shield.getLastSyncedBlock() + ) { + await this.#shield.handleBlock(block); + } + for (const tx of block.txs) { + const parsed = Transaction.fromHex(tx.hex); + parsed.blockHeight = blockHeight; + parsed.blockTime = tx.blocktime; + // Avoid wasting memory on txs that do not regard our wallet + if (this.ownTransaction(parsed)) { + await this.addTransaction(parsed); + } + } + } else { + break; + } + this.#lastProcessedBlock = blockHeight; + } catch (e) { + console.error(e); + break; + } + } + + // SHIELD-only checks + if (this.hasShield()) { + if (block?.finalSaplingRoot) { + if ( + !(await this.#checkShieldSaplingRoot( + block.finalsaplingroot + )) + ) + return; + } + await this.saveShieldOnDisk(); + } + } + ); + + async #checkShieldSaplingRoot(networkSaplingRoot) { + const saplingRoot = bytesToHex( + hexToBytes(await this.#shield.getSaplingRoot()).reverse() + ); + // If explorer sapling root is different from ours, there must be a sync error + if (saplingRoot !== networkSaplingRoot) { + createAlert('warning', translation.badSaplingRoot, 5000); + this.#mempool = new Mempool(); + // TODO: take the wallet creation height in input from users + await this.#shield.reloadFromCheckpoint(4200000); + await this.#transparentSync(); + await this.#syncShield(); + return false; + } + return true; + } + + /** + * Save shield data on database + */ + async saveShieldOnDisk() { + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + // If the account has not been created yet (for example no encryption) return + if (!cAccount) { + return; + } + cAccount.shieldData = this.#shield.save(); + await cDB.updateAccount(cAccount); + } + /** + * Load shield data from database + */ + async loadShieldFromDisk() { + if (this.#shield) { + return; + } + const cDB = await Database.getInstance(); + const cAccount = await cDB.getAccount(); + // If the account has not been created yet or there is no shield data return + if (!cAccount || cAccount.shieldData == '') { + return; + } + this.#shield = await PIVXShield.load(cAccount.shieldData); + getEventEmitter().emit('shield-loaded-from-disk'); + return; + } + + /** + * @returns {Promise} Number of shield satoshis of the account + */ + async getShieldBalance() { + return this.#shield?.getBalance() || 0; + } + + /** + * @returns {Promise} Number of pending shield satoshis of the account + */ + async getPendingShieldBalance() { + return this.#shield?.getPendingBalance() || 0; + } + + /** + * Create a non signed transaction + * @param {string} address - Address to send to + * @param {number} value - Amount of satoshis to send + * @param {object} [opts] - Options + * @param {boolean} [opts.isDelegation] - Whether or not this delegates PIVs to `address`. + * If set to true, `address` must be a valid cold staking address + * @param {boolean} [opts.useDelegatedInputs] - Whether or not cold stake inputs are to be used. + * Should be set if this is an undelegation transaction. + * @param {string?} [opts.changeDelegationAddress] - Which address to use as change when `useDelegatedInputs` is set to true. + * Only changes >= 1 PIV can be delegated + * @param {boolean} [opts.isProposal] - Whether or not this is a proposal transaction + */ + createTransaction( + address, + value, + { + isDelegation = false, + useDelegatedInputs = false, + useShieldInputs = false, + delegateChange = false, + changeDelegationAddress = null, + isProposal = false, + subtractFeeFromAmt = true, + changeAddress = '', + returnAddress = '', + } = {} + ) { + let balance; + if (useDelegatedInputs) { + balance = this.coldBalance; + } else if (useShieldInputs) { + balance = this.#shield.getBalance(); + } else { + balance = this.balance; + } + if (balance < value) { + throw new Error('Not enough balance'); + } + if (delegateChange && !changeDelegationAddress) + throw new Error( + '`delegateChange` was set to true, but no `changeDelegationAddress` was provided.' + ); + const transactionBuilder = TransactionBuilder.create(); + const isShieldTx = useShieldInputs || isShieldAddress(address); + + // Add primary output + if (isDelegation) { + if (!returnAddress) [returnAddress] = this.getNewAddress(1); + transactionBuilder.addColdStakeOutput({ + address: returnAddress, + addressColdStake: address, + value, + }); + } else if (isProposal) { + transactionBuilder.addProposalOutput({ + hash: address, + value, + }); + } else { + transactionBuilder.addOutput({ + address, + value, + }); + } + + if (!useShieldInputs) { + const requirement = useDelegatedInputs + ? OutpointState.P2CS + : OutpointState.P2PKH; + const utxos = this.#mempool.getUTXOs({ + requirement: requirement | OutpointState.OURS, + target: value, + blockCount, + }); + transactionBuilder.addUTXOs(utxos); + + // Shield txs will handle change internally + if (isShieldTx) { + return transactionBuilder.build(); + } + + const fee = transactionBuilder.getFee(); + const changeValue = transactionBuilder.valueIn - value - fee; + if (changeValue < 0) { + if (!subtractFeeFromAmt) { + throw new Error('Not enough balance'); + } + transactionBuilder.equallySubtractAmt(Math.abs(changeValue)); + } else if (changeValue > 0) { + // TransactionBuilder will internally add the change only if it is not dust + if (!changeAddress) [changeAddress] = this.getNewAddress(1); + if (delegateChange && changeValue >= 1 * COIN) { + transactionBuilder.addColdStakeOutput({ + address: changeAddress, + value: changeValue, + addressColdStake: changeDelegationAddress, + isChange: true, + }); + } else { + transactionBuilder.addOutput({ + address: changeAddress, + value: changeValue, + isChange: true, + }); + } + } + } + return transactionBuilder.build(); + } + + /** + * Sign a shield transaction + * @param {import('./transaction.js').Transaction} transaction + */ + async #signShield(transaction) { + if (!transaction.hasSaplingVersion) { + throw new Error( + '`signShield` was called with a tx that cannot have shield data' + ); + } + if (!this.hasShield()) { + throw new Error( + 'trying to create a shield transaction without having shield enable' + ); + } + + const periodicFunction = setInterval(async () => { + const percentage = (await this.#shield.getTxStatus()) * 100; + getEventEmitter().emit( + 'shield-transaction-creation-update', + percentage, + false + ); + }, 500); + + const value = + transaction.shieldOutput[0]?.value || transaction.vout[0].value; + try { + const { hex } = await this.#shield.createTransaction({ + address: + transaction.shieldOutput[0]?.address || + this.getAddressesFromScript(transaction.vout[0].script) + .addresses[0], + amount: value, + blockHeight: blockCount + 1, + useShieldInputs: transaction.vin.length === 0, + utxos: this.#getUTXOsForShield(value), + transparentChangeAddress: this.getNewAddress(1)[0], + }); + return transaction.fromHex(hex); + } catch (e) { + // sleep a full period of periodicFunction + await sleep(500); + throw e; + } finally { + clearInterval(periodicFunction); + getEventEmitter().emit( + 'shield-transaction-creation-update', + 0.0, + true + ); + } + } + + /** + * @param {import('./transaction.js').Transaction} transaction - transaction to sign + * @throws {Error} if the wallet is view only + * @returns {Promise} a reference to the same transaction, signed + */ + async sign(transaction) { + if (this.isViewOnly()) { + throw new Error('Cannot sign with a view only wallet'); + } + if (!transaction.vin.length || transaction.shieldOutput[0]) { + // TODO: separate signing and building process for shield? + return await this.#signShield(transaction); + } + for (let i = 0; i < transaction.vin.length; i++) { + const input = transaction.vin[i]; + const { type } = this.getAddressesFromScript(input.scriptSig); + const path = this.getPath(input.scriptSig); + const wif = this.getMasterKey().getPrivateKey(path); + await transaction.signInput(i, wif, { + isColdStake: type === 'p2cs', + }); + } + return transaction; + } + + /** + * Adds a transaction to the mempool. To be called after it's signed and sent to the network, if successful + * @param {import('./transaction.js').Transaction} transaction + */ + async addTransaction(transaction, skipDatabase = false) { + this.#mempool.addTransaction(transaction); + let i = 0; + for (const out of transaction.vout) { + this.updateHighestUsedIndex(out); + const status = this.getScriptType(out.script); + if (status & OutpointState.OURS) { + this.#mempool.addOutpointStatus( + new COutpoint({ + txid: transaction.txid, + n: i, + }), + status + ); + } + i++; + } + + if (transaction.hasShieldData) { + await wallet.#shield?.finalizeTransaction(transaction.txid); + } + + if (!skipDatabase) { + const db = await Database.getInstance(); + await db.storeTx(transaction); + } + } + + /** + * Check if any vin or vout of the transaction belong to the wallet + * @param {import('./transaction.js').Transaction} transaction + */ + ownTransaction(transaction) { + const ownVout = + transaction.vout.filter((out) => { + return this.getScriptType(out.script) & OutpointState.OURS; + }).length > 0; + const ownVin = + transaction.vin.filter((input) => { + return ( + this.#mempool.getOutpointStatus(input.outpoint) & + OutpointState.OURS + ); + }).length > 0; + return ownVout || ownVin; + } + + /** + * Discard a transaction. Must be called only if network doesn't accept it. + * @param {import('./transaction.js').Transaction} transaction + */ + discardTransaction(transaction) { + wallet.#shield?.discardTransaction(transaction.txid); + } + + /** + * @returns {UTXO[]} Any UTXO that has value of + * exactly `cChainParams.current.collateralInSats` + */ + getMasternodeUTXOs() { + const collateralValue = cChainParams.current.collateralInSats; + return this.#mempool + .getUTXOs({ + requirement: OutpointState.P2PKH | OutpointState.OURS, + blockCount, + }) + .filter((u) => u.value === collateralValue); + } + + /** + * @returns {import('./transaction.js').Transaction[]} a list of all transactions + */ + getTransactions() { + return this.#mempool.getTransactions(); + } + + get balance() { + return this.#mempool.getBalance(blockCount); + } + + get immatureBalance() { + return this.#mempool.getImmatureBalance(blockCount); + } + + get coldBalance() { + return this.#mempool.getColdBalance(blockCount); + } + + /** + * Utility function to get the UTXO from an outpoint + * @param {COutpoint} outpoint + * @returns {UTXO?} + */ + outpointToUTXO(outpoint) { + return this.#mempool.outpointToUTXO(outpoint); + } + + async loadFromDisk() { + const db = await Database.getInstance(); + if ((await db.getAccount())?.publicKey !== this.getKeyToExport()) { + await db.removeAllTxs(); + return; + } + const txs = await db.getTxs(); + for (const tx of txs) { + this.addTransaction(tx, true); + } + } } -//B58 Decoding -var from_b58 = function ( - S, //Base58 encoded string input - A //Base58 characters (i.e. "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + +/** + * @type{Wallet} + */ +export const wallet = new Wallet({ nAccount: 0 }); // For now we are using only the 0-th account, (TODO: update once account system is done) + +/** + * Clean a Seed Phrase string and verify it's integrity + * + * This returns an object of the validation status and the cleaned Seed Phrase for safe low-level usage. + * @param {String} strPhraseInput - The Seed Phrase string + * @param {Boolean} fPopupConfirm - Allow a warning bypass popup if the Seed Phrase is unusual + */ +export async function cleanAndVerifySeedPhrase( + strPhraseInput = '', + fPopupConfirm = true ) { - var d = [], //the array for storing the stream of decoded bytes - b = [], //the result byte array that will be returned - i, //the iterator variable for the base58 string - j, //the iterator variable for the byte array (d) - c, //the carry amount variable that is used to overflow from the current byte to the next byte - n; //a temporary placeholder variable for the current byte - for (i in S) { //loop through each base58 character in the input string - j = 0, //reset the byte iterator - c = A.indexOf(S[i]); //set the initial carry amount equal to the current base58 digit - if (c < 0) //see if the base58 digit lookup is invalid (-1) - return undefined; //if invalid base58 digit, bail out and return undefined - c || b.length ^ i ? i : b.push(0); //prepend the result array with a zero if the base58 digit is zero and non-zero characters haven't been seen yet (to ensure correct decode length) - while (j in d || c) { //start looping through the bytes until there are no more bytes and no carry amount - n = d[j]; //set the placeholder for the current byte - n = n ? n * 58 + c : c; //shift the current byte 58 units and add the carry amount (or just add the carry amount if this is a new byte) - c = n >> 8; //find the new carry amount (1-byte shift of current byte value) - d[j] = n % 256; //reset the current byte to the remainder (the carry amount will pass on the overflow) - j++ //iterate to the next byte - } - } - while (j--) //since the byte array is backwards, loop through it in reverse order - b.push(d[j]); //append each byte to the result - return new Uint8Array(b) //return the final byte array in Uint8Array format + // Clean the phrase (removing unnecessary spaces) and force to lowercase + const strPhrase = strPhraseInput.trim().replace(/\s+/g, ' ').toLowerCase(); + + // Count the Words + const nWordCount = strPhrase.trim().split(' ').length; + + // Ensure it's a word count that makes sense + if (nWordCount === 12 || nWordCount === 24) { + if (!validateMnemonic(strPhrase)) { + // If a popup is allowed and Advanced Mode is enabled, warn the user that the + // ... seed phrase is potentially bad, and ask for confirmation to proceed + if (!fPopupConfirm || !fAdvancedMode) + return { + ok: false, + msg: translation.importSeedErrorTypo, + phrase: strPhrase, + }; + + // The reason we want to ask the user for confirmation is that the mnemonic + // could have been generated with another app that has a different dictionary + const fSkipWarning = await confirmPopup({ + title: translation.popupSeedPhraseBad, + html: translation.popupSeedPhraseBadNote, + }); + + if (fSkipWarning) { + // User is probably an Arch Linux user and used `-f` + return { + ok: true, + msg: translation.importSeedErrorSkip, + phrase: strPhrase, + }; + } else { + // User heeded the warning and rejected the phrase + return { + ok: false, + msg: translation.importSeedError, + phrase: strPhrase, + }; + } + } else { + // Valid count and mnemonic + return { + ok: true, + msg: translation.importSeedValid, + phrase: strPhrase, + }; + } + } else { + // Invalid count + return { + ok: false, + msg: translation.importSeedErrorSize, + phrase: strPhrase, + }; + } } -var randArr = new Uint8Array(32) //create a typed array of 32 bytes (256 bits) -if (debug) { - document.getElementById('Debug').innerHTML = " DEBUG MODE "; + +/** + * @returns {Promise} If the wallet has an encrypted database backup + */ +export async function hasEncryptedWallet() { + const database = await Database.getInstance(); + const account = await database.getAccount(); + return !!account?.encWif; } -document.getElementById('dcfooter').innerHTML = '© 2021 StakeCube - All rights reserved.
SCC Web 3.0 - v' + wallet_version + ''; -//Wallet Import -importWallet = function () { - if (walletAlreadyMade != 0) { - var walletConfirm = window.confirm("Do you really want to import a new address? If you haven't saved the last private key, the key will get LOST forever alongside ANY funds with it."); - } else { - walletConfirm = true; - } - if (walletConfirm) { - walletAlreadyMade++; - //Wallet Import Format to Private Key - var privateKeyWIF = document.getElementById("privateKey").value; - privateKeyForTransactions = privateKeyWIF; - var byteArryConvert = from_b58(privateKeyWIF, MAP) - var droplfour = byteArryConvert.slice(0, byteArryConvert.length - 4); - var key = droplfour.slice(1, droplfour.length); - var privateKeyBytes = key.slice(0, key.length - 1); - if (debug) { - //WIF to Private Key - console.log(byteToHexString(privateKeyWIF)); - console.log(byteToHexString(byteArryConvert)); - console.log(byteToHexString(droplfour)); - console.log(byteToHexString(privateKeyBytes)); - } - //Public Key Generation - var privateKeyBigInt = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(byteToHexString(privateKeyBytes).toUpperCase())); - var curve = EllipticCurve.getSECCurveByName("secp256k1"); - var curvePt = curve.getG().multiply(privateKeyBigInt); - var x = curvePt.getX().toBigInteger(); - var y = curvePt.getY().toBigInteger(); - var publicKeyBytes = EllipticCurve.integerToBytes(x, 32); - publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)); - publicKeyBytes.unshift(0x04); - if (bitjs.compressed == true) { - var publicKeyBytesCompressed = EllipticCurve.integerToBytes(x, 32) - if (y.isEven()) { - publicKeyBytesCompressed.unshift(0x02) - } else { - publicKeyBytesCompressed.unshift(0x03) - } - var pubKeyExtended = publicKeyBytesCompressed; - } else { - var pubKeyExtended = publicKeyBytes; - } - var publicKeyHex = byteToHexString(pubKeyExtended).toUpperCase() - const pubKeyHashing = new jsSHA("SHA-256", "HEX", { "numRounds": 1 }); - pubKeyHashing.update(publicKeyHex); - const pubKeyHash = pubKeyHashing.getHash("HEX"); - var pubKeyHashRipemd160 = byteToHexString(ripemd160(hexStringToByte(pubKeyHash))).toUpperCase() - var pubKeyHashNetwork = "7D" + pubKeyHashRipemd160 - const pubKeyHashingS = new jsSHA("SHA-256", "HEX", { "numRounds": 2 }); - pubKeyHashingS.update(pubKeyHashNetwork); - const pubKeyHashingSF = pubKeyHashingS.getHash("HEX").toUpperCase(); - var checksumPubKey = String(pubKeyHashingSF).substr(0, 8).toUpperCase() - var pubKeyPreBase = pubKeyHashNetwork + checksumPubKey - var pubKey = to_b58(hexStringToByte(pubKeyPreBase), MAP) - publicKeyForNetwork = pubKey; - console.log(pubKey); - //Display Text - document.getElementById('Privatelabel').style.display = 'block'; - document.getElementById('Publiclabel').style.display = 'block'; - document.getElementById('PrivateTxt').innerHTML = privateKeyWIF; - document.getElementById('PublicTxt').innerHTML = pubKey; - //QR Codes - var typeNumber = 4; - var errorCorrectionLevel = 'L'; - var qr = qrcode(typeNumber, errorCorrectionLevel); - qr.addData(privateKeyWIF); - qr.make(); - document.getElementById('PrivateQR').innerHTML = qr.createImgTag(); - var typeNumber = 4; - var errorCorrectionLevel = 'L'; - var qr = qrcode(typeNumber, errorCorrectionLevel); - qr.addData(pubKey); - qr.make(); - document.getElementById('PublicQR').innerHTML = qr.createImgTag(); - } + +export async function getNewAddress({ + updateGUI = false, + verify = false, + shield = false, + nReceiving = 0, +} = {}) { + const [address, path] = wallet.getNewAddress(nReceiving); + if (verify && wallet.isHardwareWallet()) { + // Generate address to present to the user without asking to verify + const confAddress = await confirmPopup({ + title: ALERTS.CONFIRM_POPUP_VERIFY_ADDR, + html: createAddressConfirmation(address), + resolvePromise: wallet.getMasterKey().verifyAddress(path), + }); + if (address !== confAddress) { + throw new Error('User did not verify address'); + } + } + + // If we're generating a new address manually, then render the new address in our Receive Modal + if (updateGUI) { + guiToggleReceiveType( + shield ? RECEIVE_TYPES.SHIELD : RECEIVE_TYPES.ADDRESS + ); + } + + return [address, path]; } -//Wallet Generation -generateWallet = async function (strPrefix = false) { - if (walletAlreadyMade != 0 && strPrefix === false) { - var walletConfirm = window.confirm("Do you really want to generate a new address? If you haven't saved the last private key the key will get lost forever and any funds with it."); - } else { - walletConfirm = true; - } - if (walletConfirm) { - walletAlreadyMade++; - if (debug) { - var privateKeyBytes = hexStringToByte("FFE09E40CE1C5F7092801D2388347C552C408FC9056734E8273977E658BC201F"); - } else { - var randArr = new Uint8Array(32) - window.crypto.getRandomValues(randArr) //populate array with cryptographically secure random numbers - var privateKeyBytes = [] - for (var i = 0; i < randArr.length; ++i) - privateKeyBytes[i] = randArr[i] - } - //Private Key Generation - var privateKeyHex = byteToHexString(privateKeyBytes).toUpperCase() - var privateKeyAndVersion = "FD" + privateKeyHex + "01" - const shaObj = new jsSHA("SHA-256", "HEX", { "numRounds": 2 }); - shaObj.update(privateKeyAndVersion); - const hash = shaObj.getHash("HEX"); - var checksum = String(hash).substr(0, 8).toUpperCase() - var keyWithChecksum = privateKeyAndVersion + checksum - var privateKeyWIF = to_b58(hexStringToByte(keyWithChecksum), MAP) - privateKeyForTransactions = privateKeyWIF; - //Public Key Generation - var privateKeyBigInt = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(byteToHexString(privateKeyBytes).toUpperCase())); - var curve = EllipticCurve.getSECCurveByName("secp256k1"); - var curvePt = curve.getG().multiply(privateKeyBigInt); - var x = curvePt.getX().toBigInteger(); - var y = curvePt.getY().toBigInteger(); - var publicKeyBytes = EllipticCurve.integerToBytes(x, 32); - publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)); - publicKeyBytes.unshift(0x04); - if (bitjs.compressed == true) { - var publicKeyBytesCompressed = EllipticCurve.integerToBytes(x, 32) - if (y.isEven()) { - publicKeyBytesCompressed.unshift(0x02) - } else { - publicKeyBytesCompressed.unshift(0x03) - } - var pubKeyExtended = publicKeyBytesCompressed; - } else { - var pubKeyExtended = publicKeyBytes; - } - var publicKeyHex = byteToHexString(pubKeyExtended).toUpperCase() - const pubKeyHashing = new jsSHA("SHA-256", "HEX", { "numRounds": 1 }); - pubKeyHashing.update(publicKeyHex); - const pubKeyHash = pubKeyHashing.getHash("HEX"); - var pubKeyHashRipemd160 = byteToHexString(ripemd160(hexStringToByte(pubKeyHash))).toUpperCase() - var pubKeyHashNetwork = "7D" + pubKeyHashRipemd160 - const pubKeyHashingS = new jsSHA("SHA-256", "HEX", { "numRounds": 2 }); - pubKeyHashingS.update(pubKeyHashNetwork); - const pubKeyHashingSF = pubKeyHashingS.getHash("HEX").toUpperCase(); - var checksumPubKey = String(pubKeyHashingSF).substr(0, 8).toUpperCase() - var pubKeyPreBase = pubKeyHashNetwork + checksumPubKey - var pubKey = to_b58(hexStringToByte(pubKeyPreBase), MAP) - publicKeyForNetwork = pubKey; - //Debug Console - if (debug && strPrefix === false) { - console.log("Private Key") - console.log(privateKeyHex) - console.log("Private Key Plus Leading Digits") - console.log(privateKeyAndVersion) - console.log("Double SHA-256 Hash") - console.log(hash) - console.log('CheckSum') - console.log(checksum) - console.log('Key With CheckSum') - console.log(keyWithChecksum) - console.log('Private Key') - console.log(privateKeyWIF) - console.log('Public Key') - console.log(publicKeyHex) - console.log('Public Key Extened') - console.log(byteToHexString(pubKeyExtended)) - console.log('SHA256 Public Key') - console.log(pubKeyHash) - console.log('RIPEMD160 Public Key') - console.log(pubKeyHashRipemd160) - console.log('PubKeyHash w/NetworkBytes') - console.log(pubKeyHashNetwork) - console.log('2x SHA256 Public Key Secound Time') - console.log(pubKeyHashingSF) - console.log("CheckSum Public Key") - console.log(checksumPubKey) - console.log("Pub Key with Checksum") - console.log(pubKeyPreBase) - console.log('Public Key Base 64') - console.log(pubKey) - } - // VANITY ONLY: During a search, we don't update the DOM until a match is found, or the renderer consumes a shitload of resources. - let nRet = { - pubkey: null, - privkey: null, - vanity_match: false - } - if (strPrefix === false || (strPrefix !== false && pubKey.toLowerCase().startsWith(strPrefix))) { - //Display Text - document.getElementById('genKeyWarning').style.display = 'block'; - document.getElementById('Privatelabel').style.display = 'block'; - document.getElementById('Publiclabel').style.display = 'block'; - document.getElementById('PrivateTxt').innerHTML = privateKeyWIF; - document.getElementById('PublicTxt').innerHTML = pubKey; - //QR Codes - var typeNumber = 4; - var errorCorrectionLevel = 'L'; - var qr = qrcode(typeNumber, errorCorrectionLevel); - qr.addData('scc:' + privateKeyWIF); - qr.make(); - document.getElementById('PrivateQR').innerHTML = qr.createImgTag(); - var typeNumber = 4; - var errorCorrectionLevel = 'L'; - var qr = qrcode(typeNumber, errorCorrectionLevel); - qr.addData('scc:' + pubKey); - qr.make(); - document.getElementById('PublicQR').innerHTML = qr.createImgTag(); - // VANITY ONLY: If we reached here during a vanity search, we found our match! - nRet.pubkey = pubKey; - nRet.privkey = privateKeyWIF; - nRet.vanity_match = true; - } - return nRet; - } +function createAddressConfirmation(address) { + return `${translation.popupHardwareAddrCheck} ${strHardwareName}. +
${address}
`; } diff --git a/test_setup.js b/test_setup.js new file mode 100644 index 000000000..7e0f9a19d --- /dev/null +++ b/test_setup.js @@ -0,0 +1,7 @@ +// We need to attach the component to a HTML, +// or .isVisible() function does not work +document.body.innerHTML = ` +
+
+
+`; diff --git a/tests/components/AccessWallet.test.js b/tests/components/AccessWallet.test.js new file mode 100644 index 000000000..e2f89d3fd --- /dev/null +++ b/tests/components/AccessWallet.test.js @@ -0,0 +1,164 @@ +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { expect } from 'vitest'; +import AccessWallet from '../../scripts/dashboard/AccessWallet.vue'; +import { vi, it, describe } from 'vitest'; + +describe('access wallet tests', () => { + afterEach(() => vi.clearAllMocks()); + it('Access wallet (no advanced)', async () => { + const wrapper = mount(AccessWallet, { + props: { + advancedMode: false, + }, + attachTo: document.getElementById('app'), + }); + + const accWalletButton = wrapper.find('[data-testid=accWalletButton]'); + const passwordInp = wrapper.find('[data-testid=passwordInp]'); + const secretInp = wrapper.find('[data-testid=secretInp]'); + const importWalletButton = wrapper.find( + '[data-testid=importWalletButton]' + ); + + // before clicking the button, + // all input texts + import wallet button are hidden + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + expect(accWalletButton.isVisible()).toBeTruthy(); + expect(passwordInp.isVisible()).toBeFalsy(); + expect(secretInp.isVisible()).toBeFalsy(); + expect(importWalletButton.isVisible()).toBeFalsy(); + + //click the access Wallet button + await accWalletButton.trigger('click'); + expect(accWalletButton.isVisible()).toBeTruthy(); + // button clicked, so now everything should be visible apart the passwordInp + expect(passwordInp.isVisible()).toBeFalsy(); + expect(secretInp.isVisible()).toBeTruthy(); + expect(importWalletButton.isVisible()).toBeTruthy(); + + // secretInput type should become visible! + expect(secretInp.attributes('type')).toBe('password'); + expect(secretInp.element.value).toBe(''); + expect(passwordInp.element.value).toBe(''); + + // Insert a secret + secretInp.element.value = 'dog'; + secretInp.trigger('input'); + await nextTick(); + // No spaces! attribute is still a password + expect(secretInp.attributes('type')).toBe('password'); + expect(passwordInp.isVisible()).toBeFalsy(); + + secretInp.element.value = 'dog pig'; + secretInp.trigger('input'); + await nextTick(); + // bip 39 (there is a space), secret is now visible + expect(secretInp.attributes('type')).toBe('text'); + // + no advanced mode, so passwordInp is still invisible + expect(passwordInp.isVisible()).toBeFalsy(); + + // Finally press the import button and verify that the event is emitted + await importWalletButton.trigger('click'); + // first of all this must empty the two input box + expect(secretInp.element.value).toBe(''); + expect(passwordInp.element.value).toBe(''); + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + ['dog pig', ''], + ]); + }); + it('Access wallet (advanced)', async () => { + const wrapper = mount(AccessWallet, { + props: { + advancedMode: true, + }, + attachTo: document.getElementById('app'), + }); + + const accWalletButton = wrapper.find('[data-testid=accWalletButton]'); + const passwordInp = wrapper.find('[data-testid=passwordInp]'); + const secretInp = wrapper.find('[data-testid=secretInp]'); + const importWalletButton = wrapper.find( + '[data-testid=importWalletButton]' + ); + + // before clicking the button, + // all input texts + import wallet button are hidden + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + expect(accWalletButton.isVisible()).toBeTruthy(); + expect(passwordInp.isVisible()).toBeFalsy(); + expect(secretInp.isVisible()).toBeFalsy(); + expect(importWalletButton.isVisible()).toBeFalsy(); + + //click the access Wallet button + await accWalletButton.trigger('click'); + expect(accWalletButton.isVisible()).toBeTruthy(); + // button clicked, so now everything should be visible apart the passwordInp + expect(passwordInp.isVisible()).toBeFalsy(); + expect(secretInp.isVisible()).toBeTruthy(); + expect(importWalletButton.isVisible()).toBeTruthy(); + + // Insert a pseudo bip39 seedphrase (i.e. something with a space) + // secretInput type should become visible! + expect(secretInp.attributes('type')).toBe('password'); + expect(secretInp.element.value).toBe(''); + expect(passwordInp.element.value).toBe(''); + secretInp.element.value = 'dog'; + secretInp.trigger('input'); + await nextTick(); + expect(secretInp.attributes('type')).toBe('password'); + // no spaces! so passwordInp is still invisible + expect(passwordInp.isVisible()).toBeFalsy(); + + // the users inserts the second word + secretInp.element.value = 'dog pig'; + secretInp.trigger('input'); + await nextTick(); + expect(secretInp.attributes('type')).toBe('text'); + // Finally the password field appeared! + expect(passwordInp.isVisible()).toBeTruthy(); + passwordInp.element.value = 'myPass'; + passwordInp.trigger('input'); + + // Finally press the import button and verify that the event is emitted + await importWalletButton.trigger('click'); + // first of all this must empty the two input box + expect(secretInp.element.value).toBe(''); + expect(passwordInp.element.value).toBe(''); + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + ['dog pig', 'myPass'], + ]); + + // Round 2, this time the user wants to insert his private key + // but the user by mistake begins inserting the seedphrase + secretInp.element.value = 'dog pig'; + secretInp.trigger('input'); + await nextTick(); + expect(secretInp.attributes('type')).toBe('text'); + expect(passwordInp.isVisible()).toBeTruthy(); + passwordInp.element.value = 'myPass'; + passwordInp.trigger('input'); + await nextTick(); + + // Oops I inserted the bip39! let me change + secretInp.element.value = 'xprivkey'; + secretInp.trigger('input'); + await nextTick(); + expect(secretInp.attributes('type')).toBe('password'); + // Password field must be cleared and invisible + expect(passwordInp.isVisible()).toBeFalsy(); + expect(passwordInp.element.value).toBe(''); + await nextTick(); + + await importWalletButton.trigger('click'); + expect(secretInp.element.value).toBe(''); + expect(passwordInp.element.value).toBe(''); + expect(wrapper.emitted('import-wallet')).toHaveLength(2); + expect(wrapper.emitted('import-wallet')[1]).toStrictEqual([ + 'xprivkey', + '', + ]); + }); +}); diff --git a/tests/components/CreateWallet.test.js b/tests/components/CreateWallet.test.js new file mode 100644 index 000000000..b189a020e --- /dev/null +++ b/tests/components/CreateWallet.test.js @@ -0,0 +1,105 @@ +import { mount } from '@vue/test-utils'; +import { expect } from 'vitest'; +import CreateWallet from '../../scripts/dashboard/CreateWallet.vue'; +import Modal from '../../scripts/Modal.vue'; +import { vi, it, describe } from 'vitest'; + +describe('create wallet tests', () => { + it('Generates wallet', async () => { + const wrapper = mount(CreateWallet, { + props: { + advancedMode: false, + }, + }); + expect(wrapper.emitted('importWallet')).toBeUndefined(); + // Modal with seedphrase is still hidden + expect( + wrapper + .findComponent(Modal) + .findAll('[data-testid=seedphraseModal]') + ).toHaveLength(0); + const genWalletButton = wrapper.find('[data-testid=generateWallet]'); + expect(genWalletButton.isVisible).toBeTruthy(); + await genWalletButton.trigger('click'); + + // The click generated a seedphrase modal + const seedphraseModals = wrapper + .findComponent(Modal) + .findAll('[data-testid=seedphraseModal]'); + // But there is no passphrase + expect( + wrapper.findComponent(Modal).findAll('[data-testid=passPhrase]') + ).toHaveLength(0); + expect(seedphraseModals).toHaveLength(1); + const seedphrase = wrapper.findComponent(Modal).text(); + // We must have 12 words in the seedphrase + expect(seedphrase.split(' ')).toHaveLength(12); + await seedphraseModals[0].trigger('click'); + // Which now disappeared again + expect( + wrapper + .findComponent(Modal) + .findAll('[data-testid=seedphraseModal]') + ).toHaveLength(0); + + // Ok We emitted exactly one event importWallet + expect(wrapper.emitted('importWallet')).toHaveLength(1); + // We emitted exactly the seedphrase with empty passphrase + expect(wrapper.emitted('importWallet')).toStrictEqual([ + [seedphrase, ''], + ]); + }); + it('Generates wallet advanced mode', async () => { + const wrapper = mount(CreateWallet, { + props: { + advancedMode: true, + }, + }); + expect(wrapper.emitted('importWallet')).toBeUndefined(); + // Modal with seedphrase and passphrase is still hidden + expect( + wrapper + .findComponent(Modal) + .findAll('[data-testid=seedphraseModal]') + ).toHaveLength(0); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=passPhrase]') + ).toHaveLength(0); + const genWalletButton = wrapper.find('[data-testid=generateWallet]'); + expect(genWalletButton.isVisible).toBeTruthy(); + await genWalletButton.trigger('click'); + + // The click generated a modal with seedphrase and passphrase + const seedphraseModals = wrapper + .findComponent(Modal) + .findAll('[data-testid=seedphraseModal]'); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=passPhrase]') + ).toHaveLength(1); + expect(seedphraseModals).toHaveLength(1); + const seedphrase = wrapper.findComponent(Modal).text(); + // We must have 12 words in the seedphrase + expect(seedphrase.split(' ')).toHaveLength(12); + // Select a pass phrase + const passPhrase = wrapper + .findComponent(Modal) + .find('[data-testid=passPhrase]'); + expect(passPhrase.element.value).toBe(''); + passPhrase.element.value = 'panleone'; + passPhrase.trigger('input'); + await seedphraseModals[0].trigger('click'); + // Which now disappeared again + expect( + wrapper + .findComponent(Modal) + .findAll('[data-testid=seedphraseModal]') + ).toHaveLength(0); + + // Ok We emitted exactly one event importWallet + expect(wrapper.emitted('importWallet')).toHaveLength(1); + // We emitted exactly the seedphrase with empty passphrase + expect(wrapper.emitted('importWallet')).toStrictEqual([ + [seedphrase, 'panleone'], + ]); + }); +}); diff --git a/tests/components/ExportPrivKey.test.js b/tests/components/ExportPrivKey.test.js new file mode 100644 index 000000000..c908b1a6d --- /dev/null +++ b/tests/components/ExportPrivKey.test.js @@ -0,0 +1,78 @@ +import { mount } from '@vue/test-utils'; +import { expect } from 'vitest'; +import ExportPrivKey from '../../scripts/dashboard/ExportPrivKey.vue'; +import Modal from '../../scripts/Modal.vue'; +import { vi, it, describe } from 'vitest'; + +describe('Export private key tests', () => { + afterEach(() => vi.clearAllMocks()); + it('Export Private key (showed)', async () => { + const wrapper = mount(ExportPrivKey, { + props: { + privateKey: 'MyPrivateSecretKey', + show: true, + }, + }); + expect(wrapper.emitted('close')).toBeUndefined(); + // show = true, i.e. both privateKeyText, blurButton and close button are visible + expect( + wrapper.findComponent(Modal).findAll('[data-testid=privateKeyText]') + ).toHaveLength(1); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=blurBtn]') + ).toHaveLength(1); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=closeBtn]') + ).toHaveLength(1); + + const privKeyText = wrapper + .findComponent(Modal) + .findAll('[data-testid=privateKeyText]')[0]; + const blurBtn = wrapper + .findComponent(Modal) + .findAll('[data-testid=blurBtn]')[0]; + const closeBtn = wrapper + .findComponent(Modal) + .findAll('[data-testid=closeBtn]')[0]; + + // Private key must be blurred + expect(privKeyText.attributes()['class']).toBe('blurred'); + expect(privKeyText.text()).toBe('MyPrivateSecretKey'); + // Click the button and privateKey should unblur + await blurBtn.trigger('click'); + expect(privKeyText.attributes()['class']).toBe(''); + expect(privKeyText.text()).toBe('MyPrivateSecretKey'); + // Click it again and it should blur again + await blurBtn.trigger('click'); + expect(privKeyText.attributes()['class']).toBe('blurred'); + expect(privKeyText.text()).toBe('MyPrivateSecretKey'); + // Finally unblur and close it + await blurBtn.trigger('click'); + await closeBtn.trigger('click'); + // on closing the privateKey must be blurred + expect(privKeyText.attributes()['class']).toBe('blurred'); + expect(privKeyText.text()).toBe('MyPrivateSecretKey'); + // The event close must have been emitted + expect(wrapper.emitted('close')).toHaveLength(1); + expect(wrapper.emitted('close')).toStrictEqual([[]]); + }); + it('Export Private key (closed)', async () => { + const wrapper = mount(ExportPrivKey, { + props: { + privateKey: 'MyPrivateSecretKey', + show: false, + }, + }); + expect(wrapper.emitted('close')).toBeUndefined(); + // show = false, i.e. nothing is there + expect( + wrapper.findComponent(Modal).findAll('[data-testid=privateKeyText]') + ).toHaveLength(0); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=blurBtn]') + ).toHaveLength(0); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=closeBtn]') + ).toHaveLength(0); + }); +}); diff --git a/tests/components/GenKeyWarning.test.js b/tests/components/GenKeyWarning.test.js new file mode 100644 index 000000000..8836c0399 --- /dev/null +++ b/tests/components/GenKeyWarning.test.js @@ -0,0 +1,271 @@ +import { mount } from '@vue/test-utils'; +import { beforeEach, expect } from 'vitest'; +import GenKeyWarning from '../../scripts/dashboard/GenKeyWarning.vue'; +import Modal from '../../scripts/Modal.vue'; +import { vi, it, describe } from 'vitest'; +import { nextTick } from 'vue'; +import * as translation from '../../scripts/i18n.js'; +import * as misc from '../../scripts/misc.js'; +import { MIN_PASS_LENGTH } from '../../scripts/chain_params.js'; + +const checkEventsEmitted = (wrapper, nClose, nOnEncrypt, nOpen) => { + if (nClose == 0) { + expect(wrapper.emitted('close')).toBeUndefined(); + } else { + expect(wrapper.emitted('close')).toHaveLength(nClose); + } + if (nOnEncrypt == 0) { + expect(wrapper.emitted('onEncrypt')).toBeUndefined(); + } else { + expect(wrapper.emitted('onEncrypt')).toHaveLength(nOnEncrypt); + } + if (nOpen == 0) { + expect(wrapper.emitted('open')).toBeUndefined(); + } else { + expect(wrapper.emitted('open')).toHaveLength(nOpen); + } +}; + +const checkModalExistence = (wrapper, exist) => { + expect( + wrapper + .findComponent(Modal) + .findAll('[data-testid=currentPasswordModal]') + ).toHaveLength(exist); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=newPasswordModal]') + ).toHaveLength(exist); + expect( + wrapper + .findComponent(Modal) + .findAll('[data-testid=confirmPasswordModal]') + ).toHaveLength(exist); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=submitBtn]') + ).toHaveLength(exist); + expect( + wrapper.findComponent(Modal).findAll('[data-testid=closeBtn]') + ).toHaveLength(exist); +}; + +describe('GenKeyWarning tests', () => { + beforeEach(() => { + // Mock translate and createAlert and the two translations used + vi.spyOn(translation, 'tr').mockImplementation((message, variables) => { + return message + variables[0].MIN_PASS_LENGTH; + }); + vi.spyOn(misc, 'createAlert').mockImplementation( + (type, message, timeout = 0) => { + return message; + } + ); + vi.spyOn(translation, 'ALERTS', 'get').mockReturnValue({ + PASSWORD_TOO_SMALL: 'pass_too_small', + PASSWORD_DOESNT_MATCH: 'pass_doesnt_match', + }); + }); + afterEach(() => vi.clearAllMocks()); + it('GenKeyWarning (no box)', async () => { + const wrapper = mount(GenKeyWarning, { + props: { + showModal: false, + showBox: false, + isEncrypt: false, + }, + attachTo: document.getElementById('app'), + }); + // No events emitted and nothing to see in this case! + checkEventsEmitted(wrapper, 0, 0, 0); + const encryptBox = wrapper.find('[data-testid=encryptBox]'); + expect(encryptBox.isVisible()).toBeFalsy(); + checkModalExistence(wrapper, 0); + }); + it('GenKeyWarning (no modal)', async () => { + const wrapper = mount(GenKeyWarning, { + props: { + showModal: false, + showBox: true, + isEncrypt: false, + }, + attachTo: document.getElementById('app'), + }); + checkEventsEmitted(wrapper, 0, 0, 0); + const encryptBox = wrapper.find('[data-testid=encryptBox]'); + expect(encryptBox.isVisible()).toBeTruthy(); + // Modal does not exist + checkModalExistence(wrapper, 0); + + // Click the encryptBox and the open event should be emitted + await encryptBox.trigger('click'); + await nextTick(); + checkEventsEmitted(wrapper, 0, 0, 1); + }); + it('GenKeyWarning (no encrypt)', async () => { + const wrapper = mount(GenKeyWarning, { + props: { + showModal: true, + showBox: true, + isEncrypt: false, + }, + attachTo: document.getElementById('app'), + }); + checkEventsEmitted(wrapper, 0, 0, 0); + const encryptBox = wrapper.find('[data-testid=encryptBox]'); + expect(encryptBox.isVisible()).toBeTruthy(); + // Modal exist + checkModalExistence(wrapper, 1); + + const newPassword = wrapper + .findComponent(Modal) + .findAll('[data-testid=newPasswordModal]')[0]; + const confirmPassword = wrapper + .findComponent(Modal) + .findAll('[data-testid=confirmPasswordModal]')[0]; + const submitBtn = wrapper + .findComponent(Modal) + .findAll('[data-testid=submitBtn]')[0]; + const currentPassword = wrapper + .findComponent(Modal) + .findAll('[data-testid=currentPasswordModal]')[0]; + const closeBtn = wrapper + .findComponent(Modal) + .findAll('[data-testid=closeBtn]')[0]; + // Everything but currentPassword is visible + expect(newPassword.isVisible()).toBeTruthy(); + expect(confirmPassword.isVisible()).toBeTruthy(); + expect(submitBtn.isVisible()).toBeTruthy(); + expect(currentPassword.isVisible()).toBeFalsy(); + expect(closeBtn.isVisible()).toBeTruthy(); + + // Let's try to insert a password + // Password match but it's too short! + newPassword.element.value = 'p'; + newPassword.trigger('input'); + await nextTick(); + confirmPassword.element.value = 'p'; + confirmPassword.trigger('input'); + await nextTick(); + await submitBtn.trigger('click'); + await nextTick(); + // no event should have been emitted + checkEventsEmitted(wrapper, 0, 0, 0); + expect(misc.createAlert).toHaveBeenCalled(); + expect(misc.createAlert).toHaveReturnedWith( + 'pass_too_small' + MIN_PASS_LENGTH + ); + + // Ok now the length has been changed to the minimum allowed value but passwords dont match! + const safePassword = new Array(MIN_PASS_LENGTH + 1).join('x'); + newPassword.element.value = safePassword; + newPassword.trigger('input'); + await nextTick(); + await submitBtn.trigger('click'); + await nextTick(); + // no event should have been emitted + checkEventsEmitted(wrapper, 0, 0, 0); + expect(misc.createAlert).toHaveBeenCalled(); + expect(misc.createAlert).toHaveReturnedWith('pass_doesnt_match'); + // Finally passwords match + confirmPassword.element.value = safePassword; + confirmPassword.trigger('input'); + await nextTick(); + await submitBtn.trigger('click'); + await nextTick(); + checkEventsEmitted(wrapper, 1, 1, 0); + expect(wrapper.emitted('onEncrypt')).toStrictEqual([ + [safePassword, ''], + ]); + expect(wrapper.emitted('close')).toStrictEqual([[]]); + + // Test the close button + await closeBtn.trigger('click'); + await nextTick(); + checkEventsEmitted(wrapper, 2, 1, 0); + expect(wrapper.emitted('close')).toStrictEqual([[], []]); + }); + it('GenKeyWarning (with encrypt)', async () => { + const wrapper = mount(GenKeyWarning, { + props: { + showModal: true, + showBox: true, + isEncrypt: true, + }, + attachTo: document.getElementById('app'), + }); + checkEventsEmitted(wrapper, 0, 0, 0); + const encryptBox = wrapper.find('[data-testid=encryptBox]'); + expect(encryptBox.isVisible()).toBeTruthy(); + // Modal exist + checkModalExistence(wrapper, 1); + + const newPassword = wrapper + .findComponent(Modal) + .findAll('[data-testid=newPasswordModal]')[0]; + const confirmPassword = wrapper + .findComponent(Modal) + .findAll('[data-testid=confirmPasswordModal]')[0]; + const submitBtn = wrapper + .findComponent(Modal) + .findAll('[data-testid=submitBtn]')[0]; + const currentPassword = wrapper + .findComponent(Modal) + .findAll('[data-testid=currentPasswordModal]')[0]; + const closeBtn = wrapper + .findComponent(Modal) + .findAll('[data-testid=closeBtn]')[0]; + // Every input box/ button must be visible + expect(newPassword.isVisible()).toBeTruthy(); + expect(confirmPassword.isVisible()).toBeTruthy(); + expect(submitBtn.isVisible()).toBeTruthy(); + expect(currentPassword.isVisible()).toBeTruthy(); + expect(closeBtn.isVisible()).toBeTruthy(); + + // Let's try to insert a password + // Password match but it's too short! + newPassword.element.value = 'p'; + newPassword.trigger('input'); + await nextTick(); + confirmPassword.element.value = 'p'; + confirmPassword.trigger('input'); + await nextTick(); + await submitBtn.trigger('click'); + await nextTick(); + // no event should have been emitted + checkEventsEmitted(wrapper, 0, 0, 0); + expect(misc.createAlert).toHaveBeenCalled(); + expect(misc.createAlert).toHaveReturnedWith( + 'pass_too_small' + MIN_PASS_LENGTH + ); + + // Ok now the length has been changed to the minimum allowed value but passwords dont match! + const safePassword = new Array(MIN_PASS_LENGTH + 1).join('x'); + newPassword.element.value = safePassword; + newPassword.trigger('input'); + await nextTick(); + await submitBtn.trigger('click'); + await nextTick(); + // no event should have been emitted + checkEventsEmitted(wrapper, 0, 0, 0); + expect(misc.createAlert).toHaveBeenCalled(); + expect(misc.createAlert).toHaveReturnedWith('pass_doesnt_match'); + // Finally passwords matches and verify also current password + currentPassword.element.value = 'panleon'; + currentPassword.trigger('input'); + await nextTick(); + confirmPassword.element.value = safePassword; + confirmPassword.trigger('input'); + await nextTick(); + await submitBtn.trigger('click'); + await nextTick(); + checkEventsEmitted(wrapper, 1, 1, 0); + expect(wrapper.emitted('onEncrypt')).toStrictEqual([ + [safePassword, 'panleon'], + ]); + expect(wrapper.emitted('close')).toStrictEqual([[]]); + // Test the close button + await closeBtn.trigger('click'); + await nextTick(); + checkEventsEmitted(wrapper, 2, 1, 0); + expect(wrapper.emitted('close')).toStrictEqual([[], []]); + }); +}); diff --git a/tests/components/Login.test.js b/tests/components/Login.test.js new file mode 100644 index 000000000..dbc68f123 --- /dev/null +++ b/tests/components/Login.test.js @@ -0,0 +1,138 @@ +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { expect } from 'vitest'; +import Login from '../../scripts/dashboard/Login.vue'; +import CreateWallet from '../../scripts/dashboard/CreateWallet.vue'; +import VanityGen from '../../scripts/dashboard/VanityGen.vue'; +import AccessWallet from '../../scripts/dashboard/AccessWallet.vue'; +import { vi, it, describe } from 'vitest'; + +describe('Login tests', () => { + afterEach(() => vi.clearAllMocks()); + test('Create wallet login (no advanced)', async () => { + const wrapper = shallowMount(Login, { + props: { + advancedMode: false, + }, + attachTo: document.getElementById('app'), + }); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + const createWalletComponent = wrapper.findComponent(CreateWallet); + // Create Wallet component must be visible + expect(createWalletComponent.isVisible()).toBeTruthy(); + expect(createWalletComponent.props()).toStrictEqual({ + advancedMode: false, + }); + // We can just emit the event: CreateWallet has already been unit tested! + createWalletComponent.vm.$emit('import-wallet', 'mySecret', ''); + // Make sure the Login component relays the right event + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + [{ password: '', secret: 'mySecret', type: 'hd' }], + ]); + }); + test('Create wallet login (advanced)', async () => { + const wrapper = shallowMount(Login, { + props: { + advancedMode: true, + }, + attachTo: document.getElementById('app'), + }); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + const createWalletComponent = wrapper.findComponent(CreateWallet); + // Create Wallet component must be visible + expect(createWalletComponent.isVisible()).toBeTruthy(); + expect(createWalletComponent.props()).toStrictEqual({ + advancedMode: true, + }); + // We can just emit the event: CreateWallet has already been unit tested! + createWalletComponent.vm.$emit('import-wallet', 'mySecret', 'myPass'); + // Make sure the Login component relays the right event + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + [{ password: 'myPass', secret: 'mySecret', type: 'hd' }], + ]); + }); + test('Vanity gen login', async () => { + const wrapper = shallowMount(Login, { + props: { + advancedMode: false, + }, + attachTo: document.getElementById('app'), + }); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + const vanityGenComponent = wrapper.findComponent(VanityGen); + // Create Wallet component must be visible + expect(vanityGenComponent.isVisible()).toBeTruthy(); + // Vanity gen is easy: it has no props + expect(vanityGenComponent.props()).toStrictEqual({}); + // We can just emit a complete random event: VanityGen has already been unit tested! + vanityGenComponent.vm.$emit('import-wallet', 'mySecret'); + // Make sure the Login component relays the right event + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + [{ secret: 'mySecret', type: 'legacy' }], + ]); + }); + test('Access wallet login (no advanced)', async () => { + const wrapper = shallowMount(Login, { + props: { + advancedMode: false, + }, + attachTo: document.getElementById('app'), + }); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + const accessWalletComponent = wrapper.findComponent(AccessWallet); + // Make sure that access Wallet Component has been created with the correct props + expect(accessWalletComponent.props()).toStrictEqual({ + advancedMode: false, + }); + // We can just emit a complete random event: AccessWallet has already been unit tested! + accessWalletComponent.vm.$emit('import-wallet', 'mySecret', ''); + // Make sure the Login component relays the right event + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + [{ secret: 'mySecret', type: 'hd', password: '' }], + ]); + }); + test('Access wallet login (advanced)', async () => { + const wrapper = shallowMount(Login, { + props: { + advancedMode: true, + }, + attachTo: document.getElementById('app'), + }); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + const accessWalletComponent = wrapper.findComponent(AccessWallet); + // Make sure that access Wallet Component has been created with the correct props + expect(accessWalletComponent.props()).toStrictEqual({ + advancedMode: true, + }); + // We can just emit a complete random event: AccessWallet has already been unit tested! + accessWalletComponent.vm.$emit('import-wallet', 'mySecret', 'myPass'); + // Make sure the Login component relays the right event + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + [{ secret: 'mySecret', type: 'hd', password: 'myPass' }], + ]); + }); + test('HardwareWallet login', async () => { + const wrapper = shallowMount(Login, { + props: { + advancedMode: false, + }, + attachTo: document.getElementById('app'), + }); + const hardwareWalletBtn = wrapper.find( + '[data-testid=hardwareWalletBtn]' + ); + // Make sure it's visible and click it + expect(hardwareWalletBtn.isVisible()).toBeTruthy(); + await hardwareWalletBtn.trigger('click'); + // Make sure the Login component relays the right event + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')).toStrictEqual([ + [{ type: 'hardware' }], + ]); + }); +}); diff --git a/tests/components/StakeBalance.test.js b/tests/components/StakeBalance.test.js new file mode 100644 index 000000000..4d87961fc --- /dev/null +++ b/tests/components/StakeBalance.test.js @@ -0,0 +1,102 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import * as misc from '../../scripts/misc.js'; +import { nextTick } from 'vue'; +import { mount as vueMount } from '@vue/test-utils'; +import StakeBalance from '../../scripts/stake/StakeBalance.vue'; +import Modal from '../../scripts/Modal.vue'; +describe('stake balance tests', () => { + beforeAll(() => { + vi.spyOn(misc, 'createAlert').mockImplementation( + (type, message, timeout = 0) => { + return message; + } + ); + return vi.clearAllMocks(); + }); + function mount(props) { + return vueMount(StakeBalance, { + props, + attachTo: document.getElementById('app'), + }); + } + it('shows correct balance', async () => { + const wrapper = mount({ + coldBalance: 3.5 * 10 ** 8, + price: 3.1, + currency: 'DOGE', + displayDecimals: 2, + }); + + const balance = wrapper.find('[data-testid=coldBalance]'); + expect(balance.text()).toBe('3.50'); + + const value = wrapper.find('[data-testid=coldBalanceValue]'); + expect(value.text()).toBe(`$${3.5 * 3.1}`); + + const currency = wrapper.find('[data-testid=coldBalanceCurrency]'); + expect(currency.text()).toBe('DOGE'); + }); + + it('emits stake and unstake events', async () => { + const wrapper = mount({ + coldBalance: 3.5 * 10 ** 8, + price: 3.1, + currency: 'DOGE', + displayDecimals: 2, + }); + await wrapper.find('[data-testid=showStakeButton]').trigger('click'); + expect(wrapper.emitted('showStake')).toStrictEqual([[]]); + await wrapper.find('[data-testid=showUnstakeButton]').trigger('click'); + expect(wrapper.emitted('showUnstake')).toStrictEqual([[]]); + }); + it('updates cold stake address', async () => { + const wrapper = mount({ + coldBalance: 3.5 * 10 ** 8, + price: 3.1, + currency: 'DOGE', + displayDecimals: 2, + coldStakingAddress: 'oldcsaddr', + 'onUpdate:coldStakingAddress': (e) => + wrapper.setProps({ coldStakingAddress: e }), + }); + await wrapper.find('[data-testid=setColdStakeButton]').trigger('click'); + const csaddrInput = wrapper + .findComponent(Modal) + .find('[data-testid=csAddrInput]'); + // Test that it is in focus + expect(csaddrInput.element).toBe(document.activeElement); + expect(csaddrInput.isVisible()).toBeTruthy(); + const newCsAddr = 'SdgQDpS8jDRJDX8yK8m9KnTMarsE84zdsy'; + csaddrInput.element.value = newCsAddr; + csaddrInput.trigger('input'); + const confirmButton = wrapper + .findComponent(Modal) + .find('[data-testid=csAddrSubmit]'); + await confirmButton.trigger('click'); + expect(wrapper.props().coldStakingAddress).toBe(newCsAddr); + }); + it('cancels stake address without updating', async () => { + const wrapper = mount({ + coldBalance: 3.5 * 10 ** 8, + price: 3.1, + currency: 'DOGE', + displayDecimals: 2, + coldStakingAddress: 'oldcsaddr', + 'onUpdate:coldStakingAddress': (e) => + wrapper.setProps({ coldStakingAddress: e }), + }); + await wrapper.find('[data-testid=setColdStakeButton]').trigger('click'); + const csaddrInput = wrapper + .findComponent(Modal) + .find('[data-testid=csAddrInput]'); + expect(csaddrInput.isVisible()).toBeTruthy(); + const newCsAddr = 'SdgQDpS8jDRJDX8yK8m9KnTMarsE84zdsy'; + csaddrInput.element.value = newCsAddr; + csaddrInput.trigger('input'); + const cancelButton = wrapper + .findComponent(Modal) + .find('[data-testid=csAddrCancel]'); + await cancelButton.trigger('click'); + expect(wrapper.props().coldStakingAddress).toBe('oldcsaddr'); + }); +}); diff --git a/tests/components/StakeInput.test.js b/tests/components/StakeInput.test.js new file mode 100644 index 000000000..27fb1c6d2 --- /dev/null +++ b/tests/components/StakeInput.test.js @@ -0,0 +1,63 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import * as misc from '../../scripts/misc.js'; +import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; +import StakeInput from '../../scripts/stake/StakeInput.vue'; +import Modal from '../../scripts/Modal.vue'; +const price = 0.4; +const mountSI = (amount = '123', unstake = false) => { + const wrapper = mount(StakeInput, { + props: { + unstake, + price, + show: true, + amount, + 'onUpdate:amount': (e) => wrapper.setProps({ amount: e }), + }, + }); + return wrapper; +}; + +describe('stake balance tests', () => { + it('updates inputs', async () => { + const wrapper = mountSI(); + + const amount = wrapper.find('[data-testid=amount]'); + const currency = wrapper.find('[data-testid=amountCurrency]'); + + amount.trigger('input'); + await nextTick(); + await nextTick(); + + // Test that amount -> currency updates + expect(amount.element.value).toBe('123'); + expect(currency.element.value).toBe(`${123 * price}`); + // Test that currency -> amount updates + currency.element.value = '49'; + currency.trigger('input'); + await nextTick(); + + expect(amount.element.value).toBe(`${49 / price}`); + expect(currency.element.value).toBe(`49`); + + // Test that setting one as empty clears the other + currency.element.value = ''; + currency.trigger('input'); + await nextTick(); + + expect(amount.element.value).toBe(''); + expect(currency.element.value).toBe(''); + }); + it('closes correctly', async () => { + const wrapper = mountSI(); + expect(wrapper.emitted('close')).toBeUndefined(); + wrapper.find('[data-testid=closeButton]').trigger('click'); + expect(wrapper.emitted('close')).toHaveLength(1); + }); + it('Unstakes correctly', async () => { + const wrapper = mountSI('60'); + expect(wrapper.emitted('submit')).toBeUndefined(); + await wrapper.find('[data-testid=sendButton]').trigger('click'); + expect(wrapper.emitted('submit')).toStrictEqual([[60 * 10 ** 8, '']]); + }); +}); diff --git a/tests/components/TransferMenu.test.js b/tests/components/TransferMenu.test.js new file mode 100644 index 000000000..14e6439e8 --- /dev/null +++ b/tests/components/TransferMenu.test.js @@ -0,0 +1,76 @@ +import 'fake-indexeddb/auto'; +import { mount } from '@vue/test-utils'; +import { nextTick, ref } from 'vue'; +import { expect, describe, vi } from 'vitest'; +import TransferMenu from '../../scripts/dashboard/TransferMenu.vue'; +const price = 0.4; +const mountTM = (amount = '123', address = '') => { + const wrapper = mount(TransferMenu, { + props: { + show: true, + price, + currency: 'USD', + amount, + address, + + 'onUpdate:amount': (e) => wrapper.setProps({ amount: e }), + shieldEnabled: true, + }, + }); + return wrapper; +}; + +describe('transfer menu tests', () => { + beforeEach(async () => { + // Reset indexedDB before each test + vi.stubGlobal('indexedDB', new IDBFactory()); + return vi.unstubAllGlobals; + }); + it('Updates inputs', async () => { + const wrapper = mountTM(); + + const amount = wrapper.find('[data-testid=amount]'); + const currency = wrapper.find('[data-testid=amountCurrency]'); + + amount.trigger('input'); + + await nextTick(); + await nextTick(); + + // Test that amount -> currency updates + expect(amount.element.value).toBe('123'); + expect(currency.element.value).toBe(`${123 * price}`); + + // Test that currency -> amount updates + currency.element.value = '49'; + currency.trigger('input'); + await nextTick(); + + expect(amount.element.value).toBe(`${49 / price}`); + expect(currency.element.value).toBe(`49`); + + // Test that setting one as empty clears the other + currency.element.value = ''; + currency.trigger('input'); + await nextTick(); + + expect(amount.element.value).toBe(''); + expect(currency.element.value).toBe(''); + }); + + it('Closes correctly', async () => { + const wrapper = mountTM(); + expect(wrapper.emitted('close')).toBeUndefined(); + wrapper.find('[data-testid=closeButton]').trigger('click'); + expect(wrapper.emitted('close')).toHaveLength(1); + }); + + it('Sends transaction correctly', async () => { + const wrapper = mountTM('60', 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc'); + expect(wrapper.emitted('send')).toBeUndefined(); + await wrapper.find('[data-testid=sendButton]').trigger('click'); + expect(wrapper.emitted('send')).toStrictEqual([ + ['DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc', '60', false], + ]); + }); +}); diff --git a/tests/components/VanityGen.test.js b/tests/components/VanityGen.test.js new file mode 100644 index 000000000..320d84c36 --- /dev/null +++ b/tests/components/VanityGen.test.js @@ -0,0 +1,133 @@ +import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { expect } from 'vitest'; +import VanityGen from '../../scripts/dashboard/VanityGen.vue'; +import { vi, it, describe } from 'vitest'; +import * as translation from '../../scripts/i18n.js'; +import * as misc from '../../scripts/misc.js'; + +describe('VanityGen tests', () => { + beforeEach(() => { + vi.spyOn(translation, 'tr').mockImplementation((message, variables) => { + return message + variables[0].char; + }); + vi.spyOn(misc, 'createAlert').mockImplementation( + (type, message, timeout = 0) => { + return message; + } + ); + vi.spyOn(translation, 'ALERTS', 'get').mockReturnValue({ + UNSUPPORTED_WEBWORKERS: 'unsupported_web_worker', + UNSUPPORTED_CHARACTER: 'unsupported_character', + }); + }); + afterEach(() => { + vi.clearAllMocks(); + vi.unstubAllGlobals(); + }); + it('Unsupported worker test', async () => { + const wrapper = mount(VanityGen, { + attachTo: document.getElementById('app'), + }); + const vanityWalletButton = wrapper.find( + '[data-testid=vanityWalletButton]' + ); + await vanityWalletButton.trigger('click'); + const generateBtn = wrapper.find('[data-testid=generateBtn]'); + await generateBtn.trigger('click'); + await nextTick(); + // We don't have a valid webworker + expect(misc.createAlert).toHaveReturnedWith('unsupported_web_worker'); + }); + it('Vanity Gen test', async () => { + // Mock the vanity gen worker + // NB: we are taking for granted that the Worker of VanityGen works as intended, + // and we are just mocking it with random stuff + const Worker = vi.fn(); + const mocks = [ + { + priv: new Uint8Array([30, 40, 50]), + pub: 'Dddd', + }, + { + priv: new Uint8Array([50, 60, 70]), + pub: 'Dpan', + }, + ]; + let tryMock = 0; + Worker.prototype.postMessage = function () { + this.onmessage({ data: mocks[tryMock++ % mocks.length] }); + }; + Worker.prototype.terminate = vi.fn(); + vi.stubGlobal('Worker', Worker); + vi.stubGlobal('navigator', { hardwareConcurrency: 1 }); + // Mock translate and createAlert and the two translations used + const wrapper = mount(VanityGen, { + attachTo: document.getElementById('app'), + }); + + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + const vanityWalletButton = wrapper.find( + '[data-testid=vanityWalletButton]' + ); + await vanityWalletButton.trigger('click'); + const generateBtn = wrapper.find('[data-testid=generateBtn]'); + const prefixInput = wrapper.find('[data-testid=prefixInput]'); + + //click and now the prefix input box should be visible + await generateBtn.trigger('click'); + await nextTick(); + expect(generateBtn.isVisible()).toBeTruthy(); + expect(prefixInput.isVisible()).toBeTruthy(); + expect(prefixInput.element.value).toBe(''); + + // Insert a non-valid prefix + prefixInput.element.value = 'd%a'; + prefixInput.trigger('input'); + await nextTick(); + //click again and verify the createAlert + await generateBtn.trigger('click'); + await nextTick(); + expect(misc.createAlert).toHaveBeenCalled(); + expect(misc.createAlert).toHaveReturnedWith('unsupported_character%'); + expect(generateBtn.isVisible()).toBeTruthy(); + expect(prefixInput.isVisible()).toBeTruthy(); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + + // Add another invalid character: the createAlert should be called even if we don't press the button! + prefixInput.element.value = 'd%a$'; + prefixInput.trigger('input'); + await nextTick(); + expect(misc.createAlert).toHaveBeenCalled(); + expect(misc.createAlert).toHaveReturnedWith('unsupported_character$'); + + // Click again to stop the search + await generateBtn.trigger('click'); + prefixInput.trigger('input'); + expect(prefixInput.element.disabled).toBe(false); + prefixInput.element.value = 'panle'; + prefixInput.trigger('input'); + await nextTick(); + await generateBtn.trigger('click'); + await nextTick(); + // prefix input should be disabled while generating + expect(generateBtn.isVisible()).toBeTruthy(); + expect(prefixInput.element.disabled).toBe(true); + // ok... prefix was too long it will never finish! stop the execution + await generateBtn.trigger('click'); + await nextTick(); + expect(prefixInput.element.disabled).toBe(false); + expect(wrapper.emitted('import-wallet')).toBeUndefined(); + + // monocharacter prefix + prefixInput.element.value = 'p'; + prefixInput.trigger('input'); + await nextTick(); + await generateBtn.trigger('click'); + await nextTick(); + // We found an address! Verify that the result is the expected (mocked in this case) one + const res = new Uint8Array([50, 60, 70]); + expect(wrapper.emitted('import-wallet')).toHaveLength(1); + expect(wrapper.emitted('import-wallet')[0]).toStrictEqual([res]); + }); +}); diff --git a/tests/integration/wallet/sync.spec.js b/tests/integration/wallet/sync.spec.js new file mode 100644 index 000000000..be86ce6c4 --- /dev/null +++ b/tests/integration/wallet/sync.spec.js @@ -0,0 +1,192 @@ +import { + getWalletFromAddress, + setUpHDMainnetWallet, + setUpLegacyMainnetWallet, +} from '../../utils/test_utils'; + +import 'fake-indexeddb/auto'; +import { describe, it, vi, expect, afterAll } from 'vitest'; +import { + getNetwork, + resetNetwork, +} from '../../../scripts/__mocks__/network.js'; +import { refreshChainData } from '../../../scripts/global.js'; +import { sleep } from '../../../scripts/utils.js'; +import { COIN } from '../../../scripts/chain_params.js'; + +vi.mock('../../../scripts/network.js'); + +/** + * @param{import('scripts/wallet').Wallet} wallet - wallet that will generate the transaction + * @param{string} address - address that will receive funds + * @param{number} value - amounts to transfer + * @returns {Promise} + */ +async function crateAndSendTransaction(wallet, address, value) { + const tx = wallet.createTransaction(address, value); + await wallet.sign(tx); + expect(getNetwork().sendTransaction(tx.serialize())).toBeTruthy(); + await wallet.addTransaction(tx); +} + +async function mineAndSync() { + getNetwork().mintBlock(); + await refreshChainData(); + // 500 milliseconds are enough time to make the wallets sync and handle the new blocks + await sleep(500); +} + +/** + * Mine a given number of blocks + * @param{number} nBlocks + * @returns {Promise} + */ +async function mineBlocks(nBlocks) { + for (let i = 0; i < nBlocks; i++) { + getNetwork().mintBlock(); + } + await refreshChainData(); + await sleep(500); +} + +describe('Wallet sync tests', () => { + let walletHD; + let walletLegacy; + beforeEach(async () => { + resetNetwork(); + // Update the global variable blockCount + await refreshChainData(); + walletHD = await setUpHDMainnetWallet(false); + walletLegacy = await setUpLegacyMainnetWallet(); + // Reset indexedDB before each test + vi.stubGlobal('indexedDB', new IDBFactory()); + }); + + it('Basic 2 wallets sync test', async () => { + // --- Verify that funds are received after sending a transaction --- + // The legacy wallet sends the HD wallet 0.05 PIVs + await crateAndSendTransaction( + walletLegacy, + walletHD.getCurrentAddress(), + 0.05 * 10 ** 8 + ); + // Mint the block with the transaction + await mineAndSync(); + // getLatestBlocks sync up until chain tip - 1 block, + // so at this point walletHD doesn't still know about the UTXO he received + expect(walletHD.balance).toBe(1 * 10 ** 8); + // mine an empty block and verify that the tx arrived + await mineAndSync(); + expect(walletHD.balance).toBe((1 + 0.05) * 10 ** 8); + + // Sends funds back to the legacy wallet and verify that he also correctly receives funds + const legacyBalance = walletLegacy.balance; + await crateAndSendTransaction( + walletHD, + walletLegacy.getCurrentAddress(), + 1 * 10 ** 8 + ); + await mineAndSync(); + // again we need an empty block... + await mineAndSync(); + expect(walletLegacy.balance - legacyBalance).toBe(1 * 10 ** 8); + }); + it('MAX_ACCOUNT_GAP is respected', async () => { + // --- Verify that MAX_ACCOUNT_GAP is respected --- + // at this point the HD wallet has only 1 UTXO received at path .../0 + let path = "m/44'/119'/0'/0/0"; + let nAddress = 0; + // So according to BIP32 standard + // wallets must be aware of addresses up to nAddress + MAX_ACCOUNT_GAP + for (let i = 0; i < 5; i++) { + nAddress += 20; + let newAddress = walletHD.getAddressFromPath( + path.slice(0, -1) + String(nAddress) + ); + await crateAndSendTransaction( + walletLegacy, + newAddress, + 0.01 * 10 ** 8 + ); + await mineAndSync(); + expect(walletHD.balance).toBe((1 + 0.01 * i) * 10 ** 8); + } + }); + it('recognizes immature balance', async () => { + const globalNetWork = getNetwork(); + const DLabsWatchOnly = await getWalletFromAddress( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb' + ); + // Starting point: the wallet might have some initial balance that we don't really care + const initBalance = DLabsWatchOnly.balance; + const initImmatureBalance = DLabsWatchOnly.immatureBalance; + const initialColdBalance = DLabsWatchOnly.coldBalance; + + // At this point superblock happens... and people decided to fund 20k PIVs to a cat + const superblockTx = + '01000000012f4c0d09d96acce3e6f3dbb3d076bd5e13aae0a8cd79825fd31c81ec00bbfdba010000004847304402205d80a436187e90a416d0f30d39de8e47d2edbedf9af0bafcb1e117d4eac636e40220486914efa286caf31479f6e1d28ad82eed05acaa32b5925589682990760925e001ffffffff030000000000000000001d29131125000000232102c9461a4648cf10d61da673feb4486ee0ba9a3f62810364ff2a509a58be58a5a4ac00204aa9d10100001976a914a95cc6408a676232d61ec29dc56a180b5847835788ac00000000'; + let superblockProfit = 20000 * COIN; + + expect(globalNetWork.sendTransaction(superblockTx)).toBeTruthy(); + // Mint a couple of block to have the balance updated. + await mineBlocks(2); + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe( + superblockProfit + ); + expect(DLabsWatchOnly.balance - initBalance).toBe(0); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe(0); + + await mineBlocks(48); + // after a while balance is still unchanged, but the wallet receives a cold stake reward + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe( + superblockProfit + ); + expect(DLabsWatchOnly.balance - initBalance).toBe(0); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe(0); + + const coldStakeTx = + '01000000016308bafc9df672f80b657fadaac1217b790a470c2cfbbb489cce59a7508b76c2010000006c47304402207718aa50f2f583815e8e6920eea7d26127bdc8fedf1af92352e736cc077862880220070d2b670cad0e57f4cb302f10e4246c3d1d211f5bdb575fd40d054c8db24c610101512102883374ead5b57d8db4a302f64b0b72e214f6a428dd1eff85919ec289ad92c52effffffff03000000000000000000007cead30b0000003376a97b63d114b3be8567d0190c67ca4675a0019089c55fe695f96714a95cc6408a676232d61ec29dc56a180b584783576888ac0046c323000000001976a914d4a0bee06c596bfd9fec604d316cbc3b67f4424e88ac00000000'; + let coldStakeProfit = 508 * COIN; + expect(globalNetWork.sendTransaction(coldStakeTx)).toBeTruthy(); + // Mint a couple of block to have the balance updated. + await mineBlocks(2); + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe( + superblockProfit + coldStakeProfit + ); + expect(DLabsWatchOnly.balance - initBalance).toBe(0); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe(0); + + // 99 blocks after the superblock... still nothing + await mineBlocks(48); + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe( + superblockProfit + coldStakeProfit + ); + expect(DLabsWatchOnly.balance - initBalance).toBe(0); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe(0); + + await mineBlocks(1); + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe( + coldStakeProfit + ); + expect(DLabsWatchOnly.balance - initBalance).toBe(superblockProfit); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe(0); + + // 99 blocks after the coldStake reward... unchanged + await mineBlocks(49); + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe( + coldStakeProfit + ); + expect(DLabsWatchOnly.balance - initBalance).toBe(superblockProfit); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe(0); + + await mineBlocks(1); + expect(DLabsWatchOnly.immatureBalance - initImmatureBalance).toBe(0); + expect(DLabsWatchOnly.balance - initBalance).toBe(superblockProfit); + expect(DLabsWatchOnly.coldBalance - initialColdBalance).toBe( + coldStakeProfit + ); + }); + afterAll(() => { + vi.clearAllMocks(); + }); +}); diff --git a/tests/unit/database.spec.js b/tests/unit/database.spec.js new file mode 100644 index 000000000..86b053db7 --- /dev/null +++ b/tests/unit/database.spec.js @@ -0,0 +1,247 @@ +import 'fake-indexeddb/auto'; +import { PromoWallet } from '../../scripts/promos.js'; +import { it, describe, vi, expect } from 'vitest'; +import { Database } from '../../scripts/database.js'; +import { Account } from '../../scripts/accounts'; +import * as misc from '../../scripts/misc.js'; +import { Settings } from '../../scripts/settings'; +import Masternode from '../../scripts/masternode'; +import { Transaction } from '../../scripts/transaction'; +describe('database tests', () => { + beforeAll(() => { + // Mock createAlert + vi.spyOn(misc, 'createAlert').mockImplementation(vi.fn()); + vi.stubGlobal(global.console, 'error'); + return () => { + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + }; + }); + beforeEach(async () => { + // Reset indexedDB before each test + vi.stubGlobal('indexedDB', new IDBFactory()); + return vi.unstubAllGlobals; + }); + it('stores account correctly', async () => { + const db = await Database.create('test'); + const account = new Account({ + publicKey: 'test1', + coldAddress: 'very cold', + }); + await db.addAccount(account); + expect(await db.getAccount()).toStrictEqual(account); + await db.updateAccount( + new Account({ + encWif: 'newWIF!', + localProposals: ['prop1', 'prop2'], + }) + ); + expect((await db.getAccount()).encWif).toBe('newWIF!'); + expect((await db.getAccount()).publicKey).toBe('test1'); + expect((await db.getAccount()).coldAddress).toBe('very cold'); + expect((await db.getAccount()).localProposals).toStrictEqual([ + 'prop1', + 'prop2', + ]); + + // Setting localProposals as empty doesn't overwrite the array + await db.updateAccount( + new Account({ + encWif: 'newWIF2!', + localProposals: [], + }) + ); + expect((await db.getAccount()).localProposals).toStrictEqual([ + 'prop1', + 'prop2', + ]); + + // Unless `allowDeletion` is set to true + await db.updateAccount( + new Account({ + encWif: 'newWIF2!', + localProposals: [], + }), + true + ); + expect((await db.getAccount()).localProposals).toHaveLength(0); + + await db.removeAccount({ publicKey: 'test1' }); + + expect(await db.getAccount()).toBeNull(); + }); + + it('stores transaction correctly', async () => { + const transactions = [ + '0100000001d895a1dfa0198d6b8c8cf2eacccca96586ee0d2281b19c36b995aad096899cdb010000006c473044022030b9d2447e976562133a7c2b9fb70b25f427b205491d14a4f11c5096426a4d8202204bde1afbdd4dd8eb4ec6ab3b22512354dc917c4eccb464f228c2c6a28fed46080101512102883374ead5b57d8db4a302f64b0b72e214f6a428dd1eff85919ec289ad92c52effffffff030000000000000000007bd4532b0f0000003376a97b63d114b3be8567d0190c67ca4675a0019089c55fe695f967142bd00337677ee0216d47a2dbdf1f4e107e54e4206888ac0046c323000000001976a914f5f49c7ed95e5b7f54b0f93a81d9699472011feb88ac00000000', + '01000000019e07debe5a00f18f9343b270dc7d408c3116e605dd937eb432e1cab23ea407fa010000006d483045022100e4ecab97c5c733c9d883046653045fa269031a89812921ae2bb956e063b911d9022053413e4c8e0aae5ac580bbd6c9a3eb08222ecb0ab6821a96d3389872a304ec3a01015121025559a792b8805da7f5d61237a45d7e70c9b3133a176e755e7e90514e37673720ffffffff03000000000000000000900a44e8100000003376a97b63d114490254a35e71df73197db35a8d4c5f50068cd1f26714f250ed27afc2f51164c67501471a52661cd8f62f6888ac0046c323000000001976a914074610f922bee2ec55dc15a1e335e8ca9cec991388ac00000000', + '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05038bce3f00ffffffff0100000000000000000000000000', + ].map((h) => Transaction.fromHex(h)); + const db = await Database.create('test'); + for (const tx of transactions) { + await db.storeTx(tx); + } + expect( + (await db.getTxs()).sort((a, b) => a.txid.localeCompare(b.txid)) + ).toStrictEqual( + transactions.sort((a, b) => a.txid.localeCompare(b.txid)) + ); + // If we store a tx multiple times, it won't repeat the transaction + await db.storeTx(transactions[0]); + expect( + (await db.getTxs()).sort((a, b) => a.txid.localeCompare(b.txid)) + ).toStrictEqual( + transactions.sort((a, b) => a.txid.localeCompare(b.txid)) + ); + }); + + it('stores masternodes correctly', async () => { + const db = await Database.create('test'); + // Masternode should be null by default + expect(await db.getMasternode()).toBe(null); + let masternode = new Masternode({ + collateralTxId: 'mntxid', + }); + await db.addMasternode(masternode); + expect(await db.getMasternode()).toStrictEqual(masternode); + masternode = new Masternode({ + collateralTxId: 'mntxid2', + }); + // Subsequent calls to `addMasternode` should overwrite it. + await db.addMasternode(masternode); + expect(await db.getMasternode()).toStrictEqual(masternode); + // Check that it removes mn correectly + await db.removeMasternode(); + expect(await db.getMasternode()).toBe(null); + }); + + it('stores promos correctly', async () => { + const testPromos = new Array(50).fill(0).map( + (_, i) => + new PromoWallet({ + code: `${i}`, + }) + ); + const db = await Database.create('test'); + // It starts with no promos + expect(await db.getAllPromos()).toHaveLength(0); + + await db.addPromo(testPromos[0]); + expect(await db.getAllPromos()).toStrictEqual([testPromos[0]]); + + // If we add the same promo twice, it should not duplicate it + await db.addPromo(testPromos[0]); + expect(await db.getAllPromos()).toStrictEqual([testPromos[0]]); + + // Removes correctly + await db.removePromo(testPromos[0].code); + expect(await db.getAllPromos()).toHaveLength(0); + + for (const promo of testPromos) { + await db.addPromo(promo); + } + expect( + (await db.getAllPromos()).sort( + (a, b) => parseInt(a.code) - parseInt(b.code) + ) + ).toStrictEqual(testPromos); + await db.removePromo('23'); + expect( + (await db.getAllPromos()).sort( + (a, b) => parseInt(a.code) - parseInt(b.code) + ) + ).toStrictEqual(testPromos.filter((p) => p.code != '23')); + }); + + it('stores settings correctly', async () => { + const db = await Database.create('test'); + const settings = new Settings({ + explorer: 'duddino.com', + node: 'pivx.com', + }); + // Settings should be left as default at the beginning + expect(await db.getSettings()).toStrictEqual(new Settings()); + await db.setSettings(settings); + expect(await db.getSettings()).toStrictEqual(settings); + // Test that overwrite works as expected + await db.setSettings({ + node: 'pivx.org', + }); + expect(await db.getSettings()).toStrictEqual( + new Settings({ + explorer: 'duddino.com', + node: 'pivx.org', + }) + ); + }); + + it('throws when calling addAccount twice', async () => { + const db = await Database.create('test'); + const account = new Account(); + db.addAccount(account); + expect(() => db.addAccount(account)).rejects.toThrow( + /account already exists/i + ); + }); + it('throws when called with an invalid account', async () => { + const db = await Database.create('test'); + expect(() => db.addAccount({ publicKey: 'jaeir' })).rejects.toThrow( + /invalid account/ + ); + expect(() => db.updateAccount({ publicKey: 'jaeir' })).rejects.toThrow( + /invalid account/ + ); + }); + it("throws when updating an account that doesn't exist", async () => { + const db = await Database.create('test'); + expect(() => db.updateAccount(new Account())).rejects.toThrow( + /account doesn't exist/ + ); + }); + + it('migrates from local storage correctly', async () => { + vi.stubGlobal('localStorage', { + explorer: 'duddino.com', + translation: 'DE', + encwif: 'ENCRYPTED_WIF', + publicKey: 'PUB_KEY', + masternode: JSON.stringify( + new Masternode({ collateralTxId: 'mntxid' }) + ), + }); + const db = await Database.create('test'); + expect(await db.getAccount()).toStrictEqual( + new Account({ + publicKey: 'PUB_KEY', + encWif: 'ENCRYPTED_WIF', + }) + ); + expect(await db.getSettings()).toStrictEqual( + new Settings({ + explorer: 'duddino.com', + translation: 'DE', + }) + ); + expect(await db.getMasternode()).toStrictEqual( + new Masternode({ collateralTxId: 'mntxid' }) + ); + + vi.unstubAllGlobals(); + }); + + it('is isolated between different instances', async () => { + const db = await Database.create('test'); + const db2 = await Database.create('test2'); + // Initially, both accounts are null + expect(await db.getAccount()).toBe(null); + expect(await db2.getAccount()).toBe(null); + const account = new Account({ + publicKey: 'test1', + }); + // Let's add an account to the first db + await db.addAccount(account); + // First DB has the account, the second one is undefined + expect((await db.getAccount())?.publicKey).toBe('test1'); + expect((await db2.getAccount())?.publicKey).toBeUndefined(); + }); +}); diff --git a/tests/unit/encoding.spec.js b/tests/unit/encoding.spec.js new file mode 100644 index 000000000..3136a66bb --- /dev/null +++ b/tests/unit/encoding.spec.js @@ -0,0 +1,155 @@ +import { + parseWIF, + numToBytes, + numToByteArray, + numToVarInt, + bytesToNum, + varIntToNum, +} from '../../scripts/encoding.js'; +import { describe, it, test, expect } from 'vitest'; +import { + isColdAddress, + isExchangeAddress, + isShieldAddress, + isStandardAddress, + isValidPIVXAddress, +} from '../../scripts/misc'; + +describe('parse WIF tests', () => { + it('Parses WIF correctly', () => { + expect( + parseWIF('YU12G8Y9LwC3wb2cwUXvvg1iMvBey1ibCF23WBAapCuaKhd6a4R6') + ).toStrictEqual( + new Uint8Array([ + 181, 66, 141, 90, 213, 58, 137, 158, 160, 57, 109, 252, 51, 227, + 221, 192, 8, 4, 223, 42, 42, 8, 191, 7, 251, 231, 167, 119, 54, + 161, 194, 229, + ]) + ); + }); + it('Throws when network is wrong', () => { + expect(() => + parseWIF('cW6uViWJU7fUUsB44CDaVN3mKe7dAM3Jun8NHUajT3kgavFx91me') + ).toThrow(/testnet/i); + }); +}); + +describe('num to bytes tests', () => { + test('numToBytes', () => { + expect(numToBytes(0n, 8)).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]); + expect(numToBytes(1n, 8)).toStrictEqual([1, 0, 0, 0, 0, 0, 0, 0]); + expect(numToBytes(0n, 4)).toStrictEqual([0, 0, 0, 0]); + expect(numToBytes(1n, 4)).toStrictEqual([1, 0, 0, 0]); + // Little endian order + expect(numToBytes(0xdeadbeefn, 4)).toStrictEqual([ + 0xef, 0xbe, 0xad, 0xde, + ]); + expect(numToBytes(0xdeadbeefn, 8)).toStrictEqual([ + 0xef, 0xbe, 0xad, 0xde, 0, 0, 0, 0, + ]); + }); + + test('numToByteArray', () => { + expect(numToByteArray(0n)).toStrictEqual([0]); + expect(numToByteArray(1n)).toStrictEqual([1]); + expect(numToByteArray(0xdeadbeefn)).toStrictEqual([ + 0xef, 0xbe, 0xad, 0xde, + ]); + expect(numToByteArray(0xdeadbeefdeadbeefn)).toStrictEqual([ + 0xef, 0xbe, 0xad, 0xde, 0xef, 0xbe, 0xad, 0xde, + ]); + }); + + test('numToVarInt', () => { + // Tests taken from https://wiki.bitcoinsv.io/index.php/VarInt + expect(numToVarInt(0n)).toStrictEqual([0]); + expect(numToVarInt(187n)).toStrictEqual([187]); + expect(numToVarInt(255n)).toStrictEqual([0xfd, 0xff, 0x00]); + expect(numToVarInt(0x3419n)).toStrictEqual([0xfd, 0x19, 0x34]); + expect(numToVarInt(0x80081e5n)).toStrictEqual([ + 0xfe, 0xe5, 0x81, 0x00, 0x08, + ]); + expect(numToVarInt(0x4bf583a17d59c158n)).toStrictEqual([ + 0xff, 0x58, 0xc1, 0x59, 0x7d, 0xa1, 0x83, 0xf5, 0x4b, + ]); + }); + + test('bytesToNum', () => { + expect(bytesToNum([0, 0, 0, 0, 0, 0, 0, 0])).toStrictEqual(0n); + expect(bytesToNum([1, 0, 0, 0, 0, 0, 0, 0])).toStrictEqual(1n); + expect(bytesToNum([0, 0, 0, 0])).toStrictEqual(0n); + expect(bytesToNum([1, 0, 0, 0])).toStrictEqual(1n); + expect(bytesToNum([0xef, 0xbe, 0xad, 0xde])).toStrictEqual(0xdeadbeefn); + expect(bytesToNum([0xef, 0xbe, 0xad, 0xde, 0, 0, 0, 0])).toStrictEqual( + 0xdeadbeefn + ); + }); + + test('varIntToNum', () => { + // Tests taken from https://wiki.bitcoinsv.io/index.php/VarInt + expect(varIntToNum([0])).toStrictEqual({ + readBytes: 1, + num: 0n, + }); + + expect(varIntToNum([187])).toStrictEqual({ + readBytes: 1, + num: 187n, + }); + expect(varIntToNum([0xfd, 0xff, 0x00])).toStrictEqual({ + readBytes: 3, + num: 255n, + }); + expect(varIntToNum([0xfd, 0x19, 0x34])).toStrictEqual({ + readBytes: 3, + num: 0x3419n, + }); + expect(varIntToNum([0xfe, 0xe5, 0x81, 0x00, 0x08])).toStrictEqual({ + readBytes: 5, + num: 0x80081e5n, + }); + expect( + varIntToNum([0xff, 0x58, 0xc1, 0x59, 0x7d, 0xa1, 0x83, 0xf5, 0x4b]) + ).toStrictEqual({ + readBytes: 9, + num: 0x4bf583a17d59c158n, + }); + }); +}); + +describe('Address validation', () => { + const addresses = [ + { addr: 'DSxfioagfTXeCX1teMQNbWnzqWkz5mZNtH', desc: 'p2pkh' }, + { addr: 'EXMDbnWT4K3nWfK1311otFrnYLcFSipp3iez', desc: 'exc' }, + { addr: 'SbqnpKgFRm1zPLHQRRuvuUH4Tyc6Em53xt', desc: 'cold' }, + { + addr: 'ps10g8s4f87fc787e8nzw65men80kqsdmem4uu3yj6zer3uz7ya4m2fjnvc9l5f3009ur6kszfjp34', + desc: 'shield', + }, + { addr: 'DSxfioagfTUeCX1teMQNbWnzqWkz5mZNtH', desc: 'invalid' }, + { addr: 'EXMDbnWT4K3nWfK2311otFrnYLcFSipp3iez', desc: 'invalid' }, + { addr: 'SbqnpKgFRm1zPLHQRRuvuUh4Tyc6Em53xt', desc: 'invalid' }, + { + addr: 'ps10g8s4f87fc78788nzw65men80kqsdmem4uu3yj6zer3uz7ya4m2fjnvc9l5f3009ur6kszfjp34', + desc: 'invalid', + }, + ]; + + it.each(addresses)('recognises shield addresses', (address) => { + expect(isShieldAddress(address.addr)).toBe(address.desc === 'shield'); + }); + it.each(addresses)('recognises p2pkh addresses', (address) => { + expect(isStandardAddress(address.addr)).toBe(address.desc === 'p2pkh'); + }); + it.each(addresses)('recognises exchange addresses', (address) => { + expect(isExchangeAddress(address.addr)).toBe(address.desc === 'exc'); + }); + it.each(addresses)('recognises cold addresses', (address) => { + expect(isColdAddress(address.addr)).toBe(address.desc === 'cold'); + }); + it.each(addresses)('recognises valid addresses', (address) => { + expect(isValidPIVXAddress(address.addr)).toBe( + address.desc !== 'invalid' + ); + }); +}); diff --git a/tests/unit/i18n.spec.js b/tests/unit/i18n.spec.js new file mode 100644 index 000000000..568c7ac64 --- /dev/null +++ b/tests/unit/i18n.spec.js @@ -0,0 +1,12 @@ +import { describe, it, expect } from 'vitest'; +import { getParentLanguage } from '../../scripts/i18n'; + +describe('i18n tests', () => { + it('returns correct parent language', () => { + expect(getParentLanguage('es-ES')).toBe('es'); + expect(getParentLanguage('es')).toBe('en'); + expect(getParentLanguage('en-US')).toBe('en'); + expect(getParentLanguage('it')).toBe('en'); + expect(getParentLanguage('en')).toBe('en'); + }); +}); diff --git a/tests/unit/lockable_function.spec.js b/tests/unit/lockable_function.spec.js new file mode 100644 index 000000000..1de8805f3 --- /dev/null +++ b/tests/unit/lockable_function.spec.js @@ -0,0 +1,32 @@ +import { lockableFunction } from '../../scripts/lock.js'; +import { sleep } from '../../scripts/utils.js'; +import { beforeEach, it } from 'vitest'; + +describe('Lockable function tests', () => { + let test_function; + const sleep_time = 1000; + beforeEach(() => { + test_function = lockableFunction(async (str_input) => { + await sleep(sleep_time); + return str_input; + }); + }); + it('Lockable function returns the correct value', async () => { + expect(await test_function('test_locks')).toBe('test_locks'); + }); + it('Lockable function gives the correct value for the lock', async () => { + // At the beginning there is no lock + expect(test_function.isLocked()).toBeFalsy(); + test_function('test_locks'); + await sleep(sleep_time / 2); + // When the function is running the lock is acquired + expect(test_function.isLocked()).toBeTruthy(); + await sleep(sleep_time / 2); + // When the function stop running the lock is dropped + expect(test_function.isLocked()).toBeFalsy(); + }); + it("Calling when locked doesn't make the function run twice", async () => { + test_function('test_locks'); + expect(await test_function('test_locks')).toBeUndefined(); + }); +}); diff --git a/tests/unit/masterkey.spec.js b/tests/unit/masterkey.spec.js new file mode 100644 index 000000000..073e76464 --- /dev/null +++ b/tests/unit/masterkey.spec.js @@ -0,0 +1,249 @@ +import { getLegacyMainnet, getLegacyTestnet } from '../utils/test_utils.js'; +import { HdMasterKey } from '../../scripts/masterkey.js'; +import { mnemonicToSeed } from 'bip39'; +import { verifyPubkey } from '../../scripts/encoding.js'; +import { cChainParams } from '../../scripts/chain_params.js'; + +async function getHdKeyBySeed() { + return new HdMasterKey({ + seed: await mnemonicToSeed( + 'high fruit sick panther hospital place robust seminar maze benefit shoe bleak' + ), + }); +} +function getHdKeyByXpriv() { + return new HdMasterKey({ + xpriv: 'xprv9s21ZrQH143K3T6FKuAJ8fZ4CrnPjPGUMDA38wmpyazxn8aNrCyebV4wh9LiD5oZyQmC5zaTDSjacWgpJ4PGzkQgFK9n1AAebt4shda53wK', + }); +} +function getHdKeyByXpub(testnet = false) { + return new HdMasterKey({ + xpub: testnet + ? 'xpub6BsLmaiXvY1vKDpkVv39aqWHVJYiL7ft9LS1SKwJRzv7UW5gGRCMMqBSJsqJPVQuV6k6aa8Zt8vidVTdTJotHGzaaEqFag9bMwbNWHfW86q' + : 'xpub6D5vhgPg8E8gykS6mHhquBey5bVRmu96xedXKqcm2wNY2orePTj2oBtkgAGvm75jCNTNvRPXtXM7Kbi5trEQ4YGyqf4STGVm4pD62w1zgzM', + }); +} + +describe('mainnet tests', () => { + beforeAll(() => { + cChainParams.current = cChainParams.main; + }); + test('Legacy master key', async () => { + const l = getLegacyMainnet(); + expect(await l.getAddress()).toBe('DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC'); + expect(await l.getKeyToExport()).toBe( + 'DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC' + ); + expect(l.keyToBackup).toBe( + 'YU12G8Y9LwC3wb2cwUXvvg1iMvBey1ibCF23WBAapCuaKhd6a4R6' + ); + expect(async () => { + l.getxpub(); + }).rejects.toThrow(/extended public key/i); + }); + + test('Hd keys basic properties', async () => { + const s = await getHdKeyBySeed(); + const pr = getHdKeyByXpriv(); + const pu = getHdKeyByXpub(); + const allKeys = [s, pr, pu]; + expect(allKeys.every((k) => !k.isHardwareWallet)).toBe(true); + expect(allKeys.every((k) => k.isHD)).toBe(true); + expect(s.isViewOnly).toBe(false); + expect(pr.isViewOnly).toBe(false); + expect(pu.isViewOnly).toBe(true); + }); + + test('Hd master key are all the same', async () => { + const s = await getHdKeyBySeed(); + const pr = getHdKeyByXpriv(); + const pu = getHdKeyByXpub(); + + expect(s.getKeyToExport(0)).toBe( + 'xpub6D5vhgPg8E8gykS6mHhquBey5bVRmu96xedXKqcm2wNY2orePTj2oBtkgAGvm75jCNTNvRPXtXM7Kbi5trEQ4YGyqf4STGVm4pD62w1zgzM' + ); + expect(s.getKeyToExport(0)).toBe(pr.getKeyToExport(0)); + expect(s.getKeyToExport(0)).toBe(pu.getKeyToExport(0)); + + expect(s.keyToBackup).toBe( + 'xprv9s21ZrQH143K3T6FKuAJ8fZ4CrnPjPGUMDA38wmpyazxn8aNrCyebV4wh9LiD5oZyQmC5zaTDSjacWgpJ4PGzkQgFK9n1AAebt4shda53wK' + ); + expect(s.keyToBackup).toBe(pr.keyToBackup); + expect(() => pu.keyToBackup).toThrow(/view only/i); + }); + test('Correct derivation of HdKey', async () => { + const keys = [ + await getHdKeyBySeed(), + getHdKeyByXpriv(), + getHdKeyByXpub(), + ]; + // Taken from https://iancoleman.io/bip39/ + // Map nth account -> first 5 addresses + const addresses = { + 0: [ + 'DR658aDrQviYy3rDUGM1Mzzi12WHwqQDpE', + 'DDdiX27tHmaaZqFHXkz1SnnmK81DzFJVca', + 'DEMqSgPEvqGAhWvq11oVymyLT6aJK5NQNx', + 'DFzV4XA2KdQJx4oYJw97VoSxN21xNvqwBc', + 'DCrtxnTx7UFtx1HfKLVNFXmKvcdcoepQh1', + ], + 1: [ + 'DF9Q8himXERhrqwUq4pGQkSkunkRXJiMLk', + 'DAhRJMT3u3dF5Y1QDBoBQq9fDLRNonW68z', + 'DM7hXW79nCf9FjcF8aH5oAFYfRTSQD5sck', + 'DMQRXhooJcFwE8tgnMsQBfrc4BQD2UBKd8', + 'D6nhBBnAzBhHpaqAxmiJDMph8HByM92ovX', + ], + 2: [ + 'DFEDsA5WfRSrv5FEcHtUs9u1xdksxiLYkC', + 'DN2Nj8KHyo4fP4bshXemoYfMhX7cJpE8xq', + 'DRtGvuVRFhdHtNs2skFWT52cWNN6BuPJM2', + 'D5dfPtyZp1Bh9pyEutSL43kcw2YEBhjwLf', + 'DT6Wn5rRq2fsKfMS4cdvq9EiUPwT2SGpmV', + ], + }; + for (const key of keys) { + for (const account of Object.keys(addresses)) { + /** + * @type{0|1|2} + */ + const a = Number.parseInt(account); + for (const [i, address] of addresses[a].entries()) { + // view only key has been derived using the 0th account, can't derive others + if (a !== 0 && key.isViewOnly) { + continue; + } + expect(key.getAddress(key.getDerivationPath(a, 0, i))).toBe( + address + ); + } + } + } + }); + test('Correct Base58Check validation of addresses', () => { + const arrTestAddresses = [ + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', // VALID + 'tLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', // BAD + 'TLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', // BAD + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4BB', // BAD + 'DLabs zGMnsK5K9uRTMCF6NoYNY6ET4Bb', // BAD + 'i55j', // BAD + '', // BAD + 'yJ9zhqrwEj7VAjxJZEWqEEtoWQaHK2NKye', // BAD (Testnet Address) + ]; + + // Test verifying each address and expect that each of them follow the above validity table + for (let i = 0; i < arrTestAddresses.length; i++) { + const address = arrTestAddresses[i]; + const isValid = verifyPubkey(address); + // Only the first address should be valid + expect(isValid).toBe(i === 0); + } + }); +}); + +describe('testnet tests', () => { + beforeAll(() => { + cChainParams.current = cChainParams.testnet; + }); + test('Legacy master key', async () => { + const l = getLegacyTestnet(); + expect(await l.getAddress()).toBe('yKHdgqedD1xosww724TnEvohVqnNp4S3pP'); + expect(await l.getKeyToExport()).toBe( + 'yKHdgqedD1xosww724TnEvohVqnNp4S3pP' + ); + expect(l.keyToBackup).toBe( + 'cW6uViWJU7fUUsB44CDaVN3mKe7dAM3Jun8NHUajT3kgavFx91me' + ); + expect(async () => { + await l.getxpub(); + }).rejects.toThrow(/extended public key/i); + }); + test('Hd master key are all the same', async () => { + const s = await getHdKeyBySeed(); + const pr = getHdKeyByXpriv(); + const pu = getHdKeyByXpub(true); + + expect(s.getKeyToExport(0)).toBe( + 'xpub6BsLmaiXvY1vKDpkVv39aqWHVJYiL7ft9LS1SKwJRzv7UW5gGRCMMqBSJsqJPVQuV6k6aa8Zt8vidVTdTJotHGzaaEqFag9bMwbNWHfW86q' + ); + expect(s.getKeyToExport(0)).toBe(pr.getKeyToExport(0)); + expect(s.getKeyToExport(0)).toBe(pu.getKeyToExport(0)); + + expect(s.keyToBackup).toBe( + 'xprv9s21ZrQH143K3T6FKuAJ8fZ4CrnPjPGUMDA38wmpyazxn8aNrCyebV4wh9LiD5oZyQmC5zaTDSjacWgpJ4PGzkQgFK9n1AAebt4shda53wK' + ); + expect(s.keyToBackup).toBe(pr.keyToBackup); + expect(() => pu.keyToBackup).toThrow(/view only/i); + }); + + test('Correct derivation of HdKey', async () => { + const keys = [ + await getHdKeyBySeed(), + getHdKeyByXpriv(), + getHdKeyByXpub(true), + ]; + // Taken from https://iancoleman.io/bip39/ + // Map nth account -> first 5 addresses + const addresses = { + 0: [ + 'y5eBXdy8fhXAHFuYu3JgirBunz1SsvyQ7g', + 'yFDsWerVPm2TzekdpmuACNz2tRsCudnYmU', + 'y4Swao25gHXSELKTzn5F5is9GytZm4kkJt', + 'yBrsVZg3a3bJEoVCGTcppX2SFkGPJy7Duv', + 'y9upM38TYo79boE48NdvkdQ2Hyi2JJekRo', + ], + 1: [ + 'yAFRMPnsnCQbxRKFx6u1GhKQLafX1EHLTx', + 'yHQrYayVZxYQhBbhPxNu6C4HBSjt8AScTT', + 'xzFHoPegqHquLE8W2DMMobeRnWq11bVvMB', + 'y1bWJZST917SvrSH6ynT3keD4D3ob3VyXD', + 'xwpxLcrDywZboafRNTVvWjDXqNrw6iFDtm', + ], + 2: [ + 'y9mY3ku8SxEYEWYCK8Q1B2Sskg3pzBhBzP', + 'yDY276awC1Hg43Nxr3EuoMvHakMsX31Bgy', + 'yEKycCqkDqQs2D6DgAJWpZsN3WqmAzS5dA', + 'y5ZVwmyrBmmd4WaQjM7enqZGWT8gYYFA8P', + 'xw3MUT7M69VTdGb2WTEwMEVnFxxyrZU5GR', + ], + }; + for (const key of keys) { + for (const account of Object.keys(addresses)) { + /** + * @type{0|1|2} + */ + const a = Number.parseInt(account); + for (const [i, address] of addresses[a].entries()) { + // view only key has been derived using the 0th account, can't derive others + if (a !== 0 && key.isViewOnly) { + continue; + } + expect(key.getAddress(key.getDerivationPath(a, 0, i))).toBe( + address + ); + } + } + } + }); + test('Correct Base58Check validation of addresses', () => { + const arrTestAddresses = [ + 'yJ9zhqrwEj7VAjxJZEWqEEtoWQaHK2NKye', // VALID + 'tJ9zhqrwEj7VAjxJZEWqEEtoWQaHK2NKye', // BAD + 'DJ9zhqrwEj7VAjxJZEWqEEtoWQaHK2NKye', // BAD + 'yJ9zhqrwEj7VAjxJZEWqEEtoWQaHK2NKyE', // BAD + 'yJ9zh wEj7VAjxJZEWqEEtoWQaHK2NKye', // BAD + 'i55j', // BAD + '', // BAD + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', // BAD (Mainnet Address) + ]; + + // Test verifying each address and expect that each of them follow the above validity table + for (let i = 0; i < arrTestAddresses.length; i++) { + const address = arrTestAddresses[i]; + const isValid = verifyPubkey(address); + // Only the first address should be valid + expect(isValid).toBe(i === 0); + } + }); +}); diff --git a/tests/unit/mempool.spec.js b/tests/unit/mempool.spec.js new file mode 100644 index 000000000..2294f7883 --- /dev/null +++ b/tests/unit/mempool.spec.js @@ -0,0 +1,210 @@ +import { it, describe, beforeEach, expect, vi } from 'vitest'; + +import { + Transaction, + CTxOut, + COutpoint, + UTXO, + CTxIn, +} from '../../scripts/transaction.js'; +import { Mempool, OutpointState } from '../../scripts/mempool.js'; + +describe('mempool tests', () => { + /** @type{Mempool} */ + let mempool; + let tx; + let txBlockHeight = 1000; + beforeEach(() => { + mempool = new Mempool(); + tx = new Transaction({ + version: 1, + vin: [], + vout: [ + new CTxOut({ + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4992400, + }), + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }), + ], + blockHeight: txBlockHeight, + }); + mempool.addTransaction(tx); + mempool.setOutpointStatus( + new COutpoint({ txid: tx.txid, n: 0 }), + OutpointState.OURS | OutpointState.P2PKH + ); + mempool.setOutpointStatus( + new COutpoint({ txid: tx.txid, n: 1 }), + OutpointState.OURS | OutpointState.P2PKH + ); + }); + it('gets UTXOs correctly', () => { + let expectedUTXOs = [ + new UTXO({ + outpoint: new COutpoint({ txid: tx.txid, n: 0 }), + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4992400, + }), + new UTXO({ + outpoint: new COutpoint({ txid: tx.txid, n: 1 }), + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }), + ]; + + // By default, it should return all UTXOs + expect(mempool.getUTXOs()).toStrictEqual(expectedUTXOs); + + // With target, should only return the first one + expect( + mempool.getUTXOs({ + target: 4000000, + }) + ).toStrictEqual([expectedUTXOs[0]]); + + mempool.setSpent(new COutpoint({ txid: tx.txid, n: 0 })); + // After spending one UTXO, it should not return it again + expect(mempool.getUTXOs()).toStrictEqual([expectedUTXOs[1]]); + mempool.setSpent(new COutpoint({ txid: tx.txid, n: 1 })); + expect(mempool.getUTXOs()).toHaveLength(0); + + [0, 1].forEach((n) => + mempool.removeOutpointStatus( + new COutpoint({ txid: tx.txid, n }), + OutpointState.SPENT + ) + ); + mempool.addOutpointStatus( + new COutpoint({ txid: tx.txid, n: 1 }), + OutpointState.LOCKED + ); + // any LOCKED UTXOs is removed + expect(mempool.getUTXOs()).toStrictEqual([expectedUTXOs[0]]); + }); + + it('gets correct balance', () => { + const confirmedHeight = txBlockHeight + 100; + expect(mempool.getBalance(confirmedHeight)).toBe(4992400 + 5000000); + // Subsequent calls should be cached + expect(mempool.getBalance(confirmedHeight)).toBe(4992400 + 5000000); + expect(mempool.getColdBalance(confirmedHeight)).toBe(0); + expect(mempool.getImmatureBalance(confirmedHeight)).toBe(0); + + mempool.setSpent(new COutpoint({ txid: tx.txid, n: 0 })); + expect(mempool.getBalance(confirmedHeight)).toBe(5000000); + mempool.setSpent(new COutpoint({ txid: tx.txid, n: 1 })); + expect(mempool.getBalance(confirmedHeight)).toBe(0); + }); + + it('gives correct debit', () => { + const spendTx = new Transaction({ + version: 1, + vin: [ + new CTxIn({ + scriptSig: 'dummy', + outpoint: new COutpoint({ + txid: tx.txid, + n: 1, + }), + }), + new CTxIn({ + scriptSig: 'dummy', + outpoint: new COutpoint({ + txid: tx.txid, + n: 0, + }), + }), + ], + vout: [], + }); + mempool.addTransaction(spendTx); + expect(mempool.getDebit(spendTx)).toBe(5000000 + 4992400); + + expect(mempool.getDebit(new Transaction())).toBe(0); + }); + + it('gives correct credit', () => { + expect(mempool.getCredit(tx)).toBe(5000000 + 4992400); + + // Result should stay the same even if the UTXOs are spent + mempool.setSpent(new COutpoint({ txid: tx.txid, n: 1 })); + expect(mempool.getCredit(tx)).toBe(5000000 + 4992400); + mempool.setSpent(new COutpoint({ txid: tx.txid, n: 0 })); + expect(mempool.getCredit(tx)).toBe(5000000 + 4992400); + expect(mempool.getCredit(new Transaction())).toBe(0); + }); + + it('marks outpoint as spent correctly', () => { + const o = [0, 1].map((n) => new COutpoint({ txid: tx.txid, n })); + expect(o.map((out) => mempool.isSpent(out))).toStrictEqual([ + false, + false, + ]); + mempool.setSpent(o[0]); + expect(o.map((out) => mempool.isSpent(out))).toStrictEqual([ + true, + false, + ]); + mempool.setSpent(o[1]); + expect(o.map((out) => mempool.isSpent(out))).toStrictEqual([ + true, + true, + ]); + }); + + it('returns transactions', () => { + expect(mempool.getTransactions()).toStrictEqual([tx]); + }); + + it('correctly handles statuses', () => { + const o = new COutpoint({ txid: tx.txid, n: 0 }); + expect(mempool.getOutpointStatus(o)).toBe( + OutpointState.P2PKH | OutpointState.OURS + ); + // Remove removes one status + mempool.removeOutpointStatus(o, OutpointState.P2PKH); + expect(mempool.getOutpointStatus(o)).toBe(OutpointState.OURS); + mempool.addOutpointStatus(o, OutpointState.P2CS); + expect(mempool.getOutpointStatus(o)).toBe( + OutpointState.P2CS | OutpointState.OURS + ); + // Adding 0 should do nothing + mempool.addOutpointStatus(o, 0); + expect(mempool.getOutpointStatus(o)).toBe( + OutpointState.P2CS | OutpointState.OURS + ); + // Removing 0 should do nothing + mempool.removeOutpointStatus(o, 0); + expect(mempool.getOutpointStatus(o)).toBe( + OutpointState.P2CS | OutpointState.OURS + ); + // Set should override the status + mempool.setOutpointStatus(o, OutpointState.SPENT); + expect(mempool.getOutpointStatus(o)).toBe(OutpointState.SPENT); + // Add should work with multiple flags + mempool.addOutpointStatus(o, OutpointState.P2CS | OutpointState.OURS); + expect(mempool.getOutpointStatus(o)).toBe( + OutpointState.P2CS | OutpointState.SPENT | OutpointState.OURS + ); + // Adding an already set flag should do nothing + mempool.addOutpointStatus(o, OutpointState.SPENT); + expect(mempool.getOutpointStatus(o)).toBe( + OutpointState.P2CS | OutpointState.SPENT | OutpointState.OURS + ); + // Remove should work with multiple flags + mempool.removeOutpointStatus( + o, + OutpointState.LOCKED | OutpointState.P2CS | OutpointState.SPENT + ); + expect(mempool.getOutpointStatus(o)).toBe(OutpointState.OURS); + // Removing a non set flag should do nothing + mempool.removeOutpointStatus(o, OutpointState.LOCKED); + expect(mempool.getOutpointStatus(o)).toBe(OutpointState.OURS); + // Removing MAX_SAFE_INTEGER should remove everything + mempool.removeOutpointStatus(o, Number.MAX_SAFE_INTEGER); + expect(mempool.getOutpointStatus(o)).toBe(0); + }); +}); diff --git a/tests/unit/parsed_secret.json b/tests/unit/parsed_secret.json new file mode 100644 index 000000000..9a2c4b487 --- /dev/null +++ b/tests/unit/parsed_secret.json @@ -0,0 +1,78 @@ +[ + { + "secret": "xprv9s21ZrQH143K4842CrnVAqMTubBcVBjFghHFiNfb2tbceBcn5ac3HaKRLL1yFbFxD9V7KbHRGjnnbDaZRAn1zKSPNmZzCu2gVGoCVsmpcUd", + "password": "", + "expected": { + "xpriv": "xprv9s21ZrQH143K4842CrnVAqMTubBcVBjFghHFiNfb2tbceBcn5ac3HaKRLL1yFbFxD9V7KbHRGjnnbDaZRAn1zKSPNmZzCu2gVGoCVsmpcUd" + } + }, + { + "secret": [ + "display item diagram flip slogan firm illness measure staff shrimp absent abandon", + "display item diagram flip slogan firm illness measure staff shrimp absent abandon ", + " display item diagram flip slogan firm illness measure staff shrimp absent abandon", + " \ndisplay item diagram flip \tslogan firm illness \tmeasure staff shrimp absent abandon\n\t" + ], + "password": "", + "expected": { + "xpriv": "xprv9s21ZrQH143K35E747GySrQzeXGAS1rsfCyfMjEyFAczB4Xwy6phKaE5ps9FxcswDAaFqamX3BEAR6CePBKZqHGK5mtjUtMJ3EzevA773h6", + "seed": "2e92a1b65e77b6f1e5a77b9a5bab3be47e47cc8286a04f863d9d7829300b4f944037b9472c9b8d578dc966c30f3d7ac81021b6cbb783f1453612d8ebccd6c4ed" + } + }, + { + "secret": "display item diagram flip slogan firm illness measure staff shrimp absent abandon", + "password": "123456", + "expected": { + "xpriv": "xprv9s21ZrQH143K2LMsnBWQyzuwJWqeTagZ8i3hDwRNhYPVauTTmw3gmqDYDaZSthdLJ6HhvKrp6qAP4xEC3SvzofZmV4XSUPo3B7WE1JWEt8Y", + "seed": "9e867a891a3420fbf2e2ee5c760cea1b27f5d920a009b6ae7582466f2b6149537be6e7f87489f11bd929e284c49e4ca98c592a8c84147f203e75ce71a0be80df" + } + }, + { + "secret": "xpub6CsavE7yYKNdVzdFk8uJZtMhzVcb2EjAeen6RV6BySjbWHjy53bziWd7RYpGoydH2F7azeQfxfLoZMyqK89vXgNsJtP6TcpzMZ1pey2HnAE", + "password": "", + "expected": { + "xpub": "xpub6CsavE7yYKNdVzdFk8uJZtMhzVcb2EjAeen6RV6BySjbWHjy53bziWd7RYpGoydH2F7azeQfxfLoZMyqK89vXgNsJtP6TcpzMZ1pey2HnAE" + } + }, + { + "secret": "DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb", + "password": "", + "expected": { + "xpub": "DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb" + } + }, + { + "secret": "YVgYP7qfwtBGmtRb8radKwdVh8VxwHH1GNFTTMJxr8LyaEZDrtw3", + "password": "", + "expected": { + "xpriv": "YVgYP7qfwtBGmtRb8radKwdVh8VxwHH1GNFTTMJxr8LyaEZDrtw3" + } + }, + { + "secret": "{\"mk\":\"YVgYP7qfwtBGmtRb8radKwdVh8VxwHH1GNFTTMJxr8LyaEZDrtw3\"}", + "password": "", + "expected": { + "xpriv": "YVgYP7qfwtBGmtRb8radKwdVh8VxwHH1GNFTTMJxr8LyaEZDrtw3" + } + }, + { + "secret": "{\"mk\":\"xprv9s21ZrQH143K4NxC78XEV4KUNRkiKyCgqbZotxoTVFPYNQtTjamRHcJde9uN34evLyATmEJZNUPF7wRLbHimH3cnAdc42EvRkQMczGVdVeV\",\"shield\":\"p-secret-spending-key-main1qwynqcz3qqqqpqrsy02v0pn4068y5j2586zzr35pjvhlmfhg6s6h400s9wgyw6kzhnkg8urj7t0l7qap56glm8gaj9zlvxv64nvw86eveg37mfnvrw4qp86hm66v4pqq8mw620wx426zf9halmk2g248t8l7gy0mxphgg4sxefwfqyhfxpkwwrcr9f5psj87h2hgq5fqlg2hnfwztnnheq6cx5txm5sxx7hqze0me2qlfcrakkq9xeywf62dcg7hfhdqetelggugzzcnz6703\"}", + "password": "", + "expected": { + "xpriv": "xprv9s21ZrQH143K4NxC78XEV4KUNRkiKyCgqbZotxoTVFPYNQtTjamRHcJde9uN34evLyATmEJZNUPF7wRLbHimH3cnAdc42EvRkQMczGVdVeV", + "extsk": "p-secret-spending-key-main1qwynqcz3qqqqpqrsy02v0pn4068y5j2586zzr35pjvhlmfhg6s6h400s9wgyw6kzhnkg8urj7t0l7qap56glm8gaj9zlvxv64nvw86eveg37mfnvrw4qp86hm66v4pqq8mw620wx426zf9halmk2g248t8l7gy0mxphgg4sxefwfqyhfxpkwwrcr9f5psj87h2hgq5fqlg2hnfwztnnheq6cx5txm5sxx7hqze0me2qlfcrakkq9xeywf62dcg7hfhdqetelggugzzcnz6703" + } + }, + { + "secret": "XHfwRfUnF+bRBZD3HuJSrK3cGFGv83TZzcTzuBPDYjrC+FYpDaSUaxToNgo+uDoBOgZkNyWeNcYJcgZ1hyNHZp2LRqBChAixevvj0yB6vr5m1pNkLeLAoJHpXSj/G7Kfhx7i3EkN7Q2U6V42hHc6LnSvBAnWcJFfZ+S3X8pzaFXmkouvXJoNDUyarI8le7sUmNWBD59YH8u9vKl6toVVRT6fUX26LG49axZg8gOpa7bAIkDa+7cEciJfAjoX9SnacV0ucayReksT1cOcyMAe8wp5atr+GOmd8f7816i92jCzCafnqRGieCIWaYxplMZJyMky7m7S+9q6cEi2L2Rtijua2YA1aVLljtcOXnG0d/Ka4E0f2xntnW3MDeFCheqkKOINGAtQtUDiAKw6FjOjpN7PpkviDMIian+Nhpz4q0mgzdG1aWczpjmTu+NC2x5YmzT0y2cqwolx0yOEm2Fd4nMYWk5culv5jVRE6UJ8+dZCn3VbQW+lE5KuAcW2dqojrpdzPZNx7hFi8rH+ICVMHnugnaFLxk7+T9Bb/Vn0bFTJt/9iCylFvu+59bUpMO741wj7gYXLT97dTYs3OSOGSIvjf9YLb9b3sKuoZR1UMHt66TmhIOh4mGPj5XTKIWfs", + "password": "654321", + "expected": { + "xpriv": "xprv9s21ZrQH143K4NxC78XEV4KUNRkiKyCgqbZotxoTVFPYNQtTjamRHcJde9uN34evLyATmEJZNUPF7wRLbHimH3cnAdc42EvRkQMczGVdVeV", + "extsk": "p-secret-spending-key-main1qwynqcz3qqqqpqrsy02v0pn4068y5j2586zzr35pjvhlmfhg6s6h400s9wgyw6kzhnkg8urj7t0l7qap56glm8gaj9zlvxv64nvw86eveg37mfnvrw4qp86hm66v4pqq8mw620wx426zf9halmk2g248t8l7gy0mxphgg4sxefwfqyhfxpkwwrcr9f5psj87h2hgq5fqlg2hnfwztnnheq6cx5txm5sxx7hqze0me2qlfcrakkq9xeywf62dcg7hfhdqetelggugzzcnz6703" + } + }, + { + "secret": ["invalidsecret", "{\"invalid\":\"secret\"}"], + "expected": {} + } +] diff --git a/tests/unit/parsed_secret.spec.js b/tests/unit/parsed_secret.spec.js new file mode 100644 index 000000000..7b3d6bc2b --- /dev/null +++ b/tests/unit/parsed_secret.spec.js @@ -0,0 +1,53 @@ +import { describe, it, vi, beforeEach, afterAll } from 'vitest'; +import { ParsedSecret } from '../../scripts/parsed_secret.js'; +import secretTestCases from './parsed_secret.json'; +import * as pivxShield from 'pivx-shield'; +import { bytesToHex } from '../../scripts/utils.js'; + +vi.mock('pivx-shield', () => { + return { + PIVXShield: { + create: vi.fn(), + }, + }; +}); + +describe('Parsed secret tests', () => { + beforeEach(() => { + pivxShield.PIVXShield.create = vi.fn(); + }); + it.each(secretTestCases)( + 'parses secret $secret', + async ({ secret: secrets, password, expected }) => { + if (!Array.isArray(secrets)) { + secrets = [secrets]; + } + for (const secret of secrets) { + const parsedSecret = await ParsedSecret.parse(secret, password); + if (expected.xpriv) + expect(parsedSecret.masterKey?.keyToBackup ?? null).toBe( + expected.xpriv + ); + if (expected.xpub) + expect( + parsedSecret.masterKey?.getKeyToExport(0) ?? null + ).toBe(expected.xpub); + const { seed, extendedSpendingKey: extsk } = + pivxShield.PIVXShield.create.mock.calls.at(-1)?.at(0) ?? {}; + + if (seed) expect(bytesToHex(seed)).toBe(expected.seed); + else { + expect(expected.seed).toBeUndefined(); + } + if (extsk) expect(extsk).toBe(expected.extsk); + else { + expect(expected.extsk).toBeUndefined(); + } + } + } + ); + afterAll(() => { + vi.clearAllMocks(); + vi.resetAllMocks(); + }); +}); diff --git a/tests/unit/polyfill.spec.js b/tests/unit/polyfill.spec.js new file mode 100644 index 000000000..e0ba736e4 --- /dev/null +++ b/tests/unit/polyfill.spec.js @@ -0,0 +1,61 @@ +import { + createHash, + createHmac, + randomBytes, +} from '../../scripts/polyfills/crypto.js'; +import { describe, it, vi } from 'vitest'; +import { randomBytes as nobleRandomBytes } from '@noble/hashes/utils'; +import { Buffer } from 'buffer'; + +vi.mock('@noble/hashes/utils', () => { + const randomBytes = vi.fn( + (length) => new Uint8Array(Array(length).fill(0xde)) + ); + return { randomBytes }; +}); + +describe('polyfill tests', () => { + it('creates sha256 hash', () => { + const hash = createHash('sha256'); + const res = hash.update(Buffer.from([1, 2, 3])).digest(); + expect([...res]).toStrictEqual([ + 3, 144, 88, 198, 242, 192, 203, 73, 44, 83, 59, 10, 77, 20, 239, + 119, 204, 15, 120, 171, 204, 206, 213, 40, 125, 132, 161, 162, 1, + 28, 251, 129, + ]); + + expect(res).toBeInstanceOf(Buffer); + }); + + it('creates sha1 hash', () => { + const hash = createHash('sha1'); + const res = hash.update(Buffer.from([1, 2, 3])).digest(); + expect([...res]).toStrictEqual([ + 112, 55, 128, 113, 152, 194, 42, 125, 43, 8, 7, 55, 29, 118, 55, + 121, 168, 79, 223, 207, + ]); + + expect(res).toBeInstanceOf(Buffer); + }); + it('creates hmac of sha512', () => { + const hash = createHmac('sha512', new Uint8Array([1])); + const res = hash.update(Buffer.from([1, 2, 3])).digest(); + expect([...res]).toStrictEqual([ + 42, 165, 188, 179, 149, 47, 231, 243, 195, 184, 211, 105, 0, 174, + 117, 166, 83, 153, 157, 205, 30, 101, 126, 120, 164, 115, 20, 51, + 27, 100, 96, 233, 201, 65, 213, 234, 78, 156, 209, 19, 237, 238, + 246, 102, 167, 244, 0, 8, 167, 49, 235, 41, 58, 41, 200, 142, 25, + 206, 236, 48, 209, 152, 148, 11, + ]); + + expect(res).toBeInstanceOf(Buffer); + }); + it('generates random bytes', async () => { + const res = randomBytes(10); + expect([...res]).toStrictEqual([ + 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, + ]); + expect(res).toBeInstanceOf(Buffer); + expect(nobleRandomBytes).toBeCalled(1); + }); +}); diff --git a/tests/unit/start_batch.test.js b/tests/unit/start_batch.test.js new file mode 100644 index 000000000..ca723dab8 --- /dev/null +++ b/tests/unit/start_batch.test.js @@ -0,0 +1,39 @@ +import { startBatch } from '../../scripts/utils.js'; +import { describe, it, test } from 'vitest'; + +describe('Start batch tests', () => { + const basicFunc = async (arrSize, batchSize) => { + const arr = new Array(arrSize).fill(0).map((_, i) => i * 2); + const promiseFactory = async (i) => { + return arr[i] * 2; + }; + const res = await startBatch(promiseFactory, arr.length, batchSize); + for (let i = 0; i < arr.length; i++) { + expect(res[i]).toBe(arr[i] * 2); + } + }; + test('basic functionality', async () => await basicFunc(100, 8)); + it('works with batchSize > length', async () => basicFunc(4, 500)); + it('works with batchSize = length', async () => basicFunc(500, 500)); + + it('correctly handles errors', async () => { + const arr = new Array(100).fill(0).map((_, i) => i * 2); + // Simulated fail calls with a ~80% success rate + const failCalls = [ + 5, 13, 16, 18, 20, 28, 29, 34, 40, 41, 51, 58, 65, 67, 70, 71, 73, + 81, 88, 91, 94, 97, 100, 105, 112, 115, 117, 119, 127, 128, 130, + ]; + let counter = 0; + const promiseFactory = async (i) => { + counter++; + if (failCalls.includes(counter)) { + throw new Error(':('); + } + return arr[i] * 2; + }; + const res = await startBatch(promiseFactory, arr.length, 8, 50); + for (let i = 0; i < arr.length; i++) { + expect(res[i]).toBe(arr[i] * 2); + } + }); +}); diff --git a/tests/unit/transaction.spec.js b/tests/unit/transaction.spec.js new file mode 100644 index 000000000..739a694da --- /dev/null +++ b/tests/unit/transaction.spec.js @@ -0,0 +1,144 @@ +import { describe, it, test, vi, beforeAll, expect } from 'vitest'; +import { + Transaction, + CTxIn, + CTxOut, + COutpoint, +} from '../../scripts/transaction.js'; +import { hexToBytes } from '../../scripts/utils.js'; +import testVector from './transaction.test.json'; +import * as encoding from '../../scripts/encoding.js'; + +testVector = testVector.map(([tx, txid, hex, wif]) => [ + new Transaction({ + version: tx.version, + vin: tx.vin.map( + (input) => + new CTxIn({ + outpoint: new COutpoint({ + txid: input.outpoint.txid, + n: input.outpoint.n, + }), + scriptSig: input.scriptSig, + sequence: input.sequence, + }) + ), + vout: tx.vout.map( + (output) => + new CTxOut({ + script: output.script, + value: output.value, + }) + ), + valueBalance: tx.valueBalance, + shieldSpend: tx.shieldSpend, + shieldOutput: tx.shieldOutput, + shieldData: Array.from(hexToBytes(tx.shieldData ?? '')), + bindingSig: tx.bindingSig, + lockTime: tx.lockTime, + }), + txid, + hex, + wif, +]); + +describe('transaction tests', () => { + beforeAll(() => { + const oldParseWIF = encoding.parseWIF; + // Make parseWIF skip verification, we don't care about that here + vi.spyOn(encoding, 'parseWIF').mockImplementation((strWIF) => + oldParseWIF(strWIF, true) + ); + return vi.restoreAllMocks; + }); + test('Coinstake/Coinbase detection work', () => { + expect(testVector.map(([t]) => t.isCoinBase())).toStrictEqual([ + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + ]); + expect(testVector.map(([t]) => t.isCoinStake())).toStrictEqual([ + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + ]); + }); + it.each(testVector)('serializes correctly ($txid)', (tx, txid, hex) => { + expect(tx.serialize()).toBe(hex); + expect(tx.txid).toBe(txid); + }); + it.each(testVector)('deserializes correctly ($txid)', (tx, _, hex) => { + const ourTx = Transaction.fromHex(hex); + expect(ourTx).toStrictEqual(tx); + }); + + it.sequential('updates txid when a property changes', () => { + const [tx, txid] = testVector[0]; + const originalVersion = tx.version; + expect(tx.txid).toBe(txid); + tx.version = 10; + expect(tx.txid).not.toBe(txid); + expect(tx.txid).toBe( + '69f75c046610514156fdd816ed562ba8a9f1578008740c0cbd752254a9767d2c' + ); + tx.version = originalVersion; + }); + + it.each(testVector.filter((t) => t[3]))( + 'signs correctly ($txid)', + async (tx, _, hex, inputs) => { + for (let i = 0; i < inputs.length; i++) { + const script = inputs[i][1]; + tx.vin[i].scriptSig = script; + } + for (let i = 0; i < inputs.length; i++) { + const wif = inputs[i][0]; + const isColdStake = !!inputs[i][2]; + await tx.signInput(i, wif, { isColdStake }); + } + expect(tx.serialize()).toBe(hex); + } + ); +}); + +describe('COutpoint tests', () => { + it.each([ + new COutpoint({ + txid: 'c463e9484973e085fac81763f4ba882dad961885890edd02be55b94aa291a739', + n: 123, + }), + new COutpoint({ + txid: '25ffc9a52fe7f4b7b0865d6f5db7aefd17871971ef09fabafe519ceef2174c89', + n: 0, + }), + new COutpoint({ + txid: 'aab36ff5f222ef56f569b5c84d335f522339db8755e5eeae0cee5a447126a9d6', + n: 1, + }), + new COutpoint({ + txid: 'b067b802d4cc673ebf0f124fe4d2304ca5ca7d733fde573011ed29a90dee8e39', + n: 13, + }), + new COutpoint({ + txid: '5be92981ac013a819bd4490aa2f64ef45e4eb0f48abb9e6ed0d5a6cfbf4965f8', + n: 19348113, + }), + ])('converts from and to unique', (outpoint) => { + const unique = outpoint.toUnique(); + expect(COutpoint.fromUnique(unique)).toStrictEqual(outpoint); + }); +}); diff --git a/tests/unit/transaction.test.json b/tests/unit/transaction.test.json new file mode 100644 index 000000000..d696ea9b9 --- /dev/null +++ b/tests/unit/transaction.test.json @@ -0,0 +1,356 @@ +[ + [ + { + "comment": "Regular p2pkh tx", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c", + "n": 1 + }, + "scriptSig": "483045022100a4eac56caaf3700c4f53822fbb858256f3a5c154d268f416ade685de3fe61de202206fb38cfe8fd4faf8b14dc7ac0799c4acfd50a81c4d93509ebd6fb0bca3bb8a7a0121035b57e0afed95b86ad3ccafb9a8c752dc173cea16274cf9dd9b7a43364d36cf38" + } + ], + "vout": [ + { + "script": "76a914f49b25384b79685227be5418f779b98a6be4c73888ac", + "value": 4992400 + }, + { + "script": "76a914a95cc6408a676232d61ec29dc56a180b5847835788ac", + "value": 5000000 + } + ], + "lockTime": 0, + "shieldData": "" + }, + "9cf01cffc85d53b80a9c7ca106fc7326efa0f4f1db3eaf5be0ac45eb6105b8ab", + "01000000017c5d281d04a546f989bd5ff922447c6bf6896416cc9145b6a782c30ad868f9f8010000006b483045022100a4eac56caaf3700c4f53822fbb858256f3a5c154d268f416ade685de3fe61de202206fb38cfe8fd4faf8b14dc7ac0799c4acfd50a81c4d93509ebd6fb0bca3bb8a7a0121035b57e0afed95b86ad3ccafb9a8c752dc173cea16274cf9dd9b7a43364d36cf38ffffffff02902d4c00000000001976a914f49b25384b79685227be5418f779b98a6be4c73888ac404b4c00000000001976a914a95cc6408a676232d61ec29dc56a180b5847835788ac00000000", + [ + [ + "YU12G8Y9LwC3wb2cwUXvvg1iMvBey1ibCF23WBAapCuaKhd6a4R6", + "76a914f49b25384b79685227be5418f779b98a6be4c73888ac" + ] + ] + ], + [ + { + "comment": "Tx with cold stake", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "bf6d18b280a5c68480e9cad557589729668be59bda48375906a04fd9fbf6ff13", + "n": 0 + }, + "scriptSig": "4830450221009de7f40ae52ae9da0fd103c40e3f914654fd699909c8ff9b083983701d807a0c02203d81de9ebca45f067317fcb5c31326cd9aa18734abe015ffd65cfef62f710dde012102ff1cfb54a2ec3de473e3171d0724356f3e80c6522319b521b5454ddd62403a3e" + } + ], + "vout": [ + { + "script": "76a9143232b7bd616dd5ebeefcd216671fe9a7c2f96b2e88ac", + "value": 409978200 + }, + { + "script": "76a97b63d114f912041b9c6d2351a4022cb1e8ee0108ed7239796714c212b614b19765cd544e8c2186fa17d6b8aeb2f16888ac", + "value": 100000000 + } + ], + "lockTime": 0, + "shieldData": "" + }, + "351925a42176fe2b95f1e650532529d5441cb5b4b66ecf29d45b6184f8203dbe", + "010000000113fff6fbd94fa006593748da9be58b6629975857d5cae98084c6a580b2186dbf000000006b4830450221009de7f40ae52ae9da0fd103c40e3f914654fd699909c8ff9b083983701d807a0c02203d81de9ebca45f067317fcb5c31326cd9aa18734abe015ffd65cfef62f710dde012102ff1cfb54a2ec3de473e3171d0724356f3e80c6522319b521b5454ddd62403a3effffffff0258c56f18000000001976a9143232b7bd616dd5ebeefcd216671fe9a7c2f96b2e88ac00e1f505000000003376a97b63d114f912041b9c6d2351a4022cb1e8ee0108ed7239796714c212b614b19765cd544e8c2186fa17d6b8aeb2f16888ac00000000", + null + ], + [ + { + "comment": "Coinstake tx", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "563c3a606121282be03b743a7e714ead79f3fc850032e197883a3b88608ec124", + "n": 1 + }, + "scriptSig": "4730440220773979ad4cac8eb810cc57c8099866f7c2512550b877559a8c2f61e99e1780630220057bb31305908a3d502238d9535b90446721513324df221c4d0805d8681005a001" + } + ], + "vout": [ + { + "script": "", + "value": 0 + }, + { + "script": "2103112df8b7ece0ebdfaa17d13d7d9e4df3ff1261ba107d9f929c6eea633c71bd90ac", + "value": 72056900000 + }, + { + "value": 600000000, + "script": "76a9140363526ab523d61302f8c74305e5891ad8af922388ac" + } + ], + "lockTime": 0, + "shieldData": "" + }, + "a9c4aea4a3b7962ce6d33190f738ee6cf266e5dd3b7061f25fd8f285ae1fabba", + "010000000124c18e60883b3a8897e1320085fcf379ad4e717e3a743be02b282161603a3c5601000000484730440220773979ad4cac8eb810cc57c8099866f7c2512550b877559a8c2f61e99e1780630220057bb31305908a3d502238d9535b90446721513324df221c4d0805d8681005a001ffffffff03000000000000000000a009edc610000000232103112df8b7ece0ebdfaa17d13d7d9e4df3ff1261ba107d9f929c6eea633c71bd90ac0046c323000000001976a9140363526ab523d61302f8c74305e5891ad8af922388ac00000000", + null + ], + [ + { + "comment": "Coinbase tx", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "0000000000000000000000000000000000000000000000000000000000000000", + "n": 4294967295 + }, + "scriptSig": "035e9c3f00" + } + ], + "vout": [ + { + "value": 0, + "script": "" + } + ] + }, + "ae5f760b98070225757b1e21a2e84882ab9f71dff5a6aebde69f5d7ca20be6da", + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05035e9c3f00ffffffff0100000000000000000000000000", + null + ], + [ + { + "comment": "Shield tx", + "version": 3, + "vin": [], + "vout": [ + { + "value": 2500000000, + "script": "76a914ce59419cfde9363da04d999f153357459b7307e888ac" + } + ], + "valueBalance": 2501835000, + "shieldSpend": [ + { + "cv": "66ddd5487718dd4379ac502c49c6bfc15565fb07d0ac6c957f2895fc59fe1f78", + "anchor": "195a062c970597dc61613125f5206636ad745e72d6c1a8ddeb8731d464866902", + "nullifier": "dbb47806aab001478d8d801f4a9d83e9eba2a85ce21514280325d84297084b4d", + "rk": "df2cd185c8e9334a3e454715d2ac8e28fdfe0747ce63556dbf39059ef1e07859", + "proof": "b764610d5317249b978d3020332766a64176c18dc7ab89005ea03b1d438f20f00d4f37c0672418e4b74fb5042d574e04b5ab5df38e9d83274dba8d14dc8d6f36e3a2e890ac7aace1749af1eb263cd4a236c19cdd4d5e727963f2a111f165548513e11d9d99399f32aef9cea82f21e2e58d67357e8f4f55416bc8ce4fb4993fe7812d4565470adf1e8bf6bcf1606cf3d58ed0208f8f52d5d23aeaff5b2945f845828d0e29fa9e5cc60812377da9aaeb152eb92069a5916995e69b837a0b4f9f10", + "spendAuthSig": "bea5fa6e956d391e1fd9433485fe5e7e8f31b2c92e5e7d2060a8efd3706d89e90d247e96abed2b91f61100a6ae95fdfa4b88469c7096d54224aad7a98318c508" + }, + { + "cv": "5be3959fd093e8bc428b825cf1e83c25076871231d562a1f3d8ba92ec1edb463", + "anchor": "195a062c970597dc61613125f5206636ad745e72d6c1a8ddeb8731d464866902", + "nullifier": "4e0017ddee9a1fa017d8f6aac109be67aa883a841c3d81e45295cdbb7187c84d", + "rk": "88f21b900d0b48a7ae03d659e8622a696547b2a0577bce92760fa10c5c8cd773", + "proof": "97388db4c3c750f697cc193e5f1b385f52c1ea2a5e1e9403771ae2c7cda0133f5abdd0f839d62866a73e6772ec66d2e0930c79b9c51de72d93f13130729e3d7edd5fe62e19a8854028aaa64bfdda6aae577a8abd5666d55a833614d0435249430f361d63d17c166a643ae98e3fb06bf8ae07591c93cd83145e3420dc0969b892ff2033328f4e23ce218e3e28f5230888b4ac6983f1fba77a2c5d89a0442a7e4f3bd377e59408f300257529abea93ac081493402ab89dd0a99945829f153db947", + "spendAuthSig": "6c9b7060be67d9cb2c22a4badee80c2915aedaefcefe04e04f5321947b97edd23a25831d11a4dc5ad1d61adcdc400e10d8075f4fbe703ec52f0beeec10b47604" + } + ], + "shieldOutput": [ + { + "cv": "a81cc8b8e7ba64bdc89bf70c52588ded902becda55b5f966907639a6ebd62691", + "cmu": "183ab57b43b842c402274b4c43c7e31ccc6da5ace32e6c6654234c8dc47180d4", + "ephemeralKey": "6d9ace129605902baf878b2f38be29cc2178beb906c1c983636625c3ecc75771", + "encCiphertext": "5196da268d27ad5232426c0010f969327de3fb8104841e17a2ca71706ba74182fdfa90a81afeae1bd23b561dff1c8d7961dfd6ef97fa6dda95909abbdb95f66c946e266eaefa4efae64de30c8f39306ebf9c9642f4b93b5eb6240de67197ac278b482148f37deb15e63d2a9a8a87959a650808e10a6cca75758b006c9855750fdb841c3aae8eb0f314b9f9a6425668ba8386dcaf4ce4344262538e43c3dcd21c45e21c00f1757fcfae39dd9561688257fa7cc04af5514e6443bf015e1cd51d4f843ff14784067dc58501c87121db46713e00f2ffecb391ca830b7f1d8b8f79ed6bc735efada94a497366a46a2858114d3d19d2f40078e19a8aef6ab5b88573534e53e5674f983c8494ed4d213899a184e3cb103acc5605f05cd780b527458948e8ad61ef43d6da53377f709f4271e61a67526ff5ef31f9957789df95c1c50f73707042aad07326e04756cdceff5c98219e4348d0358970c32711fad2ada19280005b737050203793f2debec78e2ab86e87888950af17c745030ff7d9cf86a94f620c5bc201092cd3b03c9abe70ae45c9d811fd90482f3b2f7c9f218325dd2934545fbdabb3fc64cbda30843e7036ab8b56e96b26a1cdb6bae99a0c8f578f4de3794d73d1e262fb6cc48c0faf5e46c5729d8db2fce57be6159fe9dce874effccdee8b729c253a39ec4f34c5b1c734838f67949f162d2b1c2ac056e663926ba50e558b01bdd1936270410123b22be571b49fc774f4e349915ec7cec70b103ee8e9e497279d2d613d07384feadb371ad857a5eef0b74b6956c6e29ab4494c026593dbd2f2ef", + "outCiphertext": "8b4b04fbdd28a441dab042657f5a50a9214cf1ce801e12c750e77c884f1bdfa296f4aaa469f42bec681f187d00659381b7a2017c2e73bcd8ffd322278d0e3813a4aef2b4b2d04113223be1cfe008004a", + "proof": "a618705fad0d840ea6c830b9fb7bc049186750efb567b070f661798466a154009d3a074a23d6b0d91d416d62eb1011e1aeca68f6c39c6e123433f1d77f4216097cc1590c6e43acc969d7033647ae345549cc166ef24d0a93265bccd3edec752f0f91c374f48eb0f55b7abd3e86b9ac5cd2b6becbfb5f62154d7a2d60d47fbf58af2b9715335c7f193d57f50d7d4157b4b02fd2a063238e15e09a0b4562d006c2668be044eca96a2abf0d6fc009e6ceff7e245c0a27b550ff20c5e2f636c0e84d" + } + ], + "bindingSig": "5762b2ed880d1e18c8ed6dd2a2d28915d0482481baa07f7d6a85fd100c50318ad64074c0db077954432cfa65246e2ac23573292db523ac526590fd3d1422d10a" + }, + "40c869d15a0f12b378611ff06347d9de9d50df50b1a093efb00e54ec3c33461a", + "03000000000100f90295000000001976a914ce59419cfde9363da04d999f153357459b7307e888ac0000000001f8f81e950000000002781ffe59fc95287f956cacd007fb6555c1bfc6492c50ac7943dd187748d5dd6602698664d43187ebdda8c1d6725e74ad366620f525316161dc9705972c065a194d4b089742d82503281415e25ca8a2ebe9839d4a1f808d8d4701b0aa0678b4db5978e0f19e0539bf6d5563ce4707fefd288eacd21547453e4a33e9c885d12cdfb764610d5317249b978d3020332766a64176c18dc7ab89005ea03b1d438f20f00d4f37c0672418e4b74fb5042d574e04b5ab5df38e9d83274dba8d14dc8d6f36e3a2e890ac7aace1749af1eb263cd4a236c19cdd4d5e727963f2a111f165548513e11d9d99399f32aef9cea82f21e2e58d67357e8f4f55416bc8ce4fb4993fe7812d4565470adf1e8bf6bcf1606cf3d58ed0208f8f52d5d23aeaff5b2945f845828d0e29fa9e5cc60812377da9aaeb152eb92069a5916995e69b837a0b4f9f10bea5fa6e956d391e1fd9433485fe5e7e8f31b2c92e5e7d2060a8efd3706d89e90d247e96abed2b91f61100a6ae95fdfa4b88469c7096d54224aad7a98318c50863b4edc12ea98b3d1f2a561d23716807253ce8f15c828b42bce893d09f95e35b02698664d43187ebdda8c1d6725e74ad366620f525316161dc9705972c065a194dc88771bbcd9552e4813d1c843a88aa67be09c1aaf6d817a01f9aeedd17004e73d78c5c0ca10f7692ce7b57a0b24765692a62e859d603aea7480b0d901bf28897388db4c3c750f697cc193e5f1b385f52c1ea2a5e1e9403771ae2c7cda0133f5abdd0f839d62866a73e6772ec66d2e0930c79b9c51de72d93f13130729e3d7edd5fe62e19a8854028aaa64bfdda6aae577a8abd5666d55a833614d0435249430f361d63d17c166a643ae98e3fb06bf8ae07591c93cd83145e3420dc0969b892ff2033328f4e23ce218e3e28f5230888b4ac6983f1fba77a2c5d89a0442a7e4f3bd377e59408f300257529abea93ac081493402ab89dd0a99945829f153db9476c9b7060be67d9cb2c22a4badee80c2915aedaefcefe04e04f5321947b97edd23a25831d11a4dc5ad1d61adcdc400e10d8075f4fbe703ec52f0beeec10b47604019126d6eba639769066f9b555daec2b90ed8d58520cf79bc8bd64bae7b8c81ca8d48071c48d4c2354666c2ee3aca56dcc1ce3c7434c4b2702c442b8437bb53a187157c7ecc325666383c9c106b9be7821cc29be382f8b87af2b90059612ce9a6d5196da268d27ad5232426c0010f969327de3fb8104841e17a2ca71706ba74182fdfa90a81afeae1bd23b561dff1c8d7961dfd6ef97fa6dda95909abbdb95f66c946e266eaefa4efae64de30c8f39306ebf9c9642f4b93b5eb6240de67197ac278b482148f37deb15e63d2a9a8a87959a650808e10a6cca75758b006c9855750fdb841c3aae8eb0f314b9f9a6425668ba8386dcaf4ce4344262538e43c3dcd21c45e21c00f1757fcfae39dd9561688257fa7cc04af5514e6443bf015e1cd51d4f843ff14784067dc58501c87121db46713e00f2ffecb391ca830b7f1d8b8f79ed6bc735efada94a497366a46a2858114d3d19d2f40078e19a8aef6ab5b88573534e53e5674f983c8494ed4d213899a184e3cb103acc5605f05cd780b527458948e8ad61ef43d6da53377f709f4271e61a67526ff5ef31f9957789df95c1c50f73707042aad07326e04756cdceff5c98219e4348d0358970c32711fad2ada19280005b737050203793f2debec78e2ab86e87888950af17c745030ff7d9cf86a94f620c5bc201092cd3b03c9abe70ae45c9d811fd90482f3b2f7c9f218325dd2934545fbdabb3fc64cbda30843e7036ab8b56e96b26a1cdb6bae99a0c8f578f4de3794d73d1e262fb6cc48c0faf5e46c5729d8db2fce57be6159fe9dce874effccdee8b729c253a39ec4f34c5b1c734838f67949f162d2b1c2ac056e663926ba50e558b01bdd1936270410123b22be571b49fc774f4e349915ec7cec70b103ee8e9e497279d2d613d07384feadb371ad857a5eef0b74b6956c6e29ab4494c026593dbd2f2ef8b4b04fbdd28a441dab042657f5a50a9214cf1ce801e12c750e77c884f1bdfa296f4aaa469f42bec681f187d00659381b7a2017c2e73bcd8ffd322278d0e3813a4aef2b4b2d04113223be1cfe008004aa618705fad0d840ea6c830b9fb7bc049186750efb567b070f661798466a154009d3a074a23d6b0d91d416d62eb1011e1aeca68f6c39c6e123433f1d77f4216097cc1590c6e43acc969d7033647ae345549cc166ef24d0a93265bccd3edec752f0f91c374f48eb0f55b7abd3e86b9ac5cd2b6becbfb5f62154d7a2d60d47fbf58af2b9715335c7f193d57f50d7d4157b4b02fd2a063238e15e09a0b4562d006c2668be044eca96a2abf0d6fc009e6ceff7e245c0a27b550ff20c5e2f636c0e84d5762b2ed880d1e18c8ed6dd2a2d28915d0482481baa07f7d6a85fd100c50318ad64074c0db077954432cfa65246e2ac23573292db523ac526590fd3d1422d10a" + ], + [ + { + "comment": "Cold stake", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "6e4213309565ec730f752a639d0a552dad85399d6d64488b09957ab6bd8ae1f5", + "n": 1 + }, + "scriptSig": "483045022100c4037315756d0c0a8e9b4b9968a3a63b5607059a98bc687e1a84c774f08a317602207e8e57fea23d6c8edbd92212e6f5edf16f67cdbae0ab348d4d20a0ef3e8e45ac012102814b13a56bfb930565ff9002298d315e50a11e6902aff2a0c334d2c27b1aa89b" + }, + { + "outpoint": { + "txid": "3d922c4320e27a6ba656aea0820e2297c8129ca4bfec1af37dca9cb2f50b21b9", + "n": 1 + }, + "scriptSig": "4830450221008954308d17283bfb6140b06b5d6563a5a615c071295c2b6ae0ddd379935207b7022005b362b220cbab72c3b77baa4a67c95a4e817ceee53a8986dc933de4eac0bd71012102814b13a56bfb930565ff9002298d315e50a11e6902aff2a0c334d2c27b1aa89b" + } + ], + "vout": [ + { + "script": "76a9149139c74239af4ff8279b4675643e9db474bfefe488ac", + "value": 19985800 + }, + { + "script": "76a97b63d114f912041b9c6d2351a4022cb1e8ee0108ed72397967149139c74239af4ff8279b4675643e9db474bfefe46888ac", + "value": 100000000 + } + ], + "shieldData": "", + "lockTime": 0 + }, + "00d268b6476e27669c11780963d6a5d28c42eb7032d1d5e0fd796a7b59d91c3e", + "0100000002f5e18abdb67a95098b48646d9d3985ad2d550a9d632a750f73ec65953013426e010000006b483045022100c4037315756d0c0a8e9b4b9968a3a63b5607059a98bc687e1a84c774f08a317602207e8e57fea23d6c8edbd92212e6f5edf16f67cdbae0ab348d4d20a0ef3e8e45ac012102814b13a56bfb930565ff9002298d315e50a11e6902aff2a0c334d2c27b1aa89bffffffffb9210bf5b29cca7df31aecbfa49c12c897220e82a0ae56a66b7ae220432c923d010000006b4830450221008954308d17283bfb6140b06b5d6563a5a615c071295c2b6ae0ddd379935207b7022005b362b220cbab72c3b77baa4a67c95a4e817ceee53a8986dc933de4eac0bd71012102814b13a56bfb930565ff9002298d315e50a11e6902aff2a0c334d2c27b1aa89bffffffff0288f53001000000001976a9149139c74239af4ff8279b4675643e9db474bfefe488ac00e1f505000000003376a97b63d114f912041b9c6d2351a4022cb1e8ee0108ed72397967149139c74239af4ff8279b4675643e9db474bfefe46888ac00000000", + [ + [ + "cUaEiLoRAuh4LRQhxFDfHMH6CmHqCPJqSsUuzLD2n5CpnCHKKSwX", + "76a9149139c74239af4ff8279b4675643e9db474bfefe488ac" + ], + [ + "cUaEiLoRAuh4LRQhxFDfHMH6CmHqCPJqSsUuzLD2n5CpnCHKKSwX", + "76a9149139c74239af4ff8279b4675643e9db474bfefe488ac" + ] + ] + ], + [ + { + "comment": "Unstake transaction", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "00d268b6476e27669c11780963d6a5d28c42eb7032d1d5e0fd796a7b59d91c3e", + "n": 1 + }, + "scriptSig": "483045022100e7b2e0788d030ff380a3c69c57b0f9d3df3689007e249e00a5463d0b7f20af4a02204a672266779f95d5011e09d776ad1468d64cf68524b3cca2a39536ab15f9403d01002102814b13a56bfb930565ff9002298d315e50a11e6902aff2a0c334d2c27b1aa89b" + } + ], + "vout": [ + { + "script": "76a9149139c74239af4ff8279b4675643e9db474bfefe488ac", + "value": 49989800 + }, + { + "script": "76a9149139c74239af4ff8279b4675643e9db474bfefe488ac", + "value": 50000000 + } + ], + "lockTime": 0, + "shieldData": "" + }, + "3bda3fd067c482bdbf3ada34d0f0e1b0f0e514fe8306c90995998d4ee9374a66", + "01000000013e1cd9597b6a79fde0d5d13270eb428cd2a5d6630978119c66276e47b668d200010000006c483045022100e7b2e0788d030ff380a3c69c57b0f9d3df3689007e249e00a5463d0b7f20af4a02204a672266779f95d5011e09d776ad1468d64cf68524b3cca2a39536ab15f9403d01002102814b13a56bfb930565ff9002298d315e50a11e6902aff2a0c334d2c27b1aa89bffffffff02a8c8fa02000000001976a9149139c74239af4ff8279b4675643e9db474bfefe488ac80f0fa02000000001976a9149139c74239af4ff8279b4675643e9db474bfefe488ac00000000", + [ + [ + "cUaEiLoRAuh4LRQhxFDfHMH6CmHqCPJqSsUuzLD2n5CpnCHKKSwX", + "76a97b63d114f912041b9c6d2351a4022cb1e8ee0108ed72397967149139c74239af4ff8279b4675643e9db474bfefe46888ac", + true + ] + ] + ], + [ + { + "comment": "Tx with locktime = 0", + "version": 1, + "vin": [ + { + "outpoint": { + "txid": "ad33a6786b9fa8a55155c9694db938cadc8d4a4eebfc65b010dedd3d2fa4a3ff", + "n": 1 + }, + "scriptSig": "47304402200d3fe81ea14d48e22485731c1d404bcb68c449be6e6ee017651c8cb9c7eff2980220343e228dd6ef42f8baf3eabeb41766eba906835c6c26bd47b5d710b3382253a6012103b6c8fe9e4a7c1e7bd040f2b1d13b023f89f78f9a61dce73665c25d96d0f34bde", + "sequence": 0 + } + ], + "vout": [ + { + "script": "76a914a95cc6408a676232d61ec29dc56a180b5847835788ac", + "value": 5000000 + }, + { + "script": "76a9149209691ea10d302c1eb8dd21d966beb7c24e0c6988ac", + "value": 4997514 + } + ], + "lockTime": 0, + "shieldData": "" + }, + "f4e8367e511c0c0119766b9f31f63e25efd806167e017960e6ef7fbd83da1cf9", + "0100000001ffa3a42f3dddde10b065fceb4e4a8ddcca38b94d69c95551a5a89f6b78a633ad010000006a47304402200d3fe81ea14d48e22485731c1d404bcb68c449be6e6ee017651c8cb9c7eff2980220343e228dd6ef42f8baf3eabeb41766eba906835c6c26bd47b5d710b3382253a6012103b6c8fe9e4a7c1e7bd040f2b1d13b023f89f78f9a61dce73665c25d96d0f34bde0000000002404b4c00000000001976a914a95cc6408a676232d61ec29dc56a180b5847835788ac8a414c00000000001976a9149209691ea10d302c1eb8dd21d966beb7c24e0c6988ac00000000" + ], + [ + { + "comment": "Version 3 p2pkh tx", + "version": 3, + "vin": [ + { + "outpoint": { + "txid": "f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c", + "n": 1 + }, + "scriptSig": "483045022100a4eac56caaf3700c4f53822fbb858256f3a5c154d268f416ade685de3fe61de202206fb38cfe8fd4faf8b14dc7ac0799c4acfd50a81c4d93509ebd6fb0bca3bb8a7a0121035b57e0afed95b86ad3ccafb9a8c752dc173cea16274cf9dd9b7a43364d36cf38" + } + ], + "vout": [ + { + "script": "76a914f49b25384b79685227be5418f779b98a6be4c73888ac", + "value": 4992400 + }, + { + "script": "76a914a95cc6408a676232d61ec29dc56a180b5847835788ac", + "value": 5000000 + } + ], + "lockTime": 0, + "shieldData": "" + }, + "7b0c3690e82ca98adcbc2ad5971da6d5b497be9a80684fe5d23898ed2eff69be", + "03000000017c5d281d04a546f989bd5ff922447c6bf6896416cc9145b6a782c30ad868f9f8010000006b483045022100a4eac56caaf3700c4f53822fbb858256f3a5c154d268f416ade685de3fe61de202206fb38cfe8fd4faf8b14dc7ac0799c4acfd50a81c4d93509ebd6fb0bca3bb8a7a0121035b57e0afed95b86ad3ccafb9a8c752dc173cea16274cf9dd9b7a43364d36cf38ffffffff02902d4c00000000001976a914f49b25384b79685227be5418f779b98a6be4c73888ac404b4c00000000001976a914a95cc6408a676232d61ec29dc56a180b5847835788ac000000000000000000000000000000" + ], + [ + { + "comment": "Shield tx", + "txid": "9c97631d1289a4a582b04e3e2037522f46b9341c7c102e54d58f99d3eb028cb4", + "version": 3, + "vin": [ + { + "outpoint": { + "txid": "8d9bba4b752f5409ed9d765fe3c201da598bdfac87c0d453d5d88a0d5b3025fd", + "n": 1 + }, + + "scriptSig": "483045022100ee099a2199676aa8d224962c383c427d90cd8abd3be6e0389c2d123401d35fd3022075fbcad0ee824260c0bf46a5c9cfd1afaf36644653ad1d0b3bbf935a919fe5410121021867892c331da4088ecb08dd2ba5435580ce3ad9c16380cd7955296f4794833b" + }, + { + "outpoint": { + "txid": "99a06ba2ae296dfaa89190d6620a262a9d2bc7dfeddaa28b23d65cd92a96bf5b", + "n": 1 + }, + "scriptSig": "483045022100a9858a202eb11b09886991240a931a0582f29faee7dc8c45331096c35126b20a02206179bb3db9b5278e2eb27370a9a465407c2227003425f36862b8a873ee6ea75c012102c2fd0e3bbca514340c21a2936c182338833e88403defbd03f671af8df183cea2" + } + ], + "vout": [ + { + "value": 97719000, + "script": "76a9140807b2aa8776a75d053c1a3e4aac00cc30f4ff9a88ac" + } + ], + "valueBalance": -100000000, + "shieldSpend": [], + "shieldOutput": [ + { + "cv": "28eef32a349b2ab3334b1bb51f942b8d42ac8fac48e2428d5dee07c49771a952", + "cmu": "72f607c2bd0224eb439483df05b662c468a1662378c29b86e0d775f4550e5915", + "ephemeralKey": "bcbf72855c6b44923c80c8999694d50fc8c06f259702dc819e1a74f59c7f8e3c", + "encCiphertext": "4efd126ce46eff6359b2de578433ca4673750cedb628f5229f970c58207545818398544b562512db05849c3025a1cfdca4d33573b116ed394b44782eb898f943f61e755df34e5c0fb9e1b883752a7f2a0e464d94485f064eea147e4d674ebae6de0f967689ca1d1b791e961cdc8cefe5e10909d546d25a0e432621ea8f0030098702b0de0a99fb01e363dc8b2a630b2d3deefbe1543cba12443f15784e042f24a087d88afe2b7512dbad69accc56e6c7b9f0a747b9ef7bd83e4a857b02504f017659668b64375c2d98706ebd1f1a02fd058a303af9585da185e54e70ee40fcd0f123a2505601ec59b9e376803ac6f1c06665377ff3ff404de49b54e1bb6fb913d2aa744a5a402dd364c6e1485e9581e2d96de5e918004631f6165e41ebf6e4eb73708e5b4367dc38d67cb8815caed59d00672677f3e5c4c240e9fbe32b70663997b462b808a8a8f61c6c140867d7478f17ecdd60ecd8a5f83b235a2c2c185d459bea10ba89beb8a2933e3c4baa91093433066047bcfadd8743bd7e0f791e43bdf3ac4478543770ec617fbe354b133888324ddb769b34ad57cb5c81cc4d915f5eeea871c5412a702d53e3bcae955d52030b157cc421d8f9404defe0dca62c0153c7013ebff59665ad5aa9175be8e64730c555b2c87747d59e7d8eeaef98b2c5a166cbade07ac1955cbb16af38811c9ed5dd94fccc85931e71335473c133f389f178f246391ed57c72e494b8b37c2ce84e2c05bf111eff2acfd5be2fe82587cd0285079cc2c1d2ea51cde1fd0c78df390a8989f8d0c4436c54950c8a57144f60304b998a38", + "outCiphertext": "fa71d891bc823bb5d0163730b8e5392d685255f5da15b3ac5f828ca318d49997e2951c7157979e4a5bd94b65832fac914aef754632b96f83ab2eaee04042fc747aef92257536cd007e6b40dbeda171c7", + "proof": "a54aff3a79cd8ccdefd6600b6bebe4dd30b596bb2b550e3fe51596a67991a5080fc6961c5e40bd72d2760d480202d5fbb1ddd0d3357ba6cef99c9ac9d8b2727c4d1cabec362314936f0a96d2b0896c5469fbdc385932ddc61aa085f49653ff4715060202226d6408e01b4406f2d41641feea8f4f43990b86d5df6736c9f8cecba194fc7c9e14b3c8bffccca03c9d3f9c908c5b16fdae3642a5809bb721cebb9cdb391c0d79d4250c465cd1ba2fbb252218f6efacf6ca366eb77876599a878689" + } + ], + "bindingSig": "af4f434b4e9f30b4f563453167e1032bfae80d8fc725c16fd6c92fea903df6bbde7a07fd057afef5c4ba7e12fcf0f2794c759e0df37bb1c971bb077f5238ff01" + }, + "9c97631d1289a4a582b04e3e2037522f46b9341c7c102e54d58f99d3eb028cb4", + "0300000002fd25305b0d8ad8d553d4c087acdf8b59da01c2e35f769ded09542f754bba9b8d010000006b483045022100ee099a2199676aa8d224962c383c427d90cd8abd3be6e0389c2d123401d35fd3022075fbcad0ee824260c0bf46a5c9cfd1afaf36644653ad1d0b3bbf935a919fe5410121021867892c331da4088ecb08dd2ba5435580ce3ad9c16380cd7955296f4794833bffffffff5bbf962ad95cd6238ba2daeddfc72b9d2a260a62d69091a8fa6d29aea26ba099010000006b483045022100a9858a202eb11b09886991240a931a0582f29faee7dc8c45331096c35126b20a02206179bb3db9b5278e2eb27370a9a465407c2227003425f36862b8a873ee6ea75c012102c2fd0e3bbca514340c21a2936c182338833e88403defbd03f671af8df183cea2ffffffff01d812d305000000001976a9140807b2aa8776a75d053c1a3e4aac00cc30f4ff9a88ac0000000001001f0afaffffffff000152a97197c407ee5d8d42e248ac8fac428d2b941fb51b4b33b32a9b342af3ee2815590e55f475d7e0869bc2782366a168c462b605df839443eb2402bdc207f6723c8e7f9cf5741a9e81dc0297256fc0c80fd5949699c8803c92446b5c8572bfbc4efd126ce46eff6359b2de578433ca4673750cedb628f5229f970c58207545818398544b562512db05849c3025a1cfdca4d33573b116ed394b44782eb898f943f61e755df34e5c0fb9e1b883752a7f2a0e464d94485f064eea147e4d674ebae6de0f967689ca1d1b791e961cdc8cefe5e10909d546d25a0e432621ea8f0030098702b0de0a99fb01e363dc8b2a630b2d3deefbe1543cba12443f15784e042f24a087d88afe2b7512dbad69accc56e6c7b9f0a747b9ef7bd83e4a857b02504f017659668b64375c2d98706ebd1f1a02fd058a303af9585da185e54e70ee40fcd0f123a2505601ec59b9e376803ac6f1c06665377ff3ff404de49b54e1bb6fb913d2aa744a5a402dd364c6e1485e9581e2d96de5e918004631f6165e41ebf6e4eb73708e5b4367dc38d67cb8815caed59d00672677f3e5c4c240e9fbe32b70663997b462b808a8a8f61c6c140867d7478f17ecdd60ecd8a5f83b235a2c2c185d459bea10ba89beb8a2933e3c4baa91093433066047bcfadd8743bd7e0f791e43bdf3ac4478543770ec617fbe354b133888324ddb769b34ad57cb5c81cc4d915f5eeea871c5412a702d53e3bcae955d52030b157cc421d8f9404defe0dca62c0153c7013ebff59665ad5aa9175be8e64730c555b2c87747d59e7d8eeaef98b2c5a166cbade07ac1955cbb16af38811c9ed5dd94fccc85931e71335473c133f389f178f246391ed57c72e494b8b37c2ce84e2c05bf111eff2acfd5be2fe82587cd0285079cc2c1d2ea51cde1fd0c78df390a8989f8d0c4436c54950c8a57144f60304b998a38fa71d891bc823bb5d0163730b8e5392d685255f5da15b3ac5f828ca318d49997e2951c7157979e4a5bd94b65832fac914aef754632b96f83ab2eaee04042fc747aef92257536cd007e6b40dbeda171c7a54aff3a79cd8ccdefd6600b6bebe4dd30b596bb2b550e3fe51596a67991a5080fc6961c5e40bd72d2760d480202d5fbb1ddd0d3357ba6cef99c9ac9d8b2727c4d1cabec362314936f0a96d2b0896c5469fbdc385932ddc61aa085f49653ff4715060202226d6408e01b4406f2d41641feea8f4f43990b86d5df6736c9f8cecba194fc7c9e14b3c8bffccca03c9d3f9c908c5b16fdae3642a5809bb721cebb9cdb391c0d79d4250c465cd1ba2fbb252218f6efacf6ca366eb77876599a878689af4f434b4e9f30b4f563453167e1032bfae80d8fc725c16fd6c92fea903df6bbde7a07fd057afef5c4ba7e12fcf0f2794c759e0df37bb1c971bb077f5238ff01" + ] +] diff --git a/tests/unit/transaction_builder.spec.js b/tests/unit/transaction_builder.spec.js new file mode 100644 index 000000000..3e5c8d8c1 --- /dev/null +++ b/tests/unit/transaction_builder.spec.js @@ -0,0 +1,217 @@ +import { describe, it } from 'vitest'; +import { + COutpoint, + CTxIn, + CTxOut, + Transaction, + UTXO, +} from '../../scripts/transaction.js'; +import { TransactionBuilder } from '../../scripts/transaction_builder.js'; + +describe('Transaction builder tests', () => { + it('Builds a transaction correctly', () => { + const txBuilder = TransactionBuilder.create() + .addUTXO( + new UTXO({ + outpoint: new COutpoint({ + txid: 'abcd', + n: 4, + }), + script: 'script1', + value: 5, + }) + ) + .addUTXOs([ + new UTXO({ + outpoint: new COutpoint({ + txid: 'fgea', + n: 2, + }), + script: 'script2', + value: 6, + }), + ]) + .addOutputs([ + { + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 3, + }, + { + address: 'DShxa9sykpVUYBe2VKZfq9dzE8f2yBbtmg', + value: 8, + }, + ]); + expect(txBuilder.valueIn).toBe(5 + 6); + expect(txBuilder.valueOut).toBe(3 + 8); + expect(txBuilder.value).toBe(5 + 6 - 3 - 8); + const tx = txBuilder.build(); + expect(tx).toStrictEqual( + new Transaction({ + version: 1, + blockHeight: -1, + vin: [ + new CTxIn({ + outpoint: new COutpoint({ + txid: 'abcd', + n: 4, + }), + scriptSig: 'script1', + }), + new CTxIn({ + outpoint: new COutpoint({ + txid: 'fgea', + n: 2, + }), + scriptSig: 'script2', + }), + ], + vout: [ + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 3, + }), + new CTxOut({ + script: '76a914ec91b7a8809f5ff50439ad5c8186131cfc36ea4c88ac', + value: 8, + }), + ], + blockTime: -1, + lockTime: 0, + shieldOutput: [], + }) + ); + // Subsequent builds must return null + expect(txBuilder.build()).toBe(null); + }); + + it('builds an exchange tx correctly', () => { + const tx = TransactionBuilder.create() + .addOutput({ + address: 'EXMDbnWT4K3nWfK1311otFrnYLcFSipp3iez', + value: 1, + }) + .addUTXO( + new UTXO({ + outpoint: new COutpoint({ + txid: 'abcd', + n: 4, + }), + script: 'script1', + value: 5, + }) + ) + .build(); + expect(tx).toStrictEqual( + new Transaction({ + vin: [ + new CTxIn({ + outpoint: new COutpoint({ + txid: 'abcd', + n: 4, + }), + scriptSig: 'script1', + }), + ], + vout: [ + new CTxOut({ + script: 'e076a9141c62aa5fb5bc8a4932491fcfc1832fb5422e0cd288ac', + value: 1, + }), + ], + }) + ); + }); + + it('builds a s->s transaction correctly', () => { + const tx = TransactionBuilder.create() + .addOutput({ + address: + 'ps1kw7d704cpvy4f5e5usk3xhykytxnjfk872fpty7ct6znvmdepsxq4s90p9a3arg0qg8tzjk7vkn', + value: 1000, + }) + .build(); + expect(tx).toStrictEqual( + new Transaction({ + version: 3, + shieldOutput: [ + { + address: + 'ps1kw7d704cpvy4f5e5usk3xhykytxnjfk872fpty7ct6znvmdepsxq4s90p9a3arg0qg8tzjk7vkn', + value: 1000, + }, + ], + }) + ); + }); + + it('builds a s->t transaction correctly', () => { + const tx = TransactionBuilder.create() + .addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 3, + }) + .build(); + expect(tx).toStrictEqual( + new Transaction({ + version: 3, // The important thing here is version=3 + vout: [ + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 3, + }), + ], + }) + ); + }); + + it('builds a t->s transaction correctly', () => { + const tx = TransactionBuilder.create() + .addUTXO( + new UTXO({ + outpoint: new COutpoint({ + txid: 'abcd', + n: 4, + }), + script: 'script1', + value: 5, + }) + ) + .addOutput({ + address: + 'ps1kw7d704cpvy4f5e5usk3xhykytxnjfk872fpty7ct6znvmdepsxq4s90p9a3arg0qg8tzjk7vkn', + value: 1000, + }) + .build(); + expect(tx).toStrictEqual( + new Transaction({ + version: 3, + shieldOutput: [ + { + address: + 'ps1kw7d704cpvy4f5e5usk3xhykytxnjfk872fpty7ct6znvmdepsxq4s90p9a3arg0qg8tzjk7vkn', + value: 1000, + }, + ], + vin: [ + new CTxIn({ + outpoint: new COutpoint({ + txid: 'abcd', + n: 4, + }), + scriptSig: 'script1', + }), + ], + }) + ); + }); + + it('throws when address is invalid', () => { + const txBuilder = TransactionBuilder.create(); + expect(() => + txBuilder.addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc', + value: 5, + }) + ).toThrow(/address/); + }); +}); diff --git a/tests/unit/use_wallet.spec.js b/tests/unit/use_wallet.spec.js new file mode 100644 index 000000000..3b9c5aa68 --- /dev/null +++ b/tests/unit/use_wallet.spec.js @@ -0,0 +1,91 @@ +import 'fake-indexeddb/auto'; +import { getEventEmitter } from '../../scripts/event_bus.js'; +import { describe, it, beforeEach, vi } from 'vitest'; +import { useWallet } from '../../scripts/composables/use_wallet.js'; +import { hasEncryptedWallet, wallet } from '../../scripts/wallet.js'; +import { LegacyMasterKey } from '../../scripts/masterkey.js'; +import { getNetwork } from '../../scripts/network.js'; +import { strCurrency } from '../../scripts/settings.js'; +import { setUpLegacyMainnetWallet } from '../utils/test_utils'; + +vi.mock('../../scripts/network.js'); + +describe('useWallet tests', () => { + let walletComposable; + beforeEach(async () => { + walletComposable = useWallet(); + vi.stubGlobal('indexedDB', new IDBFactory()); + vi.stubGlobal('wallet', await setUpLegacyMainnetWallet()); + getEventEmitter().emit('balance-update'); + }); + + async function isSyncedWithWallet() { + expect(wallet.isLoaded()).toBe(walletComposable.isImported); + expect(wallet.isViewOnly()).toBe(walletComposable.isViewOnly); + expect(wallet.isSynced).toBe(walletComposable.isSynced); + expect(await hasEncryptedWallet()).toBe(walletComposable.isEncrypted); + expect(wallet.hasShield()).toBe(walletComposable.hasShield); + expect(wallet.isHardwareWallet()).toBe( + walletComposable.isHardwareWallet + ); + expect(wallet.isHD()).toBe(walletComposable.isHD); + expect(wallet.balance).toBe(walletComposable.balance); + expect(await wallet.getShieldBalance()).toBe( + walletComposable.shieldBalance + ); + expect(wallet.coldBalance).toBe(walletComposable.coldBalance); + expect(await wallet.getPendingShieldBalance()).toBe( + walletComposable.pendingShieldBalance + ); + expect(wallet.immatureBalance).toBe(walletComposable.immatureBalance); + expect(walletComposable.currency).toBe(strCurrency.toUpperCase()); + + return true; + } + + it('is synced initially', async () => { + expect(await isSyncedWithWallet()).toBe(true); + }); + + it('is synced after importing key', async () => { + walletComposable.setMasterKey({ + mk: new LegacyMasterKey({ + pkBytes: new Uint8Array([ + 181, 66, 141, 90, 213, 58, 137, 158, 160, 57, 109, 252, 51, + 227, 221, 192, 8, 4, 223, 42, 42, 8, 191, 7, 251, 231, 167, + 119, 54, 161, 194, 229, + ]), + }), + }); + expect(await isSyncedWithWallet()).toBe(true); + }); + + it('is synced after encryption', async () => { + await walletComposable.encrypt('123456'); + expect(await isSyncedWithWallet()).toBe(true); + }); + + it('is synced after syncing', async () => { + await walletComposable.sync(); + expect(await isSyncedWithWallet()).toBe(true); + }); + + it('is synced after creating tx', async () => { + await walletComposable.setMasterKey({ + mk: new LegacyMasterKey({ + pkBytes: new Uint8Array([ + 181, 66, 141, 90, 213, 58, 137, 158, 160, 57, 109, 252, 51, + 227, 221, 192, 8, 4, 223, 42, 42, 8, 191, 7, 251, 231, 167, + 119, 54, 161, 194, 229, + ]), + }), + }); + + await walletComposable.createAndSendTransaction( + getNetwork(), + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0 + ); + expect(await isSyncedWithWallet()).toBe(true); + }); +}); diff --git a/tests/unit/wallet/signature.spec.js b/tests/unit/wallet/signature.spec.js new file mode 100644 index 000000000..194f1a007 --- /dev/null +++ b/tests/unit/wallet/signature.spec.js @@ -0,0 +1,173 @@ +import { + getLegacyMainnet, + PIVXShield, + setUpLegacyMainnetWallet, +} from '../../utils/test_utils'; +import { describe, it, vi, expect } from 'vitest'; +import 'fake-indexeddb/auto'; +import { + COutpoint, + CTxIn, + CTxOut, + Transaction, +} from '../../../scripts/transaction.js'; +import { hexToBytes } from '../../../scripts/utils'; + +vi.mock('../../../scripts/network.js'); +vi.mock('../../../scripts/global.js'); + +describe('Wallet signature tests', () => { + let wallet; + beforeEach(async () => { + wallet = await setUpLegacyMainnetWallet(); + // Reset indexedDB before each test + vi.stubGlobal('indexedDB', new IDBFactory()); + }); + + it('throws when is view only', async () => { + wallet.wipePrivateData(); + expect(wallet.sign({})).rejects.toThrow(/view only/i); + }); + it('signs a transaction correctly', async () => { + const tx = new Transaction(); + tx.version = 1; + tx.blockHeight = -1; + tx.blockTime = -1; + tx.vin = [ + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }), + ]; + tx.vout = [ + new CTxOut({ + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4992400, + }), + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }), + ]; + const signedTx = await wallet.sign(tx); + // Return value must reference the same tx + expect(signedTx).toBe(tx); + expect(signedTx.txid).toBe( + '9cf01cffc85d53b80a9c7ca106fc7326efa0f4f1db3eaf5be0ac45eb6105b8ab' + ); + }); + + it('signs a s->s transaction correctly', async () => { + const tx = new Transaction({ + version: 3, + blockHeight: -1, + vin: [], + vout: [], + shieldOutput: [ + { + value: 100000, + address: 'ptest1234567', + }, + ], + }); + const txRef = await wallet.sign(tx); + expect(txRef).toBe(tx); + expect(PIVXShield.prototype.createTransaction).toHaveBeenCalledWith({ + address: 'ptest1234567', + amount: 100000, + blockHeight: 1504904, + transparentChangeAddress: 'DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC', + useShieldInputs: true, // Because vin is empty + utxos: [ + { + amount: 10000000, + private_key: getLegacyMainnet().getPrivateKeyBytes(), + script: hexToBytes( + '76a914f49b25384b79685227be5418f779b98a6be4c73888ac' + ), + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + vout: 1, + }, + ], + }); + }); + it('signs a s->t tx correctly', async () => { + const tx = new Transaction({ + version: 3, + blockHeight: -1, + vin: [], + vout: [ + new CTxOut({ + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4992400, + }), + ], + shieldOutput: [], + }); + const txRef = await wallet.sign(tx); + expect(txRef).toBe(tx); + expect(PIVXShield.prototype.createTransaction).toHaveBeenCalledWith({ + address: 'DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC', + amount: 4992400, + blockHeight: 1504904, + transparentChangeAddress: 'DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC', + useShieldInputs: true, // Because vin is empty + utxos: [ + { + amount: 10000000, + private_key: getLegacyMainnet().getPrivateKeyBytes(), + script: hexToBytes( + '76a914f49b25384b79685227be5418f779b98a6be4c73888ac' + ), + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + vout: 1, + }, + ], + }); + }); + it('signs a t->s tx correctly', async () => { + const tx = new Transaction({ + version: 3, + blockHeight: -1, + vin: [ + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + }), + ], + vout: [], + shieldOutput: [ + { + value: 100000, + address: 'ptest1234567', + }, + ], + }); + const txRef = await wallet.sign(tx); + expect(txRef).toBe(tx); + expect(PIVXShield.prototype.createTransaction).toHaveBeenCalledWith({ + address: 'ptest1234567', + amount: 100000, + blockHeight: 1504904, + transparentChangeAddress: 'DTSTGkncpC86sbEUZ2rCBLEe2aXSeZPLnC', + useShieldInputs: false, + utxos: [ + { + amount: 10000000, + private_key: getLegacyMainnet().getPrivateKeyBytes(), + script: hexToBytes( + '76a914f49b25384b79685227be5418f779b98a6be4c73888ac' + ), + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + vout: 1, + }, + ], + }); + }); +}); diff --git a/tests/unit/wallet/transactions.spec.js b/tests/unit/wallet/transactions.spec.js new file mode 100644 index 000000000..b682ef28e --- /dev/null +++ b/tests/unit/wallet/transactions.spec.js @@ -0,0 +1,382 @@ +import { Wallet } from '../../../scripts/wallet.js'; +import { Mempool } from '../../../scripts/mempool.js'; +import { setUpLegacyMainnetWallet } from '../../utils/test_utils'; +import { describe, it, vi, afterAll, expect } from 'vitest'; +import { + COutpoint, + CTxIn, + CTxOut, + Transaction, +} from '../../../scripts/transaction.js'; + +import 'fake-indexeddb/auto'; +import { TransactionBuilder } from '../../../scripts/transaction_builder.js'; + +vi.stubGlobal('localStorage', { length: 0 }); +vi.mock('../../../scripts/global.js'); +vi.mock('../../../scripts/network.js'); + +/** + * @param {Wallet} wallet + * @param {Transaction} tx + * @param {number} feesPerBytes + */ +async function checkFees(wallet, tx, feesPerBytes) { + let fees = 0; + for (const vout of tx.vout) { + fees -= vout.value; + } + + for (const vin of tx.vin) { + fees += wallet.outpointToUTXO(vin.outpoint).value; + } + // Sign and verify that it pays enough fees, and that it is greedy enough + const nBytes = (await wallet.sign(tx)).serialize().length / 2; + expect(fees).toBeGreaterThanOrEqual(feesPerBytes * nBytes); + expect(fees).toBeLessThanOrEqual((feesPerBytes + 1) * nBytes); +} +describe('Wallet transaction tests', () => { + let wallet; + const MIN_FEE_PER_BYTE = new TransactionBuilder().MIN_FEE_PER_BYTE; + beforeEach(async () => { + wallet = await setUpLegacyMainnetWallet(); + + // Reset indexedDB before each test + vi.stubGlobal('indexedDB', new IDBFactory()); + return vi.unstubAllGlobals; + }); + it('Creates a transaction correctly', async () => { + const tx = wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0.05 * 10 ** 8 + ); + expect(tx.version).toBe(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout[1].script).toBe( + '76a914f49b25384b79685227be5418f779b98a6be4c73888ac' + ); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('creates an exchange tx correctly', async () => { + const tx = wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0.05 * 10 ** 8 + ); + expect(tx.version).toBe(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout[1]).toStrictEqual( + new CTxOut({ + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4997730, + }) + ); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('Creates a tx with change address', async () => { + const tx = wallet.createTransaction( + 'EXMDbnWT4K3nWfK1311otFrnYLcFSipp3iez', + 0.05 * 10 ** 8, + { changeAddress: 'D8Ervc3Ka6TuKgvXZH9Eo4ou24AiVwTbL6' } + ); + expect(tx.version).toBe(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout[1]).toStrictEqual( + new CTxOut({ + script: '76a91421ff8214d09d60713b89809bb413a0651ee6931488ac', + value: 4997720, + }) + ); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: 'e076a9141c62aa5fb5bc8a4932491fcfc1832fb5422e0cd288ac', + value: 5000000, + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('Creates a proposal tx correctly', async () => { + const tx = wallet.createTransaction( + 'bcea39f87b1dd7a5ba9d11d3d956adc6ce57dfff9397860cc30c11f08b3aa7c8', + 0.05 * 10 ** 8, + { isProposal: true } + ); + expect(tx.version).toBe(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout[1]).toStrictEqual( + new CTxOut({ + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4997640, + }) + ); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '6a20bcea39f87b1dd7a5ba9d11d3d956adc6ce57dfff9397860cc30c11f08b3aa7c8', + value: 5000000, + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('Creates a cold stake tx correctly', async () => { + const tx = wallet.createTransaction( + 'SR3L4TFUKKGNsnv2Q4hWTuET2a4vHpm1b9', + 0.05 * 10 ** 8, + { isDelegation: true } + ); + expect(tx.version).toBe(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout[1]).toStrictEqual( + new CTxOut({ + script: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + value: 4997470, + }) + ); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '76a97b63d114291a25b5b4d1802e0611e9bf724a1e57d9210e826714f49b25384b79685227be5418f779b98a6be4c7386888ac', + value: 5000000, + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('creates a tx with max balance', async () => { + const tx = wallet.createTransaction( + 'SR3L4TFUKKGNsnv2Q4hWTuET2a4vHpm1b9', + 0.1 * 10 ** 8, + { isDelegation: true } + ); + expect(tx.version).toBe(1); + expect(tx.vin).toHaveLength(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout).toHaveLength(1); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '76a97b63d114291a25b5b4d1802e0611e9bf724a1e57d9210e826714f49b25384b79685227be5418f779b98a6be4c7386888ac', + value: 9997810, // 0.1 PIV - fee + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('creates a t->s tx correctly', () => { + const addr = + 'ps1a0x2few52sy3t0nrdhun0re4c870e04w448qpa7c26qjw9ljs4quhja40hat95f7hy8tcuvcn2s'; + const tx = wallet.createTransaction(addr, 0.05 * 10 ** 8); + expect(tx).toStrictEqual( + new Transaction({ + vin: [ + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: + '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', + }), + ], + shieldOutput: [ + { + address: addr, + value: 0.05 * 10 ** 8, + }, + ], + version: 3, + }) + ); + }); + + it('it does not insert dust change', async () => { + // The tipical output has 34 bytes, so a 200 satoshi change is surely going to be dust + // a P2PKH with 1 input and 1 output will have more or less 190 bytes in size and 1900 satoshi of fees + // Finally 0.1*10**8 is the value of the UTXO we are spending (0.1 PIVs) + const value = 0.1 * 10 ** 8 - 1900 - 200; + const tx = wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value, + { subtractFeeFromAmt: false } + ); + expect(tx.version).toBe(1); + expect(tx.vin).toHaveLength(1); + expect(tx.vin[0]).toStrictEqual( + new CTxIn({ + outpoint: new COutpoint({ + txid: 'f8f968d80ac382a7b64591cc166489f66b7c4422f95fbd89f946a5041d285d7c', + n: 1, + }), + scriptSig: '76a914f49b25384b79685227be5418f779b98a6be4c73888ac', // Script sig must be the UTXO script since it's not signed + }) + ); + expect(tx.vout).toHaveLength(1); + expect(tx.vout[0]).toStrictEqual( + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: value, + }) + ); + await checkFees(wallet, tx, MIN_FEE_PER_BYTE); + }); + + it('creates a s->t tx correctly', async () => { + const tx = wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0.05 * 10 ** 8, + { useShieldInputs: true } + ); + expect(tx).toStrictEqual( + new Transaction({ + version: 3, + vout: [ + new CTxOut({ + script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac', + value: 5000000, + }), + ], + }) + ); + }); + + it('creates a s->s tx correctly', async () => { + const addr = + 'ps1a0x2few52sy3t0nrdhun0re4c870e04w448qpa7c26qjw9ljs4quhja40hat95f7hy8tcuvcn2s'; + const tx = wallet.createTransaction(addr, 0.05 * 10 ** 8, { + useShieldInputs: true, + }); + expect(tx).toStrictEqual( + new Transaction({ + version: 3, + shieldOutput: [ + { + address: addr, + value: 0.05 * 10 ** 8, + }, + ], + }) + ); + }); + + it('throws when balance is insufficient', () => { + expect(() => + wallet.createTransaction( + 'SR3L4TFUKKGNsnv2Q4hWTuET2a4vHpm1b9', + 20 * 10 ** 8, + { isDelegation: true } + ) + ).toThrow(/not enough balance/i); + expect(() => + wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 20 * 10 ** 8 + ) + ).toThrow(/not enough balance/i); + expect(() => + wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 50 * 10 ** 8, + { useShieldInputs: true } + ) + ).toThrow(/not enough balance/i); + + // Should use shield balance when `useShieldInputs` is true + expect( + wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 30 * 10 ** 8, + { useShieldInputs: true } + ) + ).toBeDefined(); + // MaX balance is set but we don't allow subtracting fee from amount + expect(() => + wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0.1 * 10 ** 8, + { subtractFeeFromAmt: false } + ) + ).toThrow(/not enough balance/i); + }); + + it('throws when delegateChange is set, but changeDelegationAddress is not', () => { + expect(() => + wallet.createTransaction( + 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + 0.1 * 10 ** 8, + { delegateChange: true } + ) + ).toThrow(/was set to/i); + }); + + it('finalizes transaction correctly', () => { + const spy = vi.spyOn(Mempool.prototype, 'addTransaction'); + const tx = new Transaction(); + wallet.addTransaction(tx); + expect(spy).toBeCalledTimes(1); + expect(spy).toBeCalledWith(tx); + }); + + afterAll(() => { + vi.clearAllMocks(); + }); +}); diff --git a/tests/utils/test_utils.js b/tests/utils/test_utils.js new file mode 100644 index 000000000..60bde9ca7 --- /dev/null +++ b/tests/utils/test_utils.js @@ -0,0 +1,113 @@ +import { + HdMasterKey, + LegacyMasterKey, + MasterKey, +} from '../../scripts/masterkey.js'; +import { Wallet } from '../../scripts/wallet.js'; +import { Mempool } from '../../scripts/mempool.js'; +import { vi } from 'vitest'; + +export function getLegacyMainnet() { + return new LegacyMasterKey({ + pkBytes: new Uint8Array([ + 181, 66, 141, 90, 213, 58, 137, 158, 160, 57, 109, 252, 51, 227, + 221, 192, 8, 4, 223, 42, 42, 8, 191, 7, 251, 231, 167, 119, 54, 161, + 194, 229, + ]), + }); +} + +export const PIVXShield = vi.fn(); +PIVXShield.prototype.createTransaction = vi.fn(() => { + return { + hex: '00', + }; +}); +// Return + infinity so that we don't have to mock this.#shield.handleBlock(block); +PIVXShield.prototype.getLastSyncedBlock = vi.fn(() => { + return Infinity; +}); +PIVXShield.prototype.getBalance = vi.fn(() => 40 * 10 ** 8); + +/** + * set up and sync a wallet + * @param {MasterKey} masterKey - masterKey of the wallet + * @param {boolean} includeShield + * @returns {Promise} + */ +async function setUpWallet(masterKey, includeShield) { + const mempool = new Mempool(); + const wallet = new Wallet({ nAccount: 0, isMainWallet: false, mempool }); + await wallet.setMasterKey({ mk: masterKey }); + await wallet.sync(); + if (includeShield) { + // TODO: shield sync is a bit problematic and a better plan to mock it is needed + // for the moment just set the shield after the initial sync + wallet.setShield(new PIVXShield()); + } + expect(wallet.isSynced).toBeTruthy(); + expect(wallet.isSyncing).toBeFalsy(); + return wallet; +} +/** + * Creates a mainnet wallet with a legacy master key and a spendable UTXO and a dummy PIVXShield + * @returns {Promise} + */ +export async function setUpLegacyMainnetWallet() { + // TODO: legacy wallets shouldn't have shield, make includeShield = false and rewrite some tests + const wallet = await setUpWallet(getLegacyMainnet(), true); + + // sanity check on the balance + expect(wallet.balance).toBe(0.1 * 10 ** 8); + expect(wallet.coldBalance).toBe(0); + expect(wallet.immatureBalance).toBe(0); + + return wallet; +} + +/** + * Create a mainnet HD wallet + * for the moment includeShield must be false, TODO: generalize + * @param{boolean} includeShield + * @returns{Promise} + */ +export async function setUpHDMainnetWallet(includeShield) { + const wallet = await setUpWallet(getHDMainnet(), includeShield); + + // sanity check on the balance + expect(wallet.balance).toBe(1 * 10 ** 8); + expect(wallet.coldBalance).toBe(0); + expect(wallet.immatureBalance).toBe(0); + + return wallet; +} + +export function getLegacyTestnet() { + return new LegacyMasterKey({ + pkBytes: new Uint8Array([ + 254, 60, 197, 153, 164, 198, 53, 142, 244, 155, 71, 44, 96, 5, 195, + 133, 140, 205, 48, 232, 157, 152, 118, 173, 49, 41, 118, 47, 175, + 196, 232, 82, + ]), + }); +} + +function getHDMainnet() { + return new HdMasterKey({ + seed: new Uint8Array([ + 159, 45, 151, 205, 11, 183, 130, 131, 116, 62, 56, 190, 142, 201, + 142, 222, 16, 196, 8, 154, 101, 90, 8, 12, 191, 160, 222, 153, 10, + 19, 97, 133, 225, 213, 43, 109, 103, 146, 79, 217, 191, 212, 211, + 95, 120, 171, 18, 126, 47, 138, 85, 99, 120, 150, 103, 108, 254, + 209, 99, 51, 209, 70, 127, 81, + ]), + }); +} + +/** + * Returns a watch only wallet from a given address + * @param{String} address + */ +export async function getWalletFromAddress(address) { + return await setUpWallet(new LegacyMasterKey({ address }), false); +} diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 000000000..e5f97b69a --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + assetsInclude: '**/*.toml', + plugins: [vue()], + test: { + environment: 'happy-dom', + globals: true, + coverage: { + provider: 'istanbul', + }, + setupFiles: ['test_setup.js'], + pool: 'forks', + }, +}); diff --git a/webpack.common.js b/webpack.common.js new file mode 100644 index 000000000..8d9546cda --- /dev/null +++ b/webpack.common.js @@ -0,0 +1,150 @@ +/* istanbul ignore file */ +/* eslint-env node */ +/* eslint @typescript-eslint/no-var-requires: "off" */ + +import path from 'path'; +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import CopyPlugin from 'copy-webpack-plugin'; +import PreloadWebpackPlugin from '@vue/preload-webpack-plugin'; +import toml from 'toml'; +import { VueLoaderPlugin } from 'vue-loader'; + +import { readFileSync } from 'fs'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// Inject the Changelog and Version to the app +const changelog = readFileSync('./changelog.md', { encoding: 'utf8' }); + +export default { + entry: './scripts/index.js', + output: { + path: path.resolve(__dirname, './dist'), + filename: './mpw.js', + library: 'MPW', + libraryTarget: 'var', + clean: true, + }, + devtool: 'source-map', + module: { + rules: [ + { + test: /\.css$/i, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, + { + test: /\.(jpe?g|png|gif|svg|mp3|svg)$/i, + type: 'asset/resource', + }, + { + test: /\.vue/i, + use: { + loader: 'vue-loader', + options: { + compilerOptions: { + isCustomElement: (tag) => tag === 'center', + }, + }, + }, + }, + { + test: /\.toml$/, + // Json means we're returing an object. + // See https://webpack.js.org/configuration/module/#ruleparserparse + type: 'json', + parser: { + parse: (str) => + toml.parse( + str + .split('\n') + // Ignore lines starting with ~~, it means we haven't + // translated them yet + .filter((l) => !l.match(/^[\w\s]+=\s*['"]~~/)) + .join('\n') + ), + }, + }, + { + test: /countries.json$/, + type: 'json', + parser: { + parse: (str) => + JSON.parse(str).map((c) => { + return { + alpha2: c.alpha2, + currency: c.currency, + }; + }), + }, + }, + { + test: /\.svg$/i, + type: 'asset/source', + }, + ], + }, + resolve: { + fallback: { + fs: false, + crypto: path.resolve(__dirname, 'scripts/polyfills/crypto.js'), + }, + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './index.template.html', + filename: 'index.html', + favicon: './assets/favicon.ico', + meta: { + viewport: + 'width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no', + }, + }), + new VueLoaderPlugin(), + // Polyfill for non web libraries + new NodePolyfillPlugin({ + includeAliases: ['stream', 'process', 'Buffer'], + }), + // Prevents non styled flashing on load + new MiniCssExtractPlugin(), + // Make jquery available globally + new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + 'window.jQuery': 'jquery', + }), + // Make the Changelog available globally + new webpack.DefinePlugin({ + CHANGELOG: JSON.stringify(changelog), + }), + // Ignore non english bip39 wordlists + new webpack.IgnorePlugin({ + resourceRegExp: /^\.\/wordlists\/(?!english)/, + contextRegExp: /bip39\/src$/, + }), + // Ignore countries-intl + new webpack.IgnorePlugin({ + resourceRegExp: /countries-intl.json$/, + }), + // Copy static web-facing files + new CopyPlugin({ + patterns: [ + { from: 'manifest.json' }, + { from: 'assets/icons' }, + { from: 'assets/logo_opaque-dark-bg.png' }, + { from: 'scripts/native-worker.js' }, + ], + }), + new PreloadWebpackPlugin({ + // This is something made up, it's just to get the + // bundle name in the service worker + rel: 'serviceworkprefetch', + include: 'all', + fileWhitelist: [/\.wasm$/, /(pivx-shield|util)/], + }), + ], +}; diff --git a/webpack.dev.js b/webpack.dev.js new file mode 100644 index 000000000..db540ee95 --- /dev/null +++ b/webpack.dev.js @@ -0,0 +1,53 @@ +/* istanbul ignore file */ +/* eslint-env node */ +/* eslint @typescript-eslint/no-var-requires: "off" */ + +import path from 'path'; +import { merge } from 'webpack-merge'; +import common from './webpack.common.js'; +import webpack from 'webpack'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; +import { readFileSync } from 'fs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const commitHash = execSync('git rev-parse --short HEAD').toString().trim(); +const version = JSON.parse( + readFileSync('./package.json', { encoding: 'utf8' }) +).version; + +export default merge(common, { + mode: 'development', + devServer: { + static: { + directory: path.join(__dirname, './'), + watch: { + ignored: [ + // ignore changes in '.git' subdirectory (prevent constant hot-reloading in auto-fetch configurations) + '**/.git', + /node_modules/, + /coverage/, + ], + }, + }, + compress: true, + port: 5500, + hot: true, + allowedHosts: ['all'], + client: { + overlay: false, + }, + headers: { + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin', + }, + }, + plugins: [ + new webpack.DefinePlugin({ + __VUE_OPTIONS_API__: false, + __VUE_PROD_DEVTOOLS__: true, + VERSION: JSON.stringify(`${version}-${commitHash}`), + }), + ], +}); diff --git a/webpack.prod.js b/webpack.prod.js new file mode 100644 index 000000000..9d11b38b7 --- /dev/null +++ b/webpack.prod.js @@ -0,0 +1,28 @@ +/* istanbul ignore file */ +/* eslint-env node */ +/* eslint @typescript-eslint/no-var-requires: "off" */ + +import { merge } from 'webpack-merge'; +import common from './webpack.common.js'; +import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; +import webpack from 'webpack'; +import { readFileSync } from 'fs'; + +const version = JSON.parse( + readFileSync('./package.json', { encoding: 'utf8' }) +).version; + +export default merge(common, { + mode: 'production', + optimization: { + // Inject a CSS minimizer alongside the default JS minimizer (the '...' is the inclusion of the default webpack JS minimizer!) + minimizer: [new CssMinimizerPlugin(), '...'], + }, + plugins: [ + new webpack.DefinePlugin({ + __VUE_OPTIONS_API__: false, + __VUE_PROD_DEVTOOLS__: false, + VERSION: JSON.stringify(version), + }), + ], +});