def on_search(): ok.setDisabled(ok_disables) name = acct.text().strip() tup = wallet.cashacct.parse_string(name) if tup: ca_msg( _("Searching for <b>{cash_account_name}</b> please wait ..."). format(cash_account_name=name), True) results = None exc = [] t0 = time.time() def resolve_verify(): nonlocal results results = wallet.cashacct.resolve_verify(name, exc=exc) code = VerifyingDialog( parent.top_level_window(), _("Verifying Cash Account {name} please wait ...").format( name=name), resolve_verify, auto_show=False).exec_() if code == QDialog.Rejected: # user cancel -- the waiting dialog thread will continue to run in the background but that's ok.. it will be a no-op d.reject() return if results: ca.setItems(results, auto_resize_parent=False, title='', button_type=button_type) # suppress groupbox title else: ca_msg( _("The specified Cash Account does not appear to be associated with any address" ), True) if time.time() - t0 >= cashacct.timeout: if (wallet.verifier and wallet.synchronizer and # check these are still alive: these could potentially go away from under us if wallet is stopped when we get here. (not wallet.verifier.is_up_to_date() or not wallet.synchronizer.is_up_to_date())): parent.show_message( _("No results found. However, your wallet is busy updating." " This can interfere with Cash Account lookups." " You may want to try again when it is done.")) else: parent.show_message( _("A network timeout occurred while looking up this Cash Account. " "You may want to check that your internet connection is up and " "not saturated processing other requests.")) elif exc and isinstance(exc[-1], requests.ConnectionError): parent.show_error( _("A network connectivity error occured. Please check your internet connection and try again." )) nres = len(results or []) title = "<b>" + name + "</b> - " + ngettext( "{number} Cash Account", "{number} Cash Accounts", nres).format(number=nres) tit_lbl.setText(title) else: ca_msg(_("Invalid Cash Account name, please try again"), True)
def do_freeze_unfreeze(self): coins = getattr(self.freeze_button, "_coins", []) op = getattr(self.freeze_button, "_op", None) if not coins or op is None: return freeze = op == self.FreezeOp.Freeze # Freeze / Unfreeze self.wallet.set_frozen_coin_state(coins, freeze) delattr(self.freeze_button, "_coins") delattr(self.freeze_button, "_op") self.update() self.main_window.update_tabs() if freeze: # Freeze op success message self.show_message(ngettext("{count} coin has been frozen.", "{count} coins have been frozen.", len(coins)).format(count=len(coins)) + "\n" + _("Check the Coins tab to unfreeze.")) else: # Unfreeze op success message self.show_message(ngettext("{count} coin has been unfrozen.", "{count} coins have been unfrozen.", len(coins)).format(count=len(coins)))
def verify_multiple_blocks(blocks: List[int], parent: MessageBoxMixin, wallet: Abstract_Wallet, timeout=10.0) -> int: ''' Pass a list of blocks and will attempt to verify them all in 1 pass. This is used by the Contacts tab to verify unverified Cash Accounts that may have been imported. Returns the number of successfully verified blocks or None on user cancel. ''' if not len(blocks): return 0 blocks = set(blocks) nblocks = len(blocks) q = queue.Queue() def done_cb(thing): if isinstance(thing, cashacct.ProcessedBlock) and thing.reg_txs: q.put(thing) else: q.put(None) ctr = 0 def thread_func(): nonlocal ctr for number in blocks: wallet.cashacct.verify_block_asynch(number, success_cb=done_cb, error_cb=done_cb, timeout=timeout) errs = 0 while ctr + errs < nblocks: try: thing = q.get(timeout=timeout) if thing is None: errs += 1 else: ctr += 1 except queue.Empty: return code = VerifyingDialog( parent.top_level_window(), ngettext("Verifying {count} block please wait ...", "Verifying {count} blocks please wait ...", nblocks).format(count=nblocks), thread_func, auto_show=False, on_error=lambda e: parent.show_error(str(e))).exec_() if code != QDialog.Accepted: return None return ctr
def validate_op_return_output_and_get_data( output: tuple, # tuple(typ, 'address', amount) max_size: int = 220, # in bytes max_pushes: int = 1 # number of pushes supported after the OP_RETURN, most HW wallets support only 1 push, some more than 1. Specify None to omit the number-of-pushes check. ) -> bytes: # will return address.script[2:] (everyting after the first OP_RETURN & PUSH bytes) _type, address, _amount = output if max_pushes is None: # Caller says "no limit", so just to keep the below code simple, we # do this and effectively sets the limit on pushes to "unlimited", # since there can never be more pushes than bytes in the payload! max_pushes = max_size assert max_pushes >= 1 if _type != TYPE_SCRIPT: raise Exception("Unexpected output type: {}".format(_type)) ops = Script.get_ops(address.script) num_pushes = len(ops) - 1 if len(ops) < 1 or ops[0][0] != OpCodes.OP_RETURN: raise RuntimeError(_("Only OP_RETURN scripts are supported.")) if num_pushes < 1 or num_pushes > max_pushes or any( ops[i + 1][1] is None for i in range(num_pushes)): raise RuntimeError( ngettext("OP_RETURN is limited to {max_pushes} data push.", "OP_RETURN is limited to {max_pushes} data pushes.", max_pushes).format(max_pushes=max_pushes)) data = address.script[ 2:] # caller expects everything after the OP_RETURN and PUSHDATA op if len(data) > max_size: raise RuntimeError( _("OP_RETURN data size exceeds the maximum of {} bytes.".format( max_size))) if _amount != 0: raise RuntimeError(_("Amount for OP_RETURN output must be zero.")) return data
def setItems( self, items: List[Tuple[ cashacct.Info, str, str]], # list of 2 or 3 tuple : Info, minimal_chash[, formatted_string] title=None, auto_resize_parent=True, sort=True, button_type: ButtonType = ButtonType.Radio): items = items or [] nitems = len(items) title = ngettext("{number} Cash Account", "{number} Cash Accounts", nitems).format( number=nitems) if title is None else title wallet = self.wallet if items and (sort or len(items[0]) != 3): # sort items by formatted cash account string, also adding the string to # the items tuples; tuples now are modified to 3 elements: # (info, min_chash, formatted_ca_string) formatter = lambda x: (x[0], x[1], wallet.cashacct.fmt_info(x[0], x[1])) if sort: items = sorted((formatter(x) for x in items), key=lambda tup: tup[2]) else: items = [formatter(x) for x in items] self._items = items self.button_type = button_type self.setTitle(title) self.refresh() if auto_resize_parent and self.parent(): weakParent = util.Weak.ref(self.parent()) QTimer.singleShot( 0, lambda: weakParent() and weakParent().resize(weakParent(). sizeHint()))
def add_io(self, vbox): if self.tx.locktime > 0: lbl = QLabel(_("LockTime: {lock_time}").format(lock_time=self.tx.locktime)) lbl.setTextInteractionFlags(lbl.textInteractionFlags() | Qt.TextSelectableByMouse) vbox.addWidget(lbl) hbox = QHBoxLayout() hbox.setContentsMargins(0,12,0,0) self.i_text = i_text = TextBrowserKeyboardFocusFilter() num_inputs = len(self.tx.inputs()) inputs_lbl_text = ngettext("&Input", "&Inputs ({num_inputs})", num_inputs).format(num_inputs=num_inputs) l = QLabel(inputs_lbl_text) l.setBuddy(i_text) hbox.addWidget(l) hbox.addSpacerItem(QSpacerItem(20, 0)) # 20 px padding self.dl_input_chk = chk = QCheckBox(_("&Download input data")) chk.setChecked(self.is_fetch_input_data()) chk.clicked.connect(self.set_fetch_input_data) chk.setToolTip(_("If this is checked, accurate fee and input value data will be retrieved from the network")) hbox.addWidget(chk) hbox.addStretch(1) if not self.wallet.network: # it makes no sense to enable this checkbox if the network is offline chk.setHidden(True) self.schnorr_label = QLabel(_('{} = Schnorr signed').format(SCHNORR_SIGIL)) self.schnorr_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight) f = self.schnorr_label.font() f.setPointSize(f.pointSize()-1) # make it a little smaller self.schnorr_label.setFont(f) hbox.addWidget(self.schnorr_label) self.schnorr_label.setHidden(True) vbox.addLayout(hbox) i_text.setOpenLinks(False) # disable automatic link opening i_text.anchorClicked.connect(self._open_internal_link) # send links to our handler self.i_text_has_selection = False def set_i_text_has_selection(b): self.i_text_has_selection = bool(b) i_text.copyAvailable.connect(set_i_text_has_selection) i_text.setContextMenuPolicy(Qt.CustomContextMenu) i_text.customContextMenuRequested.connect(self.on_context_menu_for_inputs) i_text.setFont(QFont(MONOSPACE_FONT)) i_text.setReadOnly(True) i_text.setTextInteractionFlags(i_text.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) vbox.addWidget(i_text) hbox = QHBoxLayout() hbox.setContentsMargins(0,0,0,0) vbox.addLayout(hbox) self.o_text = o_text = TextBrowserKeyboardFocusFilter() num_outputs = len(self.tx.outputs()) outputs_lbl_text = ngettext("&Output", "&Outputs ({num_outputs})", num_outputs).format(num_outputs=num_outputs) l = QLabel(outputs_lbl_text) l.setBuddy(o_text) hbox.addWidget(l) box_char = "█" self.recv_legend = QLabel("<font color=" + ColorScheme.GREEN.as_color(background=True).name() + ">" + box_char + "</font> = " + _("Receiving Address")) self.change_legend = QLabel("<font color=" + ColorScheme.YELLOW.as_color(background=True).name() + ">" + box_char + "</font> = " + _("Change Address")) f = self.recv_legend.font(); f.setPointSize(f.pointSize()-1) self.recv_legend.setFont(f) self.change_legend.setFont(f) hbox.addStretch(2) hbox.addWidget(self.recv_legend) hbox.addWidget(self.change_legend) self.recv_legend.setHidden(True) self.change_legend.setHidden(True) o_text.setOpenLinks(False) # disable automatic link opening o_text.anchorClicked.connect(self._open_internal_link) # send links to our handler self.o_text_has_selection = False def set_o_text_has_selection(b): self.o_text_has_selection = bool(b) o_text.copyAvailable.connect(set_o_text_has_selection) o_text.setContextMenuPolicy(Qt.CustomContextMenu) o_text.customContextMenuRequested.connect(self.on_context_menu_for_outputs) o_text.setFont(QFont(MONOSPACE_FONT)) o_text.setReadOnly(True) o_text.setTextInteractionFlags(o_text.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) vbox.addWidget(o_text) self.cashaddr_signal_slots.append(self.update_io) self.main_window.gui_object.cashaddr_toggled_signal.connect(self.update_io) self.update_io()
def _make_freeze_button_text(cls, op: FreezeOp = FreezeOp.Freeze, num_coins: int = 0) -> str: if op == cls.FreezeOp.Freeze: return ngettext("&Freeze Coin", "&Freeze Coins", num_coins) elif op == cls.FreezeOp.Unfreeze: return ngettext("&Unfreeze Coin", "&Unfreeze Coins", num_coins) raise ValueError(f"Invalid op: {op!r}")
def create_menu(self, position): menu = QMenu() selected = self.selectedItems() i2c = self._i2c ca_unverified = self._get_ca_unverified(include_temp=False) if selected: names = [item.text(1) for item in selected] keys = [i2c(item) for item in selected] payable_keys = [k for k in keys if k.type != 'cashacct_T'] deletable_keys = [k for k in keys if k.type in contact_types] needs_verif_keys = [k for k in keys if k in ca_unverified] column = self.currentColumn() column_title = self.headerItem().text(column) column_data = '\n'.join([item.text(column) for item in selected]) item = self.currentItem() typ = i2c(item).type if item else 'unknown' ca_info = None if item and typ in ('cashacct', 'cashacct_W'): ca_info = self.wallet.cashacct.get_verified(i2c(item).name) if column == 1 and len(selected) == 1: # hack .. for Cash Accounts just say "Copy Cash Account" column_title = _('Cash Account') if ca_info: column_data = self.wallet.cashacct.fmt_info(ca_info, emoji=True) if len(selected) > 1: column_title += f" ({len(selected)})" menu.addAction( _("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data)) if item and column in self.editable_columns and self.on_permit_edit( item, column): key = item.data(0, self.DataRoles.Contact) # this key & find_item business is so we don't hold a reference # to the ephemeral item, which may be deleted while the # context menu is up. Accessing the item after on_update runs # means the item is deleted and you get a C++ object deleted # runtime error. menu.addAction( _("Edit {}").format(column_title), lambda: self._on_edit_item(key, column)) a = menu.addAction( _("Pay to"), lambda: self.parent.payto_contacts(payable_keys)) if needs_verif_keys or not payable_keys: a.setDisabled(True) a = menu.addAction( _("Delete"), lambda: self.parent.delete_contacts(deletable_keys)) if not deletable_keys: a.setDisabled(True) # Add sign/verify and encrypt/decrypt menu - but only if just 1 thing selected if len(keys) == 1 and Address.is_valid(keys[0].address): signAddr = Address.from_string(keys[0].address) a = menu.addAction( _("Sign/verify message") + "...", lambda: self.parent.sign_verify_message(signAddr)) if signAddr.kind != Address.ADDR_P2PKH: a.setDisabled( True ) # We only allow this for P2PKH since it makes no sense for P2SH (ambiguous public key) URLs = [ web.BE_URL(self.config, 'addr', Address.from_string(key.address)) for key in keys if Address.is_valid(key.address) ] a = menu.addAction(_("View on block explorer"), lambda: [URL and webopen(URL) for URL in URLs]) if not any(URLs): a.setDisabled(True) if ca_info: menu.addAction( _("View registration tx..."), lambda: self.parent.do_process_from_txid( txid=ca_info.txid, tx_desc=self.wallet.get_label(ca_info.txid))) if typ in ('cashacct_W', 'cashacct'): _contact_d = i2c(item) menu.addAction( _("Details..."), lambda: cashacctqt.cash_account_detail_dialog( self.parent, _contact_d.name)) menu.addSeparator() menu.addAction(self.icon_cashacct, _("Add Contact") + " - " + _("Cash Account"), self.new_cash_account_contact_dialog) menu.addAction(self.icon_contacts, _("Add Contact") + " - " + _("Address"), self.parent.new_contact_dialog) menu.addSeparator() menu.addAction(self.icon_cashacct, _("Register Cash Account..."), self.parent.register_new_cash_account) menu.addSeparator() menu.addAction( QIcon(":icons/import.svg" if not ColorScheme.dark_scheme else ":icons/import_dark_theme.svg"), _("Import file"), self.import_contacts) if not self.parent.contacts.empty: menu.addAction( QIcon(":icons/save.svg" if not ColorScheme.dark_scheme else ":icons/save_dark_theme.svg"), _("Export file"), self.export_contacts) menu.addSeparator() a = menu.addAction(_("Show My Cash Accounts"), self.toggle_show_my_cashaccts) a.setCheckable(True) a.setChecked(self.show_my_cashaccts) if ca_unverified: def kick_off_verify(): bnums = set() for contact in ca_unverified: tup = self.wallet.cashacct.parse_string(contact.name) if not tup: continue bnums.add(tup[1]) # number ret = cashacctqt.verify_multiple_blocks( bnums, self.parent, self.wallet) if ret is None: # user cancel return verified = ca_unverified - self._get_ca_unverified() if not verified: self.parent.show_error( _("Cash Account verification failure")) menu.addSeparator() num = len(ca_unverified) a = menu.addAction( self.icon_unverif, ngettext("Verify {count} Cash Account", "Verify {count} Cash Accounts", num).format(count=num), kick_off_verify) if not self.wallet.network: a.setDisabled(True) run_hook('create_contact_menu', menu, selected) menu.exec_(self.viewport().mapToGlobal(position))