Exemple #1
0
 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),
     }
Exemple #2
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
Exemple #3
0
    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
Exemple #4
0
 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))
Exemple #5
0
 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)))
Exemple #6
0
 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
Exemple #7
0
    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()
Exemple #8
0
    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()
Exemple #9
0
 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)))
Exemple #10
0
 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))
Exemple #11
0
 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("")
Exemple #12
0
    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
Exemple #13
0
    def invoice_to_model(self, invoice: Invoice):
        item = super().invoice_to_model(invoice)
        item['type'] = 'invoice'
        item['key'] = invoice.get_id()

        return item
Exemple #14
0
    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()
Exemple #15
0
 def setValidLightningInvoice(self, invoice: Invoice):
     self._logger.debug('setValidLightningInvoice')
     if not invoice.is_lightning():
         raise Exception('unexpected Onchain invoice')
     self.set_effective_invoice(invoice)
Exemple #16
0
    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'))