def on_restore_wallet(self, wallet, wizard): assert isinstance(wallet, self.wallet_class) msg = _("Enter the seed for your %s wallet:" % self.device) f = lambda x: wizard.run("on_restore_seed", x) wizard.enter_seed_dialog( run_next=f, title=_("Restore hardware wallet"), message=msg, is_valid=self.is_valid_seed )
def context_menu(self): view_menu = QMenu() themes_menu = view_menu.addMenu(_("&Themes")) selected_theme = self.actuator.selected_theme() theme_group = QActionGroup(self) for theme_name in self.actuator.theme_names(): theme_action = themes_menu.addAction(theme_name) theme_action.setCheckable(True) if selected_theme == theme_name: theme_action.setChecked(True) class SelectThemeFunctor: def __init__(self, theme_name, toggle_theme): self.theme_name = theme_name self.toggle_theme = toggle_theme def __call__(self, checked): if checked: self.toggle_theme(self.theme_name) delegate = SelectThemeFunctor(theme_name, self.toggle_theme) theme_action.toggled.connect(delegate) theme_group.addAction(theme_action) view_menu.addSeparator() show_receiving = view_menu.addAction(_("Show Receiving addresses")) show_receiving.setCheckable(True) show_receiving.toggled.connect(self.toggle_receiving_layout) show_receiving.setChecked(self.config.get("gui_show_receiving",False)) show_history = view_menu.addAction(_("Show History")) show_history.setCheckable(True) show_history.toggled.connect(self.show_history) show_history.setChecked(self.config.get("gui_show_history",False)) return view_menu
def load_wallet(self, wallet): self.wallet = wallet if self.trezor_is_connected(): if not self.wallet.check_proper_device(): QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Trezor device"), _('OK')) else: QMessageBox.information(self.window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode."), _('OK'))
def on_verify_restore_ok(self, wallet, _dlg, btn, restore=False): if btn in (_dlg.ids.back, _dlg.ids.but_close) : _dlg.close() Factory.CreateRestoreDialog( on_release=self.on_creatrestore_complete).open() return seed = self.get_seed_text(_dlg.ids.text_input_seed) if not seed: return app.show_error(_("No seed!"), duration=.5) _dlg.close() if Wallet.is_seed(seed): return self.password_dialog(wallet=wallet, mode='restore', seed=seed) elif Wallet.is_xpub(seed): wallet = Wallet.from_xpub(seed, self.storage) elif Wallet.is_address(seed): wallet = Wallet.from_address(seed, self.storage) elif Wallet.is_private_key(seed): wallet = Wallet.from_private_key(seed, self.storage) else: return app.show_error(_('Not a valid seed. App will now exit'), exit=True, modal=True, duration=.5) return
def build_tray_menu(self): m = QMenu() m.addAction(_("Show/Hide"), self.show_or_hide) m.addAction(_("Dark/Light"), self.toggle_tray_icon) m.addSeparator() m.addAction(_("Exit Electrum"), self.close) self.tray.setContextMenu(m)
def save(self): name = 'signed_%s.txn' % (self.tx.hash()[0:8]) if self.tx.is_complete else 'unsigned.txn' fileName = self.parent.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn") if fileName: with open(fileName, "w+") as f: f.write(json.dumps(self.tx.as_dict(),indent=4) + '\n') self.show_message(_("Transaction saved successfully"))
def add_io(self, vbox): if self.tx.locktime > 0: vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime)) vbox.addWidget(QLabel(_("Inputs"))) def format_input(x): if x.get('is_coinbase'): return 'coinbase' else: _hash = x.get('prevout_hash') return _hash[0:16] + '...' + _hash[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address') lines = map(format_input, self.tx.inputs ) i_text = QTextEdit() i_text.setText('\n'.join(lines)) i_text.setReadOnly(True) i_text.setMaximumHeight(100) vbox.addWidget(i_text) vbox.addWidget(QLabel(_("Outputs"))) lines = map(lambda x: x[0] + u'\t\t' + self.parent.format_amount(x[1]), self.tx.outputs) o_text = QTextEdit() o_text.setText('\n'.join(lines)) o_text.setReadOnly(True) o_text.setMaximumHeight(100) vbox.addWidget(o_text)
def do_schedule(self): r = self.win.read_send_tab() now = QDateTime.currentDateTime() t = now.secsTo(self.time_e.dateTime()) outputs, fee, label, coins = r #print type(r), "\n", outputs, "\n", fee, "\n", label, "\n", coins if not r: return if t <= 240: QMessageBox.warning(None, _('Error'), _("Please select a time at least five minutes later than current time"), _('OK')) return time = self.time_e.dateTime().toTime_t() amount = self.win.amount_e.get_amount() fee = self.win.fee_e.get_amount() addr = self.win.payto_e.get_outputs()[0][1] self.schedule_requests = self.wallet.storage.get('schedule_requests',{}) self.schedule_requests[time] = (amount, fee, addr) self.wallet.storage.put('schedule_requests', self.schedule_requests) self.update_schedule_table() self.conn = sqlite3.connect('schedule.db') c = conn.cursor() c.execute("DROP TABLE IF EXISTS list;") c.execute("CREATE TABLE IF NOT EXISTS list (timestamp, amount, fee, address, info);") c.execute("INSERT INTO list VALUES (?, ?, ?, ?, ?);", (time, amount, fee, address)) #c.commit() conn.close()
def close(self): if not self.saved: if QMessageBox.question( self, _('Message'), _('This transaction is not saved. Close anyway?'), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.No: return QWidget.close(self)
def __init__(self, transaction_id, parent): super(TransactionWindow, self).__init__() self.tx_id = str(transaction_id) self.parent = parent self.setModal(True) self.resize(200,100) self.setWindowTitle(_("Transaction successfully sent")) self.layout = QGridLayout(self) history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference.")) self.layout.addWidget(QLabel(history_label)) self.label_edit = QLineEdit() self.label_edit.setPlaceholderText(_("Transaction label")) self.label_edit.setObjectName("label_input") self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0) self.label_edit.setFocusPolicy(Qt.ClickFocus) self.layout.addWidget(self.label_edit) self.save_button = QPushButton(_("Save")) self.layout.addWidget(self.save_button) self.save_button.clicked.connect(self.set_label) self.exec_()
def mouseReleaseEvent(self, event): dialog = QDialog(self) dialog.setWindowTitle(_('Electrum update')) dialog.setModal(1) main_layout = QGridLayout() main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3) ignore_version = QPushButton(_("Ignore this version")) ignore_version.clicked.connect(self.ignore_this_version) ignore_all_versions = QPushButton(_("Ignore all versions")) ignore_all_versions.clicked.connect(self.ignore_all_version) open_website = QPushButton(_("Goto download page")) open_website.clicked.connect(self.open_website) main_layout.addWidget(ignore_version, 1, 0) main_layout.addWidget(ignore_all_versions, 1, 1) main_layout.addWidget(open_website, 1, 2) dialog.setLayout(main_layout) self.dialog = dialog if not dialog.exec_(): return
def __init__(self, parent): super(CharacterDialog, self).__init__(parent) self.setWindowTitle(_("KeepKey Seed Recovery")) self.character_pos = 0 self.word_pos = 0 self.loop = QEventLoop() self.word_help = QLabel() self.char_buttons = [] vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(CHARACTER_RECOVERY)) hbox = QHBoxLayout() hbox.addWidget(self.word_help) for i in range(4): char_button = CharacterButton('*') char_button.setMaximumWidth(36) self.char_buttons.append(char_button) hbox.addWidget(char_button) self.accept_button = CharacterButton(_("Accept Word")) self.accept_button.clicked.connect(partial(self.process_key, 32)) self.rejected.connect(partial(self.loop.exit, 1)) hbox.addWidget(self.accept_button) hbox.addStretch(1) vbox.addLayout(hbox) self.finished_button = QPushButton(_("Seed Entered")) self.cancel_button = QPushButton(_("Cancel")) self.finished_button.clicked.connect(partial(self.process_key, Qt.Key_Return)) self.cancel_button.clicked.connect(self.rejected) buttons = Buttons(self.finished_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show()
def value_str(self, satoshis, rate): if satoshis is None: # Can happen with incomplete history return _("Unknown") if rate: value = Decimal(satoshis) / COIN * Decimal(rate) return "%s" % (self.ccy_amount_str(value, True)) return _("No data")
def update(self): self.menu_actions = [(_("Show"), self.do_show), (_("Delete"), self.do_delete)] requests_list = self.screen.ids.requests_container requests_list.clear_widgets() _list = self.app.wallet.get_sorted_requests(self.app.electrum_config) for req in _list: address = req["address"] timestamp = req.get("time", 0) amount = req.get("amount") expiration = req.get("exp", None) status = req.get("status") signature = req.get("sig") ci = Factory.RequestItem() ci.address = req["address"] ci.memo = self.app.wallet.get_label(address) status = req.get("status") if status == PR_PAID: ci.icon = "atlas://gui/kivy/theming/light/confirmed" elif status == PR_EXPIRED: ci.icon = "atlas://gui/kivy/theming/light/important" else: ci.icon = "atlas://gui/kivy/theming/light/important" ci.amount = self.app.format_amount_and_units(amount) if amount else "" ci.date = format_time(timestamp) ci.screen = self requests_list.add_widget(ci) if not _list: msg = _("This screen shows the list of payment requests you made.") requests_list.add_widget(EmptyLabel(text=msg))
def get_library_not_available_message(self) -> str: if hasattr(self, 'libraries_available_message'): message = self.libraries_available_message else: message = _("Missing libraries for {}.").format(self.name) message += '\n' + _("Make sure you install it with python3") return message
def parse_history(self, items): for item in items: tx_hash, conf, value, timestamp, balance = item time_str = _("unknown") if conf > 0: try: time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(" ")[:-3] except Exception: time_str = _("error") if conf == -1: time_str = _("unverified") icon = "atlas://gui/kivy/theming/light/close" elif conf == 0: time_str = _("pending") icon = "atlas://gui/kivy/theming/light/unconfirmed" elif conf < 6: conf = max(1, conf) icon = "atlas://gui/kivy/theming/light/clock{}".format(conf) else: icon = "atlas://gui/kivy/theming/light/confirmed" label = self.app.wallet.get_label(tx_hash) if tx_hash else _("Pruned transaction outputs") date = timestamp_to_datetime(timestamp) rate = run_hook("history_rate", date) if self.app.fiat_unit: s = run_hook("historical_value_str", value, date) quote_text = "..." if s is None else s + " " + self.app.fiat_unit else: quote_text = "" yield (conf, icon, time_str, label, value, tx_hash, quote_text)
def update(self): self.menu_actions = [(_("Pay"), self.do_pay), (_("Delete"), self.do_delete)] invoices_list = self.screen.ids.invoices_container invoices_list.clear_widgets() _list = self.app.invoices.sorted_list() for pr in _list: ci = Factory.InvoiceItem() ci.key = pr.get_id() ci.requestor = pr.get_requestor() ci.memo = pr.memo ci.amount = self.app.format_amount_and_units(pr.get_amount()) status = self.app.invoices.get_status(ci.key) if status == PR_PAID: ci.icon = "atlas://gui/kivy/theming/light/confirmed" elif status == PR_EXPIRED: ci.icon = "atlas://gui/kivy/theming/light/important" else: ci.icon = "atlas://gui/kivy/theming/light/important" exp = pr.get_expiration_date() ci.date = format_time(exp) if exp else _("Never") ci.screen = self invoices_list.add_widget(ci) if not _list: msg = _("This screen shows the list of payment requests that have been sent to you.") invoices_list.add_widget(EmptyLabel(text=msg))
def create_contact_menu(self, menu, item): label = unicode(item.text(1)) if label in self.aliases.keys(): addr = unicode(item.text(0)) label = unicode(item.text(1)) menu.addAction(_("View alias details"), lambda: self.show_contact_details(label)) menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
def delete_alias(self, x): if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")): if x in self.aliases: self.aliases.pop(x) self.update_history_tab() self.update_contacts_tab() self.update_completions()
def toggle_passphrase(self): if self.features.passphrase_protection: self.msg = _("Confirm on your %s device to disable passphrases") else: self.msg = _("Confirm on your %s device to enable passphrases") enabled = not self.features.passphrase_protection self.apply_settings(use_passphrase=enabled)
def __init__(self, parent): MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 3) self.currentItemChanged.connect(self.item_changed) self.itemClicked.connect(self.item_changed) self.setSortingEnabled(True) self.setColumnWidth(0, 180) self.hideColumn(1)
def before_send(self): ''' Change URL to address before making a send. IMPORTANT: return False to continue execution of the send return True to stop execution of the send ''' if self.win.payto_e.is_multiline(): # only supports single line entries atm return False if self.win.payto_e.is_pr: return payto_e = str(self.win.payto_e.toPlainText()) regex = re.compile(r'^([^\s]+) <([A-Za-z0-9]+)>') # only do that for converted addresses try: (url, address) = regex.search(payto_e).groups() except AttributeError: return False if not self.validated: msgBox = QMessageBox() msgBox.setText(_('WARNING: the address ' + address + ' could not be validated via an additional security check, DNSSEC, and thus may not be correct.')) msgBox.setInformativeText(_('Do you wish to continue?')) msgBox.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok) msgBox.setDefaultButton(QMessageBox.Cancel) reply = msgBox.exec_() if reply != QMessageBox.Ok: return True return False
def choose_wallet_type(self): grid = QGridLayout() grid.setSpacing(5) msg = _("Choose your wallet.") label = QLabel(msg) label.setWordWrap(True) grid.addWidget(label, 0, 0) gb = QGroupBox() b1 = QRadioButton(gb) b1.setText(_("Standard wallet (protected by password)")) b1.setChecked(True) b2 = QRadioButton(gb) b2.setText(_("Multi-signature wallet (two-factor authentication)")) grid.addWidget(b1,1,0) grid.addWidget(b2,2,0) vbox = QVBoxLayout() vbox.addLayout(grid) vbox.addStretch(1) vbox.addLayout(ok_cancel_buttons(self, _('Next'))) self.set_layout(vbox) if not self.exec_(): return if b1.isChecked(): return 'standard' elif b2.isChecked(): return '2of3'
def mpk_dialog(self): vbox = QVBoxLayout() vbox.addWidget(QLabel(_("Please enter your master public key."))) grid = QGridLayout() grid.setSpacing(8) label = QLabel(_("Key")) grid.addWidget(label, 0, 0) mpk_e = QTextEdit() mpk_e.setMaximumHeight(100) grid.addWidget(mpk_e, 0, 1) label = QLabel(_("Chain")) #grid.addWidget(label, 1, 0) chain_e = QTextEdit() chain_e.setMaximumHeight(100) #grid.addWidget(chain_e, 1, 1) vbox.addLayout(grid) vbox.addStretch(1) vbox.addLayout(ok_cancel_buttons(self, _('Next'))) self.set_layout(vbox) if not self.exec_(): return None, None mpk = str(mpk_e.toPlainText()).strip() chain = str(chain_e.toPlainText()).strip() return mpk, chain
def parse_history(self, items): for item in items: tx_hash, conf, value, timestamp, balance = item time_str = _("unknown") if conf > 0: try: time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] except Exception: time_str = _("error") if conf == -1: time_str = _('unverified') icon = "atlas://gui/kivy/theming/light/close" elif conf == 0: time_str = _('pending') icon = "atlas://gui/kivy/theming/light/unconfirmed" elif conf < 6: conf = max(1, conf) icon = "atlas://gui/kivy/theming/light/clock{}".format(conf) else: icon = "atlas://gui/kivy/theming/light/confirmed" label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs') date = timestamp_to_datetime(timestamp) rate = run_hook('history_rate', date) if self.app.fiat_unit: quote_text = "..." if rate is None else "{0:.3} {1}".format(Decimal(value) / 100000000 * Decimal(rate), self.app.fiat_unit) else: quote_text = '' yield (conf, icon, time_str, label, value, tx_hash, quote_text)
def update(self): self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)] requests_list = self.screen.ids.requests_container requests_list.clear_widgets() for req in self.app.wallet.get_sorted_requests(self.app.electrum_config): address = req['address'] timestamp = req.get('time', 0) amount = req.get('amount') expiration = req.get('exp', None) status = req.get('status') signature = req.get('sig') ci = Factory.RequestItem() ci.address = req['address'] ci.memo = self.app.wallet.get_label(address) status = req.get('status') if status == PR_PAID: ci.icon = "atlas://gui/kivy/theming/light/confirmed" elif status == PR_EXPIRED: ci.icon = "atlas://gui/kivy/theming/light/important" else: ci.icon = "atlas://gui/kivy/theming/light/important" ci.amount = self.app.format_amount_and_units(amount) if amount else '' ci.date = format_time(timestamp) ci.screen = self requests_list.add_widget(ci)
def multi_mpk_dialog(self, xpub_hot, n): vbox = QVBoxLayout() scroll = QScrollArea() scroll.setEnabled(True) scroll.setWidgetResizable(True) vbox.addWidget(scroll) w = QWidget() scroll.setWidget(w) innerVbox = QVBoxLayout() w.setLayout(innerVbox) vbox0 = seed_dialog.show_seed_box(MSG_SHOW_MPK, xpub_hot, 'hot') innerVbox.addLayout(vbox0) entries = [] for i in range(n): msg = _("Please enter the master public key of cosigner") + ' %d'%(i+1) vbox2, seed_e2 = seed_dialog.enter_seed_box(msg, self, 'cold') innerVbox.addLayout(vbox2) entries.append(seed_e2) vbox.addStretch(1) button = OkButton(self, _('Next')) vbox.addLayout(Buttons(CancelButton(self), button)) button.setEnabled(False) f = lambda: button.setEnabled( map(lambda e: Wallet.is_xpub(self.get_seed_text(e)), entries) == [True]*len(entries)) for e in entries: e.textChanged.connect(f) self.set_layout(vbox) if not self.exec_(): return return map(lambda e: self.get_seed_text(e), entries)
def parse_history(self, items): for item in items: tx_hash, conf, value, timestamp, balance = item time_str = _("unknown") if conf > 0: try: time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] except Exception: time_str = _("error") if conf == -1: time_str = _('unverified') icon = "atlas://gui/kivy/theming/light/close" elif conf == 0: time_str = _('pending') icon = "atlas://gui/kivy/theming/light/unconfirmed" elif conf < 6: time_str = '' # add new to fix error when conf < 0 conf = max(1, conf) icon = "atlas://gui/kivy/theming/light/clock{}".format(conf) else: icon = "atlas://gui/kivy/theming/light/confirmed" if tx_hash: label, is_default_label = self.app.wallet.get_label(tx_hash) else: label = _('Pruned transaction outputs') is_default_label = False quote_currency = 'USD' rate = self.get_history_rate(value, timestamp) quote_text = "..." if rate is None else "{0:.3} {1}".format(rate, quote_currency) yield (conf, icon, time_str, label, value, tx_hash, quote_text)
def password_dialog(self, wallet): msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\ +_("Leave these fields empty if you want to disable encryption.") from password_dialog import make_password_dialog, run_password_dialog self.set_layout( make_password_dialog(self, wallet, msg) ) run_password_dialog(self, wallet, self)
def show_summary(self): fx = self.parent.fx show_fiat = fx and fx.is_enabled() and fx.get_history_config() if not show_fiat: self.parent.show_message( _("Enable fiat exchange rate with history.")) return h = self.wallet.get_detailed_history( from_timestamp=time.mktime(self.start_date.timetuple()) if self.start_date else None, to_timestamp=time.mktime(self.end_date.timetuple()) if self.end_date else None, fx=fx) summary = h['summary'] if not summary: self.parent.show_message(_("Nothing to summarize.")) return start = summary['begin'] end = summary['end'] flow = summary['flow'] start_date = start.get('date') end_date = end.get('date') format_amount = lambda x: self.parent.format_amount( x.value) + ' ' + self.parent.base_unit() format_fiat = lambda x: str(x) + ' ' + self.parent.fx.ccy d = WindowModalDialog(self, _("Summary")) d.setMinimumSize(600, 150) vbox = QVBoxLayout() msg = messages.to_rtf(messages.MSG_CAPITAL_GAINS) vbox.addWidget(WWLabel(msg)) grid = QGridLayout() grid.addWidget(QLabel(_("Begin")), 0, 1) grid.addWidget(QLabel(_("End")), 0, 2) # grid.addWidget(QLabel(_("Date")), 1, 0) grid.addWidget(QLabel(self.format_date(start_date)), 1, 1) grid.addWidget(QLabel(self.format_date(end_date)), 1, 2) # grid.addWidget(QLabel(_("BTC balance")), 2, 0) grid.addWidget(QLabel(format_amount(start['BTC_balance'])), 2, 1) grid.addWidget(QLabel(format_amount(end['BTC_balance'])), 2, 2) # grid.addWidget(QLabel(_("BTC Fiat price")), 3, 0) grid.addWidget(QLabel(format_fiat(start.get('BTC_fiat_price'))), 3, 1) grid.addWidget(QLabel(format_fiat(end.get('BTC_fiat_price'))), 3, 2) # grid.addWidget(QLabel(_("Fiat balance")), 4, 0) grid.addWidget(QLabel(format_fiat(start.get('fiat_balance'))), 4, 1) grid.addWidget(QLabel(format_fiat(end.get('fiat_balance'))), 4, 2) # grid.addWidget(QLabel(_("Acquisition price")), 5, 0) grid.addWidget(QLabel(format_fiat(start.get('acquisition_price', ''))), 5, 1) grid.addWidget(QLabel(format_fiat(end.get('acquisition_price', ''))), 5, 2) # grid.addWidget(QLabel(_("Unrealized capital gains")), 6, 0) grid.addWidget(QLabel(format_fiat(start.get('unrealized_gains', ''))), 6, 1) grid.addWidget(QLabel(format_fiat(end.get('unrealized_gains', ''))), 6, 2) # grid2 = QGridLayout() grid2.addWidget(QLabel(_("BTC incoming")), 0, 0) grid2.addWidget(QLabel(format_amount(flow['BTC_incoming'])), 0, 1) grid2.addWidget(QLabel(_("Fiat incoming")), 1, 0) grid2.addWidget(QLabel(format_fiat(flow.get('fiat_incoming'))), 1, 1) grid2.addWidget(QLabel(_("BTC outgoing")), 2, 0) grid2.addWidget(QLabel(format_amount(flow['BTC_outgoing'])), 2, 1) grid2.addWidget(QLabel(_("Fiat outgoing")), 3, 0) grid2.addWidget(QLabel(format_fiat(flow.get('fiat_outgoing'))), 3, 1) # grid2.addWidget(QLabel(_("Realized capital gains")), 4, 0) grid2.addWidget( QLabel(format_fiat(flow.get('realized_capital_gains'))), 4, 1) vbox.addLayout(grid) vbox.addWidget(QLabel(_('Cash flow'))) vbox.addLayout(grid2) vbox.addLayout(Buttons(CloseButton(d))) d.setLayout(vbox) d.exec_()
class ChannelsList(MyTreeView): update_rows = QtCore.pyqtSignal(Abstract_Wallet) update_single_row = QtCore.pyqtSignal(Abstract_Wallet, AbstractChannel) gossip_db_loaded = QtCore.pyqtSignal() class Columns(IntEnum): SHORT_CHANID = 0 NODE_ALIAS = 1 LOCAL_BALANCE = 2 REMOTE_BALANCE = 3 CHANNEL_STATUS = 4 headers = { Columns.SHORT_CHANID: _('Short Channel ID'), Columns.NODE_ALIAS: _('Node alias'), Columns.LOCAL_BALANCE: _('Local'), Columns.REMOTE_BALANCE: _('Remote'), Columns.CHANNEL_STATUS: _('Status'), } filter_columns = [ Columns.SHORT_CHANID, Columns.NODE_ALIAS, Columns.CHANNEL_STATUS, ] _default_item_bg_brush = None # type: Optional[QBrush] def __init__(self, parent): super().__init__(parent, self.create_menu, stretch_column=self.Columns.NODE_ALIAS, editable_columns=[]) self.setModel(QtGui.QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.main_window = parent self.gossip_db_loaded.connect(self.on_gossip_db) self.update_rows.connect(self.do_update_rows) self.update_single_row.connect(self.do_update_single_row) self.network = self.parent.network self.lnworker = self.parent.wallet.lnworker self.lnbackups = self.parent.wallet.lnbackups self.setSortingEnabled(True) def format_fields(self, chan): labels = {} for subject in (REMOTE, LOCAL): bal_minus_htlcs = chan.balance_minus_outgoing_htlcs( subject) // 1000 label = self.parent.format_amount(bal_minus_htlcs) other = subject.inverted() bal_other = chan.balance(other) // 1000 bal_minus_htlcs_other = chan.balance_minus_outgoing_htlcs( other) // 1000 if bal_other != bal_minus_htlcs_other: label += ' (+' + self.parent.format_amount( bal_other - bal_minus_htlcs_other) + ')' labels[subject] = label status = chan.get_state_for_GUI() closed = chan.is_closed() if self.network and self.network.has_channel_db(): node_info = self.parent.network.channel_db.get_node_info_for_node_id( chan.node_id) node_alias = (node_info.alias if node_info else '') or '' else: node_alias = '' return [ chan.short_id_for_GUI(), node_alias, '' if closed else labels[LOCAL], '' if closed else labels[REMOTE], status ] def on_success(self, txid): self.main_window.show_error('Channel closed' + '\n' + txid) def on_failure(self, exc_info): type_, e, tb = exc_info traceback.print_tb(tb) self.main_window.show_error('Failed to close channel:\n{}'.format( repr(e))) def close_channel(self, channel_id): msg = _('Close channel?') if not self.parent.question(msg): return def task(): coro = self.lnworker.close_channel(channel_id) return self.network.run_from_another_thread(coro) WaitingDialog(self, 'please wait..', task, self.on_success, self.on_failure) def force_close(self, channel_id): chan = self.lnworker.channels[channel_id] to_self_delay = chan.config[REMOTE].to_self_delay msg = _('Force-close channel?') + '\n\n'\ + _('Funds retrieved from this channel will not be available before {} blocks after forced closure.').format(to_self_delay) + ' '\ + _('After that delay, funds will be sent to an address derived from your wallet seed.') + '\n\n'\ + _('In the meantime, channel funds will not be recoverable from your seed, and might be lost if you lose your wallet.') + ' '\ + _('To prevent that, you should have a backup of this channel on another device.') if self.parent.question(msg): def task(): coro = self.lnworker.force_close_channel(channel_id) return self.network.run_from_another_thread(coro) WaitingDialog(self, 'please wait..', task, self.on_success, self.on_failure) def remove_channel(self, channel_id): if self.main_window.question( _('Are you sure you want to delete this channel? This will purge associated transactions from your wallet history.' )): self.lnworker.remove_channel(channel_id) def remove_channel_backup(self, channel_id): if self.main_window.question(_('Remove channel backup?')): self.lnbackups.remove_channel_backup(channel_id) def export_channel_backup(self, channel_id): msg = ' '.join([ _("Channel backups can be imported in another instance of the same wallet, by scanning this QR code." ), _("Please note that channel backups cannot be used to restore your channels." ), _("If you lose your wallet file, the only thing you can do with a backup is to request your channel to be closed, so that your funds will be sent on-chain." ), ]) data = self.lnworker.export_channel_backup(channel_id) self.main_window.show_qrcode(data, 'channel backup', help_text=msg, show_copy_text_btn=True) def request_force_close(self, channel_id): def task(): coro = self.lnbackups.request_force_close(channel_id) return self.network.run_from_another_thread(coro) def on_success(b): self.main_window.show_message('success') WaitingDialog(self, 'please wait..', task, on_success, self.on_failure) def create_menu(self, position): menu = QMenu() menu.setSeparatorsCollapsible( True) # consecutive separators are merged together selected = self.selected_in_column(self.Columns.NODE_ALIAS) if not selected: menu.addAction( _("Import channel backup"), lambda: self.parent.do_process_from_text_channel_backup()) menu.exec_(self.viewport().mapToGlobal(position)) return multi_select = len(selected) > 1 if multi_select: return idx = self.indexAt(position) if not idx.isValid(): return item = self.model().itemFromIndex(idx) if not item: return channel_id = idx.sibling(idx.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID) if channel_id in self.lnbackups.channel_backups: menu.addAction(_("Request force-close"), lambda: self.request_force_close(channel_id)) menu.addAction(_("Delete"), lambda: self.remove_channel_backup(channel_id)) menu.exec_(self.viewport().mapToGlobal(position)) return chan = self.lnworker.channels[channel_id] menu.addAction(_("Details..."), lambda: self.parent.show_channel(channel_id)) cc = self.add_copy_menu(menu, idx) cc.addAction( _("Node ID"), lambda: self.place_text_on_clipboard(chan.node_id.hex(), title=_("Node ID"))) cc.addAction( _("Long Channel ID"), lambda: self.place_text_on_clipboard(channel_id.hex(), title=_("Long Channel ID"))) if not chan.is_closed(): if not chan.is_frozen_for_sending(): menu.addAction(_("Freeze (for sending)"), lambda: chan.set_frozen_for_sending(True)) else: menu.addAction(_("Unfreeze (for sending)"), lambda: chan.set_frozen_for_sending(False)) if not chan.is_frozen_for_receiving(): menu.addAction(_("Freeze (for receiving)"), lambda: chan.set_frozen_for_receiving(True)) else: menu.addAction(_("Unfreeze (for receiving)"), lambda: chan.set_frozen_for_receiving(False)) funding_tx = self.parent.wallet.db.get_transaction( chan.funding_outpoint.txid) if funding_tx: menu.addAction(_("View funding transaction"), lambda: self.parent.show_transaction(funding_tx)) if not chan.is_closed(): menu.addSeparator() if chan.peer_state == PeerState.GOOD: menu.addAction(_("Close channel"), lambda: self.close_channel(channel_id)) menu.addAction(_("Force-close channel"), lambda: self.force_close(channel_id)) else: item = chan.get_closing_height() if item: txid, height, timestamp = item closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid) if closing_tx: menu.addAction( _("View closing transaction"), lambda: self.parent.show_transaction(closing_tx)) menu.addSeparator() menu.addAction(_("Export backup"), lambda: self.export_channel_backup(channel_id)) if chan.is_redeemed(): menu.addSeparator() menu.addAction(_("Delete"), lambda: self.remove_channel(channel_id)) menu.exec_(self.viewport().mapToGlobal(position)) @QtCore.pyqtSlot(Abstract_Wallet, AbstractChannel) def do_update_single_row(self, wallet: Abstract_Wallet, chan: AbstractChannel): if wallet != self.parent.wallet: return for row in range(self.model().rowCount()): item = self.model().item(row, self.Columns.NODE_ALIAS) if item.data(ROLE_CHANNEL_ID) != chan.channel_id: continue for column, v in enumerate(self.format_fields(chan)): self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole) items = [self.model().item(row, column) for column in self.Columns] self._update_chan_frozen_bg(chan=chan, items=items) if wallet.lnworker: self.update_can_send(wallet.lnworker) @QtCore.pyqtSlot() def on_gossip_db(self): self.do_update_rows(self.parent.wallet) @QtCore.pyqtSlot(Abstract_Wallet) def do_update_rows(self, wallet): if wallet != self.parent.wallet: return channels = list( wallet.lnworker.channels.values()) if wallet.lnworker else [] backups = list(wallet.lnbackups.channel_backups.values()) if wallet.lnworker: self.update_can_send(wallet.lnworker) self.model().clear() self.update_headers(self.headers) for chan in channels + backups: items = [QtGui.QStandardItem(x) for x in self.format_fields(chan)] self.set_editability(items) if self._default_item_bg_brush is None: self._default_item_bg_brush = items[ self.Columns.NODE_ALIAS].background() items[self.Columns.NODE_ALIAS].setData(chan.channel_id, ROLE_CHANNEL_ID) items[self.Columns.NODE_ALIAS].setFont(QFont(MONOSPACE_FONT)) items[self.Columns.LOCAL_BALANCE].setFont(QFont(MONOSPACE_FONT)) items[self.Columns.REMOTE_BALANCE].setFont(QFont(MONOSPACE_FONT)) self._update_chan_frozen_bg(chan=chan, items=items) self.model().insertRow(0, items) self.sortByColumn(self.Columns.SHORT_CHANID, Qt.DescendingOrder) def _update_chan_frozen_bg(self, *, chan: AbstractChannel, items: Sequence[QStandardItem]): assert self._default_item_bg_brush is not None # frozen for sending item = items[self.Columns.LOCAL_BALANCE] if chan.is_frozen_for_sending(): item.setBackground(ColorScheme.BLUE.as_color(True)) item.setToolTip( _("This channel is frozen for sending. It will not be used for outgoing payments." )) else: item.setBackground(self._default_item_bg_brush) item.setToolTip("") # frozen for receiving item = items[self.Columns.REMOTE_BALANCE] if chan.is_frozen_for_receiving(): item.setBackground(ColorScheme.BLUE.as_color(True)) item.setToolTip( _("This channel is frozen for receiving. It will not be included in invoices." )) else: item.setBackground(self._default_item_bg_brush) item.setToolTip("") def update_can_send(self, lnworker: LNWallet): msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\ + ' ' + self.parent.base_unit() + '; '\ + _('can receive') + ' ' + self.parent.format_amount(lnworker.num_sats_can_receive())\ + ' ' + self.parent.base_unit() self.can_send_label.setText(msg) def get_toolbar(self): h = QHBoxLayout() self.can_send_label = QLabel('') h.addWidget(self.can_send_label) h.addStretch() self.swap_button = EnterButton(_('Swap'), self.swap_dialog) self.swap_button.setEnabled(self.parent.wallet.has_lightning()) self.new_channel_button = EnterButton(_('Open Channel'), self.new_channel_with_warning) self.new_channel_button.setEnabled(self.parent.wallet.has_lightning()) h.addWidget(self.new_channel_button) h.addWidget(self.swap_button) return h def new_channel_with_warning(self): if not self.parent.wallet.lnworker.channels: warning1 = _("Lightning support in Electrum is experimental. " "Do not put large amounts in lightning channels.") warning2 = _( "Funds stored in lightning channels are not recoverable from your seed. " "You must backup your wallet file everytime you create a new channel." ) answer = self.parent.question( _('Do you want to create your first channel?') + '\n\n' + _('WARNINGS') + ': ' + '\n\n' + warning1 + '\n\n' + warning2) if answer: self.new_channel_dialog() else: self.new_channel_dialog() def statistics_dialog(self): channel_db = self.parent.network.channel_db capacity = self.parent.format_amount( channel_db.capacity()) + ' ' + self.parent.base_unit() d = WindowModalDialog(self.parent, _('Lightning Network Statistics')) d.setMinimumWidth(400) vbox = QVBoxLayout(d) h = QGridLayout() h.addWidget(QLabel(_('Nodes') + ':'), 0, 0) h.addWidget(QLabel('{}'.format(channel_db.num_nodes)), 0, 1) h.addWidget(QLabel(_('Channels') + ':'), 1, 0) h.addWidget(QLabel('{}'.format(channel_db.num_channels)), 1, 1) h.addWidget(QLabel(_('Capacity') + ':'), 2, 0) h.addWidget(QLabel(capacity), 2, 1) vbox.addLayout(h) vbox.addLayout(Buttons(OkButton(d))) d.exec_() def new_channel_dialog(self): lnworker = self.parent.wallet.lnworker d = WindowModalDialog(self.parent, _('Open Channel')) vbox = QVBoxLayout(d) vbox.addWidget( QLabel(_('Enter Remote Node ID or connection string or invoice'))) local_nodeid = FreezableLineEdit() local_nodeid.setMinimumWidth(700) local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey)) local_nodeid.setFrozen(True) local_nodeid.setCursorPosition(0) remote_nodeid = QLineEdit() remote_nodeid.setMinimumWidth(700) amount_e = BTCAmountEdit(self.parent.get_decimal_point) # max button def spend_max(): amount_e.setFrozen(max_button.isChecked()) if not max_button.isChecked(): return make_tx = self.parent.mktx_for_open_channel('!') try: tx = make_tx(None) except (NotEnoughFunds, NoDynamicFeeEstimates) as e: max_button.setChecked(False) amount_e.setFrozen(False) self.main_window.show_error(str(e)) return amount = tx.output_value() amount = min(amount, LN_MAX_FUNDING_SAT) amount_e.setAmount(amount) max_button = EnterButton(_("Max"), spend_max) max_button.setFixedWidth(100) max_button.setCheckable(True) clear_button = QPushButton(d, text=_('Clear')) clear_button.setFixedWidth(100) def on_clear(): amount_e.setText('') amount_e.setFrozen(False) amount_e.repaint() # macOS hack for #6269 remote_nodeid.setText('') remote_nodeid.repaint() # macOS hack for #6269 max_button.setChecked(False) max_button.repaint() # macOS hack for #6269 clear_button.clicked.connect(on_clear) h = QGridLayout() h.addWidget(QLabel(_('Your Node ID')), 0, 0) h.addWidget(local_nodeid, 0, 1, 1, 4) h.addWidget(QLabel(_('Remote Node ID')), 1, 0) h.addWidget(remote_nodeid, 1, 1, 1, 4) h.addWidget(QLabel('Amount'), 3, 0) h.addWidget(amount_e, 3, 1) h.addWidget(max_button, 3, 2) h.addWidget(clear_button, 3, 3) vbox.addLayout(h) ok_button = OkButton(d) ok_button.setDefault(True) vbox.addLayout(Buttons(CancelButton(d), ok_button)) if not d.exec_(): return if max_button.isChecked( ) and amount_e.get_amount() < LN_MAX_FUNDING_SAT: # if 'max' enabled and amount is strictly less than max allowed, # that means we have fewer coins than max allowed, and hence we can # spend all coins funding_sat = '!' else: funding_sat = amount_e.get_amount() connect_str = str(remote_nodeid.text()).strip() if not connect_str or not funding_sat: return self.parent.open_channel(connect_str, funding_sat, 0) def swap_dialog(self): from .swap_dialog import SwapDialog d = SwapDialog(self.parent) d.run()
def mouseReleaseEvent(self, x): custom_message_box(icon=QMessageBox.Information, parent=self, title=_('Help'), text=self.help_text)
def done_processing(self): QMessageBox.information(None, _("Labels synchronised"), _("Your labels have been synchronised."))
def settings_widget(self, window): return EnterButton(_('Settings'), partial(self.settings_dialog, window))
def new_channel_dialog(self): lnworker = self.parent.wallet.lnworker d = WindowModalDialog(self.parent, _('Open Channel')) vbox = QVBoxLayout(d) vbox.addWidget( QLabel(_('Enter Remote Node ID or connection string or invoice'))) local_nodeid = FreezableLineEdit() local_nodeid.setMinimumWidth(700) local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey)) local_nodeid.setFrozen(True) local_nodeid.setCursorPosition(0) remote_nodeid = QLineEdit() remote_nodeid.setMinimumWidth(700) amount_e = BTCAmountEdit(self.parent.get_decimal_point) # max button def spend_max(): amount_e.setFrozen(max_button.isChecked()) if not max_button.isChecked(): return make_tx = self.parent.mktx_for_open_channel('!') try: tx = make_tx(None) except (NotEnoughFunds, NoDynamicFeeEstimates) as e: max_button.setChecked(False) amount_e.setFrozen(False) self.main_window.show_error(str(e)) return amount = tx.output_value() amount = min(amount, LN_MAX_FUNDING_SAT) amount_e.setAmount(amount) max_button = EnterButton(_("Max"), spend_max) max_button.setFixedWidth(100) max_button.setCheckable(True) clear_button = QPushButton(d, text=_('Clear')) clear_button.setFixedWidth(100) def on_clear(): amount_e.setText('') amount_e.setFrozen(False) amount_e.repaint() # macOS hack for #6269 remote_nodeid.setText('') remote_nodeid.repaint() # macOS hack for #6269 max_button.setChecked(False) max_button.repaint() # macOS hack for #6269 clear_button.clicked.connect(on_clear) h = QGridLayout() h.addWidget(QLabel(_('Your Node ID')), 0, 0) h.addWidget(local_nodeid, 0, 1, 1, 4) h.addWidget(QLabel(_('Remote Node ID')), 1, 0) h.addWidget(remote_nodeid, 1, 1, 1, 4) h.addWidget(QLabel('Amount'), 3, 0) h.addWidget(amount_e, 3, 1) h.addWidget(max_button, 3, 2) h.addWidget(clear_button, 3, 3) vbox.addLayout(h) ok_button = OkButton(d) ok_button.setDefault(True) vbox.addLayout(Buttons(CancelButton(d), ok_button)) if not d.exec_(): return if max_button.isChecked( ) and amount_e.get_amount() < LN_MAX_FUNDING_SAT: # if 'max' enabled and amount is strictly less than max allowed, # that means we have fewer coins than max allowed, and hence we can # spend all coins funding_sat = '!' else: funding_sat = amount_e.get_amount() connect_str = str(remote_nodeid.text()).strip() if not connect_str or not funding_sat: return self.parent.open_channel(connect_str, funding_sat, 0)
def update_can_send(self, lnworker: LNWallet): msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\ + ' ' + self.parent.base_unit() + '; '\ + _('can receive') + ' ' + self.parent.format_amount(lnworker.num_sats_can_receive())\ + ' ' + self.parent.base_unit() self.can_send_label.setText(msg)
def remove_channel_backup(self, channel_id): if self.main_window.question(_('Remove channel backup?')): self.lnbackups.remove_channel_backup(channel_id)
def __init__(self, parent): QTreeWidget.__init__(self) self.parent = parent self.setHeaderLabels([_('Connected node'), _('Height')]) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.create_menu)
def create_menu(self, position): menu = QMenu() menu.setSeparatorsCollapsible( True) # consecutive separators are merged together selected = self.selected_in_column(self.Columns.NODE_ALIAS) if not selected: menu.addAction( _("Import channel backup"), lambda: self.parent.do_process_from_text_channel_backup()) menu.exec_(self.viewport().mapToGlobal(position)) return multi_select = len(selected) > 1 if multi_select: return idx = self.indexAt(position) if not idx.isValid(): return item = self.model().itemFromIndex(idx) if not item: return channel_id = idx.sibling(idx.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID) if channel_id in self.lnbackups.channel_backups: menu.addAction(_("Request force-close"), lambda: self.request_force_close(channel_id)) menu.addAction(_("Delete"), lambda: self.remove_channel_backup(channel_id)) menu.exec_(self.viewport().mapToGlobal(position)) return chan = self.lnworker.channels[channel_id] menu.addAction(_("Details..."), lambda: self.parent.show_channel(channel_id)) cc = self.add_copy_menu(menu, idx) cc.addAction( _("Node ID"), lambda: self.place_text_on_clipboard(chan.node_id.hex(), title=_("Node ID"))) cc.addAction( _("Long Channel ID"), lambda: self.place_text_on_clipboard(channel_id.hex(), title=_("Long Channel ID"))) if not chan.is_closed(): if not chan.is_frozen_for_sending(): menu.addAction(_("Freeze (for sending)"), lambda: chan.set_frozen_for_sending(True)) else: menu.addAction(_("Unfreeze (for sending)"), lambda: chan.set_frozen_for_sending(False)) if not chan.is_frozen_for_receiving(): menu.addAction(_("Freeze (for receiving)"), lambda: chan.set_frozen_for_receiving(True)) else: menu.addAction(_("Unfreeze (for receiving)"), lambda: chan.set_frozen_for_receiving(False)) funding_tx = self.parent.wallet.db.get_transaction( chan.funding_outpoint.txid) if funding_tx: menu.addAction(_("View funding transaction"), lambda: self.parent.show_transaction(funding_tx)) if not chan.is_closed(): menu.addSeparator() if chan.peer_state == PeerState.GOOD: menu.addAction(_("Close channel"), lambda: self.close_channel(channel_id)) menu.addAction(_("Force-close channel"), lambda: self.force_close(channel_id)) else: item = chan.get_closing_height() if item: txid, height, timestamp = item closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid) if closing_tx: menu.addAction( _("View closing transaction"), lambda: self.parent.show_transaction(closing_tx)) menu.addSeparator() menu.addAction(_("Export backup"), lambda: self.export_channel_backup(channel_id)) if chan.is_redeemed(): menu.addSeparator() menu.addAction(_("Delete"), lambda: self.remove_channel(channel_id)) menu.exec_(self.viewport().mapToGlobal(position))
def exchange_rate_button(self, grid): quote_currency = self.config.get("currency", "EUR") self.fiat_button = EnterButton(_(quote_currency), self.fiat_dialog) grid.addWidget(self.fiat_button, 4, 3, Qt.AlignHCenter)
def remove_channel(self, channel_id): if self.main_window.question( _('Are you sure you want to delete this channel? This will purge associated transactions from your wallet history.' )): self.lnworker.remove_channel(channel_id)
def history_tab_update(self): if self.config.get('history_rates', 'unchecked') == "checked": cur_exchange = self.config.get('use_exchange', "Blockchain") try: tx_list = self.tx_list except Exception: return try: mintimestr = datetime.datetime.fromtimestamp( int( min(tx_list.items(), key=lambda x: x[1]['timestamp']) [1]['timestamp'])).strftime('%Y-%m-%d') except Exception: return maxtimestr = datetime.datetime.now().strftime('%Y-%m-%d') if cur_exchange == "CoinDesk": try: resp_hist = self.exchanger.get_json( 'api.coindesk.com', "/v1/bpi/historical/close.json?start=" + mintimestr + "&end=" + maxtimestr) except Exception: return elif cur_exchange == "Winkdex": try: resp_hist = self.exchanger.get_json( 'winkdex.com', "/static/data/0_86400_730.json")['prices'] except Exception: return elif cur_exchange == "BitcoinVenezuela": cur_currency = self.config.get('currency', "EUR") if cur_currency == "VEF": try: resp_hist = self.exchanger.get_json( 'api.bitcoinvenezuela.com', "/historical/index.php?coin=BTC")['VEF_BTC'] except Exception: return elif cur_currency == "ARS": try: resp_hist = self.exchanger.get_json( 'api.bitcoinvenezuela.com', "/historical/index.php?coin=BTC")['ARS_BTC'] except Exception: return else: return self.gui.main_window.is_edit = True self.gui.main_window.history_list.setColumnCount(6) self.gui.main_window.history_list.setHeaderLabels([ '', _('Date'), _('Description'), _('Amount'), _('Balance'), _('Fiat Amount') ]) root = self.gui.main_window.history_list.invisibleRootItem() childcount = root.childCount() for i in range(childcount): item = root.child(i) try: tx_info = tx_list[str( item.data(0, Qt.UserRole).toPyObject())] except Exception: newtx = self.wallet.get_tx_history() v = newtx[[x[0] for x in newtx].index( str(item.data(0, Qt.UserRole).toPyObject()))][3] tx_info = {'timestamp': int(time.time()), 'value': v} pass tx_time = int(tx_info['timestamp']) if cur_exchange == "CoinDesk": tx_time_str = datetime.datetime.fromtimestamp( tx_time).strftime('%Y-%m-%d') try: tx_USD_val = "%.2f %s" % ( Decimal(str(tx_info['value'])) / 100000000 * Decimal(resp_hist['bpi'][tx_time_str]), "USD") except KeyError: tx_USD_val = "%.2f %s" % (self.btc_rate * Decimal( str(tx_info['value'])) / 100000000, "USD") elif cur_exchange == "Winkdex": tx_time_str = int(tx_time) - (int(tx_time) % (60 * 60 * 24)) try: tx_rate = resp_hist[[x['x'] for x in resp_hist ].index(tx_time_str)]['y'] tx_USD_val = "%.2f %s" % (Decimal(tx_info['value']) / 100000000 * Decimal(tx_rate), "USD") except ValueError: tx_USD_val = "%.2f %s" % (self.btc_rate * Decimal( tx_info['value']) / 100000000, "USD") elif cur_exchange == "BitcoinVenezuela": tx_time_str = datetime.datetime.fromtimestamp( tx_time).strftime('%Y-%m-%d') try: num = resp_hist[tx_time_str].replace(',', '') tx_BTCVEN_val = "%.2f %s" % ( Decimal(str(tx_info['value'])) / 100000000 * Decimal(num), cur_currency) except KeyError: tx_BTCVEN_val = _("No data") if cur_exchange == "CoinDesk" or cur_exchange == "Winkdex": item.setText(5, tx_USD_val) elif cur_exchange == "BitcoinVenezuela": item.setText(5, tx_BTCVEN_val) if Decimal(str(tx_info['value'])) < 0: item.setForeground(5, QBrush(QColor("#BC1E1E"))) for i, width in enumerate( self.gui.main_window.column_widths['history']): self.gui.main_window.history_list.setColumnWidth(i, width) self.gui.main_window.history_list.setColumnWidth(4, 140) self.gui.main_window.history_list.setColumnWidth(5, 120) self.gui.main_window.is_edit = False
def __init__(self, network, config, wizard=False): self.network = network self.config = config self.protocol = None self.tor_proxy = None self.tabs = tabs = QTabWidget() server_tab = QWidget() proxy_tab = QWidget() blockchain_tab = QWidget() tabs.addTab(blockchain_tab, _('Overview')) tabs.addTab(server_tab, _('Server')) tabs.addTab(proxy_tab, _('Proxy')) # server tab grid = QGridLayout(server_tab) grid.setSpacing(8) self.server_host = QLineEdit() self.server_host.setFixedWidth(200) self.server_port = QLineEdit() self.server_port.setFixedWidth(60) self.autoconnect_cb = QCheckBox(_('Select server automatically')) self.autoconnect_cb.setEnabled( self.config.is_modifiable('auto_connect')) self.server_host.editingFinished.connect(self.set_server) self.server_port.editingFinished.connect(self.set_server) self.autoconnect_cb.clicked.connect(self.set_server) self.autoconnect_cb.clicked.connect(self.update) msg = ' '.join([ _("If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain." ), _("If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging." ) ]) grid.addWidget(self.autoconnect_cb, 0, 0, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) grid.addWidget(QLabel(_('Server') + ':'), 1, 0) grid.addWidget(self.server_host, 1, 1, 1, 2) grid.addWidget(self.server_port, 1, 3) label = _('Server peers') if network.is_connected() else _( 'Default Servers') grid.addWidget(QLabel(label), 2, 0, 1, 5) self.servers_list = ServerListWidget(self) grid.addWidget(self.servers_list, 3, 0, 1, 5) # Proxy tab grid = QGridLayout(proxy_tab) grid.setSpacing(8) # proxy setting self.proxy_cb = QCheckBox(_('Use proxy')) self.proxy_cb.clicked.connect(self.check_disable_proxy) self.proxy_cb.clicked.connect(self.set_proxy) self.proxy_mode = QComboBox() self.proxy_mode.addItems(['SOCKS4', 'SOCKS5', 'HTTP']) self.proxy_host = QLineEdit() self.proxy_host.setFixedWidth(200) self.proxy_port = QLineEdit() self.proxy_port.setFixedWidth(60) self.proxy_user = QLineEdit() self.proxy_user.setPlaceholderText(_("Proxy user")) self.proxy_password = QLineEdit() self.proxy_password.setPlaceholderText(_("Password")) self.proxy_password.setEchoMode(QLineEdit.Password) self.proxy_password.setFixedWidth(60) self.proxy_mode.currentIndexChanged.connect(self.set_proxy) self.proxy_host.editingFinished.connect(self.set_proxy) self.proxy_port.editingFinished.connect(self.set_proxy) self.proxy_user.editingFinished.connect(self.set_proxy) self.proxy_password.editingFinished.connect(self.set_proxy) self.proxy_mode.currentIndexChanged.connect( self.proxy_settings_changed) self.proxy_host.textEdited.connect(self.proxy_settings_changed) self.proxy_port.textEdited.connect(self.proxy_settings_changed) self.proxy_user.textEdited.connect(self.proxy_settings_changed) self.proxy_password.textEdited.connect(self.proxy_settings_changed) self.tor_cb = QCheckBox(_("Use Tor Proxy")) self.tor_cb.setIcon(QIcon(":icons/tor_logo.png")) self.tor_cb.hide() self.tor_cb.clicked.connect(self.use_tor_proxy) grid.addWidget(self.tor_cb, 1, 0, 1, 3) grid.addWidget(self.proxy_cb, 2, 0, 1, 3) grid.addWidget( HelpButton( _('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.' )), 2, 4) grid.addWidget(self.proxy_mode, 4, 1) grid.addWidget(self.proxy_host, 4, 2) grid.addWidget(self.proxy_port, 4, 3) grid.addWidget(self.proxy_user, 5, 2) grid.addWidget(self.proxy_password, 5, 3) grid.setRowStretch(7, 1) # Blockchain Tab grid = QGridLayout(blockchain_tab) msg = ' '.join([ _("SnowGem Electrum connects to several nodes in order to download block headers and find out the longest blockchain." ), _("This blockchain is used to verify the transactions sent by your transaction server." ) ]) self.status_label = QLabel('') grid.addWidget(QLabel(_('Status') + ':'), 0, 0) grid.addWidget(self.status_label, 0, 1, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) self.server_label = QLabel('') msg = _( "SnowGem Electrum sends your wallet addresses to a single server, in order to receive your transaction history." ) grid.addWidget(QLabel(_('Server') + ':'), 1, 0) grid.addWidget(self.server_label, 1, 1, 1, 3) grid.addWidget(HelpButton(msg), 1, 4) self.height_label = QLabel('') msg = _('This is the height of your local copy of the blockchain.') grid.addWidget(QLabel(_('Blockchain') + ':'), 2, 0) grid.addWidget(self.height_label, 2, 1) grid.addWidget(HelpButton(msg), 2, 4) self.split_label = QLabel('') grid.addWidget(self.split_label, 3, 0, 1, 3) self.nodes_list_widget = NodesListWidget(self) grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5) vbox = QVBoxLayout() vbox.addWidget(tabs) self.layout_ = vbox # tor detector self.td = td = TorDetector() td.found_proxy.connect(self.suggest_proxy) td.start() self.fill_in_proxy_settings() self.update()
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() self.signing = True inputs = [] inputsPaths = [] pubKeys = [] chipInputs = [] redeemScripts = [] signatures = [] preparedTrustedInputs = [] changePath = "" changeAmount = None output = None outputAmount = None p2shTransaction = False segwitTransaction = False pin = "" self.get_client() # prompt for the PIN before displaying the dialog if necessary # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for txin in tx.inputs(): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] in ['p2sh']: p2shTransaction = True if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']: if not self.get_client_electrum().supports_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True if txin['type'] in ['p2wpkh', 'p2wsh']: if not self.get_client_electrum().supports_native_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin) for i, x_pubkey in enumerate(x_pubkeys): if x_pubkey in derivations: signingPos = i s = derivations.get(x_pubkey) hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1]) break else: self.give_error("No matching x_key for sign_transaction") # should never happen redeemScript = Transaction.get_preimage_script(txin) inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1) ]) inputsPaths.append(hwAddress) pubKeys.append(pubkeys) # Sanity check if p2shTransaction: for txin in tx.inputs(): if txin['type'] != 'p2sh': self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen txOutput = var_int(len(tx.outputs())) for txout in tx.outputs(): output_type, addr, amount = txout txOutput += int_to_hex(amount, 8) script = tx.pay_script(output_type, addr) txOutput += var_int(len(script)//2) txOutput += script txOutput = bfh(txOutput) # Recognize outputs - only one output and one change is authorized if not p2shTransaction: if not self.get_client_electrum().supports_multi_output(): if len(tx.outputs()) > 2: self.give_error("Transaction with more than 2 outputs not supported") for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if (info is not None) and (len(tx.outputs()) != 1): index, xpubs, m = info changePath = self.get_derivation()[2:] + "/%d/%d"%index changeAmount = amount else: output = address outputAmount = amount self.handler.show_message(_("Confirm Transaction on your Ledger device...")) try: # Get trusted inputs from the original transactions for utxo in inputs: sequence = int_to_hex(utxo[5], 4) if segwitTransaction: txtmp = bitcoinTransaction(bfh(utxo[0])) tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) tmp += txtmp.outputs[utxo[1]].amount chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) elif not p2shTransaction: txtmp = bitcoinTransaction(bfh(utxo[0])) trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1]) trustedInput['sequence'] = sequence chipInputs.append(trustedInput) redeemScripts.append(txtmp.outputs[utxo[1]].script) else: tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) chipInputs.append({'value' : tmp, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) # Sign all inputs firstTransaction = True inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) if segwitTransaction: self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [ chipInputs[inputIndex] ] self.get_client().startUntrustedTransaction(False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 else: while inputIndex < len(inputs): self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput if firstTransaction: transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) else: # Sign input with the provided PIN inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 if pin != 'paired': firstTransaction = False except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BaseException as e: traceback.print_exc(file=sys.stdout) self.give_error(e, True) finally: self.handler.finished() for i, txin in enumerate(tx.inputs()): signingPos = inputs[i][4] txin['signatures'][signingPos] = bh2u(signatures[i]) tx.raw = tx.serialize() self.signing = False
def settings_dialog(self): d = QDialog() d.setWindowTitle("Settings") layout = QGridLayout(d) layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0) layout.addWidget(QLabel(_('Currency: ')), 1, 0) layout.addWidget(QLabel(_('History Rates: ')), 2, 0) combo = QComboBox() combo_ex = QComboBox() hist_checkbox = QCheckBox() hist_checkbox.setEnabled(False) if self.config.get('history_rates', 'unchecked') == 'unchecked': hist_checkbox.setChecked(False) else: hist_checkbox.setChecked(True) ok_button = QPushButton(_("OK")) def on_change(x): try: cur_request = str(self.currencies[x]) except Exception: return if cur_request != self.config.get('currency', "EUR"): self.config.set_key('currency', cur_request, True) cur_exchange = self.config.get('use_exchange', "Blockchain") if cur_request == "USD" and (cur_exchange == "CoinDesk" or cur_exchange == "Winkdex"): hist_checkbox.setEnabled(True) elif cur_request == "VEF" and (cur_exchange == "BitcoinVenezuela"): hist_checkbox.setEnabled(True) elif cur_request == "ARS" and (cur_exchange == "BitcoinVenezuela"): hist_checkbox.setEnabled(True) else: hist_checkbox.setChecked(False) hist_checkbox.setEnabled(False) self.win.update_status() try: self.fiat_button except: pass else: self.fiat_button.setText(cur_request) def disable_check(): hist_checkbox.setChecked(False) hist_checkbox.setEnabled(False) def on_change_ex(x): cur_request = str(self.exchanges[x]) if cur_request != self.config.get('use_exchange', "Blockchain"): self.config.set_key('use_exchange', cur_request, True) self.currencies = [] combo.clear() self.exchanger.query_rates.set() cur_currency = self.config.get('currency', "EUR") if cur_request == "CoinDesk" or cur_request == "Winkdex": if cur_currency == "USD": hist_checkbox.setEnabled(True) else: disable_check() elif cur_request == "BitcoinVenezuela": if cur_currency == "VEF" or cur_currency == "ARS": hist_checkbox.setEnabled(True) else: disable_check() else: disable_check() set_currencies(combo) self.win.update_status() def on_change_hist(checked): if checked: self.config.set_key('history_rates', 'checked') self.history_tab_update() else: self.config.set_key('history_rates', 'unchecked') self.gui.main_window.history_list.setHeaderLabels([ '', _('Date'), _('Description'), _('Amount'), _('Balance') ]) self.gui.main_window.history_list.setColumnCount(5) for i, width in enumerate( self.gui.main_window.column_widths['history']): self.gui.main_window.history_list.setColumnWidth(i, width) def set_hist_check(hist_checkbox): cur_exchange = self.config.get('use_exchange', "Blockchain") if cur_exchange == "CoinDesk" or cur_exchange == "Winkdex": hist_checkbox.setEnabled(True) elif cur_exchange == "BitcoinVenezuela": hist_checkbox.setEnabled(True) else: hist_checkbox.setEnabled(False) def set_currencies(combo): current_currency = self.config.get('currency', "EUR") try: combo.clear() except Exception: return combo.addItems(self.currencies) try: index = self.currencies.index(current_currency) except Exception: index = 0 combo.setCurrentIndex(index) def set_exchanges(combo_ex): try: combo_ex.clear() except Exception: return combo_ex.addItems(self.exchanges) try: index = self.exchanges.index( self.config.get('use_exchange', "Blockchain")) except Exception: index = 0 combo_ex.setCurrentIndex(index) def ok_clicked(): d.accept() set_exchanges(combo_ex) set_currencies(combo) set_hist_check(hist_checkbox) combo.currentIndexChanged.connect(on_change) combo_ex.currentIndexChanged.connect(on_change_ex) hist_checkbox.stateChanged.connect(on_change_hist) combo.connect(self.win, SIGNAL('refresh_currencies_combo()'), lambda: set_currencies(combo)) combo_ex.connect(d, SIGNAL('refresh_exchanges_combo()'), lambda: set_exchanges(combo_ex)) ok_button.clicked.connect(lambda: ok_clicked()) layout.addWidget(combo, 1, 1) layout.addWidget(combo_ex, 0, 1) layout.addWidget(hist_checkbox, 2, 1) layout.addWidget(ok_button, 3, 1) if d.exec_(): return True else: return False
from electrum.util import print_error, is_verbose, bfh, bh2u, versiontuple try: import hid from btchip.btchipComm import HIDDongleHIDAPI, DongleWait from btchip.btchip import btchip from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script from btchip.bitcoinTransaction import bitcoinTransaction from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware from btchip.btchipException import BTChipException BTCHIP = True BTCHIP_DEBUG = is_verbose except ImportError: BTCHIP = False MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \ ' https://www.ledgerwallet.com' MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcoin" app) too old for Segwit support. Please update at') + \ ' https://www.ledgerwallet.com' MULTI_OUTPUT_SUPPORT = '1.1.4' SEGWIT_SUPPORT = '1.1.10' SEGWIT_SUPPORT_SPECIAL = '1.0.4' class Ledger_Client(): def __init__(self, hidDevice): self.dongleObject = btchip(hidDevice) self.preflightDone = False def is_pairable(self): return True
def decrypt_message(self, sequence, message, password): raise UserFacingException( _('Encryption and decryption are not implemented by {}').format( self.device))
def create_menu(self, position): selected = self.get_selected_outpoints() if not selected: return menu = QMenu() menu.setSeparatorsCollapsible( True) # consecutive separators are merged together coins = [self.utxo_dict[name] for name in selected] menu.addAction(_("Spend"), lambda: self.parent.spend_coins(coins)) assert len(coins) >= 1, len(coins) if len(coins) == 1: utxo_dict = coins[0] addr = utxo_dict['address'] txid = utxo_dict['prevout_hash'] # "Details" tx = self.wallet.db.get_transaction(txid) if tx: label = self.wallet.get_label( txid ) or None # Prefer None if empty (None hides the Description: field in the window) menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx, label)) # "Copy ..." idx = self.indexAt(position) if not idx.isValid(): return col = idx.column() column_title = self.model().horizontalHeaderItem(col).text() copy_text = self.model().itemFromIndex( idx).text() if col != self.Columns.OUTPOINT else selected[0] if col == self.Columns.AMOUNT: copy_text = copy_text.strip() menu.addAction( _("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(copy_text)) # "Freeze coin" if not self.wallet.is_frozen_coin(utxo_dict): menu.addAction( _("Freeze Coin"), lambda: self.parent. set_frozen_state_of_coins([utxo_dict], True)) else: menu.addSeparator() menu.addAction(_("Coin is frozen"), lambda: None).setEnabled(False) menu.addAction( _("Unfreeze Coin"), lambda: self.parent. set_frozen_state_of_coins([utxo_dict], False)) menu.addSeparator() # "Freeze address" if not self.wallet.is_frozen_address(addr): menu.addAction( _("Freeze Address"), lambda: self.parent. set_frozen_state_of_addresses([addr], True)) else: menu.addSeparator() menu.addAction(_("Address is frozen"), lambda: None).setEnabled(False) menu.addAction( _("Unfreeze Address"), lambda: self.parent. set_frozen_state_of_addresses([addr], False)) menu.addSeparator() else: # multiple items selected menu.addSeparator() addrs = [utxo_dict['address'] for utxo_dict in coins] is_coin_frozen = [ self.wallet.is_frozen_coin(utxo_dict) for utxo_dict in coins ] is_addr_frozen = [ self.wallet.is_frozen_address(utxo_dict['address']) for utxo_dict in coins ] if not all(is_coin_frozen): menu.addAction( _("Freeze Coins"), lambda: self.parent.set_frozen_state_of_coins(coins, True)) if any(is_coin_frozen): menu.addAction( _("Unfreeze Coins"), lambda: self.parent.set_frozen_state_of_coins( coins, False)) if not all(is_addr_frozen): menu.addAction( _("Freeze Addresses"), lambda: self.parent.set_frozen_state_of_addresses( addrs, True)) if any(is_addr_frozen): menu.addAction( _("Unfreeze Addresses"), lambda: self.parent.set_frozen_state_of_addresses( addrs, False)) menu.exec_(self.viewport().mapToGlobal(position))
def decrypt_message(self, pubkey, message, password): raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
def receive_list_menu(self, menu, addr): window = get_parent_main_window(menu) menu.addAction(_("Send via e-mail"), lambda: self.send(window, addr))
class UTXOList(MyTreeView): class Columns(IntEnum): OUTPOINT = 0 ADDRESS = 1 LABEL = 2 AMOUNT = 3 HEIGHT = 4 headers = { Columns.ADDRESS: _('Address'), Columns.LABEL: _('Label'), Columns.AMOUNT: _('Amount'), Columns.HEIGHT: _('Height'), Columns.OUTPOINT: _('Output point'), } filter_columns = [Columns.ADDRESS, Columns.LABEL] def __init__(self, parent=None): super().__init__(parent, self.create_menu, stretch_column=self.Columns.LABEL, editable_columns=[]) self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) self.update() def update(self): self.wallet = self.parent.wallet utxos = self.wallet.get_utxos() self.utxo_dict = {} self.model().clear() self.update_headers(self.__class__.headers) for idx, x in enumerate(utxos): self.insert_utxo(idx, x) self.filter() def insert_utxo(self, idx, x): address = x['address'] height = x.get('height') name = x.get('prevout_hash') + ":%d" % x.get('prevout_n') name_short = x.get( 'prevout_hash')[:16] + '...' + ":%d" % x.get('prevout_n') self.utxo_dict[name] = x label = self.wallet.get_label(x.get('prevout_hash')) amount = self.parent.format_amount(x['value'], whitespaces=True) labels = [name_short, address, label, amount, '%d' % height] utxo_item = [QStandardItem(x) for x in labels] self.set_editability(utxo_item) utxo_item[self.Columns.ADDRESS].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT)) utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole) if self.wallet.is_frozen_address(address): utxo_item[self.Columns.ADDRESS].setBackground( ColorScheme.BLUE.as_color(True)) utxo_item[self.Columns.ADDRESS].setToolTip(_('Address is frozen')) if self.wallet.is_frozen_coin(x): utxo_item[self.Columns.OUTPOINT].setBackground( ColorScheme.BLUE.as_color(True)) utxo_item[self.Columns.OUTPOINT].setToolTip( f"{name}\n{_('Coin is frozen')}") else: utxo_item[self.Columns.OUTPOINT].setToolTip(name) self.model().insertRow(idx, utxo_item) def get_selected_outpoints(self) -> Optional[List[str]]: if not self.model(): return None items = self.selected_in_column(self.Columns.ADDRESS) if not items: return None return [x.data(Qt.UserRole) for x in items] def create_menu(self, position): selected = self.get_selected_outpoints() if not selected: return menu = QMenu() menu.setSeparatorsCollapsible( True) # consecutive separators are merged together coins = [self.utxo_dict[name] for name in selected] menu.addAction(_("Spend"), lambda: self.parent.spend_coins(coins)) assert len(coins) >= 1, len(coins) if len(coins) == 1: utxo_dict = coins[0] addr = utxo_dict['address'] txid = utxo_dict['prevout_hash'] # "Details" tx = self.wallet.db.get_transaction(txid) if tx: label = self.wallet.get_label( txid ) or None # Prefer None if empty (None hides the Description: field in the window) menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx, label)) # "Copy ..." idx = self.indexAt(position) if not idx.isValid(): return col = idx.column() column_title = self.model().horizontalHeaderItem(col).text() copy_text = self.model().itemFromIndex( idx).text() if col != self.Columns.OUTPOINT else selected[0] if col == self.Columns.AMOUNT: copy_text = copy_text.strip() menu.addAction( _("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(copy_text)) # "Freeze coin" if not self.wallet.is_frozen_coin(utxo_dict): menu.addAction( _("Freeze Coin"), lambda: self.parent. set_frozen_state_of_coins([utxo_dict], True)) else: menu.addSeparator() menu.addAction(_("Coin is frozen"), lambda: None).setEnabled(False) menu.addAction( _("Unfreeze Coin"), lambda: self.parent. set_frozen_state_of_coins([utxo_dict], False)) menu.addSeparator() # "Freeze address" if not self.wallet.is_frozen_address(addr): menu.addAction( _("Freeze Address"), lambda: self.parent. set_frozen_state_of_addresses([addr], True)) else: menu.addSeparator() menu.addAction(_("Address is frozen"), lambda: None).setEnabled(False) menu.addAction( _("Unfreeze Address"), lambda: self.parent. set_frozen_state_of_addresses([addr], False)) menu.addSeparator() else: # multiple items selected menu.addSeparator() addrs = [utxo_dict['address'] for utxo_dict in coins] is_coin_frozen = [ self.wallet.is_frozen_coin(utxo_dict) for utxo_dict in coins ] is_addr_frozen = [ self.wallet.is_frozen_address(utxo_dict['address']) for utxo_dict in coins ] if not all(is_coin_frozen): menu.addAction( _("Freeze Coins"), lambda: self.parent.set_frozen_state_of_coins(coins, True)) if any(is_coin_frozen): menu.addAction( _("Unfreeze Coins"), lambda: self.parent.set_frozen_state_of_coins( coins, False)) if not all(is_addr_frozen): menu.addAction( _("Freeze Addresses"), lambda: self.parent.set_frozen_state_of_addresses( addrs, True)) if any(is_addr_frozen): menu.addAction( _("Unfreeze Addresses"), lambda: self.parent.set_frozen_state_of_addresses( addrs, False)) menu.exec_(self.viewport().mapToGlobal(position))
def decrypt_message(self, sequence, message, password): raise RuntimeError( _('Encryption and decryption are not implemented by {}').format( self.device))
def verify_seed(self, seed, is_valid=None): while True: r = self.request_seed(MSG_VERIFY_SEED, is_valid) if prepare_seed(r) == prepare_seed(seed): return self.show_error(_('Incorrect seed'))
def get_data_for_role(self, index: QModelIndex, role: Qt.ItemDataRole) -> QVariant: # note: this method is performance-critical. # it is called a lot, and so must run extremely fast. assert index.isValid() col = index.column() window = self.model.parent tx_item = self.get_data() is_lightning = tx_item.get('lightning', False) timestamp = tx_item['timestamp'] if is_lightning: status = 0 if timestamp is None: status_str = 'unconfirmed' else: status_str = format_time(int(timestamp)) else: tx_hash = tx_item['txid'] conf = tx_item['confirmations'] try: status, status_str = self.model.tx_status_cache[tx_hash] except KeyError: tx_mined_info = self.model.tx_mined_info_from_tx_item(tx_item) status, status_str = window.wallet.get_tx_status( tx_hash, tx_mined_info) if role == ROLE_SORT_ORDER: d = { HistoryColumns.STATUS: # respect sort order of self.transactions (wallet.get_full_history) -index.row(), HistoryColumns.DESCRIPTION: tx_item['label'] if 'label' in tx_item else None, HistoryColumns.AMOUNT: (tx_item['bc_value'].value if 'bc_value' in tx_item else 0)\ + (tx_item['ln_value'].value if 'ln_value' in tx_item else 0), HistoryColumns.BALANCE: (tx_item['balance'].value if 'balance' in tx_item else 0), HistoryColumns.FIAT_VALUE: tx_item['fiat_value'].value if 'fiat_value' in tx_item else None, HistoryColumns.FIAT_ACQ_PRICE: tx_item['acquisition_price'].value if 'acquisition_price' in tx_item else None, HistoryColumns.FIAT_CAP_GAINS: tx_item['capital_gain'].value if 'capital_gain' in tx_item else None, HistoryColumns.TXID: tx_hash if not is_lightning else None, } return QVariant(d[col]) if role == MyTreeView.ROLE_EDIT_KEY: return QVariant(get_item_key(tx_item)) if role not in (Qt.DisplayRole, Qt.EditRole): if col == HistoryColumns.STATUS and role == Qt.DecorationRole: icon = "lightning" if is_lightning else TX_ICONS[status] return QVariant(read_QIcon(icon)) elif col == HistoryColumns.STATUS and role == Qt.ToolTipRole: if is_lightning: msg = 'lightning transaction' else: # on-chain if tx_item['height'] == TX_HEIGHT_LOCAL: # note: should we also explain double-spends? msg = _( "This transaction is only available on your local machine.\n" "The currently connected server does not know about it.\n" "You can either broadcast it now, or simply remove it." ) else: msg = str(conf) + _(" confirmation" + ("s" if conf != 1 else "")) return QVariant(msg) elif col > HistoryColumns.DESCRIPTION and role == Qt.TextAlignmentRole: return QVariant(int(Qt.AlignRight | Qt.AlignVCenter)) elif col > HistoryColumns.DESCRIPTION and role == Qt.FontRole: monospace_font = QFont(MONOSPACE_FONT) return QVariant(monospace_font) #elif col == HistoryColumns.DESCRIPTION and role == Qt.DecorationRole and not is_lightning\ # and self.parent.wallet.invoices.paid.get(tx_hash): # return QVariant(read_QIcon("seal")) elif col in (HistoryColumns.DESCRIPTION, HistoryColumns.AMOUNT) \ and role == Qt.ForegroundRole and tx_item['value'].value < 0: red_brush = QBrush(QColor("#BC1E1E")) return QVariant(red_brush) elif col == HistoryColumns.FIAT_VALUE and role == Qt.ForegroundRole \ and not tx_item.get('fiat_default') and tx_item.get('fiat_value') is not None: blue_brush = QBrush(QColor("#1E1EFF")) return QVariant(blue_brush) return QVariant() if col == HistoryColumns.STATUS: return QVariant(status_str) elif col == HistoryColumns.DESCRIPTION and 'label' in tx_item: return QVariant(tx_item['label']) elif col == HistoryColumns.AMOUNT: bc_value = tx_item['bc_value'].value if 'bc_value' in tx_item else 0 ln_value = tx_item['ln_value'].value if 'ln_value' in tx_item else 0 value = bc_value + ln_value v_str = window.format_amount(value, is_diff=True, whitespaces=True) return QVariant(v_str) elif col == HistoryColumns.BALANCE: balance = tx_item['balance'].value balance_str = window.format_amount(balance, whitespaces=True) return QVariant(balance_str) elif col == HistoryColumns.FIAT_VALUE and 'fiat_value' in tx_item: value_str = window.fx.format_fiat(tx_item['fiat_value'].value) return QVariant(value_str) elif col == HistoryColumns.FIAT_ACQ_PRICE and \ tx_item['value'].value < 0 and 'acquisition_price' in tx_item: # fixme: should use is_mine acq = tx_item['acquisition_price'].value return QVariant(window.fx.format_fiat(acq)) elif col == HistoryColumns.FIAT_CAP_GAINS and 'capital_gain' in tx_item: cg = tx_item['capital_gain'].value return QVariant(window.fx.format_fiat(cg)) elif col == HistoryColumns.TXID: return QVariant(tx_hash) if not is_lightning else QVariant('') return QVariant()
def description(self): return _("Send and receive payment requests via email")
def onclick(self): custom_message_box(icon=QMessageBox.Information, parent=self, title=_('Info'), text=self.help_text, rich_text=True)
def create_menu(self, position: QPoint): org_idx: QModelIndex = self.indexAt(position) idx = self.proxy.mapToSource(org_idx) if not idx.isValid(): # can happen e.g. before list is populated for the first time return tx_item = idx.internalPointer().get_data() if tx_item.get('lightning') and tx_item['type'] == 'payment': menu = QMenu() menu.addAction( _("View Payment"), lambda: self.parent.show_lightning_transaction(tx_item)) cc = self.add_copy_menu(menu, idx) cc.addAction( _("Payment Hash"), lambda: self.place_text_on_clipboard(tx_item['payment_hash'], title="Payment Hash")) cc.addAction( _("Preimage"), lambda: self.place_text_on_clipboard(tx_item['preimage'], title="Preimage")) key = tx_item['payment_hash'] log = self.wallet.lnworker.logs.get(key) if log: menu.addAction( _("View log"), lambda: self.parent.invoice_list.show_log(key, log)) menu.exec_(self.viewport().mapToGlobal(position)) return tx_hash = tx_item['txid'] if tx_item.get('lightning'): tx = self.wallet.adb.get_transaction(tx_hash) else: tx = self.wallet.db.get_transaction(tx_hash) if not tx: return tx_URL = block_explorer_URL(self.config, 'tx', tx_hash) tx_details = self.wallet.get_tx_info(tx) is_unconfirmed = tx_details.tx_mined_status.height <= 0 menu = QMenu() if tx_details.can_remove: menu.addAction(_("Remove"), lambda: self.remove_local_tx(tx_hash)) cc = self.add_copy_menu(menu, idx) cc.addAction( _("Transaction ID"), lambda: self.place_text_on_clipboard(tx_hash, title="TXID")) for c in self.editable_columns: if self.isColumnHidden(c): continue label = self.hm.headerData(c, Qt.Horizontal, Qt.DisplayRole) # TODO use siblingAtColumn when min Qt version is >=5.11 persistent = QPersistentModelIndex( org_idx.sibling(org_idx.row(), c)) menu.addAction(_("Edit {}").format(label), lambda p=persistent: self.edit(QModelIndex(p))) menu.addAction(_("View Transaction"), lambda: self.show_transaction(tx_item, tx)) channel_id = tx_item.get('channel_id') if channel_id: menu.addAction( _("View Channel"), lambda: self.parent.show_channel(bytes.fromhex(channel_id))) if is_unconfirmed and tx: if tx_details.can_bump: menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx)) else: if tx_details.can_cpfp: menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp_dialog(tx)) if tx_details.can_dscancel: menu.addAction(_("Cancel (double-spend)"), lambda: self.parent.dscancel_dialog(tx)) invoices = self.wallet.get_relevant_invoices_for_tx(tx) if len(invoices) == 1: menu.addAction( _("View invoice"), lambda inv=invoices[0]: self.parent.show_onchain_invoice(inv)) elif len(invoices) > 1: menu_invs = menu.addMenu(_("Related invoices")) for inv in invoices: menu_invs.addAction( _("View invoice"), lambda inv=inv: self.parent.show_onchain_invoice(inv)) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webopen(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position))
class GuiMixin(object): # Requires: self.proto, self.device messages = { 3: _("Confirm the transaction output on your {} device"), 4: _("Confirm internal entropy on your {} device to begin"), 5: _("Write down the seed word shown on your {}"), 6: _("Confirm on your {} that you want to wipe it clean"), 7: _("Confirm on your {} device the message to sign"), 8: _("Confirm the total amount spent and the transaction fee on your " "{} device"), 10: _("Confirm wallet address on your {} device"), 'default': _("Check your {} device to continue"), } def callback_Failure(self, msg): # BaseClient's unfortunate call() implementation forces us to # raise exceptions on failure in order to unwind the stack. # However, making the user acknowledge they cancelled # gets old very quickly, so we suppress those. The NotInitialized # one is misnamed and indicates a passphrase request was cancelled. if msg.code in (self.types.FailureType.PinCancelled, self.types.FailureType.ActionCancelled, self.types.FailureType.NotInitialized): raise UserCancelled() raise RuntimeError(msg.message) def callback_ButtonRequest(self, msg): message = self.msg if not message: message = self.messages.get(msg.code, self.messages['default']) self.handler.show_message(message.format(self.device), self.cancel) return self.proto.ButtonAck() def callback_PinMatrixRequest(self, msg): if msg.type == 2: msg = _("Enter a new PIN for your {}:") elif msg.type == 3: msg = (_("Re-enter the new PIN for your {}.\n\n" "NOTE: the positions of the numbers have changed!")) else: msg = _("Enter your current {} PIN:") pin = self.handler.get_pin(msg.format(self.device)) if len(pin) > 9: self.handler.show_error( _('The PIN cannot be longer than 9 characters.')) pin = '' # to cancel below if not pin: return self.proto.Cancel() return self.proto.PinMatrixAck(pin=pin) def callback_PassphraseRequest(self, req): if req and hasattr(req, 'on_device') and req.on_device is True: return self.proto.PassphraseAck() if self.creating_wallet: msg = _("Enter a passphrase to generate this wallet. Each time " "you use this wallet your {} will prompt you for the " "passphrase. If you forget the passphrase you cannot " "access the bitcoins in the wallet.").format(self.device) else: msg = _("Enter the passphrase to unlock this wallet:") passphrase = self.handler.get_passphrase(msg, self.creating_wallet) if passphrase is None: return self.proto.Cancel() passphrase = bip39_normalize_passphrase(passphrase) return self.proto.PassphraseAck(passphrase=passphrase) def callback_PassphraseStateRequest(self, msg): return self.proto.PassphraseStateAck() def callback_WordRequest(self, msg): self.step += 1 msg = _("Step {}/24. Enter seed word as explained on " "your {}:").format(self.step, self.device) word = self.handler.get_word(msg) # Unfortunately the device can't handle self.proto.Cancel() return self.proto.WordAck(word=word) def callback_CharacterRequest(self, msg): char_info = self.handler.get_char(msg) if not char_info: return self.proto.Cancel() return self.proto.CharacterAck(**char_info)
def format_date(self, d): return str(datetime.date(d.year, d.month, d.day)) if d else _('None')
def __init__(self, parent): super(MatrixDialog, self).__init__(parent) self.setWindowTitle(_("Trezor Matrix Recovery")) self.num = 9 self.loop = QEventLoop() vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(MATRIX_RECOVERY)) grid = QGridLayout() grid.setSpacing(0) self.char_buttons = [] for y in range(3): for x in range(3): button = QPushButton('?') button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x)) grid.addWidget(button, 3 - y, x) self.char_buttons.append(button) vbox.addLayout(grid) self.backspace_button = QPushButton("<=") self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace)) self.cancel_button = QPushButton(_("Cancel")) self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape)) buttons = Buttons(self.backspace_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show()