Merge branch 'master' into favicon-for-light-and-dark-mode

This commit is contained in:
Manolis Mandrapilias 2023-03-02 20:22:59 +01:00 committed by GitHub
commit 801e3aa4a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 502 additions and 304 deletions

View file

@ -46,7 +46,6 @@ describe('Connecting nodes', () => {
}
else {
cy.log('Test was skipped: Elementsd was not started by the test script.')
Cypress.runner.stop()
}
})
})

View file

@ -5,7 +5,7 @@ describe('Test adding different devices', () => {
})
it('Validity of device name', () => {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
cy.get('#btn_new_device').click()
cy.get('#trezor_device_card').click()
cy.get("#device_name").type("'invalid")
@ -16,7 +16,7 @@ describe('Test adding different devices', () => {
});
it('Filter devices', () => {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
cy.get('#btn_new_device').click()
cy.contains('Select your signing device')
cy.get('#device-type-searchbar').type("Specter")
@ -27,7 +27,7 @@ describe('Test adding different devices', () => {
})
it('Electrum device', () => {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
cy.get('#btn_new_device').click()
cy.get('#electrum_device_card').click()
cy.get('#device_name').type("New Electrum Device")

View file

@ -9,7 +9,7 @@ describe('Test plugins', () => {
cy.get('main').contains('Addresses').click()
// Click on the first address
cy.contains('td', '#0').siblings().contains('bcrt').click()
cy.get('#associate-btn').click()
cy.get('[data-cy="associate-address-with-service-btn"]').click()
cy.contains("Service integration requires an authentication method that includes a password")
// OK, let's set a password
cy.get('select').select("passwordonly")

View file

@ -14,9 +14,8 @@ describe('Test QR code signing flow', () => {
cy.get('#messageDerivationPath').should('have.value', 'm/84h/1h/0h/0/0')
cy.get('#message').type('The DIY is the best signing device.')
cy.get('#diy_ghost_qr_sign_msg_btn').click()
cy.get('#diy_ghost_sign_msg_qr > h2').contains('Scan this QR code')
// To close the overlay (no cancel button here)
cy.get('#page_overlay_popup').click()
cy.get('[data-cy="scan-qr-code-for-msg-signing-overlay-headline"]').contains('Scan this QR code')
cy.get('[data-cy="scan-qr-code-for-msg-signing-overlay-close-btn"]').click()
})
it('No QR message signing button for a Trezor', () => {
@ -27,14 +26,14 @@ describe('Test QR code signing flow', () => {
// Only USB signing available for a Trezor device
cy.contains('Sign message via USB').should('be.visible')
cy.contains('Sign message via QR code').should('not.exist')
cy.get('#page_overlay_popup').click()
cy.get('[data-cy="close-msg-signing-overlay-btn"]').click()
cy.changeDeviceType("DIY ghost", "specter")
})
it('No message signing with Electrum', () => {
cy.get('body').then(($body) => {
if (!$body.text().includes("Electrum Device")) {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
cy.get('#btn_new_device').click()
cy.get('#electrum_device_card').click()
cy.get('#device_name').type("Electrum Device")
@ -64,4 +63,4 @@ describe('Test QR code signing flow', () => {
cy.deleteWallet("Wallet that cannot sign messages")
cy.deleteDevice("Electrum Device")
})
})
})

View file

@ -29,13 +29,13 @@ import 'cypress-wait-until';
Cypress.Commands.add("addDevice", (name, device_type, mnemonic) => {
cy.get('body').then(($body) => {
if ($body.text().includes(name)) {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
cy.contains(name).click()
return
}
cy.get('#side-content').click()
if (!cy.get('#btn_new_device').isVisible) {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
}
cy.get('#btn_new_device').click()
cy.contains('Manual configuration').click()
@ -66,7 +66,7 @@ Cypress.Commands.add("addHotDevice", (name, node_type) => {
}
cy.get('#side-content').click()
if (!cy.get('#btn_new_device').isVisible) {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
}
cy.get('#btn_new_device').click( {force: true} )
cy.get(`#${node_type}core_device_card`).click()
@ -80,7 +80,7 @@ Cypress.Commands.add("addHotDevice", (name, node_type) => {
Cypress.Commands.add("deleteDevice", (name) => {
cy.get('body').then(($body) => {
if ($body.text().includes(name)) {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
cy.contains(name).click( {force: true} )
cy.get('#forget_device').click()
cy.reload()
@ -94,9 +94,9 @@ Cypress.Commands.add("deleteDevice", (name) => {
Cypress.Commands.add("changeDeviceType", (deviceName, newType) => {
cy.get('body').then(($body) => {
if ($body.text().includes(deviceName)) {
cy.get('#toggle_devices_list').then(($devicesList) => {
cy.get('#devices_toggle').then(($devicesList) => {
if ($devicesList.text() == 'Devices ▸') {
cy.get('#toggle_devices_list').click()
cy.get('#devices_toggle').click()
}
})
let refDeviceName = deviceName.toLowerCase().replace(/ /g,"_");
@ -208,10 +208,10 @@ Cypress.Commands.add("deleteWallet", (walletName) => {
Cypress.Commands.add("selectWallet", (walletName) => {
cy.get('body').then(($body) => {
if ($body.text().includes(walletName)) {
cy.get('#toggle_wallets_list').then(($walletsList) => {
cy.get('#wallets_toggle').then(($walletsList) => {
// Only toggle the wallets list if it is not already toggled
if ($walletsList.text() == 'Wallets ▸') {
cy.get('#toggle_wallets_list').click()
cy.get('#wallets_toggle').click()
}
cy.get(`[data-cy='wallet-sidebar-btn-${walletName}']`).click()
})

View file

@ -1,26 +1,61 @@
const fs = require('fs')
const os = require('os')
const path = require('path')
const crypto = require('crypto')
const readLastLines = require('read-last-lines');
const downloadloc = require('./downloadloc');
const { loggers } = require('winston');
const appName = downloadloc.appName()
const appNameLower = appName.toLowerCase()
const isDev = process.env.NODE_ENV === "development"
const unresolvedDevFolder = process.env.SPECTER_DATA_FOLDER || "~/.specter_dev"
const devFolder = unresolvedDevFolder.replace(/^~/, os.homedir());
const prodFolder = path.resolve(os.homedir(), `.${appNameLower}`)
const isMac = process.platform === 'darwin'
let appSettingsPath
let specterdDirPath
let specterAppLogPath
let versionData
try {
// Use different version-data.jsons
// Should look like this:
// {
// "version": "v2.0.0-pre32",
// "sha256": "aa049abf3e75199bad26fbded08ee5911ad48e325b42c43ec195136bd0736785"
// }
if (isDev) {
let versionDataPath = `${devFolder}/version-data.json`
try {
versionData = require(versionDataPath)
}
catch {
}
}
else {
try {
versionData = require('./version-data.json')
console.log(versionData)
} catch (e) {
console.log('Could not find default version data configurations...'+e)
}
catch (e) {
console.log('Could not find default version data configurations: '+e)
versionData = {
"version": "",
"sha256": ""
}
}
}
if (isDev) {
appSettingsPath = `${devFolder}/app_settings.json`
specterdDirPath = `${devFolder}/specterd-binaries`
specterAppLogPath = `${devFolder}/specterApp.log`
}
else {
appSettingsPath = `${prodFolder}/app_settings.json`
specterdDirPath = `${prodFolder}/specterd-binaries`
specterAppLogPath = `${prodFolder}/specterApp.log`
}
const appSettingsPath = path.resolve(require('os').homedir(), `.${appNameLower}/app_settings.json`)
const specterdDirPath = path.resolve(require('os').homedir(), `.${appNameLower}/specterd-binaries`)
const specterAppLogPath = path.resolve(require('os').homedir(), `.${appNameLower}/specterApp.log`)
function getFileHash(filename, callback) {
let shasum = crypto.createHash('sha256')
@ -45,8 +80,8 @@ function getAppSettings() {
basicAuthPass: '',
tor: false,
proxyURL: "socks5://127.0.0.1:9050",
specterdVersion: versionData.version,
specterdHash: versionData.sha256,
specterdVersion: (versionData && versionData.version !== undefined) ? versionData.version : 'unknown',
specterdHash: (versionData && versionData.sha256 !== undefined) ? versionData.sha256 : 'unknown',
specterdCLIArgs: "",
versionInitialized: false
}
@ -82,5 +117,9 @@ module.exports = {
getAppSettings: getAppSettings,
specterdDirPath: specterdDirPath,
getSpecterAppLogs: getSpecterAppLogs,
specterAppLogPath: specterAppLogPath
specterAppLogPath: specterAppLogPath,
versionData,
isDev: isDev,
devFolder,
isMac, isMac
}

View file

@ -9,34 +9,30 @@ const extract = require('extract-zip')
const defaultMenu = require('electron-default-menu');
const ProgressBar = require('electron-progressbar')
const { spawn, exec } = require('child_process');
const helpers = require('./helpers')
const getFileHash = helpers.getFileHash
const getAppSettings = helpers.getAppSettings
const appSettingsPath = helpers.appSettingsPath
const specterdDirPath = helpers.specterdDirPath
const { getFileHash, getAppSettings, appSettingsPath, specterdDirPath, specterAppLogPath, versionData, isDev, devFolder, isMac } = require('./helpers')
const downloadloc = require('./downloadloc')
const getDownloadLocation = downloadloc.getDownloadLocation
const appName = downloadloc.appName()
const appNameLower = appName.toLowerCase()
// Quit again if there is no version-data in dev
if (isDev && versionData === undefined) {
console.log(`You need to create a version-data.json in your dev folder (${devFolder}) to run the app. Check helpers.js for the format. Quitting ...`)
app.quit()
return
}
ipcMain.handle("showMessageBoxSync", (e, message, buttons) => {
dialog.showMessageBoxSync(mainWindow, { message, buttons });
});
// Helper
const isMac = process.platform === 'darwin'
const isDev = process.env.NODE_ENV !== "production"
// Logging
const {transports, format, createLogger } = require('winston')
const combinedLog = new transports.File({ filename: helpers.specterAppLogPath });
const combinedLog = new transports.File({ filename: specterAppLogPath });
const winstonOptions = {
exitOnError: false,
format: format.combine(
format.timestamp(),
// format.timestamp({format:'MM/DD/YYYY hh:mm:ss.SSS'}),
format.json(),
format.printf(info => {
return `${info.timestamp} [${info.level}] : ${info.message}`;
@ -326,19 +322,6 @@ app.whenReady().then(() => {
fs.mkdirSync(specterdDirPath, { recursive: true });
}
let versionData
// Download specterd, run sha256sum on it and then set your own versionData in dev, should look this:
// {
// "version": "v2.0.0-pre32",
// "sha256": "aa049abf3e75199bad26fbded08ee5911ad48e325b42c43ec195136bd0736785"
// }
if (isDev) {
versionData = require('./version-data-dev.json')
}
else {
versionData = require('./version-data.json')
}
if (!appSettings.versionInitialized || appSettings.versionInitialized != versionData.version) {
logger.info(`Updating ${appSettingsPath} : ${JSON.stringify(appSettings)}`);
appSettings.specterdVersion = versionData.version

View file

@ -4,9 +4,9 @@
"version": "v0.0.0",
"main": "main.js",
"scripts": {
"start": "cp -R ../../src/cryptoadvance/specter/static/fonts ../../src/cryptoadvance/specter/static/output.css ../../src/cryptoadvance/specter/static/typography.css . && electron .",
"start": "export NODE_ENV=development && cp -R ../../src/cryptoadvance/specter/static/fonts ../../src/cryptoadvance/specter/static/output.css ../../src/cryptoadvance/specter/static/typography.css . && electron .",
"pack": "electron-builder --dir",
"dist": "export NODE_ENV=production && electron-builder --publish=never",
"dist": "electron-builder --publish=never",
"postinstall": "electron-builder install-app-deps"
},
"repository": "https://github.com/cryptoadvance/specter-desktop",
@ -53,4 +53,4 @@
"request": "^2.88.2",
"winston": "^3.3.3"
}
}
}

View file

@ -1,5 +0,0 @@
{
"version": "v2.0.0-pre32",
"sha256": "aa049abf3e75199bad26fbded08ee5911ad48e325b42c43ec195136bd0736785"
}

View file

@ -112,12 +112,17 @@ def to_ascii20(name: str) -> str:
return "".join([c for c in name if ord(c) < 127])[:20]
# TODO: Rename this function to sth. like create_unique_id
def alias(name):
"""
Create a filesystem-friendly alias from a string.
Replaces space with _ and keeps only alphanumeric chars.
Leading and trailing whitespaces are removed.
Replaces space(s) and hyphen(s) with one underscore.
Keeps only alphanumeric chars and returns in lowercase.
"""
name = name.replace(" ", "_")
name = name.strip().replace(" ", "_").replace("-", "_")
while "__" in name:
name = name.replace("__", "_")
return "".join(x for x in name if x.isalnum() or x == "_").lower()

View file

@ -5,7 +5,7 @@ import sys
import pathlib
import sys
from typing import Dict
from typing import Dict, List
from flask_babel import lazy_gettext as _
from flask import copy_current_request_context
from cryptoadvance.specter.rpc import BitcoinRPC
@ -298,9 +298,13 @@ class WalletManager:
return working_folder
@property
def wallets_names(self):
def wallets_names(self) -> List:
return sorted(self.wallets.keys())
@property
def wallets_aliases(self) -> List:
return [wallet.alias for wallet in self.wallets.values()]
@property
def rpc(self):
"""returns a BitcoinRpc depending on the chain"""
@ -354,14 +358,6 @@ class WalletManager:
walletsindir = []
self._check_duplicate_keys(keys)
wallet_alias = alias(name)
i = 2
# Ensure unique wallet alias
while (
os.path.isfile(os.path.join(self.working_folder, "%s.json" % wallet_alias))
or os.path.join(self.rpc_path, wallet_alias) in walletsindir
):
wallet_alias = alias("%s %d" % (name, i))
i += 1
w = self.WalletClass.create(
self.rpc,

View file

@ -11,7 +11,7 @@ from flask_babel import lazy_gettext as _
from flask_login import login_required
from ...commands.psbt_creator import PsbtCreator
from ...helpers import bcur2base64, get_devices_with_keys_by_type, get_txid
from ...helpers import bcur2base64, get_devices_with_keys_by_type, get_txid, alias
from ...key import Key
from ...managers.wallet_manager import purposes
from ...persistence import delete_file
@ -231,13 +231,6 @@ def new_wallet(wallet_type):
).format(device.name)
break
name = wallet_type.title()
wallet_name = name
i = 2
while wallet_name in app.specter.wallet_manager.wallets_names:
wallet_name = "%s %d" % (name, i)
i += 1
return render_template(
"wallet/new_wallet/new_wallet_keys.jinja",
cosigners=devices,
@ -253,8 +246,8 @@ def new_wallet(wallet_type):
address_type = request.form["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")
if alias(wallet_name) in app.specter.wallet_manager.wallets_aliases:
err = _("Wallet name already exists. Choose a different name!")
if err:
devices = [
app.specter.device_manager.get_by_alias(
@ -805,8 +798,10 @@ def settings(wallet_alias):
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")
elif alias(wallet_name) in app.specter.wallet_manager.wallets_aliases:
flash(
_("Wallet name already exists. Choose a different name!"), "error"
)
else:
app.specter.wallet_manager.rename_wallet(wallet, wallet_name)
flash("Wallet successfully renamed!")

View file

@ -9,9 +9,11 @@
- isRight: Is the item at the right edge (last item) of the navigation bar?
#}
{% macro menu_item(service_id, tab, title, active_menuitem, isLeft=false, isRight=false) -%}
<a
href="{{ url_for(service_id+'_endpoint.' ~ tab)}}"
class="btn radio {% if isLeft %}left{% endif %} {% if isRight %}right{% endif %} {% if active_menuitem == tab %}checked{% endif %}">
{{ title }}
</a>
<li class="mr-2 py-2 border-b-2 border-transparent {% if active_menuitem == tab %}text-link border-b-2 border-link active{% endif %}">
<a
href="{{ url_for(service_id+'_endpoint.' ~ tab)}}"
class="text-lg inline-block py-1 px-3 hover:text-white rounded-lg hover:bg-dark-700">
{{ title }}
</a>
</li>
{%- endmacro %}

View file

@ -1239,6 +1239,10 @@ input[type="number"]::-webkit-outer-spin-button,
margin-left: 1rem;
}
.ml-5 {
margin-left: 1.25rem;
}
.ml-6 {
margin-left: 1.5rem;
}
@ -1295,10 +1299,6 @@ input[type="number"]::-webkit-outer-spin-button,
margin-top: 1.25rem;
}
.mt-6 {
margin-top: 1.5rem;
}
.mt-8 {
margin-top: 2rem;
}
@ -1307,10 +1307,6 @@ input[type="number"]::-webkit-outer-spin-button,
margin-top: -1px;
}
.ml-1 {
margin-left: 0.25rem;
}
.block {
display: block;
}
@ -1523,6 +1519,10 @@ input[type="number"]::-webkit-outer-spin-button,
min-width: 300px;
}
.max-w-\[1100px\] {
max-width: 1100px;
}
.max-w-\[40px\] {
max-width: 40px;
}
@ -1535,10 +1535,6 @@ input[type="number"]::-webkit-outer-spin-button,
max-width: 65ch;
}
.max-w-\[1100px\] {
max-width: 1100px;
}
.flex-shrink-0 {
flex-shrink: 0;
}
@ -1571,6 +1567,11 @@ input[type="number"]::-webkit-outer-spin-button,
flex-grow: 3;
}
.rotate-90 {
--tw-rotate: 90deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@ -1694,6 +1695,12 @@ input[type="number"]::-webkit-outer-spin-button,
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-5 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1.25rem * var(--tw-space-x-reverse));
margin-left: calc(1.25rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-1 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@ -1746,10 +1753,6 @@ input[type="number"]::-webkit-outer-spin-button,
white-space: pre-wrap;
}
.break-words {
overflow-wrap: break-word;
}
.break-all {
word-break: break-all;
}
@ -2052,6 +2055,10 @@ input[type="number"]::-webkit-outer-spin-button,
text-align: center;
}
.text-right {
text-align: right;
}
.align-middle {
vertical-align: middle;
}

View file

@ -88,7 +88,7 @@
{% endif %}
{% endwith %}
{% if error %}
<message-box type="error">{{ _("ERROR:") }} {{error | safe }}</message-box>
<message-box type="error">{{error | safe }}</message-box>
{% endif %}
</div>

View file

@ -74,23 +74,38 @@
{% if device.hwi_support or device.supports_qr_message_signing %}
<button class="button" type="button" onclick="showPageOverlay('message-signing')">{{ _("Sign message") }}</button>
<div id="message-signing" class="hidden" >
<div id="message-signing" class="hidden p-4 bg-dark-800">
<div class="flex justify-between mb-3">
<h3 class="mb-0">{{ _("Message Signing") }}</h3>
<p class="m-0 cursor-pointer" onclick="hidePageOverlay()" class="cursor-pointer" data-cy="close-msg-signing-overlay-btn">Close</p>
</div>
{% if origin %}
{{ _("Address") }}:<input type="text" id="signing-address" type="text" value="" placeholder="bc1q ..."><br><br>
<div class="floating-wrapper">
<input class="floating-input peer" type="text" id="signing-address" type="text" value="" placeholder=" ">
<label for="signing-address" class="floating-label">Address</label>
</div>
{% endif %}
{{ _("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"></textarea>
<div class="floating-wrapper">
<input class="floating-input peer" type="text" id="messageDerivationPath" type="text" value="" placeholder=" ">
<label class="floating-label">{{ _("Derivation path (e.g. m/84h/0h/0h/0/0)") }}</label>
</div>
<textarea class="my-3" id="message" placeholder="Enter the message you would like to sign"></textarea>
<span id="signature-label" class="hidden">{{ _("Message signature") }}:</span>
<p id="signature" title='{{ _("Copy signature") }}' class="hidden" onclick="copyText(this.innerText, '{{ _("Copied message signature") }}')">
</p>
<p id="signature" title='{{ _("Copy signature") }}' class="hidden" onclick="copyText(this.innerText, '{{ _("Copied message signature") }}')"></p>
{% if device.hwi_support %}
<button class="button" id="{{ device.alias }}_usb_sign_msg_btn" type="button" onclick="signMessageOnDevice();">{{ _("Sign message via USB") }}</button>
<button class="button mb-0 bg-accent" id="{{ device.alias }}_usb_sign_msg_btn" type="button" onclick="signMessageOnDevice();">{{ _("Sign message via USB") }}</button>
{% endif %}
{% if device.supports_qr_message_signing %}
{% include "includes/overlay/qr_code_sign_message.jinja" %}
<button class="button" id="{{ device.alias }}_qr_sign_msg_btn">{{ _("Sign message via QR code") }}</button>
<button class="button mb-0 mt-3 bg-accent" id="{{ device.alias }}_qr_sign_msg_btn">{{ _("Sign message via QR code") }}</button>
{% endif %}
</div>
{% endif %}

View file

@ -122,11 +122,16 @@
{{ _("Paste xpub") }}
</button>
<div id="paste-xpub-popup" class="hidden">
<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='';">{{ _("Add xpub") }}</button>
<div id="paste-xpub-popup" class="hidden bg-dark-800 p-4 w-96">
<div class="flex justify-between mb-2">
<h3 class="mb-0">{{ _("Paste xpub") }}</h3>
<p class="m-0 cursor-pointer" onclick="hidePageOverlay()" class="cursor-pointer">Close</p>
</div>
<div class="floating-wrapper mb-3 mt-1">
<input class="floating-input peer" type="text" id="paste-xpub-text" placeholder=" ">
<label class="floating-label" for="paste-xpub-text">xpub</label>
</div>
<button class="button bg-accent mr-0 mb-1 w-full" type="button" onclick="addXpubs(document.getElementById('paste-xpub-text').value);hidePageOverlay();document.getElementById('paste-xpub-text').value='';">{{ _("Add xpub") }}</button>
</div>
</div>
</div>

View file

@ -19,27 +19,6 @@
height: 24px;
vertical-align: middle;
}
.service-btn, .signing-btn {
background: var(--cmap-border);
border: 1px solid transparent;
border-radius: 4px;
padding: 8px 15px 8px 10px;
color: #fff;
font-size: 1em;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
min-height: 33px;
}
.signing-btn {
max-width: 315px;
}
.service-btn:hover, .signing-btn:hover {
border: 1px solid var(--main-color);
background: var(--cmap-border);
}
</style>
<div class="address-data bg-dark-800 rounded-xl p-4">
<div class="flex justify-between mb-2">
@ -119,7 +98,7 @@
addressInfoHTML += `<tr><td>{{ _("Service") }}:</td><td><img class="service-icon" src='{{ext_url_prefix}}/${this.serviceId}/static/${services[this.serviceId].icon}'>${services[this.serviceId].name}</td></tr>`;
} else {
let associateServiceUrl = `{{ url_for('services_endpoint.associate_addr', wallet_alias='WALLET_ALIAS', address='ADDRESS') }}`.replace('WALLET_ALIAS', this.wallet).replace('ADDRESS', jsonResponse.address);
addressInfoHTML += `<tr><td>{{ _("Service") }}:</td><td><button type="button" id="associate-btn" class="service-btn" onclick="location.href='${associateServiceUrl}';">&#128229;&nbsp;{{ _("Associate with a service") }}</button></td></tr>`;
addressInfoHTML += `<tr><td>{{ _("Service") }}:</td><td><button type="button" id="associate-btn" class="btn" onclick="location.href='${associateServiceUrl}';" data-cy="associate-address-with-service-btn">&#128229;&nbsp;{{ _("Associate with a service") }}</button></td></tr>`;
};
// Message signing feature restricted to singlesig wallets using a Specter DIY
@ -127,7 +106,7 @@
{% if wallet.is_singlesig and device.hwi_support or wallet.is_singlesig and device.supports_qr_message_signing %}
let redirectUrl = `{{ url_for('devices_endpoint.device', device_alias=device.alias, origin='wallet', address='ADDRESS', derivation_path='DERIVATION_PATH') }}`.replace('ADDRESS', address).replace('DERIVATION_PATH', derivation_path);
addressInfoHTML += `<tr><td>{{ _("Message signing") }}:</td><td>
<button type="button" id="msg-signing-btn" class="signing-btn" onclick="location.href='${redirectUrl}';">
<button type="button" id="msg-signing-btn" class="btn" onclick="location.href='${redirectUrl}';">
<div style="word-wrap: anywhere">&#128396;&nbsp;{{ _("Sign a message with your") }} {{ device.name }} {{ _("device") }}</div>
</button></td></tr>`;
{% endif %}

View file

@ -503,7 +503,7 @@
data-label="${addressData.label}"
data-address-wallet="${wallet}"
/>`;
showPageOverlay('address-popup');
showPageOverlay('address-popup', null, 'start');
} else {
copyText(address, `{{ _("Copied address") }}: ${ address }`);
}

View file

@ -1,5 +1,5 @@
<div class="hidden z-30 absolute w-screen h-screen" id="page_overlay">
<div class="w-screen overflow-auto h-screen bg-black/40 flex flex-col relative justify-start items-center py-5" id="page_overlay_popup">
<div class="w-screen overflow-auto h-screen bg-black/40 flex flex-col relative justify-center items-center py-5" id="page_overlay_popup">
<div class="box-shadow-xl transition-all absolute max-w-[700px] overflow-hidden rounded-xl text-white" id="page_overlay_popup_content"></div>
</div>
</div>
@ -27,7 +27,7 @@
document.getElementById('pacman').style.display='flex';
}
function showPageOverlay(container_id, position) {
function showPageOverlay(container_id, position, location) {
// Transfer the content to the popup overlay div
document.getElementById("page_overlay_popup_content").appendChild(document.getElementById(container_id));
@ -47,6 +47,10 @@
contentContainer.style.bottom = position.bottom || "auto";
}
if (location) {
overlayPopup.style.justifyContent = location
}
// Show the content
var pageContent = document.getElementById("page_overlay_popup_content").children[0];
pageContent.style.display = "block";

View file

@ -1,14 +1,19 @@
<form id="{{ device.alias }}_sign_tx_qr" class="flex-center flex-column hidden">
<form id="{{ device.alias }}_sign_tx_qr" class="flex-center flex-column hidden p-4 bg-dark-800">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<h2>{{ _("Scan this QR code with your") }} {{device.name}}.</h2>
<div class="flex justify-between mb-2">
<h3 class="mb-0">{{ _("Scan this QR code with your") }} {{device.name}}.</h3>
<p class="m-0 cursor-pointer" onclick="hidePageOverlay()" class="cursor-pointer">Close</p>
</div>
<div class="row" data-style="min-height: 400px; margin-top:20px">
<span id="compressed_psbt" data-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>
</span>
</div>
<qr-scanner id="{{ device.alias }}_qr_scan_btn">
<a slot="button" class="btn flex-item" data-style="width: 250px; margin: 15px auto;">
<a slot="button" class="button bg-accent">
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" data-style="width: 30px; margin: 0px;" class="svg-white"> {{ _("Scan signed transaction") }}
</a>
</qr-scanner>
@ -30,4 +35,4 @@
}
});
</script>
</form>
</form>

View file

@ -1,15 +1,19 @@
<form id="{{ device.alias }}_sign_msg_qr" class="flex-center flex-column hidden">
<form id="{{ device.alias }}_sign_msg_qr" class="flex-center flex-column hidden p-4 bg-dark-800">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<h2>{{ _("Scan this QR code with your") }} {{device.name}}</h2>
<div class="flex justify-between mb-2 space-x-5">
<h3 class="mb-0" data-cy="scan-qr-code-for-msg-signing-overlay-headline">{{ _("Scan this QR code with your") }} {{device.name}}</h3>
<p class="m-0 ml-5 inline-block cursor-pointer" onclick="hidePageOverlay()" data-cy="scan-qr-code-for-msg-signing-overlay-close-btn">Close</p>
</div>
<div class="row" data-style="min-height: 400px; margin-top:20px">
<span id="compressed_msg" data-style="margin: auto;">
<qr-code id="msg-qr-code" animate="auto" data-style="margin-top:20px;" width="300"></qr-code>
</span>
</div>
<qr-scanner id="{{ device.alias }}_qr_scan_btn">
<a slot="button" class="btn flex-item" data-style="width: 350px; margin: 15px auto; font-size: 1em;">
&#128247; {{ _("Click here to scan signature") }}
<a slot="button" class="button bg-accent mb-0 mt-3 w-full">
{{ _("Click here to scan signature") }}
</a>
</qr-scanner>
@ -31,4 +35,4 @@
}
});
</script>
</form>
</form>

View file

@ -1,11 +1,10 @@
<div class="mx-3 mt-3" id="services_list">
<div class="mx-3" id="services_list">
{% for _,plugin in specter.service_manager.services.items() %}
{% if plugin.id in current_user.services and plugin.visible_in_sidebar %}
<a class="flex items-center space-x-2" href="{{ url_for(plugin.id +'_endpoint.index') }}" class="item service">
<img class="w-8 h-8" src="{{ url_for(plugin.id +'_endpoint' + '.static', filename=plugin.icon) }}">
<div>{{ plugin.name }}</div>
</a>
<br>
<a class="flex items-center my-1 h-12 space-x-2" href="{{ url_for(plugin.id +'_endpoint.index') }}" class="item service">
<img class="w-8 h-8" src="{{ url_for(plugin.id +'_endpoint' + '.static', filename=plugin.icon) }}">
<div>{{ plugin.name }}</div>
</a>
{% endif %}
{% endfor %}
</div>

View file

@ -65,17 +65,17 @@
{% endif %}
</div>
<div class="overflow-scroll p-3 flex flex-col grow">
<div class="mx-3 flex items-center justify-between {% if not specter.chain %}opacity-40 pointer-events-none{% else %}cursor-pointer{% endif %}">
<span class="" id="toggle_wallets_list">{{ _("Wallets") }} ▸</span>
<img class="rounded-full bg-dark-700 w-4 h-4 animate-spin {% if not specter.wallet_manager.is_loading or not specter.chain %} hidden {% endif %}" id="wallets-loading-indicator" data-style="vertical-align: sub;" src="{{ url_for('static', filename='img/refresh.svg') }}"/>
</div>
<div class="mx-3 mt-3 hidden" id="reload-wallets-list-btn">
<a class="text-white flex items-center rounded-lg bg-dark-600 hover:bg-dark-700 flex items-center justify-center py-1" onclick=reloadPage()>
<svg class="text-white w-5 h-5 mr-2" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M52.8359,43.6067l1.30463e-08,-1.88843e-08c-0.312506,0.452349 -0.932543,0.565714 -1.38489,0.253208c-0.0990138,-0.0684038 -0.184804,-0.154194 -0.253208,-0.253208l-6.9888,-9.9742l-5.91546e-08,-8.31834e-08c-0.328807,-0.46237 -0.220533,-1.10375 0.241837,-1.43255c0.168889,-0.120103 0.370155,-0.186318 0.577363,-0.189948h4.9835l3.54852e-07,0.000503677c-0.00686545,-9.94223 -8.0722,-17.9964 -18.0144,-17.9896c-4.13508,0.00285541 -8.14312,1.42919 -11.3504,4.03924l4.60883e-08,-3.91242e-08c-0.842285,0.715013 -2.10472,0.611839 -2.81974,-0.230446c-0.715013,-0.842285 -0.611839,-2.10472 0.230446,-2.81974l3.46098e-07,-2.83814e-07c9.39634,-7.70537 23.26,-6.33457 30.9654,3.06177c3.22304,3.93035 4.98589,8.85536 4.98903,13.9382h4.9935l2.44822e-09,4.33431e-11c0.567328,0.0100438 1.0191,0.478096 1.00905,1.04542c-0.0036664,0.207098 -0.0698445,0.408252 -0.189853,0.577075Zm-40.0129,-23.1717l6.9888,9.9742l-2.71216e-09,-3.81325e-09c0.32881,0.4623 0.220594,1.10362 -0.241706,1.43243c-0.168809,0.120065 -0.369975,0.186285 -0.577094,0.189969h-4.983l2.60913e-07,0.000383721c0.0068661,9.94206 8.07207,17.9961 18.0141,17.9893c4.1354,-0.00285595 8.14372,-1.42943 11.3511,-4.03989l-7.47965e-08,6.34738e-08c0.8424,-0.714877 2.10482,-0.6115 2.8197,0.2309c0.714877,0.8424 0.6115,2.10482 -0.2309,2.8197l4.04125e-07,-3.31403e-07c-9.39617,7.70531 -23.2597,6.33461 -30.965,-3.06156c-3.22311,-3.93039 -4.98597,-8.85549 -4.98903,-13.9384h-4.9947l5.45126e-08,9.68797e-10c-0.567328,-0.0100825 -1.01906,-0.478166 -1.00898,-1.04549c0.00367938,-0.207033 0.0698308,-0.408123 0.189782,-0.576906l6.9888,-9.9742l-3.21808e-08,4.66031e-08c0.312407,-0.452417 0.93242,-0.565917 1.38484,-0.25351c0.0990287,0.0683822 0.184838,0.154154 0.253263,0.253153Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<div>Refresh list</div>
</a>
<div class="overflow-scroll font-medium p-3 flex flex-col grow">
<div id="wallets_toggle" class="cursor-pointer text-dark-200 group py-1 px-3 font-medium hover:bg-dark-800 rounded-lg flex justify-between items-center hover:text-dark-50">
<div class="flex items-center w-full justify-between {% if not specter.chain %}opacity-40 pointer-events-none{% endif %}">
<div class="space-x-2 flex items-center">
<svg id="wallets_caret" class="rotate-90 transition-all fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6742 9.15634C15.2917 9.54929 15.2917 10.4507 14.6742 10.8437L6.53688 16.022C5.87115 16.4456 5 15.9674 5 15.1783L5 4.82167C5 4.03258 5.87115 3.55437 6.53688 3.97801L14.6742 9.15634Z" fill="currentColor"/>
</svg>
<span>{{ _("Wallets") }}</span>
</div>
<img class="rounded-full bg-dark-700 w-4 h-4 animate-spin {% if not specter.wallet_manager.is_loading or not specter.chain %} hidden {% endif %}" id="wallets-loading-indicator" data-style="vertical-align: sub;" src="{{ url_for('static', filename='img/refresh.svg') }}"/>
</div>
</div>
<div class="mx-3" id="wallets_list">
@ -117,6 +117,7 @@
{% for wallet in specter.wallet_manager.failed_load_wallets %}
<div class="bg-dark-800 p-4 rounded-lg ">
<h1>{{ _("Wallet Name") }}: {{wallet.name}}</h1>
<h5>{{ _("Wallet alias") }}: {{wallet.alias}}</h5>
<h2>{{ _("Error Details") }}:</h2>
<p data-style="color: red">{{wallet.loading_error}}</p>
<form class="mt-3" action="{{ url_for('wallets_endpoint.failed_wallets') }}" method="POST" class="row center">
@ -148,12 +149,24 @@
<!-- {% endif %} -->
</div>
<div class="mx-3 mt-3 {% if not specter.chain %}opacity-40 pointer-events-none{% endif %}">
{{ sidebar_btn(url_for('wallets_endpoint.new_wallet_type'), _('Add new wallet'), 'btn_new_wallet', icon=false) }}
<div class="mx-3 mt-3 cursor-pointer hidden" id="reload-wallets-list-btn">
<a class="text-white flex items-center rounded-lg bg-dark-600 hover:bg-dark-700 flex items-center justify-center py-1" onclick=reloadPage()>
<svg class="text-white w-5 h-5 mr-2" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M52.8359,43.6067l1.30463e-08,-1.88843e-08c-0.312506,0.452349 -0.932543,0.565714 -1.38489,0.253208c-0.0990138,-0.0684038 -0.184804,-0.154194 -0.253208,-0.253208l-6.9888,-9.9742l-5.91546e-08,-8.31834e-08c-0.328807,-0.46237 -0.220533,-1.10375 0.241837,-1.43255c0.168889,-0.120103 0.370155,-0.186318 0.577363,-0.189948h4.9835l3.54852e-07,0.000503677c-0.00686545,-9.94223 -8.0722,-17.9964 -18.0144,-17.9896c-4.13508,0.00285541 -8.14312,1.42919 -11.3504,4.03924l4.60883e-08,-3.91242e-08c-0.842285,0.715013 -2.10472,0.611839 -2.81974,-0.230446c-0.715013,-0.842285 -0.611839,-2.10472 0.230446,-2.81974l3.46098e-07,-2.83814e-07c9.39634,-7.70537 23.26,-6.33457 30.9654,3.06177c3.22304,3.93035 4.98589,8.85536 4.98903,13.9382h4.9935l2.44822e-09,4.33431e-11c0.567328,0.0100438 1.0191,0.478096 1.00905,1.04542c-0.0036664,0.207098 -0.0698445,0.408252 -0.189853,0.577075Zm-40.0129,-23.1717l6.9888,9.9742l-2.71216e-09,-3.81325e-09c0.32881,0.4623 0.220594,1.10362 -0.241706,1.43243c-0.168809,0.120065 -0.369975,0.186285 -0.577094,0.189969h-4.983l2.60913e-07,0.000383721c0.0068661,9.94206 8.07207,17.9961 18.0141,17.9893c4.1354,-0.00285595 8.14372,-1.42943 11.3511,-4.03989l-7.47965e-08,6.34738e-08c0.8424,-0.714877 2.10482,-0.6115 2.8197,0.2309c0.714877,0.8424 0.6115,2.10482 -0.2309,2.8197l4.04125e-07,-3.31403e-07c-9.39617,7.70531 -23.2597,6.33461 -30.965,-3.06156c-3.22311,-3.93039 -4.98597,-8.85549 -4.98903,-13.9384h-4.9947l5.45126e-08,9.68797e-10c-0.567328,-0.0100825 -1.01906,-0.478166 -1.00898,-1.04549c0.00367938,-0.207033 0.0698308,-0.408123 0.189782,-0.576906l6.9888,-9.9742l-3.21808e-08,4.66031e-08c0.312407,-0.452417 0.93242,-0.565917 1.38484,-0.25351c0.0990287,0.0683822 0.184838,0.154154 0.253263,0.253153Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<div>Refresh wallets</div>
</a>
</div>
<div class="mx-3 mt-8 cursor-pointer">
<span class="" id="toggle_devices_list" data-style="cursor: pointer;">{{ _("Devices") }} ▸</span>
<div class="mx-3 mt-3 {% if not specter.chain %}opacity-40 pointer-events-none{% endif %}">
{{ sidebar_btn(url_for('wallets_endpoint.new_wallet_type'), _('Add wallet'), 'btn_new_wallet', icon=false) }}
</div>
<div id="devices_toggle" class="mt-8 cursor-pointer text-dark-200 group py-1 px-3 font-medium hover:bg-dark-800 rounded-lg flex justify-between items-center hover:text-dark-50">
<div class="space-x-2 flex items-center">
<svg id="devices_caret" class="rotate-90 transition-all fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6742 9.15634C15.2917 9.54929 15.2917 10.4507 14.6742 10.8437L6.53688 16.022C5.87115 16.4456 5 15.9674 5 15.1783L5 4.82167C5 4.03258 5.87115 3.55437 6.53688 3.97801L14.6742 9.15634Z" fill="currentColor"/>
</svg>
<span>{{ _("Devices") }}</span>
</div>
</div>
{% from 'includes/sidebar/components/sidebar_device_list_item.jinja' import sidebar_device_list_item %}
@ -164,17 +177,22 @@
</div>
<div class="mx-3 mt-3">
{{ sidebar_btn(url_for('devices_endpoint.new_device_type'), _('Add new device'),'btn_new_device', icon=false) }}
{{ sidebar_btn(url_for('devices_endpoint.new_device_type'), _('Add device'),'btn_new_device', icon=false) }}
</div>
<div class="mx-3 mt-8">
<span class="" id="toggle_services_list" data-style="cursor: pointer;">Plugins</span>
<div id="services_toggle" class="mt-8 cursor-pointer text-dark-200 group py-1 px-3 font-medium hover:bg-dark-800 rounded-lg flex justify-between items-center hover:text-dark-50">
<div class="space-x-2 flex items-center">
<svg id="services_caret" class="rotate-90 transition-all fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6742 9.15634C15.2917 9.54929 15.2917 10.4507 14.6742 10.8437L6.53688 16.022C5.87115 16.4456 5 15.9674 5 15.1783L5 4.82167C5 4.03258 5.87115 3.55437 6.53688 3.97801L14.6742 9.15634Z" fill="currentColor"/>
</svg>
<span>{{ _("Services") }}</span>
</div>
</div>
{% include "includes/sidebar/components/sidebar_services_list.jinja" %}
<div class="mx-3 mt-3">
{{ sidebar_btn(url_for('services_endpoint.choose'), 'Choose plugins', 'btn_plugins', icon=false) }}
{{ sidebar_btn(url_for('services_endpoint.choose'), 'Add plugin', 'btn_plugins', icon=false) }}
</div>
<br>
</div>
@ -238,20 +256,53 @@
{% endif %}
<script>
if (localStorage.wallets === 'true' || localStorage.wallets === undefined) {
document.getElementById('wallets_list').classList.remove('hidden')
document.getElementById('toggle_wallets_list').innerHTML = 'Wallets ▾';
} else {
document.getElementById('wallets_list').classList.add('hidden')
document.getElementById('toggle_wallets_list').innerHTML = 'Wallets ▸';
let wallets_toggle = document.getElementById('wallets_toggle');
let wallets_caret = document.getElementById('wallets_caret');
let wallets_list = document.getElementById('wallets_list');
let devices_toggle = document.getElementById('devices_toggle');
let devices_caret = document.getElementById('devices_caret');
let devices_list = document.getElementById('devices_list');
let services_toggle = document.getElementById('services_toggle');
let services_caret = document.getElementById('services_caret');
let services_list = document.getElementById('services_list');
if (localStorage.getItem('wallets_hidden') === 'true') {
wallets_caret.classList.remove('rotate-90')
wallets_list.classList.add('hidden')
}
if (localStorage.devices === 'true' || localStorage.devices === undefined) {
document.getElementById('devices_list').classList.remove('hidden')
document.getElementById('toggle_devices_list').innerHTML = 'Devices ▾';
} else {
document.getElementById('devices_list').classList.add('hidden')
document.getElementById('toggle_devices_list').innerHTML = 'Devices ▸';
if (localStorage.getItem('devices_hidden') === 'true') {
devices_caret.classList.remove('rotate-90')
devices_list.classList.add('hidden')
}
if (localStorage.getItem('services_hidden') === 'true') {
services_caret.classList.remove('rotate-90')
services_list.classList.add('hidden')
}
function toggleList(type) {
let toggle = document.getElementById(type + '_toggle');
let caret = document.getElementById(type + '_caret');
let list = document.getElementById(type + '_list');
if (list.classList.contains('hidden')) {
// The list is hidden
localStorage.removeItem(type + '_hidden')
// Make it visible
list.classList.remove('hidden')
caret.classList.add('rotate-90')
} else {
// The list is visible
localStorage.setItem(type + '_hidden', true)
// Make it hidden
list.classList.add('hidden')
caret.classList.remove('rotate-90')
}
}
async function updateWalletsLoadingData() {
@ -283,44 +334,25 @@
location.reload()
}
function toggleList(type, display_text) {
let titleButton = document.getElementById('toggle_' + type + '_list');
let list = document.getElementById(type + '_list');
if (!list.classList || list.classList.contains('hidden')) {
document.getElementById(type + '_list').classList.remove('hidden')
localStorage[type] = true;
titleButton.innerHTML = display_text + ' ▾';
} else {
document.getElementById(type + '_list').classList.add('hidden')
localStorage[type] = false;
titleButton.innerHTML = display_text + ' ▸';
}
}
document.addEventListener("DOMContentLoaded", function(){
if ('{{ specter.device_manager.devices_names | length }}' == '0') {
document.getElementById('toggle_devices_list').innerHTML = `{{ _("Devices") }}`;
} else {
document.getElementById('toggle_devices_list').addEventListener('click', (event) => {
toggleList('devices', '{{ _("Devices") }}');
});
}
/*
* Set event listeners
*/
if ('{{ specter.wallet_manager.wallets_names | length }}' == '0') {
document.getElementById('toggle_wallets_list').innerHTML = `{{ _("Wallets") }}`;
} else {
document.getElementById('toggle_wallets_list').addEventListener('click', (event) => {
toggleList('wallets', '{{ _("Wallets") }}');
});
}
if ('{{ specter.wallet_manager.wallets_names | length }}' == '0') {}
wallets_toggle.addEventListener('click', (event) => {
toggleList('wallets');
});
if ('{{ specter.service_manager.service_names | length }}' == '0') {
document.getElementById('toggle_services_list').innerHTML = 'Plugins';
} else {
document.getElementById('toggle_services_list').addEventListener('click', (event) => {
toggleList('services');
});
}
if ('{{ specter.device_manager.devices_names | length }}' == '0') {}
devices_toggle.addEventListener('click', (event) => {
toggleList('devices');
});
if ('{{ specter.service_manager.service_names | length }}' == '0') {}
services_toggle.addEventListener('click', (event) => {
toggleList('services');
});
{% if specter.wallet_manager.is_loading %}
updateWalletsLoadingData();

View file

@ -1176,7 +1176,7 @@
data-txid="${txid}"
data-tx-wallet="${wallet}">
</tx-data>`;
showPageOverlay('tx-popup');
showPageOverlay('tx-popup', null, 'start');
} else {
copyText(txid, `{{ _("Copied transaction") }}: ${ txid }`);
}

View file

@ -9,22 +9,24 @@
</div>
{% endif %}
{% if specter.chain %}
<table>
<tr> <td data-style="text-align: left;">{{ _("Network") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Blocks count") }}:</td> <td data-style="text-align: right;">{{specter.info['blocks']}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Host") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.node.host}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Port") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.node.port}}</td> </tr>
<tr> <td data-style="text-align: left; width: 35%;">{{ _("Bitcoin Core Version") }}:</td> <td data-style="text-align: right;">v{{specter.bitcoin_core_version}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Connections count") }}:</td> <td data-style="text-align: right;">{{specter.network_info['connections']}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Size on disk") }}:</td> <td data-style="text-align: right;">{{specter.info['size_on_disk']|bytessize }}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Mempool size") }}:</td> <td data-style="text-align: right;">{{specter.info['mempool_info'].size}} transactions</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Node uptime") }}:</td> <td data-style="text-align: right;">~ {{(specter.info['uptime'] / 60 // 60) | int }} {{ _("Hours") }}</td> </tr>
{% if specter.info['pruned'] %}
<tr> <td data-style="text-align: left;">{{ _("Automatic pruning") }}:</td> <td data-style="text-align: right;">{{specter.info['automatic_pruning']}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Prune height") }}:</td> <td data-style="text-align: right;">{{specter.info['pruneheight']}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Prune target size") }}:</td> <td data-style="text-align: right;">{{specter.info['prune_target_size']}}</td> </tr>
{% endif %}
</table>
<div class="m-3 mb-1">
<table>
<tr> <td>{{ _("Network") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
<tr> <td>{{ _("Blocks count") }}</td> <td class="text-right">{{specter.info['blocks']}}</td> </tr>
<tr> <td>{{ _("Host") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.node.host}}</td> </tr>
<tr> <td>{{ _("Port") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.node.port}}</td> </tr>
<tr> <td>{{ _("Bitcoin Core Version") }}</td> <td class="text-right">v{{specter.bitcoin_core_version}}</td> </tr>
<tr> <td>{{ _("Connections count") }}</td> <td class="text-right">{{specter.network_info['connections']}}</td> </tr>
<tr> <td>{{ _("Size on disk") }}</td> <td class="text-right">{{specter.info['size_on_disk']|bytessize }}</td> </tr>
<tr> <td>{{ _("Mempool size") }}</td> <td class="text-right">{{specter.info['mempool_info'].size}} transactions</td> </tr>
<tr> <td>{{ _("Node uptime") }}</td> <td class="text-right">~ {{(specter.info['uptime'] / 60 // 60) | int }} {{ _("Hours") }}</td> </tr>
{% if specter.info['pruned'] %}
<tr> <td>{{ _("Automatic pruning") }}</td> <td class="text-right">{{specter.info['automatic_pruning']}}</td> </tr>
<tr> <td>{{ _("Prune height") }}</td> <td class="text-right">{{specter.info['pruneheight']}}</td> </tr>
<tr> <td>{{ _("Prune target size") }}</td> <td class="text-right">{{specter.info['prune_target_size']}}</td> </tr>
{% endif %}
</table>
</div>
{% if specter.bitcoin_core_version_raw < 200000 %}
<div class="flex p-4 m-3 mb-4 bg-dark-600 rounded-lg" role="alert">
<svg class="flex-shrink-0 inline w-6 h-6 mr-3" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M32.0022,55.9844l-1.04907e-06,-2.84217e-14c-13.2548,-5.79387e-07 -24,-10.7452 -24,-24c5.79387e-07,-13.2548 10.7452,-24 24,-24c13.2548,5.79387e-07 24,10.7452 24,24l2.13163e-14,-1.04907e-06c0,13.2548 -10.7452,24 -24,24Zm1.5,-32l3.82016e-08,-2.71086e-09c-1.79133,0.127116 -3.35165,-1.21031 -3.5,-3l6.50646e-08,-7.8524e-07c0.148296,-1.78972 1.70866,-3.12717 3.5,-3l-5.85322e-09,4.15529e-10c1.79134,-0.127171 3.3517,1.21028 3.5,3l-5.57104e-08,6.72365e-07c-0.148295,1.78976 -1.70872,3.12723 -3.5001,3Zm1.4206,6.7635l-2.7853,10.5093l-8.37179e-08,3.15667e-07c-0.270677,1.02062 0.262417,2.08281 1.2425,2.4757l-5.91015e-08,-2.36708e-08c0.379505,0.151996 0.628277,0.519688 0.6282,0.9285v0.323l8.52651e-14,4.05e-07c0,0.551933 -0.447167,0.999503 -0.9991,1h-2.9891l-4.04477e-08,1.96948e-10c-1.65938,0.00807986 -3.01111,-1.33056 -3.01919,-2.98993c-0.00127082,-0.260991 0.0314695,-0.521035 0.0973943,-0.773566l2.7852,-10.5094l2.79671e-08,-1.05468e-07c0.270652,-1.02067 -0.262537,-2.08289 -1.2427,-2.4757l-2.20542e-08,-8.83296e-09c-0.379505,-0.151996 -0.628276,-0.519688 -0.6282,-0.9285v-0.3229l-1.77636e-14,-9.40042e-08c-8.33514e-08,-0.552011 0.447289,-0.999614 0.9993,-1h2.9887l1.06497e-07,-5.28143e-10c1.65943,-0.00822943 3.01133,1.33033 3.01956,2.98976c0.00129458,0.261047 -0.0314356,0.52115 -0.0973639,0.773738Z" fill="currentColor" fill-rule="evenodd"></path></svg>

View file

@ -58,7 +58,7 @@
<div class="grid grid-cols-2 gap-3 mb-3">
<div class="floating-wrapper">
<input class="floating-input peer" type="text" id="host" name="host" type="text" {% if node.host %} value="{{ node.protocol }}://{{ node.host }}" {% else %} placeholder="" {% endif %}>
<input class="floating-input peer" type="text" id="host" name="host" type="text" {% if node.host %} value="{{ node.protocol }}://{{ node.host }}" {% else %} placeholder=" " {% endif %}>
<label class="floating-label" for="host">Host</label>
</div>
<div class="floating-wrapper">

View file

@ -150,6 +150,15 @@
</div>
</text-explainer>
<text-explainer class="p-3">
<span slot="title" class="cursor-pointer">{{ _("Support Tools") }} </span>
<div slot="content" class="mt-3">
<a class="btn" href="{{url_for('spectrum_endpoint.wallets_get') }}">
Wallet Management
</a>
</div>
</text-explainer>
<div class="mt-8">
<button type="submit" class="button bg-accent text-white" name="action" value="save">{{ _("Save") }}</button>
</div>

View file

@ -9,7 +9,7 @@
<h3 class="mt-8">Information</h3>
<div class="floating-wrapper">
<input type="text" pattern="^[^']+" title="Do not use single quotes" id="wallet_name" name="wallet_name" class="peer floating-input" value="{{ wallet_name }}" placeholder=" " required>
<input type="text" pattern="^[^']+" title="Choose a unique name. Capitalization, spaces, and hyphens will not affect uniqueness. Don't use single quotes." id="wallet_name" name="wallet_name" class="peer floating-input" value="{{ wallet_name }}" placeholder=" " required>
<label class="floating-label">Wallet Name</label>
</div>

View file

@ -44,7 +44,7 @@
{{ _("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="button">{{ _("Verify address via QR code") }}</button>
{% endif %}
{% set supports_hwi = [] %}
{% set supports_hwi_multisig_display_address = [] %}

View file

@ -123,7 +123,7 @@
<h3>{{ _("Export to wallet software") }}</h3>
<p>{{ _("Export 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="button mt-3" data-cy="show-export-details-overlay-btn">{{ _("Go to export details") }}</button>
<button onclick="showPageOverlay('account_map_export', null, 'start')" type="button" class="button mt-3" data-cy="show-export-details-overlay-btn">{{ _("Go to export details") }}</button>
<h3 class="mt-8">{{ _("Specter wallet backup") }}</h3>
<p>{{ _("Create a JSON file containing all wallet data including labels.") }}</p>

View file

@ -8,11 +8,8 @@
{% endif %}
<div class="max-w-prose">
<p>{{ _("Specter is a convenient wallet to self-custody your bitcoin. Its great for single signature wallets, but is specifically designed with a focus on multiple signature setups with multiple signing devices.") }}</p>
<p class="mt-6">
{{ _("Learn more about ") }}
<a href="https://specter-desktop-docs.netlify.app/multisig-guide/" target="_blank">{{ _("multiple signature setups here") }}</a>.
</p>
<p>{{ _("Specter empowers individuals, communities and businesses to control their own Bitcoin keys, improving their security and enabling them to manage their bitcoin with greater control.") }}</p>
<!-- <p>{{ _("Lets get you setup. First, let's decide whether you'll be using your own Bitcoin server, or whether you'd like to use one run by a trusted Bitcoin company.") }}</p> -->
</div>
{% if not specter.chain %}

View file

@ -166,3 +166,27 @@ def index_post():
)
raise Exception(f"Unknown action: {action}")
@spectrum_endpoint.route("/wallets", methods=["GET"])
@login_required
def wallets_get():
wallets = ext().spectrum_node.get_rpc().listwallets()
wallets_dict = {}
for wallet_name in wallets:
rpc = ext().spectrum_node.get_rpc().wallet(wallet_name)
wallets_dict[wallet_name] = rpc.getwalletinfo()
return render_template(
"spectrum/spectrum_wallets.jinja", wallets=wallets, wallets_dict=wallets_dict
)
@spectrum_endpoint.route("/wallets", methods=["POST"])
@login_required
def wallets_post():
wallet_name = request.form["wallet_name"]
# Does not work yet
# ext().spectrum_node.get_rpc().wallet()delete_wallet()
logger.info("Would delete wallet if it would yet be possible!")
flash("Deletion not yet implemented!")
return redirect(url_for(f"{ SpectrumService.get_blueprint_name()}.wallets_get"))

View file

@ -8,14 +8,16 @@
</div>
{% endif %}
{% if specter.chain %}
<table>
<tr> <td data-style="text-align: left;">{{ _("Network") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Blocks count") }}:</td> <td data-style="text-align: right;">{{specter.info['blocks']}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Host") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.node.host}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Port") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.node.port}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("SSL") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.node.ssl}}</td> </tr>
<tr> <td data-style="text-align: left;">{{ _("Node uptime") }}:</td> <td data-style="text-align: right;">~ {{(specter.info['uptime'] / 60 // 60) | int }} {{ _("Hours") }}</td> </tr>
</table>
<div class="m-3 mb-1">
<table>
<tr> <td>{{ _("Network") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
<tr> <td>{{ _("Blocks count") }}</td> <td class="text-right">{{specter.info['blocks']}}</td> </tr>
<tr> <td>{{ _("Host") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.node.host}}</td> </tr>
<tr> <td>{{ _("Port") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.node.port}}</td> </tr>
<tr> <td>{{ _("SSL") }}</td> <td class="text-right" id="node-info-specter-chain">{{specter.node.ssl}}</td> </tr>
<tr> <td>{{ _("Node uptime") }}</td> <td class="text-right">~ {{(specter.info['uptime'] / 60 // 60) | int }} {{ _("Hours") }}</td> </tr>
</table>
</div>
<div class="px-3 pb-4 mt-2">
{% if current_user.is_admin %}
@ -57,4 +59,4 @@
setTimeout(updateProgressBar, 10000);
}
}
</script>
</script>

View file

@ -6,11 +6,10 @@
- active_menuitem: Current active tab. Options: 'general', 'settings', ...
#}
{% macro spectrum_menu(active_menuitem) -%}
<nav class="row collapse-on-mobile">
{{ menu_item(service.id, 'index', 'Main', active_menuitem, isLeft=true) }}
{{ menu_item(service.id, 'settings_get', 'Settings', active_menuitem, isRight=true) }}
<a href="javascript:void(0);" class="mobile-nav-icon" onclick="toggleMobileNav(this, `{{ url_for('static', filename='img/expand-more.svg') }}`, `{{ url_for('static', filename='img/expand-less.svg') }}`)">
<img style="width: 36px;" src="{{ url_for('static', filename='img/expand-more.svg') }}"/>
</a>
<nav class="text-center text-dark-200 border-b border-dark-600">
<ul class="flex flex-wrap -mb-px">
{{ menu_item(service.id, 'index', 'Main', active_menuitem) }}
{{ menu_item(service.id, 'wallets_get', 'Wallet Management', active_menuitem, isRight=true) }}
</ul>
</nav>
{%- endmacro %}

View file

@ -1,4 +1,4 @@
{% extends "spectrum/base.jinja" %}
{% extends "spectrum/components/spectrum_tab.jinja" %}
{% block main %}
<h1>{{ _("Connect with Electrum") }}</h1>
@ -44,7 +44,6 @@
</div>
<div id ="elec_list_container" class="floating-wrapper">
<select id="server-list" name="elec_option" class="floating-input peer">
<option selected style='display: none'></option>
{% for electrum_option in elec_options %}
<option value="{{ electrum_option }}" {% if elec_chosen_option == electrum_option %}selected{% endif %}>{{ electrum_option }}</option>
{% endfor %}

View file

@ -2,7 +2,7 @@
{% block main %}
<div>
<h1>{{ _("Spectrum - Connecting Specter to Electrum") }}</h1><br>
<h1>{{ _("Connect with Electrum") }}</h1>
<div>
{% if host_type == "list" %}
{% if success %}

View file

@ -0,0 +1,63 @@
{% extends "spectrum/components/spectrum_tab.jinja" %}
{% block title %}{% endblock %}
{% set tab = 'index' %}
{% block content %}
<div>
<h1>{{ _("Wallet Management") }}</h1>
<div class="note">
{{ _("Other than core, Spectrum lets you delete wallets. The limitation of core has been") }}<br/>
{{ _("surprising effects about wallet naming. This could have lead to inconsitencies which are") }}<br/>
{{ _("difficult to solve.") }}<br/>
{{ _("Here, you can delete wallets if you really need to. Use it with care!") }}<br/>
</div>
<div class="table-holder">
<table class="space-y-3 max-w-[700px] m-auto min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Version</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Balance</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Un. Bl.</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Imm. Bl.</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tx #</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Keyp</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Keyp Int</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pay Tx Fee</th>
<th scope="col" class="py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-gray-200">
{% for wallet_name, wallet in wallets_dict.items() %}
<tr>
<td class="py-4 ">{{ wallet['walletname'] }}</td>
<td class="py-4 ">{{ wallet['walletversion'] }}</td>
<td class="py-4 ">{{ wallet['balance'] }}</td>
<td class="py-4 ">{{ wallet['uconfirmed_balance'] }}</td>
<td class="py-4 ">{{ wallet['immature_balance'] }}</td>
<td class="py-4 ">{{ wallet['txcount'] }}</td>
<td class="py-4 ">{{ wallet['keypoolsize'] }}</td>
<td class="py-4 ">{{ wallet['keypoolsize_hd_internal'] }}</td>
<td class="py-4 ">{{ wallet['paytxfee'] }}</td>
<td class="py-4 ">
<form action="{{ url_for('spectrum_endpoint.wallets_post')}}" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="wallet_name" value="{{ wallet['walletname'] }}"/>
<button type="submit" name="action" value="delete" class="button">
<img src="/static/img/cross_thick.svg" class="w-5">
<tool-tip id="tooltip-wallet-deletion">
<h4 slot="title">{{ _("Wallet deletion") }}</h4>
<span slot="paragraph">
{{ _("Clicking here would delete the Wallet but only on the Spectrum, not in Specter.") }}
</span>
</tool-tip>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<br/>
</div>
{% endblock %}

View file

@ -1,9 +1,10 @@
import logging
from cryptoadvance.specter.helpers import deep_update, load_jsons, is_ip_private, alias
logger = logging.getLogger(__name__)
def test_deep_update():
import cryptoadvance.specter.helpers as helpers
base_value = {
"pli": {"pla": "blub", "yes": "yes"},
"pli2": ["arrayelm1", "arrayelm2", "arrayelm3"],
@ -18,7 +19,7 @@ def test_deep_update():
"pli": {"newSubKey": "blub2"},
"pli2": ["arrayelm4", "arrayelm5"],
}
helpers.deep_update(base_value, update_value)
deep_update(base_value, update_value)
# There is now a newRootKey
assert len(base_value) == 4
# keys get added
@ -26,19 +27,18 @@ def test_deep_update():
# Arrays get replaced, not appended!
assert len(base_value["pli2"]) == 2
# you cannot delete stuff with empty dicts
helpers.deep_update(base_value, {"newRootKey": {}})
deep_update(base_value, {"newRootKey": {}})
assert base_value["newRootKey"]["pla"] == "blub"
def test_load_jsons(caplog):
caplog.set_level(logging.INFO)
caplog.set_level(logging.DEBUG, logger="cryptoadvance.specter")
import cryptoadvance.specter.helpers as helpers
mydict = helpers.load_jsons("./tests/helpers_testdata")
mydict = load_jsons("./tests/helpers_testdata")
assert mydict["some_jsonfile"]["blub"] == "bla"
assert mydict["some_other_jsonfile"]["bla"] == "blub"
mydict = helpers.load_jsons("./tests/helpers_testdata", "id")
mydict = load_jsons("./tests/helpers_testdata", "id")
assert "some_jsonfile" not in mydict
# instead the value for the key "id" is now used as the top-level key
assert mydict["ID123"]["blub"] == "bla"
@ -56,7 +56,6 @@ def test_load_jsons(caplog):
def test_is_ip_private(caplog):
caplog.set_level(logging.INFO)
caplog.set_level(logging.DEBUG, logger="cryptoadvance.specter")
import cryptoadvance.specter.helpers as helpers
# https://en.wikipedia.org/wiki/Private_network
# For each private network address range, we are testing the lower, arbitrary middle and upper part of the range
@ -68,14 +67,43 @@ def test_is_ip_private(caplog):
# Randomly generated public ip addresses
public_addresses = ["58.28.76.138", "128.5.218.201", "170.45.214.32"]
assert helpers.is_ip_private("localhost")
assert is_ip_private("localhost")
for ip in priv_lo_addresses:
assert helpers.is_ip_private(ip)
assert is_ip_private(ip)
for ip in priv_24_addresses:
assert helpers.is_ip_private(ip)
assert is_ip_private(ip)
for ip in priv_20_addresses:
assert helpers.is_ip_private(ip)
assert is_ip_private(ip)
for ip in priv_16_addresses:
assert helpers.is_ip_private(ip)
assert is_ip_private(ip)
for ip in public_addresses:
assert not helpers.is_ip_private(ip)
assert not is_ip_private(ip)
def test_alias():
the_same_unique_names = [
"ghost wallet",
"Ghost Wallet",
"GHOST WALLET",
"gHoSt wALlEt",
" Ghost-Wallet",
"Ghost-Wallet ",
" Ghost-Wallet ",
"ghost-wallet",
"Ghost_Wallet",
"ghost_wallet",
"ghost wallet",
"ghost----wallet",
"Ghost Wallet?",
"Ghost Wallet***",
]
assert all(alias(name) == "ghost_wallet" for name in the_same_unique_names)
not_the_same_unique_names = [
"ghost wallet",
"ghost wallet 2",
"ghostwallet",
"ghost.wallet",
"ghostwallet123",
"my_ghos_wallet",
]
assert not all(alias(name) == "ghost_wallet" for name in not_the_same_unique_names)

View file

@ -138,7 +138,7 @@ def test_device_wallets(
assert device.wallets(wm)[0].alias == wallet.alias
second_device = device_manager.get_by_alias("specter")
multisig_wallet = wm.create_wallet(
"a_multisig_test_wallet",
"multisig_wallet_for_device_test",
1,
"wsh",
[device.keys[7], second_device.keys[0]],

View file

@ -42,9 +42,11 @@ def test_WalletManager(
device = device_manager.get_by_alias("trezor")
assert device != None
# Lets's create a wallet with the WalletManager
wm.create_wallet("a_test_wallet", 1, "wpkh", [device.keys[5]], [device])
wm.create_wallet(
"wallet_for_wallet_manager_test", 1, "wpkh", [device.keys[5]], [device]
)
# The wallet-name gets its filename and therefore its alias
wallet = wm.wallets["a_test_wallet"]
wallet = wm.wallets["wallet_for_wallet_manager_test"]
assert wallet != None
assert wallet.balance["trusted"] == 0
assert wallet.balance["untrusted_pending"] == 0
@ -76,12 +78,18 @@ def test_WalletManager(
multisig_wallet.update_balance()
assert multisig_wallet.amount_total == 4
# The WalletManager also has a `wallets_names` property, returning a sorted list of the names of all wallets
assert wm.wallets_names == ["a_multisig_test_wallet", "a_test_wallet"]
assert wm.wallets_names == [
"a_multisig_test_wallet",
"wallet_for_wallet_manager_test",
]
# You can rename a wallet using the wallet manager using `rename_wallet`, passing the wallet object and the new name to assign to it
wm.rename_wallet(multisig_wallet, "new_name_test_wallet")
assert multisig_wallet.name == "new_name_test_wallet"
assert wm.wallets_names == ["a_test_wallet", "new_name_test_wallet"]
assert wm.wallets_names == [
"new_name_test_wallet",
"wallet_for_wallet_manager_test",
]
# You can also delete a wallet by passing it to the wallet manager's `delete_wallet` method
# It will delete the json and attempt to remove it from Bitcoin Core
@ -100,7 +108,7 @@ def test_WalletManager(
):
wm.delete_wallet(wallet, node)
# Check that the wallet wasn't deleted in Specter because of the RpcError
assert wm.wallets_names == ["a_test_wallet"]
assert wm.wallets_names == ["wallet_for_wallet_manager_test"]
# The following deletion should not remove the wallet file on the node
assert node_with_empty_datadir.datadir == ""
wm.rpc.loadwallet(wallet_rpc_path) # we need to load the wallet again
@ -109,7 +117,8 @@ def test_WalletManager(
# The wallet in Specter was already deleted, so trying to delete it again should raise a SpecterError
wm.rpc.loadwallet(wallet_rpc_path) # we need to load the wallet again
with pytest.raises(
SpecterError, match="The wallet a_test_wallet has already been deleted."
SpecterError,
match="The wallet wallet_for_wallet_manager_test has already been deleted.",
):
assert wm.delete_wallet(wallet)
@ -137,9 +146,9 @@ def test_WalletManager_2_nodes(
device = device_manager.get_by_alias("trezor")
assert device != None
first_wallet = wm.create_wallet(
"a_test_wallet", 1, "wpkh", [device.keys[5]], [device]
"wallet_for_test_with_two_nodes", 1, "wpkh", [device.keys[5]], [device]
)
assert wm.wallets_names == ["a_test_wallet"]
assert wm.wallets_names == ["wallet_for_test_with_two_nodes"]
assert wm.chain == "regtest"
assert wm.working_folder.endswith("regtest")
assert wm.rpc.port == 18543
@ -154,7 +163,7 @@ def test_WalletManager_2_nodes(
"regtest",
] # wm.rpcs looks like this: {'regtest': <BitcoinRpc http://localhost:18543>, 'regtest2': <BitcoinRpc http://localhost:18544>}
assert wm.rpc.port == 18544
assert wm.wallets_names == ["a_test_wallet"]
assert wm.wallets_names == ["wallet_for_test_with_two_nodes"]
assert wm.chain == "regtest"
assert wm.working_folder.endswith("test")
second_wallet = wm.create_wallet(
@ -162,7 +171,10 @@ def test_WalletManager_2_nodes(
)
# Note: "regtest2" is recognised by the get_network() from embit as Liquid, that is why there is an error in the logs saying the Bitcoin address is not valid since a Liquid address is derived.
assert len(wm.wallets_names) == 2
assert wm.wallets_names == ["a_regtest2_test_wallet", "a_test_wallet"]
assert wm.wallets_names == [
"a_regtest2_test_wallet",
"wallet_for_test_with_two_nodes",
]
def test_WalletManager_check_duplicate_keys(empty_data_folder):
@ -243,7 +255,7 @@ def test_wallet_sortedmulti(
for i in range(2):
if i == 0:
multisig_wallet = wm.create_wallet(
"a_multisig_test_wallet",
"multisig_wallet_for_sortedmulti_test",
1,
"wsh",
[device.keys[7], second_device.keys[0]],
@ -251,7 +263,7 @@ def test_wallet_sortedmulti(
)
else:
multisig_wallet = wm.create_wallet(
"a_multisig_test_wallet",
"another_multisig_wallet_for_sortedmulti_test",
1,
"wsh",
[second_device.keys[0], device.keys[7]],