Babel integration (#1247)
* Default bitcoind timeout to 60s for all platforms * Create bitcoind_setup_tasks.py * Missing prop setter bugfix Fixes #1221 * Early progress check-in * Docs update, initial FR translation file * Lots more text wrapping, babel.cfg fix for html * docs update * Updated translator procedures, added initial German, Hebrew, and Portugese files * Update README.md * Update README.md * Update messages.po Started the translation process using Poedit. Lines * Test first modif in ES First modification (only adding one line here) using Poedit and following the translation steps given by kdmukai. If everything is working well here I am ready to start translating. * Update messages.po * Bugfix; multi-language mnemonic support * Explicit language selection * Translation support for strings with variable insertions * Bugfixes; define supported langages in only one place * Cleanup * Update messages.po * Update messages.po * Latest translations integrated * Regenerating messages; disabling fuzzy matching * Compiled messages * template bugfixes * Use jinja's "tojson" to safely escape account_map device labels * Update wallet_pdf.jinja * Update helpers.py * Update node.py * Bugfixes * Updated requirements.txt; bugfix on language selection on Settings * Update wallets.py * Require `mnemonic==0.19` for multi-language BIP39 wordlists * Update iw translation * "he" language code fix for Hebrew * Update README.md * Adding blank Greek file; minor README update * Update messages.po 50% on this ... My ass is flat. * Spanish translation file updated * Update messages.po * Extracted from the latest code; recompiled with the latest translations * Added missing wrapped strings * Update messages.po * Updated Spanish translations * Adding blank Dutch ("nl") translation file * Blank Korean ("ko") translation file * Blank Portugese ("pt") file updated * Blank Turkish ("tr") translation file * Adding blank Serbian ("sr") translation file * Update messages.po * Compiled latest Spanish translations * Translation progress Translated the first 181 strings * Blank Italian ("it") translation file * More translation work done 6667810 strings translated. Nice number, guess I'll stop here :) * Compiled first batch of German translations * Update messages.po * Translation 100% All done. Would be extra pleased if my name popped up somewhere in credits. Not required though :) * Update messages.po * Update messages.po All done !!! :) * Compiled in initial Greek translations * Updated completion of German translation * Initial Dutch translations compiled in * Compiled full Spanish translations * Re-ran extraction, update, compile * Update Dutch translation * Blank Icelandic ("is") translation file * Blank Chinese (zh_Hans_CN and zh_hant_TW) translation files * Compiled latest Dutch translations * Add missing strings to translation files * Blank Swedish (sv) translation file * Blank Polish ("pl") translation file * Blank Bulgarian ("bg") translation file * Blank Russian ("ru") translation file * Swedish 30% done * Update messages.po * Blank Croatian ("hr") translation file * Delete messages.mo * Added translator Also fixed some warnings in poedit * Babel docs update * Compiled latest Dutch and Swedish translations * Update messages.po Completed 100% * Update wallets.py * Compiled in latest German translations * Update messages.po * Extracted, updated, compiled translation files * Compiled in first batch of Polish translations * Update messages.po More translations. * Compiled latest Polish translations * Hindi, Tamil, Malayalam translation files * Completed Swedish translation * 80% complete * Blank Finnish ("fi") translation file * Compiled latest Swedish translations * Full update and recompile of Swedish * Compiled Polish translations * Full update and recompile for Polish * Update messages.po * Updating Poedit docs * Removed word wrap; compiled latest Dutch * Hindi Translation --initial * updated w/no line wrap; compiled first Hindi translations * Update messages.po * Update messages.po Initial and partial test Bulgarian commit * updated and compiled initial Bulgarian translations. * Bugfix on text wrapping * Update messages.po * Update messages.po BG translation ~25% complete * Fully completed polish translation (first draft) Probably needs some further tweaking here and there. * Start Russian translation * Russian translation 30% complete * Format fixes * Compiled latest French translations * Compiled latest Chinese (Traditional) translations * Compiled latest Bulgarian translations * Compiled latest Polish translations * Compiled latest Russian translations * Update French to 36% * Update messages.po * Compiled in latest French translations * Compiled latest Dutch translations * Russian 70% complete * Update messages.po * Russian 100% complete * Update French to 44% Slowly but surely * Adds in-progress languages; latest Russian translations compiled * Extracted latest; updated Russian translation file. * Compiled latest Chinese (Traditional) translations * Updated and compiled Chinese (Traditional) * Compiled latest Russian translations * Updated and compiled latest French translations * Updated all translation files * Fix double quotes format issue Co-authored-by: Kim Neunert <k9ert@gmx.de> Co-authored-by: 2a3dex <58116837+2a3dex@users.noreply.github.com> Co-authored-by: rafa1239 <raphael.altieri@gmail.com> Co-authored-by: benk10 <ben.kaufman10@gmail.com> Co-authored-by: Volker Herminghaus <volker@bitcorn.wtf> Co-authored-by: glowleaf <69010742+glowleaf@users.noreply.github.com> Co-authored-by: DirkVdk <86686185+DirkVdk@users.noreply.github.com> Co-authored-by: mutatrum <mutatrum@gmail.com> Co-authored-by: Kryptoministern <77808545+Kryptoministern@users.noreply.github.com> Co-authored-by: PommbearBTC <86745004+PommbearBTC@users.noreply.github.com> Co-authored-by: Gummybear <holuan@gmail.com> Co-authored-by: sreshta suresh <sreshtasuresh@gmail.com> Co-authored-by: KST-Energy <82534243+KST-Energy@users.noreply.github.com> Co-authored-by: alltheseas <64376233+alltheseas@users.noreply.github.com> Co-authored-by: dudezoo <86764634+dudezoo@users.noreply.github.com> Co-authored-by: Sergei Tikhomirov <sergey.s.tikhomirov@gmail.com> Co-authored-by: Bitpaint <67663265+bitpaint@users.noreply.github.com>
178
babel/README.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# Babel multi-language support
|
||||
|
||||
|
||||
## For Translators
|
||||
Many of you translator volunteers will be new to git/github so we'll start with the basics:
|
||||
|
||||
|
||||
### Create github account, install Github Desktop
|
||||
Install the [Github Desktop app](https://desktop.github.com/). This is an easy way to manage your changes to an open source project.
|
||||
|
||||
When Github Desktop starts up it'll ask you to log in to your github account. If you don't already have an account, create one now.
|
||||
|
||||
|
||||
### Clone the Specter Desktop repo
|
||||
Now you're going to grab a copy of the full Specter Desktop source code, which includes the translation files.
|
||||
|
||||
Open the Github Desktop app and add the Specter Desktop repo via "Clone a Repository". On the "URL" tab type in ~~"cryptoadvance/specter-desktop"~~ (**for now we have to point to my work-in-progress repo: "kdmukai/specter-desktop"**):
|
||||
|
||||
<img src="img/add_repo.png">
|
||||
|
||||
Select where you want to save the source code on your local computer and hit "Clone".
|
||||
|
||||
When you're presented with the following choice, select "For my own purposes":
|
||||
|
||||
<img src="img/github_desktop_setup_01.jpg">
|
||||
|
||||
_When the clone is finished, click on the "Current Branch" button at the top middle and change to "origin/babel_integration" to point to my work-in-progress branch_
|
||||
|
||||
|
||||
### Editing a translation file
|
||||
Before you begin editing, ALWAYS go to Github Desktop and click the "Fetch origin" button at the top right. This pulls down all the latest changes from the repo. We'll all try our best to make sure that only one person is making changes at a time on a given translation file but if a change is submitted ahead of you, "Fetch origin" will grab it and get you in sync. We want to avoid messy conflicts where two or more people have edited the same translation file.
|
||||
|
||||
You'll be working with a `messages.po` file for each specific language. This file lists all the text that is displayed in Specter in English as a series of "msgid"s. Your job is to complete the matching translation in the "msgstr" field.
|
||||
|
||||
The `messages.po` text format is fairly straightforward but can be a little hard to work with, especially for multi-line phrases. Instead, we recommend that translators use the free tool [Poedit](https://poedit.net/download).
|
||||
|
||||
Once it's installed, go to Poedit preferences and uncheck "Wrap at":
|
||||
|
||||
<img src="img/poedit_01.png">
|
||||
|
||||
Navigate to the directory where you cloned the repo. The translation files can be found organized by two-letter language code (e.g. Spanish = "es") under `src/cryptoadvance/specter/translations`.
|
||||
|
||||
In Poedit open the `messages.po` file for your target langage (e.g. Spanish: [src/cryptoadvance/specter/translations/es/LC_MESSAGES/messages.po](../src/cryptoadvance/specter/translations/es/LC_MESSAGES/messages.po)).
|
||||
|
||||
Add or update translations, then save your changes.
|
||||
|
||||
|
||||
### Editing Guidelines
|
||||
* It's okay to leave some lines blank! If you can't provide a proper translation, just skip it and other translators will fill them in later. A blank entry will default to just displaying the original English.
|
||||
|
||||
* Some of the text strings have html or other code-related markup within them:
|
||||
```
|
||||
Please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank">Telegram</a> to fix it.
|
||||
```
|
||||
|
||||
Your translations must preserve all of the html markup. If you are unfamiliar with html markup, feel free to just skip these entries.
|
||||
|
||||
* You'll also see passages with markers for where text will be dynamically inserted:
|
||||
```
|
||||
"Hello {}, welcome back!"
|
||||
```
|
||||
In this example case the user's name will be inserted in the curly brackets. Your translation must preserve the curly brackets.
|
||||
|
||||
This is used in a lot of our error reporting:
|
||||
```
|
||||
"Wallet failed to load. {}"
|
||||
```
|
||||
The brackets at the end will display an error code, debugging info, etc.
|
||||
|
||||
* Much of the text will include some very technical terms. If you're unfamiliar with the term in English or don't know its equivalent in your target language (e.g. "derivation path" or "hash"), review other occurrences of that word in existing translations in the `messages.po` file or ask in the Telegram group. Obviously we want to try to use the most recognizable, common term in each language, even though there won't necessarily be a standard set.
|
||||
|
||||
And many languages will most likely just keep many of the technical words the same (e.g. "xpub").
|
||||
|
||||
* The strings are presented out of context and a word may have many possible translations. If you can't figure out the right usage, ask for help in the Telegram group. Each string includes the exact line number where it was found in the code (right-click on it in Poedit and you'll see "Code Occurrences"). Include this info and others can help you get a better sense of how it's being used in that context. You can also directly review that line of code yourself (click on the entry in "Code Occurrences") but that may be bewildering if you are not a developer.
|
||||
|
||||
|
||||
### Submitting your changes
|
||||
The updated `messages.po` file should now show up in Github Desktop as having changes.
|
||||
|
||||
<img src="img/github_desktop_1.png">
|
||||
|
||||
_Note: You'll see the app offering to "create a fork". Do not create a fork! We want your local copy of the Specter Desktop code to remain connected to our repo, not a forked version that you'll have to manage._
|
||||
|
||||
**_The next steps will break from typical `git` procedures in order to keep things simple for our non-programmer translators._**
|
||||
|
||||
In a web browser go to github's latest version of the translation file you were just working on. Remember that the directories are organized by the two-letter language codes. So for Spanish ("es"):
|
||||
[https://github.com/kdmukai/specter-desktop/tree/babel_integration/src/cryptoadvance/specter/translations/es/LC_MESSAGES/messages.po](https://github.com/kdmukai/specter-desktop/tree/babel_integration/src/cryptoadvance/specter/translations/es/LC_MESSAGES/messages.po)
|
||||
|
||||
Click on the pencil icon to edit the translation file:
|
||||
|
||||
<img src="img/github_edit_1.png">
|
||||
|
||||
You can now directly edit the file. But we want to use the version you just updated in Poedit.
|
||||
|
||||
On your computer, navigate to the `messages.po` file and open it with a simple text editor (macOS: TextEdit; Windows: Notepad). We have to make sure that the text editor doesn't change the file when it opens it (more complex editors might try to reformat the file). Copy the entire contents of the file.
|
||||
|
||||
Back in your browser, delete all the content in the editing window (click into the editing area and CTRL-A to select all, then delete), then paste your latest text into the online editing window.
|
||||
|
||||
If everything went correctly, you should just see your edits when you click the "Preview changes" tab just above the online editing window.
|
||||
|
||||
<img src="img/github_edit_2.png">
|
||||
|
||||
Any changed lines will be highlighted. Red highlights are deletions, green are additions.
|
||||
|
||||
Scroll to the bottom of that web page to the "Commit changes section". If your update addresses a specific issue that was raised in the repo's "Issues" section, reference it by number in the optional extended description box using this format: "Fixes #1234" (using the actual issue number, obv).
|
||||
|
||||
Select the option to "Create a new branch" and then click "Propose changes":
|
||||
|
||||
<img src="img/github_edit_3.png">
|
||||
|
||||
Then on the next screen follow the prompts to create your pull request and submit it to the project.
|
||||
|
||||
A reviewer on the Specter Desktop team will review your changes and (hopefully) accept the changes. Your contributions will be merged into the main Specter Desktop code repository. You will officially be a Specter Desktop open source contributor!
|
||||
|
||||
|
||||
|
||||
## For Developers:
|
||||
### "Wrapping" text for translation
|
||||
All you have to do in your code is wrap each piece of English text with the `ugettext` shorthand `_()`:
|
||||
* Wrap jinja template text: `<p>Hello, world!</p>` becomes `<p>{{ _('Hello, world!') }}</p>`
|
||||
* Wrap python strings: `error="No device was selected."` becomes `error=_("No device was selected.")`
|
||||
* Use `.format()` to wrap strings with variable injections:
|
||||
```
|
||||
mystr = f"My dad's name is {dad.name} and my name is {self.name}."
|
||||
mystr = _("My dad's name is {} and my name is {}").format(dad.name, self.name)
|
||||
```
|
||||
|
||||
If there are a lot of variables to inject, placeholder names can be used:
|
||||
```
|
||||
mystr = _("My dad's name is {dad_name} and my name is {my_name}").format(dad_name=dad.name, my_name=self.name)
|
||||
```
|
||||
|
||||
|
||||
### Rescanning for text that needs translations
|
||||
Re-generate the `messages.pot` file:
|
||||
```
|
||||
pybabel extract -F babel/babel.cfg -o babel/messages.pot .
|
||||
```
|
||||
This will rescan all wrapped text, picking up new strings as well as updating existings strings that have been edited.
|
||||
|
||||
Then run `update`:
|
||||
```
|
||||
pybabel update -N --no-wrap -i babel/messages.pot -d src/cryptoadvance/specter/translations
|
||||
|
||||
# Or target a specific language code:
|
||||
pybabel update -N --no-wrap -i babel/messages.pot -d src/cryptoadvance/specter/translations -l es
|
||||
```
|
||||
_note: the `-N` flag prevents babel from trying to use fuzzy matching to re-use existing translations for new strings. The fuzzy matching does not seem to do what we would want so we keep it disabled._
|
||||
|
||||
Any newly wrapped text strings will be added to each `messages.po` file. Altered strings will be flagged as needing review to see if the existing translations can still be used.
|
||||
|
||||
Once the next round of translations is complete, recompile the results:
|
||||
```
|
||||
pybabel compile -d src/cryptoadvance/specter/translations
|
||||
|
||||
# Or target a specific language code:
|
||||
pybabel compile -d src/cryptoadvance/specter/translations -l es
|
||||
```
|
||||
|
||||
|
||||
### Adding support for another language
|
||||
Assuming you have `extract`ed an updated `messages.pot`, all you have to do is generate the initial version of each new language file:
|
||||
```
|
||||
pybabel init -i babel/messages.pot -d src/cryptoadvance/specter/translations -l es
|
||||
pybabel init -i babel/messages.pot -d src/cryptoadvance/specter/translations -l fr
|
||||
pybabel init -i babel/messages.pot -d src/cryptoadvance/specter/translations -l de
|
||||
```
|
||||
|
||||
Then `update` and `compile` as above.
|
||||
|
||||
The only other step is to add the new language to the LANGUAGES list in `config.py`; the UI droplist automatically populates from that list. By convention use the native way to present the language:
|
||||
```
|
||||
# wrong:
|
||||
"he": "Hebrew",
|
||||
|
||||
# right:
|
||||
"he": "עברית",
|
||||
```
|
||||
4
babel/babel.cfg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[python: src/**/**.py]
|
||||
[jinja2: src/**/templates/**.html]
|
||||
[jinja2: src/**/templates/**.jinja]
|
||||
extensions=jinja2.ext.autoescape,jinja2.ext.with_
|
||||
BIN
babel/img/add_repo.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
babel/img/github_desktop_1.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
babel/img/github_desktop_setup_01.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
babel/img/github_edit_1.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
babel/img/github_edit_2.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
babel/img/github_edit_3.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
babel/img/poedit_01.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
4531
babel/messages.pot
Normal file
|
|
@ -3,6 +3,7 @@ chardet==3.0.4
|
|||
Click==7.1.2
|
||||
daemonize==2.5.0
|
||||
Flask==1.1.2
|
||||
Flask-Babel==2.0.0
|
||||
Flask-Cors==3.0.9
|
||||
Flask-Login==0.5.0
|
||||
hwi==2.0.1
|
||||
|
|
@ -19,3 +20,4 @@ pyopenssl==20.0.1
|
|||
flask_wtf==0.14.3
|
||||
pgpy==0.5.3
|
||||
cbor==1.0.0
|
||||
mnemonic==0.19
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# This file is autogenerated by pip-compile with python 3.8
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile --generate-hashes requirements.in
|
||||
#
|
||||
babel==2.9.1 \
|
||||
--hash=sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9 \
|
||||
--hash=sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0
|
||||
# via flask-babel
|
||||
base58==2.0.1 \
|
||||
--hash=sha256:365c9561d9babac1b5f18ee797508cd54937a724b6e419a130abad69cec5ca79 \
|
||||
--hash=sha256:447adc750d6b642987ffc6d397ecd15a799852d5f6a1d308d384500243825058
|
||||
|
|
@ -103,6 +107,19 @@ ecdsa==0.16.1 \
|
|||
embit==0.4.2 \
|
||||
--hash=sha256:d67fc0f7fbdb7588c3eb24441bf8e05770056260bc8e5537399a1b3ce5ccf12a
|
||||
# via -r requirements.in
|
||||
flask==1.1.2 \
|
||||
--hash=sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060 \
|
||||
--hash=sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-babel
|
||||
# flask-cors
|
||||
# flask-login
|
||||
# flask-wtf
|
||||
flask-babel==2.0.0 \
|
||||
--hash=sha256:e6820a052a8d344e178cdd36dd4bb8aea09b4bda3d5f9fa9f008df2c7f2f5468 \
|
||||
--hash=sha256:f9faf45cdb2e1a32ea2ec14403587d4295108f35017a7821a2b1acb8cfd9257d
|
||||
# via -r requirements.in
|
||||
flask-cors==3.0.9 \
|
||||
--hash=sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8 \
|
||||
--hash=sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324
|
||||
|
|
@ -111,14 +128,6 @@ flask-login==0.5.0 \
|
|||
--hash=sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b \
|
||||
--hash=sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0
|
||||
# via -r requirements.in
|
||||
flask==1.1.2 \
|
||||
--hash=sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060 \
|
||||
--hash=sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask-cors
|
||||
# flask-login
|
||||
# flask-wtf
|
||||
flask_wtf==0.14.3 \
|
||||
--hash=sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2 \
|
||||
--hash=sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720
|
||||
|
|
@ -169,7 +178,9 @@ itsdangerous==1.1.0 \
|
|||
jinja2==2.11.3 \
|
||||
--hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \
|
||||
--hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6
|
||||
# via flask
|
||||
# via
|
||||
# flask
|
||||
# flask-babel
|
||||
libusb1==1.8 \
|
||||
--hash=sha256:240f65ac70ba3fab77749ec84a412e4e89624804cb80d6c9d394eef5af8878d6
|
||||
# via hwi
|
||||
|
|
@ -210,15 +221,15 @@ markupsafe==1.1.1 \
|
|||
# via
|
||||
# jinja2
|
||||
# wtforms
|
||||
mnemonic==0.18 \
|
||||
--hash=sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d
|
||||
# via hwi
|
||||
mnemonic==0.19 \
|
||||
--hash=sha256:4e37eb02b2cbd56a0079cabe58a6da93e60e3e4d6e757a586d9f23d96abea931 \
|
||||
--hash=sha256:a8d78c5100acfa7df9bab6b9db7390831b0e54490934b718ff9efd68f0d731a6
|
||||
# via
|
||||
# -r requirements.in
|
||||
# hwi
|
||||
noiseprotocol==0.3.1 \
|
||||
--hash=sha256:2e1a603a38439636cf0ffd8b3e8b12cee27d368a28b41be7dbe568b2abb23111
|
||||
# via bitbox02
|
||||
pbkdf2==1.3 \
|
||||
--hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979
|
||||
# via mnemonic
|
||||
pgpy==0.5.3 \
|
||||
--hash=sha256:a49c269cedcaf82ac6999bcae5fd3f543ecb1c759f9d48a15ad8d8fa4ac03987 \
|
||||
--hash=sha256:cba6fbbb44a896a8a4f5807b3d8d4943a8f7a6607be11587f4a27734c711c1dd
|
||||
|
|
@ -284,6 +295,12 @@ python-dotenv==0.13.0 \
|
|||
--hash=sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7 \
|
||||
--hash=sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74
|
||||
# via -r requirements.in
|
||||
pytz==2021.1 \
|
||||
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da \
|
||||
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798
|
||||
# via
|
||||
# babel
|
||||
# flask-babel
|
||||
requests==2.25.0 \
|
||||
--hash=sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8 \
|
||||
--hash=sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998
|
||||
|
|
|
|||
|
|
@ -104,6 +104,24 @@ class BaseConfig(object):
|
|||
},
|
||||
}
|
||||
|
||||
# Babel integration. English listed first; other alphabetical by language code
|
||||
LANGUAGES = {
|
||||
"en": "English",
|
||||
"bg": "Български",
|
||||
"de": "Deutsch",
|
||||
"el": "Ελληνικά",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"he": "עברית",
|
||||
"hi": "हिंदी",
|
||||
"nl": "Nederlands",
|
||||
"pl": "Polski",
|
||||
"ru": "Русский",
|
||||
"sv": "Svenska",
|
||||
"zh_Hans_CN": "简体中文",
|
||||
"zh_Hant_TW": "繁體中文",
|
||||
}
|
||||
|
||||
|
||||
class DevelopmentConfig(BaseConfig):
|
||||
# https://stackoverflow.com/questions/22463939/demystify-flask-app-secret-key
|
||||
|
|
@ -112,6 +130,9 @@ class DevelopmentConfig(BaseConfig):
|
|||
os.getenv("SPECTER_DATA_FOLDER", "~/.specter_dev")
|
||||
)
|
||||
|
||||
# Env vars take priority over config settings so ensure that this is set
|
||||
os.environ["FLASK_ENV"] = "development"
|
||||
|
||||
|
||||
class TestConfig(BaseConfig):
|
||||
SECRET_KEY = "test key"
|
||||
|
|
|
|||
|
|
@ -265,11 +265,39 @@ def get_startblock_by_chain(specter):
|
|||
|
||||
|
||||
# Hot wallet helpers
|
||||
def generate_mnemonic(strength=256):
|
||||
# Generate words list
|
||||
mnemo = Mnemonic("english")
|
||||
words = mnemo.generate(strength=strength)
|
||||
return words
|
||||
|
||||
MNEMONIC_LANGUAGES = {
|
||||
"en": "english",
|
||||
"es": "spanish",
|
||||
"fr": "french",
|
||||
"it": "italian",
|
||||
# "jp": "japanese",
|
||||
# "ko": korean",
|
||||
# "?": chinese_simplified",
|
||||
# "?": chinese_traditional",
|
||||
}
|
||||
|
||||
|
||||
def initialize_mnemonic(language_code):
|
||||
if language_code not in MNEMONIC_LANGUAGES:
|
||||
# Fall back to English if Mnemonic doesn't support the current language
|
||||
logger.debug(
|
||||
f"Language code '{language_code}' not supported by python-mnemonic; using English"
|
||||
)
|
||||
language_code = "en"
|
||||
return Mnemonic(language=MNEMONIC_LANGUAGES[language_code])
|
||||
|
||||
|
||||
def generate_mnemonic(strength=256, language_code="en"):
|
||||
mnemo = initialize_mnemonic(language_code)
|
||||
return mnemo.generate(strength=strength)
|
||||
|
||||
|
||||
def validate_mnemonic(words):
|
||||
# We cannot assume the mnemonic will be in the same language currently active
|
||||
# in the UI (e.g. a Spanish user is likely to have an English mnemonic).
|
||||
mnemo = initialize_mnemonic(Mnemonic.detect_language(words))
|
||||
return mnemo.check(words)
|
||||
|
||||
|
||||
def notify_upgrade(app, flash):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
import json
|
||||
import logging
|
||||
from flask_babel import lazy_gettext as _
|
||||
|
||||
from ..helpers import alias, load_jsons
|
||||
from ..rpc import get_default_datadir
|
||||
|
||||
|
|
@ -79,7 +81,7 @@ class DeviceManager:
|
|||
for device_name in self.devices:
|
||||
if self.devices[device_name].alias == device_alias:
|
||||
return self.devices[device_name]
|
||||
logger.error("Could not find Device %s" % device_alias)
|
||||
logger.error(_("Could not find Device {}").format(device_alias))
|
||||
|
||||
def remove_device(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import logging
|
|||
import os
|
||||
|
||||
from embit.liquid.networks import get_network
|
||||
from flask_babel import lazy_gettext as _
|
||||
from .helpers import is_testnet, is_liquid
|
||||
from .liquid.rpc import LiquidRPC
|
||||
from .persistence import write_node
|
||||
|
|
@ -312,7 +313,7 @@ class Node:
|
|||
"""
|
||||
rpc = self.get_rpc()
|
||||
if rpc is None:
|
||||
return {"out": "", "err": "Connection to node failed", "code": -1}
|
||||
return {"out": "", "err": _("Connection to node failed"), "code": -1}
|
||||
r = {}
|
||||
r["tests"] = {"connectable": False}
|
||||
r["err"] = ""
|
||||
|
|
@ -322,7 +323,7 @@ class Node:
|
|||
int(rpc.getnetworkinfo()["version"]) >= 200000
|
||||
)
|
||||
if not r["tests"]["recent_version"]:
|
||||
r["err"] = "Core Node might be too old"
|
||||
r["err"] = _("Core Node might be too old")
|
||||
|
||||
r["tests"]["connectable"] = True
|
||||
r["tests"]["credentials"] = True
|
||||
|
|
@ -339,7 +340,7 @@ class Node:
|
|||
logger.error("Caught an ConnectionError while test_rpc: %s", e)
|
||||
|
||||
r["tests"]["connectable"] = False
|
||||
r["err"] = "Failed to connect!"
|
||||
r["err"] = _("Failed to connect!")
|
||||
r["code"] = -1
|
||||
except RpcError as rpce:
|
||||
logger.error("Caught an RpcError while test_rpc: %s", rpce)
|
||||
|
|
@ -348,7 +349,7 @@ class Node:
|
|||
r["code"] = rpc.r.status_code
|
||||
if rpce.status_code == 401:
|
||||
r["tests"]["credentials"] = False
|
||||
r["err"] = "RPC authentication failed!"
|
||||
r["err"] = _("RPC authentication failed!")
|
||||
else:
|
||||
r["err"] = str(rpce.status_code)
|
||||
except Exception as e:
|
||||
|
|
@ -362,7 +363,7 @@ class Node:
|
|||
r["err"] = rpc.r["error"]
|
||||
r["code"] = rpc.r.status_code
|
||||
else:
|
||||
r["err"] = "Failed to connect"
|
||||
r["err"] = _("Failed to connect")
|
||||
r["code"] = -1
|
||||
return r
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import secrets
|
|||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, redirect, url_for
|
||||
from flask import Flask, redirect, request, url_for, jsonify, session
|
||||
from flask_babel import Babel
|
||||
from flask_login import LoginManager, login_user
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
|
||||
|
|
@ -26,6 +27,26 @@ load_dotenv(env_path)
|
|||
csrf = CSRFProtect()
|
||||
|
||||
|
||||
class SpecterFlask(Flask):
|
||||
@property
|
||||
def supported_languages(self):
|
||||
return self.config["LANGUAGES"]
|
||||
|
||||
def get_language_code(self):
|
||||
"""
|
||||
Helper for Babel and other related language selection tasks.
|
||||
"""
|
||||
if "language_code" in session:
|
||||
# Explicit selection
|
||||
return session["language_code"]
|
||||
else:
|
||||
# autodetect
|
||||
return request.accept_languages.best_match(self.supported_languages.keys())
|
||||
|
||||
def set_language_code(self, language_code):
|
||||
session["language_code"] = language_code
|
||||
|
||||
|
||||
def calc_module_name(config):
|
||||
"""tiny helper to make passing configs more convenient"""
|
||||
if "." in config:
|
||||
|
|
@ -58,11 +79,13 @@ def create_app(config=None):
|
|||
template_folder = os.path.join(sys._MEIPASS, "templates")
|
||||
static_folder = os.path.join(sys._MEIPASS, "static")
|
||||
logger.info("pyinstaller based instance running in {}".format(sys._MEIPASS))
|
||||
app = Flask(
|
||||
app = SpecterFlask(
|
||||
__name__, template_folder=template_folder, static_folder=static_folder
|
||||
)
|
||||
else:
|
||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||
app = SpecterFlask(
|
||||
__name__, template_folder="templates", static_folder="static"
|
||||
)
|
||||
app.jinja_env.autoescape = select_autoescape(default_for_string=True, default=True)
|
||||
logger.info(f"Configuration: {config}")
|
||||
app.config.from_object(config)
|
||||
|
|
@ -71,6 +94,7 @@ def create_app(config=None):
|
|||
)
|
||||
csrf.init_app(app)
|
||||
app.csrf = csrf
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
|
@ -142,6 +166,28 @@ def init_app(app, hwibridge=False, specter=None):
|
|||
return dict(tor_service_id="", tor_enabled=False)
|
||||
return dict(tor_service_id=app.tor_service_id, tor_enabled=app.tor_enabled)
|
||||
|
||||
# --------------------- Babel integration ---------------------
|
||||
babel = Babel(app)
|
||||
|
||||
@babel.localeselector
|
||||
def get_language_code():
|
||||
# Enables Babel to auto-detect current language
|
||||
return app.get_language_code()
|
||||
|
||||
@app.route("/set_language", methods=["POST"])
|
||||
def set_language_code():
|
||||
json_data = request.get_json()
|
||||
if (
|
||||
"language_code" in json_data
|
||||
and json_data["language_code"] in app.supported_languages
|
||||
):
|
||||
app.set_language_code(json_data["language_code"])
|
||||
return jsonify(success=True)
|
||||
else:
|
||||
return jsonify(success=False)
|
||||
|
||||
# --------------------- Babel integration ---------------------
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from flask import (
|
|||
jsonify,
|
||||
flash,
|
||||
)
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import login_required, current_user, logout_user
|
||||
from flask import current_app as app
|
||||
from ..helpers import alias
|
||||
|
|
@ -46,7 +47,9 @@ def login():
|
|||
)
|
||||
return redirect_login(request)
|
||||
flash(
|
||||
"We could not check your password, maybe Bitcoin Core is not running or not configured?",
|
||||
_(
|
||||
"We could not check your password, maybe Bitcoin Core is not running or not configured?"
|
||||
),
|
||||
"error",
|
||||
)
|
||||
app.logger.info("AUDIT: Failed to check password")
|
||||
|
|
@ -79,7 +82,7 @@ def login():
|
|||
app.login(user.id)
|
||||
return redirect_login(request)
|
||||
# Either invalid method or incorrect credentials
|
||||
flash("Invalid username or password", "error")
|
||||
flash(_("Invalid username or password"), "error")
|
||||
app.logger.info("AUDIT: Invalid password login attempt")
|
||||
return (
|
||||
render_template(
|
||||
|
|
@ -108,14 +111,16 @@ def register():
|
|||
otp = request.form["otp"]
|
||||
if not username:
|
||||
flash(
|
||||
"Please enter a username.",
|
||||
_("Please enter a username."),
|
||||
"error",
|
||||
)
|
||||
return redirect("register?otp={}".format(otp))
|
||||
min_chars = int(app.specter.config["auth"]["password_min_chars"])
|
||||
if not password or len(password) < min_chars:
|
||||
flash(
|
||||
"Please enter a password of a least {} characters.".format(min_chars),
|
||||
_("Please enter a password of a least {} characters.").format(
|
||||
min_chars
|
||||
),
|
||||
"error",
|
||||
)
|
||||
return redirect("register?otp={}".format(otp))
|
||||
|
|
@ -126,7 +131,9 @@ def register():
|
|||
i += 1
|
||||
user_id = "{}{}".format(alias(username), i)
|
||||
if app.specter.user_manager.get_user_by_username(username):
|
||||
flash("Username is already taken, please choose another one", "error")
|
||||
flash(
|
||||
_("Username is already taken, please choose another one"), "error"
|
||||
)
|
||||
return redirect("register?otp={}".format(otp))
|
||||
app.specter.otp_manager.remove_new_user_otp(otp)
|
||||
config = {
|
||||
|
|
@ -137,14 +144,18 @@ def register():
|
|||
user = User(user_id, username, password_hash, config, app.specter)
|
||||
app.specter.user_manager.add_user(user)
|
||||
flash(
|
||||
"You have registered successfully, \
|
||||
_(
|
||||
"You have registered successfully, \
|
||||
please login with your new account to start using Specter"
|
||||
)
|
||||
)
|
||||
return redirect(url_for("auth_endpoint.login"))
|
||||
else:
|
||||
flash(
|
||||
"Invalid registration link, \
|
||||
please request a new link from the node operator.",
|
||||
_(
|
||||
"Invalid registration link, \
|
||||
please request a new link from the node operator."
|
||||
),
|
||||
"error",
|
||||
)
|
||||
return redirect("register?otp={}".format(otp))
|
||||
|
|
@ -154,13 +165,13 @@ please request a new link from the node operator.",
|
|||
@auth_endpoint.route("/logout", methods=["GET", "POST"])
|
||||
def logout():
|
||||
logout_user()
|
||||
flash("You were logged out", "info")
|
||||
flash(_("You were logged out"), "info")
|
||||
return redirect(url_for("auth_endpoint.login"))
|
||||
|
||||
|
||||
################### Util ######################
|
||||
def redirect_login(request):
|
||||
flash("Logged in successfully.", "info")
|
||||
flash(_("Logged in successfully."), "info")
|
||||
if request.form.get("next") and request.form.get("next") != "None":
|
||||
response = redirect(request.form["next"])
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from flask import make_response
|
|||
from flask_wtf.csrf import CSRFError
|
||||
from werkzeug.exceptions import MethodNotAllowed
|
||||
from flask import render_template, request, redirect, url_for, flash, Markup
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import login_required, current_user
|
||||
from ..helpers import (
|
||||
generate_mnemonic,
|
||||
|
|
@ -50,10 +51,11 @@ def server_rpc_error(rpce):
|
|||
"""Specific SpecterErrors get passed on to the User as flash"""
|
||||
if rpce.error_code == -18: # RPC_WALLET_NOT_FOUND
|
||||
flash(
|
||||
"Wallet not found. Specter reloaded all Wallets, please try again.", "error"
|
||||
_("Wallet not found. Specter reloaded all wallets, please try again."),
|
||||
"error",
|
||||
)
|
||||
else:
|
||||
flash(f"BitcoinCore RpcError: {str(rpce)}", "error")
|
||||
flash(_("Bitcoin Core RpcError: {}").format(str(rpce)), "error")
|
||||
try:
|
||||
app.specter.wallet_manager.update()
|
||||
except SpecterError as se:
|
||||
|
|
@ -94,7 +96,9 @@ def server_error_timeout(e):
|
|||
app.specter.check()
|
||||
app.logger.error("ExternalProcessTimeoutException: %s" % e)
|
||||
flash(
|
||||
"Bitcoin Core is not coming up in time. Maybe it's just slow but please check the logs below",
|
||||
_(
|
||||
"Bitcoin Core is not coming up in time. Maybe it's just slow but please check the logs below"
|
||||
),
|
||||
"warn",
|
||||
)
|
||||
return redirect(
|
||||
|
|
@ -113,7 +117,7 @@ def server_error_csrf(e):
|
|||
app.logger.error("CSRF Exception: %s" % e)
|
||||
trace = traceback.format_exc()
|
||||
app.logger.error(trace)
|
||||
flash("Session expired. Please refresh and try again.", "error")
|
||||
flash(_("Session expired. Please refresh and try again."), "error")
|
||||
return redirect(request.url)
|
||||
|
||||
|
||||
|
|
@ -123,7 +127,7 @@ def server_error_405(e):
|
|||
app.logger.error("405 MethodNotAllowed Exception: %s" % e)
|
||||
trace = traceback.format_exc()
|
||||
app.logger.error(trace)
|
||||
flash("Session expired. Please refresh and try again.", "error")
|
||||
flash(_("Session expired. Please refresh and try again."), "error")
|
||||
return redirect(request.url)
|
||||
|
||||
|
||||
|
|
@ -180,7 +184,12 @@ def about():
|
|||
app.specter.reset_setup("bitcoind")
|
||||
app.specter.reset_setup("torbrowser")
|
||||
|
||||
return render_template("base.jinja", specter=app.specter, rand=rand)
|
||||
return render_template(
|
||||
"base.jinja",
|
||||
specter=app.specter,
|
||||
rand=rand,
|
||||
supported_languages=app.supported_languages,
|
||||
)
|
||||
|
||||
|
||||
# TODO: Move all these below to REST API
|
||||
|
|
@ -206,7 +215,12 @@ def wallets_loading():
|
|||
@app.route("/generatemnemonic/", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def generatemnemonic():
|
||||
return {"mnemonic": generate_mnemonic(strength=int(request.form["strength"]))}
|
||||
return {
|
||||
"mnemonic": generate_mnemonic(
|
||||
strength=int(request.form["strength"]),
|
||||
language_code=app.get_language_code(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
################ RPC data utils ####################
|
||||
|
|
@ -300,5 +314,7 @@ def get_whitepaper():
|
|||
else:
|
||||
return render_template(
|
||||
"500.jinja",
|
||||
error="You need a mainnet node to retrieve the whitepaper. Check your node configurations.",
|
||||
error=_(
|
||||
"You need a mainnet node to retrieve the whitepaper. Check your node configurations."
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ from flask import (
|
|||
jsonify,
|
||||
flash,
|
||||
)
|
||||
from flask_login import login_required, current_user
|
||||
from flask import current_app as app
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import login_required, current_user
|
||||
from mnemonic import Mnemonic
|
||||
from ..helpers import is_testnet, generate_mnemonic
|
||||
from ..devices.bitcoin_core import BitcoinCore
|
||||
from ..helpers import is_testnet, generate_mnemonic, validate_mnemonic
|
||||
from ..key import Key
|
||||
from ..managers.device_manager import get_device_class
|
||||
from ..devices.bitcoin_core import BitcoinCore
|
||||
from ..managers.wallet_manager import purposes
|
||||
from ..specter_error import handle_exception
|
||||
|
||||
|
|
@ -60,9 +61,9 @@ def new_device_keys(device_type):
|
|||
else:
|
||||
device_name = request.form.get("device_name", "")
|
||||
if not device_name:
|
||||
err = "Device name must not be empty"
|
||||
err = _("Device name cannot be empty")
|
||||
elif device_name in app.specter.device_manager.devices_names:
|
||||
err = "Device with this name already exists"
|
||||
err = _("Device with this name already exists")
|
||||
xpubs_rows_count = int(request.form["xpubs_rows_count"]) + 1
|
||||
keys = []
|
||||
paths = []
|
||||
|
|
@ -80,12 +81,12 @@ def new_device_keys(device_type):
|
|||
try:
|
||||
keys.append(Key.parse_xpub(xpub, purpose=purpose))
|
||||
except:
|
||||
err = "Failed to parse these xpubs:\n" + "\n".join(xpub)
|
||||
err = _("Failed to parse these xpubs") + ":\n" + "\n".join(xpub)
|
||||
break
|
||||
if not keys and not err:
|
||||
if device_type in ["bitcoincore", "elementscore"]:
|
||||
if not paths:
|
||||
err = "No paths were specified, please provide at lease one."
|
||||
err = _("No paths were specified, please provide at least one.")
|
||||
if err is None:
|
||||
if existing_device:
|
||||
device.add_hot_wallet_keys(
|
||||
|
|
@ -98,7 +99,7 @@ def new_device_keys(device_type):
|
|||
keys_range=[range_start, range_end],
|
||||
keys_purposes=keys_purposes,
|
||||
)
|
||||
flash("{} keys were added successfully".format(len(paths)))
|
||||
flash(_("{} keys were added successfully").format(len(paths)))
|
||||
return redirect(
|
||||
url_for(
|
||||
"devices_endpoint.device", device_alias=device.alias
|
||||
|
|
@ -119,7 +120,7 @@ def new_device_keys(device_type):
|
|||
keys_range=[range_start, range_end],
|
||||
keys_purposes=keys_purposes,
|
||||
)
|
||||
flash("{} was added successfully!".format(device_name))
|
||||
flash(_("{} was added successfully!").format(device_name))
|
||||
return redirect(
|
||||
url_for(
|
||||
"devices_endpoint.device", device_alias=device.alias
|
||||
|
|
@ -128,7 +129,10 @@ def new_device_keys(device_type):
|
|||
)
|
||||
except Exception as e:
|
||||
handle_exception(e)
|
||||
flash(f"Failed to setup hot wallet. Error: {e}", "error")
|
||||
flash(
|
||||
_("Failed to setup hot wallet. Error: {}").format(e),
|
||||
"error",
|
||||
)
|
||||
app.specter.device_manager.remove_device(
|
||||
device,
|
||||
app.specter.wallet_manager,
|
||||
|
|
@ -136,11 +140,11 @@ def new_device_keys(device_type):
|
|||
chain=app.specter.chain,
|
||||
)
|
||||
else:
|
||||
err = "xpubs list must not be empty"
|
||||
err = _("xpubs list must not be empty")
|
||||
elif not err:
|
||||
if existing_device:
|
||||
device.add_keys(keys)
|
||||
flash("{} keys were added successfully".format(len(keys)))
|
||||
flash(_("{} keys were added successfully").format(len(keys)))
|
||||
return redirect(
|
||||
url_for("devices_endpoint.device", device_alias=device.alias)
|
||||
)
|
||||
|
|
@ -157,7 +161,7 @@ def new_device_keys(device_type):
|
|||
rand=rand,
|
||||
)
|
||||
else:
|
||||
flash("{} was added successfully!".format(device_name))
|
||||
flash(_("{} was added successfully!").format(device_name))
|
||||
return redirect(
|
||||
url_for("devices_endpoint.device", device_alias=device.alias)
|
||||
+ "?newdevice=true"
|
||||
|
|
@ -185,18 +189,21 @@ def new_device_keys(device_type):
|
|||
def new_device_mnemonic(device_type):
|
||||
err = None
|
||||
strength = 128
|
||||
mnemonic = generate_mnemonic(strength=strength)
|
||||
mnemonic = generate_mnemonic(
|
||||
strength=strength, language_code=app.get_language_code()
|
||||
)
|
||||
existing_device = None
|
||||
if request.method == "POST":
|
||||
if len(request.form["mnemonic"].split(" ")) not in [12, 15, 18, 21, 24]:
|
||||
err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words."
|
||||
mnemo = Mnemonic("english")
|
||||
if not mnemo.check(request.form["mnemonic"]):
|
||||
err = "Invalid mnemonic entered."
|
||||
err = _(
|
||||
"Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words."
|
||||
)
|
||||
if not validate_mnemonic(words=request.form["mnemonic"]):
|
||||
err = _("Invalid mnemonic entered.")
|
||||
range_start = int(request.form["range_start"])
|
||||
range_end = int(request.form["range_end"])
|
||||
if range_start > range_end:
|
||||
err = "Invalid address range selected."
|
||||
err = _("Invalid address range selected.")
|
||||
mnemonic = request.form["mnemonic"]
|
||||
passphrase = request.form["passphrase"]
|
||||
file_password = request.form["file_password"]
|
||||
|
|
@ -238,7 +245,7 @@ def device_blinding_key(device_alias):
|
|||
device = app.specter.device_manager.get_by_alias(device_alias)
|
||||
except:
|
||||
return render_template(
|
||||
"base.jinja", error="Device not found", specter=app.specter, rand=rand
|
||||
"base.jinja", error=_("Device not found"), specter=app.specter, rand=rand
|
||||
)
|
||||
if not device:
|
||||
return redirect(url_for("index"))
|
||||
|
|
@ -247,7 +254,7 @@ def device_blinding_key(device_alias):
|
|||
blinding_key = request.form.get("blinding_key")
|
||||
device.set_blinding_key(blinding_key)
|
||||
if not new_device:
|
||||
flash("Master blinding key was added successfully")
|
||||
flash(_("Master blinding key was added successfully"))
|
||||
return redirect(
|
||||
url_for("devices_endpoint.device", device_alias=device.alias)
|
||||
+ ("?newdevice=true" if new_device else "")
|
||||
|
|
@ -271,22 +278,24 @@ def new_device_manual():
|
|||
device_name = ""
|
||||
xpubs = ""
|
||||
strength = 128
|
||||
mnemonic = generate_mnemonic(strength=strength)
|
||||
mnemonic = generate_mnemonic(
|
||||
strength=strength, language_code=app.get_language_code()
|
||||
)
|
||||
if request.method == "POST":
|
||||
action = request.form["action"]
|
||||
device_type = request.form["device_type"]
|
||||
device_name = request.form["device_name"]
|
||||
if action == "newcolddevice":
|
||||
if not device_name:
|
||||
err = "Device name must not be empty"
|
||||
err = _("Device name cannot be empty")
|
||||
elif device_name in app.specter.device_manager.devices_names:
|
||||
err = "Device with this name already exists"
|
||||
err = _("Device with this name already exists")
|
||||
xpubs = request.form["xpubs"]
|
||||
if not xpubs:
|
||||
err = "xpubs name must not be empty"
|
||||
err = _("xpubs name cannot be empty")
|
||||
keys, failed = Key.parse_xpubs(xpubs)
|
||||
if len(failed) > 0:
|
||||
err = "Failed to parse these xpubs:\n" + "\n".join(failed)
|
||||
err = _("Failed to parse these xpubs") + ":\n" + "\n".join(failed)
|
||||
if err is None:
|
||||
device = app.specter.device_manager.add_device(
|
||||
name=device_name, device_type=device_type, keys=keys
|
||||
|
|
@ -296,18 +305,20 @@ def new_device_manual():
|
|||
)
|
||||
elif action == "newhotdevice":
|
||||
if not device_name:
|
||||
err = "Device name must not be empty"
|
||||
err = _("Device name cannot be empty")
|
||||
elif device_name in app.specter.device_manager.devices_names:
|
||||
err = "Device with this name already exists"
|
||||
err = _("Device with this name already exists")
|
||||
if len(request.form["mnemonic"].split(" ")) not in [12, 15, 18, 21, 24]:
|
||||
err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words."
|
||||
mnemo = Mnemonic("english")
|
||||
if not mnemo.check(request.form["mnemonic"]):
|
||||
err = "Invalid mnemonic entered."
|
||||
err = _(
|
||||
"Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words."
|
||||
)
|
||||
|
||||
if not validate_mnemonic(words=request.form["mnemonic"]):
|
||||
err = _("Invalid mnemonic entered.")
|
||||
range_start = int(request.form["range_start"])
|
||||
range_end = int(request.form["range_end"])
|
||||
if range_start > range_end:
|
||||
err = "Invalid address range selected."
|
||||
err = _("Invalid address range selected.")
|
||||
if err is None:
|
||||
mnemonic = request.form["mnemonic"]
|
||||
paths = [
|
||||
|
|
@ -336,7 +347,7 @@ def new_device_manual():
|
|||
)
|
||||
except Exception as e:
|
||||
handle_exception(e)
|
||||
flash(f"Failed to setup hot wallet. Error: {e}", "error")
|
||||
flash(_("Failed to setup hot wallet. Error: {}").format(e), "error")
|
||||
app.specter.device_manager.remove_device(
|
||||
device,
|
||||
app.specter.wallet_manager,
|
||||
|
|
@ -345,7 +356,9 @@ def new_device_manual():
|
|||
)
|
||||
elif action == "generatemnemonic":
|
||||
strength = int(request.form["strength"])
|
||||
mnemonic = generate_mnemonic(strength=strength)
|
||||
mnemonic = generate_mnemonic(
|
||||
strength=strength, language_code=app.get_language_code()
|
||||
)
|
||||
return render_template(
|
||||
"device/new_device_manual.jinja",
|
||||
device_type=device_type,
|
||||
|
|
@ -370,7 +383,7 @@ def device(device_alias):
|
|||
device = app.specter.device_manager.get_by_alias(device_alias)
|
||||
except:
|
||||
return render_template(
|
||||
"base.jinja", error="Device not found", specter=app.specter, rand=rand
|
||||
"base.jinja", error=_("Device not found"), specter=app.specter, rand=rand
|
||||
)
|
||||
if not device:
|
||||
return redirect(url_for("index"))
|
||||
|
|
@ -379,8 +392,17 @@ def device(device_alias):
|
|||
action = request.form["action"]
|
||||
if action == "forget":
|
||||
if len(wallets) != 0:
|
||||
err = "Device could not be removed since it is used in wallets: {}.<br>You must delete those wallets before you can remove this device.<br>You can delete a wallet from its Settings -> Advanced page.".format(
|
||||
[wallet.name for wallet in wallets]
|
||||
# TODO: Long message strings like this should be moved into a template.
|
||||
err = (
|
||||
_(
|
||||
"Device could not be removed since it is used in wallets: {}"
|
||||
).format([wallet.name for wallet in wallets])
|
||||
+ "<br>"
|
||||
+ _(
|
||||
"You must delete those wallets before you can remove this device."
|
||||
)
|
||||
+ "<br>"
|
||||
+ _("You can delete a wallet from its Settings -> Advanced page.")
|
||||
)
|
||||
else:
|
||||
app.specter.device_manager.remove_device(
|
||||
|
|
@ -394,24 +416,33 @@ def device(device_alias):
|
|||
key = Key.from_json({"original": request.form["key"]})
|
||||
wallets_with_key = [w for w in wallets if key in w.keys]
|
||||
if len(wallets_with_key) != 0:
|
||||
err = "Key could not be removed since it is used in wallets: {}.<br>You must delete those wallets before you can remove this key.<br>You can delete a wallet from its Settings -> Advanced page.".format(
|
||||
", ".join([wallet.name for wallet in wallets_with_key])
|
||||
# TODO: Long message strings like this should be moved into a template.
|
||||
err = (
|
||||
_(
|
||||
"Key could not be removed since it is used in wallets: {}"
|
||||
).format(", ".join([wallet.name for wallet in wallets_with_key]))
|
||||
+ "<br>"
|
||||
+ _("You must delete those wallets before you can remove this key.")
|
||||
+ "<br>"
|
||||
+ _("You can delete a wallet from its Settings -> Advanced page.")
|
||||
)
|
||||
else:
|
||||
device.remove_key(key)
|
||||
elif action == "rename":
|
||||
device_name = request.form["newtitle"]
|
||||
if not device_name:
|
||||
flash("Device name must not be empty", "error")
|
||||
flash(_("Device name cannot be empty"), "error")
|
||||
elif device_name == device.name:
|
||||
pass
|
||||
elif device_name in app.specter.device_manager.devices_names:
|
||||
flash("Device already exists", "error")
|
||||
flash(_("Device already exists"), "error")
|
||||
else:
|
||||
device.rename(device_name)
|
||||
elif action == "add_keys":
|
||||
strength = 128
|
||||
mnemonic = generate_mnemonic(strength=strength)
|
||||
mnemonic = generate_mnemonic(
|
||||
strength=strength, language_code=app.get_language_code()
|
||||
)
|
||||
if device.hot_wallet:
|
||||
return render_template(
|
||||
"device/new_device/new_device_mnemonic.jinja",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from flask import (
|
|||
jsonify,
|
||||
flash,
|
||||
)
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import login_required, current_user
|
||||
from flask import current_app as app
|
||||
from ..rpc import get_default_datadir
|
||||
|
|
@ -43,7 +44,7 @@ def node_settings(node_alias):
|
|||
)
|
||||
except:
|
||||
return render_template(
|
||||
"base.jinja", error="Node not found", specter=app.specter, rand=rand
|
||||
"base.jinja", error=_("Node not found"), specter=app.specter, rand=rand
|
||||
)
|
||||
else:
|
||||
node = Node.from_json(
|
||||
|
|
@ -62,7 +63,7 @@ def node_settings(node_alias):
|
|||
)
|
||||
|
||||
if not current_user.is_admin:
|
||||
flash("Only an admin is allowed to access this page.", "error")
|
||||
flash(_("Only an admin is allowed to access this page"), "error")
|
||||
return redirect("")
|
||||
# The node might have been down but is now up again
|
||||
# (and the checker did not realized yet) and the user clicked "Configure Node"
|
||||
|
|
@ -97,19 +98,19 @@ def node_settings(node_alias):
|
|||
if action == "rename":
|
||||
node_name = request.form["newtitle"]
|
||||
if not node_name:
|
||||
flash("Node name must not be empty", "error")
|
||||
flash(_("Node name cannot be empty"), "error")
|
||||
elif node_name == node.name:
|
||||
pass
|
||||
elif node_name in app.specter.device_manager.devices_names:
|
||||
flash("Node with this name already exists", "error")
|
||||
flash(_("Node with this name already exists"), "error")
|
||||
else:
|
||||
node.rename(node_name)
|
||||
elif action == "forget":
|
||||
if not node_alias:
|
||||
flash("Failed to deleted node. Node isn't saved", "error")
|
||||
flash(_("Failed to deleted node. Node isn't saved"), "error")
|
||||
elif len(app.specter.node_manager.nodes) > 1:
|
||||
app.specter.node_manager.delete_node(node, app.specter)
|
||||
flash("Node deleted successfully")
|
||||
flash(_("Node deleted successfully"))
|
||||
return redirect(
|
||||
url_for(
|
||||
"nodes_endpoint.node_settings",
|
||||
|
|
@ -118,7 +119,9 @@ def node_settings(node_alias):
|
|||
)
|
||||
else:
|
||||
flash(
|
||||
"Failed to deleted node. Specter must have at least one node configured",
|
||||
_(
|
||||
"Failed to delete node. Specter must have at least one node configured"
|
||||
),
|
||||
"error",
|
||||
)
|
||||
elif action == "test":
|
||||
|
|
@ -144,16 +147,18 @@ def node_settings(node_alias):
|
|||
if "tests" in test:
|
||||
# If any test has failed, we notify the user that the test has not passed
|
||||
if False in list(test["tests"].values()):
|
||||
flash(f"Test failed: {test['err']}", "error")
|
||||
flash(_("Test failed: {}").format(test["err"]), "error")
|
||||
else:
|
||||
flash("Test passed", "info")
|
||||
flash(_("Test passed"), "info")
|
||||
elif "err" in test:
|
||||
flash(f"Test failed: {test['err']}", "error")
|
||||
flash(_("Test failed: {}").format(test["err"]), "error")
|
||||
elif action == "save":
|
||||
if not node_alias:
|
||||
if node.name in app.specter.node_manager.nodes:
|
||||
flash(
|
||||
"Node with this name already exits, please choose a different name.",
|
||||
_(
|
||||
"Node with this name already exists, please choose a different name."
|
||||
),
|
||||
"error",
|
||||
)
|
||||
return render_template(
|
||||
|
|
@ -190,7 +195,7 @@ def node_settings(node_alias):
|
|||
protocol=protocol,
|
||||
)
|
||||
if not success:
|
||||
flash("Failed connecting to the node", "error")
|
||||
flash(_("Failed connecting to the node"), "error")
|
||||
if app.specter.active_node_alias == node.alias:
|
||||
app.specter.check()
|
||||
|
||||
|
|
@ -220,7 +225,7 @@ def internal_node_settings(node_alias):
|
|||
)
|
||||
except:
|
||||
return render_template(
|
||||
"base.jinja", error="Node not found", specter=app.specter, rand=rand
|
||||
"base.jinja", error=_("Node not found"), specter=app.specter, rand=rand
|
||||
)
|
||||
else:
|
||||
# TODO: Allow internal node setup here?
|
||||
|
|
@ -232,7 +237,7 @@ def internal_node_settings(node_alias):
|
|||
)
|
||||
|
||||
if not current_user.is_admin:
|
||||
flash("Only an admin is allowed to access this page.", "error")
|
||||
flash(_("Only an admin is allowed to access this page"), "error")
|
||||
return redirect("")
|
||||
# The node might have been down but is now up again
|
||||
# (and the checker did not realized yet) and the user clicked "Configure Node"
|
||||
|
|
@ -245,7 +250,7 @@ def internal_node_settings(node_alias):
|
|||
if action == "rename":
|
||||
node_name = request.form["newtitle"]
|
||||
if not node_name:
|
||||
flash("Node name must not be empty", "error")
|
||||
flash(_("Node name must not be empty"), "error")
|
||||
elif node_name == node.name:
|
||||
pass
|
||||
elif node_name in app.specter.device_manager.devices_names:
|
||||
|
|
@ -254,13 +259,13 @@ def internal_node_settings(node_alias):
|
|||
node.rename(node_name)
|
||||
elif action == "forget":
|
||||
if not node_alias:
|
||||
flash("Failed to deleted node. Node isn't saved", "error")
|
||||
flash(_("Failed to deleted node. Node isn't saved"), "error")
|
||||
elif len(app.specter.node_manager.nodes) > 1:
|
||||
node.stop()
|
||||
app.specter.node_manager.delete_node(node, app.specter)
|
||||
if bool(request.form.get("remove_datadir", False)):
|
||||
shutil.rmtree(os.path.expanduser(node.datadir), ignore_errors=True)
|
||||
flash("Node deleted successfully")
|
||||
flash(_("Node deleted successfully"))
|
||||
return redirect(
|
||||
url_for(
|
||||
"nodes_endpoint.node_settings",
|
||||
|
|
@ -269,27 +274,29 @@ def internal_node_settings(node_alias):
|
|||
)
|
||||
else:
|
||||
flash(
|
||||
"Failed to deleted node. Specter must have at least one node configured",
|
||||
_(
|
||||
"Failed to delete node. Specter must have at least one node configured"
|
||||
),
|
||||
"error",
|
||||
)
|
||||
elif action == "stopbitcoind":
|
||||
try:
|
||||
node.stop()
|
||||
time.sleep(5)
|
||||
flash("Specter stopped Bitcoin Core successfully")
|
||||
flash(_("Specter successfully stopped Bitcoin Core"))
|
||||
except Exception as e:
|
||||
try:
|
||||
logger.exception(e)
|
||||
flash("Stopping Bitcoin Core, this might take a few moments.")
|
||||
flash(_("Stopping Bitcoin Core, this might take a few moments."))
|
||||
node.rpc.stop()
|
||||
except Exception as ne:
|
||||
logger.exception(ne)
|
||||
flash(f"Failed to stop Bitcoin Core {ne}", "error")
|
||||
flash(_("Failed to stop Bitcoin Core {}").format(ne), "error")
|
||||
elif action == "startbitcoind":
|
||||
if node.start(timeout=120):
|
||||
flash("Specter has started Bitcoin Core")
|
||||
flash(_("Specter has started Bitcoin Core"))
|
||||
else:
|
||||
flash("Specter failed to start the node...", "error")
|
||||
flash(_("Specter failed to start the node"), "error")
|
||||
elif action == "uninstall_bitcoind":
|
||||
try:
|
||||
node.stop()
|
||||
|
|
@ -299,7 +306,7 @@ def internal_node_settings(node_alias):
|
|||
)
|
||||
if bool(request.form.get("remove_datadir", False)):
|
||||
shutil.rmtree(os.path.expanduser(node.datadir), ignore_errors=True)
|
||||
flash(f"Bitcoin Core uninstalled successfully")
|
||||
flash(_("Bitcoin Core successfully uninstalled"))
|
||||
app.specter.node_manager.delete_node(node, app.specter)
|
||||
return redirect(
|
||||
url_for(
|
||||
|
|
@ -308,7 +315,7 @@ def internal_node_settings(node_alias):
|
|||
)
|
||||
)
|
||||
except Exception as e:
|
||||
flash(f"Failed to remove Bitcoin Core, error: {e}", "error")
|
||||
flash(_("Failed to remove Bitcoin Core, error: {}").format(e), "error")
|
||||
elif action == "upgrade_bitcoind":
|
||||
if node.version != app.config["INTERNAL_BITCOIND_VERSION"]:
|
||||
try:
|
||||
|
|
@ -317,10 +324,13 @@ def internal_node_settings(node_alias):
|
|||
)
|
||||
except Exception as e:
|
||||
flash(
|
||||
f"Failed to upgrade Bitcoin Core version, error: {e}", "error"
|
||||
_("Failed to upgrade Bitcoin Core version, error: {}").format(
|
||||
e
|
||||
),
|
||||
"error",
|
||||
)
|
||||
else:
|
||||
flash("Bitcoin Core version is already up to date")
|
||||
flash(_("Bitcoin Core version is already up to date"))
|
||||
return render_template(
|
||||
"node/internal_node_settings.jinja",
|
||||
node=node,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import requests
|
|||
from flask import Blueprint, Flask
|
||||
from flask import current_app as app
|
||||
from flask import flash, jsonify, redirect, render_template, request, send_file, url_for
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from ..helpers import (
|
||||
|
|
@ -108,7 +109,7 @@ def general():
|
|||
# continue with the existing one
|
||||
if "already exists" not in str(e):
|
||||
flash(
|
||||
"Failed to import wallet {}, error: {}".format(
|
||||
_("Failed to import wallet {}, error: {}").format(
|
||||
wallet["name"], e
|
||||
),
|
||||
"error",
|
||||
|
|
@ -142,16 +143,21 @@ def general():
|
|||
)
|
||||
)
|
||||
flash(
|
||||
"Failed to perform rescan for wallet: {}".format(e), "error"
|
||||
_("Failed to perform rescan for wallet: {}").format(e),
|
||||
"error",
|
||||
)
|
||||
wallet_obj.getdata()
|
||||
except Exception:
|
||||
flash("Failed to import wallet {}".format(wallet["name"]), "error")
|
||||
flash("Specter data was successfully loaded from backup.", "info")
|
||||
flash(
|
||||
_("Failed to import wallet {}").format(wallet["name"]), "error"
|
||||
)
|
||||
flash(_("Specter data was successfully loaded from backup"), "info")
|
||||
if rescanning:
|
||||
flash(
|
||||
"Wallets are rescanning for transactions history.\n\
|
||||
This may take a few hours to complete.",
|
||||
_(
|
||||
"Wallets are rescanning for transactions history.\n\
|
||||
This may take a few hours to complete."
|
||||
),
|
||||
"info",
|
||||
)
|
||||
|
||||
|
|
@ -165,6 +171,7 @@ This may take a few hours to complete.",
|
|||
specter=app.specter,
|
||||
current_version=current_version,
|
||||
rand=rand,
|
||||
supported_languages=app.supported_languages,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -179,7 +186,7 @@ def tor():
|
|||
param only_tor "on" or something else ("off")
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
flash("Only an admin is allowed to access this page.", "error")
|
||||
flash(_("Only an admin is allowed to access this page."), "error")
|
||||
return redirect("")
|
||||
app.specter.reset_setup("torbrowser")
|
||||
current_version = notify_upgrade(app, flash)
|
||||
|
|
@ -250,18 +257,18 @@ def tor():
|
|||
logger.info("Starting Tor...")
|
||||
try:
|
||||
app.specter.tor_daemon.start_tor_daemon()
|
||||
flash("Specter has started Tor")
|
||||
flash(_("Specter has started Tor"))
|
||||
except Exception as e:
|
||||
flash(f"Failed to start Tor, error: {e}", "error")
|
||||
flash(_("Failed to start Tor, error: {}").format(e), "error")
|
||||
logger.error(f"Failed to start Tor, error: {e}")
|
||||
elif action == "stoptor":
|
||||
logger.info("Stopping Tor...")
|
||||
try:
|
||||
app.specter.tor_daemon.stop_tor_daemon()
|
||||
time.sleep(1)
|
||||
flash("Specter stopped Tor successfully")
|
||||
flash(_("Specter stopped Tor successfully"))
|
||||
except Exception as e:
|
||||
flash(f"Failed to stop Tor, error: {e}", "error")
|
||||
flash(_("Failed to stop Tor, error: {}").format(e), "error")
|
||||
logger.error(f"Failed to start Tor, error: {e}")
|
||||
elif action == "uninstalltor":
|
||||
logger.info("Uninstalling Tor...")
|
||||
|
|
@ -270,9 +277,9 @@ def tor():
|
|||
app.specter.tor_daemon.stop_tor_daemon()
|
||||
shutil.rmtree(os.path.join(app.specter.data_folder, "tor-binaries"))
|
||||
os.remove(os.path.join(app.specter.data_folder, "torrc"))
|
||||
flash(f"Tor uninstalled successfully")
|
||||
flash(_("Tor uninstalled successfully"))
|
||||
except Exception as e:
|
||||
flash(f"Failed to uninstall Tor, error: {e}", "error")
|
||||
flash(_("Failed to uninstall Tor, error: {}").format(e), "error")
|
||||
logger.error(f"Failed to uninstall Tor, error: {e}")
|
||||
elif action == "test_tor":
|
||||
logger.info("Testing the Tor connection...")
|
||||
|
|
@ -287,10 +294,12 @@ def tor():
|
|||
)
|
||||
tor_connectable = res.status_code == 200
|
||||
if tor_connectable:
|
||||
flash("Tor requests test completed successfully!", "info")
|
||||
flash(_("Tor requests test completed successfully!"), "info")
|
||||
else:
|
||||
flash(
|
||||
f"Failed to make test request over Tor. Status-Code: {res.status_code}",
|
||||
_(
|
||||
"Failed to make test request over Tor. Status-Code: {}"
|
||||
).format(res.status_code),
|
||||
"error",
|
||||
)
|
||||
logger.error(
|
||||
|
|
@ -303,7 +312,10 @@ def tor():
|
|||
logger.error(app.specter.tor_daemon.get_logs())
|
||||
app.specter.tor_daemon.start_tor_daemon()
|
||||
except Exception as e:
|
||||
flash(f"Failed to make test request over Tor.\nError: {e}", "error")
|
||||
flash(
|
||||
_("Failed to make test request over Tor.\nError: {}").format(e),
|
||||
"error",
|
||||
)
|
||||
logger.error(f"Failed to make test request over Tor.\nError: {e}")
|
||||
if tor_type == "builtin":
|
||||
logger.error("Tor-Logs:")
|
||||
|
|
@ -358,7 +370,7 @@ def auth():
|
|||
if current_user.username != specter_username:
|
||||
if app.specter.user_manager.get_user_by_username(specter_username):
|
||||
flash(
|
||||
"Username is already taken, please choose another one",
|
||||
_("Username is already taken, please choose another one"),
|
||||
"error",
|
||||
)
|
||||
return render_template(
|
||||
|
|
@ -375,9 +387,9 @@ def auth():
|
|||
if specter_password:
|
||||
if len(specter_password) < min_chars:
|
||||
flash(
|
||||
"Please enter a password of a least {} characters.".format(
|
||||
min_chars
|
||||
),
|
||||
_(
|
||||
"Please enter a password of a least {} characters"
|
||||
).format(min_chars),
|
||||
"error",
|
||||
)
|
||||
return render_template(
|
||||
|
|
@ -400,9 +412,9 @@ def auth():
|
|||
if new_password:
|
||||
if len(new_password) < min_chars:
|
||||
flash(
|
||||
"Please enter a password of a least {} characters.".format(
|
||||
min_chars
|
||||
),
|
||||
_(
|
||||
"Please enter a password of a least {} characters"
|
||||
).format(min_chars),
|
||||
"error",
|
||||
)
|
||||
return render_template(
|
||||
|
|
@ -441,9 +453,9 @@ def auth():
|
|||
if timeout > 0:
|
||||
expiry = now + timeout * 60 * 60
|
||||
if timeout > 1:
|
||||
expiry_desc = " (expires in {} hours)".format(timeout)
|
||||
expiry_desc = " " + _("(expires in {} hours)").format(timeout)
|
||||
else:
|
||||
expiry_desc = " (expires in 1 hour)"
|
||||
expiry_desc = " " + _("(expires in 1 hour)")
|
||||
else:
|
||||
expiry = 0
|
||||
expiry_desc = ""
|
||||
|
|
@ -451,14 +463,14 @@ def auth():
|
|||
{"otp": new_otp, "created_at": now, "expiry": expiry}
|
||||
)
|
||||
flash(
|
||||
"New user link generated{}: {}auth/register?otp={}".format(
|
||||
_("New user link generated{}: {}auth/register?otp={}").format(
|
||||
expiry_desc, request.url_root, new_otp
|
||||
),
|
||||
"info",
|
||||
)
|
||||
else:
|
||||
flash(
|
||||
"Error: Only the admin account can issue new registration links.",
|
||||
_("Error: Only the admin account can issue new registration links"),
|
||||
"error",
|
||||
)
|
||||
elif action == "deleteuser":
|
||||
|
|
@ -467,9 +479,11 @@ def auth():
|
|||
if current_user.is_admin:
|
||||
app.specter.delete_user(user)
|
||||
users.remove(user)
|
||||
flash("User {} was deleted successfully".format(user.username), "info")
|
||||
flash(
|
||||
_("User {} was deleted successfully").format(user.username), "info"
|
||||
)
|
||||
else:
|
||||
flash("Error: Only the admin account can delete users", "error")
|
||||
flash(_("Error: Only the admin account can delete users"), "error")
|
||||
return render_template(
|
||||
"settings/auth_settings.jinja",
|
||||
method=method,
|
||||
|
|
@ -489,7 +503,7 @@ def hwi():
|
|||
if request.method == "POST":
|
||||
hwi_bridge_url = request.form["hwi_bridge_url"]
|
||||
app.specter.update_hwi_bridge_url(hwi_bridge_url, current_user)
|
||||
flash("HWIBridge URL is updated! Don't forget to whitelist Specter!")
|
||||
flash(_("HWIBridge URL is updated! Don't forget to whitelist Specter!"))
|
||||
return render_template(
|
||||
"settings/hwi_settings.jinja",
|
||||
specter=app.specter,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from flask import (
|
|||
jsonify,
|
||||
flash,
|
||||
)
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import login_required, current_user
|
||||
from flask import current_app as app
|
||||
from mnemonic import Mnemonic
|
||||
|
|
@ -120,10 +121,10 @@ def setup_tor():
|
|||
t = threading.Thread(target=setup_tor_thread, args=(app.specter,))
|
||||
t.start()
|
||||
elif os.path.isfile(app.specter.torbrowser_path):
|
||||
return {"error": "Tor is already installed"}
|
||||
return {"error": _("Tor is already installed")}
|
||||
elif app.specter.setup_status["torbrowser"]["stage_progress"] != -1:
|
||||
return {"error": "Tor installation is still under progress"}
|
||||
return {"success": "Starting Tor setup!"}
|
||||
return {"error": _("Tor installation is still in progress")}
|
||||
return {"success": _("Starting Tor setup!")}
|
||||
|
||||
|
||||
@setup_endpoint.route("/setup_bitcoind", methods=["POST"])
|
||||
|
|
@ -140,10 +141,10 @@ def setup_bitcoind():
|
|||
)
|
||||
t.start()
|
||||
elif os.path.isfile(app.specter.bitcoind_path):
|
||||
return {"error": "Bitcoin Core is already installed"}
|
||||
return {"error": _("Bitcoin Core is already installed")}
|
||||
elif app.specter.setup_status["bitcoind"]["stage_progress"] != -1:
|
||||
return {"error": "Bitcoin Core installation is still under progress"}
|
||||
return {"success": "Starting Bitcoin Core setup!"}
|
||||
return {"error": _("Bitcoin Core installation is still in progress")}
|
||||
return {"success": _("Starting Bitcoin Core setup!")}
|
||||
|
||||
|
||||
@setup_endpoint.route("/setup_bitcoind_datadir", methods=["POST"])
|
||||
|
|
@ -202,10 +203,10 @@ def setup_bitcoind_datadir():
|
|||
)
|
||||
t.start()
|
||||
elif not os.path.isfile(app.specter.bitcoind_path):
|
||||
return {"error": "Bitcoin Core in not installed but required for this step"}
|
||||
return {"error": _("Bitcoin Core in not installed but required for this step")}
|
||||
elif app.specter.setup_status["bitcoind"]["stage_progress"] != -1:
|
||||
return {"error": "Bitcoin Core installation is still under progress"}
|
||||
return {"success": "Starting Bitcoin Core setup!"}
|
||||
return {"error": _("Bitcoin Core installation is still in progress")}
|
||||
return {"success": _("Starting Bitcoin Core setup!")}
|
||||
|
||||
|
||||
######################### Setup status (open endpoint) #######################################
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import requests
|
|||
from flask import Blueprint, Flask
|
||||
from flask import current_app as app
|
||||
from flask import flash, jsonify, redirect, render_template, request, url_for
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_login import current_user, login_required
|
||||
from werkzeug.wrappers import Response
|
||||
from cryptoadvance.specter.util.psbt_creator import PsbtCreator
|
||||
|
|
@ -47,7 +48,7 @@ wallets_endpoint = Blueprint("wallets_endpoint", __name__)
|
|||
|
||||
|
||||
def handle_wallet_error(func_name, error):
|
||||
flash(f"SpecterError while {func_name}: {error}", "error")
|
||||
flash(_("SpecterError while {}: {}").format(func_name, error), "error")
|
||||
app.logger.error(f"SpecterError while {func_name}: {error}")
|
||||
app.specter.wallet_manager.update()
|
||||
return redirect(url_for("about"))
|
||||
|
|
@ -56,10 +57,9 @@ def handle_wallet_error(func_name, error):
|
|||
def check_wallet(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""checks the wallet for healthyness A wrapper function"""
|
||||
"""checks the wallet for healthiness A wrapper function"""
|
||||
if kwargs["wallet_alias"]:
|
||||
wallet_alias = kwargs["wallet_alias"]
|
||||
print("--------------------checking wallet")
|
||||
wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
|
||||
wallet.get_info()
|
||||
return func(*args, **kwargs)
|
||||
|
|
@ -105,8 +105,7 @@ def failed_wallets():
|
|||
delete_file(fullpath.replace(".json", "_txs.csv"))
|
||||
app.specter.wallet_manager.update()
|
||||
except Exception as e:
|
||||
|
||||
flash(f"Failed to delete failed wallet: {str(e)}", "error")
|
||||
flash(_("Failed to delete wallet: {}").format(str(e)), "error")
|
||||
return redirect("/")
|
||||
|
||||
|
||||
|
|
@ -118,13 +117,15 @@ def failed_wallets():
|
|||
def new_wallet_type():
|
||||
err = None
|
||||
if app.specter.chain is None:
|
||||
err = "Configure Bitcoin Core to create wallets"
|
||||
err = _("Configure Bitcoin Core to create wallets")
|
||||
return render_template("base.jinja", error=err, specter=app.specter, rand=rand)
|
||||
try:
|
||||
# Make sure wallet is enabled on Bitcoin Core
|
||||
app.specter.rpc.listwallets()
|
||||
except Exception:
|
||||
err = '<p><br>Configure Bitcoin Core is running with wallets disabled.<br><br>Please make sure disablewallet is off (set disablewallet=0 in your bitcoin.conf), then restart Bitcoin Core and try again.<br>See <a href="https://github.com/cryptoadvance/specter-desktop/blob/34ca139694ecafb2e7c2bd5ad5c4ac74c6d11501/docs/faq.md#im-not-sure-i-want-the-bitcoin-core-wallet-functionality-to-be-used-is-that-mandatory-if-so-is-it-considered-secure" target="_blank" style="color: white;">here</a> for more information.</p>'
|
||||
err = _(
|
||||
'<p><br>Bitcoin Core is running with wallets disabled.<br><br>Please make sure disablewallet is off (set disablewallet=0 in your bitcoin.conf), then restart Bitcoin Core and try again.<br>See <a href="https://github.com/cryptoadvance/specter-desktop/blob/34ca139694ecafb2e7c2bd5ad5c4ac74c6d11501/docs/faq.md#im-not-sure-i-want-the-bitcoin-core-wallet-functionality-to-be-used-is-that-mandatory-if-so-is-it-considered-secure" target="_blank" style="color: white;">here</a> for more information.</p>'
|
||||
)
|
||||
return render_template("base.jinja", error=err, specter=app.specter, rand=rand)
|
||||
return render_template(
|
||||
"wallet/new_wallet/new_wallet_type.jinja", specter=app.specter, rand=rand
|
||||
|
|
@ -136,7 +137,7 @@ def new_wallet_type():
|
|||
def new_wallet(wallet_type):
|
||||
wallet_types = ["simple", "multisig", "import_wallet"]
|
||||
if wallet_type not in wallet_types:
|
||||
flash("Unknown wallet type requested", "error")
|
||||
flash(_("Unknown wallet type requested"), "error")
|
||||
return redirect(url_for("wallets_endpoint.new_wallet_type"))
|
||||
|
||||
err = None
|
||||
|
|
@ -163,7 +164,7 @@ def new_wallet(wallet_type):
|
|||
except SpecterError as se:
|
||||
flash(str(se), "error")
|
||||
return redirect(url_for("wallets_endpoint.new_wallet_type"))
|
||||
flash("Wallet imported successfully", "info")
|
||||
flash(_("Wallet imported successfully"), "info")
|
||||
try:
|
||||
wallet_importer.rescan_as_needed(app.specter)
|
||||
except SpecterError as se:
|
||||
|
|
@ -199,17 +200,18 @@ def new_wallet(wallet_type):
|
|||
return render_template(
|
||||
"wallet/new_wallet/new_wallet.jinja",
|
||||
wallet_type=wallet_type,
|
||||
error="No device was selected. Please select a device to create the wallet for.",
|
||||
error=_(
|
||||
"No device was selected. Please select a device to create the wallet for."
|
||||
),
|
||||
specter=app.specter,
|
||||
rand=rand,
|
||||
)
|
||||
devices = get_devices_with_keys_by_type(app, cosigners, wallet_type)
|
||||
for device in devices:
|
||||
if len(device.keys) == 0:
|
||||
err = (
|
||||
"Device %s doesn't have keys matching this wallet type"
|
||||
% device.name
|
||||
)
|
||||
err = _(
|
||||
"Device {} doesn't have keys matching this wallet type"
|
||||
).format(device.name)
|
||||
break
|
||||
|
||||
name = wallet_type.title()
|
||||
|
|
@ -235,7 +237,7 @@ def new_wallet(wallet_type):
|
|||
sigs_total = int(request.form.get("sigs_total", 1))
|
||||
sigs_required = int(request.form.get("sigs_required", 1))
|
||||
if wallet_name in app.specter.wallet_manager.wallets_names:
|
||||
err = "Wallet already exists"
|
||||
err = _("Wallet already exists")
|
||||
if err:
|
||||
devices = [
|
||||
app.specter.device_manager.get_by_alias(
|
||||
|
|
@ -270,7 +272,9 @@ def new_wallet(wallet_type):
|
|||
except:
|
||||
pass
|
||||
if len(keys) != sigs_total or len(cosigners) != sigs_total:
|
||||
err = "No keys were selected for device, please try adding keys first"
|
||||
err = _(
|
||||
"No keys were selected for device, please try adding keys first"
|
||||
)
|
||||
devices = [
|
||||
app.specter.device_manager.get_by_alias(
|
||||
request.form.get("cosigner{}".format(i))
|
||||
|
|
@ -294,7 +298,7 @@ def new_wallet(wallet_type):
|
|||
wallet_name, sigs_required, address_type, keys, cosigners
|
||||
)
|
||||
except Exception as e:
|
||||
err = f"Failed to create wallet. Error: {e}"
|
||||
err = _("Failed to create wallet. Error: {}").format(e)
|
||||
return render_template(
|
||||
"wallet/new_wallet/new_wallet_keys.jinja",
|
||||
cosigners=cosigners,
|
||||
|
|
@ -483,7 +487,9 @@ def send_new(wallet_alias):
|
|||
selected_coins = request.form.getlist("coinselect")
|
||||
fee_estimation_data = get_fees(app.specter, app.config)
|
||||
if fee_estimation_data.get("failed", None):
|
||||
flash("Failed to fetch fee estimations, please use the manual fee calculation")
|
||||
flash(
|
||||
_("Failed to fetch fee estimations. Please use the manual fee calculation.")
|
||||
)
|
||||
|
||||
fee_rate = fee_estimation_data["hourFee"]
|
||||
|
||||
|
|
@ -535,7 +541,7 @@ def send_new(wallet_alias):
|
|||
rand=rand,
|
||||
)
|
||||
except Exception as e:
|
||||
flash("Failed to perform RBF. Error: %s" % e, "error")
|
||||
flash(_("Failed to perform RBF. Error: {}").format(e), "error")
|
||||
elif action == "rbf_cancel":
|
||||
try:
|
||||
rbf_tx_id = request.form["rbf_tx_id"]
|
||||
|
|
@ -551,7 +557,10 @@ def send_new(wallet_alias):
|
|||
rand=rand,
|
||||
)
|
||||
except Exception as e:
|
||||
flash("Failed to cancel transaction with RBF. Error: %s" % e, "error")
|
||||
flash(
|
||||
_("Failed to cancel transaction with RBF. Error: {}").format(e),
|
||||
"error",
|
||||
)
|
||||
elif action == "rbf_edit":
|
||||
try:
|
||||
decoded_tx = wallet.decode_tx(rbf_tx_id)
|
||||
|
|
@ -565,7 +574,7 @@ def send_new(wallet_alias):
|
|||
fee_options = "manual"
|
||||
rbf = True
|
||||
except Exception as e:
|
||||
flash("Failed to perform RBF. Error: %s" % e, "error")
|
||||
flash(_("Failed to perform RBF. Error: {}").format(e), "error")
|
||||
elif action == "signhotwallet":
|
||||
passphrase = request.form["passphrase"]
|
||||
psbt = json.loads(request.form["psbt"])
|
||||
|
|
@ -588,10 +597,10 @@ def send_new(wallet_alias):
|
|||
signed_psbt = signed_psbt["psbt"]
|
||||
except Exception as e:
|
||||
signed_psbt = None
|
||||
flash("Failed to sign PSBT: %s" % e, "error")
|
||||
flash(_("Failed to sign PSBT: {}").format(e), "error")
|
||||
else:
|
||||
signed_psbt = None
|
||||
flash("Device already signed the PSBT", "error")
|
||||
flash(_("Device already signed the PSBT"), "error")
|
||||
return render_template(
|
||||
"wallet/send/sign/wallet_send_sign_psbt.jinja",
|
||||
signed_psbt=signed_psbt,
|
||||
|
|
@ -607,7 +616,7 @@ def send_new(wallet_alias):
|
|||
try:
|
||||
rbf_utxo = wallet.get_rbf_utxo(rbf_tx_id)
|
||||
except Exception as e:
|
||||
flash("Failed to get RBF coins. Error: %s" % e, "error")
|
||||
flash(_("Failed to get RBF coins. Error: {}").format(e), "error")
|
||||
|
||||
show_advanced_settings = (
|
||||
ui_option != "ui"
|
||||
|
|
@ -661,7 +670,7 @@ def send_pending(wallet_alias):
|
|||
)
|
||||
except Exception as e:
|
||||
app.logger.error("Could not delete Pending PSBT: %s" % e)
|
||||
flash("Could not delete Pending PSBT!", "error")
|
||||
flash(_("Could not delete Pending PSBT!"), "error")
|
||||
elif action == "openpsbt":
|
||||
psbt = json.loads(request.form["pending_psbt"])
|
||||
return render_template(
|
||||
|
|
@ -699,7 +708,7 @@ def import_psbt(wallet_alias):
|
|||
b64psbt = "".join(request.form["rawpsbt"].split())
|
||||
psbt = wallet.importpsbt(b64psbt)
|
||||
except Exception as e:
|
||||
flash("Could not import PSBT: %s" % e, "error")
|
||||
flash(_("Could not import PSBT: {}").format(e), "error")
|
||||
return redirect(
|
||||
url_for("wallets_endpoint.import_psbt", wallet_alias=wallet_alias)
|
||||
)
|
||||
|
|
@ -773,7 +782,7 @@ def settings(wallet_alias):
|
|||
elif action == "abortrescan":
|
||||
res = wallet.rpc.abortrescan()
|
||||
if not res:
|
||||
error = "Failed to abort rescan. Maybe already complete?"
|
||||
error = _("Failed to abort rescan. Maybe already complete?")
|
||||
wallet.getdata()
|
||||
elif action == "rescanutxo":
|
||||
explorer = None
|
||||
|
|
@ -810,11 +819,11 @@ def settings(wallet_alias):
|
|||
elif action == "rename":
|
||||
wallet_name = request.form["newtitle"]
|
||||
if not wallet_name:
|
||||
flash("Wallet name cannot be empty", "error")
|
||||
flash(_("Wallet name cannot be empty"), "error")
|
||||
elif wallet_name == wallet.name:
|
||||
pass
|
||||
elif wallet_name in app.specter.wallet_manager.wallets_names:
|
||||
flash("Wallet already exists", "error")
|
||||
flash(_("Wallet already exists"), "error")
|
||||
else:
|
||||
app.specter.wallet_manager.rename_wallet(wallet, wallet_name)
|
||||
|
||||
|
|
@ -846,7 +855,7 @@ def combine(wallet_alias):
|
|||
|
||||
for i, psbt in enumerate(psbts):
|
||||
if not psbt:
|
||||
return "Cannot parse empty data as PSBT", 500
|
||||
return _("Cannot parse empty data as PSBT"), 500
|
||||
if "UR:BYTES/" in psbt.upper():
|
||||
psbt = bcur2base64(psbt).decode()
|
||||
|
||||
|
|
@ -870,7 +879,7 @@ def combine(wallet_alias):
|
|||
try:
|
||||
bytes.fromhex(psbt)
|
||||
except:
|
||||
return "Invalid transaction format", 500
|
||||
return _("Invalid transaction format"), 500
|
||||
|
||||
try:
|
||||
if "hex" in raw:
|
||||
|
|
@ -886,84 +895,84 @@ def combine(wallet_alias):
|
|||
except RpcError as e:
|
||||
return e.error_msg, e.status_code
|
||||
except Exception as e:
|
||||
return "Unknown error: %r" % e, 500
|
||||
return _("Unknown error: {}").format(e), 500
|
||||
return json.dumps(raw)
|
||||
|
||||
|
||||
@wallets_endpoint.route("/wallet/<wallet_alias>/broadcast/", methods=["GET", "POST"])
|
||||
@wallets_endpoint.route("/wallet/<wallet_alias>/broadcast/", methods=["POST"])
|
||||
@login_required
|
||||
def broadcast(wallet_alias):
|
||||
wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
|
||||
if request.method == "POST":
|
||||
tx = request.form.get("tx")
|
||||
res = wallet.rpc.testmempoolaccept([tx])[0]
|
||||
if res["allowed"]:
|
||||
app.specter.broadcast(tx)
|
||||
wallet.delete_pending_psbt(get_txid(tx))
|
||||
return jsonify(success=True)
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error="Failed to broadcast transaction: transaction is invalid\n%s"
|
||||
% res["reject-reason"],
|
||||
)
|
||||
return jsonify(success=False, error="broadcast tx request must use POST")
|
||||
tx = request.form.get("tx")
|
||||
res = wallet.rpc.testmempoolaccept([tx])[0]
|
||||
if res["allowed"]:
|
||||
app.specter.broadcast(tx)
|
||||
wallet.delete_pending_psbt(get_txid(tx))
|
||||
return jsonify(success=True)
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=_(
|
||||
"Failed to broadcast transaction: transaction is invalid\n{}"
|
||||
).format(res["reject-reason"]),
|
||||
)
|
||||
|
||||
|
||||
@wallets_endpoint.route(
|
||||
"/wallet/<wallet_alias>/broadcast_blockexplorer/", methods=["GET", "POST"]
|
||||
"/wallet/<wallet_alias>/broadcast_blockexplorer/", methods=["POST"]
|
||||
)
|
||||
@login_required
|
||||
def broadcast_blockexplorer(wallet_alias):
|
||||
wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
|
||||
if request.method == "POST":
|
||||
tx = request.form.get("tx")
|
||||
explorer = request.form.get("explorer")
|
||||
use_tor = request.form.get("use_tor", "true") == "true"
|
||||
res = wallet.rpc.testmempoolaccept([tx])[0]
|
||||
if res["allowed"]:
|
||||
try:
|
||||
if app.specter.chain == "main":
|
||||
url_network = ""
|
||||
elif app.specter.chain == "liquidv1":
|
||||
url_network = "liquid/"
|
||||
elif app.specter.chain == "test" or app.specter.chain == "testnet":
|
||||
url_network = "testnet/"
|
||||
elif app.specter.chain == "signet":
|
||||
url_network = "signet/"
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=f"Failed to broadcast transaction. Network not supported.",
|
||||
)
|
||||
if explorer == "mempool":
|
||||
explorer = f"MEMPOOL_SPACE{'_ONION' if use_tor else ''}"
|
||||
elif explorer == "blockstream":
|
||||
explorer = f"BLOCKSTREAM_INFO{'_ONION' if use_tor else ''}"
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=f"Failed to broadcast transaction. Block explorer not supported.",
|
||||
)
|
||||
requests_session = app.specter.requests_session(force_tor=use_tor)
|
||||
requests_session.post(
|
||||
f"{app.config['EXPLORERS_LIST'][explorer]['url']}{url_network}api/tx",
|
||||
data=tx,
|
||||
)
|
||||
wallet.delete_pending_psbt(get_txid(tx))
|
||||
return jsonify(success=True)
|
||||
except Exception as e:
|
||||
tx = request.form.get("tx")
|
||||
explorer = request.form.get("explorer")
|
||||
use_tor = request.form.get("use_tor", "true") == "true"
|
||||
res = wallet.rpc.testmempoolaccept([tx])[0]
|
||||
if res["allowed"]:
|
||||
try:
|
||||
if app.specter.chain == "main":
|
||||
url_network = ""
|
||||
elif app.specter.chain == "liquidv1":
|
||||
url_network = "liquid/"
|
||||
elif app.specter.chain == "test" or app.specter.chain == "testnet":
|
||||
url_network = "testnet/"
|
||||
elif app.specter.chain == "signet":
|
||||
url_network = "signet/"
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=f"Failed to broadcast transaction with error: {e}",
|
||||
error=_("Failed to broadcast transaction. Network not supported."),
|
||||
)
|
||||
else:
|
||||
if explorer == "mempool":
|
||||
explorer = f"MEMPOOL_SPACE{'_ONION' if use_tor else ''}"
|
||||
elif explorer == "blockstream":
|
||||
explorer = f"BLOCKSTREAM_INFO{'_ONION' if use_tor else ''}"
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=_(
|
||||
"Failed to broadcast transaction. Block explorer not supported."
|
||||
),
|
||||
)
|
||||
requests_session = app.specter.requests_session(force_tor=use_tor)
|
||||
requests_session.post(
|
||||
f"{app.config['EXPLORERS_LIST'][explorer]['url']}{url_network}api/tx",
|
||||
data=tx,
|
||||
)
|
||||
wallet.delete_pending_psbt(get_txid(tx))
|
||||
return jsonify(success=True)
|
||||
except Exception as e:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error="Failed to broadcast transaction: transaction is invalid\n%s"
|
||||
% res["reject-reason"],
|
||||
error=_("Failed to broadcast transaction with error: {}").format(e),
|
||||
)
|
||||
return jsonify(success=False, error="broadcast tx request must use POST")
|
||||
else:
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=_(
|
||||
"Failed to broadcast transaction: transaction is invalid\n{}"
|
||||
).format(res["reject-reason"]),
|
||||
)
|
||||
|
||||
|
||||
@wallets_endpoint.route("/wallet/<wallet_alias>/decoderawtx/", methods=["GET", "POST"])
|
||||
|
|
@ -1010,15 +1019,15 @@ def decoderawtx(wallet_alias):
|
|||
if "asset" in v:
|
||||
v["assetlabel"] = app.specter.asset_label(v["asset"])
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"tx": tx,
|
||||
"rawtx": rawtx,
|
||||
"walletName": wallet.name,
|
||||
}
|
||||
return jsonify(
|
||||
success=True,
|
||||
tx=tx,
|
||||
rawtx=rawtx,
|
||||
walletName=wallet.name,
|
||||
)
|
||||
except Exception as e:
|
||||
app.logger.warning("Failed to fetch transaction data. Exception: {}".format(e))
|
||||
return {"success": False}
|
||||
return jsonify(success=False)
|
||||
|
||||
|
||||
@wallets_endpoint.route(
|
||||
|
|
@ -1030,10 +1039,10 @@ def rescan_progress(wallet_alias):
|
|||
try:
|
||||
wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
|
||||
wallet.get_info()
|
||||
return {
|
||||
"active": wallet.rescan_progress is not None,
|
||||
"progress": wallet.rescan_progress,
|
||||
}
|
||||
return jsonify(
|
||||
active=wallet.rescan_progress is not None,
|
||||
progress=wallet.rescan_progress,
|
||||
)
|
||||
except SpecterError as se:
|
||||
app.logger.error("SpecterError while get wallet rescan_progress: %s" % se)
|
||||
return {}
|
||||
|
|
@ -1046,16 +1055,16 @@ def get_label(wallet_alias):
|
|||
wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
|
||||
address = request.form.get("address", "")
|
||||
label = wallet.getlabel(address)
|
||||
return {
|
||||
"address": address,
|
||||
"label": label,
|
||||
}
|
||||
return jsonify(
|
||||
address=address,
|
||||
label=label,
|
||||
)
|
||||
except Exception as e:
|
||||
handle_exception(e)
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Exception trying to get address label: Error: {e}",
|
||||
}
|
||||
return jsonify(
|
||||
success=False,
|
||||
error=_("Exception trying to get address label: Error: {}").format(e),
|
||||
)
|
||||
|
||||
|
||||
@wallets_endpoint.route("/wallet/<wallet_alias>/set_label", methods=["POST"])
|
||||
|
|
@ -1066,7 +1075,7 @@ def set_label(wallet_alias):
|
|||
address = request.form["address"]
|
||||
label = request.form["label"].rstrip()
|
||||
wallet.setlabel(address, label)
|
||||
return {"success": True}
|
||||
return jsonify(success=True)
|
||||
|
||||
|
||||
@wallets_endpoint.route("/wallet/<wallet_alias>/txlist", methods=["POST"])
|
||||
|
|
@ -1174,10 +1183,10 @@ def addresses_list(wallet_alias):
|
|||
addresses_list, idx=idx, limit=limit, sortby=sortby, sortdir=sortdir
|
||||
)
|
||||
|
||||
return {
|
||||
"addressesList": json.dumps(result["addressesList"]),
|
||||
"pageCount": result["pageCount"],
|
||||
}
|
||||
return jsonify(
|
||||
addressesList=json.dumps(result["addressesList"]),
|
||||
pageCount=result["pageCount"],
|
||||
)
|
||||
|
||||
|
||||
@wallets_endpoint.route("/wallet/<wallet_alias>/addressinfo/", methods=["POST"])
|
||||
|
|
@ -1199,9 +1208,8 @@ def addressinfo(wallet_alias):
|
|||
**address_info,
|
||||
}
|
||||
except Exception as e:
|
||||
|
||||
app.logger.warning("Failed to fetch address data. Exception: {}".format(e))
|
||||
return {"success": False}
|
||||
return jsonify(success=False)
|
||||
|
||||
|
||||
################## Wallet CSV export data endpoints #######################
|
||||
|
|
@ -1263,7 +1271,7 @@ def addresses_list_csv(wallet_alias):
|
|||
return response
|
||||
except Exception as e:
|
||||
app.logger.error("Failed to export addresses list. Error: %s" % e)
|
||||
flash("Failed to export addresses list. Error: %s" % e, "error")
|
||||
flash(_("Failed to export addresses list. Error: {}").format(e), "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
|
|
@ -1332,7 +1340,7 @@ def utxo_csv(wallet_alias):
|
|||
return response
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return "Failed to export wallet utxo. Error: %s" % e, 500
|
||||
return _("Failed to export wallet utxo. Error: {}").format(e), 500
|
||||
|
||||
|
||||
# Export all wallets transaction history combined
|
||||
|
|
@ -1367,7 +1375,7 @@ def wallet_overview_txs_csv():
|
|||
return response
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return "Failed to export wallets overview history. Error: %s" % e, 500
|
||||
return _("Failed to export wallets overview history. Error: {}").format(e), 500
|
||||
|
||||
|
||||
# Export all wallets transaction history combined
|
||||
|
|
@ -1399,7 +1407,7 @@ def wallet_overview_utxo_csv():
|
|||
return response
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return "Failed to export wallets overview utxo. Error: %s" % e, 500
|
||||
return _("Failed to export wallets overview utxo. Error: {}").format(e), 500
|
||||
|
||||
|
||||
################## Helpers #######################
|
||||
|
|
@ -1425,22 +1433,22 @@ def txlist_to_csv(wallet, _txlist, specter, current_user, includePricesHistory=F
|
|||
elif specter.price_provider.endswith("_gbp"):
|
||||
symbol = "GBP"
|
||||
row = (
|
||||
"Date",
|
||||
"Label",
|
||||
"Category",
|
||||
"Amount ({})".format(specter.unit.upper()),
|
||||
"Value ({})".format(symbol),
|
||||
"Rate (BTC/{})".format(symbol)
|
||||
_("Date"),
|
||||
_("Label"),
|
||||
_("Category"),
|
||||
_("Amount ({})").format(specter.unit.upper()),
|
||||
_("Value ({})").format(symbol),
|
||||
_("Rate (BTC/{})").format(symbol)
|
||||
if specter.unit != "sat"
|
||||
else "Rate ({}/SAT)".format(symbol),
|
||||
"TxID",
|
||||
"Address",
|
||||
"Block Height",
|
||||
"Timestamp",
|
||||
"Raw Transaction",
|
||||
else _("Rate ({}/SAT)").format(symbol),
|
||||
_("TxID"),
|
||||
_("Address"),
|
||||
_("Block Height"),
|
||||
_("Timestamp"),
|
||||
_("Raw Transaction"),
|
||||
)
|
||||
if not wallet:
|
||||
row = ("Wallet",) + row
|
||||
row = (_("Wallet"),) + row
|
||||
w.writerow(row)
|
||||
yield data.getvalue()
|
||||
data.seek(0)
|
||||
|
|
@ -1515,11 +1523,11 @@ def addresses_list_to_csv(wallet):
|
|||
w = csv.writer(data)
|
||||
# write header
|
||||
row = (
|
||||
"Address",
|
||||
"Label",
|
||||
"Index",
|
||||
"Used",
|
||||
"Current balance",
|
||||
_("Address"),
|
||||
_("Label"),
|
||||
_("Index"),
|
||||
_("Used"),
|
||||
_("Current balance"),
|
||||
)
|
||||
w.writerow(row)
|
||||
yield data.getvalue()
|
||||
|
|
@ -1534,15 +1542,16 @@ def addresses_list_to_csv(wallet):
|
|||
row = (
|
||||
address,
|
||||
address_info.label,
|
||||
"(external)"
|
||||
_("(external)")
|
||||
if address_info.is_external
|
||||
else (
|
||||
str(address_info.index) + (" (change)" if address_info.change else "")
|
||||
str(address_info.index)
|
||||
+ (_(" (change)") if address_info.change else "")
|
||||
),
|
||||
address_info.used,
|
||||
)
|
||||
if address_info.is_external:
|
||||
balance_on_address = "unknown (external address)"
|
||||
balance_on_address = _("unknown (external address)")
|
||||
else:
|
||||
balance_on_address = 0
|
||||
if address_info.used:
|
||||
|
|
@ -1565,13 +1574,13 @@ def wallet_addresses_list_to_csv(addresses_list):
|
|||
w = csv.writer(data)
|
||||
# write header
|
||||
row = (
|
||||
"Index",
|
||||
"Address",
|
||||
"Type",
|
||||
"Label",
|
||||
"Used",
|
||||
"UTXO",
|
||||
"Amount (BTC)",
|
||||
_("Index"),
|
||||
_("Address"),
|
||||
_("Type"),
|
||||
_("Label"),
|
||||
_("Used"),
|
||||
_("UTXO"),
|
||||
_("Amount (BTC)"),
|
||||
)
|
||||
w.writerow(row)
|
||||
yield data.getvalue()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="shortcut icon" type="image/png" href="{{ url_for('static', filename='img/icon.png') }}"/>
|
||||
<title>Specter Desktop</title>
|
||||
<title>{{ _("Specter Desktop") }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
|
||||
<style type="text/css">
|
||||
|
|
@ -15,15 +15,15 @@
|
|||
<main>
|
||||
<br><br><br>
|
||||
<div class="card" style="width:90%; max-width: 800px;">
|
||||
<h1>Server error!</h1>
|
||||
Exception: <b>{{ error }}</b><br><br>
|
||||
<h1>{{ _("Server error!") }}</h1>
|
||||
{{ _("Exception") }}: <b>{{ error }}</b><br><br>
|
||||
<div><br>
|
||||
<a class="btn" href="{{ url_for('index') }}">Back to the main page</a>
|
||||
<a class="btn" href="{{ url_for('index') }}">{{ _("Back to the main page") }}</a>
|
||||
<br></div>
|
||||
Details:
|
||||
{{ _("Details") }}:
|
||||
<pre><code>{{ traceback }}</code></pre>
|
||||
<br>
|
||||
<div>Please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank">Telegram</a> to fix it.</div>
|
||||
<div>{{ _('Please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank">Telegram</a> to fix it.') }}</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="shortcut icon" type="image/png" href="{{ url_for('static', filename='img/icon.png') }}"/>
|
||||
<title>Specter Desktop</title>
|
||||
<title>{{ _("Specter Desktop") }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
|
||||
<style type="text/css">
|
||||
|
|
@ -15,20 +15,20 @@
|
|||
<main>
|
||||
<br><br><br>
|
||||
<div class="card" style="width:90%; max-width: 800px;">
|
||||
<h1>Timeout Error!</h1>
|
||||
Exception: <b>{{ error }}</b><br><br>
|
||||
<h1>{{ _("Timeout Error!") }}</h1>
|
||||
{{ _("Exception") }}: <b>{{ error }}</b><br><br>
|
||||
<div><br>
|
||||
<a class="btn" href="{{ url_for('index') }}">Back to the main page</a>
|
||||
<a class="btn" href="{{ url_for('index') }}">{{ _("Back to the main page") }}</a>
|
||||
<br></div>
|
||||
Please have a look in the below log. There are effectively two ways to resolve this:
|
||||
{{ _("Please have a look in the below log. There are effectively two ways to resolve this:") }}"
|
||||
<ul>
|
||||
<li>Either the process was just slow and specter too impatient</li>
|
||||
<li>The process is in trouble which should be reflexted by the content of the logs below</li>
|
||||
<li>{{ _("Either the process was just slow and Specter was too impatient") }}</li>
|
||||
<li>{{ _("The process is in trouble which should be reflected by the content of the logs below") }}</li>
|
||||
</ul>
|
||||
Details from the Log-File:
|
||||
{{ _("Details from the log file") }}:
|
||||
<pre><code>{{ loglines }}</code></pre>
|
||||
<br>
|
||||
<div>Please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank">Telegram</a> to fix it.</div>
|
||||
<div>{{ _('Please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank">Telegram</a> to fix it.') }}</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="shortcut icon" type="image/png" href="{{ url_for('static', filename='img/icon.png') }}"/>
|
||||
<title>Specter Desktop</title>
|
||||
<title>{{ _("Specter Desktop") }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}?{{rand}}">
|
||||
|
|
@ -60,158 +60,164 @@
|
|||
{% endif %}
|
||||
{% endwith %}
|
||||
{% if error %}
|
||||
<message-box type="error">ERROR: {{error}}</message-box>
|
||||
<message-box type="error">{{ _("ERROR:") }} {{error}}</message-box>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% block main %}
|
||||
{% if error %}
|
||||
<br><br>Something went wrong :(<br><br>{{error}}
|
||||
<br><br>{{ _("Something went wrong:") }} (<br><br>{{error}}
|
||||
{% else %}
|
||||
<br>
|
||||
<div style="max-width: 60%; text-align: center;">
|
||||
<h1 style="font-size: 1.8em;">
|
||||
<img src="{{ url_for('static', filename='img/icon.png') }}" width="40px" style="vertical-align: top; margin-right: 20px;"/>
|
||||
Welcome to Specter Desktop
|
||||
{{ _('Welcome to Specter Desktop') }}
|
||||
<img src="{{ url_for('static', filename='img/ghost.png') }}" width="40px" style="vertical-align: top; margin-left: 20px;"/>
|
||||
</h1>
|
||||
<p style="font-size: 1.2em; font-weight: bolder;">Specter Desktop is a convenient wallet interface to better use your Bitcoin Core node. It’s good for singlesig wallets, but comes with a special focus on multisignature setups for different hardware wallets and air-gapped signing devices.</p>
|
||||
<br><br>
|
||||
<p style="font-size: 1.2em; font-weight: bolder;">{{ _("Specter Desktop is a convenient wallet interface to better use your Bitcoin Core node. It’s good for singlesig wallets, but comes with a special focus on multisignature setups for different hardware wallets and air-gapped signing devices.") }}</p>
|
||||
|
||||
<div style="max-width: 15em; text-align: center; margin-bottom: 3em; margin-left: auto; margin-right: auto;">
|
||||
{% include "includes/language/language_select.jinja" %}
|
||||
</div>
|
||||
|
||||
{% if not specter.chain %}
|
||||
<a href="{{url_for('setup_endpoint.' + specter.setup_status.stage)}}" class="btn action centered" style="max-width: 200px;">{% if specter.setup_status.stage == "start" %}Get started!{% else %}Continue setup{% endif %}</a>
|
||||
<br><br>
|
||||
<a href="{{url_for('setup_endpoint.' + specter.setup_status.stage)}}" class="btn action centered" style="max-width: 200px;">{% if specter.setup_status.stage == "start" %}{{ _('Get started!') }}{% else %}{{ _('Continue setup') }}{% endif %}</a>
|
||||
<br><br>
|
||||
{% else %}
|
||||
<h2 style="font-size: 1.6em;">Getting Started</h2><br>
|
||||
{% if ('://localhost:' not in url_for('index', _external=True) and '://127.0.0.1:' not in url_for('index', _external=True)) and specter.hwi_bridge_url != "http://127.0.0.1:25441/hwi/api/" %}
|
||||
<div style="margin: auto; display: block;">
|
||||
<div class="card" style="margin: auto;padding: 5px 10px 15px; width: 90%;">
|
||||
<div class="row">
|
||||
<img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/>
|
||||
<p style="color: #FF9A00; font-size: 1.1em; margin-left: 10px;">Using Specter from a remote machine?</p>
|
||||
<h2 style="font-size: 1.6em;">{{ _('Getting Started') }}</h2><br>
|
||||
{% if ('://localhost:' not in url_for('index', _external=True) and '://127.0.0.1:' not in url_for('index', _external=True)) and specter.hwi_bridge_url != "http://127.0.0.1:25441/hwi/api/" %}
|
||||
<div style="margin: auto; display: block;">
|
||||
<div class="card" style="margin: auto;padding: 5px 10px 15px; width: 90%;">
|
||||
<div class="row">
|
||||
<img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/>
|
||||
<p style="color: #FF9A00; font-size: 1.1em; margin-left: 10px;">{{ _('Using Specter from a remote machine?') }}</p>
|
||||
</div>
|
||||
<p style="margin-left: 30px; margin-top: -10px; margin-right: 20px; text-align: left;">{{ _('If you run Specter on a remote machine, like Umbrel or a myNode, you need to update settings to connect hardware wallets via USB.') }}</p>
|
||||
<a href="{{ url_for('settings_endpoint.hwi') }}" style="color: #fff; text-decoration: underline; cursor: pointer;">
|
||||
<h4>{{ _("Update your settings") }}</h4>
|
||||
</a>
|
||||
</div>
|
||||
<p style="margin-left: 30px; margin-top: -10px; margin-right: 20px; text-align: left;">If you run Specter on a remote machine, like Umbrel or a myNode, you need to update settings to connect hardware wallets via USB.</p>
|
||||
<a href="{{ url_for('settings_endpoint.hwi') }}" style="color: #fff; text-decoration: underline; cursor: pointer;">
|
||||
<h4>Update your settings</h4>
|
||||
</a>
|
||||
<br>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
{% endif %}
|
||||
<table style="width: 90%; margin: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/checkbox-' + ('un' if not specter.chain else '') + 'tick.svg') }}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('setup_endpoint.node_type') }}">
|
||||
<h2>Connect Specter with Bitcoin Core node.</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
{% if not specter.chain %}
|
||||
<button type="button" class="btn" onclick="showPageOverlay('connect-node-help-popup')">Need help?</button>
|
||||
<div id="connect-node-help-popup" class="hidden">
|
||||
<h1>Connecting Specter to your Bitcoin Core node</h1>
|
||||
<div class="overflow">
|
||||
<button style="padding: 10px; width: 80%; font-size: 1.2em; margin: 20px auto;" type="button" class="btn" onclick="hidePageOverlay();showPageOverlay('no-node-help-popup')">I don't have a Bitcoin Core node yet.</button>
|
||||
<button style="padding: 10px; width: 80%; font-size: 1.2em; margin: 20px auto;" type="button" class="btn" onclick="hidePageOverlay();showPageOverlay('local-node-help-popup')">I have Bitcoin Core on this computer.</button>
|
||||
<button style="padding: 10px; width: 80%; font-size: 1.2em; margin: 20px auto;" type="button" class="btn" onclick="hidePageOverlay();showPageOverlay('remote-node-help-popup')">I have a remote Bitcoin Core node.</button>
|
||||
{% endif %}
|
||||
|
||||
<table style="width: 90%; margin: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/checkbox-' + ('un' if not specter.chain else '') + 'tick.svg') }}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('setup_endpoint.node_type') }}">
|
||||
<h2>{{ _("Connect Specter with Bitcoin Core node.") }}</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
{% if not specter.chain %}
|
||||
<button type="button" class="btn" onclick="showPageOverlay('connect-node-help-popup')">Need help?</button>
|
||||
<div id="connect-node-help-popup" class="hidden">
|
||||
<h1>{{ _("Connecting Specter to your Bitcoin Core node") }}</h1>
|
||||
<div class="overflow">
|
||||
<button style="padding: 10px; width: 80%; font-size: 1.2em; margin: 20px auto;" type="button" class="btn" onclick="hidePageOverlay();showPageOverlay('no-node-help-popup')">{{ _("I don't have a Bitcoin Core node yet.") }}</button>
|
||||
<button style="padding: 10px; width: 80%; font-size: 1.2em; margin: 20px auto;" type="button" class="btn" onclick="hidePageOverlay();showPageOverlay('local-node-help-popup')">{{ _("I have Bitcoin Core on this computer.") }}</button>
|
||||
<button style="padding: 10px; width: 80%; font-size: 1.2em; margin: 20px auto;" type="button" class="btn" onclick="hidePageOverlay();showPageOverlay('remote-node-help-popup')">{{ _("I have a remote Bitcoin Core node.") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="no-node-help-popup" class="hidden">
|
||||
<h1>Installing and Setting Up Bitcoin Core</h1>
|
||||
Check out these video tutorials on downloading and setting up Bitcoin Core with Specter Desktop:<br><br>
|
||||
<ul style="text-align: left; margin: auto; line-height: 2;">
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/setup-bitcoin-core/" target="_blank" style="color: #fff;">KIS: Setting up Bitcoin Core</a></li>
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/how-to-use-specter-wallet-1/" target="_blank" style="color: #fff;">KIS: Setting up Specter Desktop</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=4koKF2MDXtk&t=2s" target="_blank" style="color: #fff;">Ministry of Nodes: Setting up Bitcoin Core and Specter on Windows</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="local-node-help-popup" class="hidden">
|
||||
<h1>Connecting Specter to your local Bitcoin Core node</h1>
|
||||
<p>If you're running a local Bitcoin Core node, Specter should be able to auto-detect it if its data folder is located at the default location.<br>
|
||||
In case it isn't, you might need to specify the Bitcoin Core data folder path in Specter's settings page.<br>
|
||||
Alternatively, you can switch off auto-detect from the settings and provide the Bitcoin Core RPC credentials and host URL manually.<br><br>
|
||||
<b>Please note:</b><br>Your node must be configured with <code>server=1</code> line in the <code>bitcoin.conf</code> file.<br>
|
||||
If you don't have that, add it and restart Bitcoin Core.
|
||||
</p>
|
||||
<br><br>
|
||||
In addition, you can check out these video tutorials for setting up Bitcoin Core with Specter Desktop:<br><br>
|
||||
<ul style="text-align: left; margin: auto; line-height: 2;">
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/setup-bitcoin-core/" target="_blank" style="color: #fff;">KIS: Setting up Bitcoin Core</a></li>
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/how-to-use-specter-wallet-1/" target="_blank" style="color: #fff;">KIS: Setting up Specter Desktop</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=4koKF2MDXtk&t=2s" target="_blank" style="color: #fff;">Ministry of Nodes: Setting up Bitcoin Core and Specter on Windows</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="remote-node-help-popup" class="hidden">
|
||||
<h1>Connecting Specter to your remote Bitcoin Core node</h1>
|
||||
<h3>Connecting to a "node in a box"</h3>
|
||||
<p style="font-size: 1.05em;">
|
||||
Specter can be used with a remote Bitcoin Core node running on a machine like RaspiBlitz, myNode, Nodl, Umbrel, and Embassy.<br>
|
||||
RaspiBlitz and myNode currently allow running Specter in the remote machine directly, which means it's possible to use it and skip setting up connection manually.<br><br>
|
||||
If you use a different node or would still like to connect run Specter locally but connect remotely, you can do so by going to Settings, Bitcoin Core tab, and provide the Bitcoin Core RPC credentials and host URL manually.
|
||||
</p>
|
||||
<h3>Connecting over Tor</h3>
|
||||
<p style="font-size: 1.05em;">Connecting over Tor requires that you first configure Tor on your local machine.<br>
|
||||
You can checkout <a href="https://www.keepitsimplebitcoin.com/how-to-install-tor/" style="color: #fff;" target="_blank">this video tutorial</a>, or read the documentation for <a href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/tor.md" target="_blank" style="color: #fff;">Mac and Linux</a> or <a href="https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Tor/Tor.md#windows-10" target="_blank" style="color: #fff;">Window</a><br>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/checkbox-' + ('un' if not (specter.device_manager.devices | length) else '') + 'tick.svg') }}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('devices_endpoint.new_device_type') }}">
|
||||
<h2>Add signing devices you wish to use.</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/checkbox-' + ('un' if not (specter.wallet_manager.wallets | length) else '') + 'tick.svg') }}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('wallets_endpoint.new_wallet_type') }}">
|
||||
<h2>Create your first wallet!</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
</td>
|
||||
</tr>
|
||||
{% if specter.chain == 'main' %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/ghost.png')}}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('get_whitepaper') }}">
|
||||
<h2>And when you are all set: Get the whitepaper from the timechain.</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<div id="no-node-help-popup" class="hidden">
|
||||
<h1>{{ _("Installing and Setting Up Bitcoin Core") }}</h1>
|
||||
{{ _("Check out these video tutorials on downloading and setting up Bitcoin Core with Specter Desktop") }}:<br><br>
|
||||
<ul style="text-align: left; margin: auto; line-height: 2;">
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/setup-bitcoin-core/" target="_blank" style="color: #fff;">KIS: Setting up Bitcoin Core</a></li>
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/how-to-use-specter-wallet-1/" target="_blank" style="color: #fff;">KIS: Setting up Specter Desktop</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=4koKF2MDXtk&t=2s" target="_blank" style="color: #fff;">Ministry of Nodes: Setting up Bitcoin Core and Specter on Windows</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="local-node-help-popup" class="hidden">
|
||||
<h1>{{ _('Connecting Specter to your local Bitcoin Core node') }}</h1>
|
||||
<p>{{ _("If you're running a local Bitcoin Core node, Specter should be able to auto-detect it if its data folder is located at the default location.") }}<br>
|
||||
{{ _("In case it isn't, you might need to specify the Bitcoin Core data folder path in Specter's settings page.") }}<br>
|
||||
{{ _("Alternatively, you can switch off auto-detect from the settings and provide the Bitcoin Core RPC credentials and host URL manually.") }}<br><br>
|
||||
{{ _("<b>Please note:</b><br>Your node must be configured with <code>server=1</code> line in the <code>bitcoin.conf</code> file.") }}<br>
|
||||
{{ _("If you don't have that, add it and restart Bitcoin Core.") }}"
|
||||
</p>
|
||||
<br><br>
|
||||
{{ _("In addition, you can check out these video tutorials for setting up Bitcoin Core with Specter Desktop") }}:<br><br>
|
||||
<ul style="text-align: left; margin: auto; line-height: 2;">
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/setup-bitcoin-core/" target="_blank" style="color: #fff;">KIS: Setting up Bitcoin Core</a></li>
|
||||
<li><a href="https://www.keepitsimplebitcoin.com/how-to-use-specter-wallet-1/" target="_blank" style="color: #fff;">KIS: Setting up Specter Desktop</a></li>
|
||||
<li><a href="https://www.youtube.com/watch?v=4koKF2MDXtk&t=2s" target="_blank" style="color: #fff;">Ministry of Nodes: Setting up Bitcoin Core and Specter on Windows</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="remote-node-help-popup" class="hidden">
|
||||
<h1>{{ _("Connecting Specter to your remote Bitcoin Core node") }}</h1>
|
||||
<h3>{{ _('Connecting to a "node in a box"') }}</h3>
|
||||
<p style="font-size: 1.05em;">
|
||||
{{ _("Specter can be used with a remote Bitcoin Core node running on a machine like RaspiBlitz, myNode, Nodl, Umbrel, and Embassy.") }}<br>
|
||||
{{ _("RaspiBlitz and myNode currently allow running Specter in the remote machine directly, which means it's possible to use it and skip setting up connection manually.") }}<br><br>
|
||||
{{ _("If you use a different node or would still like to connect run Specter locally but connect remotely, you can do so by going to Settings, Bitcoin Core tab, and provide the Bitcoin Core RPC credentials and host URL manually.") }}"
|
||||
</p>
|
||||
<h3>{{ _("Connecting over Tor") }}</h3>
|
||||
<p style="font-size: 1.05em;">{{ _("Connecting over Tor requires that you first configure Tor on your local machine.") }}<br>
|
||||
You can checkout <a href="https://www.keepitsimplebitcoin.com/how-to-install-tor/" style="color: #fff;" target="_blank">this video tutorial</a>, or read the documentation for <a href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/tor.md" target="_blank" style="color: #fff;">Mac and Linux</a> or <a href="https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Tor/Tor.md#windows-10" target="_blank" style="color: #fff;">Window</a><br>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/checkbox-' + ('un' if not (specter.device_manager.devices | length) else '') + 'tick.svg') }}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('devices_endpoint.new_device_type') }}">
|
||||
<h2>{{ _("Add signing devices you wish to use.") }}</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/checkbox-' + ('un' if not (specter.wallet_manager.wallets | length) else '') + 'tick.svg') }}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('wallets_endpoint.new_wallet_type') }}">
|
||||
<h2>{{ _("Create your first wallet!") }}</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
</td>
|
||||
</tr>
|
||||
{% if specter.chain == 'main' %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='img/ghost.png')}}" width="30px">
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
<a href="{{ url_for('get_whitepaper') }}">
|
||||
<h2>{{ _("And when you are all set: Get the whitepaper from the timechain.") }}</h2>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: left;">
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{% endif %}
|
||||
Having issues? Try reading <a href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/faq.md" target="_blank" style="color: #fff; font-size: 1.05em; font-weight: bolder;">the FAQ</a><br><br>
|
||||
Still having issues?<br>
|
||||
Feel free to <a href="https://github.com/cryptoadvance/specter-desktop/issues/new" target="_blank" style="color: #fff;">open an issue on the GitHub</a> or ask in the <a href="https://t.me/spectersupport" target="_blank" style="color: #fff;">Specter Community telegram chat</a>
|
||||
|
||||
{{ _('Having issues? Try reading <a href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/faq.md" target="_blank" style="color: #fff; font-size: 1.05em; font-weight: bolder;">the FAQ</a>') }}<br><br>
|
||||
{{ _("Still having issues?") }}<br>
|
||||
{{ _('Feel free to <a href="https://github.com/cryptoadvance/specter-desktop/issues/new" target="_blank" style="color: #fff;">open an issue on the GitHub</a> or ask in the <a href="https://t.me/spectersupport" target="_blank" style="color: #fff;">Specter Community telegram chat</a>') }}
|
||||
<br><br>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<a href="https://github.com/cryptoadvance/specter-desktop#help-wanted-do-you-like-specter" target="_blank" style="color: #fff; display: inline-block;"><h2 id="help-wanted-text" style="display: inline-block;">Help wanted: Do you like Specter?</h2><img src="static/img/love.png" width="20px" style="vertical-align: sub; margin-left: 10px;"/></a>
|
||||
<a href="https://github.com/cryptoadvance/specter-desktop#help-wanted-do-you-like-specter" target="_blank" style="color: #fff; display: inline-block;"><h2 id="help-wanted-text" style="display: inline-block;">{{ _('Help wanted: Do you like Specter?') }}</h2><img src="static/img/love.png" width="20px" style="vertical-align: sub; margin-left: 10px;"/></a>
|
||||
<div class="spacer"></div>
|
||||
<div class="footer">
|
||||
Supported and maintained by:<br>
|
||||
{{ _("Supported and maintained by") }}:<br>
|
||||
<a href="https://specter.solutions/" target="_blank" style="color: #fff;">
|
||||
Specter.Solutions
|
||||
</a>
|
||||
|
|
@ -256,6 +262,9 @@
|
|||
}
|
||||
</script>
|
||||
{% block scripts %}
|
||||
<script>
|
||||
{% include "includes/language/language_js.jinja" %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="text" autocomplete="off" spellcheck="false" id="newtitle" name="newtitle" value="{{ title }}" style="font-size: 1.5em; text-align: center; display: none;" />
|
||||
<div class="row center">
|
||||
<button type="submit" class="btn update" id="save" name="action" value="rename" style="display: none; width: 49.2%; margin: 3px;">Update</button>
|
||||
<button type="button" class="btn cancel" id="cancel" onclick="toggleEdit()" style="display: none; width: 49.2%; margin: 3px;">Cancel</button>
|
||||
<button type="button" class="btn edit" id="edit" onclick="toggleEdit()">Edit Name</button>
|
||||
<button type="submit" class="btn update" id="save" name="action" value="rename" style="display: none; width: 49.2%; margin: 3px;">{{ _("Update") }}</button>
|
||||
<button type="button" class="btn cancel" id="cancel" onclick="toggleEdit()" style="display: none; width: 49.2%; margin: 3px;">{{ _("Cancel") }}</button>
|
||||
<button type="button" class="btn edit" id="edit" onclick="toggleEdit()">{{ _("Edit Name") }}</button>
|
||||
</div>
|
||||
<br>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -35,30 +35,30 @@
|
|||
<div id="price-popup" class="hidden">
|
||||
<form id="price-form">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<h1>Price Settings</h1>
|
||||
<h1>{{ _("Price Settings") }}</h1>
|
||||
{% if specter.price_check %}
|
||||
<div class="card">
|
||||
<p>Please set custom rate or choose between the automated price provider options.</p>
|
||||
<p>{{ _("Please set custom rate or choose between the automated price provider options.") }}</p>
|
||||
<nav class="row">
|
||||
<button type="button" id="manual_price_btn" class="btn radio left checked" onclick="setPriceView('manual')"> Manual </button>
|
||||
<button type="button" id="automatic_price_btn" class="btn radio right" onclick="setPriceView('automatic')"> Automatic </button>
|
||||
<button type="button" id="manual_price_btn" class="btn radio left checked" onclick="setPriceView('manual')">{{ _("Manual") }}</button>
|
||||
<button type="button" id="automatic_price_btn" class="btn radio right" onclick="setPriceView('automatic')">{{ _("Automatic") }}</button>
|
||||
</nav>
|
||||
<br>
|
||||
<div id="manual_price">
|
||||
Price:
|
||||
{{ _("Price:") }}
|
||||
<input style="max-width: 150px;" type="number" min="0" step="0.01" name="alt_rate" value="{{ specter.alt_rate }}" placeholder="Set the price of bitcoin in your chosen currency">
|
||||
<br><br>
|
||||
Symbol:
|
||||
{{ _("Symbol:") }}
|
||||
<input style="max-width: 150px;" type="text" name="alt_symbol" value="{{ specter.alt_symbol }}" placeholder="eg. $/€/£">
|
||||
<br>
|
||||
</div>
|
||||
<div id="automatic_price" class="hidden">
|
||||
<h2>Select price provider:</h2>
|
||||
<h2>{{ _("Select price provider:") }}</h2>
|
||||
<table id="auto-price-table" style="border: none; margin: auto;">
|
||||
<tbody>
|
||||
<tr style="background: none;">
|
||||
<td style="padding-bottom: 0px; border: none; text-align: right;">
|
||||
Currency:
|
||||
{{ _("Currency:") }}
|
||||
</td>
|
||||
<td style="padding-bottom: 0px; border: none; text-align: left;">
|
||||
<fieldset style="border:none; display: inline; width: 100%;">
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
</tr>
|
||||
<tr style="background: none;">
|
||||
<td style="padding-top: 10px; padding-bottom: 0px; padding-right: 15px; border: none; text-align: right;">
|
||||
<p>Provider:</p>
|
||||
<p>{{ _("Provider:") }}</p>
|
||||
</td>
|
||||
<td style="padding-top: 10px; padding-bottom: 0px; border: none; text-align: left;">
|
||||
<fieldset style="border:none; display: inline; width: 100%;">
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
</tr>
|
||||
<tr style="background: none;" class="hidden" id="weight_unit_row">
|
||||
<td style="padding-top: 10px; padding-bottom: 10px; border: none; text-align: right;">
|
||||
Weight Unit:
|
||||
{{ _("Weight Unit:") }}
|
||||
</td>
|
||||
<td style="padding-top: 10px; padding-bottom: 10px; border: none; text-align: left;">
|
||||
<fieldset style="border:none; display: inline; width: 100%;">
|
||||
|
|
@ -169,26 +169,26 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="note" style="max-width: 300px; margin: auto;">Price will automatically be fetched from the selected provider every 10 minutes.</p>
|
||||
<p class="note" style="max-width: 300px; margin: auto;">{{ _("Price will automatically be fetched from the selected provider every 10 minutes.") }}</p>
|
||||
</div>
|
||||
<br><br>
|
||||
<input type="hidden" id="price_type" name="price_type" value="{{ 'automatic' if specter.price_provider else 'manual' }}">
|
||||
<button type="button" class="btn" onclick="savePriceData(true)" style="margin: auto;">Save</button>
|
||||
<button type="button" class="btn" onclick="savePriceData(true)" style="margin: auto;">{{ _("Save") }}</button>
|
||||
</div>
|
||||
<br><br>
|
||||
<button type="button" class="btn" onclick="togglePriceCheck(false)" style="margin: auto;" >Disable showing price</button>
|
||||
<button type="button" class="btn" onclick="togglePriceCheck(false)" style="margin: auto;" >{{ _("Disable showing price") }}</button>
|
||||
{% else %}
|
||||
<p>You can enable showing price feature to see the price<br>information of your Bitcoin amounts across Specter.</p>
|
||||
<p>{{ _("You can enable showing price feature to see the price<br>information of your Bitcoin amounts across Specter.") }}</p>
|
||||
<br>
|
||||
<button type="button" class="btn" style="margin: auto;" onclick="togglePriceCheck(true)">Enable showing price</button>
|
||||
<button type="button" class="btn" style="margin: auto;" onclick="togglePriceCheck(true)">{{ _("Enable showing price") }}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<script>
|
||||
async function togglePriceCheck(showPrice) {
|
||||
if (showPrice) {
|
||||
showNotification('Turning on show price mode...', 0)
|
||||
showNotification('{{ _("Turning on show price mode...") }}', 0)
|
||||
} else {
|
||||
showNotification('Turning off show price mode...', 0)
|
||||
showNotification('{{ _("Turning off show price mode...") }}', 0)
|
||||
}
|
||||
let url = `{{ url_for('price_endpoint.toggle') }}`;
|
||||
var formData = new FormData(document.getElementById('price-form'));
|
||||
|
|
@ -209,7 +209,7 @@
|
|||
location.reload();
|
||||
return
|
||||
};
|
||||
showError('Failed to toggle price mode...')
|
||||
showError('{{ _("Failed to toggle price mode...") }}')
|
||||
} catch(e) {
|
||||
console.log("Caught error: ", e);
|
||||
showError(e);
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
}
|
||||
|
||||
async function savePriceData(reload) {
|
||||
showNotification('Updating price data...', 0)
|
||||
showNotification('{{ _("Updating price data...") }}', 0)
|
||||
let url = `{{ url_for('price_endpoint.update') }}`;
|
||||
var formData = new FormData(document.getElementById('price-form'));
|
||||
try {
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
location.reload();
|
||||
return
|
||||
};
|
||||
showError('Failed to fetch price data from selected provider, please try again or select another provider.')
|
||||
showError('{{ _("Failed to fetch price data from selected provider, please try again or select another provider.") }}')
|
||||
} catch(e) {
|
||||
console.log("Caught error: ", e);
|
||||
showError(e);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<div style="display: inline;">
|
||||
<span>Device Type:</span>
|
||||
<span>{{ _("Device Type:") }}</span>
|
||||
<br>
|
||||
<fieldset style="border:none; padding: 0px; margin: 0px;">
|
||||
<select name="device_type" id="device_type">
|
||||
<option value="" selected>Select type</option>
|
||||
<option value="" selected>{{ _("Select type") }}</option>
|
||||
{% for cls in specter.device_manager.supported_devices %}
|
||||
<option value="{{cls.device_type}}">{{cls.name}}</option>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@
|
|||
<div class="row" style="text-align: center; line-height: 2;">
|
||||
{% include "device/components/device_type.jinja" %}
|
||||
</div>
|
||||
<button type="submit" class="btn hidden" id="settype" name="action" value="settype" style="margin: auto; margin-top: 10px; align-self:center;">Update</button>
|
||||
<button type="submit" class="btn hidden" id="settype" name="action" value="settype" style="margin: auto; margin-top: 10px; align-self:center;">{{ _("Update") }}</button>
|
||||
</form>
|
||||
<br>
|
||||
<div class="table-holder">
|
||||
<table id="keys-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network</th><th>Purpose</th><th class="optional">Derivation</th><th class="mobile-only">Export</th><th class="optional"></th><th class="optional">Key</th><th class="optional">Actions</th>
|
||||
<th>{{ _("Network") }}</th><th>{{ _("Purpose") }}</th><th class="optional">{{ _("Derivation") }}</th><th class="mobile-only">{{ _("Export") }}</th><th class="optional"></th><th class="optional">{{ _("Key") }}</th><th class="optional">{{ _("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<input type="hidden" id="key_selected" value="0" />
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
{% if specter.hide_sensitive_info %}
|
||||
<td></td>
|
||||
{% else %}
|
||||
<td width="15" onclick="showPageOverlay('key_export_qr');document.getElementById('key_selected').value='{{loop.index0}}';toggleKeyDisplay();" title="Show key QR code" style="cursor: pointer;">
|
||||
<td width="15" onclick="showPageOverlay('key_export_qr');document.getElementById('key_selected').value='{{loop.index0}}';toggleKeyDisplay();" title="{{ _('Show key QR code') }}" style="cursor: pointer;">
|
||||
<img style="margin-top: 5px; width: 40px;" src="{{ url_for('static', filename='img/qr-code.svg') }}" class="svg-white"/>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
<form action="./" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="key" value="{{ key.original }}">
|
||||
<button type="submit" name="action" value="delete_key" class="btn danger hovering">Delete</button>
|
||||
<button type="submit" name="action" value="delete_key" class="btn danger hovering">{{ _("Delete") }}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -66,38 +66,38 @@
|
|||
<div style="line-height: 1; margin-top: 30px;">
|
||||
<form action="./" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button id="add_keys" type="submit" name="action" value="add_keys" class="btn centered">Add more keys</button>
|
||||
<button id="add_keys" type="submit" name="action" value="add_keys" class="btn centered">{{ _("Add more keys") }}</button>
|
||||
</form>
|
||||
<br>
|
||||
{% if device.hwi_support %}
|
||||
<button type="button" class="btn centered" onclick="showPageOverlay('message-signing')">Sign message on device</button>
|
||||
<button type="button" class="btn centered" onclick="showPageOverlay('message-signing')">{{ _("Sign message on device") }}</button>
|
||||
<div id="message-signing" class="hidden" style="text-align: left;">
|
||||
Derivation path:<input type="text" id="messageDerivationPath" type="text" value="" placeholder="e.g. m/84h/0h/0h/0/0"><br><br>
|
||||
Message:<br>
|
||||
{{ _("Derivation path") }}:<input type="text" id="messageDerivationPath" type="text" value="" placeholder="e.g. m/84h/0h/0h/0/0"><br><br>
|
||||
{{ _("Message") }}:<br>
|
||||
<textarea id="message" placeholder="Enter the message you would like to sign" style="font-size: 0.95em;"></textarea>
|
||||
<span id="signature-label" class="hidden">Message signature:</span>
|
||||
<p style="word-break: break-all; margin-top: 5px;" id="signature" title="Copy message signature" class="explorer-link" onclick="copyText(this.innerText, 'Copied message signature')">
|
||||
<span id="signature-label" class="hidden">{{ _("Message signature") }}:</span>
|
||||
<p style="word-break: break-all; margin-top: 5px;" id="signature" title='{{ _("Copy message signature") }}' class="explorer-link" onclick="copyText(this.innerText, '{{ _("Copied message signature") }}')">
|
||||
</p>
|
||||
<button type="button" class="btn centered" onclick="signMessageOnDevice();">Sign message on device</button>
|
||||
<button type="button" class="btn centered" onclick="signMessageOnDevice();">{{ _("Sign message on device") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if specter.is_liquid %}
|
||||
<br>
|
||||
<a class="btn centered" href="{{ url_for('devices_endpoint.device_blinding_key', device_alias=device.alias) }}">Update master blinding key</a>
|
||||
<a class="btn centered" href="{{ url_for('devices_endpoint.device_blinding_key', device_alias=device.alias) }}">{{ _("Update master blinding key") }}</a>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if device.supports_hwi_toggle_passphrase %}
|
||||
<button type="button" class="btn centered" onclick="togglePassphrase('{{ device.device_type }}')">Toggle device passphrase</button>
|
||||
<button type="button" class="btn centered" onclick="togglePassphrase('{{ device.device_type }}')">{{ _("Toggle device passphrase") }}</button>
|
||||
<br>
|
||||
{% endif %}
|
||||
<form action="./" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" name="action" value="forget" class="btn danger centered" id="forget_device">Forget the device</button>
|
||||
<button type="submit" name="action" value="forget" class="btn danger centered" id="forget_device">{{ _("Forget the device") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
{% if wallets %}
|
||||
<h1>Wallets Using This Device</h1>
|
||||
<h1>{{ _("Wallets Using This Device") }}</h1>
|
||||
<div class="row overflow">
|
||||
{% for wallet in wallets %}
|
||||
<a href="{{ url_for('wallets_endpoint.wallet', wallet_alias=wallet.alias) }}" style="text-decoration: none;">
|
||||
|
|
@ -110,12 +110,12 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="hidden" id="new_device_popup">
|
||||
<h1>New device was added successfully!</h1>
|
||||
<h1>{{ _("New device was added successfully!") }}</h1>
|
||||
<div class="row overflow">
|
||||
<a href="{{ url_for('devices_endpoint.new_device_type') }}" style="text-decoration: none;">
|
||||
<div class="small-card radio">
|
||||
<img src="{{ url_for('static', filename='img/plus.svg') }}" class="svg-white" style="transform: scale(1.5);">
|
||||
Add another device
|
||||
{{ _("Add another device") }}
|
||||
</div>
|
||||
</a>
|
||||
<form action="{{ url_for('wallets_endpoint.new_wallet', wallet_type='simple') }}" method="POST">
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
<button type="submit" style="background: none; color: inherit; border: none; padding: 0; font: inherit; cursor: pointer; outline: inherit;">
|
||||
<div class="small-card radio">
|
||||
<img src="{{ url_for('static', filename='img/key.svg') }}" class="svg-white" style="width: 120px;">
|
||||
Create single key wallet
|
||||
{{ _("Create single key wallet") }}
|
||||
</div>
|
||||
</button>
|
||||
</form>
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
<a href="{{ url_for('wallets_endpoint.new_wallet', wallet_type='multisig') }}" style="text-decoration: none;">
|
||||
<div class="small-card radio">
|
||||
<img src="{{ url_for('static', filename='img/two-keys.svg') }}" class="svg-white" style="width: 120px;">
|
||||
Create multisignature wallet
|
||||
{{ _("Create multisignature wallet") }}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
@ -173,7 +173,7 @@
|
|||
document.getElementById('signature-label').style.display = 'block';
|
||||
document.getElementById('signature').innerText = signature;
|
||||
showPageOverlay('message-signing');
|
||||
showNotification('Message was signed successfully!')
|
||||
showNotification('{{ _("Message was signed successfully!") }}')
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -6,29 +6,29 @@
|
|||
<div style="width: auto; margin: 40px;" class="card center" >
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div>
|
||||
<h1> Get the master blinding key </h1>
|
||||
<h1> {{ _("Get the master blinding key") }} </h1>
|
||||
<input type="hidden" name="new_device" value="{{ new_device }}"/>
|
||||
<input type="hidden" name="blinding_key" id="blinding_key"/>
|
||||
<div style="width:100%;">
|
||||
<div id="connect-hwi" style="max-width: 250px; width: 250px; margin: 20px auto;" class="btn {% if not device.hwi_support %}hidden{% endif %}">
|
||||
<img src="{{ url_for('static', filename='img/usb.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white">
|
||||
Get via USB
|
||||
{{ _("Get via USB") }}
|
||||
</div>
|
||||
<qr-scanner id="key-scan" {% if not device.qr_code_support %}class="hidden"{% endif %}>
|
||||
<a slot="button" href="#" class="btn" style="max-width: 250px; width: 250px; margin: 20px auto;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Scan QR code
|
||||
{{ _("Scan QR code") }}
|
||||
</a>
|
||||
</qr-scanner>
|
||||
<button {% if device.device_type not in ['electrum', 'other'] %}class="hidden"{% endif %} type="button" onclick="showPageOverlay('paste-xpub-popup')" class="btn centered" id="xpub-paste" style="max-width: 250px; width: 250px; margin: 20px auto;">
|
||||
<img src="{{ url_for('static', filename='img/copy.svg') }}" style="width: 22px; margin: 0px;" class="svg-white">
|
||||
Paste xpub
|
||||
{{ _("Paste xpub") }}
|
||||
</button>
|
||||
<div class="hidden" id="paste-xpub-popup">
|
||||
<h1>Paste xpub</h1>
|
||||
<h1>{{ _("Paste xpub") }}</h1>
|
||||
<input type="text" id="paste-xpub-text" placeholder="Paste your xpub here">
|
||||
<br><br>
|
||||
<button type="button" onclick="addXpubs(document.getElementById('paste-xpub-text').value);hidePageOverlay();document.getElementById('paste-xpub-text').value='';" class="btn centered">Add xpub</button>
|
||||
<button type="button" onclick="addXpubs(document.getElementById('paste-xpub-text').value);hidePageOverlay();document.getElementById('paste-xpub-text').value='';" class="btn centered">{{ _("Add xpub") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,49 +46,49 @@
|
|||
<input type="hidden" name="range_end" value="{{ range_end }}"/>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h1 style="font-size: 1.8em;">Upload Keys</h1>
|
||||
<h1 style="font-size: 1.8em;">{{ _("Upload Keys") }}</h1>
|
||||
{% if not existing_device %}
|
||||
<h2 style="max-width: 500px; margin: auto;">Name your Device: <input type="text" name="device_name" placeholder="My device" id="device_name" required/></h2>
|
||||
<h2 style="max-width: 500px; margin: auto;">{{ _("Name your Device: ") }}<input type="text" name="device_name" placeholder="My device" id="device_name" required/></h2>
|
||||
<br>
|
||||
{% endif %}
|
||||
<div id="coldcard-instructions" {%if device_class.device_type != 'coldcard' %}class="hidden"{% endif %}>
|
||||
<p>
|
||||
Connect your ColdCard to the computer via USB and unlock it or upload a wallet export file from micro SD card.<br>
|
||||
{{ _("Connect your ColdCard to the computer via USB and unlock it or upload a wallet export file from micro SD card.") }}<br>
|
||||
</p>
|
||||
<span class="note">
|
||||
To export the wallet data file from the ColdCard, insert your micro SD card into the device, then:<br>
|
||||
{{ _("To export the wallet data file from the ColdCard, insert your micro SD card into the device, then:<br>
|
||||
To get the single sig xpubs, go to: Advanced -> MicroSD Card -> Export Wallet -> Generic JSON. When asked for account, select account number (default is 0) and confirm the export.<br>
|
||||
To get the multisig xpubs, go to: Settings -> Multisig Wallets -> Export XPUB and confirm the export.
|
||||
To get the multisig xpubs, go to: Settings -> Multisig Wallets -> Export XPUB and confirm the export.") }}
|
||||
</span>
|
||||
</div>
|
||||
<div id="specter-instructions" {%if device_class.device_type != 'specter' %}class="hidden"{% endif %}>
|
||||
<p>
|
||||
Connect your Specter-DIY to the computer via USB and unlock it or scan the wallet master public key.<br>
|
||||
{{ _("Connect your Specter-DIY to the computer via USB and unlock it or scan the wallet master public key.") }}<br>
|
||||
</p>
|
||||
<span class="note">
|
||||
To get the master public key QR code to scan, click on: Master public keys, then select each key type you'd like to import and scan the displayed QR code for each into Specter.
|
||||
{{ _("To get the master public key QR code to scan, click on: Master public keys, then select each key type you'd like to import and scan the displayed QR code for each into Specter.") }}
|
||||
</span>
|
||||
</div>
|
||||
<div id="cobo-instructions" {%if device_class.device_type != 'cobo' %}class="hidden"{% endif %}>
|
||||
<p>
|
||||
Scan your Cobo Vault master public key or upload a wallet export file.<br>
|
||||
{{ _("Scan your Cobo Vault master public key or upload a wallet export file.") }}<br>
|
||||
</p>
|
||||
<span class="note">
|
||||
To get the master public keys QR codes to scan:<br><br>
|
||||
{{ _("To get the master public keys QR codes to scan:<br><br>
|
||||
For single sig, click on: Setting -> Watch-Only Wallet -> Generic Wallet, Click the top right 3 dots (...) -> Wallet Info ->Toggle Address Type, then select the wallet address type you would like to use for your wallet.<br><br>
|
||||
For multisig, click on: Multisig Wallet -> Click the top right 3 dots (...) -> Show/Export XPUB, then select the wallet address type you would like to use for your wallet.<br>
|
||||
Cobo Vault will then display the QR code which you should scan into Specter.<br><br>
|
||||
To import with SD card, click on "touch here to export the file with microSD" on the same screen as the QR code.
|
||||
To import with SD card, click on \"touch here to export the file with microSD\" on the same screen as the QR code.") }}
|
||||
</span>
|
||||
</div>
|
||||
<div id="hwi-only-instructions" {%if device_class.device_type in ['bitcoincore', 'cobo', 'coldcard', 'electrum', 'other', 'specter']%}class="hidden"{% endif %}>
|
||||
<p>Connect your hardware device to the computer via USB.</p>
|
||||
<p>{{ _("Connect your hardware device to the computer via USB.") }}</p>
|
||||
</div>
|
||||
<br>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th><th>Purpose</th><th>Derivation</th><th>XPUB</th><th><button type="button" class="btn" style="width: 60px;" id="edit-xpubs-table-btn" onclick="editXpubsTable(this)">Edit</button></th>
|
||||
<th></th><th>{{ _("Purpose") }}</th><th>{{ _("Derivation") }}</th><th>XPUB</th><th><button type="button" class="btn" style="width: 60px;" id="edit-xpubs-table-btn" onclick="editXpubsTable(this)">{{ _("Edit") }}</button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="xpubs_table">
|
||||
|
|
@ -99,35 +99,35 @@
|
|||
<div class="row overflow">
|
||||
<div id="connect-hwi" style="max-width: 250px; width: 250px; margin: 10px;" class="btn {% if not device_class.hwi_support %}hidden{% endif %}">
|
||||
<img src="{{ url_for('static', filename='img/usb.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white">
|
||||
Get via USB
|
||||
{{ _("Get via USB") }}
|
||||
</div>
|
||||
<label id="connect-sdcard" {% if not device_class.sd_card_support %}class="hidden"{% endif %}>
|
||||
<input type="file" id="file" class="inputfile" multiple/>
|
||||
<div class="btn" style="max-width: 250px; width: 250px; margin: 10px;">
|
||||
<img src="{{ url_for('static', filename='img/sd-card.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Upload from SD Card
|
||||
{{ _("Upload from SD Card") }}
|
||||
</div>
|
||||
</label>
|
||||
<qr-scanner id="xpub-scan" {% if not device_class.qr_code_support %}class="hidden"{% endif %}>
|
||||
<a slot="button" href="#" class="btn" style="max-width: 250px; width: 250px; margin: 10px;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Scan QR code
|
||||
{{ _("Scan QR code") }}
|
||||
</a>
|
||||
</qr-scanner>
|
||||
<button {% if device_class.device_type not in ['electrum', 'other'] %}class="hidden"{% endif %} type="button" onclick="showPageOverlay('paste-xpub-popup')" class="btn centered" id="xpub-paste" style="max-width: 250px; width: 250px; margin: 10px;">
|
||||
<img src="{{ url_for('static', filename='img/copy.svg') }}" style="width: 22px; margin: 0px;" class="svg-white">
|
||||
Paste xpub
|
||||
{{ _("Paste xpub") }}
|
||||
</button>
|
||||
<div class="hidden" id="paste-xpub-popup">
|
||||
<h1>Paste xpub</h1>
|
||||
<h1>{{ _("Paste xpub") }}</h1>
|
||||
<input type="text" id="paste-xpub-text" placeholder="Paste your xpub here">
|
||||
<br><br>
|
||||
<button type="button" onclick="addXpubs(document.getElementById('paste-xpub-text').value);hidePageOverlay();document.getElementById('paste-xpub-text').value='';" class="btn centered">Add xpub</button>
|
||||
<button type="button" onclick="addXpubs(document.getElementById('paste-xpub-text').value);hidePageOverlay();document.getElementById('paste-xpub-text').value='';" class="btn centered">{{ _("Add xpub") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" id="submit-keys" class="btn centered action" style="width: 140px; max-width: 140px; margin: auto;">Continue</button>
|
||||
<button type="submit" id="submit-keys" class="btn centered action" style="width: 140px; max-width: 140px; margin: auto;">{{ _("Continue") }}</button>
|
||||
<br><br>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
xpubsTableRowsInput.value = xpubsTableRows;
|
||||
let xpubRowHTML = `
|
||||
<td></td>
|
||||
<td><input id="xpubs-table-row-${xpubsTableRows}-purpose" name="xpubs-table-row-${xpubsTableRows}-purpose" type="text" value="${purpose}" placeholder="XPUB purpose" style="width: 90%; min-width: 100px;"></td>
|
||||
<td><input id="xpubs-table-row-${xpubsTableRows}-purpose" name="xpubs-table-row-${xpubsTableRows}-purpose" type="text" value="${purpose}" placeholder="{{ _("XPUB purpose") }}" style="width: 90%; min-width: 100px;"></td>
|
||||
<td id="xpubs-table-row-${xpubsTableRows}-derivation" style="min-width: 0px;">${derivation}</td>
|
||||
<input id="xpubs-table-row-${xpubsTableRows}-derivation-hidden" name="xpubs-table-row-${xpubsTableRows}-derivation-hidden" type="hidden" value="${derivation}">
|
||||
<td id="xpubs-table-row-${xpubsTableRows}-xpub" class="xpub scroll" style="overflow: auto;">${xpub}</td>
|
||||
|
|
@ -177,9 +177,9 @@
|
|||
} else {
|
||||
let xpubPurpose = 'Custom'
|
||||
if (xpub.startsWith("ypub") || xpub.startsWith("upub")) {
|
||||
xpubPurpose = '#0 Single Sig (Nested)';
|
||||
xpubPurpose = '{{ _("#0 Single Sig (Nested)") }}';
|
||||
} else if (xpub.startsWith("vpub") || xpub.startsWith("zpub")) {
|
||||
xpubPurpose = '#0 Single Sig (Segwit)';
|
||||
xpubPurpose = '{{ _("#0 Single Sig (Segwit)") }}';
|
||||
}
|
||||
addXpub(xpubPurpose, 'm/', xpub)
|
||||
}
|
||||
|
|
@ -198,8 +198,8 @@
|
|||
let xpubAddAccountRowHTML = `
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><input id="account_number_xpubs" value="0" min="0" type="number" step="1" style="width: 150px;" placeholder="Account #"></td>
|
||||
<td><button type="button" class="btn" style="width: 150px;" onclick="addAccountXpubs()">Add account</button></td>
|
||||
<td><input id="account_number_xpubs" value="0" min="0" type="number" step="1" style="width: 150px;" placeholder="{{ _("Account #") }}"></td>
|
||||
<td><button type="button" class="btn" style="width: 150px;" onclick="addAccountXpubs()">{{ _("Add account") }}</button></td>
|
||||
<td></td>
|
||||
`
|
||||
let xpubAddAccountRow = document.createElement('tr');
|
||||
|
|
@ -210,10 +210,10 @@
|
|||
|
||||
let xpubAddCustomRowHTML = `
|
||||
<td></td>
|
||||
<td><input id="new_xpub_purpose" type="text" placeholder="XPUB purpose"></td>
|
||||
<td><input id="new_xpub_purpose" type="text" placeholder="{{ _("XPUB purpose") }}"></td>
|
||||
<td><input id="new_xpub_derivation" type="text" placeholder="m/..."></td>
|
||||
<td></td>
|
||||
<td><button type="button" class="btn" style="width: 60px;" onclick="addCustomDerivation()">Add</button></td>
|
||||
<td><button type="button" class="btn" style="width: 60px;" onclick="addCustomDerivation()">{{ _("Add") }}</button></td>
|
||||
`
|
||||
let xpubAddCustomRow = document.createElement('tr');
|
||||
xpubAddCustomRow.id = `edit_custom_der`;
|
||||
|
|
@ -224,8 +224,8 @@
|
|||
let xpubAddRowHTML = `
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><button type="button" class="btn" style="width: 150px;" onclick="document.getElementById('edit_add_account').style.display='table-row';">Add account</button></td>
|
||||
<td><button type="button" class="btn" style="width: 150px;" onclick="document.getElementById('edit_custom_der').style.display='table-row';">Add custom derivation</button></td>
|
||||
<td><button type="button" class="btn" style="width: 150px;" onclick="document.getElementById('edit_add_account').style.display='table-row';">{{ _("Add account") }}</button></td>
|
||||
<td><button type="button" class="btn" style="width: 150px;" onclick="document.getElementById('edit_custom_der').style.display='table-row';">{{ _("Add custom derivation") }}</button></td>
|
||||
<td></td>
|
||||
`
|
||||
let xpubAddRow = document.createElement('tr');
|
||||
|
|
@ -275,11 +275,11 @@
|
|||
|
||||
function editXpubsTable(editBtn) {
|
||||
let visibility = '';
|
||||
if (editBtn.innerHTML == 'Edit') {
|
||||
editBtn.innerHTML = 'Done';
|
||||
if (editBtn.innerHTML == '{{ _("Edit") }}') {
|
||||
editBtn.innerHTML = '{{ _("Done") }}';
|
||||
visibility = 'visible';
|
||||
} else {
|
||||
editBtn.innerHTML = 'Edit';
|
||||
editBtn.innerHTML = '{{ _("Edit") }}';
|
||||
visibility = 'hidden';
|
||||
}
|
||||
document.getElementById('edit_select_add_method').style.display = 'table-row';
|
||||
|
|
@ -308,7 +308,7 @@
|
|||
}
|
||||
// this shouldn't happen actually
|
||||
if(devices.length == 0){
|
||||
showError("No devices found :(");
|
||||
showError('{{ _("No devices found :(") }}');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +342,7 @@
|
|||
"{specter.chain}"
|
||||
);
|
||||
if(xpub == null){
|
||||
showError('Failed to retrive device data. Please try again.');
|
||||
showError('{{ _("Failed to retrive device data. Please try again.") }}');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -520,7 +520,7 @@
|
|||
let str = `[${obj.xfp}/${path}]${obj.xpub}`;
|
||||
addXpubs(str);
|
||||
}else{
|
||||
showError("Unknown key format");
|
||||
showError('{{ _("Unknown key format") }}');
|
||||
}
|
||||
}else{
|
||||
addXpubs(result);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
</style>
|
||||
<form id="form" action="{{ url_for('devices_endpoint.new_device_mnemonic', device_type=device_type) }}" method="POST" style="width: 100%;" onsubmit="showPacman();">
|
||||
<div class="card center" style="width: auto; margin: 40px;" >
|
||||
<h1 style="font-size: 2em;">Add {% if existing_device %}Keys{% else %}Device{% endif %}</h1><br>
|
||||
<h1 style="font-size: 2em;">{{ _("Add") }} {% if existing_device %}{{ _("Keys") }}{% else %}{{ _("Device") }}{% endif %}</h1><br>
|
||||
{% if existing_device %}
|
||||
<input type="hidden" name="existing_device" value="{{ existing_device.alias }}">
|
||||
{% endif %}
|
||||
|
|
@ -44,20 +44,20 @@
|
|||
<br>
|
||||
<p class="note" style="border-radius: 5px; padding: 20px; background: rgba(0,0,0,0.1); color: #ccc;">
|
||||
<span style="display: inline-block;font-weight: bold; margin-bottom: 8px;">⚠️ A note on hot wallets:</span><br>
|
||||
This will create a <a style="color: #ddd;" href="https://en.bitcoin.it/wiki/Hot_wallet" target="_blank">hot wallet</a> on your Bitcoin Core node.<br>
|
||||
Hot wallets are considered less secure and are not recommended for use with significant amounts. Use with caution and beware the potential risks.
|
||||
{{ _('This will create a <a style="color: #ddd;" href="https://en.bitcoin.it/wiki/Hot_wallet" target="_blank">hot wallet</a> on your Bitcoin Core node.') }}<br>
|
||||
{{ _("Hot wallets are considered less secure and are not recommended for use with significant amounts. Use with caution and beware the potential risks.") }}
|
||||
</p>
|
||||
<p>Enter your mnemonic here{% if not existing_device %} or use the randomly generated one{% endif %}:</p>
|
||||
{% if not existing_device %}<p><b>Make sure to backup these words!</b></p>{% endif %}
|
||||
<p>{{ _("Enter your mnemonic here") }}{% if not existing_device %} {{ _("or use the randomly generated one") }}{% endif %}:</p>
|
||||
{% if not existing_device %}<p><b>{{ _("Make sure to backup these words!") }}</b></p>{% endif %}
|
||||
{% set wordslist = mnemonic.split(' ') %}
|
||||
{% if not existing_device %}
|
||||
<nav class="row">
|
||||
<button type="button" id="generate_mnemonic_btn" class="btn radio left checked" onclick="setMnemonicView('generate')"> Generate </button>
|
||||
<button type="button" id="import_mnemonic_btn" class="btn radio right" onclick="setMnemonicView('import')"> Import </button>
|
||||
<button type="button" id="generate_mnemonic_btn" class="btn radio left checked" onclick="setMnemonicView('generate')"> {{ _("Generate") }} </button>
|
||||
<button type="button" id="import_mnemonic_btn" class="btn radio right" onclick="setMnemonicView('import')"> {{ _("Import") }} </button>
|
||||
</nav>
|
||||
{% endif %}
|
||||
<input id="mnemonic_value" type="hidden" name="mnemonic" value="{{ mnemonic }}"/>
|
||||
<textarea id="import_mnemonic" class="hidden" id="txt" placeholder="Enter your mnemonic here"></textarea>
|
||||
<textarea id="import_mnemonic" class="hidden" id="txt" placeholder='{{ _("Enter your mnemonic here") }}'></textarea>
|
||||
<div id="generated_mnemonic" {% if existing_device %}class="hidden"{% endif %}>
|
||||
<div id="generated_mnemonic_row" class="row center break-row-mobile" style="max-height: 30px;">
|
||||
<fieldset style="border:none; display: inline;">
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
<option value="256">24 words</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
<button type="button" class="btn" style="max-width: 200px; margin-top: 9px;" onclick="generateMnemonic()">Generate New Mnemonic</button>
|
||||
<button type="button" class="btn" style="max-width: 200px; margin-top: 9px;" onclick="generateMnemonic()">{{ _("Generate New Mnemonic") }}</button>
|
||||
</div>
|
||||
<br>
|
||||
<table id="mnemonic_table">
|
||||
|
|
@ -82,25 +82,25 @@
|
|||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<span id="toggle-advanced" style="cursor: pointer;" onclick="toggleAdvanced()">Advanced ▶</span>
|
||||
<span id="toggle-advanced" style="cursor: pointer;" onclick="toggleAdvanced()">{{ _("Advanced") }} ▶</span>
|
||||
<br>
|
||||
<br>
|
||||
<div id="advanced-settings" class="hidden" style="width: 400px; margin: auto;">
|
||||
<label>Encrypt Wallet File: </label>
|
||||
<label>{{ _("Encrypt Wallet File:") }} </label>
|
||||
<input type="password" name="file_password" class="inline" placeholder="password">
|
||||
<br>
|
||||
<br>
|
||||
<label>BIP39 passphrase (optional): </label>
|
||||
<label>{{ _("BIP39 passphrase (optional):") }} </label>
|
||||
<input type="password" name="passphrase" class="inline" placeholder="passphrase">
|
||||
<br>
|
||||
<p style="margin-bottom: 10px;">Addresses range to import: </p>
|
||||
From: <input type="number" name="range_start" class="inline" placeholder="start" value=0 step=1 min=0 max=100000>
|
||||
to: <input type="number" name="range_end" class="inline" placeholder="start" value=1000 step=1 min=0 max=100000>
|
||||
<p style="margin-bottom: 10px;">{{ _("Addresses range to import:") }} </p>
|
||||
{{ _("From:") }} <input type="number" name="range_start" class="inline" placeholder="start" value=0 step=1 min=0 max=100000>
|
||||
{{ _("to:") }} <input type="number" name="range_end" class="inline" placeholder="start" value=1000 step=1 min=0 max=100000>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" id="submit-mnemonic" class="btn centered action" style="width: 140px; max-width: 140px; margin: auto;">Continue</button>
|
||||
<button type="submit" id="submit-mnemonic" class="btn centered action" style="width: 140px; max-width: 140px; margin: auto;">{{ _("Continue") }}</button>
|
||||
<br><br>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -181,10 +181,10 @@
|
|||
let advancedSettings = document.getElementById('advanced-settings');
|
||||
if (advancedSettings.style.display === 'block') {
|
||||
advancedSettings.style.display = 'none';
|
||||
advancedButton.innerHTML = 'Advanced ▶';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▶';
|
||||
} else {
|
||||
advancedSettings.style.display = 'block';
|
||||
advancedButton.innerHTML = 'Advanced ▼';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▼';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{% extends "base.jinja" %}
|
||||
{% block main %}
|
||||
<h1 style="font-size: 2em;">Add Device</h1><br>
|
||||
<h1 style="font-size: 2em;">{{ _("Add Device") }}</h1><br>
|
||||
<div id="device_setup_wizard" class="card center" style="width: auto; min-width: 90%; margin: 40px;">
|
||||
<div id="device-type-container">
|
||||
<h1 style="font-size: 1.8em;">Select Your Device Type</h1>
|
||||
<input id="device-type-searchbar" type="text" placeholder="Filter devices..." style="width: 60%; font-size: 1.4em; padding: 10px;" oninput="filterDeviceTypes(this.value)" {% if (specter.device_manager.supported_devices_for_chain(specter)|length) < 8 %}class="hidden"{% endif %}>
|
||||
<h1 style="font-size: 1.8em;">{{ _("Select Your Device Type") }}</h1>
|
||||
<input id="device-type-searchbar" type="text" placeholder='{{ _("Filter devices...") }}' style="width: 60%; font-size: 1.4em; padding: 10px;" oninput="filterDeviceTypes(this.value)" {% if (specter.device_manager.supported_devices_for_chain(specter)|length) < 8 %}class="hidden"{% endif %}>
|
||||
<div id="devices_list" class="row overflow">
|
||||
{% for cls in specter.device_manager.supported_devices_for_chain(specter) %}
|
||||
{% if not cls.hot_wallet or specter.rpc %}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a class="note center" style="text-decoration: underline; cursor: pointer;" href="{{ url_for('devices_endpoint.new_device_manual') }}">Manual configuration</a>
|
||||
<a class="note center" style="text-decoration: underline; cursor: pointer;" href="{{ url_for('devices_endpoint.new_device_manual') }}">{{ _("Manual configuration") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -4,50 +4,50 @@
|
|||
{% include "includes/qr-scanner.html" %}
|
||||
|
||||
{% if device %}
|
||||
<h1>Adding keys to {{ device.name }}</h1>
|
||||
<h1>{{ _("Adding keys to") }} {{ device.name }}</h1>
|
||||
{% else %}
|
||||
<h1>Setting up a new device</h1>
|
||||
<h1>{{ _("Setting up a new device") }}</h1>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
<form action="./" method="POST" onsubmit="{% if not device %}return checkType();{% endif %}">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
{% if not device %}
|
||||
<label>Name it:</label>
|
||||
<label>{{ _("Name it") }}:</label>
|
||||
<div class="row">
|
||||
<input type="text" id="device_name" name="device_name" value="{{ device_name }}" placeholder="Name your device">
|
||||
</div>
|
||||
<br>
|
||||
{% include "device/components/device_type.jinja" %}
|
||||
<p class="warning hidden" id="type_reminder"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>Please make sure to choose the device type before continuing</p>
|
||||
<p class="warning hidden" id="type_reminder"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("Please make sure to choose the device type before continuing") }}</p>
|
||||
<div id="toggle_passphrase_container" class="hidden">
|
||||
<p>Device Management:</p>
|
||||
<p>{{ _("Device Management") }}:</p>
|
||||
<div class="row">
|
||||
<button type="button" class="btn centered" onclick="togglePassphrase(document.getElementById('device_type').value)">Toggle device passphrase</button>
|
||||
<button type="button" class="btn centered" onclick="togglePassphrase(document.getElementById('device_type').value)">{{ _("Toggle device passphrase") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not device or not device.hot_wallet %}
|
||||
<div id="cold_device">
|
||||
<br>
|
||||
<h2>Scan, paste or load xpubs:</h2>
|
||||
<h2>{{ _("Scan, paste or load xpubs") }}:</h2>
|
||||
<div class="note">
|
||||
{% if not device %}
|
||||
You will be able add more keys later, from the device menu.
|
||||
{{ _("You will be able add more keys later from the device menu.") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<p>One line per xpub. Ideally with derivation path in the form: <span style="font-style: italic; font-weight: bold; font-size: 15px;"><br>[ fingerprint / derivation / path ] xpub.</span></p>
|
||||
<textarea style="margin: 5px;" id="txt" name="xpubs" placeholder="Enter your xpubs here">{{ xpubs }}</textarea>
|
||||
<p>{{ _("One line per xpub. Ideally with derivation path in the form") }}: <span style="font-style: italic; font-weight: bold; font-size: 15px;"><br>[ fingerprint / derivation / path ] xpub.</span></p>
|
||||
<textarea style="margin: 5px;" id="txt" name="xpubs" placeholder='{{ _("Enter your xpubs here") }}'>{{ xpubs }}</textarea>
|
||||
<div class="row">
|
||||
<qr-scanner id="xpub-scan" style="width:50%">
|
||||
<a slot="button" href="#" class="btn" style="margin: 8px;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white"> Scan
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white"> {{ _("Scan") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Using an airgapped device with QR codes<br><br></span>
|
||||
Scan QR codes with extended public keys one by one.<br><br>
|
||||
We recommend importing <b>Native Segwit</b> and <b>Segwit Multisig</b> keys.
|
||||
<span class="info__title">{{ _("Using an airgapped device with QR codes") }}<br><br></span>
|
||||
{{ _("Scan QR codes with extended public keys one by one.") }}<br><br>
|
||||
{{ _("We recommend importing <b>Native Segwit</b> and <b>Segwit Multisig</b> keys.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -56,17 +56,17 @@
|
|||
<input type="file" id="file" class="inputfile" multiple/>
|
||||
<label for="file" class="btn" style="margin: 8px;">
|
||||
<img src="{{ url_for('static', filename='img/file.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white">
|
||||
Choose files
|
||||
{{ _("Choose files") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Using an airgapped ColdCard with MicroSD<br><br></span>
|
||||
Export extended public keys for single-key wallets to the SD card from<br>
|
||||
<span class="info__title">{{ _("Using an airgapped ColdCard with MicroSD") }}<br><br></span>
|
||||
{{ _("Export extended public keys for single-key wallets to the SD card from") }}<br>
|
||||
<b>Advanced → MicroSD → Export Wallet → Generic JSON</b><br><br>
|
||||
To export multisignature pubkeys go to <br>
|
||||
{{ _("To export multisignature pubkeys go to") }} <br>
|
||||
<b>Settings → Multisig Wallets → Export XPUB</b>
|
||||
We recommend using Native Segwit.
|
||||
{{ _("We recommend using Native Segwit.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -74,45 +74,45 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<button type="button" class="btn" style="margin: 8px;" id="askhwi">
|
||||
<img src="{{ url_for('static', filename='img/usb.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white"> Get over USB
|
||||
<img src="{{ url_for('static', filename='img/usb.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white"> {{ _("Get via USB") }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="note center" style="text-decoration: underline; cursor: pointer;" onclick="showPageOverlay('usb_import_account_number')">Specify account number (advanced)</p>
|
||||
<p class="note center" style="text-decoration: underline; cursor: pointer;" onclick="showPageOverlay('usb_import_account_number')">{{ _("Specify account number (advanced)") }}</p>
|
||||
<div class="hidden" id="usb_import_account_number">
|
||||
<h1>Select the account you wish to import</h1>
|
||||
<p class="center" style="max-width: 500px; margin: 5px auto;">You can set here which account number you wish to import.<br>Account derivation uses the number choosen here to derive an xpub as specified in <a style="color: #fff" href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account" target="_blank">BIP44</a>.</p>
|
||||
<h1>{{ _("Select the account you wish to import") }}</h1>
|
||||
<p class="center" style="max-width: 500px; margin: 5px auto;">{{ _("You can set which account number you wish to import.<br>Account derivation uses the number choosen here to derive an xpub as specified in") }} <a style="color: #fff" href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account" target="_blank">BIP44</a>.</p>
|
||||
Account # <input type="number" step="1" min="0" value="0" id="usb_account_number" style="max-width: 100px; min-width: 100px;"><br><br>
|
||||
<p class="note center">This is an advanced feature.<br>If you're not familiar with account derivations please leave this as 0.</p><br>
|
||||
<button class="btn centered" type="button" onclick="hidePageOverlay()">Close</button>
|
||||
<p class="note center">{{ _("This is an advanced feature.<br>If you're not familiar with account derivations please leave this as 0.") }}</p><br>
|
||||
<button class="btn centered" type="button" onclick="hidePageOverlay()">{{ _("Close") }}</button>
|
||||
</div>
|
||||
<div class="note">
|
||||
Examples:<br>
|
||||
{{ _("Examples") }}:<br>
|
||||
<pre style="font-size: 1em">
|
||||
[f79ec910/84'/1'/0']tpubDA5h1hw24fzoBi9...pR1DiM4EBu
|
||||
[F79EC910/48h/1h/0h/2h]Vpub5DRk6RvRAjZf9rmZV1Q8...TZSDQL67e5i
|
||||
upub5En4f7k8gaG2KDHvBeEYox...rFpJRHpiZ4DE
|
||||
</pre>
|
||||
</div><br>
|
||||
<button type="submit" class="btn centered" name="action" value="newcolddevice" onsubmit="showPacman()">Continue</button>
|
||||
<button type="submit" class="btn centered" name="action" value="newcolddevice" onsubmit="showPacman()">{{ _("Continue") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not device or device.hot_wallet %}
|
||||
<div id="hot_device" class="hidden">
|
||||
<br>
|
||||
<p class="note" style="border-radius: 5px; padding: 20px; background: rgba(0,0,0,0.1); color: #ccc;">
|
||||
<span style="display: inline-block;font-weight: bold; margin-bottom: 8px;">⚠️ A note on hot wallets:</span><br>
|
||||
This will create a <a style="color: #ddd;" href="https://en.bitcoin.it/wiki/Hot_wallet" target="_blank">hot wallet</a> on your Bitcoin Core node.<br>
|
||||
Hot wallets are considered less secure and are not recommended for use with significant amounts. Use with caution and beware the potential risks.
|
||||
<span style="display: inline-block;font-weight: bold; margin-bottom: 8px;">⚠️ {{ _("A note on hot wallets") }}:</span><br>
|
||||
{{ _('This will create a <a style="color: #ddd;" href="https://en.bitcoin.it/wiki/Hot_wallet" target="_blank">hot wallet</a> on your Bitcoin Core node.') }}<br>
|
||||
{{ _("Hot wallets are considered less secure and are not recommended for use with significant amounts. Use with caution and beware the potential risks.") }}
|
||||
</p>
|
||||
<p>Enter you mnemonic here or use the randomly generated one:</p>
|
||||
<p><b>Make sure to backup these words!</b></p>
|
||||
<p>{{ _("Enter you mnemonic here or use the randomly generated one") }}:</p>
|
||||
<p><b>{{ _("Make sure to backup these words!") }}</b></p>
|
||||
{% set wordslist = mnemonic.split(' ') %}
|
||||
<nav class="row">
|
||||
<button type="button" id="generate_mnemonic_btn" class="btn radio left checked" onclick="setMnemonicView('generate')"> Generate </button>
|
||||
<button type="button" id="import_mnemonic_btn" class="btn radio right" onclick="setMnemonicView('import')"> Import </button>
|
||||
<button type="button" id="generate_mnemonic_btn" class="btn radio left checked" onclick="setMnemonicView('generate')"> {{ _("Generate") }} </button>
|
||||
<button type="button" id="import_mnemonic_btn" class="btn radio right" onclick="setMnemonicView('import')"> {{ _("Import") }} </button>
|
||||
</nav>
|
||||
<input id="mnemonic_value" type="hidden" name="mnemonic" value="{{ mnemonic }}"/>
|
||||
<textarea id="import_mnemonic" class="hidden" id="txt" placeholder="Enter your mnemonic here"></textarea>
|
||||
<textarea id="import_mnemonic" class="hidden" id="txt" placeholder='{{ _("Enter your mnemonic here") }}'></textarea>
|
||||
<div id="generated_mnemonic">
|
||||
<div id="generated_mnemonic_row" class="row center break-row-mobile" style="max-height: 30px;">
|
||||
<fieldset style="border:none; display: inline;">
|
||||
|
|
@ -121,7 +121,7 @@ upub5En4f7k8gaG2KDHvBeEYox...rFpJRHpiZ4DE
|
|||
<option value="256">24 words</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
<button type="submit" class="btn" name="action" value="generatemnemonic" style="margin: auto;max-width: 200px;">Generate New Mnemonic</button>
|
||||
<button type="submit" class="btn" name="action" value="generatemnemonic" style="margin: auto;max-width: 200px;">{{ _("Generate New Mnemonic") }}</button>
|
||||
</div>
|
||||
<br>
|
||||
<table>
|
||||
|
|
@ -136,36 +136,34 @@ upub5En4f7k8gaG2KDHvBeEYox...rFpJRHpiZ4DE
|
|||
</table>
|
||||
</div>
|
||||
<br>
|
||||
<label>Encrypt Wallet File: </label>
|
||||
<label>{{ _("Encrypt Wallet File") }}: </label>
|
||||
<input type="password" name="file_password" class="inline" placeholder="password">
|
||||
<br><br>
|
||||
<span id="toggle_advanced" style="cursor: pointer;">Advanced ▶</span>
|
||||
<span id="toggle_advanced" style="cursor: pointer;">{{ _("Advanced") }} ▶</span>
|
||||
<br><br>
|
||||
<div id="advanced_settings" class="hidden">
|
||||
<label>BIP39 passphrase (optional): </label>
|
||||
<label>{{ _("BIP39 passphrase (optional)") }}: </label>
|
||||
<input type="password" name="passphrase" class="inline" placeholder="passphrase">
|
||||
<br>
|
||||
<p>Derivation paths:<br>
|
||||
<p>{{ _("Derivation paths") }}:<br>
|
||||
<span class="note">
|
||||
Enter each derivation path as a separate line.
|
||||
Start each path with an <code>m</code>.
|
||||
No trailing slash.</span>
|
||||
{{ _("Enter each derivation path as a separate line. Start each path with an <code>m</code>. No trailing slash.") }}</span>
|
||||
</p>
|
||||
<textarea name="derivation_paths" placeholder="Specify derivation paths to import for the wallet">
|
||||
<textarea name="derivation_paths" placeholder='{{ _("Specify derivation paths to import for the wallet") }}'>
|
||||
m/84h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h
|
||||
m/49h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h
|
||||
m/48h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h/1h
|
||||
m/48h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h/2h</textarea>
|
||||
<br>
|
||||
<p style="margin-bottom: 10px;">Addresses range to import: </p>
|
||||
From: <input type="number" name="range_start" class="inline" placeholder="start" value=0 step=1 min=0 max=100000>
|
||||
<p style="margin-bottom: 10px;">{{ _("Address range to import") }}: </p>
|
||||
{{ _("From") }}: <input type="number" name="range_start" class="inline" placeholder="start" value=0 step=1 min=0 max=100000>
|
||||
<br class="mobile-only">
|
||||
<br class="mobile-only">
|
||||
to: <input type="number" name="range_end" class="inline" placeholder="start" value=1000 step=1 min=0 max=100000>
|
||||
{{ _("to") }}: <input type="number" name="range_end" class="inline" placeholder="start" value=1000 step=1 min=0 max=100000>
|
||||
<br>
|
||||
</div>
|
||||
<br><br>
|
||||
<button type="submit" class="btn centered" name="action" value="newhotdevice">Continue</button>
|
||||
<button type="submit" class="btn centered" name="action" value="newhotdevice">{{ _("Continue") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
|
@ -179,13 +177,13 @@ m/48h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h/2h</textarea>
|
|||
let advancedSettings = document.getElementById('advanced_settings');
|
||||
if (advancedSettings.style.display === 'block') {
|
||||
advancedSettings.style.display = 'none';
|
||||
advancedButton.innerHTML = 'Advanced ▶';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▶';
|
||||
if (isCoinSelectionActive()) {
|
||||
toggleExpand();
|
||||
}
|
||||
} else {
|
||||
advancedSettings.style.display = 'block';
|
||||
advancedButton.innerHTML = 'Advanced ▼';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▼';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +285,7 @@ m/48h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h/2h</textarea>
|
|||
}
|
||||
// this shouldn't happen actually
|
||||
if(devices.length == 0){
|
||||
showError("No devices found :(");
|
||||
showError('{{ _("No devices found") }} :(');
|
||||
return;
|
||||
}
|
||||
console.log(devices)
|
||||
|
|
@ -433,7 +431,7 @@ m/48h/{{ 0 if specter.info.chain == "main" else 1 }}h/0h/2h</textarea>
|
|||
addKeys(str);
|
||||
setDeviceType('cobo');
|
||||
}else{
|
||||
showError("Unknown key format");
|
||||
showError('{{ _("Unknown key format") }}');
|
||||
}
|
||||
}else{
|
||||
addKeys(result);
|
||||
|
|
|
|||
|
|
@ -8,26 +8,26 @@
|
|||
<div class="card" style="width: 600px;">
|
||||
<form action="." method="POST" role="form">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<h1>HWI Bridge Settings</h1>
|
||||
<h1>{{ _("HWI Bridge Settings") }}</h1>
|
||||
{% if tor_service_id %}
|
||||
<img style="width: 28px; padding:3px;float: left;margin-right: 5px;" src="{{ url_for('static', filename='img/tor.svg') }}"/>
|
||||
<span style="float: left;">
|
||||
HWIBridge is also accessible over Tor:<br>
|
||||
<span title="Copy Tor address" class="explorer-link" onclick="copyText('{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
|
||||
{{ _("HWI Bridge is also accessible over Tor") }}:<br>
|
||||
<span title="Copy Tor address" class="explorer-link" onclick="copyText('{{ tor_service_id }}.onion', '{{ _("Copied Tor hidden service address") }}: {{ tor_service_id }}.onion')">
|
||||
{{ tor_service_id }}.onion
|
||||
</span>
|
||||
</span><br><br><br>
|
||||
{% endif %}
|
||||
<label>Whitelisted domains</label>
|
||||
<label>{{ _("Whitelisted domains") }}</label>
|
||||
<textarea class="form-control" placeholder="e.i. http://127.0.0.1:25441" name="whitelisted_domains">{{ whitelisted_domains }}</textarea>
|
||||
<div class="note">
|
||||
To whitelist multiple domains, just list them one after another, each in a new line, for example:
|
||||
{{ _("To whitelist multiple domains, just list them one after another, each in a new line, for example") }}:
|
||||
<br>http://127.0.0.1:25440<br>http://127.0.0.1:25441<br>http://127.0.0.1:25442
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn" name="action" value="update">Update</button>
|
||||
<button type="submit" class="btn" name="action" value="update">{{ _("Update") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
</style>
|
||||
<div class="address-data">
|
||||
<h1>Address details</h1><br>
|
||||
<h1>{{ _("Address details") }}</h1><br>
|
||||
<div class="address-data-info"></div>
|
||||
<div class="note"></div>
|
||||
</div>
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
this.amountPrice = this.getAttribute('data-amount-price');
|
||||
this.address = this.getAttribute('data-address');
|
||||
this.wallet = this.getAttribute('data-address-wallet');
|
||||
this.note.innerText = `Loading address: ${this.address} details...`;
|
||||
this.note.innerText = `{{ _("Loading address") }}: ${this.address} {{ _("details") }}...`;
|
||||
fetchAddressData(this);
|
||||
|
||||
// Attach the created element to the shadow dom
|
||||
|
|
@ -79,14 +79,14 @@
|
|||
<qr-code style="margin: auto;" value="bitcoin:${address}" width="256"></qr-code><br>
|
||||
<table class="address-data-table">
|
||||
<tbody>
|
||||
<tr><td>Address:</td><td style="word-break: break-all;"><explorer-link data-type="address" data-value="${address}"></explorer-link></td></tr>
|
||||
<tr><td>Label:</td><td><address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br></td></tr>
|
||||
<tr><td>From wallet:</td><td><a href=${walletLink}>${walletName}<a></td></tr>
|
||||
<tr><td>Address index:</td><td>${addressIndex}</td></tr>
|
||||
<tr><td>Is change address:</td><td>${isChange ? 'Yes' : 'No'}</td></tr>
|
||||
<tr><td>Used:</td><td>${self.used ? 'Yes' : 'No'}</td></tr>
|
||||
<tr><td>UTXO count:</td><td>${self.utxo}</td></tr>
|
||||
<tr><td>Amount:</td><td>${self.amount} <span class="amount-price note ${self.amountPrice ? '' : 'hidden'}">${self.amountPrice}</span></td></tr>
|
||||
<tr><td>{{ _("Address") }}:</td><td style="word-break: break-all;"><explorer-link data-type="address" data-value="${address}"></explorer-link></td></tr>
|
||||
<tr><td>{{ _("Label") }}:</td><td><address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br></td></tr>
|
||||
<tr><td>{{ _("From wallet") }}:</td><td><a href=${walletLink}>${walletName}<a></td></tr>
|
||||
<tr><td>{{ _("Address index") }}:</td><td>${addressIndex}</td></tr>
|
||||
<tr><td>{{ _("Is change address") }}:</td><td>${isChange ? '{{ _("Yes") }}' : '{{ _("No") }}'}</td></tr>
|
||||
<tr><td>{{ _("Used") }}:</td><td>${self.used ? 'Yes' : 'No'}</td></tr>
|
||||
<tr><td>{{ _("UTXO count") }}:</td><td>${self.utxo}</td></tr>
|
||||
<tr><td>{{ _("Amount") }}:</td><td>${self.amount} <span class="amount-price note ${self.amountPrice ? '' : 'hidden'}">${self.amountPrice}</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
|
|
@ -94,44 +94,44 @@
|
|||
if (self.isVerifyHwi) {
|
||||
addressInfoHTML += `
|
||||
<br>
|
||||
<button type="button" onclick='hidePageOverlay();displayAddressOnDevice("${address}", unescape("${escape(JSON.stringify(descriptor))}"))' class="btn" style="min-width:200px; max-width:300px; margin: auto;">Verify address on device</button>`;
|
||||
<button type="button" onclick='hidePageOverlay();displayAddressOnDevice("${address}", unescape("${escape(JSON.stringify(descriptor))}"))' class="btn" style="min-width:200px; max-width:300px; margin: auto;">{{ _("Verify address on device") }}</button>`;
|
||||
}
|
||||
|
||||
if (self.isVerifyQR) {
|
||||
addressInfoHTML += `
|
||||
<p>Or scan this QR code:</p>
|
||||
<p>{{ _("Or scan this QR code") }}:</p>
|
||||
<qr-code value="bitcoin:${address}?index=${addressIndex}" width="256" scalable></qr-code><br>`;
|
||||
}
|
||||
|
||||
addressInfoHTML += `
|
||||
<br>
|
||||
<h2>Address descriptor</h2>
|
||||
<h2>{{ _("Address descriptor") }}</h2>
|
||||
<br>
|
||||
<div>
|
||||
<span class="switch-text">Show raw public keys </span>
|
||||
<span class="switch-text">{{ _("Show raw public keys") }} </span>
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="descriptor-switch" checked>
|
||||
<span class="slider"></span>
|
||||
</label><br><br>
|
||||
</div>
|
||||
<pre class="descriptor"><code class="explorer-link" onclick='copyText(unescape("${escape(descriptor.descriptor)}"), "Copied address descriptor")'>${descriptor.descriptor}</code></pre>
|
||||
<pre class="descriptor"><code class="explorer-link" onclick='copyText(unescape("${escape(descriptor.descriptor)}"), "{{ _("Copied address descriptor") }}")'>${descriptor.descriptor}</code></pre>
|
||||
`;
|
||||
self.info.innerHTML = addressInfoHTML;
|
||||
self.note.innerText = "";
|
||||
|
||||
self.info.querySelector('.descriptor-switch').onchange = (e) => {
|
||||
if (e.target.checked) {
|
||||
self.info.querySelector(".descriptor").innerHTML = `<code class="explorer-link" onclick='copyText(unescape("${escape(descriptor.descriptor)}"), "Copied address descriptor")'>${descriptor.descriptor}</code>`;
|
||||
self.info.querySelector(".descriptor").innerHTML = `<code class="explorer-link" onclick='copyText(unescape("${escape(descriptor.descriptor)}"), "{{ _("Copied address descriptor") }}")'>${descriptor.descriptor}</code>`;
|
||||
} else {
|
||||
self.info.querySelector(".descriptor").innerHTML = `<code class="explorer-link" onclick='copyText(unescape("${escape(descriptor.xpubs_descriptor)}"), "Copied address descriptor")'>${descriptor.xpubs_descriptor}</code>`;
|
||||
self.info.querySelector(".descriptor").innerHTML = `<code class="explorer-link" onclick='copyText(unescape("${escape(descriptor.xpubs_descriptor)}"), "{{ _("Copied address descripto") }}")'>${descriptor.xpubs_descriptor}</code>`;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
self.note.innerText = "Failed to load address details...";
|
||||
self.note.innerText = '{{ _("Failed to load address details...") }}';
|
||||
} catch(e) {
|
||||
self.note.innerText = "Failed to load address details...";
|
||||
self.note.innerText = '{{ _("Failed to load address details...") }}';
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@
|
|||
</style>
|
||||
<form class="address-label-form">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<a target="_blank" class="explorer-link"><span class="label" autocomplete="off" spellcheck="false">Fetching address label...</span></a>
|
||||
<a target="_blank" class="explorer-link"><span class="label" autocomplete="off" spellcheck="false">{{ _("Fetching address label...") }}</span></a>
|
||||
<button type="button" class="btn edit" title="Edit label"><img src="{{ url_for('static', filename='img/edit.svg') }}" style="width: 22px; margin-bottom:-5px;" class="svg-white"/></button>
|
||||
<button type="button" class="btn update hidden">Update</button>
|
||||
<button type="button" class="btn cancel hidden">Cancel</button>
|
||||
<button type="button" class="btn update hidden">{{ _("Update") }}</button>
|
||||
<button type="button" class="btn cancel hidden">{{ _("Cancel") }}</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
this.label.innerText = this.address
|
||||
}
|
||||
} else {
|
||||
showError('Failed to update address label. Please refresh the page and try again.')
|
||||
showError('{{ _("Failed to update address label. Please refresh the page and try again.") }}')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
self.explorerLink.onclick = () => {return true;}
|
||||
} else {
|
||||
self.explorerLink.onclick = () => {
|
||||
copyText(self.address, `Copied address: ${ self.address }`);
|
||||
copyText(self.address, `{{ _("Copied address") }}: ${ self.address }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,10 +231,10 @@
|
|||
}
|
||||
return;
|
||||
}
|
||||
showError('Failed to fetch data. Please refresh the page and retry.');
|
||||
showError('{{ _("Failed to fetch data. Please refresh the page and retry.") }}');
|
||||
} catch(e) {
|
||||
console.log("Caught error fetching address label: ", e);
|
||||
showError('Failed to fetch data. Please refresh the page and retry.');
|
||||
console.log('{{ _("Caught error fetching address label") }}: ', e);
|
||||
showError('{{ _("Failed to fetch data. Please refresh the page and retry.") }}');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<td class="used optional"></td>
|
||||
<td class="utxo optional"></td>
|
||||
<td class="optional"><span class="amount"></span> <span class="amount-price note hidden">()</span></td>
|
||||
<td class="verify optional"><button type="button" class="btn" style="width:150px; max-width:150px;margin: 0px;">Verify on device</button></td>
|
||||
<td class="verify optional"><button type="button" class="btn" style="width:150px; max-width:150px;margin: 0px;">{{ _("Verify on device") }}</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -111,9 +111,9 @@
|
|||
displayAddressOnDevice(this.addressData.address, JSON.stringify(descriptor));
|
||||
return;
|
||||
}
|
||||
showError("Failed to load address data to display...");
|
||||
showError('{{ _("Failed to load address data to display...") }}');
|
||||
} catch(e) {
|
||||
showError("Failed to load address data to display...");
|
||||
showError('{{ _("Failed to load address data to display...") }}');
|
||||
showError(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,16 +95,16 @@
|
|||
|
||||
<nav class="row">
|
||||
<button type="button" class="receive-addresses-view-btn btn radio left checked">
|
||||
Receive Addresses
|
||||
{{ _("Receive Addresses") }}
|
||||
</button>
|
||||
<button type="button" class="change-addresses-view-btn btn radio right">
|
||||
Change Addresses
|
||||
{{ _("Change Addresses") }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="pagination-container">
|
||||
<a class="pagination-back pagination-arrow pagination-disabled">‹</a>
|
||||
Page <input class="pagination-idx" type="number" step="1" min="1" value="1"></input><span
|
||||
{{ _("Page") }} <input class="pagination-idx" type="number" step="1" min="1" value="1"></input><span
|
||||
class="pagination-counter"></span>
|
||||
<a class="pagination-next pagination-arrow pagination-disabled">›</a>
|
||||
<br>
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
{% include "includes/page-limit-select.html" %}
|
||||
</div>
|
||||
<div class="export-container hidden">
|
||||
<h1>Export addresses to CSV</h1>
|
||||
<h1>{{ _("Export addresses to CSV") }}</h1>
|
||||
|
||||
|
||||
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
|
||||
|
||||
<a class="export btn" download>Export</a>
|
||||
<a class="export btn" download>{{ _("Export") }}</a>
|
||||
</div>
|
||||
|
||||
<table class="addresses-table">
|
||||
|
|
@ -137,28 +137,28 @@
|
|||
<th value="index" class="index-header">
|
||||
<div class="index-arrow down-arrow"></div>
|
||||
</th>
|
||||
<th value="address" class="address-header">Address<div class="address-arrow"></div>
|
||||
<th value="address" class="address-header">{{ _("Address") }}<div class="address-arrow"></div>
|
||||
</th>
|
||||
<th value="label" class="label-header optional">Label<div class="label-arrow">
|
||||
<th value="label" class="label-header optional">{{ _("Label") }}<div class="label-arrow">
|
||||
</th>
|
||||
<th value="used" class="used-header optional">Used<div class="used-arrow">
|
||||
<th value="used" class="used-header optional">{{ _("Used") }}<div class="used-arrow">
|
||||
</th>
|
||||
<th value="utxo" class="utxo-header optional">UTXO<div class="utxo-arrow">
|
||||
</th>
|
||||
<th value="amount" class="amount-header optional">Amount<div class="amount-arrow">
|
||||
<th value="amount" class="amount-header optional">{{ _("Amount") }}<div class="amount-arrow">
|
||||
</th>
|
||||
{% if supports_hwi != [] and (supports_hwi_multisig_display_address != [] or not wallet.is_multisig) %}
|
||||
<th class="optional"></th>
|
||||
{% endif %}
|
||||
<th style="width: 50px; padding: 10px;">
|
||||
<button type="button" class="export-btn btn" style="width: 50px;">Export</button>
|
||||
<button type="button" class="export-btn btn" style="width: 50px;">{{ _("Export") }}</button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr class="empty">
|
||||
<td></td>
|
||||
<td>Fetching addresses ...</td>
|
||||
<td>{{ _("Fetching addresses...") }}</td>
|
||||
<td class="optional"></td>
|
||||
<td class="optional"></td>
|
||||
<td class="optional"></td>
|
||||
|
|
@ -402,11 +402,11 @@
|
|||
async function fetchAddressesItems(self) {
|
||||
self.callId++;
|
||||
// Clean up from existing addresses list
|
||||
self.emptyRow.children[1].innerText = "Fetching addresses...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("Fetching addresses...") }}';
|
||||
self.emptyRow.classList.remove('hidden');
|
||||
self.tbody.querySelectorAll('address-row').forEach((row) => { row.remove() });
|
||||
if (!self.listType) {
|
||||
self.emptyRow.children[1].innerText = "Failed to fetch addresses...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("Failed to fetch addresses...") }}';
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -416,9 +416,9 @@
|
|||
self.export.href += `?onlyCurrentType=${self.onlyCurrentTypeSwitch.checked}&addressType=${self.listType}&sortby=${self.sortby}&sortdir=${self.sortdir}`;
|
||||
|
||||
if (self.listType == "receive") {
|
||||
self.switchText.innerText = "Only receive addresses: "
|
||||
self.switchText.innerText = '{{ _("Only receive addresses") }}: '
|
||||
} else if (self.listType == "change") {
|
||||
self.switchText.innerText = "Only change addresses: "
|
||||
self.switchText.innerText = '{{ _("Only change addresses") }}: '
|
||||
} else {
|
||||
self.switchText.innerText = "";
|
||||
}
|
||||
|
|
@ -478,7 +478,7 @@
|
|||
// Show empty row if no addresses found
|
||||
if (self.pageCount == 0 || addressesList.length == 0) {
|
||||
self.emptyRow.classList.remove('hidden');
|
||||
self.emptyRow.children[1].innerText = "No addresses found...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("No addresses found...") }}';
|
||||
self.pageCount = 1;
|
||||
} else {
|
||||
self.emptyRow.classList.add('hidden');
|
||||
|
|
@ -517,10 +517,10 @@
|
|||
}
|
||||
return;
|
||||
}
|
||||
showError('Failed to fetch addresses list.')
|
||||
showError('{{ _("Failed to fetch addresses list") }}')
|
||||
} catch (e) {
|
||||
console.log("Caught error: ", e);
|
||||
showError(`Failed to fetch addresses list: ${e}`);
|
||||
showError(`{{ _("Failed to fetch addresses list") }}: ${e}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -547,7 +547,7 @@
|
|||
</address-data>`;
|
||||
showPageOverlay('address-popup');
|
||||
} else {
|
||||
copyText(address, `Copied address: ${ address }`);
|
||||
copyText(address, `{{ _("Copied address") }}: ${ address }`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div id="hwi_passphrase" class="hidden">
|
||||
<h2>Enter Passphrase</h2>
|
||||
<h2>{{ _("Enter Passphrase") }}</h2>
|
||||
<div class="flex-center flex-column">
|
||||
<br/>
|
||||
<div>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
let inp = document.getElementById("hwi_passphrase_passphrase");
|
||||
let submit = document.createElement('button');
|
||||
submit.classList.add('btn');
|
||||
submit.innerHTML = "Submit";
|
||||
submit.innerHTML = '{{ _("Submit") }}';
|
||||
let result = null;
|
||||
submit.addEventListener('click', e=>{
|
||||
result = inp.value;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div id="hwi_pin" class="hidden">
|
||||
<h2>Enter PIN</h2>
|
||||
<h2>{{ _("Enter PIN") }}</h2>
|
||||
<div class="row">
|
||||
<div id="hwi_pin_stars" style="margin-top: 20px; font-size: 20px; height: 25px; width: 100%; padding-left: 30px;"></div>
|
||||
<img id="hwi_pin_delete" src="{{ url_for('static', filename='img/clear-character.svg') }}" width="24px" style="margin-top: 20px; height: 25px; margin-left: 10px; cursor: pointer;" class="svg-white">
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
async function enterPin(device, promptPin=true){
|
||||
let result = null;
|
||||
if (promptPin) {
|
||||
showHWIProgress("Processing...", `Keep your ${capitalize(device.type)} connected.`);
|
||||
showHWIProgress('{{ _("Processing...") }}', `{{ _("Keep your") }} ${capitalize(device.type)} {{ _("connected") }}.`);
|
||||
try {
|
||||
result = await hwi.promptPin(device);
|
||||
} catch (error) {
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
el.appendChild(document.createElement('br'));
|
||||
let submit = document.createElement('button');
|
||||
submit.classList.add('btn');
|
||||
submit.innerHTML = "Submit";
|
||||
submit.innerHTML = '{{ _("Submit") }}';
|
||||
submit.addEventListener('click', e=>{
|
||||
pinReady = true;
|
||||
});
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
}
|
||||
hidePageOverlay();
|
||||
// now we have PIN - send it
|
||||
showHWIProgress("Processing...", `Keep your ${capitalize(device.type)} connected.`);
|
||||
showHWIProgress('{{ _("Processing...") }}', `{{ _("Keep your") }} ${capitalize(device.type)} {{ _("connected") }}.`);
|
||||
result = null;
|
||||
try {
|
||||
result = await hwi.sendPin(device, pin);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div id="hwi_selector" class="hidden">
|
||||
<h1>Select USB Wallet</h1>
|
||||
<h1>{{ _("Select USB Wallet") }}</h1>
|
||||
<div class="flex-center flex-column" id="hwi_selector_container">
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
showHWIProgress("Processing...", `Keep your ${capitalize(selectedDevice.type)} connected.`);
|
||||
showHWIProgress('{{ _("Processing...") }}', `{{ _("Keep your") }} ${capitalize(selectedDevice.type)} {{ _("connected") }}.`);
|
||||
// Get the device again as it is now unlocked and the fingerprint is available
|
||||
let rescanDevices = await enumerate(selectedDevice.type, selectedDevice.path, passphrase);
|
||||
if (rescanDevices.length == 1) {
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
return null;
|
||||
}
|
||||
if(!('success' in result) || !result.success){
|
||||
throw "Failed to unlock device! Invalid PIN code?";
|
||||
throw '{{ _("Failed to unlock device! Invalid PIN code?") }}';
|
||||
}
|
||||
device.needs_pin_sent = false;
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
return;
|
||||
}
|
||||
let device = await selectDevice(devices, '');
|
||||
showHWIProgress("Processing...", `Keep your ${capitalize(device.type)} connected.`);
|
||||
showHWIProgress('{{ _("Processing...") }}', `{{ _("Keep your") }} ${capitalize(device.type)} {{ _("connected") }}.`);
|
||||
let result = null;
|
||||
try {
|
||||
result = await hwi.togglePassphrase(device);
|
||||
|
|
@ -122,7 +122,7 @@
|
|||
throw "Failed to unlock device! Invalid PIN code?";
|
||||
}
|
||||
}
|
||||
showNotification(device.type + " passphrase toggled successfuly", 5000)
|
||||
showNotification(device.type + ' {{ _("passphrase toggled successfully") }}', 5000)
|
||||
}
|
||||
|
||||
async function signMessage(deviceType, message, derivationPath, fingerprint=null){
|
||||
|
|
@ -137,10 +137,10 @@
|
|||
}
|
||||
|
||||
if (fingerprint && device.fingerprint != fingerprint) {
|
||||
handleHWIError("Selected device does not have matching signing key.");
|
||||
handleHWIError('{{ _("Selected device does not have matching signing key.") }}');
|
||||
return;
|
||||
}
|
||||
showHWIProgress("Signing message...", `Confirm on your ${capitalize(device.type)}.`);
|
||||
showHWIProgress('{{ _("Signing message...") }}', `{{ _("Confirm on your") }} ${capitalize(device.type)}.`);
|
||||
let signature = null;
|
||||
try {
|
||||
signature = await hwi.signMessage(device, message, derivationPath);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{% if wallet is defined %}
|
||||
|
||||
<div id="hwi_display_address" class="flex-center flex-column hidden" style="overflow-wrap: break-word;">
|
||||
<h2>Confirm Address</h2><br>
|
||||
<h2>{{ _("Confirm Address") }}</h2><br>
|
||||
<div>
|
||||
Please confirm address matches on your <span id="hwi_device_name">device</span><br><br>
|
||||
Expected address:<br><span id="expected_address"></span>
|
||||
{{ _("Please confirm address matches on your <span id=\"hwi_device_name\">device</span>") }}<br><br>
|
||||
{{ _("Expected address") }}:<br><span id="expected_address"></span>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<img src="{{ url_for('static', filename='img/loader_boxes.svg') }}"/>
|
||||
|
|
@ -35,10 +35,10 @@
|
|||
}
|
||||
|
||||
if (fingerprint && device.fingerprint != fingerprint) {
|
||||
handleHWIError("Selected device does not have matching signing key.");
|
||||
handleHWIError('{{ _("Selected device does not have matching signing key.") }}');
|
||||
return;
|
||||
}
|
||||
showHWIProgress("Signing transaction...", `Confirm on your ${capitalize(device.type)}.`);
|
||||
showHWIProgress('{{ _("Signing transaction...") }}', `{{ _("Confirm on your") }} ${capitalize(device.type)}.`);
|
||||
let signedTx = null;
|
||||
try {
|
||||
signedTx = await hwi.signTx(device, psbt);
|
||||
|
|
@ -87,12 +87,12 @@
|
|||
(("{{ specter.chain }}" != "regtest" || !result.startsWith("tb")) ||
|
||||
address.slice(0, -6) != "bcrt" + result.slice(2, -6))
|
||||
) {
|
||||
showError("Addresses did not match! Be careful! Please contact support and beware of using the displayed address!");
|
||||
showError('{{ _("Addresses did not match! Be careful! Please contact support and beware of using the displayed address!") }}');
|
||||
} else {
|
||||
if (device.type == 'coldcard') {
|
||||
showNotification("Address displayed successfully! Please check the device screen.", 7000);
|
||||
showNotification('{{ _("Address displayed successfully! Please check the device screen.") }}', 7000);
|
||||
} else {
|
||||
showNotification("Address was verified successfully!", 5000);
|
||||
showNotification('{{ _("Address was verified successfully!") }}', 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,26 +11,26 @@
|
|||
|
||||
|
||||
<div id="hwi_progress" class="hidden" style="width: 270px;">
|
||||
<h1 id="hwi_progress_title">Processing...</h1>
|
||||
<div class="flex-center" id="hwi_progress_text">Keep your wallet connected.</div><br>
|
||||
<h1 id="hwi_progress_title">{{ _("Processing...") }}</h1>
|
||||
<div class="flex-center" id="hwi_progress_text">{{ _("Keep your wallet connected.") }}</div><br>
|
||||
<p id="bitbox02-pairing-code" style="margin: auto;"></p>
|
||||
<img src="{{ url_for('static', filename='img/loader_boxes.svg') }}">
|
||||
<div class="flex-center hidden" id="hwi_progress_note" style="cursor: pointer;">
|
||||
If your device does not show up, ensure the Specter USB settings are correctly configured.<br><br>
|
||||
{{ _("If your device does not show up, ensure the Specter USB settings are correctly configured.") }}<br><br>
|
||||
<a href="{{ url_for('settings_endpoint.hwi') }}" style="color: #fff; text-decoration: underline; cursor: pointer;">
|
||||
View USB settings
|
||||
{{ _("View USB settings") }}
|
||||
</a>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="flex-column hidden" id="coldcard_warning">
|
||||
<p> Do you really want to use your Coldcard over USB (no airgap)? </p>
|
||||
<p> {{ _("Do you really want to use your Coldcard over USB (no airgap)?") }} </p>
|
||||
<div class="flex-center">
|
||||
<button onclick="coldCardNotice()" class="btn flex-center"> Yes, I am living on the edge! </button>
|
||||
<button onclick="coldCardNotice()" class="btn flex-center"> {{ _("Yes, I am living on the edge!") }} </button>
|
||||
</div> <br>
|
||||
<div class="flex-center">
|
||||
<button onclick = "goToCoinkite()" class="btn flex-center"> No, I'll use the address explorer on the Coldcard. Get me to the coinkite site for more info. </button>
|
||||
<button onclick = "goToCoinkite()" class="btn flex-center"> {{ _("No, I'll use the address explorer on the Coldcard. Get me to the coinkite site for more info.") }} </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
|
||||
function showHWIProgress(title="Processing...", text="Keep your wallet connected."){
|
||||
function showHWIProgress(title='{{ _("Processing...") }}', text='{{ _("Keep your wallet connected.") }}'){
|
||||
document.getElementById('hwi_progress_title').innerHTML = title;
|
||||
document.getElementById('hwi_progress_text').innerHTML = text;
|
||||
showPageOverlay('hwi_progress');
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
}
|
||||
// detect device
|
||||
if (devicePath == null) {
|
||||
showHWIProgress("Detecting...", "Plug in your device");
|
||||
showHWIProgress('{{ _("Detecting...") }}', '{{ _("Plug in your device") }}');
|
||||
}
|
||||
let result = [];
|
||||
let retryCounter = 0;
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
// Likely bitbox02 pairing
|
||||
let pairingCode = (await hwi.getBitbox02PairingCode()).code;
|
||||
if (pairingCode) {
|
||||
document.getElementById('bitbox02-pairing-code').innerHTML = `Pairing Code:<br><br><b>${pairingCode}</b><br><br>Please confirm the code matches the one displayed on your BitBox02 device.<br><br>It could take up to 20 seconds to continue after approving the pairing on the device, please be patient.`;
|
||||
document.getElementById('bitbox02-pairing-code').innerHTML = `{{ _("Pairing Code") }}:<br><br><b>${pairingCode}</b><br><br>{{ _("Please confirm the code matches the one displayed on your BitBox02 device.<br><br>It could take up to 20 seconds to continue after approving the pairing on the device, please be patient.") }}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@
|
|||
// Likely bitbox02 pairing
|
||||
let pairingCode = (await hwi.getBitbox02PairingCode()).code;
|
||||
if (pairingCode) {
|
||||
document.getElementById('bitbox02-pairing-code').innerHTML = `Pairing Code:<br><br><b>${pairingCode}</b><br><br>Please confirm the code matches the one displayed on your BitBox02 device.<br><br>It could take up to 20 seconds to continue after approving the pairing on the device, please be patient.`;
|
||||
document.getElementById('bitbox02-pairing-code').innerHTML = `{{ _("Pairing Code") }}:<br><br><b>${pairingCode}</b><br><br>{{ _("Please confirm the code matches the one displayed on your BitBox02 device.<br><br>It could take up to 20 seconds to continue after approving the pairing on the device, please be patient.") }}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -160,8 +160,8 @@
|
|||
|
||||
async function getXpubs(device, account=0, passphrase=""){
|
||||
// detect device
|
||||
showHWIProgress("Extracting public keys...",
|
||||
`It may take a while.<br><br>Keep your ${capitalize(device.type)} connected.`);
|
||||
showHWIProgress('{{ _("Extracting public keys...") }}',
|
||||
`{{ _("It may take a while.") }}<br><br>{{ _("Keep your") }} ${capitalize(device.type)} {{ _("connected") }}.`);
|
||||
let result = [];
|
||||
try {
|
||||
result = await hwi.getXpubs(device, account, passphrase);
|
||||
|
|
@ -180,8 +180,8 @@
|
|||
|
||||
async function getXpub(device, derivation="", passphrase="", chain=""){
|
||||
// detect device
|
||||
showHWIProgress("Extracting public key...<br><span style='font-size: 0.8em;'>Derivation: " + derivation + "</span>",
|
||||
`It may take a while.<br><br>Keep your ${capitalize(device.type)} connected.`);
|
||||
showHWIProgress('{{ _("Extracting public key...") }}<br><span style=\'font-size: 0.8em;\'>{{ _("Derivation") }}: ' + derivation + "</span>",
|
||||
`{{ _("It may take a while.") }}<br><br>{{ _("Keep your") }} ${capitalize(device.type)} {{ _("connected") }}.`);
|
||||
let result = [];
|
||||
try {
|
||||
result = await hwi.getXpub(device, derivation, passphrase, chain);
|
||||
|
|
@ -200,8 +200,8 @@
|
|||
|
||||
async function getMasterBlindingKey(device, passphrase="", chain=""){
|
||||
// detect device
|
||||
showHWIProgress("Extracting master blinding key...",
|
||||
`Please confirm action on your ${capitalize(device.type)} device.`);
|
||||
showHWIProgress('{{ _("Extracting master blinding key...") }}',
|
||||
`{{ _("Please confirm action on your") }} ${capitalize(device.type)} {{ _("device") }}.`);
|
||||
let result = [];
|
||||
try {
|
||||
result = await hwi.getMasterBlindingKey(device, passphrase, chain);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
function changeLanguage(selectObj) {
|
||||
// get the index of the selected option
|
||||
var idx = selectObj.selectedIndex;
|
||||
|
||||
// get the value of the selected option
|
||||
var language_code = selectObj.options[idx].value;
|
||||
|
||||
fetch(
|
||||
"{{ url_for('set_language_code') }}",
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({language_code: language_code})
|
||||
}
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(jsonResponse => {
|
||||
if (jsonResponse.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
showError('{{ _("Failed to change language") }}')
|
||||
}
|
||||
return;
|
||||
})
|
||||
.catch(err => {
|
||||
console.log("Caught error: ", err);
|
||||
showError(err);
|
||||
});
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{#
|
||||
Relies on `supported_languages=app.supported_languages` being injected into the context
|
||||
#}
|
||||
<select name="language_code" onchange="changeLanguage(this)">
|
||||
{% for language_code, language in supported_languages.items() %}
|
||||
<option value="{{ language_code }}" {% if session["language_code"] == language_code %}selected{% endif %}>{{ language }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<tool-tip title="Merkle Proof Validation" style="float: right;">
|
||||
Specter-Desktop validates <a href="https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki" target="_blank">BIP 37</a> Merkle Proofs from your full node <strong>guaranteeing</strong> that transactions displayed as confirmed have been included in the specified block hash.<br><br>
|
||||
However, a compromised full node could return a fake block that was never mined into Bitcoin's blockchain!
|
||||
If you find the displayed block hash in other locations (other nodes, block explorers, etc) <strong>and you trust specter-desktop isn't lying to you</strong>, then you can be sure this transaction exists in their blockchain as well.
|
||||
{{ _('Specter-Desktop validates <a href="https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki" target="_blank">BIP 37</a> Merkle Proofs from your full node <strong>guaranteeing</strong> that transactions displayed as confirmed have been included in the specified block hash.') }}<br><br>
|
||||
{{ _("However, a compromised full node could return a fake block that was never mined into Bitcoin's blockchain!") }}
|
||||
{{ _("If you find the displayed block hash in other locations (other nodes, block explorers, etc) <strong>and you trust specter-desktop isn't lying to you</strong>, then you can be sure this transaction exists in their blockchain as well.") }}"
|
||||
</tool-tip>
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
<div id="tx_broadcast" class="hidden">
|
||||
<h1>Transaction is Ready to Broadcast</h1>
|
||||
<h1>{{ _("Transaction is Ready to Broadcast") }}</h1>
|
||||
<div class="flex-center flex-column">
|
||||
<button id="broadcast_local_btn" class="btn flex-item" style="width: 190px;">Send transaction</button>
|
||||
<button type="button" id="broadcast_block_explorer_btn" class="btn flex-item {% if specter.chain == 'regtest' %}hidden{% endif %}" style="width: 190px;">Send via block explorer</button>
|
||||
<button type="button" id="broadcast_blockstream_btn" class="btn flex-item hidden" style="width: 190px;">Send via blockstream.info</button>
|
||||
<button type="button" id="broadcast_mempool_btn" class="btn flex-item hidden" style="width: 190px;">Send via mempool.space</button>
|
||||
<button type="button" id="broadcast_blockstream_tor_btn" class="btn flex-item hidden" style="width: 190px;">Send via blockstream.info<br>(over Tor hidden service)</button>
|
||||
<button type="button" id="broadcast_mempool_tor_btn" class="btn flex-item hidden" style="width: 190px;">Send via mempool.space<br>(over Tor hidden service)</button>
|
||||
<button type="button" id="broadcast_options_btn" class="btn flex-item hidden" style="width: 190px;">Back to options</button>
|
||||
<button id="save_psbt_btn" class="btn flex-item" style="width: 190px;">Save as pending</button>
|
||||
<button id="copy_final_tx_btn" class="btn flex-item" style="width: 190px;">Copy raw transaction</button>
|
||||
<button id="broadcast_local_btn" class="btn flex-item" style="width: 190px;">{{ _("Send transaction") }}</button>
|
||||
<button type="button" id="broadcast_block_explorer_btn" class="btn flex-item {% if specter.chain == 'regtest' %}hidden{% endif %}" style="width: 190px;">{{ _("Send via block explorer") }}</button>
|
||||
<button type="button" id="broadcast_blockstream_btn" class="btn flex-item hidden" style="width: 190px;">{{ _("Send via blockstream.info") }}</button>
|
||||
<button type="button" id="broadcast_mempool_btn" class="btn flex-item hidden" style="width: 190px;">{{ _("Send via mempool.space") }}</button>
|
||||
<button type="button" id="broadcast_blockstream_tor_btn" class="btn flex-item hidden" style="width: 190px;">{{ _("Send via blockstream.info") }}<br>({{ _("over Tor hidden service") }}</button>
|
||||
<button type="button" id="broadcast_mempool_tor_btn" class="btn flex-item hidden" style="width: 190px;">{{ _("Send via mempool.space") }}<br>({{ _("over Tor hidden service") }}</button>
|
||||
<button type="button" id="broadcast_options_btn" class="btn flex-item hidden" style="width: 190px;">{{ _("Back to options") }}</button>
|
||||
<button id="save_psbt_btn" class="btn flex-item" style="width: 190px;">{{ _("Save as pending") }}</button>
|
||||
<button id="copy_final_tx_btn" class="btn flex-item" style="width: 190px;">{{ _("Copy raw transaction") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div>
|
||||
<span id='key_qr_code'></span>
|
||||
<br><pre id="key_text" class='key center' onclick="copyText(this.innerText, 'Coppied full key')">{{ key }}</pre>
|
||||
<br><pre id="key_text" class='key center' onclick="copyText(this.innerText, '{{ _("Copied full key") }}')">{{ key }}</pre>
|
||||
</div>
|
||||
<br>
|
||||
<table style="text-align: left; width: 70%; margin: auto; border: none;">
|
||||
<tr style="background: unset; border: none;">
|
||||
<td style="border: none;">
|
||||
<label class="switch-label">Show derivation path: </label>
|
||||
<label class="switch-label">{{ _("Show derivation path") }}: </label>
|
||||
</td>
|
||||
<td style="border: none;">
|
||||
<label class="switch">
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
</tr>
|
||||
<tr style="background: unset; border: none;">
|
||||
<td style="border: none;">
|
||||
<label class="switch-label">Use SLIP-132: </label>
|
||||
<label class="switch-label">{{ _("Use SLIP-132") }}: </label>
|
||||
</td>
|
||||
<td style="border: none;">
|
||||
<label class="switch">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="page_overlay_popup" id="page_overlay_popup">
|
||||
<div id="page_overlay_popup_content"></div>
|
||||
<div class="page_overlay_popup_cancel">
|
||||
<a href="#" id="page_overlay_popup_cancel_button" class="item medium" onclick="cancelOverlay()">cancel</a>
|
||||
<a href="#" id="page_overlay_popup_cancel_button" class="item medium" onclick="cancelOverlay()">{{ _("cancel") }}</a>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="clear-both"></div>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<div class="loader" id="pacman">
|
||||
<img src="{{ url_for('static', filename='img/loader.gif') }}"/>
|
||||
<div>Loading... It may take a while...</div>
|
||||
<div>{{ _("Loading... It may take a while...") }}</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
<form id="{{ device.alias }}_sign_tx_qr" class="flex-center flex-column hidden">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<h2>Scan this QR code with your {{device.name}}.</h2>
|
||||
<h2>{{ _("Scan this QR code with your") }} {{device.name}}.</h2>
|
||||
<div class="row" style="min-height: 400px; margin-top:20px">
|
||||
<span id="compressed_psbt" style="margin: auto;">
|
||||
<qr-code animate="{{device.qr_code_animate}}" frame-rate="{{device.qr_code_frame_rate}}" class='center' value="{{ device_psbts['qrcode'] }}" width="400" scalable></qr-code>
|
||||
|
|
@ -8,7 +9,7 @@
|
|||
</div>
|
||||
<qr-scanner id="{{ device.alias }}_qr_scan_btn">
|
||||
<a slot="button" class="btn flex-item" style="width: 250px; margin: 15px auto;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 30px; margin: 0px;" class="svg-white"> Scan signed transaction
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 30px; margin: 0px;" class="svg-white"> {{ _("Scan signed transaction") }}
|
||||
</a>
|
||||
</qr-scanner>
|
||||
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
hidePageOverlay();
|
||||
combine(result);
|
||||
} else {
|
||||
showError("Scan failed, please try again...", 3000);
|
||||
showError('{{ _("Scan failed, please try again...") }}', 3000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<div id="{{ device.alias }}_sign_tx_file" class="flex-center flex-column hidden">
|
||||
<h2>Sign Transaction with file</h2><br>
|
||||
<a class="btn centered" style="margin: 15px auto;" download="to{{ psbt['address'] }}{{ psbt['amount'] }}.psbt" href="data:application/octet-stream;base64;content-disposition=attachment,{{ device_psbts['sdcard'] }}">Save transaction to file</a>
|
||||
<h2>{{ _("Sign Transaction with file") }}</h2><br>
|
||||
<a class="btn centered" style="margin: 15px auto;" download="to{{ psbt['address'] }}{{ psbt['amount'] }}.psbt" href="data:application/octet-stream;base64;content-disposition=attachment,{{ device_psbts['sdcard'] }}">{{ _("Save transaction to file") }}</a>
|
||||
<input type="file" id="{{ device.alias }}_signing_file" class="inputfile"/>
|
||||
<label for="{{ device.alias }}_signing_file" class="btn centered" style="margin: 15px auto;">Load signed transaction from file</label>
|
||||
<label for="{{ device.alias }}_signing_file" class="btn centered" style="margin: 15px auto;">{{ _("Load signed transaction from file") }}</label>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
{% macro sign_tx_method(wallet, device, psbt, chain) -%}
|
||||
{% set device_psbts = device.create_psbts(psbt['base64'], wallet) %}
|
||||
<div id="{{ device.alias }}_sign_tx_method" class="flex-center flex-column hidden">
|
||||
<h2>Sign Transaction</h2><br>
|
||||
<h2>{{ _("Sign Transaction") }}</h2><br>
|
||||
<div>
|
||||
Please choose a signing method for your {{ device.name }}.
|
||||
{{ _("Please choose a signing method for your") }} {{ device.name }}.
|
||||
</div>
|
||||
{% if device.hwi_support %}
|
||||
<button id="{{ device.alias }}_hwi_sign_btn" class="btn flex-item" style="width: 190px; margin: 15px auto;">
|
||||
<img src="{{ url_for('static', filename='img/usb.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white">Sign via USB (HWI)
|
||||
<img src="{{ url_for('static', filename='img/usb.svg') }}" style="width: 26px; margin-right: 2px;" class="svg-white">{{ _("Sign via USB (HWI)") }}
|
||||
</button>
|
||||
<script>
|
||||
document.getElementById("{{ device.alias }}_hwi_sign_btn").addEventListener("click", e => {
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
{% if device.sd_card_support %}
|
||||
{% include "includes/overlay/sd_card_sign.jinja" %}
|
||||
<button id="{{ device.alias }}_file_sign_btn" class="btn flex-item" style="width: 190px; margin: 15px auto;">
|
||||
<img src="{{ url_for('static', filename='img/sd-card.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> Sign with SD Card file
|
||||
<img src="{{ url_for('static', filename='img/sd-card.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> {{ _("Sign with SD Card file") }}
|
||||
</button>
|
||||
<script>
|
||||
document.getElementById("{{ device.alias }}_file_sign_btn").addEventListener("click", e => {
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
{% if device.qr_code_support %}
|
||||
{% include "includes/overlay/qr_code_sign.jinja" %}
|
||||
<button id="{{ device.alias }}_qr_sign_btn" class="btn flex-item" style="width: 190px; margin: 15px auto;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> Sign with QR codes
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> {{ _("Sign with QR codes") }}
|
||||
</button>
|
||||
<script>
|
||||
document.getElementById("{{ device.alias }}_qr_sign_btn").addEventListener("click", e => {
|
||||
|
|
@ -64,10 +64,10 @@
|
|||
<button id="{{ device.alias }}_hot_sign_btn" class="btn flex-item" style="width: 190px; margin: 15px auto;">
|
||||
{% if device.device_type == "bitcoincore" %}
|
||||
{% from "components/bitcoin_svg.jinja" import bitcoin_svg %}
|
||||
{{ bitcoin_svg(chain, 16) }} Sign with Bitcoin Core
|
||||
{{ bitcoin_svg(chain, 16) }} {{ _("Sign with Bitcoin Core") }}
|
||||
{% else %}
|
||||
{% from "components/liquid_svg.jinja" import liquid_svg %}
|
||||
{{ liquid_svg(chain, 16) }} Sign with Elements Core
|
||||
{{ liquid_svg(chain, 16) }} {{ _("Sign with Elements Core") }}
|
||||
{% endif %}
|
||||
</button>
|
||||
<script>
|
||||
|
|
@ -80,9 +80,9 @@
|
|||
<div id="hot_enter_passphrase" class="hidden">
|
||||
{% set is_encrypted = device.is_encrypted(wallet.manager) %}
|
||||
{% if is_encrypted %}
|
||||
<h2>Enter decryption password</h2>
|
||||
<h2>{{ _("Enter decryption password") }}</h2>
|
||||
{% else %}
|
||||
<h2>Proceed with signing</h2>
|
||||
<h2>{{ _("Proceed with signing") }}</h2>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('wallets_endpoint.send_new', wallet_alias=wallet.alias) }}" method="POST" id="hot_enter_passphrase__content" class="flex-center flex-column">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
{% if is_encrypted %}
|
||||
<input name="passphrase" type="password" id="hwi_enter_passphrase__passphrase" />
|
||||
{% else %}
|
||||
Wallet is not encrypted, just click the button.
|
||||
{{ _("Wallet is not encrypted, just click the button.") }}
|
||||
<input name="passphrase" type="hidden" id="hwi_enter_passphrase__passphrase" value="" />
|
||||
{% endif %}
|
||||
<input name="device" type="hidden" value="{{ device.alias }}" />
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<button id="hot_enter_passphrase__submit" class="btn" type="submit" name="action" value="signhotwallet">Sign transaction</button>
|
||||
<button id="hot_enter_passphrase__submit" class="btn" type="submit" name="action" value="signhotwallet">{{ _("Sign transaction") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@
|
|||
min-width: 5em;
|
||||
}
|
||||
</style>
|
||||
<span class="page-limit-label">Page limit: </span>
|
||||
<span class="page-limit-label">{{ _("Page limit") }}: </span>
|
||||
<fieldset class="page-limit-fieldset">
|
||||
<select class="page-limit-select">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="0">All</option>
|
||||
<option value="0">{{ _("All") }}</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
@ -204,7 +204,7 @@ class QRCodeElement extends HTMLElement {
|
|||
value = this.maybe_remove_checksum(value);
|
||||
this.qrcode.makeCode(value);
|
||||
if(this.isQRlarge){
|
||||
this.note.innerHTML = "Click to animate";
|
||||
this.note.innerHTML = '{{ _("Click to animate") }}';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class QRScannerElement extends HTMLElement {
|
|||
return this.triggerError("Invalid QR code! n > m!");
|
||||
}
|
||||
if(this.partial_qr.len != 0 && this.partial_qr.len != n){
|
||||
return this.triggerError("Number of frames in QR code mismatch!<br>Had "+this.partial_qr.len+", got "+n);
|
||||
return this.triggerError('{{ _("Number of frames in QR code mismatch!") }}<br>Had ' + this.partial_qr.len + ", got " + n);
|
||||
}
|
||||
// if it is the first qr code - build array
|
||||
if(this.partial_qr.len == 0){
|
||||
|
|
@ -199,14 +199,14 @@ class QRScannerElement extends HTMLElement {
|
|||
let isopen = this.hasAttribute('open');
|
||||
if(isopen){
|
||||
if(navigator.mediaDevices === undefined && location.protocol == "http:"){
|
||||
this.triggerError("Can't get access to the camera. Specter should run on localhost or over https to enable QR code scanning.");
|
||||
this.triggerError("{{ _("Can\'t get access to the camera. Specter should run on localhost or over https to enable QR code scanning.") }}");
|
||||
return;
|
||||
}
|
||||
try{
|
||||
this.popup.style.display = 'flex';
|
||||
this.scanner.start();
|
||||
}catch(e){
|
||||
this.triggerError("Can't start the QR scanner!<br>" + e);
|
||||
this.triggerError("{{ _("Can\'t start the QR scanner!") }}<br>" + e);
|
||||
}
|
||||
}else{
|
||||
this.reset();
|
||||
|
|
|
|||
|
|
@ -1,41 +1,40 @@
|
|||
<div id="bitcoin_core_info" class="hidden" style="text-align: left; max-width: 500px;">
|
||||
<h1>Bitcoin Core Node Info:</h1>
|
||||
<h1>{{ _("Bitcoin Core Node Info") }}:</h1>
|
||||
{% if specter.chain %}
|
||||
{% if specter.info['pruned'] %}
|
||||
<p class="warning"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>You are using a pruned node.<br>Pruned nodes may not be able to scan for old balances when importing an existing wallet.<br>It is recommended to use Specter with an unpruned node.</p>
|
||||
<p class="warning"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("You are using a pruned node.<br>Pruned nodes may not be able to scan for old balances when importing an existing wallet.<br>It is recommended to use Specter with an unpruned node.") }}</p>
|
||||
<br>
|
||||
{% elif not specter.info['blockfilterindex'] %}
|
||||
<p class="warning"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>Your node does not have <code>blockfilterindex</code> enabled.<br>Setting <code>blockfilterindex=1</code> in your <code>bitcoin.conf</code> file is recommended as it takes just a few GB of storage<br>and helps to speed-up blockchain rescanning.</p>
|
||||
<p class="warning"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("Your node does not have <code>blockfilterindex</code> enabled.<br>Setting <code>blockfilterindex=1</code> in your <code>bitcoin.conf</code> file is recommended as it takes just a few GB of storage<br>and helps to speed-up blockchain rescanning.") }}</p>
|
||||
<br>
|
||||
{% endif %}
|
||||
<table>
|
||||
<tr> <td style="text-align: left;">Network:</td> <td style="text-align: right;" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
|
||||
<tr> <td style="text-align: left; width: 35%;">Bitcoin Core Version:</td> <td style="text-align: right;">v{{specter.bitcoin_core_version}} <span class="note">({{specter.network_info['version']}})</span></td> </tr>
|
||||
<tr> <td style="text-align: left;">Connections count:</td> <td style="text-align: right;">{{specter.network_info['connections']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">Difficulty:</td> <td style="text-align: right;">{{specter.info.get('difficulty', 0) | int}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">Size on disk:</td> <td style="text-align: right;">{{specter.info['size_on_disk']|bytessize }}</td> </tr>
|
||||
<tr> <td style="text-align: left;">Blocks count:</td> <td style="text-align: right;">{{specter.info['blocks']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">Last block hash:</td> <td style="text-align: right"><code style="word-break: break-word;">{{specter.info['bestblockhash']}}</code></td> </tr>
|
||||
<tr> <td style="text-align: left;">Mempool Size:</td> <td style="text-align: right;">{{specter.info['mempool_info'].size}} transactions</td> </tr>
|
||||
<tr> <td style="text-align: left;">Node uptime:</td> <td style="text-align: right;">~ {{(specter.info['uptime'] / 60 // 60) | int }} Hours</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Network") }}:</td> <td style="text-align: right;" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
|
||||
<tr> <td style="text-align: left; width: 35%;">{{ _("Bitcoin Core Version") }}:</td> <td style="text-align: right;">v{{specter.bitcoin_core_version}} <span class="note">({{specter.network_info['version']}})</span></td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Connections count") }}:</td> <td style="text-align: right;">{{specter.network_info['connections']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Difficulty") }}:</td> <td style="text-align: right;">{{specter.info.get('difficulty', 0) | int}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Size on disk") }}:</td> <td style="text-align: right;">{{specter.info['size_on_disk']|bytessize }}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Blocks count") }}:</td> <td style="text-align: right;">{{specter.info['blocks']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Last block hash") }}:</td> <td style="text-align: right"><code style="word-break: break-word;">{{specter.info['bestblockhash']}}</code></td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Mempool Size") }}:</td> <td style="text-align: right;">{{specter.info['mempool_info'].size}} transactions</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Node uptime") }}:</td> <td style="text-align: right;">~ {{(specter.info['uptime'] / 60 // 60) | int }} {{ _("Hours") }}</td> </tr>
|
||||
{% if specter.info['pruned'] %}
|
||||
<tr> <td style="text-align: left;">Automatic pruning:</td> <td style="text-align: right;">{{specter.info['automatic_pruning']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">Prune height:</td> <td style="text-align: right;">{{specter.info['pruneheight']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">Prune target size:</td> <td style="text-align: right;">{{specter.info['prune_target_size']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Automatic pruning") }}:</td> <td style="text-align: right;">{{specter.info['automatic_pruning']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Prune height") }}:</td> <td style="text-align: right;">{{specter.info['pruneheight']}}</td> </tr>
|
||||
<tr> <td style="text-align: left;">{{ _("Prune target size") }}:</td> <td style="text-align: right;">{{specter.info['prune_target_size']}}</td> </tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<p id="total_supply" style="line-height: 2.5;"></p>
|
||||
<div class="row">
|
||||
<button type="button" onclick="fetchTotalSupply()" class="btn centered">
|
||||
Run the numbers!
|
||||
{{ _("Run the numbers!") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Calculate the total Bitcoin supply<br><br></span>
|
||||
This will run Bitcoin Core's <code>gettxoutsetinfo</code> command,
|
||||
which will calculate the total amount of Bitcoin's UTXO set.<br><br>
|
||||
This might take a few minutes...<br>
|
||||
<span class="info__title">{{ _("Calculate the total Bitcoin supply") }}<br><br></span>
|
||||
{{ _("This will run Bitcoin Core's <code>gettxoutsetinfo</code> command which will calculate the total amount of Bitcoin's UTXO set.") }}<br><br>
|
||||
{{ _("This might take a few minutes...") }}<br>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -44,7 +43,7 @@
|
|||
<script>
|
||||
let totalUserBalance = parseFloat(parseFloat("{{ specter.wallet_manager.wallets.values() | sum(attribute='fullbalance') }}").toFixed(8));
|
||||
async function fetchTotalSupply() {
|
||||
document.getElementById('total_supply').innerHTML = 'Running the numbers... (this might take a few minutes)';
|
||||
document.getElementById('total_supply').innerHTML = '{{ _("Running the numbers... (this might take a few minutes)") }}';
|
||||
try {
|
||||
const response = await fetch(
|
||||
"{{ url_for('txout_set_info') }}",
|
||||
|
|
@ -55,9 +54,9 @@
|
|||
let result = await response.json();
|
||||
console.log(result)
|
||||
let userBalanceFromTotal = parseFloat((100 / (result.total_amount / totalUserBalance)).toFixed(8));
|
||||
document.getElementById('total_supply').innerHTML = `Bitcoin Total Supply: ${result.total_amount} BTC<br>` +
|
||||
document.getElementById('total_supply').innerHTML = `{{ _("Bitcoin Total Supply") }}: ${result.total_amount} BTC<br>` +
|
||||
( userBalanceFromTotal > 1e-8 ?
|
||||
`<span class="note" style="margin: 7px auto;">Your wallets hold ${totalUserBalance} BTC (~${userBalanceFromTotal}% from the total supply)</span>` :
|
||||
`<span class="note" style="margin: 7px auto;">{{ _("Your wallets hold") }} ${totalUserBalance} BTC (~${userBalanceFromTotal}% {{ _("from the total supply") }}</span>` :
|
||||
''
|
||||
)
|
||||
} catch(e) {
|
||||
|
|
@ -70,7 +69,7 @@
|
|||
<div class="row">
|
||||
<a id="active-node-settings-btn" class="btn centered" href="{{ url_for('nodes_endpoint.node_settings', node_alias=specter.node.alias) }}">
|
||||
<img src="{{ url_for('static', filename='img/gear.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Configure Bitcoin Core RPC Connection
|
||||
{{ _("Configure Bitcoin Core RPC Connection") }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
}
|
||||
</style>
|
||||
<div id="node_select_popup" class="hidden" style="text-align: left;">
|
||||
<h1>Node configuration</h1>
|
||||
<h1>{{ _("Node configuration") }}</h1>
|
||||
{% for node_name in specter.node_manager.nodes_names %}
|
||||
{% set node = specter.node_manager.nodes[node_name] %}
|
||||
<form action="{{url_for('nodes_endpoint.switch_node')}}" id="{{node.alias}}-select-node-form" method="POST">
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
{% if node.chain %}
|
||||
{{node.chain | title}}, {{ node.host }}:{{ node.port }}
|
||||
{% else %}
|
||||
Node unreachable...
|
||||
{{ _("Node unreachable...") }}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<img src="{{ url_for('static', filename='img/devices/' ~ device.icon) }}" width="22px">
|
||||
<div>
|
||||
{{ device.name }}<br>
|
||||
<span class="note" style="font-size: 0.75em;">{{ device.keys | length}} keys</span>
|
||||
<span class="note" style="font-size: 0.75em;">{{ device.keys | length}} {{ _("keys") }}</span>
|
||||
</div>
|
||||
</a>
|
||||
{%- endmacro %}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@
|
|||
<img src="{{ url_for('static', filename='img/' + ('two-keys' if wallet.is_multisig else 'key') + '.svg') }}" class="svg-white" style="width: 45px; margin-left: 5px;">
|
||||
<div class="grow">
|
||||
{{ wallet.name }}<br>
|
||||
<span class="note" style="font-size: 0.75em;">{% if wallet.is_multisig %}{{ wallet.sigs_required }} out of {{ wallet.keys_count }} Multisig{% else %}Single Key{% endif %}</span>
|
||||
<span class="note" style="font-size: 0.75em;">{% if wallet.is_multisig %}{{ wallet.sigs_required }} out of {{ wallet.keys_count }} {{ _("Multisig") }}{% else %}{{ _("Single Key") }}{% endif %}</span>
|
||||
{% if wallet.rescan_progress %}
|
||||
<span id="{{ wallet.alias }}_sidebar_rescan_progress_container" style="display: contents;">
|
||||
<br>
|
||||
<span class="note" style="font-size: 0.7em;">Rescanning blockchain: <span id="{{ wallet.alias }}_sidebar_wallet_rescan_percents">{{ "%.2f"|format(wallet.rescan_progress * 100) }}</span>%</span>
|
||||
<span class="note" style="font-size: 0.7em;">{{ _("Rescanning blockchain") }}: <span id="{{ wallet.alias }}_sidebar_wallet_rescan_percents">{{ "%.2f"|format(wallet.rescan_progress * 100) }}</span>%</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
<img height="38px" class="spinning" spinning src="{{ url_for('static', filename='img/refresh.svg') }}" />
|
||||
<div class="grow">
|
||||
{{ wallet.name }}<br>
|
||||
<span class="note" style="font-size: 0.75em;">Loading wallet...</span>
|
||||
<span class="note" style="font-size: 0.75em;">{{ _("Loading wallet...") }}</span>
|
||||
</div>
|
||||
</a>
|
||||
{% if wallet.rescan_progress %}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@
|
|||
<div id="side-content">
|
||||
<nav class="side">
|
||||
{% if specter.info["initialblockdownload"] %}
|
||||
<p class="warning" style="margin-top: 30px;"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>Bitcoin Core is still syncing...<br>(data might be outdated)</p>
|
||||
<p class="warning" style="margin-top: 30px;"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("Bitcoin Core is still syncing...<br>(data might be outdated)") }}</p>
|
||||
{% endif %}
|
||||
{% if specter.network_info.version < 200000 %}
|
||||
<p class="warning" style="margin-top: 30px;"><img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/><br>Bitcoin Core version is outdated.<br>Some features might not work...<br>(minimum required: v20.0.0).</p>
|
||||
<p class="warning" style="margin-top: 30px;"><img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/><br>{{ _("Bitcoin Core version is outdated.<br>Some features might not work...<br>(minimum required: v20.0.0).") }}</p>
|
||||
{% endif %}
|
||||
<div
|
||||
id="active-node" onclick="showPageOverlay('bitcoin_core_info');document.getElementById('side-content').classList.remove('active');"
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
{{ specter.info.chain }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Unavailable. Click to configure.
|
||||
{{ _("Unavailable. Click to configure.") }}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
|
|
@ -126,26 +126,26 @@
|
|||
</script>
|
||||
{% endif %}
|
||||
{% if specter.setup_status.stage != "start" and specter.rpc is none %}
|
||||
{{ sidebar_btn(url_for('setup_endpoint.' + specter.setup_status.stage), 'See setup progress', class='btn action', icon=false) }}
|
||||
{{ sidebar_btn(url_for('setup_endpoint.' + specter.setup_status.stage), _('See setup progress'), class='btn action', icon=false) }}
|
||||
{% endif %}
|
||||
<div class="separator">
|
||||
<span id="toggle_wallets_list" style="cursor: pointer;">Wallets ▼</span>
|
||||
<span id="toggle_wallets_list" style="cursor: pointer;">{{ _("Wallets") }} ▼</span>
|
||||
{% if (specter.wallet_manager.wallets | length) > 1 %}
|
||||
<a class="note" id="wallets_overview_link" href="{{ url_for('wallets_endpoint.wallets_overview') }}" style="cursor: pointer;">
|
||||
Wallets overview
|
||||
{{ _("Wallets overview") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="wallets_list">
|
||||
<span id="wallets-loading-indicator" class="note {% if not specter.wallet_manager.is_loading or not specter.chain %}hidden{% endif %}" style="text-align: left; margin-left: 20px; margin-top: 5px;">
|
||||
<img height="16px" class="spinning" id="wallet-manager-loader" style="vertical-align: sub;" src="{{ url_for('static', filename='img/refresh.svg') }}" />
|
||||
Updating wallets data...
|
||||
{{ _("Updating wallets data...") }}
|
||||
</span>
|
||||
<span id="wallets-loading-done" class="note hidden" style="text-align: left; margin: 5px 5px 5px 20px;">
|
||||
Finished updating your wallets
|
||||
{{ _("Finished updating your wallets") }}
|
||||
<br>
|
||||
<span id="wallets-loading-done-refresh" class="explorer-link" onclick="location.reload()" style="text-decoration: underline; margin-left: 20px;">
|
||||
Click here to refresh
|
||||
{{ _("Click here to refresh") }}
|
||||
</span>
|
||||
</span>
|
||||
{% set loading_wallets = [] %}
|
||||
|
|
@ -159,29 +159,29 @@
|
|||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if specter.chain %}
|
||||
{{ sidebar_btn(url_for('wallets_endpoint.new_wallet_type'), 'Add new wallet', 'btn_new_wallet') }}
|
||||
{{ sidebar_btn(url_for('wallets_endpoint.new_wallet_type'), _('Add new wallet'), 'btn_new_wallet') }}
|
||||
{% if specter.wallet_manager.failed_load_wallets %}
|
||||
<form action="{{ url_for('wallets_endpoint.failed_wallets') }}" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<p class="warning center">
|
||||
<img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/><br>Failed to load some wallets<br>
|
||||
<button class="btn" name="action" value="retry_loading_wallets" style="margin: auto;">Retry loading wallets</button>
|
||||
<button type="button" class="btn" onclick="showPageOverlay('failed_wallets_popup')" style="margin: auto; margin-top: 5px;">Show Details</button>
|
||||
<img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/><br>{{ _("Failed to load some wallets") }}<br>
|
||||
<button class="btn" name="action" value="retry_loading_wallets" style="margin: auto;">{{ _("Retry loading wallets") }}</button>
|
||||
<button type="button" class="btn" onclick="showPageOverlay('failed_wallets_popup')" style="margin: auto; margin-top: 5px;">{{ _("Show Details") }}</button>
|
||||
</p>
|
||||
</form>
|
||||
<div class="hidden" id="failed_wallets_popup">
|
||||
{% for wallet in specter.wallet_manager.failed_load_wallets %}
|
||||
<div class="card">
|
||||
<h1>Wallet Name: {{wallet.name}}</h1>
|
||||
<h2>Error Details:</h2>
|
||||
<h1>{{ _("Wallet Name") }}: {{wallet.name}}</h1>
|
||||
<h2>{{ _("Error Details") }}:</h2>
|
||||
<p style="color: red">{{wallet.loading_error}}</p>
|
||||
<br><br>
|
||||
<form action="{{ url_for('wallets_endpoint.failed_wallets') }}" method="POST" class="row center">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="wallet_data" value='{{ wallet|tojson }}' />
|
||||
<a class="btn" id="download_failed_wallet_{{wallet.alias}}" download="{{ wallet.name }}.json" style="width: 180px; height: 35px; margin-top: 1.5px;">Download wallet file</a>
|
||||
<button class="btn" type="submit" name="action" value="importwallet" formaction="{{ url_for('wallets_endpoint.new_wallet', wallet_type='import_wallet') }}">Recreate the wallet</button>
|
||||
<button class="btn" name="action" value="delete_failed_wallet">Delete wallet</button>
|
||||
<a class="btn" id="download_failed_wallet_{{wallet.alias}}" download="{{ wallet.name }}.json" style="width: 180px; height: 35px; margin-top: 1.5px;">{{ _("Download wallet file") }}</a>
|
||||
<button class="btn" type="submit" name="action" value="importwallet" formaction="{{ url_for('wallets_endpoint.new_wallet', wallet_type='import_wallet') }}">{{ _("Recreate the wallet") }}</button>
|
||||
<button class="btn" name="action" value="delete_failed_wallet">{{ _("Delete wallet") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
|
|
@ -195,10 +195,10 @@
|
|||
{% elif not specter.setup_status.stage == "bitcoind_datadir" %}
|
||||
<p class="warning" style="border: 1px solid #fff;">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/>
|
||||
<br>Wallets are unavailable if Specter is not connected to Bitcoin Core!<br>
|
||||
<br>{{ _("Wallets are unavailable if Specter is not connected to Bitcoin Core!") }}<br>
|
||||
{% if current_user.is_admin %}
|
||||
<a style="margin: 5px; transform: scale(0.9); font-size: 1em;" class="btn" href="{{ url_for('nodes_endpoint.node_settings', node_alias=specter.node.alias) }}">
|
||||
<img src="{{ url_for('static', filename='img/gear.svg') }}" style="width: 24px; margin: 0px;" class="svg-white">Configure Node
|
||||
<img src="{{ url_for('static', filename='img/gear.svg') }}" style="width: 24px; margin: 0px;" class="svg-white">{{ _('Configure Node') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
|
@ -207,20 +207,20 @@
|
|||
</div>
|
||||
|
||||
<div class="separator">
|
||||
<span id="toggle_devices_list" style="cursor: pointer;">Devices ▼</span>
|
||||
<span id="toggle_devices_list" style="cursor: pointer;">{{ _("Devices") }} ▼</span>
|
||||
</div>
|
||||
{% from 'includes/sidebar/components/sidebar_device_list_item.jinja' import sidebar_device_list_item %}
|
||||
<div id="devices_list">
|
||||
{% for device_name in specter.device_manager.devices_names %}
|
||||
{{ sidebar_device_list_item(specter.device_manager.devices[device_name], device_alias) }}
|
||||
{% endfor %}
|
||||
{{ sidebar_btn(url_for('devices_endpoint.new_device_type'), 'Add new device','btn_new_device') }}
|
||||
{{ sidebar_btn(url_for('devices_endpoint.new_device_type'), _('Add new device'),'btn_new_device') }}
|
||||
</div>
|
||||
<br>
|
||||
<div class="footer">
|
||||
<span>Specter Version: <strong>{{ specter.version.current }}</strong></span>
|
||||
<span>{{ _("Specter Version:") }} <strong>{{ specter.version.current }}</strong></span>
|
||||
<a style="color: #fff; font-size: 1.1em; margin: 10px; text-align: center;" href="{{ url_for('about') }}">
|
||||
About Specter<br>
|
||||
{{ _("About Specter") }}<br>
|
||||
<img src="{{ url_for('static', filename='img/icon.png') }}" style="width: 35px;"/>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -262,32 +262,32 @@
|
|||
}
|
||||
}
|
||||
|
||||
function toggleList(type) {
|
||||
function toggleList(type, display_text) {
|
||||
let titleButton = document.getElementById('toggle_' + type + '_list');
|
||||
let list = document.getElementById(type + '_list');
|
||||
if (list.style.display === 'none') {
|
||||
list.style.display = 'block';
|
||||
titleButton.innerHTML = capitalize(type) + ' ▼';
|
||||
titleButton.innerHTML = capitalize(display_text) + ' ▼';
|
||||
} else {
|
||||
list.style.display = 'none';
|
||||
titleButton.innerHTML = capitalize(type) + ' ▶';
|
||||
titleButton.innerHTML = capitalize(display_text) + ' ▶';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
if ('{{ specter.device_manager.devices_names | length }}' == '0') {
|
||||
document.getElementById('toggle_devices_list').innerHTML = 'Devices';
|
||||
document.getElementById('toggle_devices_list').innerHTML = '{{ _("Devices") }}';
|
||||
} else {
|
||||
document.getElementById('toggle_devices_list').addEventListener('click', (event) => {
|
||||
toggleList('devices');
|
||||
toggleList('devices', '{{ _("Devices") }}');
|
||||
});
|
||||
}
|
||||
|
||||
if ('{{ specter.wallet_manager.wallets_names | length }}' == '0') {
|
||||
document.getElementById('toggle_wallets_list').innerHTML = 'Wallets';
|
||||
document.getElementById('toggle_wallets_list').innerHTML = '{{ _("Wallets") }}';
|
||||
} else {
|
||||
document.getElementById('toggle_wallets_list').addEventListener('click', (event) => {
|
||||
toggleList('wallets');
|
||||
toggleList('wallets', '{{ _("Wallets") }}');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
</style>
|
||||
<div class="tx-data">
|
||||
<h2>Transaction details</h2><br>
|
||||
<h2>{{ _("Transaction details") }}</h2><br>
|
||||
<div class="tx-data-info"></div>
|
||||
<div class="note"></div>
|
||||
</div>
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
this.btcUnit = this.getAttribute('data-btc-unit');
|
||||
this.price = this.getAttribute('data-price');
|
||||
this.symbol = this.getAttribute('data-symbol');
|
||||
this.note.innerText = `Loading transaction details...\n\nTransaction ID: ${this.txid}`;
|
||||
this.note.innerText = `{{ _("Loading transaction details...\n\nTransaction ID:") }}" ${this.txid}`;
|
||||
fetchTxData(this);
|
||||
|
||||
// Attach the created element to the shadow dom
|
||||
|
|
@ -63,9 +63,9 @@
|
|||
const jsonResponse = await response.json();
|
||||
return jsonResponse;
|
||||
} catch(e) {
|
||||
self.note.innerText = "Failed to load transaction details...";
|
||||
self.note.innerText = '{{ _("Failed to load transaction details...") }}';
|
||||
console.log("Caught error fetching decoded transaction: ", e);
|
||||
showError('Failed to fetch data. Please refresh the page and retry.');
|
||||
showError('{{ _("Failed to fetch data. Please refresh the page and retry.") }}');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -81,17 +81,17 @@
|
|||
if(tx.is_purged) {
|
||||
rawtxHTML += `
|
||||
<div class="warning">
|
||||
<p><b>This transaction (aka "tx") could not be found in your node's mempool!</b></p>
|
||||
<p style="text-align:left;">If the bitcoin network is very busy, nodes will start purging the pending txs with the lowest fees. Txs older than two weeks are also purged.</p>
|
||||
<p style="text-align:left;">A purged tx will never complete. Abandoning this tx will clear it from your wallet, making those funds spendable once again.</p>
|
||||
<p style="text-align:left;">But first verify that other nodes have also purged your tx! Enter the tx id into a public block explorer (warning: slight privacy leak). If the tx cannot be found, it is safe to abandon this tx.</p>
|
||||
<p><b>{{ _( "This transaction (aka \"tx\") could not be found in your node's mempool!") }}</b></p>
|
||||
<p style="text-align:left;">{{ _( "If the bitcoin network is very busy, nodes will start purging the pending txs with the lowest fees. Txs older than two weeks are also purged.") }}</p>
|
||||
<p style="text-align:left;">{{ _( "A purged tx will never complete. Abandoning this tx will clear it from your wallet, making those funds spendable once again.") }}</p>
|
||||
<p style="text-align:left;">{{ _( "But first verify that other nodes have also purged your tx! Enter the tx id into a public block explorer (warning: slight privacy leak). If the tx cannot be found, it is safe to abandon this tx.") }}</p>
|
||||
|
||||
<br/><br/>
|
||||
<form method="POST" action="${walletLink}history/">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="action" value="abandon_tx"/>
|
||||
<input type="hidden" name="txid" value="${self.txid}"/>
|
||||
<button type="submit" name="action" value="abandontx" class="btn danger centered" style="max-width: 160px;">Abandon transaction</button>
|
||||
<button type="submit" name="action" value="abandontx" class="btn danger centered" style="max-width: 160px;">{{ _("Abandon transaction") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
rawtxHTML += `
|
||||
<table class="tx-data-table">
|
||||
<tbody>
|
||||
<tr><td><span class="optional">Transaction id:</span><span class="mobile-only">TxID:</span></td><td style="word-break: break-all;"><explorer-link data-type="tx" data-value="${self.txid}"></explorer-link></td></tr>
|
||||
<tr><td><span class="optional">{{ _("Transaction id") }}:</span><span class="mobile-only">TxID:</span></td><td style="word-break: break-all;"><explorer-link data-type="tx" data-value="${self.txid}"></explorer-link></td></tr>
|
||||
<tr><td>From wallet:</td><td><a href=${walletLink}>${walletName}<a></td></tr>
|
||||
<tr><td>Size:</td><td>${rawtx.vsize} vbytes <span class="note">(${rawtx.size} bytes)</span></td></tr>
|
||||
`;
|
||||
|
|
@ -111,20 +111,20 @@
|
|||
}
|
||||
}
|
||||
rawtxHTML += `
|
||||
<tr><td>Fee:</td><td>${parseInt(-1e8 * fee)} sats</td></tr>
|
||||
<tr><td>Fee rate:</td><td>${parseFloat((-1e8 * fee / rawtx.vsize).toFixed(2)).toString()} sat/vbyte</td></tr>
|
||||
<tr><td>{{ _("Fee") }}:</td><td>${parseInt(-1e8 * fee)} sats</td></tr>
|
||||
<tr><td>{{ _("Fee rate") }}:</td><td>${parseFloat((-1e8 * fee / rawtx.vsize).toFixed(2)).toString()} sat/vbyte</td></tr>
|
||||
`;
|
||||
}
|
||||
if (tx.confirmations && tx.confirmations > 0) {
|
||||
rawtxHTML += `
|
||||
<tr><td>Mined at block:</td><td>${tx.blockheight}</td></tr>
|
||||
<tr><td>Block hash:</td><td style="word-break: break-all;">${tx.blockhash}</td></tr>
|
||||
<tr><td>Block time:</td><td>${new Date(tx.blocktime * 1000)} <span style="font-size: 0.9em;color: #ccc;">(${tx.blocktime})</span></td></tr>
|
||||
<tr><td>{{ _("Mined at block") }}:</td><td>${tx.blockheight}</td></tr>
|
||||
<tr><td>{{ _("Block hash") }}:</td><td style="word-break: break-all;">${tx.blockhash}</td></tr>
|
||||
<tr><td>{{ _("Block time") }}:</td><td>${new Date(tx.blocktime * 1000)} <span style="font-size: 0.9em;color: #ccc;">(${tx.blocktime})</span></td></tr>
|
||||
`;
|
||||
}
|
||||
rawtxHTML += `
|
||||
<tr><td>Inputs count:</td><td>${rawtx.vin.length}</td></tr>
|
||||
<tr><td>Outputs count:</td><td>${rawtx.vout.length}</td></tr>
|
||||
<tr><td>{{ _("Inputs count") }}:</td><td>${rawtx.vin.length}</td></tr>
|
||||
<tr><td>{{ _("Outputs count") }}:</td><td>${rawtx.vout.length}</td></tr>
|
||||
`;
|
||||
rawtxHTML += `
|
||||
</tbody>
|
||||
|
|
@ -175,15 +175,15 @@
|
|||
}
|
||||
|
||||
addressAndValue = `<br>
|
||||
Address: <address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br>
|
||||
Value: ${value} ${assetlabel} ${price}`;
|
||||
{{ _("Address:") }} <address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br>
|
||||
{{ _("Value:") }} ${value} ${assetlabel} ${price}`;
|
||||
}
|
||||
|
||||
if ('coinbase' in rawtx.vin[i]) {
|
||||
rawtxHTML += `
|
||||
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
|
||||
<b>Input #${i}</b><br><br>
|
||||
<b>Coinbase transaction</b>
|
||||
<b>{{ _("Input #") }}${i}</b><br><br>
|
||||
<b>{{ _("Coinbase transaction") }}</b>
|
||||
<br>
|
||||
</p>
|
||||
`;
|
||||
|
|
@ -191,15 +191,15 @@
|
|||
}
|
||||
rawtxHTML += `
|
||||
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
|
||||
<b>Input #${i}</b><br><br>
|
||||
Transaction id: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
|
||||
Output # ${rawtx.vin[i].vout}
|
||||
<b>{{ _("Input #") }}${i}</b><br><br>
|
||||
{{ _("Transaction id") }}: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
|
||||
{{ _("Output #") }}" ${rawtx.vin[i].vout}
|
||||
${addressAndValue}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
rawtxHTML += `<h3>Outputs</h3>`;
|
||||
rawtxHTML += `<h3>{{ _("Outputs") }}</h3>`;
|
||||
for (let i in rawtx.vout) {
|
||||
let address = rawtx.vout[i].addresses && rawtx.vout[i].addresses.length == 1 ? rawtx.vout[i].addresses[0] : 'Unknown';
|
||||
if (address == 'Unknown') {
|
||||
|
|
@ -241,9 +241,9 @@
|
|||
|
||||
rawtxHTML += `
|
||||
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
|
||||
<b>Output #${i}</b><br><br>
|
||||
Address: <address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br>
|
||||
Value: ${value} ${assetlabel} ${price}
|
||||
<b>{{ _("Output #${i}") }}</b><br><br>
|
||||
{{ _("Address:") }} <address-label data-copy-hidden="true" data-address="${address}" data-wallet="${self.wallet}"></address-label><br>
|
||||
{{ _("Value:") }} ${value} ${assetlabel} ${price}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@
|
|||
self.note.innerText = "";
|
||||
return;
|
||||
};
|
||||
self.note.innerText = "Failed to load transaction details...";
|
||||
self.note.innerText = '{{ _("Failed to load transaction details...") }}';
|
||||
}
|
||||
async function fetchAddressIsMine(self, address) {
|
||||
let url = `{{ url_for('wallets_endpoint.addressinfo', wallet_alias='WALLET_ALIAS') }}`.replace("WALLET_ALIAS", self.wallet);
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
return jsonResponse.isMine;
|
||||
}
|
||||
} catch(e) {
|
||||
showError("Failed to load address data...");
|
||||
showError('{{ _("Failed to load address data...") }}');
|
||||
showError(e);
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@
|
|||
<td class="time"></td>
|
||||
<td>
|
||||
<span class="confirmations"></span>
|
||||
<button class="rbf btn optional hidden" style="width: 130px; float: right;" type="button">Speed up</button>
|
||||
<button class="rbf-cancel danger btn optional hidden" style="width: 130px; float: right; margin-right: 10px;" type="button">Cancel transaction</button>
|
||||
<button class="rbf btn optional hidden" style="width: 130px; float: right;" type="button">{{ _("Speed up") }}</button>
|
||||
<button class="rbf-cancel danger btn optional hidden" style="width: 130px; float: right; margin-right: 10px;" type="button">{{ _("Cancel transaction") }}</button>
|
||||
</td>
|
||||
<td class="hidden optional blockhash"></td>
|
||||
<td><input class="select-tx-value" type="hidden" value=""><img style="vertical-align: middle;" class="select-tx-img" src="{{ url_for('static', filename='img/checkbox-untick.svg') }}" width="25px"></tool-tip></td>
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
|
||||
// Set address-label
|
||||
if (Array.isArray(this.tx.address)) {
|
||||
this.address.innerText = this.hideSensitiveInfo ? '#########' : `${this.tx.address.length} Recipients`;
|
||||
this.address.innerText = this.hideSensitiveInfo ? '#########' : `${this.tx.address.length} {{ _("Recipients") }}`;
|
||||
} else {
|
||||
this.address.innerHTML = this.hideSensitiveInfo ? '#########' : `<address-label data-address="${this.tx.address}" ${this.tx.label ? `data-label="${this.tx.label}"` : ''} data-wallet="${this.wallet ? this.wallet : this.tx.wallet_alias}"></address-label>`
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
}
|
||||
|
||||
if (this.amount == 1e-8) {
|
||||
this.amountText.innerText = 'Confidential';
|
||||
this.amountText.innerText = '{{ _("Confidential") }}';
|
||||
} else {
|
||||
this.amountText.innerHTML = this.hideSensitiveInfo ? '#########' : `${numberWithCommas(this.amount.toString())}`;
|
||||
if("assetlabel" in this.tx){
|
||||
|
|
@ -156,12 +156,12 @@
|
|||
|
||||
// Set confirmations
|
||||
if (this.tx.confirmations > 0) {
|
||||
this.confirmations.innerHTML = this.hideSensitiveInfo ? '########' : `${this.tx.confirmations}<span class="optional"> Confirmations</span>`;
|
||||
this.confirmations.innerHTML = this.hideSensitiveInfo ? '########' : `${this.tx.confirmations}<span class="optional"> {{ _("Confirmations") }}</span>`;
|
||||
} else if (this.tx.confirmations == 0) {
|
||||
this.el.classList.add('unconfirmed');
|
||||
this.confirmations.innerHTML = this.hideSensitiveInfo ? '########' : `Unconfirmed`;
|
||||
this.confirmations.innerHTML = this.hideSensitiveInfo ? '########' : `{{ _("Unconfirmed") }}`;
|
||||
} else {
|
||||
this.confirmations.innerHTML = this.hideSensitiveInfo ? '########' : `Cancelled (replaced by sender)`;
|
||||
this.confirmations.innerHTML = this.hideSensitiveInfo ? '########' : `{{ _("Cancelled (replaced by sender)") }}`;
|
||||
this.category.src = `{{ url_for('static', filename='img') }}/cross.svg`;
|
||||
this.category.classList.remove('svg-' + this.tx.category);
|
||||
this.category.classList.add('svg-cancelled');
|
||||
|
|
@ -238,19 +238,19 @@
|
|||
}
|
||||
txDataPopup.innerHTML = `
|
||||
<form action="${url}" method="POST">
|
||||
<h1>${rbfType == 'cancel' ? 'Cancel' : 'Speed up'} the Transaction</h1>
|
||||
<p>You can ${rbfType == 'cancel' ? 'cancel' : 'speed up'} the transaction by increasing its fee rate${rbfType == 'cancel' ? ' and sending the funds back to yourself' : ''}:</p>
|
||||
<h1>${rbfType == 'cancel' ? '{{ _("Cancel") }}' : '{{ _("Speed up") }}'} {{ _("the Transaction") }}</h1>
|
||||
<p>{{ _("You can") }} ${rbfType == 'cancel' ? '{{ _("cancel") }}' : '{{ _("speed up") }}'} {{ _("the transaction by increasing its fee rate") }}${rbfType == 'cancel' ? ' {{ _("and sending the funds back to yourself") }}' : ''}:</p>
|
||||
<input type="hidden" name="rbf_tx_id" value='${self.tx.txid}' />
|
||||
<input type="number" min="${newFee}" value="${newFee}" class="fee_rate" name="rbf_fee_rate" id="rbf_fee_rate" min="1" step="any" autocomplete="off"> sat/vbyte
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<br>
|
||||
<br>
|
||||
<button type="submit" name="action" value="${rbfType == 'cancel' ? 'rbf_cancel' : 'rbf'}" class="btn centered ${rbfType == 'cancel' ? 'danger' : ''}">${rbfType == 'cancel' ? 'Cancel transaction' : 'Speed up!'}</button>
|
||||
<button type="submit" name="action" value="${rbfType == 'cancel' ? 'rbf_cancel' : 'rbf'}" class="btn centered ${rbfType == 'cancel' ? 'danger' : ''}">${rbfType == 'cancel' ? '{{ _("Cancel transaction") }}' : '{{ _("Speed up!") }}'}</button>
|
||||
<br>
|
||||
<span class="toggle_advanced_rbf" style="cursor: pointer;">Advanced {% if show_advanced_settings %}▼{% else %}▶{% endif %}</span>
|
||||
<span class="toggle_advanced_rbf" style="cursor: pointer;">{{ _("Advanced") }} {% if show_advanced_settings %}▼{% else %}▶{% endif %}</span>
|
||||
<div class="advanced_rbf hidden warning">
|
||||
<p style="max-width: 400px;">If you would like further customization, you can click here to fully edit the transaction.<br>(advanced, not recommended for new users)</p>
|
||||
<button type="submit" name="action" value="rbf_edit" class="btn centered">Edit the transaction (advanced)</button>
|
||||
<p style="max-width: 400px;">{{ _("If you would like further customization, you can click here to fully edit the transaction.") }}<br>{{ _("(advanced, not recommended for new users)") }}</p>
|
||||
<button type="submit" name="action" value="rbf_edit" class="btn centered">{{ _("Edit the transaction (advanced)") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="txlist-view-btn btn radio left">
|
||||
History
|
||||
{{ _("History") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
</nav>
|
||||
<div class="pagination-container">
|
||||
<a class="pagination-back pagination-arrow pagination-disabled">‹</a>
|
||||
Page <input class="pagination-idx" type="number" step="1" min="1" value="1"></input><span class="pagination-counter"></span>
|
||||
{{ _("Page") }} <input class="pagination-idx" type="number" step="1" min="1" value="1"></input><span class="pagination-counter"></span>
|
||||
<a class="pagination-next pagination-arrow pagination-disabled">›</a>
|
||||
<br>
|
||||
</div>
|
||||
|
|
@ -125,51 +125,51 @@
|
|||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="button" class="btn freeze-tx-btn">
|
||||
<tool-tip title="Freezing UTXOs">
|
||||
Freezing a UTXO will make Specter disallow spending it.<br><br>
|
||||
This is useful especially for dealing with dust attack, where you don't want to accidentally spend the dust received.
|
||||
{{ _("Freezing a UTXO will make Specter disallow spending it.") }}<br><br>
|
||||
{{ _("This is useful especially for dealing with a dust attack, where you don't want to accidentally spend the dust received.") }}"
|
||||
</tool-tip>
|
||||
Un/Freeze UTXOs
|
||||
</button>
|
||||
</form>
|
||||
<form class="compose-tx-form" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="button" class="btn compose-tx-btn">Create new transaction</button>
|
||||
<button type="button" class="btn compose-tx-btn">{{ _("Create new transaction") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="export-container hidden">
|
||||
<h1>Export transactions to CSV</h1>
|
||||
<h1>{{ _("Export transactions to CSV") }}</h1>
|
||||
<div class="{% if not specter.price_check or not (specter.alt_rate and specter.alt_symbol) %}hidden{% endif %}">
|
||||
Fetch historical price data:
|
||||
{{ _("Fetch historical price data") }}:
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="export-prices">
|
||||
<span class="slider"></span>
|
||||
</label><br><br>
|
||||
<div class="note">
|
||||
If enabled, specter will try to fetch historical prices for each transaction date from the configured price provider.
|
||||
<br><b>This has privacy implications!</b>
|
||||
{{ _("If enabled, specter will try to fetch historical prices for each transaction date from the configured price provider.") }}
|
||||
<br><b>{{ _("This has privacy implications!") }}</b>
|
||||
</div><br>
|
||||
</div>
|
||||
<a class="export btn" download>Export</a>
|
||||
<a class="export btn" download>{{ _("Export") }}</a>
|
||||
</div>
|
||||
<table class="tx-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th value="category" class="category-header"><div class="category-arrow"></div></th>
|
||||
<th value="txid" class="optional txid-header">TxID<div class="txid-arrow"></div></th>
|
||||
<th value="label" class="optional label-header">Label<div class="label-arrow"></div></th>
|
||||
<th value="amount" class="amount-header">Amount<div class="amount-arrow"></div></th>
|
||||
<th value="time" class="time-header">Time<div class="time-arrow up-arrow"></div></th>
|
||||
<th value="confirmations" class="confirmations-header">Confirmations<div class="confirmations-arrow"></div></th>
|
||||
<th value="label" class="optional label-header">{{ _("Label") }}<div class="label-arrow"></div></th>
|
||||
<th value="amount" class="amount-header">{{ _("Amount") }}<div class="amount-arrow"></div></th>
|
||||
<th value="time" class="time-header">{{ _("Time") }}<div class="time-arrow up-arrow"></div></th>
|
||||
<th value="confirmations" class="confirmations-header">{{ _("Confirmations") }}<div class="confirmations-arrow"></div></th>
|
||||
<th value="blockhash" class="optional hidden blockhash-header">
|
||||
{% include "includes/merkletooltip.html" %}
|
||||
Block Hash
|
||||
{{ _("Block Hash") }}
|
||||
</th>
|
||||
<th style="width: 50px; padding: 10px;"><button type="button" class="export-btn btn" style="width: 50px;">Export</button></th>
|
||||
<th style="width: 50px; padding: 10px;"><button type="button" class="export-btn btn" style="width: 50px;">{{ _("Export") }}</button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr class="empty">
|
||||
<td></td>
|
||||
<td>Fetching transactions...</td>
|
||||
<td>{{ _("Fetching transactions...") }}</td>
|
||||
<td class="optional"></td>
|
||||
<td class="optional"></td>
|
||||
<td></td>
|
||||
|
|
@ -481,11 +481,11 @@
|
|||
self.callId++;
|
||||
// Clean up from existing transaction list
|
||||
self.summaryRow.classList.add('hidden');
|
||||
self.emptyRow.children[1].innerText = "Fetching transactions...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("Fetching transactions...") }}';
|
||||
self.emptyRow.classList.remove('hidden');
|
||||
self.tbody.querySelectorAll('tx-row').forEach((row) => {row.remove()});
|
||||
if (!self.listType) {
|
||||
self.emptyRow.children[1].innerText = "Failed to fetch transactions...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("Failed to fetch transactions...") }}';
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -518,7 +518,7 @@
|
|||
break
|
||||
default:
|
||||
self.export.href = ``
|
||||
self.emptyRow.children[1].innerText = "Failed to fetch transactions...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("Failed to fetch transactions...") }} ';
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -592,7 +592,7 @@
|
|||
// Show empty row if no transaction found
|
||||
if (self.pageCount == 0 || txlist.length == 0) {
|
||||
self.emptyRow.classList.remove('hidden');
|
||||
self.emptyRow.children[1].innerText = "No transactions found...";
|
||||
self.emptyRow.children[1].innerText = '{{ _("No transactions found...") }}';
|
||||
self.pageCount = 1;
|
||||
} else {
|
||||
self.emptyRow.classList.add('hidden');
|
||||
|
|
@ -654,10 +654,10 @@
|
|||
}
|
||||
return
|
||||
};
|
||||
showError('Failed to fetch transactions list.')
|
||||
showError('{{ _("Failed to fetch transactions list.") }}')
|
||||
} catch(e) {
|
||||
console.log("Caught error: ", e);
|
||||
showError(`Failed to fetch transactions list: ${e}`);
|
||||
showError(`{{ _("Failed to fetch transactions list.") }}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -682,7 +682,7 @@
|
|||
</tx-data>`;
|
||||
showPageOverlay('tx-popup');
|
||||
} else {
|
||||
copyText(txid, `Copied transaction: ${ txid }`);
|
||||
copyText(txid, `{{ _("Copied transaction") }}: ${ txid }`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@
|
|||
|
||||
{% block main %}
|
||||
<div class="card">
|
||||
<h1>Login to Specter</h1>
|
||||
<h1>{{ _("Login to Specter") }}</h1>
|
||||
<form action="{{ url_for('auth_endpoint.login') }}" method="POST" role="form">
|
||||
{% if specter.config['auth']['method'] == 'usernamepassword' %}
|
||||
<input id="username" class="form-control" placeholder="Username" name="username" type="text" value=""><br><br>
|
||||
<input id="username" class="form-control" placeholder="{{ _('Username') }}" name="username" type="text" value=""><br><br>
|
||||
{% endif %}
|
||||
<input id="password" class="form-control" placeholder="Password" name="password" type="password" value="">
|
||||
<input id="password" class="form-control" placeholder="Password" name="{{ _('password') }}" type="password" value="">
|
||||
<input type="hidden" name="next" value="{{ data.next }}">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<br>
|
||||
<br>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn">Login</button>
|
||||
<button type="submit" class="btn">{{ _("Login") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
|
||||
<form action="?" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<h1 id="title" class="settings-title">Settings</h1>
|
||||
<h1 id="title" class="settings-title">{{ _("Settings") }}</h1>
|
||||
{% from 'settings/components/settings_menu.jinja' import settings_menu %}
|
||||
{% from 'settings/components/settings_menu_item.jinja' import settings_menu_item %}
|
||||
{{ settings_menu('bitcoin_core', current_user) }}
|
||||
</form>
|
||||
<br><br><br>
|
||||
<a href="{{ url_for('nodes_endpoint.node_settings', node_alias=node_alias) }}" class="btn centered">Return</a>
|
||||
<a href="{{ url_for('nodes_endpoint.node_settings', node_alias=node_alias) }}" class="btn centered">{{ _("Return") }}</a>
|
||||
<br><br>
|
||||
<div class="card" style="width:90%; max-width: 1000px;">
|
||||
Bitcoin Logs:
|
||||
<pre><code>{{ loglines }}</code></pre>
|
||||
<br>
|
||||
<div>If this is an issue, please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank" style="color: #fff;">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank" style="color: #fff;">Telegram</a> to fix it.</div>
|
||||
<div>{{ _('If this is an issue, please let us know! Create an <a href="https://github.com/cryptoadvance/specter-desktop/issues" target="_blank" style="color: #fff;">issue</a> or ping us on <a href="https://t.me/spectersupport" target="_blank" style="color: #fff;">Telegram</a> to fix it.') }}</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -6,43 +6,43 @@
|
|||
<form action="?" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="card" style="margin: 20px auto 120px; text-align: center;">
|
||||
<h2>Built in Bitcoin Node Status: {% if node.bitcoind.status %}{{node.bitcoind.status}}{% else %}Down{% endif %}</h2><br>
|
||||
<span class="note" style="font-size: 1.3em;">Bitcoin Core Version: {{node.version}}</span>
|
||||
<h2>{{ _("Built-in Bitcoin Node Status:") }} {% if node.bitcoind.status %}{{node.bitcoind.status}}{% else %}Down{% endif %}</h2><br>
|
||||
<span class="note" style="font-size: 1.3em;">{{ _("Bitcoin Core Version:") }} {{node.version}}</span>
|
||||
{% if node.version != latest_bitcoind %}
|
||||
<br><br>
|
||||
<div class="warning" style="padding: 30px;">
|
||||
<h1 style="text-align: center; color: #000">New Bitcoin version available!</h1>
|
||||
<button type="submit" name="action" value="upgrade_bitcoind" class="btn action centered">Upgrade Bitcoin Core version to: v{{ latest_bitcoind }}</button>
|
||||
<h1 style="text-align: center; color: #000">{{ _("New Bitcoin version available!") }}</h1>
|
||||
<button type="submit" name="action" value="upgrade_bitcoind" class="btn action centered">{{ _("Upgrade Bitcoin Core version to:") }} v{{ latest_bitcoind }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<br><br>
|
||||
{% if node.bitcoind.status != "Down" %}
|
||||
<button type="submit" class="btn centered" name="action" value="stopbitcoind">Stop Bitcoin Core</button>
|
||||
<button type="submit" class="btn centered" name="action" value="stopbitcoind">{{ _("Stop Bitcoin Core") }}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn centered" name="action" value="startbitcoind">Start Bitcoin Core</button>
|
||||
<button type="submit" class="btn centered" name="action" value="startbitcoind">{{ _("Start Bitcoin Core") }}</button>
|
||||
<p class="warning">
|
||||
If this is your first time starting the node, and you haven't used the QuickSync, your node will start with Initial Block Downloading (IBD) to sync with the network.<br>
|
||||
This process of IBD syncing may take several days. If you'd like the QuickSync option, please click on it before starting your node, or if it's running, stop it and the click the QuickSync.<br>
|
||||
<b>(Warning: QuickSync will override any existing data of your Bitcoin Node!)</b>
|
||||
{{ _("If this is your first time starting the node, and you haven't used the QuickSync, your node will start with Initial Block Downloading (IBD) to sync with the network.") }}<br>
|
||||
{{ _("This process of IBD syncing may take several days. If you'd like the QuickSync option, please click on it before starting your node, or if it's running, stop it and the click the QuickSync.") }}<br>
|
||||
<b>{{ _("(Warning: QuickSync will override any existing data of your Bitcoin Node!)") }}</b>
|
||||
</p>
|
||||
{% endif %}
|
||||
<br><br>
|
||||
<br><br>
|
||||
<div class="card">
|
||||
<h1>Debug</h1>
|
||||
<a class="btn centered" href="{{url_for('nodes_endpoint.internal_node_logs', node_alias=node_alias)}}">See Bitcoind Logs</a>
|
||||
<h1>{{ _("Debug") }}</h1>
|
||||
<a class="btn centered" href="{{url_for('nodes_endpoint.internal_node_logs', node_alias=node_alias)}}">{{ _("See Bitcoind Logs") }}</a>
|
||||
<br><br>
|
||||
<h1>Danger Zone</h1>
|
||||
<h1>{{ _("Danger Zone") }}</h1>
|
||||
{% if specter.node_manager.nodes | length > 1 and node_alias %}
|
||||
<div class="row">
|
||||
<button type="submit" name="action" value="forget" class="btn danger centered">Forget node</button>
|
||||
<button type="submit" name="action" value="forget" class="btn danger centered">{{ _("Forget node") }}</button>
|
||||
{% endif %}
|
||||
<button type="submit" name="action" value="uninstall_bitcoind" class="btn danger centered">Unistall Bitcoin Core</button>
|
||||
<button type="submit" name="action" value="uninstall_bitcoind" class="btn danger centered">{{ _("Unistall Bitcoin Core") }}</button>
|
||||
{% if specter.node_manager.nodes | length > 1 and node_alias %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<br>
|
||||
<label><input type="checkbox" class="inline" name="remove_datadir"> Delete data folder?</label>
|
||||
<label><input type="checkbox" class="inline" name="remove_datadir"> {{ _("Delete data folder?") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
<form action="?" method="POST">
|
||||
{% else %}
|
||||
<form action="?" method="POST">
|
||||
<h1 id="title">Configure new node</h1>
|
||||
<label style="text-align: center; display: block; margin: auto;font-size: 1.2em; margin-bottom: 7px;">Name your new node:</label>
|
||||
<input type="text" id="name" name="name" value="{{ node.name }}" placeholder="Name your new node" style="width: auto; display: block; margin: auto; width: 70%;">
|
||||
<h1 id="title">{{ _("Configure new node") }}</h1>
|
||||
<label style="text-align: center; display: block; margin: auto;font-size: 1.2em; margin-bottom: 7px;">{{ _("Name your new node:") }}</label>
|
||||
<input type="text" id="name" name="name" value="{{ node.name }}" placeholder='{{ _("Name your new node") }}' style="width: auto; display: block; margin: auto; width: 70%;">
|
||||
{% endif %}
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="card" style="margin: 20px auto 20px;">
|
||||
<h2>Bitcoin JSON-RPC configuration</h2>
|
||||
<h2>{{ _("Bitcoin JSON-RPC configuration") }}</h2>
|
||||
<br>
|
||||
<label style="height: 25px; vertical-align: sub;">Auto-detect: </label>
|
||||
<label class="switch">
|
||||
|
|
@ -28,22 +28,21 @@
|
|||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Setting Bitcoin Core Data Directory<br></span><br>
|
||||
When auto-detect is on, Specter will check for Environment-Variables (BTC_RPC_USER, BTC_RPC_PASSWORD, BTC_RPC_HOST, BTC_RPC_PORT) to configure the connection or
|
||||
attempt to automatically locate your Bitcoin data directory and load your node configurations from it.<br><br>
|
||||
However, if your Bitcoin Core data directory is not located at the default location, you will need to enter its path here so Specter will be able to locate it.<br><br>
|
||||
If you are connecting to a specific remote node, you can disable the auto-detect feature and enter the node's configurations manually below.
|
||||
<span class="info__title">{{ _("Setting Bitcoin Core Data Directory") }}<br></span><br>
|
||||
{{ _("When auto-detect is on, Specter will check for Environment-Variables (BTC_RPC_USER, BTC_RPC_PASSWORD, BTC_RPC_HOST, BTC_RPC_PORT) to configure the connection or attempt to automatically locate your Bitcoin data directory and load your node configurations from it.") }}<br><br>
|
||||
{{ _("However, if your Bitcoin Core data directory is not located at the default location, you will need to enter its path here so Specter will be able to locate it.") }}<br><br>
|
||||
{{ _("If you are connecting to a specific remote node, you can disable the auto-detect feature and enter the node's configurations manually below.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<br><br>
|
||||
Bitcoin Core data directory path:<br><input type="text" id="datadir" name="datadir" type="text" value="{{ node.datadir }}">
|
||||
{{ _("Bitcoin Core data directory path:") }}<br><input type="text" id="datadir" name="datadir" type="text" value="{{ node.datadir }}">
|
||||
</span>
|
||||
<br><br>
|
||||
<div id="rpc_settings">
|
||||
<div id="lock" class="center">
|
||||
<p style="text-align: center; margin: auto; ">🔒</p>
|
||||
<span class="note centered"> (disable auto-detect to configure manually)</span>
|
||||
<span class="note centered"> {{ _("(disable auto-detect to configure manually)") }}</span>
|
||||
</div>
|
||||
Username:<br><input type="text" id="username" name="username" value="{{ node.user }}">
|
||||
<br><br>
|
||||
|
|
@ -54,21 +53,21 @@
|
|||
<qr-scanner id="scan_rpc" style="margin: 10px;">
|
||||
<a slot="button" href="#" class="btn" style="padding: 7px 15px 5px 10px;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 30px; margin: 0px;" class="svg-white">
|
||||
Connect with QR
|
||||
{{ _("Connect with QR") }}
|
||||
</a>
|
||||
</qr-scanner>
|
||||
</div>
|
||||
Port:<br><input type="text" id="port" name="port" type="text" value="{{ node.port }}">
|
||||
<div class="note">
|
||||
Default ports: <b>8332</b> for mainnet, <b>18332</b> for Testnet, <b>18443</b> for Regtest, <b>38332</b> for Signet, <b>7041</b> for Liquid
|
||||
{{ _("Default ports: <b>8332</b> for mainnet, <b>18332</b> for Testnet, <b>18443</b> for Regtest, <b>38332</b> for Signet, <b>7041</b> for Liquid") }}
|
||||
</div>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn" name="action" value="test">Test</button>
|
||||
<button type="submit" class="btn" name="action" value="save">Save</button>
|
||||
<button type="submit" class="btn" name="action" value="test">{{ _("Test") }}</button>
|
||||
<button type="submit" class="btn" name="action" value="save">{{ _("Save") }}</button>
|
||||
</div>
|
||||
{% if test %}
|
||||
<br><div class="log"><b>Test results:</b><br><br>
|
||||
<br><div class="log"><b>{{ _("Test results:") }}</b><br><br>
|
||||
<div style="display: grid;grid-template-columns: auto auto auto; line-height: 2.2;">
|
||||
{% macro tick_or_cross(my_boolean) %}
|
||||
<button style="background: #fff; border: none;" disabled>
|
||||
|
|
@ -80,15 +79,12 @@
|
|||
</button>
|
||||
{% endmacro %}
|
||||
{% if 'connectable' in test['tests'] %}
|
||||
<div>Connectable</div>
|
||||
<div>{{ _("Connectable") }}</div>
|
||||
<div>{{ tick_or_cross(test['tests']['connectable']) }} </div>
|
||||
{% if not test['tests']['connectable'] %}
|
||||
<tool-tip title="Your Node can't be reached" style="float: right;">
|
||||
There are a lot of potential issues preventing the connection.
|
||||
Please doublecheck the Host and the Port. Make sure you can reach
|
||||
the host and make sure that Bitcoin-Core is listening at the port
|
||||
you have specified.
|
||||
For more hints, please look at this <a style="color:grey" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/connect-your-node.md" target="_blank">article</a>.
|
||||
<tool-tip title="{{ _("Your Node can't be reached") }}" style="float: right;">
|
||||
{{ _("There are a lot of potential issues preventing the connection. Please double check the Host and the Port. Make sure you can reach the host and make sure that Bitcoin-Core is listening at the port you have specified.") }}
|
||||
{{ _('For more hints, please look at this <a style="color:grey" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/connect-your-node.md" target="_blank">article</a>.') }}
|
||||
</tool-tip>
|
||||
{% else %}
|
||||
<div></div>
|
||||
|
|
@ -99,10 +95,10 @@
|
|||
<div>Credentials</div>
|
||||
<div> {{ tick_or_cross(test['tests']['credentials']) }} </div>
|
||||
{% if not test['tests']['credentials'] %}
|
||||
<tool-tip title="Your Credentials don't work" style="float: right;">
|
||||
Please doublecheck the user and the Password. Look at the bitcoin.conf
|
||||
for the correct values in that field.
|
||||
For more hints, please look at this <a style="color:grey" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/connect-your-node.md" target="_blank">article</a>.
|
||||
<tool-tip title="{{ _("Your Credentials don't work") }}" style="float: right;">
|
||||
{{ _("Please doublecheck the user and the Password. Look at the bitcoin.conf
|
||||
for the correct values in that field.") }}
|
||||
{{ _('For more hints, please look at this <a style="color:grey" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/connect-your-node.md" target="_blank">article</a>.') }}
|
||||
</tool-tip>
|
||||
{% else %}
|
||||
<div></div>
|
||||
|
|
@ -110,13 +106,11 @@
|
|||
{% endif %}
|
||||
|
||||
{% if 'recent_version' in test['tests'] %}
|
||||
<div>Version recent enough</div>
|
||||
<div>{{ _("Version recent enough") }}</div>
|
||||
<div> {{ tick_or_cross(test['tests']['recent_version']) }} </div>
|
||||
{% if not test['tests']['recent_version'] %}
|
||||
<tool-tip title="Your Core Node might be too old" style="float: right; margin-bottom: 5px;">
|
||||
Specter is working well up from Bitcoin Core version 0.17.
|
||||
The version of your Node is too low, unfortunatley.
|
||||
Please upgrade!
|
||||
<tool-tip title='{{ _("Your Core Node might be too old") }}' style="float: right; margin-bottom: 5px;">
|
||||
{{ _("Specter is working well up from Bitcoin Core version 0.17. The version of your Node is too low, unfortunatley. Please upgrade!") }}
|
||||
</tool-tip>
|
||||
{% else %}
|
||||
<div></div>
|
||||
|
|
@ -127,9 +121,8 @@
|
|||
<div>Wallets enabled</div>
|
||||
<div> {{ tick_or_cross(test['tests']['wallets']) }} </div>
|
||||
{% if not test['tests']['wallets'] %}
|
||||
<tool-tip title="Wallet Support in your Core-Node" style="float: right; margin-bottom: 5px;">
|
||||
The RPC-Interface of your Core-Node is available, but the wallet-api is not.
|
||||
Please make sure to have 'disablewallet=0' in your bitcoin.conf.
|
||||
<tool-tip title='{{ _("Wallet Support in your Core-Node") }}' style="float: right; margin-bottom: 5px;">
|
||||
{{ _("The RPC-Interface of your Core-Node is available, but the wallet-api is not. Please make sure to have 'disablewallet=0' in your bitcoin.conf.") }}
|
||||
</tool-tip>
|
||||
{% else %}
|
||||
<div></div>
|
||||
|
|
@ -140,13 +133,13 @@
|
|||
|
||||
<br>
|
||||
<code>
|
||||
<pre>Process finished with code <b>{{ test.code }}</b>{% if test.code == 0 %} Output: {{ test.out }}{% else %}Error message: {{ test.err }}{% endif %}</pre>
|
||||
<pre>{{ _("Process finished with code ") }}<b>{{ test.code }}</b>{% if test.code == 0 %} {{ _("Output:") }} {{ test.out }}{% else %}{{ _("Error message:") }} {{ test.err }}{% endif %}</pre>
|
||||
</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if specter.node_manager.nodes | length > 1 and node_alias %}
|
||||
<button style="margin-bottom: 100px;" type="submit" name="action" value="forget" class="btn danger centered">Forget node</button>
|
||||
<button style="margin-bottom: 100px;" type="submit" name="action" value="forget" class="btn danger centered">{{ _("Forget node") }}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -211,7 +204,7 @@
|
|||
document.getElementById('host').value = host;
|
||||
document.getElementById('port').value = port;
|
||||
} catch {
|
||||
showError('Failed to read connection data from the QR', 3000)
|
||||
showError('{{ _("Failed to read connection data from the QR") }}', 3000)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@
|
|||
|
||||
{% block main %}
|
||||
<div class="card">
|
||||
<h1>Register to the Specter Node</h1>
|
||||
<h1>{{ _("Register to the Specter Node") }}</h1>
|
||||
<form action="{{ url_for('auth_endpoint.register') }}" method="POST" role="form">
|
||||
{% if specter.config['auth']['method'] == 'usernamepassword' %}
|
||||
<input class="form-control" placeholder="Username" name="username" type="text" value=""><br><br>
|
||||
<input class="form-control" placeholder='{{ _("Username") }}' name="username" type="text" value=""><br><br>
|
||||
{% endif %}
|
||||
<input class="form-control" placeholder="Password" name="password" type="password" value="">
|
||||
<input class="form-control" placeholder='{{ _("Password") }}' name="password" type="password" value="">
|
||||
<input type="hidden" name="otp" value="" id="otp">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<br>
|
||||
<br>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn">Register</button>
|
||||
<button type="submit" class="btn">{{ _("Register") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,30 +7,30 @@
|
|||
{{ settings_menu('auth', current_user) }}
|
||||
<div class="card" style="margin: 20px auto;">
|
||||
{% if method == "none" or current_user.is_admin %}
|
||||
Authentication:<br>
|
||||
{{ _("Authentication") }}:<br>
|
||||
<select name="method" onchange="toggleUsernamePassword(this)">
|
||||
<option value="none" {% if method=="none" %} selected="selected"{% endif %}>None</option>
|
||||
<option value="passwordonly" {% if method=="passwordonly" %} selected="selected"{% endif %}>Password Protection</option>
|
||||
<option value="usernamepassword" {% if method=="usernamepassword" %} selected="selected"{% endif %}>Multiple Users</option>
|
||||
<option value="rpcpasswordaspin" {% if method=="rpcpasswordaspin" %} selected="selected"{% else %}{% if specter.rpc is none or specter.node.autodetect %}disabled{% endif %}{% endif %}>RPC password as Pin</option>
|
||||
<option value="none" {% if method=="none" %} selected="selected"{% endif %}>{{ _("None") }}</option>
|
||||
<option value="passwordonly" {% if method=="passwordonly" %} selected="selected"{% endif %}>{{ _("Password Protection") }}</option>
|
||||
<option value="usernamepassword" {% if method=="usernamepassword" %} selected="selected"{% endif %}>{{ _("Multiple Users") }}</option>
|
||||
<option value="rpcpasswordaspin" {% if method=="rpcpasswordaspin" %} selected="selected"{% else %}{% if specter.rpc is none or specter.node.autodetect %}disabled{% endif %}{% endif %}>{{ _("RPC password as Pin") }}</option>
|
||||
</select>
|
||||
<br><br>
|
||||
{% endif %}
|
||||
<div id="ratelimit" class="{% if method == 'none' or not current_user.is_admin %}hidden{% endif %}">
|
||||
Rate Limiting (seconds between login/register attempts):<br><input id="rate_limit" type="number" name="rate_limit" min="0" step="1" value="{{ rate_limit }}"><br><br>
|
||||
{{ _("Rate Limiting (seconds between login/register attempts)") }}:<br><input id="rate_limit" type="number" name="rate_limit" min="0" step="1" value="{{ rate_limit }}"><br><br>
|
||||
</div>
|
||||
<div id="passwordonly" class="{% if method != 'passwordonly' %}hidden{% endif %}">
|
||||
Specter Password:<br><input id="specter_password_only" type="password" name="specter_password_only" placeholder="Set new password">
|
||||
<br><span class="note">(default: admin)</span><br>
|
||||
{{ _("Specter Password") }}:<br><input id="specter_password_only" type="password" name="specter_password_only" placeholder="Set new password">
|
||||
<br><span class="note">({{ _("default") }}: admin)</span><br>
|
||||
<br>
|
||||
</div>
|
||||
<div id="usernamepassword" class="{% if method != 'usernamepassword' %}hidden{% endif %}">
|
||||
{% if current_user.is_admin %}
|
||||
Registration Link Timeout (hours):<br><input id="registration_link_timeout" type="number" name="registration_link_timeout" min="0" step="1" value="{{ registration_link_timeout }}"><br><br>
|
||||
<span class="note">Default is username: admin, password: admin.</span><br>
|
||||
{{ _("Registration Link Timeout (hours)") }}:<br><input id="registration_link_timeout" type="number" name="registration_link_timeout" min="0" step="1" value="{{ registration_link_timeout }}"><br><br>
|
||||
<span class="note">{{ _("Default username is 'admin', password 'admin'.") }}</span><br>
|
||||
{% endif %}
|
||||
Specter Username:<br><input id="specter_username" type="text" name="specter_username" type="text" value="{{ current_user.username }}"><br><br>
|
||||
Specter Password:<br><input id="specter_password" type="password" name="specter_password" type="text" placeholder="Set new password"><br>
|
||||
{{ _("Specter Username") }}:<br><input id="specter_username" type="text" name="specter_username" type="text" value="{{ current_user.username }}"><br><br>
|
||||
{{ _("Specter Password") }}:<br><input id="specter_password" type="password" name="specter_password" type="text" placeholder="Set new password"><br>
|
||||
<br><br>
|
||||
{% if users %}
|
||||
<h2>Users:</h2>
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
<form action="?" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="row break-row-mobile" style="margin: 15px; border: 1px solid var(--cmap-border); border-radius: 4px; padding: 10px;">
|
||||
<span style="line-height: 35px; vertical-align: middle;width: 100%;"><b>Username:</b> {{ user.username }}</span>
|
||||
<span style="line-height: 35px; vertical-align: middle;width: 100%;"><b>{{ _("Username") }}:</b> {{ user.username }}</span>
|
||||
<br class="mobile-only">
|
||||
<button type="submit" name="action" value="deleteuser" style="float: right; max-width: 250px;" class="btn danger">Delete User</button>
|
||||
<button type="submit" name="action" value="deleteuser" style="float: right; max-width: 250px;" class="btn danger">{{ _("Delete User") }}</button>
|
||||
<input type="hidden" name="deleteuser" value="{{ user.id }}">
|
||||
<br class="mobile-only">
|
||||
<br class="mobile-only">
|
||||
|
|
@ -50,12 +50,12 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn" name="action" value="save">Save</button>
|
||||
<button type="submit" class="btn" name="action" value="save">{{ _("Save") }}</button>
|
||||
</div>
|
||||
{% if method == "usernamepassword" and current_user.is_admin %}
|
||||
<br>
|
||||
<div id="generateregistrationlink" class="row{% if method != 'usernamepassword' %} hidden{% endif %}">
|
||||
<button type="submit" class="btn" name="action" value="adduser">Generate New Registration Link</button>
|
||||
<button type="submit" class="btn" name="action" value="adduser">{{ _("Generate New Registration Link") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
#}
|
||||
{% macro settings_menu(active_menuitem, current_user) -%}
|
||||
<nav class="row collapse-on-mobile">
|
||||
{{ settings_menu_item('general', 'General', active_menuitem, isLeft=true) }}
|
||||
{{ settings_menu_item('auth', 'Authentication', active_menuitem, isRight=false) }}
|
||||
{{ settings_menu_item('hwi', 'USB Devices', active_menuitem, isRight=(not current_user.is_admin)) }}
|
||||
{{ settings_menu_item('general', _('General'), active_menuitem, isLeft=true) }}
|
||||
{{ settings_menu_item('auth', _('Authentication'), active_menuitem, isRight=false) }}
|
||||
{{ settings_menu_item('hwi', _('USB Devices'), active_menuitem, isRight=(not current_user.is_admin)) }}
|
||||
{% if current_user.is_admin %}
|
||||
{{ settings_menu_item('tor', 'Tor', active_menuitem, isRight=true) }}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -6,75 +6,78 @@
|
|||
{% from 'settings/components/settings_menu.jinja' import settings_menu %}
|
||||
{{ settings_menu('general', current_user) }}
|
||||
<div class="card" style="margin: 20px auto;">
|
||||
<h1> Backup and Restore </h1>
|
||||
|
||||
<h1>{{ _("Language") }}</h1>
|
||||
{% include "includes/language/language_select.jinja" %}<br/>
|
||||
<br><br>
|
||||
|
||||
<h1>{{ _("Backup and Restore") }}</h1>
|
||||
<div class="tool-tip" style="float: right; margin-bottom: 5px;">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Backing up Your Specter Data<br><br></span>
|
||||
It is recommended that you keep your backup files private, as sharing them will result in privacy leaks.<br><br>
|
||||
Backups are recommended to ensure that access to funds remain, even in the event that a multisig wallet is lost.
|
||||
<span class="info__title">{{ _("Backing up Your Specter Data") }}<br><br></span>
|
||||
{{ _("It is recommended that you keep your backup files private, as sharing them will result in privacy leaks.") }}<br><br>
|
||||
{{ _("Backups are recommended to ensure that access to funds remain, even in the event that a multisig wallet is lost.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
Specter Data Backup:
|
||||
{{ _("Specter Data Backup") }}:
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<a href="{{ url_for('settings_endpoint.backup_file') }}" class="btn" style="width: 100%; margin-top: -5px;">Download Specter backup files</a>
|
||||
<a href="{{ url_for('settings_endpoint.backup_file') }}" class="btn" style="width: 100%; margin-top: -5px;">{{ _("Download Specter backup files") }}</a>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tool-tip" style="float: right; margin-bottom: 5px;">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Restoring Specter from backup<br><br></span>
|
||||
Here you can restore your wallets and devices from an existing Specter backup.<br>
|
||||
Please make sure to unzip the backup file first, then select the extracted folder for upload.<br>
|
||||
Note: Loading devices/ wallets with names identical to existing ones may overwrite the existing ones.
|
||||
<span class="info__title">{{ _("Restoring Specter from backup") }}<br><br></span>
|
||||
{{ _("Here you can restore your wallets and devices from an existing Specter backup.") }}<br>
|
||||
{{ _("Please make sure to unzip the backup file first, then select the extracted folder for upload.") }}<br>
|
||||
{{ _("Note: Loading devices/ wallets with names identical to existing ones may overwrite the existing ones.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<span style="margin: auto 5px auto 0px;">Load Specter backup:</span><br><br>
|
||||
<span style="margin: auto 5px auto 0px;">{{ _("Load Specter backup") }}:</span><br><br>
|
||||
<div class="row">
|
||||
<input type="file" id="file" class="inputfile" webkitdirectory mozdirectory/>
|
||||
<input type="hidden" id="restorewallets" name="restorewallets"/>
|
||||
<input type="hidden" id="restoredevices" name="restoredevices"/>
|
||||
<label for="file" class="btn" style="margin-top: -5px;">
|
||||
Choose backup folder
|
||||
{{ _("Choose backup folder") }}
|
||||
</label>
|
||||
</div>
|
||||
<span id="filesloaded"></span>
|
||||
<div class="row" style="margin-top: 5px;">
|
||||
<button onsubmit="showPacman()" type="submit" class="btn hidden" name="action" value="restore" id="restore" style="margin-bottom: 5px;">Load Specter backup</button>
|
||||
<button onsubmit="showPacman()" type="submit" class="btn hidden" name="action" value="restore" id="restore" style="margin-bottom: 5px;">{{ _("Load Specter backup") }}</button>
|
||||
</div><br><br>
|
||||
<h1> Miscellaneous </h1>
|
||||
Bitcoin unit to use (BTC/sats):
|
||||
|
||||
<h1>{{ _("Miscellaneous") }} </h1>
|
||||
{{ _("Bitcoin unit to use") }} (BTC/sats):
|
||||
<select name="unit">
|
||||
<option value="btc" {% if unit!="sat" %} selected="selected"{% endif %}>BTC</option>
|
||||
<option value="sat" {% if unit=="sat" %} selected="selected"{% endif %}>sats</option>
|
||||
</select>
|
||||
<br><br>
|
||||
Block explorer URL ({{ specter.chain }}):<br>
|
||||
{{ _("Block explorer URL") }} ({{ specter.chain }}):<br>
|
||||
{% include "components/explorer_select.jinja" %}
|
||||
<div class="warning">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>
|
||||
Specter does not use the block explorer to get any data whatsoever.
|
||||
This setting is only to allow opening transactions and addresses in a block explorer directly from Specter.
|
||||
All data Specter uses comes directly from your own connected full node.
|
||||
{{ _("Specter does not use the block explorer to get any data whatsoever. This setting is only to allow opening transactions and addresses in a block explorer directly from Specter. All data Specter uses comes directly from your own connected full node.") }}
|
||||
</div>
|
||||
<br>
|
||||
Fees estimation source:<br>
|
||||
{{ _("Fees estimation source") }}:<br>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="fee_estimator" value="mempool" onclick="updateFeeEstimator()" {% if specter.fee_estimator=="mempool" %}checked{% endif %}>Mempool.space</label><br>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px" name="fee_estimator" value="bitcoin_core" onclick="updateFeeEstimator()" {% if specter.fee_estimator=="bitcoin_core" %}checked{% endif %}>Bitcoin Core</label><br>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="fee_estimator" value="custom" onclick="updateFeeEstimator()" id="fee_estimator_custom_option" {% if specter.fee_estimator=="custom" %}checked{% endif %}>Self hosted mempool.space</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="fee_estimator" value="custom" onclick="updateFeeEstimator()" id="fee_estimator_custom_option" {% if specter.fee_estimator=="custom" %}checked{% endif %}>{{ _("Self hosted mempool.space") }}</label>
|
||||
<br>
|
||||
<input type="text" name="fee_estimator_custom_url" id="fee_estimator_custom_url" value="{{ fee_estimator_custom_url }}" class="hidden" placeholder="http://yourmempool.space/" style="margin-top: 15px;"/>
|
||||
<br><br>
|
||||
{% if current_user.is_admin %}
|
||||
Log Level:
|
||||
{{ _("Log Level") }}:
|
||||
<tool-tip title="Logging" style="float: right;">
|
||||
Specter-Desktop created a Logfile in <code>{{ config.SPECTER_LOGFILE}}</code>. The Debug-Level will
|
||||
create a lot of data, so make sure to avoid that if you don't have a reason.
|
||||
{{ _("Specter-Desktop created a Logfile in") }} <code>{{ config.SPECTER_LOGFILE}}</code>. {{ _("The Debug-Level will create a lot of data, so make sure to avoid that if you don't have a reason.") }}
|
||||
</tool-tip>
|
||||
<br>
|
||||
<select name="loglevel">
|
||||
|
|
@ -85,30 +88,31 @@
|
|||
<br><br>
|
||||
{% endif %}
|
||||
|
||||
Validate Merkle Proofs:
|
||||
{{ _("Validate Merkle Proofs") }}:
|
||||
<div class="note">
|
||||
Cannot enable when using a pruned bitcoin node
|
||||
{{ _("Cannot enable when using a pruned bitcoin node") }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="validatemerkleproof" name="validatemerkleproof" {% if validate_merkle_proofs %}checked{% endif %}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<span class="note explorer-link" style="text-decoration: underline;" onclick="toggleHidden('merkle-info-details');">What's this?</span>
|
||||
<span class="note explorer-link" style="text-decoration: underline;" onclick="toggleHidden('merkle-info-details');">{{ _("What's this?") }}</span>
|
||||
</div>
|
||||
<div class="warning hidden" id="merkle-info-details">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>
|
||||
Specter-Desktop validates <a href="https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki" target="_blank">BIP 37</a> Merkle Proofs from your full node <strong>guaranteeing</strong> that transactions displayed as confirmed have been included in the specified block hash.<br><br>
|
||||
However, a compromised full node could return a fake block that was never mined into Bitcoin's blockchain!
|
||||
If you find the displayed block hash in other locations (other nodes, block explorers, etc) <strong>and you trust specter-desktop isn't lying to you</strong>, then you can be sure this transaction exists in their blockchain as well.
|
||||
{{ _('Specter-Desktop validates <a href="https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki" target="_blank">BIP 37</a> Merkle Proofs from your full node <strong>guaranteeing</strong> that displayed as confirmed have been included in the specified block hash.') }}<br><br>
|
||||
{{ _("However, a compromised full node could return a fake block that was never mined into Bitcoin's blockchain! If you find the displayed block hash in other locations (other nodes, block explorers, etc) <strong>and you trust specter-desktop isn't lying to you</strong>, then you can be sure this transaction exists in their blockchain as well.") }}
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn" name="action" value="save">Save</button>
|
||||
<button type="submit" class="btn" name="action" value="save">{{ _("Save") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
function toggleHidden(elementId) {
|
||||
|
|
@ -164,5 +168,8 @@
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
{% include "includes/language/language_js.jinja" %}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -7,27 +7,27 @@
|
|||
}
|
||||
</style>
|
||||
{% include "includes/hwi/hwi.jinja" %}
|
||||
<h1 id="title" class="settings-title">Settings</h1>
|
||||
<h1 id="title" class="settings-title">{{ _("Settings") }}</h1>
|
||||
{% from 'settings/components/settings_menu.jinja' import settings_menu %}
|
||||
{{ settings_menu('hwi', current_user) }}
|
||||
<br>
|
||||
<h1>Hardware Devices Bridge</h1>
|
||||
<p class="note center">Allows Specter to communicate with your hardware devices over USB.</p>
|
||||
<h1>{{ _("Hardware Devices Bridge") }}</h1>
|
||||
<p class="note center">{{ _("Allows Specter to communicate with your hardware devices over USB.") }}</p>
|
||||
<br>
|
||||
<label class="card radio-btn" style="padding: 20px 10px 5px;">
|
||||
<div class="row">
|
||||
<input type="radio" name="hwibridge" value="local" style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="switchHWIBridgeConfView(this.value)" {% if specter.hwi_bridge_url == '/hwi/api/'%} checked {% endif %}/>
|
||||
<h2 style="text-align: left; margin-left: 10px;">Local USB connection</h2>
|
||||
<h2 style="text-align: left; margin-left: 10px;">{{ _("Local USB connection") }}</h2>
|
||||
</div>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: 0px;">Select this if Specter is installed on this compter</p>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: 0px;">{{ _("Select this if Specter is installed on this computer") }}</p>
|
||||
</label>
|
||||
<br>
|
||||
<label class="card radio-btn" style="padding: 20px 10px 5px;">
|
||||
<div class="row">
|
||||
<input type="radio" name="hwibridge" value="remote" style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="switchHWIBridgeConfView(this.value)" {% if specter.hwi_bridge_url == 'http://127.0.0.1:25441/hwi/api/' %} checked {% endif %}/>
|
||||
<h2 style="text-align: left; margin-left: 10px;">Remote Specter USB connection</h2>
|
||||
<h2 style="text-align: left; margin-left: 10px;">{{ _("Remote Specter USB connection") }}</h2>
|
||||
</div>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: 0px;">Select this if Specter is installed on a different machine</p>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: 0px;">{{ _("Select this if Specter is installed on a different machine") }}</p>
|
||||
</label>
|
||||
<br>
|
||||
<label class="card radio-btn" style="padding: 20px 10px 5px;">
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
<input type="radio" id="custom_hwibridge" name="hwibridge" value="custom" style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="switchHWIBridgeConfView(this.value)" {% if specter.hwi_bridge_url != 'http://127.0.0.1:25441/hwi/api/' and specter.hwi_bridge_url != '/hwi/api/' %} checked {% endif %}/>
|
||||
<h2 style="text-align: left; margin-left: 10px;">Custom URL</h2>
|
||||
</div>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: 0px;">Select this if you would like to configure manually (experts only)</p>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: 0px;">{{ _("Select this if you would like to configure manually (experts only)") }}</p>
|
||||
<div style="margin: 20px;">
|
||||
<input type="text" id="custom_url" value="{{ specter.hwi_bridge_url }}" oninput="if(document.getElementById('custom_hwibridge').checked){document.getElementById('hwi_bridge_url').value=this.value}" />
|
||||
</div>
|
||||
|
|
@ -72,22 +72,22 @@
|
|||
<div class="card" style="padding: 5px 10px 15px;">
|
||||
<div class="row">
|
||||
<img src="{{ url_for('static', filename='img/warning_sign.svg') }}" style="width: 20px;"/>
|
||||
<p style="color: #FF9A00; font-size: 1.1em; margin-left: 10px;">Before changing these settings:</p>
|
||||
<p style="color: #FF9A00; font-size: 1.1em; margin-left: 10px;">{{ _("Before changing these settings") }}:</p>
|
||||
</div>
|
||||
<p style="margin-left: 30px; margin-top: -10px;">If you haven’t done so already, head to “Preferences” on your desktop app (found on the top navigation). Select the option to connect remotely.</p>
|
||||
<p style="margin-left: 30px; margin-top: -10px;">{{ _('If you haven’t done so already, head to "Preferences" on your desktop app (found on the top navigation). Select the option to connect remotely.') }}</p>
|
||||
<img src="{{ url_for('static', filename='img/preferences_tab_screenshot.png')}}" width="400px;" style="margin: auto; border: 0px solid #000; display: block;"/>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" class="btn" value="update_hwi" style="width: 580px;">Save Changes</button>
|
||||
<button type="submit" class="btn" value="update_hwi" style="width: 580px;">{{ _("Save Changes") }}</button>
|
||||
</form>
|
||||
<br>
|
||||
<p style="text-align: left; width: 580px;">You might also need to allow Specter to communicate with the device bridge.<br></p>
|
||||
<p style="text-align: left; width: 580px;">{{ _("You might also need to allow Specter to communicate with the device bridge.") }}<br></p>
|
||||
<ol style="text-align: left; width: 580px;line-height:2;">
|
||||
<li>Access the device bridge settings. <a href='{{specter.hwi_bridge_url.replace("/hwi/api", "/hwi/settings")}}' target="_blank" style="color: #fff;">Click here</a>.</li>
|
||||
<li>Add the domain {{ url_for('index', _external=True) }} <span style="text-decoration: underline; cursor: pointer;" onclick="copyText('{{ url_for('index', _external=True) }}', 'Copied Specter URL')">(copy)</span> to the whitelist there.<br>Save the changes and close the window.</li>
|
||||
<li>Connect your hardware wallet to your computer and test if Specter manage to reach it<br><button class="btn" type="button" style="display: inline; width: 140px; margin-top:5px;" onclick="testHWIConnection()">Test connection</button></li>
|
||||
<li>{{ _("Access the device bridge settings.") }} <a href='{{specter.hwi_bridge_url.replace("/hwi/api", "/hwi/settings")}}' target="_blank" style="color: #fff;">Click here</a>.</li>
|
||||
<li>{{ _("Add the domain") }} {{ url_for('index', _external=True) }} <span style="text-decoration: underline; cursor: pointer;" onclick="copyText('{{ url_for('index', _external=True) }}', '{{ _("Copied Specter URL") }}')">({{ _("copy") }}</span> {{ _("to the whitelist there.") }}<br>{{ _("Save the changes and close the window.") }}</li>
|
||||
<li>{{ _("Connect your hardware wallet to your computer and test if Specter manage to reach it") }}<br><button class="btn" type="button" style="display: inline; width: 140px; margin-top:5px;" onclick="testHWIConnection()">{{ _("Test connection") }}</button></li>
|
||||
</ol>
|
||||
|
||||
<div style="height: 100%"></div>
|
||||
|
|
@ -95,9 +95,9 @@
|
|||
<script>
|
||||
async function testHWIConnection() {
|
||||
if (await enumerate()) {
|
||||
showNotification('Device detected successfully! USB connections configured correctly!');
|
||||
showNotification('{{ _("Device detected successfully! USB connections configured correctly!") }}');
|
||||
} else {
|
||||
showError('Failed to detect devices');
|
||||
showError('{{ _("Failed to detect devices") }}');
|
||||
}
|
||||
}
|
||||
function switchHWIBridgeConfView(viewType) {
|
||||
|
|
|
|||
|
|
@ -26,79 +26,79 @@
|
|||
<br>
|
||||
<h1>
|
||||
<img style="width: 25px; margin-right: 7px; vertical-align: middle;" src="{{ url_for('static', filename='img/tor.svg') }}"/><span style="vertical-align: bottom; margin-right: 10px;">
|
||||
Tor configurations
|
||||
{{ _("Tor configurations") }}
|
||||
</h1>
|
||||
<p class="note center">Enable Tor to anonymise your data connections.</p>
|
||||
<p class="note center">{{ _("Enable Tor to anonymise your data connections.") }}</p>
|
||||
<br>
|
||||
<label for="tor_type_builtin" class="card radio-btn" style="padding: 20px 10px 5px; display: block; margin: auto;">
|
||||
{% if torbrowser_installed %}
|
||||
{% if torbrowser_running %}
|
||||
<span style="display: inline; float: right;margin-top: 10px; margin-right: 10px;">
|
||||
<span class="note">Running <img src="{{ url_for('static', filename='img/check.svg') }}" class="svg-check-green" style="vertical-align: middle;"/> </span>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="stoptor">Stop</button>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="test_tor">Test</button>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="stoptor">{{ _("Stop") }}</button>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="test_tor">{{ _("Test") }}</button>
|
||||
</span>
|
||||
{% else %}
|
||||
<span style="display: inline; float: right; margin-top: 10px; margin-right: 10px;">
|
||||
<span class="note">Stopped </span>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="uninstalltor">Uninstall</button>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="starttor">Start</button>
|
||||
<span class="note">{{ _("Stopped") }} </span>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="uninstalltor">{{ _("Uninstall") }}</button>
|
||||
<button style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="starttor">{{ _("Start") }}</button>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a style="display: inline; float: right; width: 110px; margin-top: 10px; margin-right: 10px;" href="{{ url_for('setup_endpoint.tor_from_settings' ) }}" class="btn centered action" id="setup-tor-button">Set up</a>
|
||||
<a style="display: inline; float: right; width: 110px; margin-top: 10px; margin-right: 10px;" href="{{ url_for('setup_endpoint.tor_from_settings' ) }}" class="btn centered action" id="setup-tor-button">{{ _("Set up") }}</a>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<input id="tor_type_builtin" type="radio" name="tor_type" value="builtin" style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="toggletor_type('builtin')" {% if tor_type == 'builtin' %} checked {% endif %}/>
|
||||
<h2 style="text-align: left; margin-left: 10px; font-size: 1.1em;">Built in</h2>
|
||||
</div>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: -5px;">Quick and automatic setup.</p>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: -5px;">{{ _("Quick and automatic setup.") }}</p>
|
||||
</label>
|
||||
<br>
|
||||
<label id="custom-tor-label" for="tor_type_custom" class="card radio-btn no-bottom-border-radius" style="padding: 20px 10px 5px; display: block; margin: auto;">
|
||||
<div class="row">
|
||||
<input id="tor_type_custom" type="radio" name="tor_type" value="custom" style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="toggletor_type('custom')" {% if tor_type == 'custom' %} checked {% endif %}/>
|
||||
<h2 style="text-align: left; margin-left: 10px; font-size: 1.1em;">Custom</h2>
|
||||
<h2 style="text-align: left; margin-left: 10px; font-size: 1.1em;">{{ _("Custom") }}</h2>
|
||||
</div>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: -5px;">Use your own Tor configuration.</p>
|
||||
<p class="note" style="text-align: left; margin-left: 38px;margin-top: -5px;">{{ _("Use your own Tor configuration.") }}</p>
|
||||
</label>
|
||||
<div id="custom-tor-details" class="card" style="margin: auto;border-top-left-radius: 0px; border-top-right-radius: 0px; border-top: none;">
|
||||
Use Tor proxy running at:
|
||||
{{ _("Use Tor proxy running at:") }}
|
||||
<input id="proxy-url" name="proxy_url" value="{{ proxy_url }}" type="url" placeholder="i.e. socks5://127.0.0.1:9050" />
|
||||
<br>
|
||||
<p class="note">
|
||||
Default for Tor daemon: socks5://127.0.0.1:9050<br>
|
||||
Default for Tor Browser: socks5://127.0.0.1:9150
|
||||
{{ _("Default for Tor daemon:") }} socks5://127.0.0.1:9050<br>
|
||||
{{ _("Default for Tor Browser:") }} socks5://127.0.0.1:9150
|
||||
</p>
|
||||
<br>
|
||||
Tor control port:
|
||||
{{ _("Tor control port:") }}
|
||||
<p class="note">
|
||||
Restart Specter for change of the control port to take effect.
|
||||
{{ _("Restart Specter for change of the control port to take effect.") }}
|
||||
</p>
|
||||
<input name="tor_control_port" value="{{ tor_control_port }}" type="text" placeholder='Set to custom port here, if kept empty the default (9051 and 9151) is used' />
|
||||
<input name="tor_control_port" value="{{ tor_control_port }}" type="text" placeholder='{{ _("Set to custom port here, if kept empty the default (9051 and 9151) is used") }}' />
|
||||
<br><br>
|
||||
<button type="submit" class="btn" name="action" value="test_tor" style="width: 300px;">Test connection</button>
|
||||
<button type="submit" class="btn" name="action" value="test_tor" style="width: 300px;">{{ _("Test connection") }}</button>
|
||||
</div>
|
||||
<br><br>
|
||||
<h2 style="text-align: center; color: #ccc;">Additional Options</h2>
|
||||
<h2 style="text-align: center; color: #ccc;">{{ _("Additional Options") }}</h2>
|
||||
<div style="width: 580px; margin: auto;">
|
||||
{% if not debug %}
|
||||
<tool-tip title="Tor hidden service" style="float: right; margin-top: 15px;">
|
||||
Running Specter behind a Tor hidden service allows you to acccess Specter over Tor from anywhere via a Tor supporting browser.
|
||||
{{ _("Running Specter behind a Tor hidden service allows you to acccess Specter over Tor from anywhere via a Tor supporting browser.") }}
|
||||
</tool-tip>
|
||||
<label class="switch" style="float: right; margin-top: 15px;">
|
||||
<input type="checkbox" id="hidden_service" name="hidden_service" onchange="checkAuthEabled()"{% if tor_service_id %}checked{% endif %}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<p style="font-size: 1.1em">Enable Tor hidden service</p>
|
||||
<p style="font-size: 1.1em">{{ _("Enable Tor hidden service") }}</p>
|
||||
<p class="note">
|
||||
Allows access to Specter from anywhere.<br>
|
||||
Requires authentication to be enabled. <a style="color: var(--cmap-blue);" href="{{ url_for('settings_endpoint.auth' ) }}">Authentication settings</a>
|
||||
{{ _("Allows access to Specter from anywhere.") }}<br>
|
||||
{{ _('Requires authentication to be enabled.') }} <a style="color: var(--cmap-blue);" href="{{ url_for('settings_endpoint.auth' ) }}">{{ _("Authentication settings") }}</a>
|
||||
</p>
|
||||
{% if tor_service_id %}
|
||||
<p><b>
|
||||
Specter hidden service is running at:
|
||||
<span title="Copy Tor address" style="word-break: break-word;" class="explorer-link" onclick="copyText('http:/\/{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
|
||||
{{ _("Specter hidden service is running at:") }}
|
||||
<span title="Copy Tor address" style="word-break: break-word;" class="explorer-link" onclick="copyText('http:/\/{{ tor_service_id }}.onion', '{{ _("Copied Tor hidden service address:") }} {{ tor_service_id }}.onion')">
|
||||
{{ tor_service_id }}.onion
|
||||
</span>
|
||||
</b></p>
|
||||
|
|
@ -106,20 +106,20 @@
|
|||
{% endif %}
|
||||
<br>
|
||||
<tool-tip title="Tor Only mode" style="float: right; margin-top: 30px;">
|
||||
Some optional Specter functionalities, like rescanning UTXO on a pruned node, or getting the Bitcoin price, might make calls to external APIs and services.<br><br>
|
||||
Toggle this on to ensure Specter routes all these external calls over Tor proxy.<br><br>
|
||||
Note: Some external sources may stop working if they block Tor call.
|
||||
{{ _("Some optional Specter functionality, like rescanning UTXO on a pruned node or getting the Bitcoin price, might make calls to external APIs and services.") }}<br><br>
|
||||
{{ _("Toggle this on to ensure Specter routes all these external calls over Tor proxy.") }}<br><br>
|
||||
{{ _("Note: Some external sources may stop working if they block Tor call.") }}
|
||||
</tool-tip>
|
||||
<label class="switch" style="float: right; margin-top: 30px;">
|
||||
<input type="checkbox" id="only-tor" name="only_tor" {% if only_tor %}checked{% endif %}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<p style="font-size: 1.1em">Force external calls over Tor?</p>
|
||||
<p style="font-size: 1.1em">{{ _("Force external calls over Tor?") }}</p>
|
||||
<p class="note">
|
||||
Route all external calls over the Tor proxy for improved privacy.
|
||||
{{ _("Route all external calls over the Tor proxy for improved privacy.") }}
|
||||
</p>
|
||||
<br><br>
|
||||
<button type="submit" class="btn" name="action" value="save" style="width: 100%;">Save changes</button>
|
||||
<button type="submit" class="btn" name="action" value="save" style="width: 100%;">{{ _("Save changes") }}</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
|
@ -139,7 +139,7 @@
|
|||
function checkAuthEabled() {
|
||||
{% if specter.config.auth.method == "none" %}
|
||||
document.getElementById('hidden_service').checked = false;
|
||||
showError('Please set an authentication method before enabling the hidden service')
|
||||
showError('{{ _("Please set an authentication method before enabling the hidden service") }}')
|
||||
{% else %}
|
||||
return true;
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
{% block setup %}
|
||||
<h1><img src="{{ url_for('static', filename='img/icon.png') }}" style="width: 60px;"/> <img src="{{ url_for('static', filename='img/arrow-right.svg') }}" style="width: 60px;" class="svg-white"/> {{ bitcoin_svg('main', 60) }}<br>
|
||||
<br>Setup Bitcoin Core</h1><br>
|
||||
<br>{{ _("Setup Bitcoin Core") }}</h1><br>
|
||||
<div class="row">
|
||||
<button type="button" class="btn wizard-btn action" id="setup-bitcoind-button" onclick="setupBitcoind()">Start the Setup!</button>
|
||||
<button type="button" class="btn wizard-btn action" id="setup-bitcoind-button" onclick="setupBitcoind()">{{ _("Start the Setup!") }}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
}
|
||||
showError(jsonResponse.error, 4000);
|
||||
} catch(e) {
|
||||
showError("Failed to download Bitcoin Core...");
|
||||
showError('{{ _("Failed to download Bitcoin Core...") }}');
|
||||
showError(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,108 +2,107 @@
|
|||
|
||||
{% block setup %}
|
||||
<h1><img src="{{ url_for('static', filename='img/icon.png') }}" style="width: 60px;"/> <img src="{{ url_for('static', filename='img/arrow-right.svg') }}" style="width: 60px;" class="svg-white"/> {{ bitcoin_svg(network, 60) }}<br>
|
||||
<br>Configure your node</h1>
|
||||
<p id="quicksync-option" {% if network != 'main' %}class="hidden"{% endif %}>QuickSync?
|
||||
<br>{{ _("Configure your node") }}</h1>
|
||||
<p id="quicksync-option" {% if network != 'main' %}class="hidden"{% endif %}>{{ _("QuickSync?") }}
|
||||
<label class="switch" id="quicksync-switch">
|
||||
<input type="checkbox" name="quicksync" id="quicksync" onchange="toggleNodeTypeSelect()" checked>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<tool-tip title="What is QuickSync?">
|
||||
QuickSync will set up your new node from a trusted state of the blockchain.<br><br>
|
||||
This means all block validation will be skipped until the most recent block in the snapshot.<br><br>
|
||||
Later blocks will be normally validated like with a regular full node.
|
||||
<tool-tip title='{{ _("What is QuickSync?") }}'>
|
||||
{{ _("QuickSync will set up your new node from a trusted state of the blockchain.") }}<br><br>
|
||||
{{ _("This means all block validation will be skipped until the most recent block in the snapshot.") }}<br><br>
|
||||
{{ _("Later blocks will be normally validated like with a regular full node.") }}
|
||||
</tool-tip>
|
||||
</p>
|
||||
<div {% if network == 'main' %}class="hidden"{% endif %} id="select-node-type-div">
|
||||
<br>
|
||||
<h1>Select node type:</h1>
|
||||
<h1>{{ _("Select node type:") }}</h1>
|
||||
<div class="row">
|
||||
<label style="position: relative;">
|
||||
<input type="radio" name="node-type" id="node-type-archival" class="hidden" value="archival" {% if network != 'main' %}checked{% endif %}>
|
||||
<div class="small-card radio">
|
||||
<span style="font-size: 1.2em; font-weight: bold;">Archival Node</span><br>
|
||||
<span style="font-size: 1.2em; font-weight: bold;">{{ _("Archival Node") }}</span><br>
|
||||
{% if network == 'main' %}
|
||||
Requires about 400GB of available storage
|
||||
{{ _("Requires about 400GB of available storage") }}<br><br>
|
||||
{% elif network == 'testnet' %}
|
||||
Requires about 30GB of available storage<br><br>
|
||||
{{ _("Requires about 30GB of available storage") }}<br><br>
|
||||
{% else %}
|
||||
Requires a few MB of available storage<br><br>
|
||||
{{ _("Requires a few MB of available storage") }}<br><br>
|
||||
{% endif %}
|
||||
</div>
|
||||
</label>
|
||||
<label style="position: relative;">
|
||||
<input type="radio" name="node-type" class="hidden" value="pruned" {% if network == 'main' %}checked{% endif %}>
|
||||
<div class="small-card radio">
|
||||
<span style="font-size: 1.2em; font-weight: bold;">Pruned Node</span><br>
|
||||
<span style="font-size: 1.2em; font-weight: bold;">{{ _("Pruned Node") }}</span><br>
|
||||
{% if network == 'main' %}
|
||||
Requires about 10GB of available storage<br><br>
|
||||
{{ _("Requires about 10GB of available storage") }}<br><br>
|
||||
{% elif network == 'testnet' %}
|
||||
Requires about 3GB of available storage<br><br>
|
||||
{{ _("Requires about 3GB of available storage") }}<br><br>
|
||||
{% else %}
|
||||
Requires a few MB of available storage<br><br>
|
||||
{{ _("Requires a few MB of available storage") }}<br><br>
|
||||
{% endif %}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hidden warning" id="helper-text">
|
||||
Now that you have Bitcoin Core installed, your next step will be to sync with the network.<br><br>
|
||||
Specter allows you to choose between normal syncing (IBD) - which will download and validate the entire history of the blockchain, or downloading a trusted snapshot of the current state of the network.<br><br>
|
||||
This snapshot is produced and signed by Specter team - which means you will be trusting it to get the present state of the blockchain, instead of verifying the history yourself.<br><br>
|
||||
It takes about 5GB to download and will setup a fully synced pruned node for you to use.
|
||||
You can also disable the QuickSync option, and choose between setting up an archival node (which keeps the entire history) or a pruned node.<br><br>
|
||||
<b>Please note: it is recommended to use a full archival node, or if storage is a constraint, a pruned node synced from scratch. The option for QuickSync is useful for getting started quickly, but is not recommended in the long term or when using Specter with large amounts.</b>
|
||||
{{ _("Now that you have Bitcoin Core installed, your next step will be to sync with the network.") }}<br><br>
|
||||
{{ _("Specter allows you to choose between normal syncing (IBD) - which will download and validate the entire history of the blockchain, or downloading a trusted snapshot of the current state of the network.") }}<br><br>
|
||||
{{ _("This snapshot is produced and signed by Specter team - which means you will be trusting it to get the present state of the blockchain, instead of verifying the history yourself.") }}<br><br>
|
||||
{{ _("It takes about 5GB to download and will setup a fully synced pruned node for you to use.") }}<br><br>
|
||||
{{ _("You can also disable the QuickSync option, and choose between setting up an archival node (which keeps the entire history) or a pruned node.") }}<br><br>
|
||||
<b>{{ _("Please note: it is recommended to use a full archival node, or if storage is a constraint, a pruned node synced from scratch. The option for QuickSync is useful for getting started quickly, but is not recommended in the long term or when using Specter with large amounts.") }}</b>
|
||||
</p>
|
||||
<div class="row center">
|
||||
<p class="explorer-link" id="helper" onclick="toggleHelper()">Unsure? Click here for more info!</p>
|
||||
<a class="explorer-link hidden" id="support" href="https://t.me/spectersupport" target="_blank">Still need help?</a>
|
||||
<p class="explorer-link" id="helper" onclick="toggleHelper()">{{ _("Unsure? Click here for more info!") }}</p>
|
||||
<a class="explorer-link hidden" id="support" href="https://t.me/spectersupport" target="_blank">{{ _("Still need help?") }}</a>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
{% if specter.only_tor %}
|
||||
You have activated to only connect via Tor. This will significantly slow down the download-process and is not recommended.
|
||||
<br>
|
||||
{{ _("You have activated to only connect via Tor. This will significantly slow down the download-process and is not recommended.") }}
|
||||
<br>
|
||||
{% endif %}
|
||||
<span id="toggle_advanced" style="cursor: pointer;" onclick="toggleAdvanced()">Advanced ▶</span>
|
||||
<span id="toggle_advanced" style="cursor: pointer;" onclick="toggleAdvanced()">{{ _("Advanced") }} ▶</span>
|
||||
<br>
|
||||
<br>
|
||||
<div id="advanced_settings" class="card hidden">
|
||||
<h2 style="text-decoration: underline;">Advanced configurations</h2><br>
|
||||
Node Network: <button id="select-network-btn" type="button" onclick="showPageOverlay('switch-network-popup')" class="btn centered">{{ network | title }} (switch)</button>
|
||||
<h2 style="text-decoration: underline;">{{ _("Advanced configurations") }}</h2><br>
|
||||
{{ _("Node Network:") }} <button id="select-network-btn" type="button" onclick="showPageOverlay('switch-network-popup')" class="btn centered">{{ network | title }} {{ _("(switch)") }}</button>
|
||||
<p style="text-align: left;">
|
||||
Bitcoin Core data directory path:<br>
|
||||
<input type="text" id="bitcoin_core_datadir" name="bitcoin_core_datadir" type="text" value="" placeholder="default">
|
||||
{{ _("Bitcoin Core data directory path:") }}<br>
|
||||
<input type="text" id="bitcoin_core_datadir" name="bitcoin_core_datadir" type="text" value="" placeholder='{{ _("default") }}'>
|
||||
<br><br>
|
||||
</p>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<button type="button" class="btn wizard-btn action" id="setup-bitcoind-dir-button" onclick="setupBitcoindDirectory()">Start Syncing!</button>
|
||||
<button type="button" class="btn wizard-btn action" id="setup-bitcoind-dir-button" onclick="setupBitcoindDirectory()">{{ _("Start Syncing!") }}</button>
|
||||
</div>
|
||||
<div id="override-data-folder-popup" class="hidden" style="margin: auto;">
|
||||
<p>Your Bitcoin Core data folder seems to be already used.<br>
|
||||
Would you like to override it or select a different folder path?
|
||||
<p>{{ _("Your Bitcoin Core data folder seems to be already used.<br>Would you like to override it or select a different folder path?") }}
|
||||
</p><br>
|
||||
<div class="row">
|
||||
<button type="button" class="btn" onclick="hidePageOverlay()">Edit path</button>
|
||||
<button type="button" class="btn" onclick="hidePageOverlay();setupBitcoindDirectory(true)">Override existing</button>
|
||||
<button type="button" class="btn" onclick="hidePageOverlay()">{{ _("Edit path") }}</button>
|
||||
<button type="button" class="btn" onclick="hidePageOverlay();setupBitcoindDirectory(true)">{{ _("Override existing") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="switch-network-popup" class="hidden" style="margin: auto;">
|
||||
<h1> Select Network to setup </h1>
|
||||
<h1> {{ _("Select Network to setup") }} </h1>
|
||||
<div class="row overflow">
|
||||
<a href="{{ url_for('setup_endpoint.bitcoind_datadir', network='main') }}" style="color: #fff; text-decoration: none;">
|
||||
<div class="small-card radio" style="min-width: 150px;">
|
||||
{{ bitcoin_svg('main', 40) }}
|
||||
<br>
|
||||
Mainnet
|
||||
{{ _("Mainnet") }}
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ url_for('setup_endpoint.bitcoind_datadir', network='testnet') }}" style="color: #fff; text-decoration: none;">
|
||||
<div class="small-card radio" style="min-width: 150px;">
|
||||
{{ bitcoin_svg('test', 40) }}
|
||||
<br>
|
||||
Testnet
|
||||
{{ _("Testnet") }}
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ url_for('setup_endpoint.bitcoind_datadir', network='signet') }}" style="color: #fff; text-decoration: none;">
|
||||
|
|
@ -180,7 +179,7 @@
|
|||
}
|
||||
showError(jsonResponse.error, 4000);
|
||||
} catch(e) {
|
||||
showError("Failed to start Bitcoin Core...");
|
||||
showError('{{ _("Failed to start Bitcoin Core...") }}';
|
||||
showError(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% block setup %}
|
||||
<img style="width: 100px;" src="{{ url_for('static', filename='img/party.png') }}"/><br><br>
|
||||
<h1 style="font-size: 1.8em;">Setup Completed Successfully!</h1>
|
||||
<p>Your new node is up and running!</p><br>
|
||||
<a href="{{url_for('about')}}" class="btn wizard-btn action" id="finish-setup-btn">Done</a>
|
||||
<h1 style="font-size: 1.8em;">{{ _("Setup Completed Successfully!") }}</h1>
|
||||
<p>{{ _("Your new node is up and running!") }}</p><br>
|
||||
<a href="{{url_for('about')}}" class="btn wizard-btn action" id="finish-setup-btn">{{ _("Done") }}</a>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
{% block setup %}
|
||||
<h1><img src="{{ url_for('static', filename='img/icon.png') }}" style="width: 60px;"/> <img src="{{ url_for('static', filename='img/arrow-right.svg') }}" style="width: 60px;" class="svg-white"/> {{ bitcoin_svg('main', 60) }}<br>
|
||||
<br>Would you like to setup a new Bitcoin node or connect to an existing one?</h1>
|
||||
<p class="hidden warning" id="helper-text">If you have an existing Bitcoin Core node you would like to use, either running locally on this computer or somewhere else, like on Umbrel, myNode, or RaspiBlitz, you can just connect and use it.<br><br>
|
||||
Don't have a node? Specter can help you setup a new one! This guide will help you get it up and running!</p>
|
||||
<br>{{ _("Would you like to setup a new Bitcoin node or connect to an existing one?") }}</h1>
|
||||
<p class="hidden warning" id="helper-text">{{ _("If you have an existing Bitcoin Core node you would like to use, either running locally on this computer or somewhere else, like on Umbrel, myNode, or RaspiBlitz, you can just connect and use it.") }}<br><br>
|
||||
{{ _("Don't have a node? Specter can help you setup a new one! This guide will help you get it up and running!") }}</p>
|
||||
<div class="row center">
|
||||
<p class="explorer-link" id="helper" onclick="toggleHelper()">Unsure? Click here for more info!</p>
|
||||
<a class="explorer-link hidden" id="support" href="https://t.me/spectersupport" target="_blank">Still need help?</a>
|
||||
<p class="explorer-link" id="helper" onclick="toggleHelper()">{{ _("Unsure? Click here for more info!") }}</p>
|
||||
<a class="explorer-link hidden" id="support" href="https://t.me/spectersupport" target="_blank">{{ _("Still need help?") }}</a>
|
||||
</div>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<a href="{{ url_for('nodes_endpoint.node_settings', node_alias=specter.node.alias) if not specter.node.rpc and specter.node.external_node else url_for('nodes_endpoint.node_settings') }}" class="btn wizard-btn">Connect existing node</a>
|
||||
<a href="{{ url_for('setup_endpoint.bitcoind' if not bitcoind_installed else 'setup_endpoint.bitcoind_datadir') }}" class="btn wizard-btn action" id="setup-node-btn">Setup a new node</a>
|
||||
<a href="{{ url_for('nodes_endpoint.node_settings', node_alias=specter.node.alias) if not specter.node.rpc and specter.node.external_node else url_for('nodes_endpoint.node_settings') }}" class="btn wizard-btn">{{ _("Connect existing node") }}</a>
|
||||
<a href="{{ url_for('setup_endpoint.bitcoind' if not bitcoind_installed else 'setup_endpoint.bitcoind_datadir') }}" class="btn wizard-btn action" id="setup-node-btn">{{ _("Setup a new node") }}</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
<div class="row">
|
||||
<form method="POST" action="{{ url_for('about') }}">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" name="action" value="cancelsetup" class="btn hidden danger" id="reset-setup-process" style="max-height: 36px;" onclick="">Cancel process</button>
|
||||
<button type="submit" name="action" value="cancelsetup" class="btn hidden danger" id="reset-setup-process" style="max-height: 36px;" onclick="">{{ _("Cancel process") }}</button>
|
||||
</form>
|
||||
<button type="button" class="btn hidden action" id="show-progress-details" style="max-height: 36px;" onclick="showPageOverlay('progress-details');">View Progress</button>
|
||||
<button type="button" class="btn hidden action" id="show-progress-details" style="max-height: 36px;" onclick="showPageOverlay('progress-details');">{{ _("View Progress") }}</button>
|
||||
</div>
|
||||
<h1 id="progress-details" class="hidden" style="margin: auto;"></h1>
|
||||
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
helper.classList.add("helper-btn");
|
||||
support.classList.add("helper-btn");
|
||||
} else {
|
||||
helper.innerText = "Unsure? Click here for more info!";
|
||||
helper.innerText = '{{ _("Unsure? Click here for more info!") }}';
|
||||
helperText.classList.add("hidden");
|
||||
support.classList.add("hidden");
|
||||
helper.classList.remove("helper-btn");
|
||||
|
|
@ -60,10 +60,10 @@
|
|||
let advancedSettings = document.getElementById(`advanced_settings`);
|
||||
if (advancedSettings.classList.contains("hidden")) {
|
||||
advancedSettings.classList.remove("hidden");
|
||||
advancedButton.innerHTML = 'Advanced ▼';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▼';
|
||||
} else {
|
||||
advancedSettings.classList.add("hidden");
|
||||
advancedButton.innerHTML = 'Advanced ▶';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▶';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
);
|
||||
let result = await response.json();
|
||||
if (result.error) {
|
||||
showError('Encountered an error:' + result.error);
|
||||
showError('{{ _("Encountered an error:") }}' + result.error);
|
||||
document.getElementById('progress-details').classList.add('hidden');
|
||||
hidePageOverlay('progress-details');
|
||||
return;
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
}
|
||||
document.getElementById('progress-details').innerHTML = `${stage}<br>`;
|
||||
if (progress > 0 && progress < 100) {
|
||||
document.getElementById('progress-details').innerHTML += `<br><br><b style="font-size:0.8em;">Download Progress: ${progress}%</b>`;
|
||||
document.getElementById('progress-details').innerHTML += `<br><br><b style="font-size:0.8em;">{{ _("Download Progress:") }}" ${progress}%</b>`;
|
||||
}
|
||||
setTimeout(fetchProgress, 1000);
|
||||
} catch(e) {
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
document.getElementById('show-progress-details').classList.remove('hidden');
|
||||
document.getElementById('reset-setup-process').classList.remove('hidden');
|
||||
showPageOverlay('progress-details');
|
||||
document.getElementById('progress-details').innerHTML = 'Starting up...';
|
||||
document.getElementById('progress-details').innerHTML = '{{ _("Starting up...") }}';
|
||||
|
||||
setTimeout(fetchProgress, 1000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
{% extends "setup/setup_page.jinja" %}
|
||||
|
||||
{% block setup %}
|
||||
<h1>Specter Quickstart!</h1>
|
||||
<h2>Get up and running with Specter in just a few clicks!</h2><br>
|
||||
<h1>{{ _("Specter Quickstart!") }}</h1>
|
||||
<h2>{{ _("Get up and running with Specter in just a few clicks!") }}</h2><br>
|
||||
<img style="width: 50px;" src="{{ url_for('static', filename='img/tor.svg') }}"/>
|
||||
{{ bitcoin_svg('main', 50) }}
|
||||
<img src="{{ url_for('static', filename='img/icon.png') }}" style="width: 50px;"/>
|
||||
<br><br>
|
||||
<p style="max-width: 400px; margin: auto;">
|
||||
Here, we'll take you through the process of setting up Specter with your own Bitcoin Core node and Tor.<br><br>
|
||||
Everything is completely automated, but you make choices to customize your setup to fit your needs.
|
||||
{{ _("Here, we'll take you through the process of setting up Specter with your own Bitcoin Core node and Tor.") }}<br><br>
|
||||
{{ _("Everything is completely automated, but you make choices to customize your setup to fit your needs.") }}
|
||||
</p>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<a href="{{ url_for('setup_endpoint.tor') }}" class="btn wizard-btn action" id="start-setup-btn">Get Started!</a>
|
||||
<a href="{{ url_for('setup_endpoint.tor') }}" class="btn wizard-btn action" id="start-setup-btn">{{ _("Get Started!") }}</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
{% block setup %}
|
||||
<h1><img src="{{ url_for('static', filename='img/icon.png') }}" style="width: 60px;"/> <img src="{{ url_for('static', filename='img/arrow-right.svg') }}" style="width: 60px;" class="svg-white"/> <img style="width: 60px;" src="{{ url_for('static', filename='img/tor.svg') }}"/><br>
|
||||
<br>Setup Tor daemon</h1>
|
||||
<br>{{ _("Setup Tor daemon") }}</h1>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
{% if nextURL == 'setup_endpoint.node_type'%}
|
||||
<a href="{{ url_for('setup_endpoint.node_type') }}" class="btn wizard-btn">Skip</a>
|
||||
<a href="{{ url_for('setup_endpoint.node_type') }}" class="btn wizard-btn">{{ _("Skip") }}</a>
|
||||
{% endif %}
|
||||
<button type="button" class="btn wizard-btn action" id="setup-tor-button" onclick="setupTorDaemon()">Setup Tor</button>
|
||||
<button type="button" class="btn wizard-btn action" id="setup-tor-button" onclick="setupTorDaemon()">{{ _("Setup Tor") }}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
showError(jsonResponse.error, 4000);
|
||||
} catch(e) {
|
||||
showError("Failed to download Tor daemon...");
|
||||
showError('{{ _("Failed to download Tor daemon...") }}');
|
||||
showError(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
#}
|
||||
{% macro editable_label(label, elementId) -%}
|
||||
<input type="text" class="label" autocomplete="off" spellcheck="false" id="{{'label' ~ elementId }}" name="label" value="{{ label }}" style="border: none; font-size: 1.5em;" disabled />
|
||||
<button type="submit" class="btn update hidden" id="{{ 'save' ~ elementId }}" name="action" value="updatelabel">Update</button>
|
||||
<button type="button" class="btn cancel hidden" id="{{ 'cancel' ~ elementId }}" onclick="toggleEdit('{{ elementId }}', '{{ label }}')">Cancel</button>
|
||||
<button type="button" class="btn edit" id="{{ 'edit' ~ elementId }}" onclick="toggleEdit('{{ elementId }}', '{{ label }}')">Edit Label</button>
|
||||
<button type="submit" class="btn update hidden" id="{{ 'save' ~ elementId }}" name="action" value="updatelabel">{{ _("Update") }}</button>
|
||||
<button type="button" class="btn cancel hidden" id="{{ 'cancel' ~ elementId }}" onclick="toggleEdit('{{ elementId }}', '{{ label }}')">{{ _("Cancel") }}</button>
|
||||
<button type="button" class="btn edit" id="{{ 'edit' ~ elementId }}" onclick="toggleEdit('{{ elementId }}', '{{ label }}')">{{ _("Edit Label") }}</button>
|
||||
|
||||
<script>
|
||||
function toggleEdit(id, originalLabel) {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
#}
|
||||
{% macro wallet_menu(active_menuitem, wallet_alias) -%}
|
||||
<nav class="row collapse-on-mobile">
|
||||
{{ wallet_menu_item('history', 'Transactions', wallet_alias, active_menuitem, isLeft=true, id='btn_transactions') }}
|
||||
{{ wallet_menu_item('addresses', 'Addresses', wallet_alias, active_menuitem) }}
|
||||
{{ wallet_menu_item('receive', 'Receive', wallet_alias, active_menuitem, id='btn_receive') }}
|
||||
{{ wallet_menu_item('send', 'Send', wallet_alias, active_menuitem, id='btn_send') }}
|
||||
{{ wallet_menu_item('settings', 'Settings', wallet_alias, active_menuitem, isRight=true, id='btn_settings') }}
|
||||
{{ wallet_menu_item('history', _('Transactions'), wallet_alias, active_menuitem, isLeft=true, id='btn_transactions') }}
|
||||
{{ wallet_menu_item('addresses', _('Addresses'), wallet_alias, active_menuitem) }}
|
||||
{{ wallet_menu_item('receive', _('Receive'), wallet_alias, active_menuitem, id='btn_receive') }}
|
||||
{{ wallet_menu_item('send', _('Send'), wallet_alias, active_menuitem, id='btn_send') }}
|
||||
{{ wallet_menu_item('settings', _('Settings'), wallet_alias, active_menuitem, isRight=true, id='btn_settings') }}
|
||||
<a href="javascript:void(0);" class="mobile-nav-icon" onclick="toggleMobileNav(this, `{{ url_for('static', filename='img/caret-down.svg') }}`, `{{ url_for('static', filename='img/caret-up.svg') }}`)">
|
||||
<img style="width: 36px;" src="{{ url_for('static', filename='img/caret-down.svg') }}" class="svg-white"/>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@
|
|||
let useSlip132 = document.getElementById("use_slip_132_pdf").checked;
|
||||
let currentHeight = 15;
|
||||
doc.setFontSize(18);
|
||||
doc.text('Specter Backup File', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("Specter Backup File") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 10;
|
||||
doc.setFontSize(14);
|
||||
doc.text('(keep this information with each of your key backups)', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("(keep this information with each of your key backups)") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 11;
|
||||
|
||||
doc.setFontSize(18);
|
||||
|
|
@ -27,16 +27,16 @@
|
|||
doc.setFontSize(16);
|
||||
{% if wallet.is_multisig %}
|
||||
if ('{{ wallet.address_type }}' == 'bech32') {
|
||||
doc.text('{{ wallet.sigs_required }} of {{ wallet.keys | length }} multisig (sorted, native segwit)', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ wallet.sigs_required }} of {{ wallet.keys | length }} {{ _("multisig") }} (sorted, native segwit)', 105, currentHeight, {align: 'center'});
|
||||
} else {
|
||||
doc.text('{{ wallet.sigs_required }} of {{ wallet.keys | length }} multisig (sorted, nested segwit)', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ wallet.sigs_required }} of {{ wallet.keys | length }} {{ _("multisig") }} (sorted, nested segwit)', 105, currentHeight, {align: 'center'});
|
||||
}
|
||||
currentHeight += 7;
|
||||
{% else %}
|
||||
if ('{{ wallet.address_type }}' == 'bech32') {
|
||||
doc.text('(Native Segwit)', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("(Native Segwit)") }}', 105, currentHeight, {align: 'center'});
|
||||
} else {
|
||||
doc.text('(Nested Segwit)', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("(Nested Segwit)") }}', 105, currentHeight, {align: 'center'});
|
||||
}
|
||||
currentHeight += 7;
|
||||
{% endif %}
|
||||
|
|
@ -51,9 +51,9 @@
|
|||
currentHeight += 110;
|
||||
|
||||
doc.setFontSize(16);
|
||||
doc.text('Scan this QR code with Specter-Desktop', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("Scan this QR code with Specter-Desktop") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 7;
|
||||
doc.text('or any other compatible wallet.', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("or any other compatible wallet.") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 11;
|
||||
|
||||
doc.setFontSize(16)
|
||||
|
|
@ -80,11 +80,11 @@
|
|||
currentHeight += 8
|
||||
|
||||
doc.setFont("Roboto", "normal");
|
||||
doc.text('Device type: ' + deviceType, 15, currentHeight);
|
||||
doc.text('{{ _("Device type:") }}" ' + deviceType, 15, currentHeight);
|
||||
currentHeight += 8
|
||||
doc.text("Master fingerprint: {{ key.fingerprint }}", 15, currentHeight);
|
||||
doc.text('{{ _("Master fingerprint:") }}" {{ key.fingerprint }}', 15, currentHeight);
|
||||
currentHeight += 8
|
||||
doc.text("Derivation path: {{ key.derivation }}", 15, currentHeight);
|
||||
doc.text('{{ _("Derivation path:") }}" {{ key.derivation }}', 15, currentHeight);
|
||||
currentHeight += 8
|
||||
|
||||
if (useSlip132)
|
||||
|
|
@ -116,11 +116,11 @@
|
|||
doc.addPage();
|
||||
currentHeight = 20;
|
||||
}
|
||||
doc.text('Advanced Recovery Details', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("Advanced Recovery Details") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 15;
|
||||
|
||||
doc.setFontSize(16);
|
||||
doc.text('Specter Desktop Raw Data', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("Specter Desktop Raw Data") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 10;
|
||||
|
||||
doc.setFontSize(14)
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
}
|
||||
|
||||
doc.setFontSize(16);
|
||||
doc.text('Bitcoin Core Receive Descriptor', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("Bitcoin Core Receive Descriptor") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 10;
|
||||
|
||||
doc.setFontSize(14)
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
}
|
||||
|
||||
doc.setFontSize(16);
|
||||
doc.text('Bitcoin Core Change Descriptor', 105, currentHeight, {align: 'center'});
|
||||
doc.text('{{ _("Bitcoin Core Change Descriptor") }}', 105, currentHeight, {align: 'center'});
|
||||
currentHeight += 10;
|
||||
|
||||
doc.setFontSize(14)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@
|
|||
<form action="./" method="POST" onsubmit="return isValidForm()">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
{% if wallet_type == 'multisig' %}
|
||||
<h1 style="margin: 20px;">Pick the devices you want in your multisig quorum</h1>
|
||||
<h1 style="margin: 20px;">{{ _("Pick the devices you want in your multisig quorum") }}</h1>
|
||||
{% else %}
|
||||
<h1 style="margin: 20px;">Pick the device you want to use</h1>
|
||||
<h1 style="margin: 20px;">{{ _("Pick the device you want to use") }}</h1>
|
||||
{% endif %}
|
||||
<div class="row overflow">
|
||||
{# Disable BitBox02 multisig for now... #}
|
||||
{% for device_name in specter.device_manager.devices_names %}
|
||||
{% set device = specter.device_manager.devices[device_name] %}
|
||||
<label style="position: relative;">
|
||||
|
|
@ -27,18 +26,18 @@
|
|||
<a href="{{ url_for('devices_endpoint.new_device_type') }}" style="text-decoration: none; color: #fff;">
|
||||
<div class="small-card radio">
|
||||
<img src="{{ url_for('static', filename='img/plus.svg') }}" style="transform: scale(1.5);" class="svg-white">
|
||||
Add new device
|
||||
{{ _("Add new device") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<button id="submit-device" type="submit" name="action" value="device" class="btn centered">Continue</button>
|
||||
<button id="submit-device" type="submit" name="action" value="device" class="btn centered">{{ _("Continue") }}</button>
|
||||
<br>
|
||||
<div class="note">
|
||||
<center>
|
||||
{% if wallet_type == 'multisig' %}
|
||||
We use sorted multisig (BIP-67), so <b>order is NOT important.</b><br>
|
||||
{{ _("We use sorted multisig (BIP-67), so <b>order is NOT important.</b>") }}<br>
|
||||
<br>
|
||||
Be aware of <a style="color: #fff" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/multisig-security-tradeoffs.md" target="_blank">multisig security tradeoffs</a>.<br>
|
||||
{{ _('Be aware of <a style="color: #fff" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/multisig-security-tradeoffs.md" target="_blank">multisig security tradeoffs</a>.') }}<br>
|
||||
<br>
|
||||
{% endif %}
|
||||
</center>
|
||||
|
|
|
|||
|
|
@ -2,36 +2,36 @@
|
|||
{% block main %}
|
||||
<form id="keysform" action="./" method="POST" onsubmit="showPacman()" class="table-holder">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<h1>Name your Wallet</h1>
|
||||
<h1>{{ _("Name your Wallet") }}</h1>
|
||||
<div class="center">
|
||||
<input type="text" id="wallet_name" name="wallet_name" class="inline" value="{{ wallet_name }}" placeholder="My new wallet" required>
|
||||
</div>
|
||||
<br>
|
||||
<h1>Type of the wallet</h1>
|
||||
<h1>{{ _("Type of the wallet") }}</h1>
|
||||
<nav class="row">
|
||||
<label>
|
||||
<input type="radio" name="type" value="{{'sh-wsh' if wallet_type == 'multisig' else 'sh-wpkh'}}"
|
||||
class="hidden" onchange="updateType(this.value);">
|
||||
<div class="btn radio left">Nested Segwit</div>
|
||||
<div class="btn radio left">{{ _("Nested Segwit") }}</div>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="type" value="{{'wsh' if wallet_type == 'multisig' else 'wpkh'}}" checked
|
||||
class="hidden" onchange="updateType(this.value);">
|
||||
<div class="btn radio right">Segwit</div>
|
||||
<div class="btn radio right">{{ _("Segwit") }}</div>
|
||||
</label>
|
||||
</nav>
|
||||
|
||||
<div class="note optional">
|
||||
<center>
|
||||
<br><b>Segwit</b> uses bech32-encoded addresses (bc1), <b>Nested Segwit</b> makes it compatible with legacy software. Don't use legacy.
|
||||
<br>{{ _("<b>Segwit</b> uses bech32-encoded addresses (bc1), <b>Nested Segwit</b> makes it compatible with legacy software. Don't use legacy.") }}
|
||||
</center>
|
||||
</div>
|
||||
<br>
|
||||
{% if wallet_type == 'multisig' %}
|
||||
<div class="center">
|
||||
Using
|
||||
{{ _("Using") }}
|
||||
<input class="inline" type="number" name="sigs_required" min=1 max="{{ cosigners | length }}" step=1 value="{{ sigs_required }}" />
|
||||
of
|
||||
{{ _("of") }}
|
||||
<span style="margin: 0px 40px; font-size: 1.2em;">{{ cosigners | length }}<span>
|
||||
</div>
|
||||
<br>
|
||||
|
|
@ -39,13 +39,13 @@
|
|||
<div class="center" style="max-width: 500px; margin: auto;">
|
||||
<div style="display: inline-block; text-align: center; margin-top: 10px;">
|
||||
<input form="keysform" type="checkbox" name="rescanblockchain" id="rescan" class="inline" onchange="toggleScanOptions(this)">
|
||||
<label for="rescan">Scan for existing funds?</label>
|
||||
<label for="rescan">{{ _("Scan for existing funds?") }}</label>
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Importing an existing wallet<br></span>
|
||||
Check this option to scan the blockchain for existing wallet balance and history.
|
||||
<span class="info__title">{{ _("Importing an existing wallet") }}<br></span>
|
||||
{{ _("Check this option to scan the blockchain for existing wallet balance and history.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -53,16 +53,16 @@
|
|||
<div id="scan-options" class="hidden">
|
||||
<br>
|
||||
<div>
|
||||
Rescan type:
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="full_rescan_option" value="full" onclick="toggleFullRescanOption(this)" checked>Full rescan</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px" name="full_rescan_option" value="utxo" onclick="toggleFullRescanOption(this);">UTXO only</label>
|
||||
{{ _("Rescan type:") }}
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="full_rescan_option" value="full" onclick="toggleFullRescanOption(this)" checked>{{ _("Full rescan") }}</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px" name="full_rescan_option" value="utxo" onclick="toggleFullRescanOption(this);">{{ _("UTXO only") }}</label>
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Choosing Rescan Type<br></span>
|
||||
Full rescan is a relativly slow process which looks for and imports the full wallet transaction history and balance.<br><br>
|
||||
UTXO only is a fast rescan option which only looks for existing balances (UTXO), but will not import the full transaction history.
|
||||
<span class="info__title">{{ _("Choosing Rescan Type") }}<br></span>
|
||||
{{ _("Full rescan is a relativly slow process which looks for and imports the full wallet transaction history and balance.") }}<br><br>
|
||||
{{ _("UTXO only is a fast rescan option which only looks for existing balances (UTXO), but will not import the full transaction history.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -74,32 +74,32 @@
|
|||
{% else %}
|
||||
{% set startblock=(0 if not specter.info['pruned'] else specter.info['pruneheight']) %}
|
||||
{% endif %}
|
||||
Rescan blockchain from block: <input type="number" name="startblock" value="{{startblock}}" min="{{startblock}}" style="max-width: 150px; margin-left: 5px;">
|
||||
{{ _("Rescan blockchain from block:") }} <input type="number" name="startblock" value="{{startblock}}" min="{{startblock}}" style="max-width: 150px; margin-left: 5px;">
|
||||
{% if specter.info['pruned'] %}
|
||||
<span class="warning" style="display: inline-block; max-width:300px;">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>You are using a pruned node.<br>Wallet history may not fully appear.<br>Rescan is limited by the pruned height to block: {{specter.info['pruneheight']}}
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("You are using a pruned node.<br>Wallet history may not fully appear.<br>Rescan is limited by the pruned height to block:") }} {{specter.info['pruneheight']}}
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="note center" style="margin-top: 5px; line-height: 2;">In case you're not sure, it is best to just leave the default number.<br><b>481824</b> was the first Segwit block.</div>
|
||||
<div class="note center" style="margin-top: 5px; line-height: 2;">{{ _("In case you're not sure, it is best to just leave the default number.<br><b>481824</b> was the first Segwit block.") }}</div>
|
||||
<br>
|
||||
</div>
|
||||
<div id="utxo-scan-options" style="display: none">
|
||||
{% if specter.info['pruned'] %}
|
||||
<label for="use_explorer"><input style="width: auto; min-width: auto;" type="checkbox" id="use_explorer" name="use_explorer" checked> fetch missing data from block explorer
|
||||
<tool-tip title="Fetch missing data">
|
||||
You are using a pruned node. Blocks for some transactions may be missing.<br><br>
|
||||
Check this checkbox if you want to fetch missing information from block explorer.
|
||||
<label for="use_explorer"><input style="width: auto; min-width: auto;" type="checkbox" id="use_explorer" name="use_explorer" checked> {{ _("fetch missing data from block explorer") }}
|
||||
<tool-tip title="{{ _('Fetch missing data') }}">
|
||||
{{ _("You are using a pruned node. Blocks for some transactions may be missing.") }}<br><br>
|
||||
{{ _("Check this checkbox if you want to fetch missing information from block explorer.") }}
|
||||
</tool-tip>
|
||||
</label>
|
||||
<div id="utxo-explorer">
|
||||
<br>
|
||||
Block explorer URL:
|
||||
{{ _("Block explorer URL:") }}
|
||||
{% include "components/explorer_select.jinja" %}
|
||||
<span class="warning" id="utxo-warning" style="display: inline-block; max-width:300px;">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>Careful with block explorers!<br>
|
||||
This may have privacy implication!<br>
|
||||
Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.<br>
|
||||
"They" will know what transactions you are interested in.
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("Careful with block explorers!") }}<br>
|
||||
{{ _("This may have privacy implications!") }}<br>
|
||||
{{ _("Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.") }}<br>
|
||||
{{ _('"They" will know what transactions you are interested in.') }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -112,18 +112,18 @@
|
|||
{% endfor %}
|
||||
<input type="hidden" name="sigs_total" value="{{ cosigners | length }}">
|
||||
{% for device in cosigners %}
|
||||
<h1>Signer {{ loop.index }} - {{ device.name }}</h1>
|
||||
<h1>{{ _("Signer") }} {{ loop.index }} - {{ device.name }}</h1>
|
||||
{% set outer_loop = loop %}
|
||||
{% if device.keys | length > 0 %}
|
||||
<div class="table-holder">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network</th>
|
||||
<th>Purpose</th>
|
||||
<th class="optional">Derivation</th>
|
||||
<th class="optional">Key</th>
|
||||
<th>Actions</th>
|
||||
<th>{{ _("Network") }}</th>
|
||||
<th>{{ _("Purpose") }}</th>
|
||||
<th class="optional">{{ _("Derivation") }}</th>
|
||||
<th class="optional">{{ _("Key") }}</th>
|
||||
<th>{{ _("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="device{{ loop.index0 }}">
|
||||
|
|
@ -138,17 +138,17 @@
|
|||
<td width="120px">
|
||||
<label>
|
||||
<input class="device-key hidden" data-key-types="{{key.key_type}}" type="radio" name="key{{ outer_loop.index0 }}" value="{{ key.original }}" {% if loop.index == 1 %}checked{% endif %}/>
|
||||
<div class="btn inline hovering">Use this key</div>
|
||||
<div class="btn inline hovering">{{ _("Use this key") }}</div>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="hidden">
|
||||
<td>
|
||||
The device does not have keys matching the wallet type selected.
|
||||
{{ _("The device does not have keys matching the wallet type selected.") }}
|
||||
</td>
|
||||
<td>
|
||||
Please choose a different type or add the keys to the device...
|
||||
{{ _("Please choose a different type or add the keys to the device...") }}
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
|
|
@ -161,11 +161,11 @@
|
|||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="center">Looks like this device doesn't have keys matching this type of wallet.</div>
|
||||
<div class="center">{{ _("Looks like this device doesn't have keys matching this type of wallet.") }}</div>
|
||||
{% endif %}
|
||||
<div class="spacer"></div>
|
||||
{% endfor %}
|
||||
<button type="submit" name="action" value="key" class="btn centered action">Create wallet</button>
|
||||
<button type="submit" name="action" value="key" class="btn centered action">{{ _("Create wallet") }}</button>
|
||||
</form>
|
||||
<div class="spacer"></div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
{% extends "base.jinja" %}
|
||||
{% block main %}
|
||||
<div class="spacer"></div>
|
||||
<h1>Select the type of the wallet</h1>
|
||||
<h1>{{ _("Select the type of the wallet") }}</h1>
|
||||
<div>
|
||||
<center>
|
||||
<span>Adding a wallet with existing funds?</span>
|
||||
<span>{{ _("Adding a wallet with existing funds?") }}</span>
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Importing an existing wallet?<br></span>
|
||||
After choosing device(s), click continue, then just select the:<br> "Scan for existing funds" checkbox on the next screen.
|
||||
<span class="info__title">{{ _("Importing an existing wallet?") }}<br></span>
|
||||
{{ _('After choosing device(s), click continue, then just select the:<br> "Scan for existing funds" checkbox on the next screen.') }}
|
||||
<br>
|
||||
{% if specter.info["pruned"] %}
|
||||
<span class="warning" style="display: inline-block;"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>
|
||||
Please Note:<br>You are using a pruned node. Wallet history may not fully appear.
|
||||
{{ _("Please Note:<br>You are using a pruned node. Wallet history may not fully appear.") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
|
@ -25,26 +25,26 @@
|
|||
<nav class="row overflow">
|
||||
<a href="./simple/" class="small-card">
|
||||
<img src="{{ url_for('static', filename='img/key.svg') }}" class="svg-white" style="width: 120px;"/>
|
||||
Single key wallet
|
||||
{{ _("Single key wallet") }}
|
||||
</a>
|
||||
<a class="small-card" href="./multisig/">
|
||||
<img src="{{ url_for('static', filename='img/two-keys.svg') }}" class="svg-white" style="width: 120px;"/>
|
||||
Multisignature wallet<br><br>
|
||||
{{ _("Multisignature wallet") }}<br><br>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="raw">
|
||||
<button onclick="showPageOverlay('account_map_import')" type="button" class="btn centered padded">
|
||||
Import from wallet software
|
||||
{{ _("Import from wallet software") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Import From Wallet Software<br> <br></span>
|
||||
Click here to import an existing wallet from a wallet software like Electrum, Fully-Noded, Sparrow Wallet, or another Specter instance.
|
||||
<span class="info__title">{{ _("Import From Wallet Software") }}<br> <br></span>
|
||||
{{ _("Click here to import an existing wallet from a wallet software like Electrum, Fully-Noded, Sparrow Wallet, or another Specter instance.") }}
|
||||
<br>
|
||||
{% if specter.info["pruned"] %}
|
||||
<span class="warning" style="display: inline-block;"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>
|
||||
Please Note:<br>You are using a pruned node. Wallet history may not fully appear.
|
||||
{{ _("Please Note:<br>You are using a pruned node. Wallet history may not fully appear.") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
|
@ -54,6 +54,6 @@
|
|||
</div>
|
||||
{% include "wallet/new_wallet/components/import_wallet_account_map.jinja" %}
|
||||
<div class="note">
|
||||
<center>Multisig is better for large-ish amounts.</center>
|
||||
<center>{{ _("Multisig is better for large-ish amounts.") }}</center>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -37,28 +37,27 @@
|
|||
<address-label data-address="{{ wallet.address }}" data-label="{{ wallet.getlabel(wallet.address) }}" data-wallet="{{ wallet_alias }}" date-size="title"></address-label>
|
||||
</form>
|
||||
<div class="warning" style="font-weight: lighter; background: #0003; color: #ddd; display: inline-block; margin:5% auto 5%; border: 1px solid #777; padding: 20px; max-width: 100%;">
|
||||
<p title="Click to copy" id="address" class="padded" style="word-break: break-all;" onclick="copyText('{{ wallet.address }}', 'Copied Address: {{ wallet.address }}');">{{ wallet.address }}</p>
|
||||
(click to copy)
|
||||
<p title="Click to copy" id="address" class="padded" style="word-break: break-all;" onclick="copyText('{{ wallet.address }}', '{{ _("Copied Address:") }} {{ wallet.address }}');">{{ wallet.address }}</p>
|
||||
{{ _("(click to copy)") }}
|
||||
<br><br>
|
||||
<qr-code style="margin: auto;" value="{{'bitcoin:' + wallet.address }}" width="256"></qr-code><br>
|
||||
</div><br>
|
||||
<form action="./" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" name="action" value="newaddress" class="btn centered" style="margin-bottom: 15px">Get new address</button>
|
||||
<button type="submit" name="action" value="newaddress" class="btn centered" style="margin-bottom: 15px">{{ _("Get new address") }}</button>
|
||||
{% set supports_qr_code_verify = [] %}
|
||||
{% for device in wallet.devices if device.qr_code_support_verify %}
|
||||
{% set supports_qr_code_verify = supports_qr_code_verify.append(device) %}
|
||||
{% endfor %}
|
||||
{% if supports_qr_code_verify != [] %}
|
||||
<div id="verify_address_qr_code" class="hidden">
|
||||
<h1>Scan to Verify Address on Your Device</h1><br>
|
||||
<h1>{{ _("Scan to Verify Address on Your Device") }}</h1><br>
|
||||
<div id="verify_address_qr_code_container"></div><br>
|
||||
<p>
|
||||
Specter can verify this address if you scan it.<br>
|
||||
It has an address index included in the QR code.
|
||||
{{ _("Specter can verify this address if you scan it.<br>It has an address index included in the QR code.") }}
|
||||
</p><br>
|
||||
</div>
|
||||
<button onclick="verifyQRCode('{{ wallet.address }}', '{{ wallet.address_index | string }}')" type="button" class="btn centered padded">Verify address via QR code</button>
|
||||
<button onclick="verifyQRCode('{{ wallet.address }}', '{{ wallet.address_index | string }}')" type="button" class="btn centered padded">{{ _("Verify address via QR code") }}</button>
|
||||
{% endif %}
|
||||
{% set supports_hwi = [] %}
|
||||
{% set supports_hwi_multisig_display_address = [] %}
|
||||
|
|
@ -69,9 +68,9 @@
|
|||
{% set supports_hwi = supports_hwi.append(device) %}
|
||||
{% endfor %}
|
||||
{% if supports_hwi != [] and (supports_hwi_multisig_display_address != [] or not wallet.is_multisig) %}
|
||||
<button type="button" onclick='displayAddressOnDevice(`{{ wallet.address }}`, `{{ wallet.get_descriptor()|tojson }}`)' class="btn centered optional">Display address on device</button><br>
|
||||
<button type="button" onclick='displayAddressOnDevice(`{{ wallet.address }}`, `{{ wallet.get_descriptor()|tojson }}`)' class="btn centered optional">{{ _("Display address on device") }}</button><br>
|
||||
{% if wallet.is_multisig %}
|
||||
<p class="note center optional">Multsig address on-device display is only available for BitBox02, ColdCard, KeepKey, Specter, and Trezor devices.</p>
|
||||
<p class="note center optional">{{ _("Multsig address on-device display is only available for BitBox02, ColdCard, KeepKey, Specter, and Trezor devices.") }}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
|
|
@ -84,10 +83,10 @@
|
|||
{% set supports_export_to_device = supports_export_to_device.append(device) %}
|
||||
{% endfor %}
|
||||
{% if supports_export_to_device != [] %}
|
||||
<h1>Export To Device</h1>
|
||||
<p style="max-width: 500px;">Some of your devices require that you first import your multisig wallet data into the device before you can start verifying addresses and sending transactions.</p>
|
||||
<h1>{{ _("Export To Device") }}</h1>
|
||||
<p style="max-width: 500px;">{{ _("Some of your devices require that you first import your multisig wallet data into the device before you can start verifying addresses and sending transactions.") }}</p>
|
||||
<div class="note center">
|
||||
Import this wallet to a device by scanning its QR code or importing its data file.
|
||||
{{ _("Import this wallet to a device by scanning its QR code or importing its data file.") }}
|
||||
</div>
|
||||
|
||||
{% for device in wallet.devices %}
|
||||
|
|
@ -97,60 +96,60 @@
|
|||
download="{{ wallet.name | ascii20 }}.txt"
|
||||
href="data:text/plain;charset=US-ASCII,{{ device.export_wallet(wallet) }}"
|
||||
{% if device.device_type == 'coldcard' %}
|
||||
onclick="showNotification('Import wallet file to your ColdCard: Settings -> Multisig Wallets -> Import from SD', 0)"
|
||||
onclick="showNotification('{{ _("Import wallet file to your ColdCard: Settings -> Multisig Wallets -> Import from SD") }}', 0)"
|
||||
{% endif %}
|
||||
class="btn centered padded">
|
||||
Save {{ device.name }} file
|
||||
{{ _("Save") }} {{ device.name }} {{ _("file") }}
|
||||
</a>
|
||||
{% elif device.wallet_export_type == 'qr' %}
|
||||
<button onclick="hidePageOverlay();showPageOverlay('{{ device.alias }}_export_qr_code')" type="button" class="btn centered padded">Show {{ device.name }} QR Code</button>
|
||||
<button onclick="hidePageOverlay();showPageOverlay('{{ device.alias }}_export_qr_code')" type="button" class="btn centered padded">{{ _("Show") }} {{ device.name }} {{ _("QR Code") }}</button>
|
||||
<div id="{{ device.alias }}_export_qr_code" style="display: none;">
|
||||
<h1>Scan this with your {{ device.name }} device </h1>
|
||||
<h1>{{ _("Scan this with your") }} {{ device.name }} "{{ _("device") }} </h1>
|
||||
<qr-code value="{{ device.export_wallet(wallet) }}" width="400" scalable></qr-code>
|
||||
<br>
|
||||
<button onclick="hidePageOverlay();showPageOverlay('new_wallet_devices_popup')" type="button" class="btn centered padded">Back</button>
|
||||
<button onclick="hidePageOverlay();showPageOverlay('new_wallet_devices_popup')" type="button" class="btn centered padded">{{ _("Back") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<br>
|
||||
{% endif %}
|
||||
<p style="max-width: 500px;">You can always do this later from the wallet Settings tab.</p>
|
||||
<button type="button" class="btn centered padded" onclick="hidePageOverlay()">Finish</button>
|
||||
<p style="max-width: 500px;">{{ _("You can always do this later from the wallet Settings tab.") }}</p>
|
||||
<button type="button" class="btn centered padded" onclick="hidePageOverlay()">{{ _("Finish") }}</button>
|
||||
</div>
|
||||
<div class="hidden" id="new_wallet_popup">
|
||||
<h1>New wallet was created successfully!</h1>
|
||||
<h1>{{ _("New wallet was created successfully!") }}</h1>
|
||||
<button id="pdf-wallet-download" class="btn centered action" type="button">
|
||||
<img src="{{ url_for('static', filename='img/file.svg') }}" style="width: 30px; margin-right: 2px;" class="svg-white">
|
||||
Save Backup PDF
|
||||
{{ _("Save Backup PDF") }}
|
||||
</button>
|
||||
{% include 'wallet/components/wallet_pdf.jinja' %}
|
||||
<br>
|
||||
<span id="advanced_pdf_export_toggle" style="cursor: pointer;" onclick="togglePDFExportAdvanced()">Advanced ▶</span>
|
||||
<span id="advanced_pdf_export_toggle" style="cursor: pointer;" onclick="togglePDFExportAdvanced()">{{ _("Advanced") }} ▶</span>
|
||||
<br>
|
||||
<br>
|
||||
<div style="margin: 0 auto;" class="center hidden" id="advanced_pdf_export_container">
|
||||
<label style="font-size: 1em;"">Use SLIP-132: </label>
|
||||
<label style="font-size: 1em;"">{{ _("Use SLIP-132:") }} </label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="use_slip_132_pdf" style="margin: auto;" onchange="toggleKeyDisplay()">
|
||||
<span class="slider"></span>
|
||||
</label><br><br>
|
||||
</div>
|
||||
<p class="note center" style="margin-top: 5px; max-width: 90%;">To format master keys on the PDF backup document.</p>
|
||||
<p class="note center" style="margin-top: 5px; max-width: 90%;">{{ _("To format master keys on the PDF backup document.") }}</p>
|
||||
<p style="max-width: 500px;">
|
||||
It is recommended that you print and save this wallet backup file with each of your devices.
|
||||
{{ _("It is recommended that you print and save this wallet backup file with each of your devices.") }}
|
||||
</p>
|
||||
<p style="max-width: 500px;" class="note">
|
||||
This backup includes all the information you'd need to restore your wallet in Specter Desktop or other compatible wallets including Bitcoin Core itself.<br>
|
||||
{{ _("This backup includes all the information you'd need to restore your wallet in Specter Desktop or other compatible wallets including Bitcoin Core itself.") }}<br>
|
||||
<p>
|
||||
<p style="max-width: 500px;" class="note">
|
||||
It is recommended that you keep this file private, as it contains all the information needed to track all of the wallet balance and transactions history.
|
||||
{{ _("It is recommended that you keep this file private, as it contains all the information needed to track all of the wallet balance and transactions history.") }}
|
||||
<p>
|
||||
<p style="max-width: 500px;">You can always download this file later from the wallet Settings tab.</p>
|
||||
<p style="max-width: 500px;">{{ _("You can always download this file later from the wallet Settings tab.") }}</p>
|
||||
{% if wallet.is_multisig and supports_export_to_device != [] %}
|
||||
<button type="button" class="btn centered padded" onclick="hidePageOverlay();showPageOverlay('new_wallet_devices_popup')">Continue</button>
|
||||
<button type="button" class="btn centered padded" onclick="hidePageOverlay();showPageOverlay('new_wallet_devices_popup')">{{ _("Continue") }}</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn centered padded" onclick="hidePageOverlay()" id="btn_continue">Continue</button>
|
||||
<button type="button" class="btn centered padded" onclick="hidePageOverlay()" id="btn_continue">{{ _("Continue") }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<script>
|
||||
|
|
@ -171,10 +170,10 @@
|
|||
let advancedSettings = document.getElementById(`advanced_pdf_export_container`);
|
||||
if (advancedSettings.classList.contains("hidden")) {
|
||||
advancedSettings.classList.remove("hidden");
|
||||
advancedButton.innerHTML = 'Advanced ▼';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▼';
|
||||
} else {
|
||||
advancedSettings.classList.add("hidden");
|
||||
advancedButton.innerHTML = 'Advanced ▶';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▶';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@
|
|||
{% macro send_nav(active_menuitem, wallet_alias) -%}
|
||||
<br>
|
||||
<nav class="row collapse-on-mobile">
|
||||
{{ wallet_menu_item('send_new', 'New', wallet_alias, active_menuitem, isLeft=true) }}
|
||||
{{ wallet_menu_item('send_pending', 'Unsigned', wallet_alias, active_menuitem) }}
|
||||
{{ wallet_menu_item('import_psbt', 'Import', wallet_alias, active_menuitem, isRight=true) }}
|
||||
{{ wallet_menu_item('send_new', _('New'), wallet_alias, active_menuitem, isLeft=true) }}
|
||||
{{ wallet_menu_item('send_pending', _('Unsigned'), wallet_alias, active_menuitem) }}
|
||||
{{ wallet_menu_item('import_psbt', _('Import'), wallet_alias, active_menuitem, isRight=true) }}
|
||||
<a href="javascript:void(0);" class="mobile-nav-icon" onclick="toggleMobileNav(this, `{{ url_for('static', filename='img/caret-down.svg') }}`, `{{ url_for('static', filename='img/caret-up.svg') }}`)">
|
||||
<img style="width: 36px;" src="{{ url_for('static', filename='img/caret-down.svg') }}" class="svg-white"/>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -4,24 +4,24 @@
|
|||
{% include "includes/qr-scanner.html" %}
|
||||
{% from 'wallet/send/components/send_nav.jinja' import send_nav %}
|
||||
{{ send_nav('import_psbt', wallet_alias) }}
|
||||
<h1 class="padded">Import PSBT transaction:</h1>
|
||||
<span class="center note padded">Paste base64-encoded PSBT transaction here to import.</span>
|
||||
<h1 class="padded">{{ _("Import PSBT transaction:") }}</h1>
|
||||
<span class="center note padded">{{ _("Paste base64-encoded PSBT transaction here to import.") }}</span>
|
||||
<form action="{{ url_for('wallets_endpoint.import_psbt',wallet_alias=wallet_alias) }}" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<textarea name="rawpsbt" id="rawpsbt" placeholder="Paste PSBT here"></textarea>
|
||||
<textarea name="rawpsbt" id="rawpsbt" placeholder="{{ _('Paste PSBT here') }}"></textarea>
|
||||
</div>
|
||||
<div class="row break-row-mobile">
|
||||
<qr-scanner id="psbt-scan" style="width:50%">
|
||||
<a slot="button" href="#" class="btn" style="margin: 8px;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> Scan
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> {{ _("Scan") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Using an airgapped device with QR codes<br><br></span>
|
||||
PSBT should be base64 encoded.
|
||||
<span class="info__title">{{ _("Using an airgapped device with QR codes") }}<br><br></span>
|
||||
{{ _("PSBT should be base64 encoded.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -29,19 +29,19 @@
|
|||
</qr-scanner>
|
||||
<input type="file" id="file" class="inputfile"/>
|
||||
<label for="file" class="btn" style="margin: 8px;">
|
||||
<img src="{{ url_for('static', filename='img/file.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> Choose file
|
||||
<img src="{{ url_for('static', filename='img/file.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> {{ _("Choose file") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Using a file<br><br></span>
|
||||
The file should contain either base64 or binary encoded PSBT.
|
||||
<span class="info__title">{{ _("Using a file") }}<br><br></span>
|
||||
{{ _("The file should contain either base64 or binary encoded PSBT.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" name="action" value="importpsbt" class="btn centered" style="margin-top: 20px;">Import transaction</button>
|
||||
<button type="submit" name="action" value="importpsbt" class="btn centered" style="margin-top: 20px;">{{ _("Import transaction") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<table style="table-layout: fixed; display: {% if selected_coins %}block{% else %}none{% endif %}; max-width:98%;" id="coin_selection_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th><th>TxID</th><th>Address</th><th>Amount</th>
|
||||
<th></th><th>{{ _("TxID") }}</th><th>{{ _("Address") }}</th><th>{{ _("Amount") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -2,31 +2,31 @@
|
|||
|
||||
<div class="fee_container">
|
||||
<div>
|
||||
<label><input type="checkbox" class="inline" name="subtract" id="subtract" {% if subtract %}checked{% endif %} onchange="toggleSubtractFrom(this)"> Subtract fees from amount</label>
|
||||
<label><input type="checkbox" class="inline" name="subtract" id="subtract" {% if subtract %}checked{% endif %} onchange="toggleSubtractFrom(this)"> {{ _("Subtract fees from amount") }}</label>
|
||||
<div class="tool-tip" style="text-align: center;">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Subtract fees from amount<br><br></span>
|
||||
If checked, the transaction fees will be paid off from the transaction amount.<br><br>
|
||||
Otherwise, the fees will be paid as an added cost in addition to the amount sent.
|
||||
<span class="info__title">{{ _("Subtract fees from amount") }}<br><br></span>
|
||||
{{ _("If checked, the transaction fees will be paid off from the transaction amount.") }}<br><br>
|
||||
{{ _("Otherwise, the fees will be paid as an added cost in addition to the amount sent.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span id="subtract_from" class="hidden"><br>Subtract from recipient number: <input id="subtract_from_input" name="subtract_from" type="number" min="1" value="{{ subtract_from }}" step="1" style="width: 80px; min-width: 80px;"><br></span>
|
||||
<span id="subtract_from" class="hidden"><br>{{ _("Subtract from recipient number:") }} <input id="subtract_from_input" name="subtract_from" type="number" min="1" value="{{ subtract_from }}" step="1" style="width: 80px; min-width: 80px;"><br></span>
|
||||
<br>
|
||||
<div>
|
||||
Fees:
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" id="fee_options_dynamic" name="fee_options" value="dynamic" onclick="showFeeOption(this)" {% if fee_options_dynamic %}checked{% endif %}>dynamic</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px" id="fee_option_manual" name="fee_options" value="manual" onclick="showFeeOption(this);" {% if not fee_options_dynamic %}checked{% endif %}>manual</label>
|
||||
{{ _("Fees:") }}
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" id="fee_options_dynamic" name="fee_options" value="dynamic" onclick="showFeeOption(this)" {% if fee_options_dynamic %}checked{% endif %}>{{ _("dynamic") }}</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px" id="fee_option_manual" name="fee_options" value="manual" onclick="showFeeOption(this);" {% if not fee_options_dynamic %}checked{% endif %}>{{ _("manual") }}</label>
|
||||
</div>
|
||||
<br>
|
||||
<div id = "fee_manual" style="display: {% if fee_options_dynamic %}none{% else %}block{% endif %}">
|
||||
Fee rate:<br>
|
||||
{{ _("Fee rate:") }}<br>
|
||||
<input type="number" class="fee_rate" name="fee_rate" id="fee_rate" min="1" step="any" autocomplete="off" {% if not fee_options_dynamic %}value="{{ fee_rate }}"{% endif %}> sat/vbyte
|
||||
<div class="note">
|
||||
leave blank to set automatically, 1 sat/vbyte is the minimal fee rate.
|
||||
{{ _("leave blank to set automatically, 1 sat/vbyte is the minimal fee rate.") }}
|
||||
</div>
|
||||
</div>
|
||||
<div id ="fee_dynamic" style="display: {% if fee_options_dynamic %}block{% else %}none{% endif %}">
|
||||
|
|
@ -34,13 +34,13 @@
|
|||
<input type="range" style="width: 12em" min="{{fee_estimation_data['minimumFee']}}" max="{{fee_estimation_data['fastestFee'] * 1.4}}" value="{{ fee_estimation }}" step="1" id="fees_slider" oninput="dynamicFeeUpdated()">
|
||||
<input type="hidden" id="fee_rate_dynamic" name="fee_rate_dynamic" value="0">
|
||||
<div>
|
||||
Estimated speed: <span id="fee_rate_speed_text"></span>
|
||||
{{ _("Estimated speed:") }} <span id="fee_rate_speed_text"></span>
|
||||
<br>
|
||||
<span class="note">(Fee rate: <span id="fee_rate_dynamic_text"></span> sat/vbyte)</span>
|
||||
<span class="note">({{ _("Fee rate:") }} <span id="fee_rate_dynamic_text"></span> sat/vbyte)</span>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<label><input type="checkbox" class="inline" name="rbf" id="rbf" {% if rbf %}checked{% endif %}> RBF Enabled</label>
|
||||
<label><input type="checkbox" class="inline" name="rbf" id="rbf" {% if rbf %}checked{% endif %}> {{ _("RBF Enabled") }}</label>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
@ -66,17 +66,17 @@
|
|||
let halfHourFee = parseInt("{{fee_estimation_data['halfHourFee']}}");
|
||||
let fastestFee = parseInt("{{fee_estimation_data['fastestFee']}}");
|
||||
if (feesSlider.value <= minFee + ((hourFee - minFee) / 2)) {
|
||||
document.getElementById('fee_rate_speed_text').innerText = 'Very slow';
|
||||
document.getElementById('fee_rate_speed_text').innerText = '{{ _("Very slow") }}';
|
||||
} else if (feesSlider.value > minFee + ((hourFee - minFee) / 2) && feesSlider.value < hourFee) {
|
||||
document.getElementById('fee_rate_speed_text').innerText = 'Slow';
|
||||
document.getElementById('fee_rate_speed_text').innerText = '{{ _("Slow") }}';
|
||||
} else if (feesSlider.value >= hourFee && feesSlider.value < halfHourFee) {
|
||||
document.getElementById('fee_rate_speed_text').innerText = 'Medium (1 hour)';
|
||||
document.getElementById('fee_rate_speed_text').innerText = '{{ _("Medium (1 hour)") }}';
|
||||
} else if (feesSlider.value >= halfHourFee && feesSlider.value < fastestFee) {
|
||||
document.getElementById('fee_rate_speed_text').innerText = 'Fast (30 minutes)';
|
||||
document.getElementById('fee_rate_speed_text').innerText = '{{ _("Fast (30 minutes)") }}';
|
||||
} else if (feesSlider.value >= fastestFee && feesSlider.value < fastestFee * 1.2) {
|
||||
document.getElementById('fee_rate_speed_text').innerText = 'Very fast (10 minutes)';
|
||||
document.getElementById('fee_rate_speed_text').innerText = '{{ _("Very fast (10 minutes)") }}';
|
||||
} else if (feesSlider.value >= fastestFee * 1.2) {
|
||||
document.getElementById('fee_rate_speed_text').innerText = 'Overpaid! (10 minutes)';
|
||||
document.getElementById('fee_rate_speed_text').innerText = '{{ _("Overpaid! (10 minutes)") }}';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@
|
|||
<form action="{{ url_for('wallets_endpoint.send_new',wallet_alias=wallet_alias) }}" id="send-form" method="POST" style="width: 100%;">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="rbf_tx_id" value="{{ rbf_tx_id }}"/>
|
||||
<h1 class="padded">Create Transaction</h1>
|
||||
<p class="center">Available Funds: {{wallet.full_available_balance | btcunitamount}}
|
||||
<h1 class="padded">{{ _("Create Transaction") }}</h1>
|
||||
<p class="center">{{ _("Available Funds:") }} {{wallet.full_available_balance | btcunitamount}}
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
{% endif %}
|
||||
{% if specter.is_liquid and wallet.balance.get("assets", {}) %}
|
||||
<span>
|
||||
<br>Assets:<br>
|
||||
<br>{{ _("Assets:") }}<br>
|
||||
{% for asset in wallet.balance.get("assets",{}).keys() | sort %}
|
||||
{% set balance = wallet.balance.get("assets",{}).get(asset, {}) %}
|
||||
<span style="margin: 0 10px;">
|
||||
|
|
@ -55,12 +55,12 @@
|
|||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Funds waiting in unsigned transactions<br><br></span>
|
||||
To make funds available to spend in new transactions, just click on the "Unsigned" tab and delete unsigned transaction you have no need for.
|
||||
<span class="info__title">{{ _("Funds waiting in unsigned transactions") }}<br><br></span>
|
||||
{{ _('To make funds available to spend in new transactions, just click on the "Unsigned" tab and delete unsigned transaction you have no need for.') }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p style="display: inline; margin: auto; text-align: center;"> You have waiting {{wallet.locked_amount | btcunitamount}} {% if specter.unit == 'sat' %}sats{% else %}{% if specter.is_testnet %}t{%endif%}BTC{% endif %} still in unsigned transactions.</p>
|
||||
<p style="display: inline; margin: auto; text-align: center;"> {{ _("You have waiting") }} {{wallet.locked_amount | btcunitamount}} {% if specter.unit == 'sat' %}sats{% else %}{% if specter.is_testnet %}t{%endif%}BTC{% endif %} {{ _("still in unsigned transactions.") }}</p>
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
|
|
@ -68,24 +68,24 @@
|
|||
<div class="card" style="margin: auto;">
|
||||
<div id="recipients" {% if ui_option != 'ui' %}class="hidden"{% endif %}></div>
|
||||
<div id="recipients-txt-container" {% if ui_option == 'ui' %}class="hidden"{% endif %}>
|
||||
Unit:
|
||||
{{ _("Unit:") }}
|
||||
<label><input type="radio" class="inline" style="margin: 0 5px;" name="amount_unit_text" value="sat" onchange="toggleUnit(this, 'text')" {% if specter.unit == 'sat' %}checked{% endif %}>sat</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 5px;" name="amount_unit_text" value="btc" onchange="toggleUnit(this, 'text')" {% if specter.unit != 'sat' %}checked{% endif %}>BTC</label><br>
|
||||
|
||||
<textarea id="recipients-txt" name="recipients" placeholder="Enter recipient address, amount" style="margin-top: 5px; font-size: 0.95em;" onblur="validateForm()">{{ recipients_txt }}</textarea>
|
||||
<textarea id="recipients-txt" name="recipients" placeholder="{{ _('Enter recipient address, amount') }}" style="margin-top: 5px; font-size: 0.95em;" onblur="validateForm()">{{ recipients_txt }}</textarea>
|
||||
<p class="note" style="line-height: 2; background-color: #16202d; padding: 10px; border-radius: 7px;">
|
||||
Enter each recipient address, amount as a new line:<br>
|
||||
<ADDRESS1>, <AMOUNT1><br>
|
||||
<ADDRESS2>, <AMOUNT2><br>
|
||||
<ADDRESS3>, <AMOUNT3><br>
|
||||
{{ _("Enter each recipient address, amount as a new line:") }}<br>
|
||||
<{{ _("ADDRESS") }}1>, <{{ _("AMOUNT") }}1><br>
|
||||
<{{ _("ADDRESS") }}2>, <{{ _("AMOUNT") }}2><br>
|
||||
<{{ _("ADDRESS") }}3>, <{{ _("AMOUNT") }}3><br>
|
||||
</p>
|
||||
</div>
|
||||
<button id="remove-recipient" style="width: 200px; height: 38px;" type="button" class="btn hidden" onclick="removeRecipient()"><b>−</b> Remove recipient</button>
|
||||
<p><span id="calculated_tx_fee_label" class="note" style="margin-top: 17px;" onclick="calculateEstimatedFee()">Calculate estimated fee</span><span class="note" id="calculated_tx_fee"></span><p>
|
||||
<span id="toggle_advanced" style="cursor: pointer;">Advanced {% if show_advanced_settings %}▼{% else %}▶{% endif %}</span>
|
||||
<button id="remove-recipient" style="width: 200px; height: 38px;" type="button" class="btn hidden" onclick="removeRecipient()"><b>−</b> {{ _("Remove recipient") }}</button>
|
||||
<p><span id="calculated_tx_fee_label" class="note" style="margin-top: 17px;" onclick="calculateEstimatedFee()">{{ _("Calculate estimated fee") }}</span><span class="note" id="calculated_tx_fee"></span><p>
|
||||
<span id="toggle_advanced" style="cursor: pointer;">{{ _("Advanced") }} {% if show_advanced_settings %}▼{% else %}▶{% endif %}</span>
|
||||
<br><br>
|
||||
<div id="advanced_settings" style="margin: auto; max-width: 90%; display: {% if show_advanced_settings %}block{% else %}none{% endif %};">
|
||||
<div>Transaction editor:
|
||||
<div>{{ _("Transaction editor:") }}
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="ui_option" value="ui" onclick="toggleSendUIType(this)" {% if ui_option == 'ui' %}checked{% endif %} id="ui-radio-btn">UI</label>
|
||||
<label><input type="radio" class="inline" style="margin: 0 10px 0 20px;" name="ui_option" value="text" onclick="toggleSendUIType(this);" {% if ui_option != 'ui' %}checked{% endif %}>text</label>
|
||||
</div><br>
|
||||
|
|
@ -95,17 +95,17 @@
|
|||
<br>
|
||||
<br>
|
||||
<div class="row break-row-mobile" id="coin-selection-row" style="margin-top: 30px;">
|
||||
<button id="add-recipient" style="width: 200px; height: 38px;" type="button" class="btn" onclick="addRecipient('', 0, 'btc', '')"><svg width="20" height="20" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> Add recipient </button>
|
||||
<button id="add-recipient" style="width: 200px; height: 38px;" type="button" class="btn" onclick="addRecipient('', 0, 'btc', '')"><svg width="20" height="20" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> {{ _("Add recipient") }} </button>
|
||||
{% if wallet.utxo | length > 0 %}
|
||||
<button style="width: 200px; height: 38px; margin-left: 10px;" id="coinselect" type="button" class="btn" onclick="toggleExpand()">Select coins<span class="optional"> manually</span></button>
|
||||
<button style="width: 200px; height: 38px; margin-left: 10px;" id="coinselect" type="button" class="btn" onclick="toggleExpand()">{{ _("Select coins") }}<span class="optional"> {{ _("manually") }}</span></button>
|
||||
{% endif %}
|
||||
</div><br>
|
||||
{% with selected_coins=selected_coins %}
|
||||
{% include "wallet/send/new/components/coin_selection.jinja" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<button onclick="validateForm(true)" type="button" name="action" value="createpsbt" id="create_psbt_btn" class="btn centered" style="margin-top: 20px;">Create <span class="optional"> unsigned </span>transaction</button>
|
||||
</div>
|
||||
<button onclick="validateForm(true)" type="button" name="action" value="createpsbt" id="create_psbt_btn" class="btn centered" style="margin-top: 20px;">{{ _('Create <span class="optional"> unsigned </span>transaction') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -170,24 +170,24 @@
|
|||
{% endif %}
|
||||
let recipientForm = `
|
||||
<div id="recipient_${i}" style="border-bottom: 1px solid var(--cmap-border); padding-bottom: 10px; margin-bottom: 10px;">
|
||||
Recipient address:<br>
|
||||
{{ _("Recipient address:") }}<br>
|
||||
<div class="row">
|
||||
<input type="text" id="address_${i}" name="address_${i}" oninput="validateForm()" value="${addr}">
|
||||
<qr-scanner id="address-scan-${i}" style="margin-top: 3px;">
|
||||
<a slot="button" class="btn" style="height: 35px;">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> Scan</a>
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white"> {{ _("Scan") }}</a>
|
||||
</qr-scanner>
|
||||
</div>
|
||||
<br>
|
||||
Address label (optional):<br>
|
||||
{{ _("Address label (optional):") }}<br>
|
||||
<input type="text" id="label_${i}" name="label_${i}" value="${label}">
|
||||
<br><br>
|
||||
Amount:<br>
|
||||
{{ _("Amount:") }}<br>
|
||||
<input style="width: 200px" type="number" name="amount_${i}" oninput="calculateConvertedUnit(${i})" id="amount_${i}" min=0 step="${step}" autocomplete="off" value="${amount}">
|
||||
<input type="hidden" name="btc_amount_${i}" id="btc_amount_${i}">
|
||||
<div class="mobile-only" style="margin-top: 10px;"></div>
|
||||
${assetSelector}
|
||||
<span class="note max-btn" style="margin-left: 5px;" id="send_max_${i}" onclick="setMaxAmount(${i})">(send max)</span>
|
||||
<span class="note max-btn" style="margin-left: 5px;" id="send_max_${i}" onclick="setMaxAmount(${i})">({{ _("send max") }})</span>
|
||||
<div>
|
||||
<span class="note" id="converted_unit_amount_${i}">-</span> <span class="note" id="converted_unit_label_${i}">${convertedUnit}</span> <span class="note" id="converted_unit_alt_${i}"></span>
|
||||
</div>
|
||||
|
|
@ -334,7 +334,7 @@
|
|||
async function setMaxAmount(i) {
|
||||
let amountInput = document.getElementById('amount_' + i);
|
||||
if (!validateAddress(i)) {
|
||||
showError('Please first enter a valid address');
|
||||
showError('{{ _("Please first enter a valid address") }}');
|
||||
return;
|
||||
}
|
||||
if(units[i] == 'sat' || units[i] == 'btc'){
|
||||
|
|
@ -382,7 +382,7 @@
|
|||
if (isNaN(amount)) {
|
||||
setVisibility('amount_errors_container', 'block');
|
||||
document.getElementById('amount_errors_container')
|
||||
.innerHTML = "Amount entered is invalid!";
|
||||
.innerHTML = '{{ _("Amount entered is invalid!") }}';
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -396,17 +396,17 @@
|
|||
if (isAboveWalletBalance(unit, amount)) {
|
||||
setVisibility('amount_errors_container', 'block');
|
||||
document.getElementById('amount_errors_container')
|
||||
.innerHTML = `You cannot send more than ${assetBalances[unit].balance} ${assetBalances[unit].label}!`;
|
||||
.innerHTML = `{{ _("You cannot send more than") }} ${assetBalances[unit].balance} ${assetBalances[unit].label}!`;
|
||||
return false;
|
||||
} else if (shouldSelectMoreCoins(unit, amount)) {
|
||||
setVisibility('amount_errors_container', 'block');
|
||||
document.getElementById('amount_errors_container')
|
||||
.innerHTML = "You need to select more coins to match your amount!";
|
||||
.innerHTML = '{{ _("You need to select more coins to match your amount!") }}';
|
||||
return false;
|
||||
} else if (isInvalidValue(unit, amount)) {
|
||||
setVisibility('amount_errors_container', 'block');
|
||||
document.getElementById('amount_errors_container')
|
||||
.innerHTML = "Invalid amount! Wrong unit?";
|
||||
.innerHTML = '{{ _("Invalid amount! Wrong unit?") }}';
|
||||
return false;
|
||||
} else {
|
||||
setVisibility('amount_errors_container', 'none');
|
||||
|
|
@ -442,7 +442,7 @@
|
|||
}
|
||||
}
|
||||
if (submitted && !amount) {
|
||||
showError('Please enter the amount that you wish to send', 5000);
|
||||
showError('{{ _("Please enter the amount that you wish to send") }}', 5000);
|
||||
}
|
||||
if (document.getElementById("amount_" + i).value == '') {
|
||||
amount = 0;
|
||||
|
|
@ -456,7 +456,7 @@
|
|||
for(let i in amounts) {
|
||||
if (!validateAddress(i)) {
|
||||
if (submitted) {
|
||||
showError('Please enter a valid recipient address', 5000);
|
||||
showError('{{ _("Please enter a valid recipient address") }}', 5000);
|
||||
}
|
||||
createPSBTButton.setAttribute('type', 'button');
|
||||
return;
|
||||
|
|
@ -499,13 +499,13 @@
|
|||
let advancedSettings = document.getElementById('advanced_settings');
|
||||
if (advancedSettings.style.display === 'block') {
|
||||
advancedSettings.style.display = 'none';
|
||||
advancedButton.innerHTML = 'Advanced ▶';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▶';
|
||||
if (isCoinSelectionActive()) {
|
||||
toggleExpand();
|
||||
}
|
||||
} else {
|
||||
advancedSettings.style.display = 'block';
|
||||
advancedButton.innerHTML = 'Advanced ▼';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▼';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -582,7 +582,7 @@
|
|||
document.getElementById('calculated_tx_fee').innerText = `: ${result.error}`;
|
||||
}
|
||||
} catch {
|
||||
document.getElementById('calculated_tx_fee').innerText = `: failed to calculate transaction fees`;
|
||||
document.getElementById('calculated_tx_fee').innerText = `: {{ _("failed to calculate transaction fees") }}`;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
{% if pending_psbt['address']|length == 1 %}
|
||||
<address-label data-address="{{ pending_psbt['address'][0] }}" data-label="{{ wallet.getlabel(pending_psbt['address'][0]) }}" data-wallet="{{ wallet_alias }}"></address-label>
|
||||
{% else %}
|
||||
{{ pending_psbt['address']|length }} Recipients
|
||||
{{ pending_psbt['address']|length }} {{ _("Recipients") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -31,13 +31,13 @@
|
|||
<form action="{{ url_for('wallets_endpoint.send_pending',wallet_alias=wallet_alias) }}" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="pending_psbt" value='{{ pending_psbt|tojson|safe }}'>
|
||||
<button type="submit" name="action" value="openpsbt" class="btn" style="margin-right: 5px;">Open</button>
|
||||
<button type="submit" name="action" value="openpsbt" class="btn" style="margin-right: 5px;">{{ _("Open") }}</button>
|
||||
</form>
|
||||
|
||||
<form action="{{ url_for('wallets_endpoint.send_pending',wallet_alias=wallet_alias) }}" method="POST">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="pending_psbt" value='{{ pending_psbt|tojson|safe }}'>
|
||||
<button type="submit" name="action" value="deletepsbt" class="btn danger" style="margin-left: 5px;" onclick="javascript: form.action='./?pending=True';">Delete</button>
|
||||
<button type="submit" name="action" value="deletepsbt" class="btn danger" style="margin-left: 5px;" onclick="javascript: form.action='./?pending=True';">{{ _("Delete") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th><th class="optional">Address</th><th>Amount</th><th class="optional">Time</th><th>Sigs</th><th>Actions</th>
|
||||
<th></th><th class="optional">{{ _("Address") }}</th><th>{{ _("Amount") }}</th><th class="optional">{{ _("Time") }}</th><th>{{ _("Sigs") }}</th><th>{{ _("Actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
}}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<td></td><td>No transactions yet.</td><td></td><td class="optional"></td><td class="optional"></td><td></td>
|
||||
<td></td><td>{{ _("No transactions yet.") }}</td><td></td><td class="optional"></td><td class="optional"></td><td></td>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
{% block content %}
|
||||
{% from 'wallet/send/components/send_nav.jinja' import send_nav %}
|
||||
{{ send_nav('send_pending', wallet_alias) }}
|
||||
<h1 class="padded">Transactions Awaiting Signing:</h1>
|
||||
<h1 class="padded">{{ _("Transactions Awaiting Signing:") }}</h1>
|
||||
<div class="table-holder">
|
||||
{% from 'wallet/send/pending/components/pending_psbt_table.jinja' import pending_psbt_table %}
|
||||
{{ pending_psbt_table(pending_psbts, wallet_alias, wallet, specter) }}
|
||||
</div><br>
|
||||
<span class="center note padded">Here you can manage PSBTs (Partially Signed Bitcoin Transactions) you have created but did not finish signing or broadcasted to the network.</span>
|
||||
<span class="center note padded">{{ _("Here you can manage PSBTs (Partially Signed Bitcoin Transactions) you have created but did not finish signing or broadcasted to the network.") }}</span>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -77,9 +77,9 @@
|
|||
{% for i in range(psbt['address']|length) %}
|
||||
<div style="word-break: break-all;">
|
||||
{% if "asset" in psbt %}
|
||||
Sending <b>{{ psbt["amount"][i] | btcamount }}</b> <asset-label data-asset="{{psbt['asset'][i]}}" data-label="{{psbt['asset'][i] | assetlabel}}"></asset-label>
|
||||
{{ _("Sending") }} <b>{{ psbt["amount"][i] | btcamount }}</b> <asset-label data-asset="{{psbt['asset'][i]}}" data-label="{{psbt['asset'][i] | assetlabel}}"></asset-label>
|
||||
{% else %}
|
||||
Sending <b>{{ psbt["amount"][i] | btcunitamount }}</b>
|
||||
{{ _("Sending") }} <b>{{ psbt["amount"][i] | btcunitamount }}</b>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
<span class="note">({{ psbt["amount"][i] | altunit }})</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
to<b>
|
||||
{{ _("to") }}<b>
|
||||
{% set addr_label = wallet.getlabel(psbt['address'][i]) %}
|
||||
<address-label data-address="{{ psbt['address'][i] }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
|
||||
</b>
|
||||
|
|
@ -102,32 +102,32 @@
|
|||
{% endfor %}
|
||||
{% if wallet.is_multisig %}
|
||||
<div class="log" style="margin: auto;">
|
||||
Acquired <b id="sigscount">{{ psbt['sigs_count'] }}</b> of <b>{{ wallet.sigs_required }}</b> signatures.
|
||||
{{ _("Acquired") }} <b id="sigscount">{{ psbt['sigs_count'] }}</b> {{ _("of") }} <b>{{ wallet.sigs_required }}</b> {{ _("signatures") }}.
|
||||
</div>
|
||||
{% endif %}
|
||||
<br>
|
||||
|
||||
<input type="checkbox" id="showdetails">
|
||||
<label for="showdetails" class="btn togglebtn">
|
||||
transaction details
|
||||
{{ _("transaction details") }}
|
||||
</label>
|
||||
<div class="toggle" id="tx_details" style="word-break: break-all;">
|
||||
<br>
|
||||
<h2 class="tx_details_header">Transaction Info</h2>
|
||||
<h2 class="tx_details_header">{{ _("Transaction Info") }}</h2>
|
||||
<p style="text-align: left; background-color: #131a24; padding: 10px; line-height: 2; border-radius: 15px;">
|
||||
<b>Total fee:</b> {{ psbt['fee'] | btc2sat }} sats {% if specter.price_check %}<span class="note"> ({{ psbt['fee'] | altunit }})</span>{% endif %}<br>
|
||||
<b>Fee rate:</b>
|
||||
<b>{{ _("Total fee:") }}/b> {{ psbt['fee'] | btc2sat }} sats {% if specter.price_check %}<span class="note"> ({{ psbt['fee'] | altunit }})</span>{% endif %}<br>
|
||||
<b>{{ _("Fee rate:") }}</b>
|
||||
{% if 'tx_full_size' in psbt %}
|
||||
{{ (psbt['fee'] / psbt['tx_full_size']) | feerate }} sat/vbyte<br>
|
||||
<b>Estimated size:</b> {{ psbt['tx_full_size'] }} vbytes<br>
|
||||
<b>{{ _("Estimated size:") }}</b> {{ psbt['tx_full_size'] }} vbytes<br>
|
||||
{% else %}
|
||||
{{ (psbt['fee'] / psbt['tx']['weight']) | feerate }} sat/vbyte<br>
|
||||
<b>Size:</b> {{ psbt['tx']['vsize'] }} vbytes<br>
|
||||
<b>{{ _("Size:") }}</b> {{ psbt['tx']['vsize'] }} vbytes<br>
|
||||
{% endif %}
|
||||
<b>Inputs count:</b> {{ psbt['tx']['vin'] | length }}<br>
|
||||
<b>Outputs count:</b> {{ psbt['tx']['vout'] | length }}
|
||||
<b>{{ _("Inputs count:") }}</b> {{ psbt['tx']['vin'] | length }}<br>
|
||||
<b>{{ _("Outputs count:") }}</b> {{ psbt['tx']['vout'] | length }}
|
||||
</p>
|
||||
<h2 class="tx_details_header">Inputs ({{psbt['tx']['vin'] | length}})</h2>
|
||||
<h2 class="tx_details_header">{{ _("Inputs") }} ({{psbt['tx']['vin'] | length}})</h2>
|
||||
{% for input in psbt['tx']['vin'] %}
|
||||
{% set tx = wallet.gettransaction(input['txid'], decode=true) %}
|
||||
{% set bg_color = '#131a24' %}
|
||||
|
|
@ -137,17 +137,17 @@
|
|||
{% endif %}
|
||||
<p class="tx_info" style="text-align: left; background-color: {{ bg_color }};">
|
||||
<b style="margin: auto;">Input #{{loop.index0}}</b><br><br>
|
||||
<b>Transaction id:</b><br>
|
||||
<b>{{ _("Transaction id:") }}</b><br>
|
||||
{{ explorer_link('tx', input['txid'], input['txid'], specter.explorer) }}
|
||||
(Output #{{ input['vout'] }})<br>
|
||||
({{ _("Output") }} #{{ input['vout'] }})<br>
|
||||
{% if tx %}
|
||||
{% if address %}
|
||||
{% set addr_label = wallet.getlabel(address) %}
|
||||
<b>Address:</b>
|
||||
<b>{{ _("Address:") }}</b>
|
||||
{{ address }}
|
||||
<br>
|
||||
{% if addr_label != address %}
|
||||
<b>Label:</b>
|
||||
<b>{{ _("Label:") }}</b>
|
||||
<address-label data-address="{{ address }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
|
||||
<br>
|
||||
{% endif %}
|
||||
|
|
@ -155,58 +155,58 @@
|
|||
{% if specter.is_liquid %}
|
||||
{% set amount = psbt['inputs'][loop.index0].get('value', -1) %}
|
||||
{% set asset = psbt['inputs'][loop.index0].get('asset', "") %}
|
||||
<b>Amount:</b> {{ amount|btcamount }} <asset-label data-asset="{{asset}}" data-label="{{asset | assetlabel}}"></asset-label>
|
||||
<b>{{ _("Amount:") }}</b> {{ amount|btcamount }} <asset-label data-asset="{{asset}}" data-label="{{asset | assetlabel}}"></asset-label>
|
||||
{% else %}
|
||||
{% set amount = psbt['inputs'][loop.index0].get('witness_utxo',{}).get('amount', -1) %}
|
||||
<b>Amount:</b> {{ amount|btcamount }} BTC {% if specter.price_check %}<span class="note"> ({{ amount | altunit }})</span>{% endif %}
|
||||
<b>{{ _("Amount:") }}</b> {{ amount|btcamount }} BTC {% if specter.price_check %}<span class="note"> ({{ amount | altunit }})</span>{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endfor %}
|
||||
<h2 class="tx_details_header">Outputs ({{psbt['tx']['vout']|length}})</h2>
|
||||
<h2 class="tx_details_header">{{ _("Outputs") }} ({{psbt['tx']['vout']|length}})</h2>
|
||||
{% for output in psbt['tx']['vout'] if output['scriptPubKey']['type'] != 'fee' %}
|
||||
{% set address = output['scriptPubKey']['addresses'][0] if "addresses" in output["scriptPubKey"] else output["scriptPubKey"]["address"] %}
|
||||
{% set bg_color = '#154984' if wallet.is_address_mine(address) else '#131a24' %}
|
||||
<p class="tx_info" style="text-align: left; background-color: {{ bg_color }};">
|
||||
<b>Output #{{loop.index0}}</b> {% if address not in psbt['address'] %}(Change){% endif %}<br><br>
|
||||
<b>{{ _("Output") }} #{{loop.index0}}</b> {% if address not in psbt['address'] %}(Change){% endif %}<br><br>
|
||||
{% set addr_label = wallet.getlabel(address) %}
|
||||
<b>Address:</b>
|
||||
<b>{{ _("Address:") }}</b>
|
||||
{{ address }}
|
||||
<br>
|
||||
{% if addr_label != address %}
|
||||
<b>Label:</b>
|
||||
<b>{{ _("Label:") }}</b>
|
||||
<address-label data-address="{{ address }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
|
||||
<br>
|
||||
{% endif %}
|
||||
<b>Amount:</b> {{ output['value']|btcamount }} BTC {% if specter.price_check %}<span class="note"> ({{ output['value'] | altunit }})</span>{% endif %}
|
||||
<b>{{ _("Amount:") }}</b> {{ output['value']|btcamount }} BTC {% if specter.price_check %}<span class="note"> ({{ output['value'] | altunit }})</span>{% endif %}
|
||||
</p>
|
||||
{% endfor %}
|
||||
Raw PSBT:<textarea id="raw-psbt" disabled style="background-color: #131a24;">{{ psbt['base64'] }}</textarea>
|
||||
{{ _("Raw PSBT:") }}<textarea id="raw-psbt" disabled style="background-color: #131a24;">{{ psbt['base64'] }}</textarea>
|
||||
<div class="row break-row-mobile" style="margin:auto">
|
||||
<a id="download-psbt-btn" class="btn"
|
||||
download="binary_{{ psbt['tx']['hash'] }}.psbt" href="data:application/octet-stream;base64;content-disposition=attachment,{{ psbt['base64'] }}">
|
||||
<img src="{{ url_for('static', filename='img/file.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Save binary
|
||||
{{ _("Save binary") }}
|
||||
</a>
|
||||
<a id="download-psbt-btn" class="btn"
|
||||
download="base64_{{ psbt['tx']['hash'] }}.psbt" href="data:text/plain;content-disposition=attachment,{{ psbt['base64'] }}">
|
||||
<img src="{{ url_for('static', filename='img/file.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Save base64
|
||||
{{ _("Save base64") }}
|
||||
</a>
|
||||
<a id="copy-raw-psbt-btn" class="btn">
|
||||
<img src="{{ url_for('static', filename='img/copy.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Copy Raw PSBT
|
||||
{{ _("Copy Raw PSBT") }}
|
||||
</a>
|
||||
<a id="show-qr-psbt-btn" class="btn">
|
||||
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" style="width: 26px; margin: 0px;" class="svg-white">
|
||||
Show QR code
|
||||
{{ _("Show QR code") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="signing_container" class="signing_container flex-column {% if psbt['raw'] %}hidden{% endif %}" style="text-align: center;">
|
||||
<p style="margin-bottom: 15px; margin-top: 0px;">Sign transaction with your:</p>
|
||||
<p style="margin-bottom: 15px; margin-top: 0px;">{{ _("Sign transaction with your:") }}</p>
|
||||
{% for device in wallet.devices %}
|
||||
<button type="button" class="btn signing-column-btn" id="{{ device.alias }}_tx_sign_btn" {{ 'disabled style=background-color:#303c49;' if device.alias in psbt["devices_signed"] else '' }}>
|
||||
{{ device.name }} {% if device.alias in psbt['devices_signed'] %} (✔) {% endif %}
|
||||
|
|
@ -217,26 +217,26 @@
|
|||
{# ===================== Possible tx signers' outputs ===================== #}
|
||||
|
||||
<div id="ready_container" class="output_option {% if not psbt['raw'] %}hidden{% endif %}">
|
||||
<p>Transaction is ready to send (✔)</p>
|
||||
<p>{{ _("Transaction is ready to send") }} (✔)</p>
|
||||
<div class="output_option">
|
||||
<a class="btn centered" id="send_tx_btn">Send transaction</a>
|
||||
<a class="btn centered" id="send_tx_btn">{{ _("Send transaction") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="paste_signed" class="output_option {% if psbt['raw'] %}hidden{% endif %}" style="margin-top:20px">
|
||||
<button type="button" onclick="showPageOverlay('paste-tx-popup')" class="btn centered" id="pastetx">Paste signed transaction</a>
|
||||
<button type="button" onclick="showPageOverlay('paste-tx-popup')" class="btn centered" id="pastetx">{{ _("Paste signed transaction") }}</a>
|
||||
</div>
|
||||
<div class="hidden" id="paste-tx-popup">
|
||||
<h1>Paste signed transaction</h1>
|
||||
<input type="text" id="paste-tx-text" placeholder="Paste your transaction here">
|
||||
<h1>{{ _("Paste signed transaction") }}</h1>
|
||||
<input type="text" id="paste-tx-text" placeholder="{{ _('Paste your transaction here') }}">
|
||||
<br><br>
|
||||
<button type="button" onclick="currentSigningDevice = '';combine(document.getElementById('paste-tx-text').value);hidePageOverlay();document.getElementById('paste-tx-text').value='';" class="btn centered">Confirm</button>
|
||||
<button type="button" onclick="currentSigningDevice = '';combine(document.getElementById('paste-tx-text').value);hidePageOverlay();document.getElementById('paste-tx-text').value='';" class="btn centered">{{ _("Confirm") }}</button>
|
||||
</div>
|
||||
|
||||
{% include 'includes/overlay/broadcast_tx.jinja' %}
|
||||
|
||||
<div class="row padded">
|
||||
<input type="hidden" name="pending_psbt" value='{{psbt|tojson|safe}}'>
|
||||
<button id="deletepsbt_btn" type="submit" name="action" value="deletepsbt" class="btn danger centered" style="margin-left: 5px;">Delete Transaction</button>
|
||||
<button id="deletepsbt_btn" type="submit" name="action" value="deletepsbt" class="btn danger centered" style="margin-left: 5px;">{{ _("Delete Transaction") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
@ -415,7 +415,7 @@
|
|||
}
|
||||
|
||||
async function broadcastLocal(tx) {
|
||||
showNotification("Sending transaction...");
|
||||
showNotification("{{ _('Sending transaction...') }}");
|
||||
let url="{{ url_for('wallets_endpoint.broadcast', wallet_alias=wallet.alias) }}";
|
||||
|
||||
var formData = new FormData();
|
||||
|
|
@ -437,15 +437,15 @@
|
|||
hidePageOverlay();
|
||||
window.location.replace("{{ url_for('wallets_endpoint.history', wallet_alias=wallet.alias) }}");
|
||||
} else {
|
||||
showError("Server failed to broadcast transactions!\n" + jsonResponse.error);
|
||||
showError("{{ _('Server failed to broadcast transactions!') }}\n" + jsonResponse.error);
|
||||
}
|
||||
} catch(e) {
|
||||
showError("Server failed to broadcast transactions!\n" + e);
|
||||
showError("{{ _('Server failed to broadcast transactions!') }}\n" + e);
|
||||
}
|
||||
}
|
||||
|
||||
async function broadcastBlockExplorer(tx, explorer, useTor) {
|
||||
showNotification("Sending transaction...", 0);
|
||||
showNotification("{{ _('Sending transaction...') }}", 0);
|
||||
let url = "{{ url_for('wallets_endpoint.broadcast_blockexplorer', wallet_alias=wallet.alias) }}";
|
||||
|
||||
|
||||
|
|
@ -470,10 +470,10 @@
|
|||
hidePageOverlay();
|
||||
window.location.replace("{{ url_for('wallets_endpoint.history', wallet_alias=wallet.alias) }}");
|
||||
} else {
|
||||
showError("Server failed to broadcast transactions!\n" + jsonResponse.error);
|
||||
showError("{{ _('Server failed to broadcast transactions!') }}\n" + jsonResponse.error);
|
||||
}
|
||||
} catch(e) {
|
||||
showError("Server failed to broadcast transactions!\n" + e);
|
||||
showError("{{ _('Server failed to broadcast transactions!') }}\n" + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,20 +13,20 @@
|
|||
</style>
|
||||
<br>
|
||||
<nav class="row collapse-on-mobile">
|
||||
<button type="button" id="info_settings_tab_btn" class="btn radio left checked" onclick="setSettingsView('info')"> Wallet Info </button>
|
||||
<button type="button" id="export_settings_tab_btn" class="btn radio" onclick="setSettingsView('export')"> Export </button>
|
||||
<button type="button" id="advanced_settings_tab_btn" class="btn radio right" onclick="setSettingsView('advanced')"> Advanced </button>
|
||||
<button type="button" id="info_settings_tab_btn" class="btn radio left checked" onclick="setSettingsView('info')"> {{ _("Wallet Info") }} </button>
|
||||
<button type="button" id="export_settings_tab_btn" class="btn radio" onclick="setSettingsView('export')"> {{ _("Export") }} </button>
|
||||
<button type="button" id="advanced_settings_tab_btn" class="btn radio right" onclick="setSettingsView('advanced')"> {{ _("Advanced") }} </button>
|
||||
<a href="javascript:void(0);" class="mobile-nav-icon" onclick="toggleMobileNav(this, `{{ url_for('static', filename='img/caret-down.svg') }}`, `{{ url_for('static', filename='img/caret-up.svg') }}`)">
|
||||
<img style="width: 36px;" src="{{ url_for('static', filename='img/caret-down.svg') }}" class="svg-white"/>
|
||||
</a>
|
||||
</nav><br>
|
||||
<div id="wallet_info_settings_tab" style="width: 100%">
|
||||
<h1 style="font-size: 1.7em;">Wallet Info</h1><br>
|
||||
<h1 style="font-size: 1.7em;">{{ _("Wallet Info") }}</h1><br>
|
||||
{% if wallet.is_multisig %}
|
||||
<h2 class="subtitle">Devices</h2>
|
||||
<h2 class="subtitle">{{ _("Devices") }}</h2>
|
||||
<p style="text-align: center; font-size: 1.2em;">{{ wallet.sigs_required }} out of {{ wallet.keys|length }} multisig</p>
|
||||
{% else %}
|
||||
<h2 class="subtitle">Device</h2>
|
||||
<h2 class="subtitle">{{ _("Device") }}</h2>
|
||||
{% endif %}
|
||||
<div class="row overflow">
|
||||
{% for device in wallet.devices %}
|
||||
|
|
@ -38,28 +38,28 @@
|
|||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button onclick="toggleKeysList()" id="toggle_keys_list" type="button" class="btn centered padded">Show keys details</button>
|
||||
<button onclick="toggleKeysList()" id="toggle_keys_list" type="button" class="btn centered padded">{{ _("Show keys details") }}</button>
|
||||
<br>
|
||||
<div id="keys_list" class="center" style="margin: auto; display: none; width: 85%;">
|
||||
<label style="font-size: 1.3em;">Use SLIP-132: </label>
|
||||
<label style="font-size: 1.3em;">{{ _("Use SLIP-132:") }} </label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="use_slip_132" onchange="toggleKeyDisplay()">
|
||||
<span class="slider"></span>
|
||||
</label><br>
|
||||
<p class="note" style="margin-top: 5px; max-width: 90%;">SLIP-132 extended public keys (xpubs) are a different format for xpubs which usually starts with zpub or ypub.</p>
|
||||
<p class="note" style="margin-top: 5px; max-width: 90%;">{{ _("SLIP-132 extended public keys (xpubs) are a different format for xpubs which usually starts with zpub or ypub.") }}</p>
|
||||
<br>
|
||||
{% for i in range(wallet.devices | length) %}
|
||||
{% set device = wallet.devices[i] %}
|
||||
{% set key = wallet.keys[i] %}
|
||||
<h1>Signer {% if wallet.is_multisig %}{{ loop.index }} {% endif %}- {{ device.name }}</h1>
|
||||
<h1>{{ _("Signer") }} {% if wallet.is_multisig %}{{ loop.index }} {% endif %}- {{ device.name }}</h1>
|
||||
<div class="table-holder">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network</th>
|
||||
<th>Purpose</th>
|
||||
<th class="optional">Derivation</th>
|
||||
<th class="optional">Key</th>
|
||||
<th>{{ _("Network") }}</th>
|
||||
<th>{{ _("Purpose") }}</th>
|
||||
<th class="optional">{{ _("Derivation") }}</th>
|
||||
<th class="optional">{{ _("Key") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="wallet_export_settings_tab">
|
||||
<h1 style="font-size: 1.7em;">Export Wallet</h1>
|
||||
<h1 style="font-size: 1.7em;">{{ _("Export Wallet") }}</h1>
|
||||
<div class="card center">
|
||||
{% set supports_export_to_device = [] %}
|
||||
{% for device in wallet.devices if device.exportable_to_wallet %}
|
||||
|
|
@ -89,9 +89,9 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if supports_export_to_device != [] %}
|
||||
<h2 class="subtitle">Export To Device</h1>
|
||||
<h2 class="subtitle">{{ _("Export To Device") }}</h1>
|
||||
<p class="center">
|
||||
Import this wallet to a device by scanning its QR code or importing its data file.
|
||||
{{ _("Import this wallet to a device by scanning its QR code or importing its data file.") }}
|
||||
</p>
|
||||
|
||||
{% for device in wallet.devices %}
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
download="{{ wallet.name | ascii20 }}.txt"
|
||||
href="data:text/plain;charset=US-ASCII,{{ device.export_wallet(wallet) }}"
|
||||
{% if device.device_type == 'coldcard' %}
|
||||
onclick="showNotification('Import wallet file to your ColdCard: Settings -> Multisig Wallets -> Import from SD', 0)"
|
||||
onclick="showNotification(`{{ _('Import wallet file to your ColdCard: Settings -> Multisig Wallets -> Import from SD') }}`, 0)"
|
||||
{% endif %}
|
||||
class="btn centered padded">
|
||||
Save {{ device.name }} file
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
{% elif device.wallet_export_type == 'qr' %}
|
||||
<button onclick="showPageOverlay('{{ device.alias }}_export_qr_code')" type="button" class="btn centered padded">Show {{ device.name }} QR Code</button>
|
||||
<div id="{{ device.alias }}_export_qr_code" style="display: none;">
|
||||
<h1>Scan this with your {{ device.name }} device </h1>
|
||||
<h1>{{ _("Scan this with your") }} {{ device.name }} {{ _("device") }} </h1>
|
||||
<qr-code value="{{ device.export_wallet(wallet) }}" width="400" scalable></qr-code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -118,61 +118,61 @@
|
|||
<div class="section-separator"></div>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="subtitle">Export To Wallet Software</h2>
|
||||
<h2 class="subtitle">{{ _("Export To Wallet Software") }}</h2>
|
||||
<p class="center">
|
||||
Import this wallet to another Specter instance or other supported wallet softwares by scanning the QR code or importing the JSON file.
|
||||
{{ _("Import this wallet to another Specter instance or other supported wallet softwares by scanning the QR code or importing the JSON file.") }}
|
||||
</p>
|
||||
<button onclick="showPageOverlay('account_map_export')" type="button" class="btn centered padded">Export</button>
|
||||
<button onclick="showPageOverlay('account_map_export')" type="button" class="btn centered padded">{{ _("Export") }}</button>
|
||||
<div class="section-separator"></div>
|
||||
<h2 class="subtitle">Export PDF backup</h2>
|
||||
<h2 class="subtitle">{{ _("Export PDF backup") }}</h2>
|
||||
<p style="max-width: 500px;" class="center">
|
||||
It is recommended that you print and save this wallet backup file with each of your devices.
|
||||
{{ _("It is recommended that you print and save this wallet backup file with each of your devices.") }}
|
||||
</p>
|
||||
<button id="pdf-wallet-download" type="button" class="btn centered padded" style="margin: 30px auto;">Save Backup PDF</button>
|
||||
<span id="advanced_pdf_export_toggle" style="cursor: pointer;" onclick="togglePDFExportAdvanced()">Advanced ▶</span>
|
||||
<span id="advanced_pdf_export_toggle" style="cursor: pointer;" onclick="togglePDFExportAdvanced()">{{ _("Advanced") }} ▶</span>
|
||||
<br>
|
||||
<div style="margin: 0 auto;" class="center hidden" id="advanced_pdf_export_container"><br>
|
||||
<label style="font-size: 1em;"">Use SLIP-132: </label>
|
||||
<label style="font-size: 1em;">{{ _("Use SLIP-132:") }} </label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="use_slip_132_pdf" style="margin: auto;" onchange="toggleKeyDisplay()">
|
||||
<span class="slider"></span>
|
||||
</label><br>
|
||||
</div>
|
||||
<br>
|
||||
<p class="note center" style="margin-top: 5px; max-width: 90%;">To format master keys on the PDF backup document.</p>
|
||||
<p class="note center" style="margin-top: 5px; max-width: 90%;">{{ _("To format master keys on the PDF backup document.") }}</p>
|
||||
<p style="max-width: 500px;" class="note center">
|
||||
This backup includes all the information you'd need to restore your wallet in Specter Desktop or other compatible wallets including Bitcoin Core itself.<br>
|
||||
{{ _("This backup includes all the information you'd need to restore your wallet in Specter Desktop or other compatible wallets including Bitcoin Core itself.") }}<br>
|
||||
<p>
|
||||
<p style="max-width: 500px;" class="note center">
|
||||
It is recommended that you keep this file private, as it contains all the information needed to track all of the wallet balance and transactions history.
|
||||
{{ _("It is recommended that you keep this file private, as it contains all the information needed to track all of the wallet balance and transactions history.") }}
|
||||
<p>
|
||||
{% include 'wallet/components/wallet_pdf.jinja' %}
|
||||
{% include "wallet/settings/components/export_wallet_account_map.jinja" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="wallet_advanced_settings_tab">
|
||||
<h1 style="font-size: 1.7em;">Advanced Options</h1>
|
||||
<h1 style="font-size: 1.7em;">{{ _("Advanced Options") }}</h1>
|
||||
<div class="card">
|
||||
<h2 class="subtitle">Keypool control</h2>
|
||||
<h2 class="subtitle">{{ _("Keypool control") }}</h2>
|
||||
<form action="." method="POST" class="padded" style="max-width: 500px">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<div>Currently watching {{ wallet.keypool }} receiving and {{ wallet.change_keypool }} change addresses.</div>
|
||||
<div>{{ _("Currently watching") }} {{ wallet.keypool }} {{ _("receiving and") }} {{ wallet.change_keypool }} {{ _("change addresses.") }}</div>
|
||||
<div class="row aligned">
|
||||
<div style="flex-grow: 1">Watch <input type="number" name="keypooladd" value="100" step="1" style="margin: 10px" class="inline"> more addresses </div>
|
||||
<button type="submit" name="action" value="keypoolrefill" class="btn" style="max-width: 130px; margin-left: 20px">Add</button>
|
||||
<div style="flex-grow: 1">{{ _("Watch") }} <input type="number" name="keypooladd" value="100" step="1" style="margin: 10px" class="inline"> {{ _("more addresses") }} </div>
|
||||
<button type="submit" name="action" value="keypoolrefill" class="btn" style="max-width: 130px; margin-left: 20px">{{ _("Add") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="section-separator"></div>
|
||||
<h2 class="subtitle">Blockchain rescan</h2>
|
||||
<h2 class="subtitle">{{ _("Blockchain rescan") }}</h2>
|
||||
<form action="." method="POST" class="padded" style="max-width: 500px">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
{% if wallet.rescan_progress %}
|
||||
<div class="row aligned">
|
||||
<div style="margin-right: 30px; flex-grow: 1;">Rescanning blockchain: <span id="{{ wallet.alias }}_wallet_rescan_percents">{{"%.2f" % (wallet.rescan_progress*100)}}</span>%</div>
|
||||
<button type="submit" name="action" value="abortrescan" class="btn" style="max-width: 130px; margin-left: 20px">Abort rescan</button>
|
||||
<button type="submit" name="action" value="abortrescan" class="btn" style="max-width: 130px; margin-left: 20px">{{ _("Abort rescan") }}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
Rescan blockchain from block:<br>
|
||||
{{ _("Rescan blockchain from block:") }}<br>
|
||||
<div class="row aligned">
|
||||
{% if specter.info.chain == "main" %}
|
||||
{% set startblock=(481824 if not specter.info['pruned'] or specter.info['pruneheight'] < 481824 else specter.info['pruneheight']) %}
|
||||
|
|
@ -183,31 +183,31 @@
|
|||
<button type="submit" name="action" value="rescanblockchain" class="btn">Rescan</button>
|
||||
</div>
|
||||
{% if specter.info['pruned'] %}
|
||||
<div class="note center full-width">Note: You are using a pruned node. Rescan is limited by the pruned height to block: {{specter.info['pruneheight']}}</div>
|
||||
<div class="note center full-width">{{ _("Note: You are using a pruned node. Rescan is limited by the pruned height to block:") }} {{specter.info['pruneheight']}}</div>
|
||||
{% endif %}
|
||||
<div class="note center"><b>481824</b> was the first Segwit block.</div>
|
||||
<div class="note center"><b>481824</b> {{ _("was the first Segwit block.") }}</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
<form action="." method="POST" class="padded" style="max-width: 500px">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
{% if specter.info["utxorescan"] %}
|
||||
<div class="row aligned padded">
|
||||
<div style="flex-grow: 1">UTXO rescan in process: <span id="wallet_rescan_percents">{{ "%.2f"|format(specter.info["utxorescan"]) }}</span>%.</div>
|
||||
<button type="submit" name="action" value="abortrescanutxo" class="btn" style="max-width: 130px; margin-left: 20px">Abort rescan</button>
|
||||
<div style="flex-grow: 1">{{ _("UTXO rescan in process:") }} <span id="wallet_rescan_percents">{{ "%.2f"|format(specter.info["utxorescan"]) }}</span>%.</div>
|
||||
<button type="submit" name="action" value="abortrescanutxo" class="btn" style="max-width: 130px; margin-left: 20px">{{ _("Abort rescan") }}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row aligned">
|
||||
<div style="flex-grow: 1">Or rescan only unspent transactions:
|
||||
<div style="flex-grow: 1">{{ _("Or rescan only unspent transactions:") }}
|
||||
</div>
|
||||
<button type="submit" name="action" value="rescanutxo" class="btn" style="max-width: 180px; margin-left: 20px">
|
||||
Rescan UTXO
|
||||
{{ _("Rescan UTXO") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Rescanning UTXO set</span><br><br>
|
||||
Only unspent transactions will be imported, so you won't see the full history, but your balance will be there.<br><br>
|
||||
<b>Note:</b> Make sure you have enough addresses in the keypool, otherwise not all unspent transactions may be found.
|
||||
<span class="info__title">{{ _("Rescanning UTXO set") }}</span><br><br>
|
||||
{{ _("Only unspent transactions will be imported, so you won't see the full history, but your balance will be there.") }}<br><br>
|
||||
<b>{{ _("Note:") }}</b> {{ _("Make sure you have enough addresses in the keypool, otherwise not all unspent transactions may be found.") }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -215,22 +215,22 @@
|
|||
</div>
|
||||
{% if specter.info['pruned'] %}
|
||||
<div class="row" style="margin-top: 10px">
|
||||
<label for="use_explorer"><input style="width: auto; min-width: auto;" type="checkbox" id="use_explorer" name="use_explorer" checked> fetch missing data from block explorer
|
||||
<label for="use_explorer"><input style="width: auto; min-width: auto;" type="checkbox" id="use_explorer" name="use_explorer" checked> {{ _("fetch missing data from block explorer") }}
|
||||
<tool-tip title="Fetch missing data">
|
||||
You are using a pruned node. Blocks for some transactions may be missing.<br><br>
|
||||
Check this checkbox if you want to fetch missing information from block explorer.
|
||||
{{ _("You are using a pruned node. Blocks for some transactions may be missing.") }}<br><br>
|
||||
{{ _("Check this checkbox if you want to fetch missing information from block explorer.") }}
|
||||
</tool-tip>
|
||||
</label>
|
||||
</div>
|
||||
<div id="utxo-explorer">
|
||||
<br>
|
||||
Block explorer URL:
|
||||
{{ _("Block explorer URL:") }}
|
||||
{% include "components/explorer_select.jinja" %}
|
||||
<span class="warning" id="utxo-warning" style="display: inline-block; max-width:300px;">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>Careful with block explorers!<br>
|
||||
This may have privacy implication!<br>
|
||||
Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.<br>
|
||||
"They" will know what transactions you are interested in.
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>{{ _("Careful with block explorers!") }}<br>
|
||||
{{ _("This may have privacy implications!") }}<br>
|
||||
{{ _("Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.") }}<br>
|
||||
{{ _('"They" will know what transactions you are interested in.') }}
|
||||
</span>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
|
@ -249,10 +249,10 @@
|
|||
{% endif %}
|
||||
</form>
|
||||
<div class="section-separator"></div>
|
||||
<h2 class="subtitle">Delete wallet</h2>
|
||||
<h2 class="subtitle">{{ _("Delete wallet") }}</h2>
|
||||
<form action="." method="POST" class="padded" style="max-width: 500px">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" id="delete_wallet" name="action" value="deletewallet" class="btn danger centered" style="max-width: 160px;">Delete Wallet</button>
|
||||
<button type="submit" id="delete_wallet" name="action" value="deletewallet" class="btn danger centered" style="max-width: 160px;">{{ _("Delete Wallet") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -266,10 +266,10 @@
|
|||
let keysList = document.getElementById('keys_list');
|
||||
if (keysList.style.display !== 'none') {
|
||||
keysList.style.display = 'none';
|
||||
titleButton.innerHTML = 'Show keys details';
|
||||
titleButton.innerHTML = '{{ _("Show keys details") }}';
|
||||
} else {
|
||||
keysList.style.display = 'block';
|
||||
titleButton.innerHTML = 'Hide keys details';
|
||||
titleButton.innerHTML = '{{ _("Hide keys details") }}';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,10 +299,10 @@
|
|||
let advancedSettings = document.getElementById(`advanced_pdf_export_container`);
|
||||
if (advancedSettings.classList.contains("hidden")) {
|
||||
advancedSettings.classList.remove("hidden");
|
||||
advancedButton.innerHTML = 'Advanced ▼';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▼';
|
||||
} else {
|
||||
advancedSettings.classList.add("hidden");
|
||||
advancedButton.innerHTML = 'Advanced ▶';
|
||||
advancedButton.innerHTML = '{{ _("Advanced") }} ▶';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||