You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
3.3 KiB
121 lines
3.3 KiB
import attr |
|
import time |
|
|
|
from .json_db import StoredObject |
|
from .i18n import _ |
|
from .util import age |
|
from .lnaddr import lndecode |
|
from . import constants |
|
from .bitcoin import COIN |
|
from .transaction import PartialTxOutput |
|
|
|
# convention: 'invoices' = outgoing , 'request' = incoming |
|
|
|
# types of payment requests |
|
PR_TYPE_ONCHAIN = 0 |
|
PR_TYPE_LN = 2 |
|
|
|
# status of payment requests |
|
PR_UNPAID = 0 |
|
PR_EXPIRED = 1 |
|
PR_UNKNOWN = 2 # sent but not propagated |
|
PR_PAID = 3 # send and propagated |
|
PR_INFLIGHT = 4 # unconfirmed |
|
PR_FAILED = 5 |
|
PR_ROUTING = 6 |
|
|
|
pr_color = { |
|
PR_UNPAID: (.7, .7, .7, 1), |
|
PR_PAID: (.2, .9, .2, 1), |
|
PR_UNKNOWN: (.7, .7, .7, 1), |
|
PR_EXPIRED: (.9, .2, .2, 1), |
|
PR_INFLIGHT: (.9, .6, .3, 1), |
|
PR_FAILED: (.9, .2, .2, 1), |
|
PR_ROUTING: (.9, .6, .3, 1), |
|
} |
|
|
|
pr_tooltips = { |
|
PR_UNPAID:_('Pending'), |
|
PR_PAID:_('Paid'), |
|
PR_UNKNOWN:_('Unknown'), |
|
PR_EXPIRED:_('Expired'), |
|
PR_INFLIGHT:_('In progress'), |
|
PR_FAILED:_('Failed'), |
|
PR_ROUTING: _('Computing route...'), |
|
} |
|
|
|
PR_DEFAULT_EXPIRATION_WHEN_CREATING = 24*60*60 # 1 day |
|
pr_expiration_values = { |
|
0: _('Never'), |
|
10*60: _('10 minutes'), |
|
60*60: _('1 hour'), |
|
24*60*60: _('1 day'), |
|
7*24*60*60: _('1 week'), |
|
} |
|
assert PR_DEFAULT_EXPIRATION_WHEN_CREATING in pr_expiration_values |
|
|
|
outputs_decoder = lambda _list: [PartialTxOutput.from_legacy_tuple(*x) for x in _list] |
|
|
|
# hack: BOLT-11 is not really clear on what an expiry of 0 means. |
|
# It probably interprets it as 0 seconds, so already expired... |
|
# Our higher level invoices code however uses 0 for "never". |
|
# Hence set some high expiration here |
|
LN_EXPIRY_NEVER = 100 * 365 * 24 * 60 * 60 # 100 years |
|
|
|
@attr.s |
|
class Invoice(StoredObject): |
|
type = attr.ib(type=int) |
|
message = attr.ib(type=str) |
|
amount = attr.ib(type=int) |
|
exp = attr.ib(type=int) |
|
time = attr.ib(type=int) |
|
|
|
def is_lightning(self): |
|
return self.type == PR_TYPE_LN |
|
|
|
def get_status_str(self, status): |
|
status_str = pr_tooltips[status] |
|
if status == PR_UNPAID: |
|
if self.exp > 0 and self.exp != LN_EXPIRY_NEVER: |
|
expiration = self.exp + self.time |
|
status_str = _('Expires') + ' ' + age(expiration, include_seconds=True) |
|
else: |
|
status_str = _('Pending') |
|
return status_str |
|
|
|
@attr.s |
|
class OnchainInvoice(Invoice): |
|
id = attr.ib(type=str) |
|
outputs = attr.ib(type=list, converter=outputs_decoder) |
|
bip70 = attr.ib(type=str) # may be None |
|
requestor = attr.ib(type=str) # may be None |
|
|
|
def get_address(self): |
|
assert len(self.outputs) == 1 |
|
return self.outputs[0].address |
|
|
|
@attr.s |
|
class LNInvoice(Invoice): |
|
rhash = attr.ib(type=str) |
|
invoice = attr.ib(type=str) |
|
|
|
@classmethod |
|
def from_bech32(klass, invoice: str): |
|
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) |
|
amount = int(lnaddr.amount * COIN) if lnaddr.amount else None |
|
return LNInvoice( |
|
type = PR_TYPE_LN, |
|
amount = amount, |
|
message = lnaddr.get_description(), |
|
time = lnaddr.date, |
|
exp = lnaddr.get_expiry(), |
|
rhash = lnaddr.paymenthash.hex(), |
|
invoice = invoice, |
|
) |
|
|
|
|
|
def invoice_from_json(x: dict) -> Invoice: |
|
if x.get('type') == PR_TYPE_LN: |
|
return LNInvoice(**x) |
|
else: |
|
return OnchainInvoice(**x)
|
|
|