wizard: add bitbox02 new wallet init and checks to new wizard
This commit is contained in:
@@ -720,9 +720,9 @@ class WCScriptAndDerivation(WizardComponent, Logger):
|
|||||||
self.apply()
|
self.apply()
|
||||||
|
|
||||||
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
|
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
|
||||||
derivation_valid = is_bip32_derivation(cosigner_data['derivation_path'])
|
valid = is_bip32_derivation(cosigner_data['derivation_path'])
|
||||||
|
|
||||||
if derivation_valid:
|
if valid:
|
||||||
valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
|
valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
|
||||||
if not valid:
|
if not valid:
|
||||||
# TODO: user feedback
|
# TODO: user feedback
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
|
|||||||
self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))
|
self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))
|
||||||
self.back_button.setEnabled(not page.busy)
|
self.back_button.setEnabled(not page.busy)
|
||||||
self.next_button.setText(_('Next') if not self.is_last(page.wizard_data) else _('Finish'))
|
self.next_button.setText(_('Next') if not self.is_last(page.wizard_data) else _('Finish'))
|
||||||
self.next_button.setEnabled(page.valid)
|
self.next_button.setEnabled(not page.busy and page.valid)
|
||||||
self.main_widget.setVisible(not page.busy and not bool(page.error))
|
self.main_widget.setVisible(not page.busy and not bool(page.error))
|
||||||
self.please_wait.setVisible(page.busy)
|
self.please_wait.setVisible(page.busy)
|
||||||
self.please_wait_l.setText(page.busy_msg if page.busy_msg else _("Please wait..."))
|
self.please_wait_l.setText(page.busy_msg if page.busy_msg else _("Please wait..."))
|
||||||
|
|||||||
@@ -31,13 +31,8 @@ _logger = get_logger(__name__)
|
|||||||
try:
|
try:
|
||||||
from bitbox02 import bitbox02
|
from bitbox02 import bitbox02
|
||||||
from bitbox02 import util
|
from bitbox02 import util
|
||||||
from bitbox02.communication import (
|
from bitbox02.communication import (devices, HARDENED, u2fhid, bitbox_api_protocol,
|
||||||
devices,
|
FirmwareVersionOutdatedException)
|
||||||
HARDENED,
|
|
||||||
u2fhid,
|
|
||||||
bitbox_api_protocol,
|
|
||||||
FirmwareVersionOutdatedException,
|
|
||||||
)
|
|
||||||
requirements_ok = True
|
requirements_ok = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
if not (isinstance(e, ModuleNotFoundError) and e.name == 'bitbox02'):
|
if not (isinstance(e, ModuleNotFoundError) and e.name == 'bitbox02'):
|
||||||
@@ -45,6 +40,10 @@ except ImportError as e:
|
|||||||
requirements_ok = False
|
requirements_ok = False
|
||||||
|
|
||||||
|
|
||||||
|
class BitBox02NotInitialized(UserFacingException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BitBox02Client(HardwareClientBase):
|
class BitBox02Client(HardwareClientBase):
|
||||||
# handler is a BitBox02_Handler, importing it would lead to a circular dependency
|
# handler is a BitBox02_Handler, importing it would lead to a circular dependency
|
||||||
def __init__(self, handler: HardwareHandlerBase, device: Device, config: SimpleConfig, *, plugin: HW_PluginBase):
|
def __init__(self, handler: HardwareHandlerBase, device: Device, config: SimpleConfig, *, plugin: HW_PluginBase):
|
||||||
@@ -119,7 +118,7 @@ class BitBox02Client(HardwareClientBase):
|
|||||||
bitbox02_config = self.config.get("bitbox02")
|
bitbox02_config = self.config.get("bitbox02")
|
||||||
noise_keys = bitbox02_config.get("remote_static_noise_keys")
|
noise_keys = bitbox02_config.get("remote_static_noise_keys")
|
||||||
if noise_keys is not None:
|
if noise_keys is not None:
|
||||||
if pubkey.hex() in [noise_key for noise_key in noise_keys]:
|
if pubkey.hex() in noise_keys:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -192,7 +191,7 @@ class BitBox02Client(HardwareClientBase):
|
|||||||
def fail_if_not_initialized(self) -> None:
|
def fail_if_not_initialized(self) -> None:
|
||||||
assert self.bitbox02_device
|
assert self.bitbox02_device
|
||||||
if not self.bitbox02_device.device_info()["initialized"]:
|
if not self.bitbox02_device.device_info()["initialized"]:
|
||||||
raise Exception(
|
raise BitBox02NotInitialized(
|
||||||
"Please initialize the BitBox02 using the BitBox app first before using the BitBox02 in electrum"
|
"Please initialize the BitBox02 using the BitBox app first before using the BitBox02 in electrum"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -248,12 +247,8 @@ class BitBox02Client(HardwareClientBase):
|
|||||||
else:
|
else:
|
||||||
raise Exception("invalid xtype:{}".format(xtype))
|
raise Exception("invalid xtype:{}".format(xtype))
|
||||||
|
|
||||||
return self.bitbox02_device.btc_xpub(
|
return self.bitbox02_device.btc_xpub(keypath=xpub_keypath, xpub_type=out_type, coin=coin_network,
|
||||||
keypath=xpub_keypath,
|
display=display)
|
||||||
xpub_type=out_type,
|
|
||||||
coin=coin_network,
|
|
||||||
display=display,
|
|
||||||
)
|
|
||||||
|
|
||||||
@runs_in_hwd_thread
|
@runs_in_hwd_thread
|
||||||
def label(self) -> str:
|
def label(self) -> str:
|
||||||
@@ -565,6 +560,7 @@ class BitBox02Client(HardwareClientBase):
|
|||||||
)
|
)
|
||||||
return signature
|
return signature
|
||||||
|
|
||||||
|
|
||||||
class BitBox02_KeyStore(Hardware_KeyStore):
|
class BitBox02_KeyStore(Hardware_KeyStore):
|
||||||
hw_type = "bitbox02"
|
hw_type = "bitbox02"
|
||||||
device = "BitBox02"
|
device = "BitBox02"
|
||||||
@@ -600,7 +596,6 @@ class BitBox02_KeyStore(Hardware_KeyStore):
|
|||||||
keypath = self.get_derivation_prefix() + "/%d/%d" % sequence
|
keypath = self.get_derivation_prefix() + "/%d/%d" % sequence
|
||||||
return client.sign_message(keypath, message.encode("utf-8"), script_type)
|
return client.sign_message(keypath, message.encode("utf-8"), script_type)
|
||||||
|
|
||||||
|
|
||||||
@runs_in_hwd_thread
|
@runs_in_hwd_thread
|
||||||
def sign_transaction(self, tx: PartialTransaction, password: str):
|
def sign_transaction(self, tx: PartialTransaction, password: str):
|
||||||
if tx.is_complete():
|
if tx.is_complete():
|
||||||
@@ -612,7 +607,6 @@ class BitBox02_KeyStore(Hardware_KeyStore):
|
|||||||
try:
|
try:
|
||||||
self.handler.show_message("Authorize Transaction...")
|
self.handler.show_message("Authorize Transaction...")
|
||||||
client.sign_transaction(self, tx, self.handler.get_wallet())
|
client.sign_transaction(self, tx, self.handler.get_wallet())
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.handler.finished()
|
self.handler.finished()
|
||||||
|
|
||||||
@@ -639,6 +633,7 @@ class BitBox02_KeyStore(Hardware_KeyStore):
|
|||||||
self.logger.exception("")
|
self.logger.exception("")
|
||||||
self.handler.show_error(e)
|
self.handler.show_error(e)
|
||||||
|
|
||||||
|
|
||||||
class BitBox02Plugin(HW_PluginBase):
|
class BitBox02Plugin(HW_PluginBase):
|
||||||
keystore_class = BitBox02_KeyStore
|
keystore_class = BitBox02_KeyStore
|
||||||
minimum_library = (6, 2, 0)
|
minimum_library = (6, 2, 0)
|
||||||
@@ -700,9 +695,9 @@ class BitBox02Plugin(HW_PluginBase):
|
|||||||
id_ = str(d['path'])
|
id_ = str(d['path'])
|
||||||
return device._replace(id_=id_)
|
return device._replace(id_=id_)
|
||||||
|
|
||||||
# new wizard
|
|
||||||
|
|
||||||
def wizard_entry_for_device(self, device_info: 'DeviceInfo', *, new_wallet=True) -> str:
|
def wizard_entry_for_device(self, device_info: 'DeviceInfo', *, new_wallet=True) -> str:
|
||||||
|
# Note: device_info.initialized for this hardware doesn't imply a seed is present,
|
||||||
|
# only that it has firmware installed
|
||||||
if new_wallet:
|
if new_wallet:
|
||||||
return 'bitbox02_start' if device_info.initialized else 'bitbox02_not_initialized'
|
return 'bitbox02_start' if device_info.initialized else 'bitbox02_not_initialized'
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
|
import threading
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot, pyqtSignal
|
||||||
QPushButton,
|
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLineEdit, QHBoxLayout
|
||||||
QLabel,
|
|
||||||
QVBoxLayout,
|
|
||||||
QLineEdit,
|
|
||||||
QHBoxLayout,
|
|
||||||
)
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot
|
|
||||||
|
|
||||||
from electrum.gui.qt.util import (
|
|
||||||
WindowModalDialog,
|
|
||||||
OkButton,
|
|
||||||
ButtonsTextEdit,
|
|
||||||
)
|
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import hook
|
from electrum.plugin import hook
|
||||||
|
from electrum.util import UserCancelled, UserFacingException
|
||||||
|
|
||||||
from .bitbox02 import BitBox02Plugin
|
from .bitbox02 import BitBox02Plugin
|
||||||
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
||||||
from ..hw_wallet.plugin import only_hook_if_libraries_available
|
from ..hw_wallet.plugin import only_hook_if_libraries_available, OperationCancelled
|
||||||
|
|
||||||
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWUninitialized, WCHWXPub
|
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWUninitialized, WCHWXPub
|
||||||
|
from electrum.gui.qt.util import WindowModalDialog, OkButton, ButtonsTextEdit
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from electrum.gui.qt.wizard.wallet import QENewWalletWizard
|
from electrum.gui.qt.wizard.wallet import QENewWalletWizard
|
||||||
@@ -77,7 +68,7 @@ class Plugin(BitBox02Plugin, QtPluginBase):
|
|||||||
def extend_wizard(self, wizard: 'QENewWalletWizard'):
|
def extend_wizard(self, wizard: 'QENewWalletWizard'):
|
||||||
super().extend_wizard(wizard)
|
super().extend_wizard(wizard)
|
||||||
views = {
|
views = {
|
||||||
'bitbox02_start': {'gui': WCScriptAndDerivation},
|
'bitbox02_start': {'gui': WCBitbox02ScriptAndDerivation},
|
||||||
'bitbox02_xpub': {'gui': WCHWXPub},
|
'bitbox02_xpub': {'gui': WCHWXPub},
|
||||||
'bitbox02_not_initialized': {'gui': WCHWUninitialized},
|
'bitbox02_not_initialized': {'gui': WCHWUninitialized},
|
||||||
'bitbox02_unlock': {'gui': WCHWUnlock}
|
'bitbox02_unlock': {'gui': WCHWUnlock}
|
||||||
@@ -92,12 +83,7 @@ class BitBox02_Handler(QtHandlerBase):
|
|||||||
super(BitBox02_Handler, self).__init__(win, "BitBox02")
|
super(BitBox02_Handler, self).__init__(win, "BitBox02")
|
||||||
|
|
||||||
def name_multisig_account(self):
|
def name_multisig_account(self):
|
||||||
return QMetaObject.invokeMethod(
|
return QMetaObject.invokeMethod(self, "_name_multisig_account", Qt.BlockingQueuedConnection, Q_RETURN_ARG(str))
|
||||||
self,
|
|
||||||
"_name_multisig_account",
|
|
||||||
Qt.BlockingQueuedConnection,
|
|
||||||
Q_RETURN_ARG(str),
|
|
||||||
)
|
|
||||||
|
|
||||||
@pyqtSlot(result=str)
|
@pyqtSlot(result=str)
|
||||||
def _name_multisig_account(self):
|
def _name_multisig_account(self):
|
||||||
@@ -125,3 +111,46 @@ class BitBox02_Handler(QtHandlerBase):
|
|||||||
dialog.setLayout(vbox)
|
dialog.setLayout(vbox)
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
return name.text().strip()
|
return name.text().strip()
|
||||||
|
|
||||||
|
|
||||||
|
class WCBitbox02ScriptAndDerivation(WCScriptAndDerivation):
|
||||||
|
def __init__(self, parent, wizard):
|
||||||
|
WCScriptAndDerivation.__init__(self, parent, wizard)
|
||||||
|
self._busy = True
|
||||||
|
self.title = ''
|
||||||
|
self.client = None
|
||||||
|
|
||||||
|
def on_ready(self):
|
||||||
|
super().on_ready()
|
||||||
|
_name, _info = self.wizard_data['hardware_device']
|
||||||
|
plugin = self.wizard.plugins.get_plugin(_info.plugin_name)
|
||||||
|
|
||||||
|
device_id = _info.device.id_
|
||||||
|
self.client = self.wizard.plugins.device_manager.client_by_id(device_id, scan_now=False)
|
||||||
|
if not self.client.handler:
|
||||||
|
self.client.handler = plugin.create_handler(self.wizard)
|
||||||
|
self.client.setupRunning = True
|
||||||
|
self.check_device()
|
||||||
|
|
||||||
|
def check_device(self):
|
||||||
|
self.error = None
|
||||||
|
self.valid = False
|
||||||
|
self.busy = True
|
||||||
|
|
||||||
|
def check_task():
|
||||||
|
try:
|
||||||
|
self.client.pairing_dialog()
|
||||||
|
self.title = _('Script type and Derivation path')
|
||||||
|
self.valid = True
|
||||||
|
except (UserCancelled, OperationCancelled):
|
||||||
|
self.error = _('Cancelled')
|
||||||
|
self.wizard.requestPrev.emit()
|
||||||
|
except UserFacingException as e:
|
||||||
|
self.error = str(e)
|
||||||
|
except Exception as e:
|
||||||
|
self.error = repr(e)
|
||||||
|
finally:
|
||||||
|
self.busy = False
|
||||||
|
|
||||||
|
t = threading.Thread(target=check_task, daemon=True)
|
||||||
|
t.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user