Display and refresh the status of incoming payment requests:
- All requests have an expiration date - Paid requests are automatically removed from the list - Unpaid, unconfirmed and expired requests are displayed - Fix a bug in get_payment_status, conf was off by one
This commit is contained in:
@@ -670,14 +670,9 @@ class Commands:
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def _format_request(self, out):
|
||||
pr_str = {
|
||||
PR_UNKNOWN: 'Unknown',
|
||||
PR_UNPAID: 'Pending',
|
||||
PR_PAID: 'Paid',
|
||||
PR_EXPIRED: 'Expired',
|
||||
}
|
||||
from .util import get_request_status
|
||||
out['amount_BTC'] = format_satoshis(out.get('amount'))
|
||||
out['status'] = pr_str[out.get('status', PR_UNKNOWN)]
|
||||
out['status'] = get_request_status(out)
|
||||
return out
|
||||
|
||||
@command('w')
|
||||
@@ -850,9 +845,9 @@ class Commands:
|
||||
return await self.lnworker._pay(invoice, attempts=attempts)
|
||||
|
||||
@command('wn')
|
||||
async def addinvoice(self, requested_amount, message):
|
||||
async def addinvoice(self, requested_amount, message, expiration=3600):
|
||||
# using requested_amount because it is documented in param_descriptions
|
||||
payment_hash = await self.lnworker._add_invoice_coro(satoshis(requested_amount), message)
|
||||
payment_hash = await self.lnworker._add_invoice_coro(satoshis(requested_amount), message, expiration)
|
||||
invoice, direction, is_paid = self.lnworker.invoices[bh2u(payment_hash)]
|
||||
return invoice
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import asyncio
|
||||
from weakref import ref
|
||||
from decimal import Decimal
|
||||
import re
|
||||
import datetime
|
||||
import threading
|
||||
import traceback, sys
|
||||
from enum import Enum, auto
|
||||
@@ -27,7 +26,7 @@ from electrum.util import profiler, parse_URI, format_time, InvalidPassword, Not
|
||||
from electrum import bitcoin, constants
|
||||
from electrum.transaction import TxOutput, Transaction, tx_from_str
|
||||
from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI
|
||||
from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED, TxMinedInfo, age
|
||||
from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED, TxMinedInfo, get_request_status, pr_expiration_values
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.wallet import InternalAddressCorruption
|
||||
from electrum import simple_config
|
||||
@@ -404,12 +403,14 @@ class SendScreen(CScreen):
|
||||
class ReceiveScreen(CScreen):
|
||||
|
||||
kvname = 'receive'
|
||||
cards = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(ReceiveScreen, self).__init__(**kwargs)
|
||||
self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]
|
||||
self.expiration = self.app.electrum_config.get('request_expiration', 3600) # 1 hour
|
||||
Clock.schedule_interval(lambda dt: self.update(), 5)
|
||||
|
||||
def expiry(self):
|
||||
return self.app.electrum_config.get('request_expiry', 3600) # 1 hour
|
||||
|
||||
def clear(self):
|
||||
self.screen.address = ''
|
||||
@@ -452,9 +453,8 @@ class ReceiveScreen(CScreen):
|
||||
amount = self.screen.amount
|
||||
amount = self.app.get_amount(amount) if amount else 0
|
||||
message = self.screen.message
|
||||
expiration = self.expiration
|
||||
if lightning:
|
||||
payment_hash = self.app.wallet.lnworker.add_invoice(amount, message)
|
||||
payment_hash = self.app.wallet.lnworker.add_invoice(amount, message, self.expiry())
|
||||
request, direction, is_paid = self.app.wallet.lnworker.invoices.get(payment_hash.hex())
|
||||
key = payment_hash.hex()
|
||||
else:
|
||||
@@ -463,40 +463,37 @@ class ReceiveScreen(CScreen):
|
||||
self.app.show_info(_('No address available. Please remove some of your pending requests.'))
|
||||
return
|
||||
self.screen.address = addr
|
||||
req = self.app.wallet.make_payment_request(addr, amount, message, expiration)
|
||||
req = self.app.wallet.make_payment_request(addr, amount, message, self.expiry())
|
||||
self.app.wallet.add_payment_request(req, self.app.electrum_config)
|
||||
key = addr
|
||||
self.clear()
|
||||
self.update()
|
||||
self.app.show_request(lightning, key)
|
||||
|
||||
def get_card(self, req):
|
||||
is_lightning = req.get('lightning', False)
|
||||
status = req['status']
|
||||
#if status != PR_UNPAID:
|
||||
# continue
|
||||
if not is_lightning:
|
||||
address = req['address']
|
||||
key = address
|
||||
else:
|
||||
key = req['rhash']
|
||||
address = req['invoice']
|
||||
timestamp = req.get('time', 0)
|
||||
amount = req.get('amount')
|
||||
description = req.get('memo', '')
|
||||
ci = self.cards.get(key)
|
||||
if ci is None:
|
||||
ci = {}
|
||||
ci['address'] = address
|
||||
ci['is_lightning'] = is_lightning
|
||||
ci['key'] = key
|
||||
ci['screen'] = self
|
||||
self.cards[key] = ci
|
||||
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
|
||||
ci['status'] = age(timestamp)
|
||||
ci['status'] = get_request_status(req)
|
||||
ci['is_expired'] = req['status'] == PR_EXPIRED
|
||||
return ci
|
||||
|
||||
def update(self):
|
||||
if not self.loaded:
|
||||
return
|
||||
_list = self.app.wallet.get_sorted_requests(self.app.electrum_config)
|
||||
requests_container = self.screen.ids.requests_container
|
||||
requests_container.data = [self.get_card(item) for item in _list if item.get('status') != PR_PAID]
|
||||
@@ -507,16 +504,9 @@ class ReceiveScreen(CScreen):
|
||||
|
||||
def expiration_dialog(self, obj):
|
||||
from .dialogs.choice_dialog import ChoiceDialog
|
||||
choices = {
|
||||
10*60: _('10 minutes'),
|
||||
60*60: _('1 hour'),
|
||||
24*60*60: _('1 day'),
|
||||
7*24*60*60: _('1 week')
|
||||
}
|
||||
def callback(c):
|
||||
self.expiration = c
|
||||
self.app.electrum_config.set_key('request_expiration', c)
|
||||
d = ChoiceDialog(_('Expiration date'), choices, self.expiration, callback)
|
||||
self.app.electrum_config.set_key('request_expiry', c)
|
||||
d = ChoiceDialog(_('Expiration date'), pr_expiration_values, self.expiry(), callback)
|
||||
d.open()
|
||||
|
||||
def do_delete(self, req):
|
||||
|
||||
@@ -13,29 +13,22 @@
|
||||
valign: 'top'
|
||||
|
||||
<RequestItem@CardItem>
|
||||
is_expired: False
|
||||
address: ''
|
||||
memo: ''
|
||||
amount: ''
|
||||
status: ''
|
||||
date: ''
|
||||
icon: 'atlas://electrum/gui/kivy/theming/light/important'
|
||||
Image:
|
||||
id: icon
|
||||
source: root.icon
|
||||
size_hint: None, 1
|
||||
width: self.height *.54
|
||||
mipmap: True
|
||||
BoxLayout:
|
||||
spacing: '8dp'
|
||||
height: '32dp'
|
||||
orientation: 'vertical'
|
||||
Widget
|
||||
RequestLabel:
|
||||
text: root.address
|
||||
text: root.memo
|
||||
shorten: True
|
||||
Widget
|
||||
RequestLabel:
|
||||
text: root.memo
|
||||
text: root.address
|
||||
color: .699, .699, .699, 1
|
||||
font_size: '13sp'
|
||||
shorten: True
|
||||
@@ -54,7 +47,7 @@
|
||||
text: root.status
|
||||
halign: 'right'
|
||||
font_size: '13sp'
|
||||
color: .699, .699, .699, 1
|
||||
color: (1., .2, .2, 1) if root.is_expired else (.7, .7, .7, 1)
|
||||
Widget
|
||||
|
||||
<RequestRecycleView>:
|
||||
@@ -75,7 +68,6 @@ ReceiveScreen:
|
||||
message: ''
|
||||
status: ''
|
||||
is_lightning: False
|
||||
show_list: True
|
||||
|
||||
BoxLayout
|
||||
padding: '12dp', '12dp', '12dp', '12dp'
|
||||
@@ -100,7 +92,6 @@ ReceiveScreen:
|
||||
text: _('Lightning') if root.is_lightning else (s.address if s.address else _('Bitcoin Address'))
|
||||
shorten: True
|
||||
on_release: root.is_lightning = not root.is_lightning
|
||||
#on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
|
||||
CardSeparator:
|
||||
opacity: message_selection.opacity
|
||||
color: blue_bottom.foreground_color
|
||||
@@ -144,7 +135,7 @@ ReceiveScreen:
|
||||
icon: 'atlas://electrum/gui/kivy/theming/light/list'
|
||||
size_hint: 0.5, None
|
||||
height: '48dp'
|
||||
on_release: root.show_list = not root.show_list
|
||||
on_release: Clock.schedule_once(lambda dt: app.addresses_dialog())
|
||||
IconButton:
|
||||
icon: 'atlas://electrum/gui/kivy/theming/light/clock1'
|
||||
size_hint: 0.5, None
|
||||
@@ -166,5 +157,3 @@ ReceiveScreen:
|
||||
id: requests_container
|
||||
scroll_type: ['bars', 'content']
|
||||
bar_width: '25dp'
|
||||
opacity: 1 if root.show_list else 0
|
||||
disabled: not root.show_list
|
||||
|
||||
@@ -73,6 +73,7 @@ from electrum.exchange_rate import FxThread
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.logging import Logger
|
||||
from electrum.paymentrequest import PR_PAID
|
||||
from electrum.util import pr_expiration_values
|
||||
|
||||
from .exception_window import Exception_Hook
|
||||
from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
|
||||
@@ -83,7 +84,7 @@ from .fee_slider import FeeSlider
|
||||
from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog,
|
||||
WindowModalDialog, ChoicesLayout, HelpLabel, FromList, Buttons,
|
||||
OkButton, InfoButton, WWLabel, TaskThread, CancelButton,
|
||||
CloseButton, HelpButton, MessageBoxMixin, EnterButton, expiration_values,
|
||||
CloseButton, HelpButton, MessageBoxMixin, EnterButton,
|
||||
ButtonsLineEdit, CopyCloseButton, import_meta_gui, export_meta_gui,
|
||||
filename_field, address_field, char_width_in_lineedit, webopen)
|
||||
from .util import ButtonsTextEdit
|
||||
@@ -753,6 +754,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
return fileName
|
||||
|
||||
def timer_actions(self):
|
||||
self.request_list.refresh_status()
|
||||
# Note this runs in the GUI thread
|
||||
if self.need_update.is_set():
|
||||
self.need_update.clear()
|
||||
@@ -945,9 +947,20 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
|
||||
|
||||
self.expires_combo = QComboBox()
|
||||
self.expires_combo.addItems([i[0] for i in expiration_values])
|
||||
self.expires_combo.setCurrentIndex(3)
|
||||
evl = sorted(pr_expiration_values.items())
|
||||
evl_keys = [i[0] for i in evl]
|
||||
evl_values = [i[1] for i in evl]
|
||||
default_expiry = self.config.get('request_expiry', 3600)
|
||||
try:
|
||||
i = evl_keys.index(default_expiry)
|
||||
except ValueError:
|
||||
i = 0
|
||||
self.expires_combo.addItems(evl_values)
|
||||
self.expires_combo.setCurrentIndex(i)
|
||||
self.expires_combo.setFixedWidth(self.receive_amount_e.width())
|
||||
def on_expiry(i):
|
||||
self.config.set_key('request_expiry', evl_keys[i])
|
||||
self.expires_combo.currentIndexChanged.connect(on_expiry)
|
||||
msg = ' '.join([
|
||||
_('Expiration date of your request.'),
|
||||
_('This information is seen by the recipient if you send them a signed payment request.'),
|
||||
@@ -1057,13 +1070,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
def create_invoice(self, is_lightning):
|
||||
amount = self.receive_amount_e.get_amount()
|
||||
message = self.receive_message_e.text()
|
||||
i = self.expires_combo.currentIndex()
|
||||
expiration = list(map(lambda x: x[1], expiration_values))[i]
|
||||
expiry = self.config.get('request_expiry', 3600)
|
||||
if is_lightning:
|
||||
payment_hash = self.wallet.lnworker.add_invoice(amount, message)
|
||||
payment_hash = self.wallet.lnworker.add_invoice(amount, message, expiry)
|
||||
key = bh2u(payment_hash)
|
||||
else:
|
||||
key = self.create_bitcoin_request(amount, message, expiration)
|
||||
key = self.create_bitcoin_request(amount, message, expiry)
|
||||
self.address_list.update()
|
||||
self.request_list.update()
|
||||
self.request_list.select_key(key)
|
||||
|
||||
@@ -30,7 +30,7 @@ from PyQt5.QtWidgets import QMenu, QHeaderView
|
||||
from PyQt5.QtCore import Qt, QItemSelectionModel
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import format_time, age
|
||||
from electrum.util import format_time, age, get_request_status
|
||||
from electrum.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT, pr_tooltips
|
||||
from electrum.lnutil import SENT, RECEIVED
|
||||
from electrum.plugin import run_hook
|
||||
@@ -85,20 +85,28 @@ class RequestList(MyTreeView):
|
||||
item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
|
||||
request_type = item.data(ROLE_REQUEST_TYPE)
|
||||
key = item.data(ROLE_RHASH_OR_ADDR)
|
||||
if request_type == REQUEST_TYPE_BITCOIN:
|
||||
req = self.wallet.receive_requests.get(key)
|
||||
if req is None:
|
||||
self.update()
|
||||
return
|
||||
req = self.wallet.get_request_URI(key)
|
||||
elif request_type == REQUEST_TYPE_LN:
|
||||
req, direction, is_paid = self.wallet.lnworker.invoices.get(key) or (None, None, None)
|
||||
if req is None:
|
||||
self.update()
|
||||
return
|
||||
else:
|
||||
raise Exception(f"unknown request type: {request_type}")
|
||||
self.parent.receive_address_e.setText(req)
|
||||
is_lightning = request_type == REQUEST_TYPE_LN
|
||||
req = self.wallet.get_request(key, is_lightning)
|
||||
if req is None:
|
||||
self.update()
|
||||
return
|
||||
text = req.get('invoice') if is_lightning else req.get('URI')
|
||||
self.parent.receive_address_e.setText(text)
|
||||
|
||||
def refresh_status(self):
|
||||
m = self.model()
|
||||
for r in range(m.rowCount()):
|
||||
idx = m.index(r, self.Columns.STATUS)
|
||||
date_idx = idx.sibling(idx.row(), self.Columns.DATE)
|
||||
date_item = m.itemFromIndex(date_idx)
|
||||
status_item = m.itemFromIndex(idx)
|
||||
key = date_item.data(ROLE_RHASH_OR_ADDR)
|
||||
is_lightning = date_item.data(ROLE_REQUEST_TYPE) == REQUEST_TYPE_LN
|
||||
req = self.wallet.get_request(key, is_lightning)
|
||||
if req:
|
||||
status_str = get_request_status(req)
|
||||
status_item.setText(status_str)
|
||||
status_item.setIcon(read_QIcon(pr_icons.get(req['status'])))
|
||||
|
||||
def update(self):
|
||||
self.wallet = self.parent.wallet
|
||||
@@ -116,7 +124,8 @@ class RequestList(MyTreeView):
|
||||
message = req['memo']
|
||||
date = format_time(timestamp)
|
||||
amount_str = self.parent.format_amount(amount) if amount else ""
|
||||
labels = [date, message, amount_str, pr_tooltips.get(status,'')]
|
||||
status_str = get_request_status(req)
|
||||
labels = [date, message, amount_str, status_str]
|
||||
items = [QStandardItem(e) for e in labels]
|
||||
self.set_editability(items)
|
||||
items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
|
||||
|
||||
@@ -45,16 +45,10 @@ pr_icons = {
|
||||
PR_UNPAID:"unpaid.png",
|
||||
PR_PAID:"confirmed.png",
|
||||
PR_EXPIRED:"expired.png",
|
||||
PR_INFLIGHT:"lightning.png",
|
||||
PR_INFLIGHT:"unconfirmed.png",
|
||||
}
|
||||
|
||||
|
||||
expiration_values = [
|
||||
(_('1 hour'), 60*60),
|
||||
(_('1 day'), 24*60*60),
|
||||
(_('1 week'), 7*24*60*60),
|
||||
(_('Never'), None)
|
||||
]
|
||||
|
||||
|
||||
class EnterButton(QPushButton):
|
||||
|
||||
@@ -868,8 +868,8 @@ class LNWallet(LNWorker):
|
||||
raise PaymentFailure(_("No path found"))
|
||||
return route
|
||||
|
||||
def add_invoice(self, amount_sat, message):
|
||||
coro = self._add_invoice_coro(amount_sat, message)
|
||||
def add_invoice(self, amount_sat, message, expiry):
|
||||
coro = self._add_invoice_coro(amount_sat, message, expiry)
|
||||
fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
|
||||
try:
|
||||
return fut.result(timeout=5)
|
||||
@@ -877,7 +877,7 @@ class LNWallet(LNWorker):
|
||||
raise Exception(_("add_invoice timed out"))
|
||||
|
||||
@log_exceptions
|
||||
async def _add_invoice_coro(self, amount_sat, message):
|
||||
async def _add_invoice_coro(self, amount_sat, message, expiry):
|
||||
payment_preimage = os.urandom(32)
|
||||
payment_hash = sha256(payment_preimage)
|
||||
amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
|
||||
@@ -887,7 +887,8 @@ class LNWallet(LNWorker):
|
||||
"Other clients will likely not be able to send to us.")
|
||||
invoice = lnencode(LnAddr(payment_hash, amount_btc,
|
||||
tags=[('d', message),
|
||||
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)]
|
||||
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
|
||||
('x', expiry)]
|
||||
+ routing_hints),
|
||||
self.node_keypair.privkey)
|
||||
self.save_invoice(payment_hash, invoice, RECEIVED, is_paid=False)
|
||||
@@ -933,26 +934,31 @@ class LNWallet(LNWorker):
|
||||
except KeyError as e:
|
||||
raise UnknownPaymentHash(payment_hash) from e
|
||||
|
||||
def get_request(self, key):
|
||||
invoice, direction, is_paid = self.invoices[key]
|
||||
status = self.get_invoice_status(key)
|
||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||
amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
|
||||
description = lnaddr.get_description()
|
||||
timestamp = lnaddr.date
|
||||
return {
|
||||
'lightning':True,
|
||||
'status':status,
|
||||
'amount':amount_sat,
|
||||
'time':timestamp,
|
||||
'exp':lnaddr.get_expiry(),
|
||||
'memo':description,
|
||||
'rhash':key,
|
||||
'invoice': invoice
|
||||
}
|
||||
|
||||
def get_invoices(self):
|
||||
items = self.invoices.items()
|
||||
out = []
|
||||
for key, (invoice, direction, is_paid) in items:
|
||||
if direction == SENT:
|
||||
continue
|
||||
status = self.get_invoice_status(key)
|
||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||
amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
|
||||
description = lnaddr.get_description()
|
||||
timestamp = lnaddr.date
|
||||
out.append({
|
||||
'lightning':True,
|
||||
'status':status,
|
||||
'amount':amount_sat,
|
||||
'time':timestamp,
|
||||
'memo':description,
|
||||
'rhash':key,
|
||||
'invoice': invoice
|
||||
})
|
||||
out.append(self.get_request(key))
|
||||
return out
|
||||
|
||||
async def _calc_routing_hints_for_invoice(self, amount_sat):
|
||||
|
||||
@@ -78,16 +78,34 @@ PR_UNPAID = 0
|
||||
PR_EXPIRED = 1
|
||||
PR_UNKNOWN = 2 # sent but not propagated
|
||||
PR_PAID = 3 # send and propagated
|
||||
PR_INFLIGHT = 4 # lightning
|
||||
PR_INFLIGHT = 4 # unconfirmed
|
||||
|
||||
pr_tooltips = {
|
||||
PR_UNPAID:_('Pending'),
|
||||
PR_PAID:_('Paid'),
|
||||
PR_UNKNOWN:_('Unknown'),
|
||||
PR_EXPIRED:_('Expired'),
|
||||
PR_INFLIGHT:_('Inflight')
|
||||
PR_INFLIGHT:_('Paid (unconfirmed)')
|
||||
}
|
||||
|
||||
pr_expiration_values = {
|
||||
10*60: _('10 minutes'),
|
||||
60*60: _('1 hour'),
|
||||
24*60*60: _('1 day'),
|
||||
7*24*60*60: _('1 week')
|
||||
}
|
||||
|
||||
def get_request_status(req):
|
||||
status = req['status']
|
||||
status_str = pr_tooltips[status]
|
||||
if status == PR_UNPAID:
|
||||
if req.get('exp'):
|
||||
expiration = req['exp'] + req['time']
|
||||
status_str = _('Expires') + ' ' + age(expiration, include_seconds=True)
|
||||
else:
|
||||
status_str = _('Pending')
|
||||
return status_str
|
||||
|
||||
|
||||
class UnknownBaseUnit(Exception): pass
|
||||
|
||||
@@ -638,22 +656,11 @@ def time_difference(distance_in_time, include_seconds):
|
||||
distance_in_seconds = int(round(abs(distance_in_time.days * 86400 + distance_in_time.seconds)))
|
||||
distance_in_minutes = int(round(distance_in_seconds/60))
|
||||
|
||||
if distance_in_minutes <= 1:
|
||||
if distance_in_minutes == 0:
|
||||
if include_seconds:
|
||||
for remainder in [5, 10, 20]:
|
||||
if distance_in_seconds < remainder:
|
||||
return "less than %s seconds" % remainder
|
||||
if distance_in_seconds < 40:
|
||||
return "half a minute"
|
||||
elif distance_in_seconds < 60:
|
||||
return "less than a minute"
|
||||
else:
|
||||
return "1 minute"
|
||||
return "%s seconds" % distance_in_seconds
|
||||
else:
|
||||
if distance_in_minutes == 0:
|
||||
return "less than a minute"
|
||||
else:
|
||||
return "1 minute"
|
||||
return "less than a minute"
|
||||
elif distance_in_minutes < 45:
|
||||
return "%s minutes" % distance_in_minutes
|
||||
elif distance_in_minutes < 90:
|
||||
|
||||
@@ -46,6 +46,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler,
|
||||
WalletFileException, BitcoinException,
|
||||
InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
|
||||
Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex)
|
||||
from .util import age
|
||||
from .simple_config import get_config
|
||||
from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script,
|
||||
is_minikey, relayfee, dust_threshold)
|
||||
@@ -59,7 +60,7 @@ from .transaction import Transaction, TxOutput, TxOutputHwInfo
|
||||
from .plugin import run_hook
|
||||
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
|
||||
TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE)
|
||||
from .paymentrequest import (PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED,
|
||||
from .paymentrequest import (PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT,
|
||||
InvoiceStore)
|
||||
from .contacts import Contacts
|
||||
from .interface import NetworkException
|
||||
@@ -1204,7 +1205,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
txid, n = txo.split(':')
|
||||
info = self.db.get_verified_tx(txid)
|
||||
if info:
|
||||
conf = local_height - info.height
|
||||
conf = local_height - info.height + 1
|
||||
else:
|
||||
conf = 0
|
||||
l.append((conf, v))
|
||||
@@ -1282,13 +1283,23 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
expiration = r.get('exp')
|
||||
if expiration and type(expiration) != int:
|
||||
expiration = 0
|
||||
|
||||
paid, conf = self.get_payment_status(address, amount)
|
||||
status = PR_PAID if paid else PR_UNPAID
|
||||
if status == PR_UNPAID and expiration is not None and time.time() > timestamp + expiration:
|
||||
status = PR_EXPIRED
|
||||
if not paid:
|
||||
if expiration is not None and time.time() > timestamp + expiration:
|
||||
status = PR_EXPIRED
|
||||
else:
|
||||
status = PR_UNPAID
|
||||
else:
|
||||
status = PR_INFLIGHT if conf <= 0 else PR_PAID
|
||||
return status, conf
|
||||
|
||||
def get_request(self, key, is_lightning):
|
||||
if not is_lightning:
|
||||
req = self.get_payment_request(key, {})
|
||||
else:
|
||||
req = self.lnworker.get_request(key)
|
||||
return req
|
||||
|
||||
def receive_tx_callback(self, tx_hash, tx, tx_height):
|
||||
super().receive_tx_callback(tx_hash, tx, tx_height)
|
||||
for txo in tx.outputs():
|
||||
|
||||
Reference in New Issue
Block a user