class QERequestListModel(QEAbstractInvoiceListModel): def __init__(self, wallet, parent=None): super().__init__(wallet, parent) _logger = get_logger(__name__) def invoice_to_model(self, invoice: Invoice): item = super().invoice_to_model(invoice) item['type'] = 'request' item['key'] = invoice.get_id() if invoice.is_lightning() else invoice.get_address() return item def get_invoice_list(self): return self.wallet.get_unpaid_requests() def get_invoice_for_key(self, key: str): return self.wallet.get_request(key) def get_invoice_as_dict(self, invoice: Invoice): return self.wallet.export_request(invoice) @pyqtSlot(str, int) def updateRequest(self, key, status): self.updateInvoice(key, status)
class Plugin(BasePlugin): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) _logger = get_logger(__name__) @hook def init_qml(self, gui: 'ElectrumGui'): self._logger.debug('init_qml hook called')
class QEWalletListModel(QAbstractListModel): _logger = get_logger(__name__) def __init__(self, parent=None): QAbstractListModel.__init__(self, parent) self.wallets = [] # define listmodel rolemap _ROLE_NAMES = ('name', 'path', 'active') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict( zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) def rowCount(self, index): return len(self.wallets) def roleNames(self): return self._ROLE_MAP def data(self, index, role): (wallet_name, wallet_path, wallet) = self.wallets[index.row()] role_index = role - Qt.UserRole role_name = self._ROLE_NAMES[role_index] if role_name == 'name': return wallet_name if role_name == 'path': return wallet_path if role_name == 'active': return wallet != None def add_wallet(self, wallet_path=None, wallet: Abstract_Wallet = None): if wallet_path == None and wallet == None: return # only add wallet instance if instance not yet in model if wallet: for name, path, w in self.wallets: if w == wallet: return self.beginInsertRows(QModelIndex(), len(self.wallets), len(self.wallets)) if wallet == None: wallet_name = os.path.basename(wallet_path) else: wallet_name = wallet.basename() item = (wallet_name, wallet_path, wallet) self.wallets.append(item) self.endInsertRows()
def __init__(self, qedaemon): super().__init__() self.logger = get_logger(__name__) self._qedaemon = qedaemon # set up notification queue and notification_timer self.user_notification_queue = queue.Queue() self.user_notification_last_time = 0 self.notification_timer = QTimer(self) self.notification_timer.setSingleShot(False) self.notification_timer.setInterval(500) # msec self.notification_timer.timeout.connect(self.on_notification_timer) self._qedaemon.walletLoaded.connect(self.on_wallet_loaded) self.userNotify.connect(self.notifyAndroid)
class AuthMixin: _auth_logger = get_logger(__name__) authRequired = pyqtSignal([str], arguments=['method']) @pyqtSlot() def authProceed(self): self._auth_logger.debug('Proceeding with authed fn()') try: self._auth_logger.debug(str(getattr(self, '__auth_fcall'))) (func, args, kwargs, reject) = getattr(self, '__auth_fcall') r = func(self, *args, **kwargs) return r except Exception as e: self._auth_logger.error('Error executing wrapped fn(): %s' % repr(e)) raise e finally: delattr(self, '__auth_fcall') @pyqtSlot() def authCancel(self): self._auth_logger.debug('Cancelling authed fn()') if not hasattr(self, '__auth_fcall'): return try: (func, args, kwargs, reject) = getattr(self, '__auth_fcall') if reject is not None: if hasattr(self, reject): getattr(self, reject)() else: self._auth_logger.error( 'Reject method \'%s\' not defined' % reject) except Exception as e: self._auth_logger.error( 'Error executing reject function \'%s\': %s' % (reject, repr(e))) raise e finally: delattr(self, '__auth_fcall')
class QEInvoiceListModel(QEAbstractInvoiceListModel): def __init__(self, wallet, parent=None): super().__init__(wallet, parent) _logger = get_logger(__name__) def invoice_to_model(self, invoice: Invoice): item = super().invoice_to_model(invoice) item['type'] = 'invoice' item['key'] = invoice.get_id() return item def get_invoice_list(self): return self.wallet.get_unpaid_invoices() def get_invoice_for_key(self, key: str): return self.wallet.get_invoice(key) def get_invoice_as_dict(self, invoice: Invoice): return self.wallet.export_invoice(invoice)
class QEQRImageProvider(QQuickImageProvider): def __init__(self, max_size, parent=None): super().__init__(QQuickImageProvider.Image) self._max_size = max_size _logger = get_logger(__name__) @profiler def requestImage(self, qstr, size): self._logger.debug('QR requested for %s' % qstr) qr = qrcode.QRCode(version=1, border=2) qr.add_data(qstr) # calculate best box_size pixelsize = min(self._max_size, 400) modules = 17 + 4 * qr.best_fit() qr.box_size = math.floor(pixelsize / (modules + 2 * 2)) qr.make(fit=True) pimg = qr.make_image(fill_color='black', back_color='white') self.qimg = ImageQt.ImageQt(pimg) return self.qimg, self.qimg.size()
class QETransactionListModel(QAbstractListModel): def __init__(self, wallet, parent=None, *, onchain_domain=None, include_lightning=True): super().__init__(parent) self.wallet = wallet self.onchain_domain = onchain_domain self.include_lightning = include_lightning self.init_model() _logger = get_logger(__name__) # define listmodel rolemap _ROLE_NAMES = ('txid', 'fee_sat', 'height', 'confirmations', 'timestamp', 'monotonic_timestamp', 'incoming', 'value', 'balance', 'date', 'label', 'txpos_in_block', 'fee', 'inputs', 'outputs', 'section', 'type', 'lightning', 'payment_hash', 'key') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict( zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) def rowCount(self, index): return len(self.tx_history) def roleNames(self): return self._ROLE_MAP def data(self, index, role): tx = self.tx_history[index.row()] role_index = role - Qt.UserRole value = tx[self._ROLE_NAMES[role_index]] if isinstance(value, (bool, list, int, str, QEAmount)) or value is None: return value if isinstance(value, Satoshis): return value.value return str(value) def clear(self): self.beginResetModel() self.tx_history = [] self.endResetModel() def tx_to_model(self, tx): #self._logger.debug(str(tx)) item = tx item['key'] = item['txid'] if 'txid' in item else item['payment_hash'] if not 'lightning' in item: item['lightning'] = False if item['lightning']: item['value'] = QEAmount(amount_sat=item['value'].value, amount_msat=item['amount_msat']) item['balance'] = QEAmount(amount_sat=item['balance'].value, amount_msat=item['amount_msat']) if item['type'] == 'payment': item['incoming'] = True if item[ 'direction'] == 'received' else False item['confirmations'] = 0 else: item['value'] = QEAmount(amount_sat=item['value'].value) item['balance'] = QEAmount(amount_sat=item['balance'].value) # newly arriving txs have no (block) timestamp # TODO? if not item['timestamp']: item['timestamp'] = datetime.timestamp(datetime.now()) txts = datetime.fromtimestamp(item['timestamp']) today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) if (txts > today): item['section'] = 'today' elif (txts > today - timedelta(days=1)): item['section'] = 'yesterday' elif (txts > today - timedelta(days=7)): item['section'] = 'lastweek' elif (txts > today - timedelta(days=31)): item['section'] = 'lastmonth' else: item['section'] = 'older' item['date'] = self.format_date_by_section( item['section'], datetime.fromtimestamp(item['timestamp'])) return item def format_date_by_section(self, section, date): #TODO: l10n dfmt = { 'today': '%H:%M:%S', 'yesterday': '%H:%M:%S', 'lastweek': '%a, %H:%M:%S', 'lastmonth': '%a %d, %H:%M:%S', 'older': '%G-%m-%d %H:%M:%S' } if section not in dfmt: section = 'older' return date.strftime(dfmt[section]) # initial model data @pyqtSlot() def init_model(self): history = self.wallet.get_full_history( onchain_domain=self.onchain_domain, include_lightning=self.include_lightning) txs = [] for key, tx in history.items(): txs.append(self.tx_to_model(tx)) self.clear() self.beginInsertRows(QModelIndex(), 0, len(txs) - 1) self.tx_history = txs self.tx_history.reverse() self.endInsertRows() def update_tx(self, txid, info): i = 0 for tx in self.tx_history: if 'txid' in tx and tx['txid'] == txid: tx['height'] = info.height tx['confirmations'] = info.conf tx['timestamp'] = info.timestamp tx['date'] = self.format_date_by_section( tx['section'], datetime.fromtimestamp(info.timestamp)) index = self.index(i, 0) roles = [ self._ROLE_RMAP[x] for x in ['height', 'confirmations', 'timestamp', 'date'] ] self.dataChanged.emit(index, index, roles) return i = i + 1 @pyqtSlot(str, str) def update_tx_label(self, key, label): i = 0 for tx in self.tx_history: if tx['key'] == key: tx['label'] = label index = self.index(i, 0) self.dataChanged.emit(index, index, [self._ROLE_RMAP['label']]) return i = i + 1 @pyqtSlot(int) def updateBlockchainHeight(self, height): self._logger.debug('updating height to %d' % height) i = 0 for tx in self.tx_history: if 'height' in tx and tx['height'] > 0: tx['confirmations'] = height - tx['height'] + 1 index = self.index(i, 0) roles = [self._ROLE_RMAP['confirmations']] self.dataChanged.emit(index, index, roles) i = i + 1
class QEAddressListModel(QAbstractListModel): def __init__(self, wallet, parent=None): super().__init__(parent) self.wallet = wallet self.init_model() _logger = get_logger(__name__) # define listmodel rolemap _ROLE_NAMES = ('type', 'iaddr', 'address', 'label', 'balance', 'numtx', 'held') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict( zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) def rowCount(self, index): return len(self.receive_addresses) + len(self.change_addresses) def roleNames(self): return self._ROLE_MAP def data(self, index, role): if index.row() > len(self.receive_addresses) - 1: address = self.change_addresses[index.row() - len(self.receive_addresses)] else: address = self.receive_addresses[index.row()] role_index = role - Qt.UserRole value = address[self._ROLE_NAMES[role_index]] if isinstance(value, (bool, list, int, str, QEAmount)) or value is None: return value if isinstance(value, Satoshis): return value.value return str(value) def clear(self): self.beginResetModel() self.receive_addresses = [] self.change_addresses = [] self.endResetModel() def addr_to_model(self, address): item = {} item['address'] = address item['numtx'] = self.wallet.adb.get_address_history_len(address) item['label'] = self.wallet.get_label(address) c, u, x = self.wallet.get_addr_balance(address) item['balance'] = QEAmount(amount_sat=c + u + x) item['held'] = self.wallet.is_frozen_address(address) return item # initial model data @pyqtSlot() def init_model(self): r_addresses = self.wallet.get_receiving_addresses() c_addresses = self.wallet.get_change_addresses() n_addresses = len(r_addresses) + len(c_addresses) def insert_row(atype, alist, address, iaddr): item = self.addr_to_model(address) item['type'] = atype item['iaddr'] = iaddr alist.append(item) self.clear() self.beginInsertRows(QModelIndex(), 0, n_addresses - 1) i = 0 for address in r_addresses: insert_row('receive', self.receive_addresses, address, i) i = i + 1 i = 0 for address in c_addresses: insert_row('change', self.change_addresses, address, i) i = i + 1 self.endInsertRows() @pyqtSlot(str) def update_address(self, address): i = 0 for a in self.receive_addresses: if a['address'] == address: self.do_update(i, a) return i = i + 1 for a in self.change_addresses: if a['address'] == address: self.do_update(i, a) return i = i + 1 def do_update(self, modelindex, modelitem): mi = self.createIndex(modelindex, 0) self._logger.debug(repr(modelitem)) modelitem.update(self.addr_to_model(modelitem['address'])) self._logger.debug(repr(modelitem)) self.dataChanged.emit(mi, mi, self._ROLE_KEYS)
class QEInvoice(QObject): class Type: Invalid = -1 OnchainOnlyAddress = 0 OnchainInvoice = 1 LightningInvoice = 2 LightningAndOnchainInvoice = 3 class Status: Unpaid = PR_UNPAID Expired = PR_EXPIRED Unknown = PR_UNKNOWN Paid = PR_PAID Inflight = PR_INFLIGHT Failed = PR_FAILED Routing = PR_ROUTING Unconfirmed = PR_UNCONFIRMED Q_ENUMS(Type) Q_ENUMS(Status) _logger = get_logger(__name__) _wallet = None _canSave = False _canPay = False _key = None def __init__(self, parent=None): super().__init__(parent) walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() canSaveChanged = pyqtSignal() @pyqtProperty(bool, notify=canSaveChanged) def canSave(self): return self._canSave @canSave.setter def canSave(self, canSave): if self._canSave != canSave: self._canSave = canSave self.canSaveChanged.emit() canPayChanged = pyqtSignal() @pyqtProperty(bool, notify=canPayChanged) def canPay(self): return self._canPay @canPay.setter def canPay(self, canPay): if self._canPay != canPay: self._canPay = canPay self.canPayChanged.emit() keyChanged = pyqtSignal() @pyqtProperty(str, notify=keyChanged) def key(self): return self._key @key.setter def key(self, key): if self._key != key: self._key = key self.keyChanged.emit() userinfoChanged = pyqtSignal() @pyqtProperty(str, notify=userinfoChanged) def userinfo(self): return self._userinfo @userinfo.setter def userinfo(self, userinfo): if self._userinfo != userinfo: self._userinfo = userinfo self.userinfoChanged.emit() def get_max_spendable_onchain(self): c, u, x = self._wallet.wallet.get_balance() #TODO determine real max return c
class QEUserEnteredPayment(QEInvoice): _logger = get_logger(__name__) _recipient = None _message = None _amount = QEAmount() validationError = pyqtSignal([str,str], arguments=['code','message']) invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message']) invoiceSaved = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.clear() recipientChanged = pyqtSignal() @pyqtProperty(str, notify=recipientChanged) def recipient(self): return self._recipient @recipient.setter def recipient(self, recipient: str): if self._recipient != recipient: self._recipient = recipient self.validate() self.recipientChanged.emit() messageChanged = pyqtSignal() @pyqtProperty(str, notify=messageChanged) def message(self): return self._message @message.setter def message(self, message): if self._message != message: self._message = message self.messageChanged.emit() amountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=amountChanged) def amount(self): return self._amount @amount.setter def amount(self, amount): if self._amount != amount: self._amount = amount self.validate() self.amountChanged.emit() def validate(self): self.canPay = False self.canSave = False self._logger.debug('validate') if not self._recipient: self.validationError.emit('recipient', _('Recipient not specified.')) return if not bitcoin.is_address(self._recipient): self.validationError.emit('recipient', _('Invalid Bitcoin address')) return if self._amount.isEmpty: self.validationError.emit('amount', _('Invalid amount')) return if self._amount.isMax: self.canPay = True else: self.canSave = True if self.get_max_spendable_onchain() >= self._amount.satsInt: self.canPay = True @pyqtSlot() def save_invoice(self): assert self.canSave assert not self._amount.isMax self._logger.debug('saving invoice to %s, amount=%s, message=%s' % (self._recipient, repr(self._amount), self._message)) inv_amt = self._amount.satsInt try: outputs = [PartialTxOutput.from_address_and_value(self._recipient, inv_amt)] self._logger.debug(repr(outputs)) invoice = self._wallet.wallet.create_invoice(outputs=outputs, message=self._message, pr=None, URI=None) except InvoiceError as e: self.invoiceCreateError.emit('fatal', _('Error creating payment') + ':\n' + str(e)) return self.key = self._wallet.wallet.get_key_for_outgoing_invoice(invoice) self._wallet.wallet.save_invoice(invoice) self.invoiceSaved.emit() @pyqtSlot() def clear(self): self._recipient = None self._amount = QEAmount() self._message = None self.canSave = False self.canPay = False
class QEFX(QObject, QtEventListener): def __init__(self, fxthread: FxThread, config: SimpleConfig, parent=None): super().__init__(parent) self.fx = fxthread self.config = config self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) _logger = get_logger(__name__) quotesUpdated = pyqtSignal() def on_destroy(self): self.unregister_callbacks() @event_listener def on_event_on_quotes(self, *args): self._logger.debug('new quotes') self.quotesUpdated.emit() historyUpdated = pyqtSignal() @event_listener def on_event_on_history(self, *args): self._logger.debug('new history') self.historyUpdated.emit() currenciesChanged = pyqtSignal() @pyqtProperty('QVariantList', notify=currenciesChanged) def currencies(self): return self.fx.get_currencies(self.historicRates) rateSourcesChanged = pyqtSignal() @pyqtProperty('QVariantList', notify=rateSourcesChanged) def rateSources(self): return self.fx.get_exchanges_by_ccy(self.fiatCurrency, self.historicRates) fiatCurrencyChanged = pyqtSignal() @pyqtProperty(str, notify=fiatCurrencyChanged) def fiatCurrency(self): return self.fx.get_currency() @fiatCurrency.setter def fiatCurrency(self, currency): if currency != self.fiatCurrency: self.fx.set_currency(currency) self.enabled = self.enabled and currency != '' self.fiatCurrencyChanged.emit() self.rateSourcesChanged.emit() historicRatesChanged = pyqtSignal() @pyqtProperty(bool, notify=historicRatesChanged) def historicRates(self): return self.fx.get_history_config() @historicRates.setter def historicRates(self, checked): if checked != self.historicRates: self.fx.set_history_config(checked) self.historicRatesChanged.emit() self.rateSourcesChanged.emit() rateSourceChanged = pyqtSignal() @pyqtProperty(str, notify=rateSourceChanged) def rateSource(self): return self.fx.config_exchange() @rateSource.setter def rateSource(self, source): if source != self.rateSource: self.fx.set_exchange(source) self.rateSourceChanged.emit() enabledUpdated = pyqtSignal( ) # curiously, enabledChanged is clashing, so name it enabledUpdated @pyqtProperty(bool, notify=enabledUpdated) def enabled(self): return self.fx.is_enabled() @enabled.setter def enabled(self, enable): if enable != self.enabled: self.fx.set_enabled(enable) self.enabledUpdated.emit() @pyqtSlot(str, result=str) @pyqtSlot(str, bool, result=str) @pyqtSlot(QEAmount, result=str) @pyqtSlot(QEAmount, bool, result=str) def fiatValue(self, satoshis, plain=True): rate = self.fx.exchange_rate() if isinstance(satoshis, QEAmount): satoshis = satoshis.satsInt else: try: sd = Decimal(satoshis) except: return '' if plain: return self.fx.ccy_amount_str(self.fx.fiat_value(satoshis, rate), False) else: return self.fx.value_str(satoshis, rate) @pyqtSlot(str, str, result=str) @pyqtSlot(str, str, bool, result=str) @pyqtSlot(QEAmount, str, result=str) @pyqtSlot(QEAmount, str, bool, result=str) def fiatValueHistoric(self, satoshis, timestamp, plain=True): if isinstance(satoshis, QEAmount): satoshis = satoshis.satsInt else: try: sd = Decimal(satoshis) except: return '' try: td = Decimal(timestamp) if td == 0: return '' except: return '' dt = datetime.fromtimestamp(int(td)) if plain: return self.fx.ccy_amount_str( self.fx.historical_value(satoshis, dt), False) else: return self.fx.historical_value_str(satoshis, dt) @pyqtSlot(str, result=str) @pyqtSlot(str, bool, result=str) def satoshiValue(self, fiat, plain=True): rate = self.fx.exchange_rate() try: fd = Decimal(fiat) except: return '' v = fd / Decimal(rate) * COIN if v.is_nan(): return '' if plain: return str(v.to_integral_value()) else: return self.config.format_amount(v)
class QEInvoiceParser(QEInvoice): _logger = get_logger(__name__) _invoiceType = QEInvoice.Type.Invalid _recipient = '' _effectiveInvoice = None _amount = QEAmount() _userinfo = '' invoiceChanged = pyqtSignal() invoiceSaved = pyqtSignal() validationSuccess = pyqtSignal() validationWarning = pyqtSignal([str,str], arguments=['code', 'message']) validationError = pyqtSignal([str,str], arguments=['code', 'message']) invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message']) def __init__(self, parent=None): super().__init__(parent) self.clear() @pyqtProperty(int, notify=invoiceChanged) def invoiceType(self): return self._invoiceType # not a qt setter, don't let outside set state def setInvoiceType(self, invoiceType: QEInvoice.Type): self._invoiceType = invoiceType recipientChanged = pyqtSignal() @pyqtProperty(str, notify=recipientChanged) def recipient(self): return self._recipient @recipient.setter def recipient(self, recipient: str): #if self._recipient != recipient: self._recipient = recipient if recipient: self.validateRecipient(recipient) self.recipientChanged.emit() @pyqtProperty(str, notify=invoiceChanged) def message(self): return self._effectiveInvoice.message if self._effectiveInvoice else '' @pyqtProperty(QEAmount, notify=invoiceChanged) def amount(self): # store ref to QEAmount on instance, otherwise we get destroyed when going out of scope self._amount = QEAmount() if not self._effectiveInvoice: return self._amount self._amount = QEAmount(from_invoice=self._effectiveInvoice) return self._amount @pyqtProperty('quint64', notify=invoiceChanged) def expiration(self): return self._effectiveInvoice.exp if self._effectiveInvoice else 0 @pyqtProperty('quint64', notify=invoiceChanged) def time(self): return self._effectiveInvoice.time if self._effectiveInvoice else 0 statusChanged = pyqtSignal() @pyqtProperty(int, notify=statusChanged) def status(self): if not self._effectiveInvoice: return PR_UNKNOWN return self._wallet.wallet.get_invoice_status(self._effectiveInvoice) @pyqtProperty(str, notify=statusChanged) def status_str(self): if not self._effectiveInvoice: return '' status = self._wallet.wallet.get_invoice_status(self._effectiveInvoice) return self._effectiveInvoice.get_status_str(status) # single address only, TODO: n outputs @pyqtProperty(str, notify=invoiceChanged) def address(self): return self._effectiveInvoice.get_address() if self._effectiveInvoice else '' @pyqtProperty('QVariantMap', notify=invoiceChanged) def lnprops(self): if not self.invoiceType == QEInvoice.Type.LightningInvoice: return {} lnaddr = self._effectiveInvoice._lnaddr self._logger.debug(str(lnaddr)) self._logger.debug(str(lnaddr.get_routing_info('t'))) return { 'pubkey': lnaddr.pubkey.serialize().hex(), 't': '', #lnaddr.get_routing_info('t')[0][0].hex(), 'r': '' #lnaddr.get_routing_info('r')[0][0][0].hex() } @pyqtSlot() def clear(self): self.recipient = '' self.setInvoiceType(QEInvoice.Type.Invalid) self._bip21 = None self.canSave = False self.canPay = False self.userinfo = '' self.invoiceChanged.emit() # don't parse the recipient string, but init qeinvoice from an invoice key # this should not emit validation signals @pyqtSlot(str) def initFromKey(self, key): self.clear() invoice = self._wallet.wallet.get_invoice(key) self._logger.debug(repr(invoice)) if invoice: self.set_effective_invoice(invoice) self.key = key def set_effective_invoice(self, invoice: Invoice): self._effectiveInvoice = invoice if invoice.is_lightning(): self.setInvoiceType(QEInvoice.Type.LightningInvoice) else: self.setInvoiceType(QEInvoice.Type.OnchainInvoice) self.canSave = True self.determine_can_pay() self.invoiceChanged.emit() self.statusChanged.emit() def determine_can_pay(self): if self.invoiceType == QEInvoice.Type.LightningInvoice: if self.status in [PR_UNPAID, PR_FAILED]: if self.get_max_spendable_lightning() >= self.amount.satsInt: self.canPay = True else: self.userinfo = _('Can\'t pay, insufficient balance') else: self.userinfo = { PR_EXPIRED: _('Can\'t pay, invoice is expired'), PR_PAID: _('Can\'t pay, invoice is already paid'), PR_INFLIGHT: _('Can\'t pay, invoice is already being paid'), PR_ROUTING: _('Can\'t pay, invoice is already being paid'), PR_UNKNOWN: _('Can\'t pay, invoice has unknown status'), }[self.status] elif self.invoiceType == QEInvoice.Type.OnchainInvoice: if self.status in [PR_UNPAID, PR_FAILED]: if self.get_max_spendable_onchain() >= self.amount.satsInt: self.canPay = True else: self.userinfo = _('Can\'t pay, insufficient balance') else: self.userinfo = { PR_EXPIRED: _('Can\'t pay, invoice is expired'), PR_PAID: _('Can\'t pay, invoice is already paid'), PR_UNCONFIRMED: _('Can\'t pay, invoice is already paid'), PR_UNKNOWN: _('Can\'t pay, invoice has unknown status'), }[self.status] def get_max_spendable_lightning(self): return self._wallet.wallet.lnworker.num_sats_can_send() def setValidAddressOnly(self): self._logger.debug('setValidAddressOnly') self.setInvoiceType(QEInvoice.Type.OnchainOnlyAddress) self._effectiveInvoice = None self.invoiceChanged.emit() def setValidOnchainInvoice(self, invoice: Invoice): self._logger.debug('setValidOnchainInvoice') if invoice.is_lightning(): raise Exception('unexpected LN invoice') self.set_effective_invoice(invoice) def setValidLightningInvoice(self, invoice: Invoice): self._logger.debug('setValidLightningInvoice') if not invoice.is_lightning(): raise Exception('unexpected Onchain invoice') self.set_effective_invoice(invoice) def create_onchain_invoice(self, outputs, message, payment_request, uri): return self._wallet.wallet.create_invoice( outputs=outputs, message=message, pr=payment_request, URI=uri ) def validateRecipient(self, recipient): if not recipient: self.setInvoiceType(QEInvoice.Type.Invalid) return maybe_lightning_invoice = recipient def _payment_request_resolved(request): self._logger.debug('resolved payment request') outputs = request.get_outputs() invoice = self.create_onchain_invoice(outputs, None, request, None) self.setValidOnchainInvoice(invoice) try: self._bip21 = parse_URI(recipient, _payment_request_resolved) if self._bip21: if 'r' in self._bip21 or ('name' in self._bip21 and 'sig' in self._bip21): # TODO set flag in util? # let callback handle state return if ':' not in recipient: # address only self.setValidAddressOnly() self.validationSuccess.emit() return else: # fallback lightning invoice? if 'lightning' in self._bip21: maybe_lightning_invoice = self._bip21['lightning'] except InvalidBitcoinURI as e: self._bip21 = None self._logger.debug(repr(e)) lninvoice = None maybe_lightning_invoice = maybe_extract_lightning_payment_identifier(maybe_lightning_invoice) if maybe_lightning_invoice is not None: try: lninvoice = Invoice.from_bech32(maybe_lightning_invoice) except InvoiceError as e: e2 = e.__cause__ if isinstance(e2, LnInvoiceException): self.validationError.emit('unknown', _("Error parsing Lightning invoice") + f":\n{e2}") self.clear() return if isinstance(e2, lnutil.IncompatibleOrInsaneFeatures): self.validationError.emit('unknown', _("Invoice requires unknown or incompatible Lightning feature") + f":\n{e2!r}") self.clear() return self._logger.exception(repr(e)) if not lninvoice and not self._bip21: self.validationError.emit('unknown',_('Unknown invoice')) self.clear() return if lninvoice: if not self._wallet.wallet.has_lightning(): if not self._bip21: # TODO: lightning onchain fallback in ln invoice #self.validationError.emit('no_lightning',_('Detected valid Lightning invoice, but Lightning not enabled for wallet')) self.setValidLightningInvoice(lninvoice) self.clear() return else: self._logger.debug('flow with LN but not LN enabled AND having bip21 uri') self.setValidOnchainInvoice(self._bip21['address']) else: self.setValidLightningInvoice(lninvoice) if not self._wallet.wallet.lnworker.channels: self.validationWarning.emit('no_channels',_('Detected valid Lightning invoice, but there are no open channels')) else: self.validationSuccess.emit() else: self._logger.debug('flow without LN but having bip21 uri') if 'amount' not in self._bip21: #TODO can we have amount-less invoices? self.validationError.emit('no_amount', 'no amount in uri') return outputs = [PartialTxOutput.from_address_and_value(self._bip21['address'], self._bip21['amount'])] self._logger.debug(outputs) message = self._bip21['message'] if 'message' in self._bip21 else '' invoice = self.create_onchain_invoice(outputs, message, None, self._bip21) self._logger.debug(repr(invoice)) self.setValidOnchainInvoice(invoice) self.validationSuccess.emit() @pyqtSlot() def save_invoice(self): self.canSave = False if not self._effectiveInvoice: return # TODO detect duplicate? self.key = self._wallet.wallet.get_key_for_outgoing_invoice(self._effectiveInvoice) self._wallet.wallet.save_invoice(self._effectiveInvoice) self.invoiceSaved.emit()
class QEAmount(QObject): _logger = get_logger(__name__) def __init__(self, *, amount_sat: int = 0, amount_msat: int = 0, is_max: bool = False, from_invoice=None, parent=None): super().__init__(parent) self._amount_sat = int(amount_sat) if amount_sat is not None else None self._amount_msat = int( amount_msat) if amount_msat is not None else None self._is_max = is_max if from_invoice: inv_amt = from_invoice.get_amount_msat() if inv_amt == '!': self._is_max = True elif inv_amt is not None: self._amount_msat = int(inv_amt) self._amount_sat = int(from_invoice.get_amount_sat()) valueChanged = pyqtSignal() @pyqtProperty('qint64', notify=valueChanged) def satsInt(self): return self._amount_sat @pyqtProperty('qint64', notify=valueChanged) def msatsInt(self): return self._amount_msat @pyqtProperty(str, notify=valueChanged) def satsStr(self): return str(self._amount_sat) @pyqtProperty(str, notify=valueChanged) def msatsStr(self): return str(self._amount_msat) @pyqtProperty(bool, notify=valueChanged) def isMax(self): return self._is_max @pyqtProperty(bool, notify=valueChanged) def isEmpty(self): return not (self._is_max or self._amount_sat or self._amount_msat) def __eq__(self, other): if isinstance(other, QEAmount): return self._amount_sat == other._amount_sat and self._amount_msat == other._amount_msat and self._is_max == other._is_max elif isinstance(other, int): return self._amount_sat == other elif isinstance(other, str): return self.satsStr == other return False def __str__(self): s = _('Amount') if self._is_max: return '%s(MAX)' % s return '%s(sats=%d, msats=%d)' % (s, self._amount_sat, self._amount_msat) def __repr__(self): return f"<QEAmount max={self._is_max} sats={self._amount_sat} msats={self._amount_msat} empty={self.isEmpty}>"
class QETxFinalizer(QObject): def __init__(self, parent=None, *, make_tx=None, accept=None): super().__init__(parent) self.f_make_tx = make_tx self.f_accept = accept self._tx = None _logger = get_logger(__name__) _address = '' _amount = QEAmount() _effectiveAmount = QEAmount() _fee = QEAmount() _feeRate = '' _wallet = None _valid = False _sliderSteps = 0 _sliderPos = 0 _method = -1 _warning = '' _target = '' _rbf = False _canRbf = False _outputs = [] config = None validChanged = pyqtSignal() @pyqtProperty(bool, notify=validChanged) def valid(self): return self._valid walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self.config = self._wallet.wallet.config self.read_config() self.walletChanged.emit() addressChanged = pyqtSignal() @pyqtProperty(str, notify=addressChanged) def address(self): return self._address @address.setter def address(self, address): if self._address != address: self._address = address self.addressChanged.emit() amountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=amountChanged) def amount(self): return self._amount @amount.setter def amount(self, amount): if self._amount != amount: self._logger.debug(str(amount)) self._amount = amount self.amountChanged.emit() effectiveAmountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=effectiveAmountChanged) def effectiveAmount(self): return self._effectiveAmount feeChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=feeChanged) def fee(self): return self._fee @fee.setter def fee(self, fee): if self._fee != fee: self._fee = fee self.feeChanged.emit() feeRateChanged = pyqtSignal() @pyqtProperty(str, notify=feeRateChanged) def feeRate(self): return self._feeRate @feeRate.setter def feeRate(self, feeRate): if self._feeRate != feeRate: self._feeRate = feeRate self.feeRateChanged.emit() targetChanged = pyqtSignal() @pyqtProperty(str, notify=targetChanged) def target(self): return self._target @target.setter def target(self, target): if self._target != target: self._target = target self.targetChanged.emit() rbfChanged = pyqtSignal() @pyqtProperty(bool, notify=rbfChanged) def rbf(self): return self._rbf @rbf.setter def rbf(self, rbf): if self._rbf != rbf: self._rbf = rbf self.update() self.rbfChanged.emit() canRbfChanged = pyqtSignal() @pyqtProperty(bool, notify=canRbfChanged) def canRbf(self): return self._canRbf @canRbf.setter def canRbf(self, canRbf): if self._canRbf != canRbf: self._canRbf = canRbf self.canRbfChanged.emit() if not canRbf and self.rbf: self.rbf = False outputsChanged = pyqtSignal() @pyqtProperty('QVariantList', notify=outputsChanged) def outputs(self): return self._outputs @outputs.setter def outputs(self, outputs): if self._outputs != outputs: self._outputs = outputs self.outputsChanged.emit() warningChanged = pyqtSignal() @pyqtProperty(str, notify=warningChanged) def warning(self): return self._warning @warning.setter def warning(self, warning): if self._warning != warning: self._warning = warning self.warningChanged.emit() sliderStepsChanged = pyqtSignal() @pyqtProperty(int, notify=sliderStepsChanged) def sliderSteps(self): return self._sliderSteps sliderPosChanged = pyqtSignal() @pyqtProperty(int, notify=sliderPosChanged) def sliderPos(self): return self._sliderPos @sliderPos.setter def sliderPos(self, sliderPos): if self._sliderPos != sliderPos: self._sliderPos = sliderPos self.save_config() self.sliderPosChanged.emit() methodChanged = pyqtSignal() @pyqtProperty(int, notify=methodChanged) def method(self): return self._method @method.setter def method(self, method): if self._method != method: self._method = method self.update_slider() self.methodChanged.emit() self.save_config() def get_method(self): dynfees = self._method > 0 mempool = self._method == 2 return dynfees, mempool def update_slider(self): dynfees, mempool = self.get_method() maxp, pos, fee_rate = self.config.get_fee_slider(dynfees, mempool) self._sliderSteps = maxp self._sliderPos = pos self.sliderStepsChanged.emit() self.sliderPosChanged.emit() def read_config(self): mempool = self.config.use_mempool_fees() dynfees = self.config.is_dynfee() self._method = (2 if mempool else 1) if dynfees else 0 self.update_slider() self.methodChanged.emit() self.update() def save_config(self): value = int(self._sliderPos) dynfees, mempool = self.get_method() self.config.set_key('dynamic_fees', dynfees, False) self.config.set_key('mempool_fees', mempool, False) if dynfees: if mempool: self.config.set_key('depth_level', value, True) else: self.config.set_key('fee_level', value, True) else: self.config.set_key('fee_per_kb', self.config.static_fee(value), True) self.update() @profiler def make_tx(self, amount): self._logger.debug('make_tx amount = %s' % str(amount)) if self.f_make_tx: tx = self.f_make_tx(amount) else: # default impl coins = self._wallet.wallet.get_spendable_coins(None) outputs = [ PartialTxOutput.from_address_and_value(self.address, amount) ] tx = self._wallet.wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=None, rbf=self._rbf) self._logger.debug('fee: %d, inputs: %d, outputs: %d' % (tx.get_fee(), len(tx.inputs()), len(tx.outputs()))) outputs = [] for o in tx.outputs(): outputs.append({ 'address': o.get_ui_address_str(), 'value_sats': o.value, 'is_mine': self._wallet.wallet.is_mine(o.get_ui_address_str()) }) self.outputs = outputs return tx @pyqtSlot() def update(self): try: # make unsigned transaction tx = self.make_tx( amount='!' if self._amount.isMax else self._amount.satsInt) except NotEnoughFunds: self.warning = _("Not enough funds") self._valid = False self.validChanged.emit() return except Exception as e: self._logger.error(str(e)) self.warning = repr(e) self._valid = False self.validChanged.emit() return self._tx = tx amount = self._amount.satsInt if not self._amount.isMax else tx.output_value( ) self._effectiveAmount = QEAmount(amount_sat=amount) self.effectiveAmountChanged.emit() tx_size = tx.estimated_size() fee = tx.get_fee() feerate = Decimal(fee) / tx_size # sat/byte self.fee = QEAmount(amount_sat=fee) self.feeRate = f'{feerate:.1f}' #TODO #x_fee = run_hook('get_tx_extra_fee', self._wallet.wallet, tx) fee_warning_tuple = self._wallet.wallet.get_tx_fee_warning( invoice_amt=amount, tx_size=tx_size, fee=fee) if fee_warning_tuple: allow_send, long_warning, short_warning = fee_warning_tuple self.warning = long_warning else: self.warning = '' target, tooltip, dyn = self.config.get_fee_target() self.target = target self._valid = True self.validChanged.emit() @pyqtSlot() def send_onchain(self): if not self._valid or not self._tx: self._logger.debug('no valid tx') return if self.f_accept: self.f_accept(self._tx) return self._wallet.sign_and_broadcast(self._tx)
class QEConfig(AuthMixin, QObject): def __init__(self, config, parent=None): super().__init__(parent) self.config = config _logger = get_logger(__name__) autoConnectChanged = pyqtSignal() @pyqtProperty(bool, notify=autoConnectChanged) def autoConnect(self): return self.config.get('auto_connect') @autoConnect.setter def autoConnect(self, auto_connect): self.config.set_key('auto_connect', auto_connect, True) self.autoConnectChanged.emit() # auto_connect is actually a tri-state, expose the undefined case @pyqtProperty(bool, notify=autoConnectChanged) def autoConnectDefined(self): return self.config.get('auto_connect') is not None serverStringChanged = pyqtSignal() @pyqtProperty('QString', notify=serverStringChanged) def serverString(self): return self.config.get('server') @serverString.setter def serverString(self, server): self.config.set_key('server', server, True) self.serverStringChanged.emit() manualServerChanged = pyqtSignal() @pyqtProperty(bool, notify=manualServerChanged) def manualServer(self): return self.config.get('oneserver') @manualServer.setter def manualServer(self, oneserver): self.config.set_key('oneserver', oneserver, True) self.manualServerChanged.emit() baseUnitChanged = pyqtSignal() @pyqtProperty(str, notify=baseUnitChanged) def baseUnit(self): return self.config.get_base_unit() @baseUnit.setter def baseUnit(self, unit): self.config.set_base_unit(unit) self.baseUnitChanged.emit() thousandsSeparatorChanged = pyqtSignal() @pyqtProperty(bool, notify=thousandsSeparatorChanged) def thousandsSeparator(self): return self.config.get('amt_add_thousands_sep', False) @thousandsSeparator.setter def thousandsSeparator(self, checked): self.config.set_key('amt_add_thousands_sep', checked) self.config.amt_add_thousands_sep = checked self.thousandsSeparatorChanged.emit() spendUnconfirmedChanged = pyqtSignal() @pyqtProperty(bool, notify=spendUnconfirmedChanged) def spendUnconfirmed(self): return not self.config.get('confirmed_only', False) @spendUnconfirmed.setter def spendUnconfirmed(self, checked): self.config.set_key('confirmed_only', not checked, True) self.spendUnconfirmedChanged.emit() pinCodeChanged = pyqtSignal() @pyqtProperty(str, notify=pinCodeChanged) def pinCode(self): return self.config.get('pin_code', '') @pinCode.setter def pinCode(self, pin_code): if pin_code == '': self.pinCodeRemoveAuth() else: self.config.set_key('pin_code', pin_code, True) self.pinCodeChanged.emit() @auth_protect(method='wallet') def pinCodeRemoveAuth(self): self.config.set_key('pin_code', '', True) self.pinCodeChanged.emit() useGossipChanged = pyqtSignal() @pyqtProperty(bool, notify=useGossipChanged) def useGossip(self): return self.config.get('use_gossip', False) @useGossip.setter def useGossip(self, gossip): self.config.set_key('use_gossip', gossip) self.useGossipChanged.emit() @pyqtSlot('qint64', result=str) @pyqtSlot('qint64', bool, result=str) @pyqtSlot(QEAmount, result=str) @pyqtSlot(QEAmount, bool, result=str) def formatSats(self, satoshis, with_unit=False): if isinstance(satoshis, QEAmount): satoshis = satoshis.satsInt if with_unit: return self.config.format_amount_and_units(satoshis) else: return self.config.format_amount(satoshis) @pyqtSlot(QEAmount, result=str) @pyqtSlot(QEAmount, bool, result=str) def formatMilliSats(self, amount, with_unit=False): if isinstance(amount, QEAmount): msats = amount.msatsInt else: return '---' s = format_satoshis(msats / 1000, decimal_point=self.decimal_point(), precision=3) return s #if with_unit: #return self.config.format_amount_and_units(msats) #else: #return self.config.format_amount(satoshis) # TODO delegate all this to config.py/util.py def decimal_point(self): return self.config.get('decimal_point', DECIMAL_POINT_DEFAULT) def max_precision(self): return self.decimal_point() + 0 #self.extra_precision @pyqtSlot(str, result=QEAmount) def unitsToSats(self, unitAmount): self._amount = QEAmount() try: x = Decimal(unitAmount) except: return self._amount # scale it to max allowed precision, make it an int max_prec_amount = int(pow(10, self.max_precision()) * x) # if the max precision is simply what unit conversion allows, just return if self.max_precision() == self.decimal_point(): self._amount = QEAmount(amount_sat=max_prec_amount) return self._amount self._logger.debug('fallthrough') # otherwise, scale it back to the expected unit #amount = Decimal(max_prec_amount) / Decimal(pow(10, self.max_precision()-self.decimal_point())) #return int(amount) #Decimal(amount) if not self.is_int else int(amount) return self._amount @pyqtSlot('quint64', result=float) def satsToUnits(self, satoshis): return satoshis / pow(10, self.config.decimal_point)
class QEQRParser(QObject): def __init__(self, text=None, parent=None): super().__init__(parent) self._text = text self.qrreader = get_qr_reader() if not self.qrreader: raise Exception( _("The platform QR detection library is not available.")) _logger = get_logger(__name__) busyChanged = pyqtSignal() dataChanged = pyqtSignal() imageChanged = pyqtSignal() _busy = False _image = None @pyqtSlot('QImage') def scanImage(self, image=None): if self._busy: self._logger.warning( "Already processing an image. Check 'busy' property before calling scanImage" ) return if image == None: self._logger.warning("No image to decode") return self._busy = True self.busyChanged.emit() self.logImageStats(image) self._parseQR(image) def logImageStats(self, image): self._logger.info('width: ' + str(image.width())) self._logger.info('height: ' + str(image.height())) self._logger.info('depth: ' + str(image.depth())) self._logger.info('format: ' + str(image.format())) def _parseQR(self, image): self.w = image.width() self.h = image.height() img_crop_rect = self._get_crop(image, 360) frame_cropped = image.copy(img_crop_rect) async def co_parse_qr(image): # Convert to Y800 / GREY FourCC (single 8-bit channel) # This creates a copy, so we don't need to keep the frame around anymore frame_y800 = image.convertToFormat(QImage.Format_Grayscale8) self.frame_id = 0 # Read the QR codes from the frame self.qrreader_res = self.qrreader.read_qr_code( frame_y800.constBits().__int__(), frame_y800.byteCount(), frame_y800.bytesPerLine(), frame_y800.width(), frame_y800.height(), self.frame_id) if len(self.qrreader_res) > 0: result = self.qrreader_res[0] self._data = result self.dataChanged.emit() self._busy = False self.busyChanged.emit() asyncio.run_coroutine_threadsafe(co_parse_qr(frame_cropped), get_asyncio_loop()) def _get_crop(self, image: QImage, scan_size: int) -> QRect: """ Returns a QRect that is scan_size x scan_size in the middle of the resolution """ self.scan_pos_x = (image.width() - scan_size) // 2 self.scan_pos_y = (image.height() - scan_size) // 2 return QRect(self.scan_pos_x, self.scan_pos_y, scan_size, scan_size) @pyqtProperty(bool, notify=busyChanged) def busy(self): return self._busy @pyqtProperty('QImage', notify=imageChanged) def image(self): return self._image @pyqtProperty(str, notify=dataChanged) def data(self): return self._data.data @pyqtProperty('QPoint', notify=dataChanged) def center(self): (x, y) = self._data.center return QPoint(x + self.scan_pos_x, y + self.scan_pos_y) @pyqtProperty('QVariant', notify=dataChanged) def points(self): result = [] for item in self._data.points: (x, y) = item result.append(QPoint(x + self.scan_pos_x, y + self.scan_pos_y)) return result
class QEAddressDetails(QObject): def __init__(self, parent=None): super().__init__(parent) _logger = get_logger(__name__) _wallet = None _address = None _label = None _frozen = False _scriptType = None _status = None _balance = QEAmount() _pubkeys = None _privkey = None _derivationPath = None _numtx = 0 _historyModel = None detailsChanged = pyqtSignal() walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() addressChanged = pyqtSignal() @pyqtProperty(str, notify=addressChanged) def address(self): return self._address @address.setter def address(self, address: str): if self._address != address: self._logger.debug('address changed') self._address = address self.addressChanged.emit() self.update() @pyqtProperty(str, notify=detailsChanged) def scriptType(self): return self._scriptType @pyqtProperty(QEAmount, notify=detailsChanged) def balance(self): return self._balance @pyqtProperty('QStringList', notify=detailsChanged) def pubkeys(self): return self._pubkeys @pyqtProperty(str, notify=detailsChanged) def derivationPath(self): return self._derivationPath @pyqtProperty(int, notify=detailsChanged) def numTx(self): return self._numtx frozenChanged = pyqtSignal() @pyqtProperty(bool, notify=frozenChanged) def isFrozen(self): return self._frozen labelChanged = pyqtSignal() @pyqtProperty(str, notify=labelChanged) def label(self): return self._label @pyqtSlot(bool) def freeze(self, freeze: bool): if freeze != self._frozen: self._wallet.wallet.set_frozen_state_of_addresses([self._address], freeze=freeze) self._frozen = freeze self.frozenChanged.emit() self._wallet.balanceChanged.emit() @pyqtSlot(str) def set_label(self, label: str): if label != self._label: self._wallet.wallet.set_label(self._address, label) self._label = label self.labelChanged.emit() historyModelChanged = pyqtSignal() @pyqtProperty(QETransactionListModel, notify=historyModelChanged) def historyModel(self): if self._historyModel is None: self._historyModel = QETransactionListModel( self._wallet.wallet, onchain_domain=[self._address], include_lightning=False) return self._historyModel def update(self): if self._wallet is None: self._logger.error('wallet undefined') return self._frozen = self._wallet.wallet.is_frozen_address(self._address) self.frozenChanged.emit() self._scriptType = self._wallet.wallet.get_txin_type(self._address) self._label = self._wallet.wallet.get_label(self._address) c, u, x = self._wallet.wallet.get_addr_balance(self._address) self._balance = QEAmount(amount_sat=c + u + x) self._pubkeys = self._wallet.wallet.get_public_keys(self._address) self._derivationPath = self._wallet.wallet.get_address_path_str( self._address) self._derivationPath = self._derivationPath.replace( 'm', self._wallet.derivationPrefix) self._numtx = self._wallet.wallet.adb.get_address_history_len( self._address) assert (self._numtx == self.historyModel.rowCount(0)) self.detailsChanged.emit()
class QEChannelOpener(QObject): def __init__(self, parent=None): super().__init__(parent) _logger = get_logger(__name__) _wallet = None _nodeid = None _amount = QEAmount() _valid = False _opentx = None validationError = pyqtSignal([str, str], arguments=['code', 'message']) conflictingBackup = pyqtSignal([str], arguments=['message']) channelOpenError = pyqtSignal([str], arguments=['message']) channelOpenSuccess = pyqtSignal([str, bool], arguments=['cid', 'has_backup']) dataChanged = pyqtSignal() # generic notify signal walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() nodeidChanged = pyqtSignal() @pyqtProperty(str, notify=nodeidChanged) def nodeid(self): return self._nodeid @nodeid.setter def nodeid(self, nodeid: str): if self._nodeid != nodeid: self._logger.debug('nodeid set -> %s' % nodeid) self._nodeid = nodeid self.nodeidChanged.emit() self.validate() amountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=amountChanged) def amount(self): return self._amount @amount.setter def amount(self, amount: QEAmount): if self._amount != amount: self._amount = amount self.amountChanged.emit() self.validate() validChanged = pyqtSignal() @pyqtProperty(bool, notify=validChanged) def valid(self): return self._valid finalizerChanged = pyqtSignal() @pyqtProperty(QETxFinalizer, notify=finalizerChanged) def finalizer(self): return self._finalizer @pyqtProperty(list, notify=dataChanged) def trampolineNodeNames(self): return list(hardcoded_trampoline_nodes().keys()) # FIXME min channel funding amount # FIXME have requested funding amount def validate(self): nodeid_valid = False if self._nodeid: if not self._wallet.wallet.config.get('use_gossip', False): self._peer = hardcoded_trampoline_nodes()[self._nodeid] nodeid_valid = True else: try: node_pubkey, host_port = extract_nodeid(self._nodeid) host, port = host_port.split(':', 1) self._peer = LNPeerAddr(host, int(port), node_pubkey) nodeid_valid = True except ConnStringFormatError as e: self.validationError.emit('invalid_nodeid', repr(e)) except ValueError as e: self.validationError.emit('invalid_nodeid', repr(e)) if not nodeid_valid: self._valid = False self.validChanged.emit() return self._logger.debug('amount=%s' % str(self._amount)) if not self._amount or not (self._amount.satsInt > 0 or self._amount.isMax): self._valid = False self.validChanged.emit() return self._valid = True self.validChanged.emit() # FIXME "max" button in amount_dialog should enforce LN_MAX_FUNDING_SAT @pyqtSlot() @pyqtSlot(bool) def open_channel(self, confirm_backup_conflict=False): if not self.valid: return self._logger.debug('Connect String: %s' % str(self._peer)) lnworker = self._wallet.wallet.lnworker if lnworker.has_conflicting_backup_with( self._peer.pubkey) and not confirm_backup_conflict: self.conflictingBackup.emit( messages.MGS_CONFLICTING_BACKUP_INSTANCE) return amount = '!' if self._amount.isMax else self._amount.satsInt self._logger.debug('amount = %s' % str(amount)) coins = self._wallet.wallet.get_spendable_coins(None, nonlocal_only=True) mktx = lambda amt: lnworker.mktx_for_open_channel(coins=coins, funding_sat=amt, node_id=self._peer. pubkey, fee_est=None) acpt = lambda tx: self.do_open_channel(tx, str(self._peer), None) self._finalizer = QETxFinalizer(self, make_tx=mktx, accept=acpt) self._finalizer.canRbf = False self._finalizer.amount = self._amount self._finalizer.wallet = self._wallet self.finalizerChanged.emit() def do_open_channel(self, funding_tx, conn_str, password): self._logger.debug('opening channel') # read funding_sat from tx; converts '!' to int value funding_sat = funding_tx.output_value_for_address(ln_dummy_address()) lnworker = self._wallet.wallet.lnworker try: chan, funding_tx = lnworker.open_channel(connect_str=conn_str, funding_tx=funding_tx, funding_sat=funding_sat, push_amt_sat=0, password=password) except Exception as e: self._logger.exception("Problem opening channel") self.channelOpenError.emit( _('Problem opening channel: ') + '\n' + repr(e)) return self._logger.debug('opening channel succeeded') self.channelOpenSuccess.emit(chan.channel_id.hex(), chan.has_onchain_backup())
class QENetwork(QObject, QtEventListener): def __init__(self, network, parent=None): super().__init__(parent) self.network = network self.register_callbacks() _logger = get_logger(__name__) networkUpdated = pyqtSignal() blockchainUpdated = pyqtSignal() heightChanged = pyqtSignal([int], arguments=['height']) defaultServerChanged = pyqtSignal() proxySet = pyqtSignal() proxyChanged = pyqtSignal() statusChanged = pyqtSignal() feeHistogramUpdated = pyqtSignal() # shared signal for static properties dataChanged = pyqtSignal() _height = 0 _status = "" @qt_event_listener def on_event_network_updated(self, *args): self.networkUpdated.emit() @qt_event_listener def on_event_blockchain_updated(self, *args): if self._height != self.network.get_local_height(): self._height = self.network.get_local_height() self._logger.debug('new height: %d' % self._height) self.heightChanged.emit(self._height) self.blockchainUpdated.emit() @qt_event_listener def on_event_default_server_changed(self, *args): self.defaultServerChanged.emit() @qt_event_listener def on_event_proxy_set(self, *args): self._logger.debug('proxy set') self.proxySet.emit() @qt_event_listener def on_event_status(self, *args): self._logger.debug('status updated: %s' % self.network.connection_status) if self._status != self.network.connection_status: self._status = self.network.connection_status self.statusChanged.emit() @qt_event_listener def on_event_fee_histogram(self, *args): self._logger.debug('fee histogram updated') self.feeHistogramUpdated.emit() @pyqtProperty(int, notify=heightChanged) def height(self): return self._height @pyqtProperty('QString', notify=defaultServerChanged) def server(self): return str(self.network.get_parameters().server) @server.setter def server(self, server): net_params = self.network.get_parameters() try: server = ServerAddr.from_str_with_inference(server) if not server: raise Exception("failed to parse") except Exception: return net_params = net_params._replace(server=server) self.network.run_from_another_thread(self.network.set_parameters(net_params)) @pyqtProperty('QString', notify=statusChanged) def status(self): return self._status @pyqtProperty(bool, notify=dataChanged) def isTestNet(self): return constants.net.TESTNET @pyqtProperty('QString', notify=dataChanged) def networkName(self): return constants.net.__name__.replace('Bitcoin','') @pyqtProperty('QVariantMap', notify=proxyChanged) def proxy(self): net_params = self.network.get_parameters() return net_params @proxy.setter def proxy(self, proxy_settings): net_params = self.network.get_parameters() if not proxy_settings['enabled']: proxy_settings = None net_params = net_params._replace(proxy=proxy_settings) self.network.run_from_another_thread(self.network.set_parameters(net_params)) self.proxyChanged.emit() @pyqtProperty('QVariant', notify=feeHistogramUpdated) def feeHistogram(self): return self.network.get_status_value('fee_histogram')
class QEAbstractInvoiceListModel(QAbstractListModel): _logger = get_logger(__name__) def __init__(self, wallet, parent=None): super().__init__(parent) self.wallet = wallet self.init_model() # define listmodel rolemap _ROLE_NAMES=('key', 'is_lightning', 'timestamp', 'date', 'message', 'amount', 'status', 'status_str', 'address', 'expiration', 'type', 'onchain_fallback', 'lightning_invoice') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) def rowCount(self, index): return len(self.invoices) def roleNames(self): return self._ROLE_MAP def data(self, index, role): invoice = self.invoices[index.row()] role_index = role - Qt.UserRole value = invoice[self._ROLE_NAMES[role_index]] if isinstance(value, (bool, list, int, str, QEAmount)) or value is None: return value if isinstance(value, Satoshis): return value.value return str(value) def clear(self): self.beginResetModel() self.invoices = [] self.endResetModel() @pyqtSlot() def init_model(self): invoices = [] for invoice in self.get_invoice_list(): item = self.invoice_to_model(invoice) #self._logger.debug(str(item)) invoices.append(item) self.clear() self.beginInsertRows(QModelIndex(), 0, len(invoices) - 1) self.invoices = invoices self.endInsertRows() def add_invoice(self, invoice: Invoice): item = self.invoice_to_model(invoice) self._logger.debug(str(item)) self.beginInsertRows(QModelIndex(), 0, 0) self.invoices.insert(0, item) self.endInsertRows() def delete_invoice(self, key: str): i = 0 for invoice in self.invoices: if invoice['key'] == key: self.beginRemoveRows(QModelIndex(), i, i) self.invoices.pop(i) self.endRemoveRows() break i = i + 1 def get_model_invoice(self, key: str): for invoice in self.invoices: if invoice['key'] == key: return invoice return None @pyqtSlot(str, int) def updateInvoice(self, key, status): self._logger.debug('updating invoice for %s to %d' % (key,status)) i = 0 for item in self.invoices: if item['key'] == key: invoice = self.get_invoice_for_key(key) item['status'] = status item['status_str'] = invoice.get_status_str(status) index = self.index(i,0) self.dataChanged.emit(index, index, [self._ROLE_RMAP['status'], self._ROLE_RMAP['status_str']]) return i = i + 1 def invoice_to_model(self, invoice: Invoice): item = self.get_invoice_as_dict(invoice) #item['key'] = invoice.get_id() item['is_lightning'] = invoice.is_lightning() if invoice.is_lightning() and 'address' not in item: item['address'] = '' item['date'] = format_time(item['timestamp']) item['amount'] = QEAmount(from_invoice=invoice) item['onchain_fallback'] = invoice.is_lightning() and invoice._lnaddr.get_fallback_address() item['type'] = 'invoice' return item @abstractmethod def get_invoice_for_key(self, key: str): raise Exception('provide impl') @abstractmethod def get_invoice_list(self): raise Exception('provide impl') @abstractmethod def get_invoice_as_dict(self, invoice: Invoice): raise Exception('provide impl')
def __init__(self, args, config, daemon): super().__init__(args) self.logger = get_logger(__name__) ElectrumQmlApplication._daemon = daemon qmlRegisterType(QEWalletListModel, 'org.electrum', 1, 0, 'WalletListModel') qmlRegisterType(QEWallet, 'org.electrum', 1, 0, 'Wallet') qmlRegisterType(QEWalletDB, 'org.electrum', 1, 0, 'WalletDB') qmlRegisterType(QEBitcoin, 'org.electrum', 1, 0, 'Bitcoin') qmlRegisterType(QEQRParser, 'org.electrum', 1, 0, 'QRParser') qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX') qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer') qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice') qmlRegisterType(QEInvoiceParser, 'org.electrum', 1, 0, 'InvoiceParser') qmlRegisterType(QEUserEnteredPayment, 'org.electrum', 1, 0, 'UserEnteredPayment') qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails') qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails') qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener') qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails') qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0, 'ChannelDetails') qmlRegisterType(QESwapHelper, 'org.electrum', 1, 0, 'SwapHelper') qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property') self.engine = QQmlApplicationEngine(parent=self) self.engine.addImportPath('./qml') screensize = self.primaryScreen().size() self.qr_ip = QEQRImageProvider( (7 / 8) * min(screensize.width(), screensize.height())) self.engine.addImageProvider('qrgen', self.qr_ip) # add a monospace font as we can't rely on device having one self.fixedFont = 'PT Mono' not_loaded = QFontDatabase.addApplicationFont( 'electrum/gui/qml/fonts/PTMono-Regular.ttf') < 0 not_loaded = QFontDatabase.addApplicationFont( 'electrum/gui/qml/fonts/PTMono-Bold.ttf') < 0 and not_loaded if not_loaded: self.logger.warning('Could not load font PT Mono') self.fixedFont = 'Monospace' # hope for the best self.context = self.engine.rootContext() self._qeconfig = QEConfig(config) self._qenetwork = QENetwork(daemon.network) self._qedaemon = QEDaemon(daemon) self._appController = QEAppController(self._qedaemon) self._maxAmount = QEAmount(is_max=True) self.context.setContextProperty('AppController', self._appController) self.context.setContextProperty('Config', self._qeconfig) self.context.setContextProperty('Network', self._qenetwork) self.context.setContextProperty('Daemon', self._qedaemon) self.context.setContextProperty('FixedFont', self.fixedFont) self.context.setContextProperty('MAX', self._maxAmount) self.context.setContextProperty( 'BUILD', { 'electrum_version': version.ELECTRUM_VERSION, 'apk_version': version.APK_VERSION, 'protocol_version': version.PROTOCOL_VERSION }) qInstallMessageHandler(self.message_handler) # get notified whether root QML document loads or not self.engine.objectCreated.connect(self.objectCreated)
class QELnPaymentDetails(QObject): def __init__(self, parent=None): super().__init__(parent) _logger = get_logger(__name__) _wallet = None _key = None _date = None detailsChanged = pyqtSignal() walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() keyChanged = pyqtSignal() @pyqtProperty(str, notify=keyChanged) def key(self): return self._key @key.setter def key(self, key: str): if self._key != key: self._logger.debug('key set -> %s' % key) self._key = key self.keyChanged.emit() self.update() labelChanged = pyqtSignal() @pyqtProperty(str, notify=labelChanged) def label(self): return self._label @pyqtSlot(str) def set_label(self, label: str): if label != self._label: self._wallet.wallet.set_label(self._key, label) self._label = label self.labelChanged.emit() @pyqtProperty(str, notify=detailsChanged) def status(self): return self._status @pyqtProperty(str, notify=detailsChanged) def date(self): return self._date @pyqtProperty(str, notify=detailsChanged) def payment_hash(self): return self._phash @pyqtProperty(str, notify=detailsChanged) def preimage(self): return self._preimage @pyqtProperty(str, notify=detailsChanged) def invoice(self): return self._invoice @pyqtProperty(QEAmount, notify=detailsChanged) def amount(self): return self._amount @pyqtProperty(QEAmount, notify=detailsChanged) def fee(self): return self._fee def update(self): if self._wallet is None: self._logger.error('wallet undefined') return if self._key not in self._wallet.wallet.lnworker.payment_info: self._logger.error('payment_hash not found') return # TODO this is horribly inefficient. need a payment getter/query method tx = self._wallet.wallet.lnworker.get_lightning_history()[bfh( self._key)] self._logger.debug(str(tx)) self._fee = QEAmount() if not tx['fee_msat'] else QEAmount( amount_msat=tx['fee_msat']) self._amount = QEAmount(amount_msat=tx['amount_msat']) self._label = tx['label'] self._date = format_time(tx['timestamp']) self._status = 'settled' # TODO: other states? get_lightning_history is deciding the filter for us :( self._phash = tx['payment_hash'] self._preimage = tx['preimage'] invoice = (self._wallet.wallet.get_invoice(self._key) or self._wallet.wallet.get_request(self._key)) self._logger.debug(str(invoice)) if invoice: self._invoice = invoice.lightning_invoice or '' else: self._invoice = '' self.detailsChanged.emit()
class QEChannelDetails(QObject, QtEventListener): _logger = get_logger(__name__) _wallet = None _channelid = None _channel = None channelChanged = pyqtSignal() channelCloseSuccess = pyqtSignal() channelCloseFailed = pyqtSignal([str], arguments=['message']) def __init__(self, parent=None): super().__init__(parent) self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) @event_listener def on_event_channel(self, wallet, channel): if wallet == self._wallet.wallet and self._channelid == channel.channel_id.hex( ): self.channelChanged.emit() def on_destroy(self): self.unregister_callbacks() walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self.walletChanged.emit() channelidChanged = pyqtSignal() @pyqtProperty(str, notify=channelidChanged) def channelid(self): return self._channelid @channelid.setter def channelid(self, channelid: str): if self._channelid != channelid: self._channelid = channelid if channelid: self.load() self.channelidChanged.emit() def load(self): lnchannels = self._wallet.wallet.lnworker.channels for channel in lnchannels.values(): #self._logger.debug('%s == %s ?' % (self._channelid, channel.channel_id)) if self._channelid == channel.channel_id.hex(): self._channel = channel self.channelChanged.emit() @pyqtProperty(str, notify=channelChanged) def name(self): if not self._channel: return return self._wallet.wallet.lnworker.get_node_alias( self._channel.node_id) or self._channel.node_id.hex() @pyqtProperty(str, notify=channelChanged) def pubkey(self): return self._channel.node_id.hex() #if self._channel else '' @pyqtProperty(str, notify=channelChanged) def short_cid(self): return self._channel.short_id_for_GUI() @pyqtProperty(str, notify=channelChanged) def state(self): return self._channel.get_state_for_GUI() @pyqtProperty(str, notify=channelChanged) def initiator(self): return 'Local' if self._channel.constraints.is_initiator else 'Remote' @pyqtProperty(QEAmount, notify=channelChanged) def capacity(self): self._capacity = QEAmount(amount_sat=self._channel.get_capacity()) return self._capacity @pyqtProperty(QEAmount, notify=channelChanged) def canSend(self): self._can_send = QEAmount( amount_sat=self._channel.available_to_spend(LOCAL) / 1000) return self._can_send @pyqtProperty(QEAmount, notify=channelChanged) def canReceive(self): self._can_receive = QEAmount( amount_sat=self._channel.available_to_spend(REMOTE) / 1000) return self._can_receive @pyqtProperty(bool, notify=channelChanged) def frozenForSending(self): return self._channel.is_frozen_for_sending() @pyqtProperty(bool, notify=channelChanged) def frozenForReceiving(self): return self._channel.is_frozen_for_receiving() @pyqtProperty(str, notify=channelChanged) def channelType(self): return self._channel.storage['channel_type'].name_minimal @pyqtProperty(bool, notify=channelChanged) def isOpen(self): return self._channel.is_open() @pyqtProperty(bool, notify=channelChanged) def canClose(self): return self.canCoopClose or self.canForceClose @pyqtProperty(bool, notify=channelChanged) def canCoopClose(self): return ChanCloseOption.COOP_CLOSE in self._channel.get_close_options() @pyqtProperty(bool, notify=channelChanged) def canForceClose(self): return ChanCloseOption.LOCAL_FCLOSE in self._channel.get_close_options( ) @pyqtProperty(bool, notify=channelChanged) def canDelete(self): return self._channel.can_be_deleted() @pyqtProperty(str, notify=channelChanged) def message_force_close(self, notify=channelChanged): return _(messages.MSG_REQUEST_FORCE_CLOSE) @pyqtSlot() def freezeForSending(self): lnworker = self._channel.lnworker if lnworker.channel_db or lnworker.is_trampoline_peer( self._channel.node_id): self._channel.set_frozen_for_sending(not self.frozenForSending) self.channelChanged.emit() else: self._logger.debug( messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP) @pyqtSlot() def freezeForReceiving(self): lnworker = self._channel.lnworker if lnworker.channel_db or lnworker.is_trampoline_peer( self._channel.node_id): self._channel.set_frozen_for_receiving(not self.frozenForReceiving) self.channelChanged.emit() else: self._logger.debug( messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP) # this method assumes the qobject is not destroyed before the close either fails or succeeds @pyqtSlot(str) def close_channel(self, closetype): async def do_close(closetype, channel_id): try: if closetype == 'remote_force': await self._wallet.wallet.lnworker.request_force_close( channel_id) elif closetype == 'local_force': await self._wallet.wallet.lnworker.force_close_channel( channel_id) else: await self._wallet.wallet.lnworker.close_channel(channel_id ) self.channelCloseSuccess.emit() except Exception as e: self._logger.exception("Could not close channel: " + repr(e)) self.channelCloseFailed.emit( _('Could not close channel: ') + repr(e)) loop = self._wallet.wallet.network.asyncio_loop coro = do_close(closetype, self._channel.channel_id) asyncio.run_coroutine_threadsafe(coro, loop) @pyqtSlot() def deleteChannel(self): self._wallet.wallet.lnworker.remove_channel(self._channel.channel_id)
class QEWallet(AuthMixin, QObject, QtEventListener): __instances = [] # this factory method should be used to instantiate QEWallet # so we have only one QEWallet for each electrum.wallet @classmethod def getInstanceFor(cls, wallet): for i in cls.__instances: if i.wallet == wallet: return i i = QEWallet(wallet) cls.__instances.append(i) return i _logger = get_logger(__name__) # emitted when wallet wants to display a user notification # actual presentation should be handled on app or window level userNotify = pyqtSignal(object, object) # shared signal for many static wallet properties dataChanged = pyqtSignal() isUptodateChanged = pyqtSignal() requestStatusChanged = pyqtSignal([str, int], arguments=['key', 'status']) requestCreateSuccess = pyqtSignal() requestCreateError = pyqtSignal([str, str], arguments=['code', 'error']) invoiceStatusChanged = pyqtSignal([str, int], arguments=['key', 'status']) invoiceCreateSuccess = pyqtSignal() invoiceCreateError = pyqtSignal([str, str], arguments=['code', 'error']) paymentSucceeded = pyqtSignal([str], arguments=['key']) paymentFailed = pyqtSignal([str, str], arguments=['key', 'reason']) requestNewPassword = pyqtSignal() _network_signal = pyqtSignal(str, object) def __init__(self, wallet, parent=None): super().__init__(parent) self.wallet = wallet self._historyModel = None self._addressModel = None self._requestModel = None self._invoiceModel = None self._channelModel = None self.tx_notification_queue = queue.Queue() self.tx_notification_last_time = 0 self.notification_timer = QTimer(self) self.notification_timer.setSingleShot(False) self.notification_timer.setInterval(500) # msec self.notification_timer.timeout.connect(self.notify_transactions) # To avoid leaking references to "self" that prevent the # window from being GC-ed when closed, callbacks should be # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) @pyqtProperty(bool, notify=isUptodateChanged) def isUptodate(self): return self.wallet.is_up_to_date() @event_listener def on_event_status(self, *args, **kwargs): self.isUptodateChanged.emit() @event_listener def on_event_request_status(self, wallet, key, status): if wallet == self.wallet: self._logger.debug('request status %d for key %s' % (status, key)) self.requestStatusChanged.emit(key, status) @event_listener def on_event_invoice_status(self, wallet, key): if wallet == self.wallet: self._logger.debug('invoice status update for key %s' % key) # FIXME event doesn't pass the new status, so we need to retrieve invoice = self.wallet.get_invoice(key) if invoice: status = self.wallet.get_invoice_status(invoice) self.invoiceStatusChanged.emit(key, status) else: self._logger.debug(f'No invoice found for key {key}') @qt_event_listener def on_event_new_transaction(self, *args): wallet, tx = args if wallet == self.wallet: self.add_tx_notification(tx) self.historyModel.init_model() # TODO: be less dramatic @qt_event_listener def on_event_verified(self, wallet, txid, info): if wallet == self.wallet: self.historyModel.update_tx(txid, info) @event_listener def on_event_wallet_updated(self, wallet): if wallet == self.wallet: self._logger.debug('wallet %s updated' % str(wallet)) self.balanceChanged.emit() @event_listener def on_event_channel(self, wallet, channel): if wallet == self.wallet: self.balanceChanged.emit() @event_listener def on_event_channels_updated(self, wallet): if wallet == self.wallet: self.balanceChanged.emit() @qt_event_listener def on_event_payment_succeeded(self, wallet, key): if wallet == self.wallet: self.paymentSucceeded.emit(key) self.historyModel.init_model() # TODO: be less dramatic @event_listener def on_event_payment_failed(self, wallet, key, reason): if wallet == self.wallet: self.paymentFailed.emit(key, reason) def on_destroy(self): self.unregister_callbacks() def add_tx_notification(self, tx): self._logger.debug('new transaction event') self.tx_notification_queue.put(tx) if not self.notification_timer.isActive(): self._logger.debug('starting wallet notification timer') self.notification_timer.start() def notify_transactions(self): if self.tx_notification_queue.qsize() == 0: self._logger.debug( 'queue empty, stopping wallet notification timer') self.notification_timer.stop() return if not self.wallet.is_up_to_date(): return # no notifications while syncing now = time.time() rate_limit = 20 # seconds if self.tx_notification_last_time + rate_limit > now: return self.tx_notification_last_time = now self._logger.info("Notifying app about new transactions") txns = [] while True: try: txns.append(self.tx_notification_queue.get_nowait()) except queue.Empty: break config = self.wallet.config # Combine the transactions if there are at least three if len(txns) >= 3: total_amount = 0 for tx in txns: tx_wallet_delta = self.wallet.get_wallet_delta(tx) if not tx_wallet_delta.is_relevant: continue total_amount += tx_wallet_delta.delta self.userNotify.emit( self.wallet, _("{} new transactions: Total amount received in the new transactions {}" ).format(len(txns), config.format_amount_and_units(total_amount))) else: for tx in txns: tx_wallet_delta = self.wallet.get_wallet_delta(tx) if not tx_wallet_delta.is_relevant: continue self.userNotify.emit( self.wallet, _("New transaction: {}").format( config.format_amount_and_units(tx_wallet_delta.delta))) historyModelChanged = pyqtSignal() @pyqtProperty(QETransactionListModel, notify=historyModelChanged) def historyModel(self): if self._historyModel is None: self._historyModel = QETransactionListModel(self.wallet) return self._historyModel addressModelChanged = pyqtSignal() @pyqtProperty(QEAddressListModel, notify=addressModelChanged) def addressModel(self): if self._addressModel is None: self._addressModel = QEAddressListModel(self.wallet) return self._addressModel requestModelChanged = pyqtSignal() @pyqtProperty(QERequestListModel, notify=requestModelChanged) def requestModel(self): if self._requestModel is None: self._requestModel = QERequestListModel(self.wallet) return self._requestModel invoiceModelChanged = pyqtSignal() @pyqtProperty(QEInvoiceListModel, notify=invoiceModelChanged) def invoiceModel(self): if self._invoiceModel is None: self._invoiceModel = QEInvoiceListModel(self.wallet) return self._invoiceModel channelModelChanged = pyqtSignal() @pyqtProperty(QEChannelListModel, notify=channelModelChanged) def channelModel(self): if self._channelModel is None: self._channelModel = QEChannelListModel(self.wallet) return self._channelModel nameChanged = pyqtSignal() @pyqtProperty(str, notify=nameChanged) def name(self): return self.wallet.basename() isLightningChanged = pyqtSignal() @pyqtProperty(bool, notify=isLightningChanged) def isLightning(self): return bool(self.wallet.lnworker) @pyqtProperty(bool, notify=dataChanged) def canHaveLightning(self): return self.wallet.can_have_lightning() @pyqtProperty(bool, notify=dataChanged) def hasSeed(self): return self.wallet.has_seed() @pyqtProperty(str, notify=dataChanged) def txinType(self): return self.wallet.get_txin_type(self.wallet.dummy_address()) @pyqtProperty(bool, notify=dataChanged) def isWatchOnly(self): return self.wallet.is_watching_only() @pyqtProperty(bool, notify=dataChanged) def isDeterministic(self): return self.wallet.is_deterministic() @pyqtProperty(bool, notify=dataChanged) def isEncrypted(self): return self.wallet.storage.is_encrypted() @pyqtProperty(bool, notify=dataChanged) def isHardware(self): return self.wallet.storage.is_encrypted_with_hw_device() @pyqtProperty(str, notify=dataChanged) def derivationPrefix(self): keystores = self.wallet.get_keystores() if len(keystores) > 1: self._logger.debug('multiple keystores not supported yet') return keystores[0].get_derivation_prefix() @pyqtProperty(str, notify=dataChanged) def masterPubkey(self): return self.wallet.get_master_public_key() balanceChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=balanceChanged) def frozenBalance(self): c, u, x = self.wallet.get_frozen_balance() self._frozenbalance = QEAmount(amount_sat=c + x) return self._frozenbalance @pyqtProperty(QEAmount, notify=balanceChanged) def unconfirmedBalance(self): self._unconfirmedbalance = QEAmount( amount_sat=self.wallet.get_balance()[1]) return self._unconfirmedbalance @pyqtProperty(QEAmount, notify=balanceChanged) def confirmedBalance(self): c, u, x = self.wallet.get_balance() self._confirmedbalance = QEAmount(amount_sat=c + x) return self._confirmedbalance @pyqtProperty(QEAmount, notify=balanceChanged) def lightningBalance(self): if not self.isLightning: self._lightningbalance = QEAmount() else: self._lightningbalance = QEAmount( amount_sat=int(self.wallet.lnworker.get_balance())) return self._lightningbalance @pyqtProperty(QEAmount, notify=balanceChanged) def lightningCanSend(self): if not self.isLightning: self._lightningcansend = QEAmount() else: self._lightningcansend = QEAmount( amount_sat=int(self.wallet.lnworker.num_sats_can_send())) return self._lightningcansend @pyqtProperty(QEAmount, notify=balanceChanged) def lightningCanReceive(self): if not self.isLightning: self._lightningcanreceive = QEAmount() else: self._lightningcanreceive = QEAmount( amount_sat=int(self.wallet.lnworker.num_sats_can_receive())) return self._lightningcanreceive @pyqtSlot() def enableLightning(self): self.wallet.init_lightning( password=None) # TODO pass password if needed self.isLightningChanged.emit() @pyqtSlot(str, int, int, bool) def send_onchain(self, address, amount, fee=None, rbf=False): self._logger.info('send_onchain: %s %d' % (address, amount)) coins = self.wallet.get_spendable_coins(None) if not bitcoin.is_address(address): self._logger.warning('Invalid Bitcoin Address: ' + address) return False outputs = [PartialTxOutput.from_address_and_value(address, amount)] self._logger.info(str(outputs)) output_values = [x.value for x in outputs] if any(parse_max_spend(outval) for outval in output_values): output_value = '!' else: output_value = sum(output_values) self._logger.info(str(output_value)) # see qt/confirm_tx_dialog qt/main_window tx = self.wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=None) self._logger.info(str(tx.to_json())) use_rbf = bool(self.wallet.config.get('use_rbf', True)) tx.set_rbf(use_rbf) self.sign_and_broadcast(tx) @auth_protect def sign_and_broadcast(self, tx): def cb(result): self._logger.info('signing was succesful? %s' % str(result)) tx = self.wallet.sign_transaction(tx, None) if not tx.is_complete(): self._logger.info('tx not complete') return self.network = self.wallet.network # TODO not always defined? try: self._logger.info('running broadcast in thread') self.network.run_from_another_thread( self.network.broadcast_transaction(tx)) self._logger.info('broadcast submit done') except TxBroadcastError as e: self._logger.info(e) return except BestEffortRequestFailed as e: self._logger.info(e) return return paymentAuthRejected = pyqtSignal() def ln_auth_rejected(self): self.paymentAuthRejected.emit() @pyqtSlot(str) @auth_protect(reject='ln_auth_rejected') def pay_lightning_invoice(self, invoice_key): self._logger.debug('about to pay LN') invoice = self.wallet.get_invoice(invoice_key) assert (invoice) assert (invoice.lightning_invoice) amount_msat = invoice.get_amount_msat() def pay_thread(): try: coro = self.wallet.lnworker.pay_invoice( invoice.lightning_invoice, amount_msat=amount_msat) fut = asyncio.run_coroutine_threadsafe( coro, self.wallet.network.asyncio_loop) fut.result() except Exception as e: self.userNotify.emit(repr(e)) threading.Thread(target=pay_thread).start() def create_bitcoin_request(self, amount: int, message: str, expiration: int, ignore_gap: bool) -> Optional[str]: addr = self.wallet.get_unused_address() if addr is None: if not self.wallet.is_deterministic(): # imported wallet # TODO implement return #msg = [ #_('No more addresses in your wallet.'), ' ', #_('You are using a non-deterministic wallet, which cannot create new addresses.'), ' ', #_('If you want to create new addresses, use a deterministic wallet instead.'), '\n\n', #_('Creating a new payment request will reuse one of your addresses and overwrite an existing request. Continue anyway?'), #] #if not self.question(''.join(msg)): #return #addr = self.wallet.get_receiving_address() else: # deterministic wallet if not ignore_gap: self.requestCreateError.emit( 'gaplimit', _("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?" )) return addr = self.wallet.create_new_address(False) req_key = self.wallet.create_request(amount, message, expiration, addr) #try: #self.wallet.add_payment_request(req) #except Exception as e: #self.logger.exception('Error adding payment request') #self.requestCreateError.emit('fatal',_('Error adding payment request') + ':\n' + repr(e)) #else: ## TODO: check this flow. Only if alias is defined in config. OpenAlias? #pass ##self.sign_payment_request(addr) return req_key, addr @pyqtSlot(QEAmount, str, int) @pyqtSlot(QEAmount, str, int, bool) @pyqtSlot(QEAmount, str, int, bool, bool) def create_request(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False): # TODO: unify this method and create_bitcoin_request try: if is_lightning: if not self.wallet.lnworker.channels: self.requestCreateError.emit( 'fatal', _("You need to open a Lightning channel first.")) return # TODO maybe show a warning if amount exceeds lnworker.num_sats_can_receive (as in kivy) # TODO fallback address robustness addr = self.wallet.get_unused_address() key = self.wallet.create_request(amount.satsInt, message, expiration, addr) else: key, addr = self.create_bitcoin_request( amount.satsInt, message, expiration, ignore_gap) if not key: return self.addressModel.init_model() except InvoiceError as e: self.requestCreateError.emit( 'fatal', _('Error creating payment request') + ':\n' + str(e)) return assert key is not None self._requestModel.add_invoice(self.wallet.get_request(key)) self.requestCreateSuccess.emit() @pyqtSlot(str) def delete_request(self, key: str): self._logger.debug('delete req %s' % key) self.wallet.delete_request(key) self._requestModel.delete_invoice(key) @pyqtSlot(str, result='QVariant') def get_request(self, key: str): return self._requestModel.get_model_invoice(key) @pyqtSlot(str) def delete_invoice(self, key: str): self._logger.debug('delete inv %s' % key) self.wallet.delete_invoice(key) self._invoiceModel.delete_invoice(key) @pyqtSlot(str, result='QVariant') def get_invoice(self, key: str): return self._invoiceModel.get_model_invoice(key) @pyqtSlot(str, result=bool) def verify_password(self, password): try: self.wallet.storage.check_password(password) return True except InvalidPassword as e: return False @pyqtSlot(str) def set_password(self, password): storage = self.wallet.storage # HW wallet not supported yet if storage.is_encrypted_with_hw_device(): return try: self.wallet.update_password(self.password, password, encrypt_storage=True) self.password = password except InvalidPassword as e: self._logger.exception(repr(e))
class QEChannelListModel(QAbstractListModel, QtEventListener): _logger = get_logger(__name__) # define listmodel rolemap _ROLE_NAMES = ('cid', 'state', 'initiator', 'capacity', 'can_send', 'can_receive', 'l_csv_delay', 'r_csv_delay', 'send_frozen', 'receive_frozen', 'type', 'node_id', 'node_alias', 'short_cid', 'funding_tx') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict( zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) _network_signal = pyqtSignal(str, object) def __init__(self, wallet, parent=None): super().__init__(parent) self.wallet = wallet self.init_model() # To avoid leaking references to "self" that prevent the # window from being GC-ed when closed, callbacks should be # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) @qt_event_listener def on_event_channel(self, wallet, channel): if wallet == self.wallet: self.on_channel_updated(channel) # elif event == 'channels_updated': @qt_event_listener def on_event_channels_updated(self, wallet): if wallet == self.wallet: self.init_model() # TODO: remove/add less crude than full re-init def on_destroy(self): self.unregister_callbacks() def rowCount(self, index): return len(self.channels) def roleNames(self): return self._ROLE_MAP def data(self, index, role): tx = self.channels[index.row()] role_index = role - Qt.UserRole value = tx[self._ROLE_NAMES[role_index]] if isinstance(value, (bool, list, int, str, QEAmount)) or value is None: return value if isinstance(value, Satoshis): return value.value return str(value) def clear(self): self.beginResetModel() self.channels = [] self.endResetModel() def channel_to_model(self, lnc): lnworker = self.wallet.lnworker item = {} item['cid'] = lnc.channel_id.hex() item['node_alias'] = lnworker.get_node_alias( lnc.node_id) or lnc.node_id.hex() item['short_cid'] = lnc.short_id_for_GUI() item['state'] = lnc.get_state_for_GUI() item['state_code'] = lnc.get_state() item['capacity'] = QEAmount(amount_sat=lnc.get_capacity()) item['can_send'] = QEAmount(amount_msat=lnc.available_to_spend(LOCAL)) item['can_receive'] = QEAmount( amount_msat=lnc.available_to_spend(REMOTE)) return item numOpenChannelsChanged = pyqtSignal() @pyqtProperty(int, notify=numOpenChannelsChanged) def numOpenChannels(self): return sum([ 1 if x['state_code'] == ChannelState.OPEN else 0 for x in self.channels ]) @pyqtSlot() def init_model(self): self._logger.debug('init_model') if not self.wallet.lnworker: self._logger.warning('lnworker should be defined') return channels = [] lnchannels = self.wallet.lnworker.channels for channel in lnchannels.values(): item = self.channel_to_model(channel) channels.append(item) self.clear() self.beginInsertRows(QModelIndex(), 0, len(channels) - 1) self.channels = channels self.endInsertRows() def on_channel_updated(self, channel): i = 0 for c in self.channels: if c['cid'] == channel.channel_id.hex(): self.do_update(i, channel) break i = i + 1 def do_update(self, modelindex, channel): modelitem = self.channels[modelindex] modelitem.update(self.channel_to_model(channel)) mi = self.createIndex(modelindex, 0) self.dataChanged.emit(mi, mi, self._ROLE_KEYS) self.numOpenChannelsChanged.emit() @pyqtSlot(str) def new_channel(self, cid): self._logger.debug('new channel with cid %s' % cid) lnchannels = self.wallet.lnworker.channels for channel in lnchannels.values(): self._logger.debug(repr(channel)) if cid == channel.channel_id.hex(): item = self.channel_to_model(channel) self._logger.debug(item) self.beginInsertRows(QModelIndex(), 0, 0) self.channels.insert(0, item) self.endInsertRows() @pyqtSlot(str) def remove_channel(self, cid): self._logger.debug('remove channel with cid %s' % cid) i = 0 for channel in self.channels: if cid == channel['cid']: self._logger.debug(cid) self.beginRemoveRows(QModelIndex(), i, i) self.channels.remove(channel) self.endRemoveRows() return i = i + 1
import re from electrum import constants from electrum.plugin import BasePlugin, hook from electrum.i18n import _ from electrum.util import UserFacingException from electrum.logging import get_logger from electrum.network import Network # Introduced in Electrum v4 try: from electrum.interface import ServerAddr except: pass _logger = get_logger('plugins.bwt') plugin_dir = os.path.dirname(__file__) bwt_bin = os.path.join(plugin_dir, 'bwt') if platform.system() == 'Windows': bwt_bin = '%s.exe' % bwt_bin class BwtPlugin(BasePlugin): wallets = set() proc = None prev_settings = None def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name)
class QEBitcoin(QObject): def __init__(self, config, parent=None): super().__init__(parent) self.config = config _logger = get_logger(__name__) generatedSeedChanged = pyqtSignal() generatedSeed = '' seedValidChanged = pyqtSignal() seedValid = False seedTypeChanged = pyqtSignal() seedType = '' validationMessageChanged = pyqtSignal() validationMessage = '' @pyqtProperty('QString', notify=generatedSeedChanged) def generated_seed(self): return self.generatedSeed @pyqtProperty(bool, notify=seedValidChanged) def seed_valid(self): return self.seedValid @pyqtProperty('QString', notify=seedTypeChanged) def seed_type(self): return self.seedType @pyqtProperty('QString', notify=validationMessageChanged) def validation_message(self): return self.validationMessage @pyqtSlot() @pyqtSlot(str) @pyqtSlot(str, str) def generate_seed(self, seed_type='segwit', language='en'): self._logger.debug('generating seed of type ' + str(seed_type)) async def co_gen_seed(seed_type, language): self.generatedSeed = mnemonic.Mnemonic(language).make_seed( seed_type=seed_type) self._logger.debug('seed generated') self.generatedSeedChanged.emit() asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), get_asyncio_loop()) @pyqtSlot(str) @pyqtSlot(str, bool, bool) @pyqtSlot(str, bool, bool, str, str, str) def verify_seed(self, seed, bip39=False, slip39=False, wallet_type='standard', language='en'): self._logger.debug('bip39 ' + str(bip39)) self._logger.debug('slip39 ' + str(slip39)) seed_type = '' seed_valid = False validation_message = '' if not (bip39 or slip39): seed_type = mnemonic.seed_type(seed) if seed_type != '': seed_valid = True elif bip39: is_checksum, is_wordlist = bip39_is_checksum_valid(seed) status = ('checksum: ' + ('ok' if is_checksum else 'failed') ) if is_wordlist else 'unknown wordlist' validation_message = 'BIP39 (%s)' % status if is_checksum: seed_type = 'bip39' seed_valid = True elif slip39: # TODO: incomplete impl, this code only validates a single share. try: share = decode_mnemonic(seed) seed_type = 'slip39' validation_message = 'SLIP39: share #%d in %dof%d scheme' % ( share.group_index, share.group_threshold, share.group_count) except Slip39Error as e: validation_message = 'SLIP39: %s' % str(e) seed_valid = False # for now # cosigning seed if wallet_type != 'standard' and seed_type not in [ 'standard', 'segwit' ]: seed_type = '' seed_valid = False self.seedType = seed_type self.seedTypeChanged.emit() if self.validationMessage != validation_message: self.validationMessage = validation_message self.validationMessageChanged.emit() if self.seedValid != seed_valid: self.seedValid = seed_valid self.seedValidChanged.emit() self._logger.debug('seed verified: ' + str(seed_valid)) @pyqtSlot(str, result=bool) def verify_derivation_path(self, path): return is_bip32_derivation(path) @pyqtSlot(str, result='QVariantMap') def parse_uri(self, uri: str) -> dict: try: return parse_URI(uri) except InvalidBitcoinURI as e: return {'error': str(e)} @pyqtSlot(str, QEAmount, str, int, int, result=str) def create_bip21_uri(self, address, satoshis, message, timestamp, expiry): extra_params = {} if expiry: extra_params['time'] = str(timestamp) extra_params['exp'] = str(expiry) return create_bip21_uri(address, satoshis.satsInt, message, extra_query_params=extra_params)
from PyQt5.QtGui import QFontMetrics from electrum.i18n import _ from electrum import constants, blockchain, util from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL from electrum.network import Network from electrum.logging import get_logger from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit, PasswordLineEdit) if TYPE_CHECKING: from electrum.simple_config import SimpleConfig _logger = get_logger(__name__) protocol_names = ['TCP', 'SSL'] protocol_letters = 'ts' class NetworkDialog(QDialog): def __init__(self, network, config, network_updated_signal_obj): QDialog.__init__(self) self.setWindowTitle(_('Network')) self.setMinimumSize(500, 500) self.nlayout = NetworkChoiceLayout(network, config) self.network_updated_signal_obj = network_updated_signal_obj vbox = QVBoxLayout(self) vbox.addLayout(self.nlayout.layout()) vbox.addLayout(Buttons(CloseButton(self))) self.network_updated_signal_obj.network_updated_signal.connect(
class QEWalletDB(QObject): def __init__(self, parent=None): super().__init__(parent) from .qeapp import ElectrumQmlApplication self.daemon = ElectrumQmlApplication._daemon self.reset() _logger = get_logger(__name__) fileNotFound = pyqtSignal() pathChanged = pyqtSignal([bool], arguments=["ready"]) needsPasswordChanged = pyqtSignal() needsHWDeviceChanged = pyqtSignal() passwordChanged = pyqtSignal() validPasswordChanged = pyqtSignal() requiresSplitChanged = pyqtSignal() splitFinished = pyqtSignal() readyChanged = pyqtSignal() createError = pyqtSignal([str], arguments=["error"]) createSuccess = pyqtSignal() invalidPassword = pyqtSignal() def reset(self): self._path = None self._needsPassword = False self._needsHWDevice = False self._password = '' self._requiresSplit = False self._validPassword = True self._storage = None self._db = None self._ready = False @pyqtProperty('QString', notify=pathChanged) def path(self): return self._path @path.setter def path(self, wallet_path): if wallet_path == self._path: return self._logger.info('setting path: ' + wallet_path) self.reset() self._path = wallet_path self.pathChanged.emit(self._ready) @pyqtProperty(bool, notify=needsPasswordChanged) def needsPassword(self): return self._needsPassword @needsPassword.setter def needsPassword(self, wallet_needs_password): if wallet_needs_password == self._needsPassword: return self._needsPassword = wallet_needs_password self.needsPasswordChanged.emit() @pyqtProperty(bool, notify=needsHWDeviceChanged) def needsHWDevice(self): return self._needsHWDevice @needsHWDevice.setter def needsHWDevice(self, wallet_needs_hw_device): if wallet_needs_hw_device == self._needsHWDevice: return self._needsHWDevice = wallet_needs_hw_device self.needsHWDeviceChanged.emit() @pyqtProperty('QString', notify=passwordChanged) def password(self): return '' # no read access @password.setter def password(self, wallet_password): if wallet_password == self._password: return self._password = wallet_password self.passwordChanged.emit() @pyqtProperty(bool, notify=requiresSplitChanged) def requiresSplit(self): return self._requiresSplit @pyqtProperty(bool, notify=validPasswordChanged) def validPassword(self): return self._validPassword @validPassword.setter def validPassword(self, validPassword): if self._validPassword != validPassword: self._validPassword = validPassword self.validPasswordChanged.emit() @pyqtProperty(bool, notify=readyChanged) def ready(self): return self._ready @pyqtSlot() def verify(self): self.load_storage() if self._storage: self.load_db() @pyqtSlot() def doSplit(self): self._logger.warning('doSplit') if not self._requiresSplit: return self._db.split_accounts(self._path) self.splitFinished.emit() def load_storage(self): self._storage = WalletStorage(self._path) if not self._storage.file_exists(): self._logger.warning('file does not exist') self.fileNotFound.emit() self._storage = None return if self._storage.is_encrypted(): self.needsPassword = True try: self._storage.decrypt( '' if not self._password else self._password) self.validPassword = True except InvalidPassword as e: self.validPassword = False self.invalidPassword.emit() if not self._storage.is_past_initial_decryption(): self._storage = None def load_db(self): # needs storage accessible self._db = WalletDB(self._storage.read(), manual_upgrades=True) if self._db.requires_split(): self._logger.warning('wallet requires split') self._requiresSplit = True self.requiresSplitChanged.emit() return if self._db.get_action(): self._logger.warning( 'action pending. QML version doesn\'t support continuation of wizard' ) return if self._db.requires_upgrade(): self._logger.warning('wallet requires upgrade, upgrading') self._db.upgrade() self._db.write(self._storage) self._ready = True self.readyChanged.emit() @pyqtSlot('QJSValue', bool, str) def create_storage(self, js_data, single_password_enabled, single_password): self._logger.info('Creating wallet from wizard data') data = js_data.toVariant() self._logger.debug(str(data)) if single_password_enabled and single_password: data['encrypt'] = True data['password'] = single_password try: path = os.path.join( os.path.dirname(self.daemon.config.get_wallet_path()), data['wallet_name']) if os.path.exists(path): raise Exception('file already exists at path') storage = WalletStorage(path) if data['seed_type'] in ['old', 'standard', 'segwit']: #2fa, 2fa-segwit self._logger.debug('creating keystore from electrum seed') k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig') elif data['seed_type'] == 'bip39': self._logger.debug('creating keystore from bip39 seed') root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words']) derivation = normalize_bip32_derivation( data['derivation_path']) script = data['script_type'] if data[ 'script_type'] != 'p2pkh' else 'standard' k = keystore.from_bip43_rootseed(root_seed, derivation, xtype=script) else: raise Exception('unsupported/unknown seed_type %s' % data['seed_type']) if data['encrypt']: if k.may_have_password(): k.update_password(None, data['password']) storage.set_password( data['password'], enc_version=StorageEncryptionVersion.USER_PASSWORD) db = WalletDB('', manual_upgrades=False) db.set_keystore_encryption( bool(data['password']) and data['encrypt']) db.put('wallet_type', data['wallet_type']) db.put('seed_type', data['seed_type']) db.put('keystore', k.dump()) if k.can_have_deterministic_lightning_xprv(): db.put( 'lightning_xprv', k.get_lightning_xprv( data['password'] if data['encrypt'] else None)) db.load_plugins() db.write(storage) # minimally populate self after create self._password = data['password'] self.path = path self.createSuccess.emit() except Exception as e: self._logger.error(str(e)) self.createError.emit(str(e))