mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-03 12:30:07 +00:00
parent
e058ee2957
commit
309ba15745
5 changed files with 71 additions and 42 deletions
|
@ -310,7 +310,11 @@ class SendScreen(CScreen):
|
|||
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, message, self.payment_request, self.parsed_URI)
|
||||
return self.app.wallet.create_invoice(
|
||||
outputs=outputs,
|
||||
message=message,
|
||||
pr=self.payment_request,
|
||||
URI=self.parsed_URI)
|
||||
|
||||
def do_save(self):
|
||||
invoice = self.read_invoice()
|
||||
|
|
|
@ -1523,7 +1523,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
if self.check_send_tab_onchain_outputs_and_show_errors(outputs):
|
||||
return
|
||||
message = self.message_e.text()
|
||||
return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI)
|
||||
return self.wallet.create_invoice(
|
||||
outputs=outputs,
|
||||
message=message,
|
||||
pr=self.payment_request,
|
||||
URI=self.payto_URI)
|
||||
|
||||
def do_save_invoice(self):
|
||||
invoice = self.read_invoice()
|
||||
|
@ -1772,7 +1776,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
return
|
||||
key = pr.get_id()
|
||||
invoice = self.wallet.get_invoice(key)
|
||||
if invoice and self.wallet.get_invoice_status() == PR_PAID:
|
||||
if invoice and self.wallet.get_invoice_status(invoice) == PR_PAID:
|
||||
self.show_message("invoice already paid")
|
||||
self.do_clear()
|
||||
self.payment_request = None
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import attr
|
||||
import time
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from .json_db import StoredObject
|
||||
from .i18n import _
|
||||
|
@ -9,6 +10,9 @@ from . import constants
|
|||
from .bitcoin import COIN
|
||||
from .transaction import PartialTxOutput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .paymentrequest import PaymentRequest
|
||||
|
||||
# convention: 'invoices' = outgoing , 'request' = incoming
|
||||
|
||||
# types of payment requests
|
||||
|
@ -54,7 +58,14 @@ pr_expiration_values = {
|
|||
}
|
||||
assert PR_DEFAULT_EXPIRATION_WHEN_CREATING in pr_expiration_values
|
||||
|
||||
outputs_decoder = lambda _list: [PartialTxOutput.from_legacy_tuple(*x) for x in _list]
|
||||
|
||||
def _decode_outputs(outputs) -> List[PartialTxOutput]:
|
||||
ret = []
|
||||
for output in outputs:
|
||||
if not isinstance(output, PartialTxOutput):
|
||||
output = PartialTxOutput.from_legacy_tuple(*output)
|
||||
ret.append(output)
|
||||
return ret
|
||||
|
||||
# hack: BOLT-11 is not really clear on what an expiry of 0 means.
|
||||
# It probably interprets it as 0 seconds, so already expired...
|
||||
|
@ -86,21 +97,35 @@ class Invoice(StoredObject):
|
|||
@attr.s
|
||||
class OnchainInvoice(Invoice):
|
||||
id = attr.ib(type=str)
|
||||
outputs = attr.ib(type=list, converter=outputs_decoder)
|
||||
outputs = attr.ib(type=list, converter=_decode_outputs)
|
||||
bip70 = attr.ib(type=str) # may be None
|
||||
requestor = attr.ib(type=str) # may be None
|
||||
|
||||
def get_address(self):
|
||||
def get_address(self) -> str:
|
||||
assert len(self.outputs) == 1
|
||||
return self.outputs[0].address
|
||||
|
||||
@classmethod
|
||||
def from_bip70_payreq(cls, pr: 'PaymentRequest') -> 'OnchainInvoice':
|
||||
return OnchainInvoice(
|
||||
type=PR_TYPE_ONCHAIN,
|
||||
amount=pr.get_amount(),
|
||||
outputs=pr.get_outputs(),
|
||||
message=pr.get_memo(),
|
||||
id=pr.get_id(),
|
||||
time=pr.get_time(),
|
||||
exp=pr.get_expiration_date() - pr.get_time(),
|
||||
bip70=pr.raw.hex() if pr else None,
|
||||
requestor=pr.get_requestor(),
|
||||
)
|
||||
|
||||
@attr.s
|
||||
class LNInvoice(Invoice):
|
||||
rhash = attr.ib(type=str)
|
||||
invoice = attr.ib(type=str)
|
||||
|
||||
@classmethod
|
||||
def from_bech32(klass, invoice: str):
|
||||
def from_bech32(klass, invoice: str) -> 'LNInvoice':
|
||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||
amount = int(lnaddr.amount * COIN) if lnaddr.amount else None
|
||||
return LNInvoice(
|
||||
|
|
|
@ -29,6 +29,7 @@ import base64
|
|||
from functools import partial
|
||||
import traceback
|
||||
import sys
|
||||
from typing import Set
|
||||
|
||||
import smtplib
|
||||
import imaplib
|
||||
|
@ -48,6 +49,8 @@ from electrum.plugin import BasePlugin, hook
|
|||
from electrum.paymentrequest import PaymentRequest
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import Logger
|
||||
from electrum.wallet import Abstract_Wallet
|
||||
from electrum.invoices import OnchainInvoice
|
||||
|
||||
|
||||
class Processor(threading.Thread, Logger):
|
||||
|
@ -150,7 +153,7 @@ class Plugin(BasePlugin):
|
|||
self.processor.start()
|
||||
self.obj = QEmailSignalObject()
|
||||
self.obj.email_new_invoice_signal.connect(self.new_invoice)
|
||||
self.wallets = set()
|
||||
self.wallets = set() # type: Set[Abstract_Wallet]
|
||||
|
||||
def on_receive(self, pr_str):
|
||||
self.logger.info('received payment request')
|
||||
|
@ -166,8 +169,9 @@ class Plugin(BasePlugin):
|
|||
self.wallets -= {wallet}
|
||||
|
||||
def new_invoice(self):
|
||||
invoice = OnchainInvoice.from_bip70_payreq(self.pr)
|
||||
for wallet in self.wallets:
|
||||
wallet.invoices.add(self.pr)
|
||||
wallet.save_invoice(invoice)
|
||||
#main_window.invoice_list.update()
|
||||
|
||||
@hook
|
||||
|
|
|
@ -68,7 +68,7 @@ from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput,
|
|||
from .plugin import run_hook
|
||||
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
|
||||
TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE)
|
||||
from .invoices import Invoice, OnchainInvoice, invoice_from_json
|
||||
from .invoices import Invoice, OnchainInvoice, invoice_from_json, LNInvoice
|
||||
from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT, PR_TYPE_ONCHAIN, PR_TYPE_LN
|
||||
from .contacts import Contacts
|
||||
from .interface import NetworkException
|
||||
|
@ -248,7 +248,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
self.frozen_coins = set(db.get('frozen_coins', [])) # set of txid:vout strings
|
||||
self.fiat_value = db.get_dict('fiat_value')
|
||||
self.receive_requests = db.get_dict('payment_requests')
|
||||
self.invoices = db.get_dict('invoices')
|
||||
self.invoices = db.get_dict('invoices') # type: Dict[str, Invoice]
|
||||
self._reserved_addresses = set(db.get('reserved_addresses', []))
|
||||
|
||||
self._prepare_onchain_invoice_paid_detection()
|
||||
|
@ -656,43 +656,33 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
'txpos_in_block': hist_item.tx_mined_status.txpos,
|
||||
}
|
||||
|
||||
def create_invoice(self, outputs: List[PartialTxOutput], message, pr, URI):
|
||||
def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
|
||||
if pr:
|
||||
return OnchainInvoice.from_bip70_payreq(pr)
|
||||
if '!' in (x.value for x in outputs):
|
||||
amount = '!'
|
||||
else:
|
||||
amount = sum(x.value for x in outputs)
|
||||
outputs = [x.to_legacy_tuple() for x in outputs]
|
||||
if pr:
|
||||
invoice = OnchainInvoice(
|
||||
type = PR_TYPE_ONCHAIN,
|
||||
amount = amount,
|
||||
outputs = outputs,
|
||||
message = pr.get_memo(),
|
||||
id = pr.get_id(),
|
||||
time = pr.get_time(),
|
||||
exp = pr.get_expiration_date() - pr.get_time(),
|
||||
bip70 = pr.raw.hex() if pr else None,
|
||||
requestor = pr.get_requestor(),
|
||||
)
|
||||
else:
|
||||
invoice = OnchainInvoice(
|
||||
type = PR_TYPE_ONCHAIN,
|
||||
amount = amount,
|
||||
outputs = outputs,
|
||||
message = message,
|
||||
id = bh2u(sha256(repr(outputs))[0:16]),
|
||||
time = URI.get('time') if URI else int(time.time()),
|
||||
exp = URI.get('exp') if URI else 0,
|
||||
bip70 = None,
|
||||
requestor = None,
|
||||
)
|
||||
invoice = OnchainInvoice(
|
||||
type=PR_TYPE_ONCHAIN,
|
||||
amount=amount,
|
||||
outputs=outputs,
|
||||
message=message,
|
||||
id=bh2u(sha256(repr(outputs))[0:16]),
|
||||
time=URI.get('time') if URI else int(time.time()),
|
||||
exp=URI.get('exp') if URI else 0,
|
||||
bip70=None,
|
||||
requestor=None,
|
||||
)
|
||||
return invoice
|
||||
|
||||
def save_invoice(self, invoice: Invoice):
|
||||
def save_invoice(self, invoice: Invoice) -> None:
|
||||
invoice_type = invoice.type
|
||||
if invoice_type == PR_TYPE_LN:
|
||||
assert isinstance(invoice, LNInvoice)
|
||||
key = invoice.rhash
|
||||
elif invoice_type == PR_TYPE_ONCHAIN:
|
||||
assert isinstance(invoice, OnchainInvoice)
|
||||
key = invoice.id
|
||||
if self.is_onchain_invoice_paid(invoice):
|
||||
self.logger.info("saving invoice... but it is already paid!")
|
||||
|
@ -729,12 +719,14 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
self._invoices_from_scriptpubkey_map = defaultdict(set) # type: Dict[bytes, Set[str]]
|
||||
for invoice_key, invoice in self.invoices.items():
|
||||
if invoice.type == PR_TYPE_ONCHAIN:
|
||||
assert isinstance(invoice, OnchainInvoice)
|
||||
for txout in invoice.outputs:
|
||||
self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key)
|
||||
|
||||
def _is_onchain_invoice_paid(self, invoice: Invoice) -> Tuple[bool, Sequence[str]]:
|
||||
"""Returns whether on-chain invoice is satisfied, and list of relevant TXIDs."""
|
||||
assert invoice.type == PR_TYPE_ONCHAIN
|
||||
assert isinstance(invoice, OnchainInvoice)
|
||||
invoice_amounts = defaultdict(int) # type: Dict[bytes, int] # scriptpubkey -> value_sats
|
||||
for txo in invoice.outputs: # type: PartialTxOutput
|
||||
invoice_amounts[txo.scriptpubkey] += 1 if txo.value == '!' else txo.value
|
||||
|
@ -763,9 +755,9 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
for invoice_key in self._get_relevant_invoice_keys_for_tx(tx):
|
||||
invoice = self.invoices.get(invoice_key)
|
||||
if invoice is None: continue
|
||||
assert invoice.get('type') == PR_TYPE_ONCHAIN
|
||||
if invoice['message']:
|
||||
labels.append(invoice['message'])
|
||||
assert isinstance(invoice, OnchainInvoice)
|
||||
if invoice.message:
|
||||
labels.append(invoice.message)
|
||||
if labels:
|
||||
self.set_label(tx_hash, "; ".join(labels))
|
||||
return bool(labels)
|
||||
|
@ -1610,7 +1602,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
status = PR_EXPIRED
|
||||
return status
|
||||
|
||||
def get_invoice_status(self, invoice):
|
||||
def get_invoice_status(self, invoice: Invoice):
|
||||
if invoice.is_lightning():
|
||||
status = self.lnworker.get_invoice_status(invoice)
|
||||
else:
|
||||
|
|
Loading…
Add table
Reference in a new issue