def invoke(firstarg='0.0.0.0', sport='8787', upnp_str=None, addr_str=None): bindhost, *extrahosts = firstarg.split(',') if len(extrahosts) > 1: raise Exception("too many hosts") elif len(extrahosts) == 1: [ announcehost, ] = extrahosts else: announcehost = None port = int(sport) pnp = get_upnp() if upnp_str == 'upnp' else None if not pnp and not addr_str: # third arg may be addr_str, so swap the args addr_str = upnp_str upnp_str = None addr = None if addr_str: assert Address.is_valid( addr_str), "Invalid donation address specified" addr = Address.from_string(addr_str) return self.start_fusion_server(network, bindhost, port, upnp=pnp, announcehost=announcehost, donation_address=addr)
def donation_address(self, window) -> Optional[Tuple[str, Address]]: ''' Plugin API: Returns a tuple of (description, Address) or None. This is the donation address that we as a client got from the remote server (as opposed to the donation address we announce if we are a server). ''' if self.remote_donation_address and Address.is_valid( self.remote_donation_address): return (self.fullname() + " " + _("Server") + ": " + self.get_server()[0], Address.from_string(self.remote_donation_address))
def delete_history(self, address: Address) -> None: if isinstance(address, str) and Address.is_valid(address): address = Address.from_string(address) storage = self.parent.wallet.storage if self.parent.wallet else None if storage: if True: #with self.lock: addrstr = address.to_storage_string() k = 'contact_history_%s' % (addrstr) storage.put(k, None) self.print_error("Deleted %s" % addrstr)
def _open_internal_link(self, target): ''' accepts either a str txid, str address, or a QUrl which should be of the bare form "txid" and/or "address" -- used by the clickable links in the inputs/outputs QTextBrowsers''' if isinstance(target, QUrl): target = target.toString(QUrl.None_) assert target if Address.is_valid(target): # target was an address, open address dialog self.main_window.show_address(Address.from_string(target), parent=self) else: # target was a txid, open new tx dialog self.main_window.do_process_from_txid(txid=target, parent=self)
def get_history(self, address: Address) -> list: ret = list() if isinstance(address, str) and Address.is_valid(address): address = Address.from_string(address) storage = self.parent.wallet.storage if self.parent.wallet else None if storage: addrstr = address.to_storage_string() k = 'contact_history_%s' % (addrstr) hdict = storage.get(k) if hdict: ret = list(hdict.values()) ret.sort(key=lambda x: x[1], reverse=True) return ret
def doReloadForKey(self, key: Any) -> Any: t0 = time.time() hist = list() unk = False duped = '' if isinstance(key, (type(None), list)): # the common case, 'None' or [Address] hist = get_history(domain=key) # contacts entires elif isinstance(key, contacts.ContactsEntry): hist = get_contact_history(key.address) elif isinstance(key, Address): # support for list-less single Address.. call self again with the proper format hist = self.get([key]) duped = ' (duped) ' elif isinstance(key, str): # support for string addresses or tx_hashes.. detect which and act accordingly if Address.is_valid(key): hist = self.get( [Address.from_string(key)] ) # recursively call self with propery list data type, which will end up calling get_history (it's ok -- this is to cache results uniformly!) duped = ' (duped) ' elif gui.ElectrumGui.gui.wallet and gui.ElectrumGui.gui.wallet.transactions.get( key, None): fullHist = self.get( None ) # recursively call self to get a full history (will be cached so it's ok!) try: hentry = fullHist.get_by_txid(key) hist.append(hentry) except KeyError: pass else: unk = True else: unk = True dstr = str(key) if not isinstance( key, contacts.ContactsEntry ) else '[ContactsEntry: ' + key.address_str + ']' if unk: utils.NSLog( "HistoryMgr: failed to retrieve any data for unknown domain=%s, returning empty list", dstr[:80]) else: time_taken = time.time() - t0 utils.NSLog( "HistoryMgr: refresh %d entries for domain=%s in %f ms%s (hist result type=%s)", len(hist), dstr[:80], time_taken * 1e3, duped, ''.join(list(str(type(hist)))[-19:-2])) gui.ElectrumGui.gui.refresh_cost('history', time_taken) return hist
def make_unsigned_transaction(self, amount, fee, all_inputs, outputs, changes): ''' make unsigned transaction ''' dust = self.dust_threshold( ) # always 546 for now, but this call is here in case something more sophisticated happens in the future coins = {} tx_inputs = [] amounts = {} try: for player in all_inputs: inputs_coins = self.get_coins(all_inputs[player]) # if there are no coins on input it terminates the process if inputs_coins: coins[player] = inputs_coins else: return None except BaseException as e: self.print_error('make_unsigned_transaction:', repr(e)) return None for player, pubkey_utxos in coins.items(): amounts[player] = 0 for pubkey, utxos in pubkey_utxos.items(): for utxo in utxos: utxo['type'] = 'p2pkh' utxo['address'] = Address.from_pubkey(pubkey) utxo['pubkeys'] = [pubkey] utxo['x_pubkeys'] = [pubkey] utxo['prevout_hash'] = utxo['tx_hash'] utxo['prevout_n'] = utxo['tx_pos'] utxo['signatures'] = [None] utxo['num_sig'] = 1 tx_inputs.append(utxo) amounts[player] += utxo['value'] tx_inputs.sort(key=lambda x: x['prevout_hash'] + str(x["tx_pos"])) tx_outputs = [(TYPE_ADDRESS, Address.from_string(output), int(amount)) for output in outputs] transaction = Transaction.from_io(tx_inputs, tx_outputs, sign_schnorr=False) tx_changes = [ (TYPE_ADDRESS, Address.from_string(changes[player]), int(amounts[player] - amount - fee)) for player in sorted(changes) if Address.is_valid(changes[player]) and int(amounts[player] - amount - fee) >= dust ] transaction.add_outputs(tx_changes) return transaction
def do_send(self): if not Address.is_valid(self.str_recipient): print(_('Invalid Bitcoin address')) return try: amount = int(PyDecimal(self.str_amount) * COIN) except Exception: print(_('Invalid Amount')) return try: fee = int(PyDecimal(self.str_fee) * COIN) except Exception: print(_('Invalid Fee')) return if self.wallet.has_password(): password = self.password_dialog() if not password: return else: password = None c = "" while c != "y": c = input("ok to send (y/n)?") if c == "n": return try: tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee) except Exception as e: print(str(e)) return if self.str_description: self.wallet.labels[tx.txid()] = self.str_description print(_("Please wait...")) status, msg = self.network.broadcast_transaction(tx) if status: print(_('Payment sent.')) else: print(_('Error'))
def open_link(link): if Address.is_valid(link): addr = Address.from_string(link) if wallet.is_mine(addr): parent.show_address(addr) else: addr_URL = web.BE_URL(parent.config, 'addr', addr) if addr_URL: webopen(addr_URL) return if link.startswith('http'): webopen(link) elif len(link) == 64: # 64 character txid tx = wallet.transactions.get(link) if tx: parent.show_transaction(tx, tx_desc=wallet.get_label(link)) else: parent.do_process_from_txid(txid=link, tx_desc=wallet.get_label(link)) return
def get_full_contacts(self, include_pseudo: bool = True) -> List[Contact]: ''' Returns all the contacts, with the "My CashAcct" pseudo-contacts clobbering dupes of the same type that were manually added. Client code should scan for type == 'cashacct' and type == 'cashacct_W' ''' if include_pseudo: # filter out cachaccts that are "Wallet", as they will be added # at the end as pseudo contacts if they also appear in real contacts real_contacts = [ contact for contact in self.parent.contacts.get_all(nocopy=True) if contact.type != 'cashacct' # accept anything that's not cashacct or not Address.is_valid( contact.address ) # or if it is, it can have invalid address as it's clearly 'not mine" or not self.wallet.is_mine( # or if it's not mine Address.from_string(contact.address)) ] return real_contacts + self._make_wallet_cashacct_pseudo_contacts() else: return self.parent.contacts.get_all(nocopy=True)
def do_send(self): if not Address.is_valid(self.str_recipient): self.show_message(_('Invalid Bitcoin address')) return try: amount = int(PyDecimal(self.str_amount) * COIN) except Exception: self.show_message(_('Invalid Amount')) return try: fee = int(PyDecimal(self.str_fee) * COIN) except Exception: self.show_message(_('Invalid Fee')) return if self.wallet.has_password(): password = self.password_dialog() if not password: return else: password = None try: tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee) except Exception as e: self.show_message(str(e)) return if self.str_description: self.wallet.labels[tx.txid()] = self.str_description self.show_message(_("Please wait..."), getchar=False) status, msg = self.network.broadcast_transaction(tx) if status: self.show_message(_('Payment sent.')) self.do_clear() #self.update_contacts_tab() else: self.show_message(_('Error'))
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))