def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network self.preblockhash = self.network.get_pre_blockhash() storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists(): print("Wallet not found. try 'electrum create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'') self.encoding = locale.getpreferredencoding() self.stdscr = curses.initscr() curses.noecho() curses.cbreak() curses.start_color() curses.use_default_colors() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN) curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) self.stdscr.keypad(1) self.stdscr.border(0) self.maxy, self.maxx = self.stdscr.getmaxyx() self.set_cursor(0) self.w = curses.newwin(10, 50, 5, 5) set_verbosity(False) self.tab = 0 self.pos = 0 self.popup_pos = 0 self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.history = None if self.network: self.network.register_callback(self.update, ['updated']) self.tab_names = [ _("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner") ] self.num_tabs = len(self.tab_names)
def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists(): print("Wallet not found. try 'electrum create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'') self.encoding = locale.getpreferredencoding() self.stdscr = curses.initscr() curses.noecho() curses.cbreak() curses.start_color() curses.use_default_colors() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN) curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) self.stdscr.keypad(1) self.stdscr.border(0) self.maxy, self.maxx = self.stdscr.getmaxyx() self.set_cursor(0) self.w = curses.newwin(10, 50, 5, 5) set_verbosity(False) self.tab = 0 self.pos = 0 self.popup_pos = 0 self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.history = None if self.network: self.network.register_callback(self.update, ['updated']) self.tab_names = [_("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner")] self.num_tabs = len(self.tab_names)
def __init__(self, *, config, daemon, plugins): BaseElectrumGui.__init__(self, config=config, daemon=daemon, plugins=plugins) self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists(): print("Wallet not found. try 'electrum create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:', stream=None) storage.decrypt(password) db = WalletDB(storage.read(), manual_upgrades=False) self.done = 0 self.last_balance = "" self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.wallet = Wallet(db, storage, config=config) # type: Optional[Abstract_Wallet] self.wallet.start_network(self.network) self.contacts = self.wallet.contacts self.register_callbacks() self.commands = [_("[h] - displays this help text"), \ _("[i] - display transaction history"), \ _("[o] - enter payment order"), \ _("[p] - print stored payment order"), \ _("[s] - send stored payment order"), \ _("[r] - show own receipt addresses"), \ _("[c] - display contacts"), \ _("[b] - print server banner"), \ _("[q] - quit")] self.num_commands = len(self.commands)
def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print("Wallet not found. try 'electrum-bitg create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'wallet_updated', 'network_updated', 'banner']) self.commands = [_("[h] - displays this help text"), \ _("[i] - display transaction history"), \ _("[o] - enter payment order"), \ _("[p] - print stored payment order"), \ _("[s] - send stored payment order"), \ _("[r] - show own receipt addresses"), \ _("[c] - display contacts"), \ _("[b] - print server banner"), \ _("[q] - quit") ] self.num_commands = len(self.commands)
def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print("Wallet not found. try 'pkpay create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'wallet_updated', 'network_updated', 'banner']) self.commands = [_("[h] - displays this help text"), _("[i] - display transaction history"), _("[o] - enter payment order"), _("[p] - print stored payment order"), _("[s] - send stored payment order"), _("[r] - show own receipt addresses"), _("[c] - display contacts"), _("[b] - print server banner"), _("[q] - quit")] self.num_commands = len(self.commands)
def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print("Wallet not found. try 'electrum create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'updated', 'banner']) self.commands = [_("[h] - displays this help text"), \ _("[i] - display transaction history"), \ _("[o] - enter payment order"), \ _("[p] - print stored payment order"), \ _("[s] - send stored payment order"), \ _("[r] - show own receipt addresses"), \ _("[c] - display contacts"), \ _("[b] - print server banner"), \ _("[q] - quit") ] self.num_commands = len(self.commands)
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() synchronized_signal = pyqtSignal(str) def __init__(self, config, app, plugins, storage): BaseWizard.__init__(self, config, storage) QDialog.__init__(self, None) self.setWindowTitle('Electrum - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.plugins = plugins self.language_for_seed = config.get('language') self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText( _('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon(':icons/electrum.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. def run_and_get_wallet(self, get_wallet_from_daemon): vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit() hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) self.msg_label = QLabel('') vbox.addWidget(self.msg_label) hbox2 = QHBoxLayout() self.pw_e = QLineEdit('', self) self.pw_e.setFixedWidth(150) self.pw_e.setEchoMode(2) self.pw_label = QLabel(_('Password') + ':') hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self.set_layout(vbox, title=_('Electrum wallet')) wallet_folder = os.path.dirname(self.storage.path) def on_choose(): path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def on_filename(filename): path = os.path.join(wallet_folder, filename) wallet_from_memory = get_wallet_from_daemon(path) try: if wallet_from_memory: self.storage = wallet_from_memory.storage else: self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except BaseException: traceback.print_exc(file=sys.stderr) self.storage = None self.next_button.setEnabled(False) if self.storage: if not self.storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False elif not wallet_from_memory: if self.storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') pw = True elif self.storage.is_encrypted_with_hw_device(): msg = _("This file is encrypted using a hardware device.") + '\n' \ + _("Press 'Next' to choose device to decrypt.") pw = False else: msg = _("Press 'Next' to open this wallet.") pw = False else: msg = _("This file is already open in memory.") + "\n" \ + _("Press 'Next' to create/focus window.") pw = False else: msg = _('Cannot read file') pw = False self.msg_label.setText(msg) if pw: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() button.clicked.connect(on_choose) self.name_e.textChanged.connect(on_filename) n = os.path.basename(self.storage.path) self.name_e.setText(n) while True: if self.storage.file_exists() and not self.storage.is_encrypted(): break if self.loop.exec_() != 2: # 2 = next return if not self.storage.file_exists(): break wallet_from_memory = get_wallet_from_daemon(self.storage.path) if wallet_from_memory: return wallet_from_memory if self.storage.file_exists() and self.storage.is_encrypted(): if self.storage.is_encrypted_with_user_pw(): password = self.pw_e.text() try: self.storage.decrypt(password) break except InvalidPassword as e: QMessageBox.information(None, _('Error'), str(e)) continue except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) return elif self.storage.is_encrypted_with_hw_device(): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET) except InvalidPassword as e: QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.' )) self.stack = [] return self.run_and_get_wallet(get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) return if self.storage.is_past_initial_decryption(): break else: return else: raise Exception('Unexpected encryption version') path = self.storage.path if self.storage.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = _('Your accounts have been moved to' ) + ':\n' + file_list + '\n\n' + _( 'Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return if self.storage.requires_upgrade(): self.storage.upgrade() self.wallet = Wallet(self.storage) return self.wallet action = self.storage.get_action() if action and action != 'new': self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run self.run(action) return self.wallet self.wallet = Wallet(self.storage) return self.wallet def finished(self): """Called in hardware client wrapper, in order to close popups.""" return def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap(QPixmap(filename).scaledToWidth(60)) return prior_filename def set_layout(self, layout, title=None, next_enabled=True): self.title.setText("<b>%s</b>" % title if title else "") self.title.setVisible(bool(title)) # Get rid of any prior layout by assigning it to a temporary widget prior_layout = self.main_widget.layout() if prior_layout: QWidget().setLayout(prior_layout) self.main_widget.setLayout(layout) self.back_button.setEnabled(True) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self.set_layout(layout, title, next_enabled) result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled if result == 1: raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice self.app.processEvents() self.app.processEvents() def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) def text_input(self, title, message, is_valid, allow_multi=False): slayout = KeysLayout(parent=self, title=message, is_valid=is_valid, allow_multi=allow_multi) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False): return self.text_input(title, message, is_valid, allow_multi) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d" % index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.' ) ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _( 'Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, test): self.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.' ) ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text): title = _("Your wallet generation seed is:") slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext']) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind, force_disable_encrypt_cb): playout = PasswordLayout( None, msg, kind, self.next_button, force_disable_encrypt_cb=force_disable_encrypt_cb) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.new_password(), playout.encrypt_cb.isChecked() @wizard_dialog def request_password(self, run_next, force_disable_encrypt_cb=False): """Request the user enter a new password and confirm it. Return the password or None for no password.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb) @wizard_dialog def request_storage_encryption(self, run_next): playout = PasswordLayoutForHW(None, MSG_HW_STORAGE_ENCRYPTION, PW_NEW, self.next_button) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.encrypt_cb.isChecked() def show_restore(self, wallet, network): # FIXME: these messages are shown after the install wizard is # finished and the window closed. On macOS they appear parented # with a re-appeared ghost install wizard window... if network: def task(): wallet.wait_until_synchronized() if wallet.is_found(): msg = _("Recovery successful") else: msg = _("No transactions found for this seed") self.synchronized_signal.emit(msg) self.synchronized_signal.connect(self.show_message) t = threading.Thread(target=task) t.daemon = True t.start() else: msg = _("This wallet was restored offline. It may " "contain more addresses than displayed.") self.show_message(msg) @wizard_dialog def confirm_dialog(self, title, message, run_next): self.confirm(message, title) def confirm(self, message, title): label = WWLabel(message) vbox = QVBoxLayout() vbox.addWidget(label) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self): self.accept_signal.emit() def waiting_dialog(self, task, msg): self.please_wait.setText(msg) self.refresh_gui() t = threading.Thread(target=task) t.start() t.join() @wizard_dialog def choice_dialog(self, title, message, choices, run_next): c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] clayout = ChoicesLayout(message, c_titles) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning='', presets=()): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) for preset in presets: button = QPushButton(preset[0]) button.clicked.connect( lambda __, text=preset[1]: line.setText(text)) button.setMaximumWidth(150) hbox = QHBoxLayout() hbox.addWidget(button, Qt.AlignCenter) vbox.addLayout(hbox) self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QVBoxLayout() layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network): message = _("Electrum communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfill the same purpose only differing in " "hardware. In most cases you simply want to let Electrum " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_('Cancel')) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() if r == 1: nlayout = NetworkChoiceLayout(network, self.config, wizard=True) if self.exec_layout(nlayout.layout()): nlayout.accept() else: network.auto_connect = True self.config.set_key('auto_connect', True, True) @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require {0} signatures').format(m)) cw.set_m(m) def on_n(n): n_label.setText(_('From {0} cosigners').format(n)) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget( WWLabel( _("Choose the number of signatures needed to unlock funds in your wallet:" ))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): def __init__(self, config, app, plugins, storage): BaseWizard.__init__(self, config, storage) QDialog.__init__(self, None) self.setWindowTitle('Electrum - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.plugins = plugins self.language_for_seed = config.get('language') self.setMinimumSize(600, 400) self.connect(self, QtCore.SIGNAL('accept'), self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addLayout(inner_vbox) hbox.setStretchFactor(inner_vbox, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon(':icons/electrum.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. def run_and_get_wallet(self): def on_filename(): wallet_folder = os.path.dirname(self.storage.path) path = unicode(QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)) if path: self.name_e.setText(path) self.storage = WalletStorage(path) update_layout() def update_layout(): name = os.path.basename(self.storage.path) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit(text=name) hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) button.clicked.connect(on_filename) hbox.addWidget(button) vbox.addLayout(hbox) self.pw_e = None if not self.storage.file_exists(): msg = _("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or chose another file.") vbox.addWidget(QLabel(msg)) elif self.storage.file_exists() and self.storage.is_encrypted(): msg = _("This file is encrypted.") + '\n' + _('Enter your password or choose another file.') vbox.addWidget(QLabel(msg)) hbox2 = QHBoxLayout() self.pw_e = QLineEdit('', self) self.pw_e.setFixedWidth(150) self.pw_e.setEchoMode(2) hbox2.addWidget(QLabel(_('Password') + ':')) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) else: msg = _("Press 'Next' to open this wallet.") vbox.addWidget(QLabel(msg)) self.set_layout(vbox, title=_('Electrum wallet')) if self.pw_e: self.pw_e.show() self.pw_e.setFocus() while True: update_layout() if self.storage.file_exists() and not self.storage.is_encrypted(): break if not self.loop.exec_(): return if not self.storage.file_exists(): break if self.storage.file_exists() and self.storage.is_encrypted(): password = unicode(self.pw_e.text()) try: self.storage.decrypt(password) break except InvalidPassword as e: QMessageBox.information(None, _('Error'), str(e), _('OK')) continue except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e), _('OK')) return path = self.storage.path if self.storage.requires_split(): self.hide() msg = _("The wallet '%s' contains multiple accounts, which are no longer supported in Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?"%path) if not self.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = _('Your accounts have been moved to') + ':\n' + file_list + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return if self.storage.requires_upgrade(): self.hide() msg = _("The format of your wallet '%s' must be upgraded for Electrum. This change will not be backward compatible"%path) if not self.question(msg): return self.storage.upgrade() self.show_warning(_('Your wallet was upgraded successfully')) self.wallet = Wallet(self.storage) self.terminate() return self.wallet action = self.storage.get_action() if action and action != 'new': self.hide() msg = _("The file '%s' contains an incompletely created wallet.\n" "Do you want to complete its creation now?") % path if not self.question(msg): if self.question(_("Do you want to delete '%s'?") % path): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run self.run(action) return self.wallet self.wallet = Wallet(self.storage) self.terminate() return self.wallet def finished(self): """Called in hardware client wrapper, in order to close popups.""" return def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap(QPixmap(filename).scaledToWidth(60)) return prior_filename def set_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self.title.setText("<b>%s</b>"%title if title else "") self.title.setVisible(bool(title)) # Get rid of any prior layout by assigning it to a temporary widget prior_layout = self.main_widget.layout() if prior_layout: QWidget().setLayout(prior_layout) self.main_widget.setLayout(layout) self.back_button.setEnabled(True) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self.set_layout(layout, title, next_enabled) result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled if result == 1: raise GoBack self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice self.app.processEvents() self.app.processEvents() def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) def text_input(self, title, message, is_valid): slayout = KeysLayout(parent=self, title=message, is_valid=is_valid) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next): return self.text_input(title, message, is_valid) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d"%index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.') ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _('Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, test): self.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.') ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text): title = _("Your wallet generation seed is:") slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext']) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind): playout = PasswordLayout(None, msg, kind, self.next_button) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.new_password(), playout.encrypt_cb.isChecked() @wizard_dialog def request_password(self, run_next): """Request the user enter a new password and confirm it. Return the password or None for no password.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW) def show_restore(self, wallet, network): # FIXME: these messages are shown after the install wizard is # finished and the window closed. On MacOSX they appear parented # with a re-appeared ghost install wizard window... if network: def task(): wallet.wait_until_synchronized() if wallet.is_found(): msg = _("Recovery successful") else: msg = _("No transactions found for this seed") self.emit(QtCore.SIGNAL('synchronized'), msg) self.connect(self, QtCore.SIGNAL('synchronized'), self.show_message) t = threading.Thread(target = task) t.daemon = True t.start() else: msg = _("This wallet was restored offline. It may " "contain more addresses than displayed.") self.show_message(msg) @wizard_dialog def confirm_dialog(self, title, message, run_next): self.confirm(message, title) def confirm(self, message, title): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self): self.emit(QtCore.SIGNAL('accept')) def waiting_dialog(self, task, msg): self.please_wait.setText(MSG_GENERATING_WAIT) self.refresh_gui() t = threading.Thread(target = task) t.start() t.join() @wizard_dialog def choice_dialog(self, title, message, choices, run_next): c_values = map(lambda x: x[0], choices) c_titles = map(lambda x: x[1], choices) clayout = ChoicesLayout(message, c_titles) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning=''): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(unicode(line.text()).split()) @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QVBoxLayout() layout = SeedLayout(xpub, title=msg, icon=False) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network): message = _("Electrum communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfil the same purpose only differing in " "hardware. In most cases you simply want to let Electrum " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_('Cancel')) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() if r == 0: auto_connect = True elif r == 1: auto_connect = True nlayout = NetworkChoiceLayout(network, self.config, wizard=True) if self.exec_layout(nlayout.layout()): auto_connect = False else: auto_connect = True network.auto_connect = auto_connect self.config.set_key('auto_connect', auto_connect, True) @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require %d signatures')%m) cw.set_m(m) def on_n(n): n_label.setText(_('From %d cosigners')%n) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:"))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)
class BitcoinWallet(Wallet): """ This class is responsible for handling your wallet of bitcoins. """ def __init__(self, wallet_dir, testnet=False): super(BitcoinWallet, self).__init__() if testnet: bitcoin.set_testnet() network.set_testnet() self.wallet_dir = wallet_dir self.wallet_file = 'tbtc_wallet' if testnet else 'btc_wallet' self.min_confirmations = 0 self.created = False self.daemon = None keychain_pw = self.get_wallet_password() self.wallet_password = keychain_pw if keychain_pw else None # Convert empty passwords to None self.storage = None self.wallet = None self.testnet = testnet self.load_wallet(self.wallet_dir, self.wallet_file) def load_wallet(self, wallet_dir, wallet_file): self.wallet_dir = wallet_dir self.wallet_file = wallet_file config = SimpleConfig(options={'cwd': self.wallet_dir, 'wallet_path': self.wallet_file}) self.storage = WalletStorage(config.get_wallet_path()) if self.storage.is_encrypted(): self.storage.decrypt(self.wallet_password) if os.path.exists(config.get_wallet_path()): self.wallet = ElectrumWallet(self.storage) self.created = True self.start_daemon() self.open_wallet() def get_wallet_password(self): return keyring.get_password('tribler', 'btc_wallet_password') def set_wallet_password(self, password): keyring.set_password('tribler', 'btc_wallet_password', password) def get_daemon(self): """ Return the daemon that can be used to send JSON RPC commands to. This method is here so we can unit test this class. """ from electrum import daemon return daemon def start_daemon(self): options = {'verbose': False, 'cmd': 'daemon', 'testnet': self.testnet, 'oneserver': False, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False, 'password': '', 'wallet_path': os.path.join('wallet', self.wallet_file)} if self.testnet: options['server'] = 'electrum.akinbo.org:51002:s' config = SimpleConfig(options) fd, _ = self.get_daemon().get_fd_or_server(config) if not fd: return self.daemon = self.get_daemon().Daemon(config, fd) self.daemon.start() def open_wallet(self): options = {'password': self.wallet_password, 'subcommand': 'load_wallet', 'verbose': False, 'cmd': 'daemon', 'testnet': self.testnet, 'oneserver': False, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False, 'wallet_path': self.wallet_file} config = SimpleConfig(options) server = self.get_daemon().get_server(config) if server is not None: # Run the command to open the wallet server.daemon(options) def get_name(self): return 'Bitcoin' if not self.testnet else 'Testnet BTC' def get_identifier(self): return 'BTC' def create_wallet(self, password=''): """ Create a new bitcoin wallet. """ self._logger.info("Creating wallet in %s", self.wallet_dir) def run_on_thread(thread_method): # We are running code that writes to the wallet on a separate thread. # This is done because ethereum does not allow writing to a wallet from a daemon thread. wallet_thread = Thread(target=thread_method, name="ethereum-create-wallet") wallet_thread.setDaemon(False) wallet_thread.start() wallet_thread.join() seed = Mnemonic('en').make_seed() k = keystore.from_seed(seed, '') k.update_password(None, password) self.storage.put('keystore', k.dump()) self.storage.put('wallet_type', 'standard') self.storage.set_password(password, bool(password)) run_on_thread(self.storage.write) self.wallet = ElectrumWallet(self.storage) self.wallet.synchronize() run_on_thread(self.wallet.storage.write) self.created = True if password is not None: self.set_wallet_password(password) self.wallet_password = password self.start_daemon() self.open_wallet() self._logger.info("Bitcoin wallet saved in '%s'", self.wallet.storage.path) return succeed(None) def get_balance(self): """ Return the balance of the wallet. """ divider = 100000000 if self.created: confirmed, unconfirmed, unmatured = self.wallet.get_balance() return succeed({ "available": float(confirmed) / divider, "pending": float(unconfirmed + unmatured) / divider, "currency": 'BTC' }) else: return succeed({"available": 0, "pending": 0, "currency": 'BTC'}) def transfer(self, amount, address): def on_balance(balance): self._logger.info("Creating Bitcoin payment with amount %f to address %s", amount, address) if balance['available'] >= amount: options = {'tx_fee': '0.0005', 'password': self.wallet_password, 'verbose': False, 'nocheck': False, 'cmd': 'payto', 'wallet_path': self.wallet_file, 'destination': address, 'cwd': self.wallet_dir, 'testnet': self.testnet, 'rbf': False, 'amount': amount, 'segwit': False, 'unsigned': False, 'portable': False} config = SimpleConfig(options) server = self.get_daemon().get_server(config) result = server.run_cmdline(options) transaction_hex = result['hex'] # Broadcast this transaction options = {'password': None, 'verbose': False, 'tx': transaction_hex, 'cmd': 'broadcast', 'testnet': self.testnet, 'timeout': 30, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False} config = SimpleConfig(options) server = self.get_daemon().get_server(config) result = server.run_cmdline(options) if not result[0]: # Transaction failed return fail(RuntimeError(result[1])) return succeed(str(result[1])) else: return fail(InsufficientFunds()) return self.get_balance().addCallback(on_balance) def monitor_transaction(self, txid): """ Monitor a given transaction ID. Returns a Deferred that fires when the transaction is present. """ monitor_deferred = Deferred() @inlineCallbacks def monitor_loop(): transactions = yield self.get_transactions() for transaction in transactions: if transaction['id'] == txid: self._logger.debug("Found transaction with id %s", txid) monitor_deferred.callback(None) monitor_lc.stop() self._logger.debug("Start polling for transaction %s", txid) monitor_lc = LoopingCall(monitor_loop) monitor_lc.start(1) return monitor_deferred def get_address(self): if not self.created: return '' return str(self.wallet.get_receiving_address()) def get_transactions(self): options = {'nolnet': False, 'password': None, 'verbose': False, 'cmd': 'history', 'wallet_path': self.wallet_file, 'testnet': self.testnet, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False} config = SimpleConfig(options) server = self.get_daemon().get_server(config) result = server.run_cmdline(options) transactions = [] for transaction in result: outgoing = transaction['value'] < 0 if outgoing: from_address = self.get_address() to_address = '' else: from_address = '' to_address = self.get_address() transactions.append({ 'id': transaction['txid'], 'outgoing': outgoing, 'from': from_address, 'to': to_address, 'amount': abs(transaction['value']), 'fee_amount': 0.0, 'currency': 'BTC', 'timestamp': str(transaction['timestamp']), 'description': '' }) return succeed(transactions) def min_unit(self): return 0.0001 # This is the minimum amount of BTC we can transfer in this market
class BitcoinWallet(Wallet): """ This class is responsible for handling your wallet of bitcoins. """ TESTNET = False def __init__(self, wallet_dir): super(BitcoinWallet, self).__init__() if self.TESTNET: bitcoin.set_testnet() network.set_testnet() self.wallet_dir = wallet_dir self.wallet_file = 'tbtc_wallet' if self.TESTNET else 'btc_wallet' self.min_confirmations = 0 self.daemon = None self.wallet_password = None self.storage = None self.wallet = None self.initialize_storage(self.wallet_dir, self.wallet_file) if self.created: # If the wallet has been created already, we try to unlock it. self.unlock_wallet() def initialize_storage(self, wallet_dir, wallet_file): """ This will initialize the storage for the BTC wallet. """ self.wallet_dir = wallet_dir self.wallet_file = wallet_file config = SimpleConfig(options={ 'cwd': self.wallet_dir, 'wallet_path': self.wallet_file }) self.storage = WalletStorage(config.get_wallet_path()) if os.path.exists(config.get_wallet_path()): self.created = True def unlock_wallet(self): """ Attempt to unlock the BTC wallet with the password in the keychain. """ if not self.created or self.unlocked: # Wallet has not been created or unlocked already, do nothing. return False if self.storage.is_encrypted(): try: keychain_pw = self.get_wallet_password() self.wallet_password = keychain_pw if keychain_pw else None # Convert empty passwords to None self.storage.decrypt(self.wallet_password) self.unlocked = True except InvalidPassword: self._logger.error( "Invalid BTC wallet password, unable to unlock the wallet!" ) except InitError: self._logger.error( "Cannot initialize the keychain, unable to unlock the wallet!" ) else: # No need to unlock the wallet self.unlocked = True if self.unlocked: config = SimpleConfig(options={ 'cwd': self.wallet_dir, 'wallet_path': self.wallet_file }) if os.path.exists(config.get_wallet_path()): self.wallet = ElectrumWallet(self.storage) self.start_daemon() self.open_wallet() return True return False def get_wallet_password(self): return keyring.get_password('tribler', 'btc_wallet_password') def set_wallet_password(self, password): keyring.set_password('tribler', 'btc_wallet_password', password) def get_daemon(self): """ Return the daemon that can be used to send JSON RPC commands to. This method is here so we can unit test this class. """ from electrum import daemon return daemon def start_daemon(self): options = { 'verbose': False, 'cmd': 'daemon', 'testnet': self.TESTNET, 'oneserver': False, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False, 'password': '', 'wallet_path': os.path.join('wallet', self.wallet_file) } if self.TESTNET: options['server'] = 'electrum.akinbo.org:51002:s' config = SimpleConfig(options) fd, _ = self.get_daemon().get_fd_or_server(config) if not fd: return self.daemon = self.get_daemon().Daemon(config, fd, is_gui=False) self.daemon.start() def open_wallet(self): options = { 'password': self.wallet_password, 'subcommand': 'load_wallet', 'verbose': False, 'cmd': 'daemon', 'testnet': self.TESTNET, 'oneserver': False, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False, 'wallet_path': self.wallet_file } config = SimpleConfig(options) server = self.get_daemon().get_server(config) if server is not None: # Run the command to open the wallet server.daemon(options) def get_name(self): return 'Bitcoin' def get_identifier(self): return 'BTC' def create_wallet(self, password=''): """ Create a new bitcoin wallet. """ self._logger.info("Creating wallet in %s", self.wallet_dir) if password is not None: try: self.set_wallet_password(password) except InitError: return fail( RuntimeError( "Cannot initialize the keychain, unable to unlock the wallet!" )) self.wallet_password = password def run_on_thread(thread_method): # We are running code that writes to the wallet on a separate thread. # This is done because Electrum does not allow writing to a wallet from a daemon thread. wallet_thread = Thread(target=thread_method, name="ethereum-create-wallet") wallet_thread.setDaemon(False) wallet_thread.start() wallet_thread.join() seed = Mnemonic('en').make_seed() k = keystore.from_seed(seed, '') k.update_password(None, password) self.storage.put('keystore', k.dump()) self.storage.put('wallet_type', 'standard') self.storage.set_password(password, bool(password)) run_on_thread(self.storage.write) self.wallet = ElectrumWallet(self.storage) self.wallet.synchronize() run_on_thread(self.wallet.storage.write) self.created = True self.unlocked = True self.start_daemon() self.open_wallet() self._logger.info("Bitcoin wallet saved in '%s'", self.wallet.storage.path) return succeed(None) def get_balance(self): """ Return the balance of the wallet. """ if self.created and self.unlocked: options = { 'nolnet': False, 'password': None, 'verbose': False, 'cmd': 'getbalance', 'wallet_path': self.wallet_file, 'testnet': self.TESTNET, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False } config = SimpleConfig(options) server = self.get_daemon().get_server(config) result = server.run_cmdline(options) confirmed = float(result['confirmed']) unconfirmed = float( result['unconfirmed']) if 'unconfirmed' in result else 0 unconfirmed += (float(result['unmatured']) if 'unmatured' in result else 0) return succeed({ "available": confirmed, "pending": unconfirmed, "currency": 'BTC', "precision": self.precision() }) return succeed({ "available": 0, "pending": 0, "currency": 'BTC', "precision": self.precision() }) def transfer(self, amount, address): def on_balance(balance): self._logger.info( "Creating Bitcoin payment with amount %f to address %s", amount, address) if balance['available'] >= amount: options = { 'password': self.wallet_password, 'verbose': False, 'nocheck': False, 'cmd': 'payto', 'wallet_path': self.wallet_file, 'destination': address, 'cwd': self.wallet_dir, 'testnet': self.TESTNET, 'rbf': False, 'amount': amount, 'segwit': False, 'unsigned': False, 'portable': False } config = SimpleConfig(options) server = self.get_daemon().get_server(config) result = server.run_cmdline(options) transaction_hex = result['hex'] # Broadcast this transaction options = { 'password': None, 'verbose': False, 'tx': transaction_hex, 'cmd': 'broadcast', 'testnet': self.TESTNET, 'timeout': 30, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False } config = SimpleConfig(options) server = self.get_daemon().get_server(config) result = server.run_cmdline(options) if not result[0]: # Transaction failed return fail(RuntimeError(result[1])) return succeed(str(result[1])) else: return fail(InsufficientFunds()) return self.get_balance().addCallback(on_balance) def monitor_transaction(self, txid): """ Monitor a given transaction ID. Returns a Deferred that fires when the transaction is present. """ monitor_deferred = Deferred() @inlineCallbacks def monitor_loop(): transactions = yield self.get_transactions() for transaction in transactions: if transaction['id'] == txid: self._logger.debug("Found transaction with id %s", txid) monitor_deferred.callback(None) monitor_lc.stop() self._logger.debug("Start polling for transaction %s", txid) monitor_lc = self.register_task("btc_poll_%s" % txid, LoopingCall(monitor_loop)) monitor_lc.start(1) return monitor_deferred def get_address(self): if not self.created or not self.unlocked: return '' return str(self.wallet.get_receiving_address()) def get_transactions(self): if not self.created or not self.unlocked: return succeed([]) options = { 'nolnet': False, 'password': None, 'verbose': False, 'cmd': 'history', 'wallet_path': self.wallet_file, 'testnet': self.TESTNET, 'segwit': False, 'cwd': self.wallet_dir, 'portable': False } config = SimpleConfig(options) server = self.get_daemon().get_server(config) try: result = server.run_cmdline(options) except ProtocolError: self._logger.error("Unable to fetch transactions from BTC wallet!") return succeed([]) transactions = [] for transaction in result: outgoing = transaction['value'] < 0 from_address = ','.join(transaction['input_addresses']) to_address = ','.join(transaction['output_addresses']) transactions.append({ 'id': transaction['txid'], 'outgoing': outgoing, 'from': from_address, 'to': to_address, 'amount': abs(transaction['value']), 'fee_amount': 0.0, 'currency': 'BTC', 'timestamp': str(transaction['timestamp']), 'description': 'Confirmations: %d' % transaction['confirmations'] }) return succeed(transactions) def min_unit(self): return 100000 # The minimum amount of BTC we can transfer in this market is mBTC (100000 Satoshi) def precision(self): return 8
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() synchronized_signal = pyqtSignal(str) def __init__(self, config, app, plugins, storage): BaseWizard.__init__(self, config, plugins, storage) QDialog.__init__(self, None) self.setWindowTitle('Electrum - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.language_for_seed = config.get('language') self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon(':icons/electrum.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. def run_and_get_wallet(self, get_wallet_from_daemon): vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit() hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) self.msg_label = QLabel('') vbox.addWidget(self.msg_label) hbox2 = QHBoxLayout() self.pw_e = QLineEdit('', self) self.pw_e.setFixedWidth(150) self.pw_e.setEchoMode(2) self.pw_label = QLabel(_('Password') + ':') hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self.set_layout(vbox, title=_('Electrum wallet')) wallet_folder = os.path.dirname(self.storage.path) def on_choose(): path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def on_filename(filename): path = os.path.join(wallet_folder, filename) wallet_from_memory = get_wallet_from_daemon(path) try: if wallet_from_memory: self.storage = wallet_from_memory.storage else: self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except BaseException: traceback.print_exc(file=sys.stderr) self.storage = None self.next_button.setEnabled(False) if self.storage: if not self.storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False elif not wallet_from_memory: if self.storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') pw = True elif self.storage.is_encrypted_with_hw_device(): msg = _("This file is encrypted using a hardware device.") + '\n' \ + _("Press 'Next' to choose device to decrypt.") pw = False else: msg = _("Press 'Next' to open this wallet.") pw = False else: msg = _("This file is already open in memory.") + "\n" \ + _("Press 'Next' to create/focus window.") pw = False else: msg = _('Cannot read file') pw = False self.msg_label.setText(msg) if pw: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() button.clicked.connect(on_choose) self.name_e.textChanged.connect(on_filename) n = os.path.basename(self.storage.path) self.name_e.setText(n) while True: if self.loop.exec_() != 2: # 2 = next return if self.storage.file_exists() and not self.storage.is_encrypted(): break if not self.storage.file_exists(): break wallet_from_memory = get_wallet_from_daemon(self.storage.path) if wallet_from_memory: return wallet_from_memory if self.storage.file_exists() and self.storage.is_encrypted(): if self.storage.is_encrypted_with_user_pw(): password = self.pw_e.text() try: self.storage.decrypt(password) break except InvalidPassword as e: QMessageBox.information(None, _('Error'), str(e)) continue except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) return elif self.storage.is_encrypted_with_hw_device(): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET) except InvalidPassword as e: QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.')) self.stack = [] return self.run_and_get_wallet(get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) return if self.storage.is_past_initial_decryption(): break else: return else: raise Exception('Unexpected encryption version') path = self.storage.path if self.storage.requires_split(): self.hide() msg = _("The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?").format(path) if not self.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = _('Your accounts have been moved to') + ':\n' + file_list + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return action = self.storage.get_action() if action and action not in ('new', 'upgrade_storage'): self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question(_("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run self.run(action) return self.wallet self.wallet = Wallet(self.storage) return self.wallet def finished(self): """Called in hardware client wrapper, in order to close popups.""" return def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap(QPixmap(filename).scaledToWidth(60, mode=Qt.SmoothTransformation)) return prior_filename def set_layout(self, layout, title=None, next_enabled=True): self.title.setText("<b>%s</b>"%title if title else "") self.title.setVisible(bool(title)) # Get rid of any prior layout by assigning it to a temporary widget prior_layout = self.main_widget.layout() if prior_layout: QWidget().setLayout(prior_layout) self.main_widget.setLayout(layout) self.back_button.setEnabled(True) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self.set_layout(layout, title, next_enabled) result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled if result == 1: raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice self.app.processEvents() self.app.processEvents() def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) def text_input(self, title, message, is_valid, allow_multi=False): slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid, allow_multi=allow_multi) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False): header_layout = QHBoxLayout() label = WWLabel(message) label.setMinimumWidth(400) header_layout.addWidget(label) if show_wif_help: header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) return self.text_input(title, header_layout, is_valid, allow_multi) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d"%index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.') ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _('Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, test): self.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.') ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text): title = _("Your wallet generation seed is:") slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext']) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind, force_disable_encrypt_cb): playout = PasswordLayout(None, msg, kind, self.next_button, force_disable_encrypt_cb=force_disable_encrypt_cb) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.new_password(), playout.encrypt_cb.isChecked() @wizard_dialog def request_password(self, run_next, force_disable_encrypt_cb=False): """Request the user enter a new password and confirm it. Return the password or None for no password.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb) @wizard_dialog def request_storage_encryption(self, run_next): playout = PasswordLayoutForHW(None, MSG_HW_STORAGE_ENCRYPTION, PW_NEW, self.next_button) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.encrypt_cb.isChecked() def show_restore(self, wallet, network): # FIXME: these messages are shown after the install wizard is # finished and the window closed. On macOS they appear parented # with a re-appeared ghost install wizard window... if network: def task(): wallet.wait_until_synchronized() if wallet.is_found(): msg = _("Recovery successful") else: msg = _("No transactions found for this seed") self.synchronized_signal.emit(msg) self.synchronized_signal.connect(self.show_message) t = threading.Thread(target = task) t.daemon = True t.start() else: msg = _("This wallet was restored offline. It may " "contain more addresses than displayed.") self.show_message(msg) @wizard_dialog def confirm_dialog(self, title, message, run_next): self.confirm(message, title) def confirm(self, message, title): label = WWLabel(message) vbox = QVBoxLayout() vbox.addWidget(label) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self): self.accept_signal.emit() def waiting_dialog(self, task, msg, on_finished=None): label = WWLabel(msg) vbox = QVBoxLayout() vbox.addSpacing(100) label.setMinimumWidth(300) label.setAlignment(Qt.AlignCenter) vbox.addWidget(label) self.set_layout(vbox, next_enabled=False) self.back_button.setEnabled(False) t = threading.Thread(target=task) t.start() while True: t.join(1.0/60) if t.is_alive(): self.refresh_gui() else: break if on_finished: on_finished() @wizard_dialog def choice_dialog(self, title, message, choices, run_next): c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] clayout = ChoicesLayout(message, c_titles) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning='', presets=()): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) for preset in presets: button = QPushButton(preset[0]) button.clicked.connect(lambda __, text=preset[1]: line.setText(text)) button.setMaximumWidth(150) hbox = QHBoxLayout() hbox.addWidget(button, Qt.AlignCenter) vbox.addLayout(hbox) self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QVBoxLayout() layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network): message = _("Electrum communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfill the same purpose only differing in " "hardware. In most cases you simply want to let Electrum " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_('Cancel')) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() if r == 1: nlayout = NetworkChoiceLayout(network, self.config, wizard=True) if self.exec_layout(nlayout.layout()): nlayout.accept() else: network.auto_connect = True self.config.set_key('auto_connect', True, True) @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require {0} signatures').format(m)) cw.set_m(m) def on_n(n): n_label.setText(_('From {0} cosigners').format(n)) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:"))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)