diff --git a/electrum/gui/qt/wallet_info_dialog.py b/electrum/gui/qt/wallet_info_dialog.py index 11ecdd86f..c40b01de6 100644 --- a/electrum/gui/qt/wallet_info_dialog.py +++ b/electrum/gui/qt/wallet_info_dialog.py @@ -162,6 +162,6 @@ class WalletInfoDialog(WindowModalDialog): vbox.addStretch(1) btn_export_info = run_hook('wallet_info_buttons', window, self) btn_close = CloseButton(self) - btns = Buttons(btn_export_info, btn_close) + btns = Buttons(*btn_export_info, btn_close) vbox.addLayout(btns) self.setLayout(vbox) diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py index b92310b07..3ded396a0 100644 --- a/electrum/plugins/coldcard/qt.py +++ b/electrum/plugins/coldcard/qt.py @@ -29,6 +29,10 @@ class Plugin(ColdcardPlugin, QtPluginBase): def create_handler(self, window): return Coldcard_Handler(window) + @staticmethod + def trim_file_suffix(path): + return path.rsplit('.', 1)[0] + @only_hook_if_libraries_available @hook def receive_menu(self, menu, addrs, wallet): @@ -48,23 +52,59 @@ class Plugin(ColdcardPlugin, QtPluginBase): # user is about to see the "Wallet Information" dialog # - add a button if multisig wallet, and a Coldcard is a cosigner. assert isinstance(main_window, ElectrumWindow), f"{type(main_window)}" + + buttons = [] wallet = main_window.wallet if type(wallet) is not Multisig_Wallet: return - if not any(type(ks) == self.keystore_class for ks in wallet.get_keystores()): + coldcard_keystores = [ + ks + for ks in wallet.get_keystores() + if type(ks) == self.keystore_class + ] + if not coldcard_keystores: # doesn't involve a Coldcard wallet, hide feature return - btn = QPushButton(_("Export for Coldcard")) - btn.clicked.connect(lambda unused: self.export_multisig_setup(main_window, wallet)) + btn_export = QPushButton(_("Export multisig for Coldcard as file")) + btn_export.clicked.connect(lambda unused: self.export_multisig_setup(main_window, wallet)) + buttons.append(btn_export) + btn_import_usb = QPushButton(_("Export multisig to Coldcard via USB")) + btn_import_usb.clicked.connect(lambda unused: self.import_multisig_wallet_to_cc(main_window, coldcard_keystores)) + buttons.append(btn_import_usb) + return buttons - return btn + def import_multisig_wallet_to_cc(self, main_window, coldcard_keystores): + from io import StringIO + from ckcc.protocol import CCProtocolPacker - def export_multisig_setup(self, main_window, wallet): + index = main_window.query_choice( + _("Please select which {} device to use:").format(self.device), + [(i, ks.label) for i, ks in enumerate(coldcard_keystores)] + ) + if index is not None: + selected_keystore = coldcard_keystores[index] + client = self.get_client(selected_keystore, force_pair=True, allow_user_interaction=False) + if client is None: + main_window.show_error("{} not connected.").format(selected_keystore.label) + return + + wallet = main_window.wallet + sio = StringIO() + basename = self.trim_file_suffix(wallet.basename()) + ColdcardPlugin.export_ms_wallet(wallet, sio, basename) + sio.seek(0) + file_len, sha = client.dev.upload_file(sio.read().encode("utf-8"), verify=True) + client.dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha)) + main_window.show_message('\n'.join([ + _("Wallet setup file '{}' imported successfully.").format(basename), + _("Confirm import on your {} device.").format(selected_keystore.label) + ])) - basename = wallet.basename().rsplit('.', 1)[0] # trim .json + def export_multisig_setup(self, main_window, wallet): + basename = self.trim_file_suffix(wallet.basename()) name = f'{basename}-cc-export.txt'.replace(' ', '-') fileName = getSaveFileName( parent=main_window, @@ -76,7 +116,7 @@ class Plugin(ColdcardPlugin, QtPluginBase): if fileName: with open(fileName, "wt") as f: ColdcardPlugin.export_ms_wallet(wallet, f, basename) - main_window.show_message(_("Wallet setup file exported successfully")) + main_window.show_message(_("Wallet setup file '{}' exported successfully").format(name)) def show_settings_dialog(self, window, keystore): # When they click on the icon for CC we come here. @@ -220,7 +260,6 @@ class CKCCSettingsDialog(WindowModalDialog): from ckcc.utils import dfu_parse from ckcc.sigheader import FW_HEADER_SIZE, FW_HEADER_OFFSET, FW_HEADER_MAGIC from ckcc.protocol import CCProtocolPacker - from hashlib import sha256 import struct try: