Merge pull request #9580 from spesmilo/lightning_requests

reintroduce separate request types for lightning and onchain
This commit is contained in:
ThomasV
2025-02-25 11:28:40 +01:00
committed by GitHub
16 changed files with 172 additions and 311 deletions
+12 -7
View File
@@ -1074,17 +1074,20 @@ class Commands(Logger):
return wallet.get_unused_address()
@command('w')
async def add_request(self, amount, memo='', expiry=3600, force=False, wallet: Abstract_Wallet = None):
async def add_request(self, amount, memo='', expiry=3600, lightning=False, force=False, wallet: Abstract_Wallet = None):
"""Create a payment request, using the first unused address of the wallet.
The address will be considered as used after this operation.
If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet."""
addr = wallet.get_unused_address()
if addr is None:
if force:
addr = wallet.create_new_address(False)
else:
return False
amount = satoshis(amount)
if not lightning:
addr = wallet.get_unused_address()
if addr is None:
if force:
addr = wallet.create_new_address(False)
else:
return False
else:
addr = None
expiry = int(expiry) if expiry else None
key = wallet.create_request(amount, memo, expiry, addr)
req = wallet.get_request(key)
@@ -1586,6 +1589,8 @@ command_options = {
'addtransaction': (None,'Whether transaction is to be used for broadcasting afterwards. Adds transaction to the wallet'),
'domain': ("-D", "List of addresses"),
'memo': ("-m", "Description of the request"),
'amount': (None, "Requested amount (in btc)"),
'lightning': (None, "Create lightning request"),
'expiry': (None, "Time in seconds"),
'timeout': (None, "Timeout in seconds"),
'force': (None, "Create new address beyond gap limit, if no more addresses are available."),
@@ -11,12 +11,13 @@ import "controls"
ElDialog {
id: dialog
title: qsTr('Receive payment')
title: qsTr('Create Invoice')
iconSource: Qt.resolvedUrl('../../icons/tab_receive.png')
property alias amount: amountBtc.text
property alias description: message.text
property alias expiry: expires.currentValue
property bool isLightning: false
padding: 0
@@ -93,11 +94,23 @@ ElDialog {
}
}
FlatButton {
Layout.fillWidth: true
text: qsTr('Create request')
icon.source: '../../icons/confirmed.png'
onClicked: doAccept()
GridLayout {
width: parent.width
columns: 2
FlatButton {
Layout.fillWidth: true
text: qsTr('Onchain')
icon.source: '../../icons/bitcoin.png'
onClicked: { dialog.isLightning = false; doAccept() }
}
FlatButton {
Layout.fillWidth: true
enabled: Daemon.currentWallet.isLightning && Daemon.currentWallet.lightningCanReceive.satsInt > amountBtc.textAsSats.satsInt
text: qsTr('Lightning')
icon.source: '../../icons/lightning.png'
onClicked: { dialog.isLightning = true; doAccept() }
}
}
}
+8 -114
View File
@@ -48,21 +48,6 @@ ElDialog {
width: parent.width
spacing: constants.paddingMedium
states: [
State {
name: 'bolt11'
PropertyChanges { target: qrloader; sourceComponent: qri_bolt11 }
},
State {
name: 'bip21uri'
PropertyChanges { target: qrloader; sourceComponent: qri_bip21uri }
},
State {
name: 'address'
PropertyChanges { target: qrloader; sourceComponent: qri_address }
}
]
TextHighlightPane {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
@@ -80,90 +65,15 @@ ElDialog {
color: 'white'
Loader {
id: qrloader
QRImage {
anchors.centerIn: parent
Component {
id: qri_bolt11
QRImage {
qrdata: _bolt11
render: _render_qr
enableToggleText: true
}
}
Component {
id: qri_bip21uri
QRImage {
qrdata: _bip21uri
render: _render_qr
enableToggleText: true
}
}
Component {
id: qri_address
QRImage {
qrdata: _address
render: _render_qr
enableToggleText: true
}
}
}
}
ButtonContainer {
Layout.fillWidth: true
showSeparator: false
Component {
id: _ind
Rectangle {
color: Material.dialogColor
opacity: parent.checked ? 1 : 0
radius: 5
width: parent.width
height: parent.height
Behavior on opacity {
NumberAnimation { duration: 200 }
}
}
}
TabButton {
id: bolt11Button
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Lightning')
enabled: _bolt11
checked: rootLayout.state == 'bolt11'
indicator: _ind.createObject()
onClicked: {
rootLayout.state = 'bolt11'
Config.preferredRequestType = 'bolt11'
}
}
TabButton {
id: bip21Button
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('URI')
enabled: _bip21uri
checked: rootLayout.state == 'bip21uri'
indicator: _ind.createObject()
onClicked: {
rootLayout.state = 'bip21uri'
Config.preferredRequestType = 'bip21uri'
}
}
TabButton {
id: addressButton
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Address')
checked: rootLayout.state == 'address'
indicator: _ind.createObject()
onClicked: {
rootLayout.state = 'address'
Config.preferredRequestType = 'address'
}
qrdata: _bolt11
? _bolt11
: _bip21uri
? _bip21uri
: _address
render: _render_qr
enableToggleText: true
}
}
}
@@ -307,22 +217,6 @@ ElDialog {
RequestDetails {
id: request
wallet: Daemon.currentWallet
onDetailsChanged: {
var req_type = Config.preferredRequestType
if (bolt11 && req_type == 'bolt11') {
rootLayout.state = 'bolt11'
} else if (bip21 && req_type == 'bip21uri') {
rootLayout.state = 'bip21uri'
} else if (req_type == 'address') {
rootLayout.state = 'address'
} else if (bolt11) {
rootLayout.state = 'bolt11'
} else if (bip21) {
rootLayout.state = 'bip21uri'
} else {
rootLayout.state = 'address'
}
}
onStatusChanged: {
if (status == RequestDetails.Paid || status == RequestDetails.Unconfirmed) {
_ispaid = true
@@ -126,9 +126,9 @@ Item {
dialog.open()
}
function createRequest(lightning_only, reuse_address) {
function createRequest(lightning, reuse_address) {
var qamt = Config.unitsToSats(_request_amount)
Daemon.currentWallet.createRequest(qamt, _request_description, _request_expiry, lightning_only, reuse_address)
Daemon.currentWallet.createRequest(qamt, _request_description, _request_expiry, lightning, reuse_address)
}
function startSweep() {
@@ -596,7 +596,7 @@ Item {
_request_amount = _receiveDetailsDialog.amount
_request_description = _receiveDetailsDialog.description
_request_expiry = _receiveDetailsDialog.expiry
createRequest(false, false)
createRequest(_receiveDetailsDialog.isLightning, false)
}
onRejected: {
console.log('rejected')
+16 -15
View File
@@ -664,24 +664,25 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
@pyqtSlot(QEAmount, str, int, bool)
@pyqtSlot(QEAmount, str, int, bool, bool)
@pyqtSlot(QEAmount, str, int, bool, bool, bool)
def createRequest(self, amount: QEAmount, message: str, expiration: int, lightning_only: bool = False, reuse_address: bool = False):
def createRequest(self, amount: QEAmount, message: str, expiration: int, lightning: bool = False, reuse_address: bool = False):
self.deleteExpiredRequests()
try:
amount = amount.satsInt
addr = self.wallet.get_unused_address()
if addr is None:
if reuse_address:
addr = self.wallet.get_receiving_address()
elif lightning_only:
addr = None
else:
msg = [
_('No address available.'),
_('All your addresses are used in pending requests.'),
_('To see the list, press and hold the Receive button.'),
]
self.requestCreateError.emit(' '.join(msg))
return
if not lightning:
addr = self.wallet.get_unused_address()
if addr is None:
if reuse_address:
addr = self.wallet.get_receiving_address()
else:
msg = [
_('No address available.'),
_('All your addresses are used in pending requests.'),
_('To see the list, press and hold the Receive button.'),
]
self.requestCreateError.emit(' '.join(msg))
return
else:
addr = None
key = self.wallet.create_request(amount, message, expiration, addr)
except InvoiceError as e:
+21 -40
View File
@@ -69,13 +69,21 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.clear_invoice_button = QPushButton(_('Clear'))
self.clear_invoice_button.clicked.connect(self.do_clear)
self.create_invoice_button = QPushButton(_('Create Request'))
self.create_invoice_button.clicked.connect(lambda: self.create_invoice())
text = _('Onchain') if self.wallet.has_lightning() else _('Request')
self.create_onchain_invoice_button = QPushButton(text)
self.create_onchain_invoice_button.setIcon(read_QIcon("bitcoin.png"))
self.create_onchain_invoice_button.clicked.connect(lambda: self.create_invoice(False))
self.create_lightning_invoice_button = QPushButton(_('Lightning'))
self.create_lightning_invoice_button.setIcon(read_QIcon("lightning.png"))
self.create_lightning_invoice_button.clicked.connect(lambda: self.create_invoice(True))
self.create_lightning_invoice_button.setVisible(self.wallet.has_lightning())
self.receive_buttons = buttons = QHBoxLayout()
buttons.addStretch(1)
buttons.addWidget(self.clear_invoice_button)
buttons.addWidget(self.create_invoice_button)
grid.addLayout(buttons, 4, 0, 1, -1)
buttons.addStretch(1)
buttons.addWidget(self.create_onchain_invoice_button)
buttons.addWidget(self.create_lightning_invoice_button)
grid.addLayout(buttons, 4, 1, 1, -1)
self.receive_e = QTextEdit()
self.receive_e.setFont(QFont(MONOSPACE_FONT))
@@ -116,6 +124,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.receive_widget = ReceiveWidget(
self, self.receive_e, self.receive_qr, self.receive_help_widget)
#self.receive_widget.mouseReleaseEvent = lambda x: self.toggle_receive_qr()
receive_widget_sp = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
receive_widget_sp.setRetainSizeWhenHidden(True)
@@ -138,12 +147,6 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.toggle_qr_button.setEnabled(False)
self.toolbar.insertWidget(2, self.toggle_qr_button)
self.toggle_view_button = QPushButton('')
self.toggle_view_button.setToolTip(_('switch between view'))
self.toggle_view_button.clicked.connect(self.toggle_view)
self.toggle_view_button.setEnabled(False)
self.update_view_button()
self.toolbar.insertWidget(2, self.toggle_view_button)
# menu
menu.addConfig(self.config.cv.WALLET_BOLT11_FALLBACK, callback=self.on_toggle_bolt11_fallback)
menu.addConfig(self.config.cv.WALLET_BIP21_LIGHTNING, callback=self.update_current_request)
@@ -204,24 +207,6 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.wallet.lnworker.clear_invoices_cache()
self.update_current_request()
def update_view_button(self):
i = self.config.GUI_QT_RECEIVE_TABS_INDEX
if i == 0:
icon, text = read_QIcon("link.png"), _('Bitcoin URI')
elif i == 1:
icon, text = read_QIcon("bitcoin.png"), _('Address')
elif i == 2:
icon, text = read_QIcon("lightning.png"), _('Lightning')
self.toggle_view_button.setText(text)
self.toggle_view_button.setIcon(icon)
def toggle_view(self):
i = self.config.GUI_QT_RECEIVE_TABS_INDEX
i = (i + 1) % (3 if self.wallet.has_lightning() else 2)
self.config.GUI_QT_RECEIVE_TABS_INDEX = i
self.update_current_request()
self.update_view_button()
def on_tab_changed(self):
text, data, help_text, title = self.get_tab_data()
self.window.do_copy(text, title=title)
@@ -277,16 +262,14 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
# always show
self.receive_widget.setVisible(True)
self.toggle_qr_button.setEnabled(True)
self.toggle_view_button.setEnabled(True)
self.update_receive_qr_window()
def get_tab_data(self):
i = self.config.GUI_QT_RECEIVE_TABS_INDEX
if i == 0:
if self.URI:
out = self.URI, self.URI, self.URI_help, _('Bitcoin URI')
elif i == 1:
elif self.addr:
out = self.addr, self.addr, self.address_help, _('Address')
elif i == 2:
else:
# encode lightning invoices as uppercase so QR encoding can use
# alphanumeric mode; resulting in smaller QR codes
out = self.lnaddr, self.lnaddr.upper(), self.ln_help, _('Lightning Request')
@@ -297,17 +280,16 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
text, data, help_text, title = self.get_tab_data()
self.window.qr_window.qrw.setData(data)
def create_invoice(self):
def create_invoice(self, is_lightning: bool):
amount_sat = self.receive_amount_e.get_amount()
message = self.receive_message_e.text()
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
if amount_sat and amount_sat < self.wallet.dust_threshold():
if is_lightning:
address = None
if not self.wallet.has_lightning():
else:
if amount_sat and amount_sat < self.wallet.dust_threshold():
self.show_error(_('Amount too small to be received onchain'))
return
else:
address = self.get_bitcoin_address_for_request(amount_sat)
if not address:
return
@@ -358,7 +340,6 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.address_help = self.URI_help = self.ln_help = ''
self.receive_widget.setVisible(False)
self.toggle_qr_button.setEnabled(False)
self.toggle_view_button.setEnabled(False)
self.receive_message_e.setText('')
self.receive_amount_e.setAmount(None)
self.request_list.clearSelection()
+1
View File
@@ -157,6 +157,7 @@ class RequestList(MyTreeView):
#items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
items[self.Columns.DATE].setData(key, ROLE_KEY)
items[self.Columns.DATE].setData(timestamp, ROLE_SORT_ORDER)
items[self.Columns.DATE].setIcon(read_QIcon("lightning" if req.is_lightning() else "bitcoin"))
items[self.Columns.AMOUNT].setData(amount_str_nots.strip(), self.ROLE_CLIPBOARD_DATA)
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
self.std_model.insertRow(self.std_model.rowCount(), items)
+16 -8
View File
@@ -109,12 +109,14 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
+ _('Keyboard shortcut: type "!" to send all your coins.'))
amount_label = HelpLabel(_('Amount'), msg)
grid.addWidget(amount_label, 3, 0)
grid.addWidget(self.amount_e, 3, 1)
amount_widgets = QHBoxLayout()
amount_widgets.addWidget(self.amount_e)
self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')
if not self.fx or not self.fx.is_enabled():
self.fiat_send_e.setVisible(False)
grid.addWidget(self.fiat_send_e, 3, 2)
amount_widgets.addWidget(self.fiat_send_e)
self.amount_e.frozen.connect(
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
@@ -125,20 +127,20 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
self.max_button.setFixedWidth(btn_width)
self.max_button.setCheckable(True)
self.max_button.setEnabled(False)
grid.addWidget(self.max_button, 3, 3)
amount_widgets.addWidget(self.max_button)
amount_widgets.addStretch(1)
grid.addLayout(amount_widgets, 3, 1, 1, -1)
invoice_error_icon = read_QIcon("warning.png")
self.invoice_error = IconLabel(reverse=True, hide_if_empty=True)
self.invoice_error.setIcon(invoice_error_icon)
grid.addWidget(self.invoice_error, 3, 4, Qt.AlignmentFlag.AlignRight)
self.paste_button = QPushButton()
self.paste_button = QPushButton(_('Paste'))
self.paste_button.clicked.connect(self.do_paste)
self.paste_button.setIcon(read_QIcon('copy.png'))
self.paste_button.setToolTip(_('Paste invoice from clipboard'))
self.paste_button.setMaximumWidth(35)
self.paste_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
grid.addWidget(self.paste_button, 0, 5)
self.spinner = QMovie(icon_path('spinner.gif'))
self.spinner.setScaledSize(QSize(24, 24))
@@ -155,10 +157,16 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
self.send_button.setEnabled(False)
self.clear_button = EnterButton(_("Clear"), self.do_clear)
buttons = QHBoxLayout()
buttons.addStretch(1)
#buttons1 = QHBoxLayout()
#buttons1.addWidget(self.paste_button)
#buttons1.addWidget(self.clear_button)
#buttons1.addStretch(1)
#grid.addLayout(buttons1, 0, 1, 1, 4)
buttons = QHBoxLayout()
buttons.addWidget(self.paste_button)
buttons.addWidget(self.clear_button)
buttons.addStretch(1)
buttons.addWidget(self.save_button)
buttons.addWidget(self.send_button)
grid.addLayout(buttons, 6, 1, 1, 4)
+43 -43
View File
@@ -226,15 +226,16 @@ class ElectrumGui(BaseElectrumGui, EventListener):
def print_receive_tab(self):
self.stdscr.clear()
self.buttons = {}
self.max_pos = 5 + len(list(self.wallet.get_unpaid_requests()))
self.max_pos = 6 + len(list(self.wallet.get_unpaid_requests()))
self.index = 0
self.add_edit_line(3, 2, _("Description"), self.str_recv_description, 40)
self.add_edit_line(5, 2, _("Amount"), self.str_recv_amount, 15)
self.stdscr.addstr(5, 31, self.config.get_base_unit())
self.add_edit_line(7, 2, _("Expiry"), self.str_recv_expiry, 15)
self.add_button(9, 15, _("[Create]"), self.do_create_request)
self.add_button(9, 25, _("[Clear]"), self.do_clear_request)
self.print_requests_list(13, 2, offset_pos=5)
self.add_button(9, 15, _("[Clear]"), self.do_clear_request)
self.add_button(9, 25, _("[Onchain]"), lambda: self.do_create_request(lightning=False))
self.add_button(9, 35, _("[Lightning]"), lambda: self.do_create_request(lightning=True))
self.print_requests_list(13, 2, offset_pos=6)
return
def run_receive_tab(self, c):
@@ -244,8 +245,8 @@ class ElectrumGui(BaseElectrumGui, EventListener):
self.str_recv_amount = self.edit_str(self.str_recv_amount, c)
elif self.pos in self.buttons and c == ord("\n"):
self.buttons[self.pos]()
elif self.pos >= 5 and c == ord("\n"):
key = self.requests[self.pos - 5]
elif self.pos >= 6 and c == ord("\n"):
key = self.requests[self.pos - 6]
self.show_request(key)
def question(self, msg):
@@ -557,19 +558,22 @@ class ElectrumGui(BaseElectrumGui, EventListener):
self.str_fee = ''
self.str_description = ''
def do_create_request(self):
amount_sat = self.parse_amount(self.str_recv_amount)
if not amount_sat:
self.show_message(_('Invalid Amount'))
return
if amount_sat < self.wallet.dust_threshold():
address = None
if not self.wallet.has_lightning():
def do_create_request(self, lightning:bool):
amount_sat = self.parse_amount(self.str_recv_amount) or 0
if not lightning:
if amount_sat and amount_sat < self.wallet.dust_threshold():
self.show_message(_('Amount too low'))
return
else:
address = self.wallet.get_unused_address()
if not address:
self.show_message(_('Nor more unused sddress'))
return
else:
if not self.wallet.has_lightning():
self.show_message(_('Lightning is disabled on this wallet'))
return
address = None
message = self.str_recv_description
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
key = self.wallet.create_request(amount_sat, message, expiry, address)
@@ -877,31 +881,26 @@ class ElectrumGui(BaseElectrumGui, EventListener):
URI = self.wallet.get_request_URI(req) or ''
lnaddr = self.wallet.get_bolt11_invoice(req) or ''
w = curses.newwin(self.maxy - 2, self.maxx - 2, 1, 1)
pos = 4
pos = 2
text = URI or addr or lnaddr
data = URI or addr or lnaddr.upper()
while True:
if pos == 1:
text = URI
data = URI
elif pos == 2:
text = lnaddr
data = lnaddr.upper()
else:
text = addr
data = addr
w.clear()
w.border(0)
w.addstr(0, 2, ' ' + _('Payment Request') + ' ')
y = 2
w.addstr(y, 2, "Address")
h1 = self.print_textbox(w, y, 13, addr, pos==0)
y += h1 + 2
w.addstr(y, 2, "URI")
h2 = self.print_textbox(w, y, 13, URI, pos==1)
y += h2 + 2
w.addstr(y, 2, "Lightning")
h3 = self.print_textbox(w, y, 13, lnaddr, pos==2)
y += h3 + 2
if URI:
w.addstr(y, 2, "URI")
h = self.print_textbox(w, y, 13, URI, False)
elif addr:
w.addstr(y, 2, "Address")
h = self.print_textbox(w, y, 13, addr, False)
elif lnaddr:
w.addstr(y, 2, "Lightning")
h = self.print_textbox(w, y, 13, lnaddr, False)
else:
return
y += h + 2
lines = self.get_qr(data)
qr_width = len(lines) * 2
x = self.maxx - qr_width
@@ -909,27 +908,28 @@ class ElectrumGui(BaseElectrumGui, EventListener):
self.print_qr(w, 1, x, lines)
else:
w.addstr(y, 35, "(Window too small for QR code)")
w.addstr(y, 13, "[Delete]", curses.A_REVERSE if pos==3 else curses.color_pair(2))
w.addstr(y, 25, "[Close]", curses.A_REVERSE if pos==4 else curses.color_pair(2))
w.addstr(y, 13, "[Copy]", curses.A_REVERSE if pos==0 else curses.color_pair(2))
w.addstr(y, 23, "[Delete]", curses.A_REVERSE if pos==1 else curses.color_pair(2))
w.addstr(y, 35, "[Close]", curses.A_REVERSE if pos==2 else curses.color_pair(2))
w.refresh()
c = self.getch()
if c in [curses.KEY_UP]:
if c in [curses.KEY_UP, curses.KEY_LEFT]:
pos -= 1
elif c in [curses.KEY_DOWN, ord("\t")]:
elif c in [curses.KEY_DOWN, curses.KEY_RIGHT, ord("\t")]:
pos += 1
elif c == ord("\n"):
if pos in [0,1,2]:
if pos == 0:
pyperclip.copy(text)
self.show_message('Text copied to clipboard')
elif pos == 3:
elif pos == 1:
if self.question("Delete Request?"):
self.wallet.delete_request(key)
self.max_pos -= 1
break
elif pos ==4:
elif pos == 2:
break
else:
break
pos = pos % 5
pos = pos % 3
self.stdscr.refresh()
return
+3 -2
View File
@@ -344,8 +344,9 @@ class Request(BaseInvoice):
) -> Optional[str]:
addr = self.get_address()
amount = self.get_amount_sat()
if amount is not None:
amount = int(amount)
if amount is None:
return
amount = int(amount)
message = self.message
extra = {}
if self.time and self.exp:
+10 -2
View File
@@ -125,14 +125,22 @@ class PayServer(Logger, EventListener):
params = await request.post()
wallet = self.wallet
if 'amount_sat' not in params or not params['amount_sat'].isdigit():
raise web.HTTPUnsupportedMediaType()
raise web.HTTPBadRequest(reason='No amount provided')
if 'onchain' in params:
address = wallet.get_unused_address()
if not address:
raise web.HTTPBadRequest(reason='wallet does not have any unused address')
else:
if not wallet.has_lightning():
raise web.HTTPBadRequest(reason='wallet does not support lightning')
address = None
amount = int(params['amount_sat'])
message = params['message'] or "donation"
key = wallet.create_request(
amount_sat=amount,
message=message,
exp_delay=3600,
address=None)
address=address)
raise web.HTTPFound(self.root + '/pay?id=' + key)
async def get_request(self, r):
-1
View File
@@ -1097,7 +1097,6 @@ Warning: setting this to too low will result in lots of payment failures."""),
'gui_qt_tx_dialog_export_include_global_xpubs', default=False, type_=bool,
short_desc=lambda: _('For hardware device; include xpubs'),
)
GUI_QT_RECEIVE_TABS_INDEX = ConfigVar('receive_tabs_index', default=0, type_=int)
GUI_QT_RECEIVE_TAB_QR_VISIBLE = ConfigVar('receive_qr_visible', default=False, type_=bool)
GUI_QT_TX_EDITOR_SHOW_IO = ConfigVar(
'show_tx_io', default=False, type_=bool,
+5 -2
View File
@@ -2833,6 +2833,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
return invoice
def create_request(self, amount_sat: Optional[int], message: Optional[str], exp_delay: Optional[int], address: Optional[str]):
""" will create a lightning request if address is None """
# for receiving
amount_sat = amount_sat or 0
assert isinstance(amount_sat, int), f"{amount_sat!r}"
@@ -2841,9 +2842,11 @@ class Abstract_Wallet(ABC, Logger, EventListener):
address = address or None # converts "" to None
exp_delay = exp_delay or 0
timestamp = int(Request._get_cur_time())
payment_hash = None # type: Optional[bytes]
if self.has_lightning():
if address is None:
assert self.has_lightning()
payment_hash = self.lnworker.create_payment_info(amount_msat=amount_msat, write_to_disk=False)
else:
payment_hash = None
outputs = [PartialTxOutput.from_address_and_value(address, amount_sat)] if address else []
height = self.adb.get_local_height()
req = Request(
+9 -9
View File
@@ -133,12 +133,12 @@ if [[ $1 == "breach" ]]; then
channel=$($alice open_channel $bob_node 0.15 --password='')
new_blocks 3
wait_until_channel_open alice
request=$($bob add_request 0.01 -m "blah" | jq -r ".lightning_invoice")
request=$($bob add_request 0.01 --lightning -m "blah" | jq -r ".lightning_invoice")
echo "alice pays"
$alice lnpay $request
sleep 2
ctx=$($alice get_channel_ctx $channel --iknowwhatimdoing)
request=$($bob add_request 0.01 -m "blah2" | jq -r ".lightning_invoice")
request=$($bob add_request 0.01 --lightning -m "blah2" | jq -r ".lightning_invoice")
echo "alice pays again"
$alice lnpay $request
echo "alice broadcasts old ctx"
@@ -289,7 +289,7 @@ if [[ $1 == "extract_preimage" ]]; then
wait_until_channel_open alice
chan_id=$($alice list_channels | jq -r ".[0].channel_point")
# alice pays bob
invoice=$($bob add_request 0.04 -m "test" | jq -r ".lightning_invoice")
invoice=$($bob add_request 0.04 --lightning -m "test" | jq -r ".lightning_invoice")
screen -S alice_payment -dm -L -Logfile /tmp/alice/screen.log $alice lnpay $invoice --timeout=600
sleep 1
unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent')
@@ -319,7 +319,7 @@ if [[ $1 == "redeem_htlcs" ]]; then
new_blocks 3
wait_until_channel_open alice
# alice pays bob
invoice=$($bob add_request 0.04 -m "test" | jq -r ".lightning_invoice")
invoice=$($bob add_request 0.04 --lightning -m "test" | jq -r ".lightning_invoice")
$alice lnpay $invoice --timeout=1 || true
unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent')
if [[ "$unsettled" == "0" ]]; then
@@ -361,7 +361,7 @@ if [[ $1 == "breach_with_unspent_htlc" ]]; then
new_blocks 3
wait_until_channel_open alice
echo "alice pays bob"
invoice=$($bob add_request 0.04 -m "test" | jq -r ".lightning_invoice")
invoice=$($bob add_request 0.04 --lightning -m "test" | jq -r ".lightning_invoice")
$alice lnpay $invoice --timeout=1 || true
unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent')
if [[ "$unsettled" == "0" ]]; then
@@ -390,7 +390,7 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then
new_blocks 3
wait_until_channel_open alice
echo "alice pays bob"
invoice=$($bob add_request 0.04 -m "test" | jq -r ".lightning_invoice")
invoice=$($bob add_request 0.04 --lightning -m "test" | jq -r ".lightning_invoice")
$alice lnpay $invoice --timeout=1 || true
ctx=$($alice get_channel_ctx $channel --iknowwhatimdoing)
unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent')
@@ -453,11 +453,11 @@ if [[ $1 == "watchtower" ]]; then
new_blocks 3
wait_until_channel_open alice
echo "alice pays bob"
invoice1=$($bob add_request 0.01 -m "invoice1" | jq -r ".lightning_invoice")
invoice1=$($bob add_request 0.01 --lightning -m "invoice1" | jq -r ".lightning_invoice")
$alice lnpay $invoice1
ctx=$($alice get_channel_ctx $channel --iknowwhatimdoing)
echo "alice pays bob again"
invoice2=$($bob add_request 0.01 -m "invoice2" | jq -r ".lightning_invoice")
invoice2=$($bob add_request 0.01 --lightning -m "invoice2" | jq -r ".lightning_invoice")
$alice lnpay $invoice2
bob_ctn=$($bob list_channels | jq '.[0].local_ctn')
msg="waiting until watchtower is synchronized"
@@ -492,7 +492,7 @@ if [[ $1 == "just_in_time" ]]; then
wait_until_channel_open carol
echo "carol pays alice"
# note: set amount to 0.001 to test failure: 'payment too low'
invoice=$($alice add_request 0.01 -m "invoice" | jq -r ".lightning_invoice")
invoice=$($alice add_request 0.01 --lightning -m "invoice" | jq -r ".lightning_invoice")
$carol lnpay $invoice
fi
+5 -58
View File
@@ -45,7 +45,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
self.assertTrue(wallet1.has_lightning())
# create payreq
addr = wallet1.get_unused_address()
pr_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr, exp_delay=86400)
pr_key = wallet1.create_request(amount_sat=10000, message="msg", address=None, exp_delay=86400)
pr = wallet1.get_request(pr_key)
self.assertIsNotNone(pr)
self.assertTrue(pr.is_lightning())
@@ -66,7 +66,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
pr_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr, exp_delay=86400)
pr = wallet1.get_request(pr_key)
self.assertIsNotNone(pr)
self.assertTrue(pr.is_lightning())
self.assertTrue(not pr.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr))
self.assertEqual(1000, pr.height)
# get paid onchain
@@ -126,7 +126,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
pr_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr, exp_delay=86400)
pr = wallet1.get_request(pr_key)
self.assertIsNotNone(pr)
self.assertTrue(pr.is_lightning())
self.assertTrue(not pr.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr))
self.assertEqual(1000, pr.height)
# get paid onchain
@@ -143,59 +143,6 @@ class TestWalletPaymentRequests(ElectrumTestCase):
wallet1.adb.add_verified_tx(tx.txid(), tx_info)
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr))
async def test_wallet_reuse_unused_fallback_onchain_addr_when_getting_paid_with_lightning(self):
text = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
d = restore_wallet_from_text(text, path=self.wallet1_path, gap_limit=5, config=self.config)
wallet1 = d['wallet'] # type: Standard_Wallet
self.assertIsNotNone(wallet1.lnworker)
self.assertTrue(wallet1.has_lightning())
# create payreq1
addr1 = wallet1.get_unused_address()
pr1_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr1, exp_delay=86400)
pr1 = wallet1.get_request(pr1_key)
self.assertTrue(pr1.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr1))
self.assertEqual(addr1, pr1.get_address())
self.assertFalse(pr1.has_expired())
# create payreq2
addr2 = wallet1.get_unused_address()
self.assertNotEqual(addr1, addr2)
pr2_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr2, exp_delay=86400)
pr2 = wallet1.get_request(pr2_key)
self.assertTrue(pr2.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr2))
self.assertEqual(addr2, pr2.get_address())
# pr1 gets paid on LN
wallet1.lnworker.set_request_status(bytes.fromhex(pr1.rhash), PR_PAID)
self.assertEqual(PR_PAID, wallet1.get_invoice_status(pr1))
# create payreq3, which should auto-reuse addr1
addr3 = wallet1.get_unused_address()
self.assertEqual(addr1, addr3)
pr3_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr3, exp_delay=86400)
pr3 = wallet1.get_request(pr3_key)
self.assertTrue(pr3.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr3))
self.assertEqual(addr3, pr3.get_address())
# pr2 gets paid onchain
wallet2 = self.create_wallet2() # type: Standard_Wallet
outputs = [PartialTxOutput.from_address_and_value(pr2.get_address(), pr2.get_amount_sat())]
tx = wallet2.create_transaction(outputs=outputs, fee=5000)
wallet1.adb.receive_tx_callback(tx, TX_HEIGHT_UNCONFIRMED)
self.assertEqual(PR_UNCONFIRMED, wallet1.get_invoice_status(pr2))
# create payreq4, which should not reuse addr2
addr4 = wallet1.get_unused_address()
self.assertEqual(3, len({addr1, addr2, addr3, addr4}))
pr4_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr4, exp_delay=86400)
pr4 = wallet1.get_request(pr4_key)
self.assertTrue(pr4.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr4))
self.assertEqual(addr4, pr4.get_address())
async def test_wallet_reuse_addr_of_expired_request(self):
text = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
d = restore_wallet_from_text(text, path=self.wallet1_path, gap_limit=3, config=self.config)
@@ -206,7 +153,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
addr1 = wallet1.get_unused_address()
pr1_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr1, exp_delay=86400)
pr1 = wallet1.get_request(pr1_key)
self.assertTrue(pr1.is_lightning())
self.assertTrue(not pr1.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr1))
self.assertEqual(addr1, pr1.get_address())
self.assertFalse(pr1.has_expired())
@@ -219,7 +166,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
self.assertEqual(addr1, addr2)
pr2_key = wallet1.create_request(amount_sat=10000, message="msg", address=addr2, exp_delay=86400)
pr2 = wallet1.get_request(pr2_key)
self.assertTrue(pr2.is_lightning())
self.assertTrue(not pr2.is_lightning())
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr2))
self.assertEqual(addr2, pr2.get_address())
self.assertFalse(pr2.has_expired())