def get_card(self, item: Invoice) -> Dict[str, Any]: status = self.app.wallet.get_invoice_status(item) status_str = item.get_status_str(status) is_lightning = item.type == PR_TYPE_LN if is_lightning: assert isinstance(item, LNInvoice) key = item.rhash address = key if self.app.wallet.lnworker: log = self.app.wallet.lnworker.logs.get(key) if status == PR_INFLIGHT and log: status_str += '... (%d)' % len(log) is_bip70 = False else: assert isinstance(item, OnchainInvoice) key = item.id address = item.get_address() is_bip70 = bool(item.bip70) return { 'is_lightning': is_lightning, 'is_bip70': is_bip70, 'screen': self, 'status': status, 'status_str': status_str, 'key': key, 'memo': item.message or _('No Description'), 'address': address, 'amount': self.app.format_amount_and_units(item.get_amount_sat() or 0), }
def get_card(self, req: Invoice) -> Dict[str, Any]: is_lightning = req.is_lightning() if not is_lightning: assert isinstance(req, OnchainInvoice) address = req.get_address() key = address else: assert isinstance(req, LNInvoice) key = req.rhash address = req.invoice amount = req.get_amount_sat() description = req.message status = self.app.wallet.get_request_status(key) status_str = req.get_status_str(status) ci = {} ci['screen'] = self ci['address'] = address ci['is_lightning'] = is_lightning ci['key'] = key ci['amount'] = self.app.format_amount_and_units( amount) if amount else '' ci['memo'] = description or _('No Description') ci['status'] = status ci['status_str'] = status_str return ci
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
def read_invoice(self): if self.check_send_tab_payto_line_and_show_errors(): return try: if not self._is_onchain: invoice_str = self.payto_e.lightning_invoice if not invoice_str: return if not self.wallet.has_lightning(): self.show_error(_('Lightning is disabled')) return invoice = Invoice.from_bech32(invoice_str) if invoice.amount_msat is None: amount_sat = self.amount_e.get_amount() if amount_sat: invoice.amount_msat = int(amount_sat * 1000) else: self.show_error(_('No amount')) return return invoice else: outputs = self.read_outputs() if self.check_send_tab_onchain_outputs_and_show_errors( outputs): return message = self.message_e.text() return self.wallet.create_invoice(outputs=outputs, message=message, pr=self.payment_request, URI=self.payto_URI) except InvoiceError as e: self.show_error(_('Error creating payment') + ':\n' + str(e))
def update_item(self, key, invoice: Invoice): model = self.std_model for row in range(0, model.rowCount()): item = model.item(row, 0) if item.data(ROLE_KEY) == key: break else: return status_item = model.item(row, self.Columns.STATUS) status = self.parent.wallet.get_request_status(key) status_str = invoice.get_status_str(status) status_item.setText(status_str) status_item.setIcon(read_QIcon(pr_icons.get(status)))
def get_card(self, req: Invoice) -> Dict[str, Any]: is_lightning = req.is_lightning() if not is_lightning: address = req.get_address() else: address = req.lightning_invoice key = self.app.wallet.get_key_for_receive_request(req) amount = req.get_amount_sat() description = req.message status = self.app.wallet.get_request_status(key) status_str = req.get_status_str(status) ci = {} ci['screen'] = self ci['address'] = address ci['is_lightning'] = is_lightning ci['key'] = key ci['amount'] = self.app.format_amount_and_units( amount) if amount else '' ci['memo'] = description or _('No Description') ci['status'] = status ci['status_str'] = status_str return ci
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 _do_pay_lightning(self, invoice: Invoice, pw) -> None: amount_msat = invoice.get_amount_msat() def pay_thread(): try: coro = self.app.wallet.lnworker.pay_invoice( invoice.lightning_invoice, amount_msat=amount_msat) fut = asyncio.run_coroutine_threadsafe( coro, self.app.network.asyncio_loop) fut.result() except Exception as e: self.app.show_error(repr(e)) self.save_invoice(invoice) threading.Thread(target=pay_thread).start()
def update_item(self, key, invoice: Invoice): model = self.std_model for row in range(0, model.rowCount()): item = model.item(row, 0) if item.data(ROLE_REQUEST_ID) == key: break else: return status_item = model.item(row, self.Columns.STATUS) status = self.parent.wallet.get_invoice_status(invoice) status_str = invoice.get_status_str(status) if self.parent.wallet.lnworker: log = self.parent.wallet.lnworker.logs.get(key) if log and status == PR_INFLIGHT: status_str += '... (%d)' % len(log) status_item.setText(status_str) status_item.setIcon(read_QIcon(pr_icons.get(status)))
def read_invoice(self): address = str(self.address) if not address: self.app.show_error( _('Recipient not specified.') + ' ' + _('Please scan a Bitcoin address or a payment request')) return if not self.amount: self.app.show_error(_('Please enter an amount')) return if self.is_max: amount = '!' else: try: amount = self.app.get_amount(self.amount) except: self.app.show_error(_('Invalid amount') + ':\n' + self.amount) return message = self.message try: if self.is_lightning: return Invoice.from_bech32(address) else: # on-chain if self.payment_request: outputs = self.payment_request.get_outputs() else: if not bitcoin.is_address(address): self.app.show_error( _('Invalid Bitcoin Address') + ':\n' + address) return outputs = [ PartialTxOutput.from_address_and_value( address, amount) ] return self.app.wallet.create_invoice(outputs=outputs, message=message, pr=self.payment_request, URI=self.parsed_URI) except InvoiceError as e: self.app.show_error(_('Error creating payment') + ':\n' + str(e))
def payment_request_ok(self): pr = self.payment_request if not pr: return invoice = Invoice.from_bip70_payreq(pr, height=0) if self.wallet.get_invoice_status(invoice) == PR_PAID: self.show_message("invoice already paid") self.do_clear() self.payment_request = None return self.payto_e.disable_checks = True if not pr.has_expired(): self.payto_e.setGreen() else: self.payto_e.setExpired() self.payto_e.setTextNoCheck(pr.get_requestor()) self.amount_e.setAmount(pr.get_amount()) self.message_e.setText(pr.get_memo()) self.set_onchain(True) self.max_button.setEnabled(False) for btn in [self.send_button, self.clear_button]: btn.setEnabled(True) # signal to set fee self.amount_e.textEdited.emit("")
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 invoice_to_model(self, invoice: Invoice): item = super().invoice_to_model(invoice) item['type'] = 'invoice' item['key'] = invoice.get_id() return item
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()
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 pay_lightning_invoice(self, invoice: Invoice): amount_sat = invoice.get_amount_sat() key = self.wallet.get_key_for_outgoing_invoice(invoice) if amount_sat is None: raise Exception("missing amount for LN invoice") if not self.wallet.lnworker.can_pay_invoice(invoice): num_sats_can_send = int(self.wallet.lnworker.num_sats_can_send()) lightning_needed = amount_sat - num_sats_can_send lightning_needed += (lightning_needed // 20 ) # operational safety margin coins = self.window.get_coins(nonlocal_only=True) can_pay_onchain = invoice.get_address( ) and self.wallet.can_pay_onchain(invoice.get_outputs(), coins=coins) can_pay_with_new_channel = self.wallet.lnworker.suggest_funding_amount( amount_sat, coins=coins) can_pay_with_swap = self.wallet.lnworker.suggest_swap_to_send( amount_sat, coins=coins) rebalance_suggestion = self.wallet.lnworker.suggest_rebalance_to_send( amount_sat) can_rebalance = bool( rebalance_suggestion) and self.window.num_tasks() == 0 choices = {} if can_rebalance: msg = ''.join([ _('Rebalance existing channels'), '\n', _('Move funds between your channels in order to increase your sending capacity.' ) ]) choices[0] = msg if can_pay_with_new_channel: msg = ''.join([ _('Open a new channel'), '\n', _('You will be able to pay once the channel is open.') ]) choices[1] = msg if can_pay_with_swap: msg = ''.join([ _('Swap onchain funds for lightning funds'), '\n', _('You will be able to pay once the swap is confirmed.') ]) choices[2] = msg if can_pay_onchain: msg = ''.join([ _('Pay onchain'), '\n', _('Funds will be sent to the invoice fallback address.') ]) choices[3] = msg if not choices: raise NotEnoughFunds() msg = _('You cannot pay that invoice using Lightning.') if self.wallet.lnworker.channels: msg += '\n' + _('Your channels can send {}.').format( self.format_amount(num_sats_can_send) + self.base_unit()) r = self.window.query_choice(msg, choices) if r is not None: self.save_pending_invoice() if r == 0: chan1, chan2, delta = rebalance_suggestion self.window.rebalance_dialog(chan1, chan2, amount_sat=delta) elif r == 1: amount_sat, min_amount_sat = can_pay_with_new_channel self.window.channels_list.new_channel_dialog( amount_sat=amount_sat, min_amount_sat=min_amount_sat) elif r == 2: chan, swap_recv_amount_sat = can_pay_with_swap self.window.run_swap_dialog( is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan]) elif r == 3: self.pay_onchain_dialog(coins, invoice.get_outputs()) return # FIXME this is currently lying to user as we truncate to satoshis amount_msat = invoice.get_amount_msat() msg = _("Pay lightning invoice?") + '\n\n' + _( "This will send {}?").format( self.format_amount_and_units(Decimal(amount_msat) / 1000)) if not self.question(msg): return self.save_pending_invoice() coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat) self.window.run_coroutine_from_thread(coro, _('Sending payment'))