Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

qml: calculate max amount when max toggle is enabled #9476

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions electrum/gui/qml/components/InvoiceDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ ElDialog {
Layout.alignment: Qt.AlignHCenter

leftPadding: constants.paddingXLarge
rightPadding: constants.paddingXLarge

property bool editmode: false

Expand Down Expand Up @@ -216,10 +217,23 @@ ElDialog {

BtcField {
id: amountBtc
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
fiatfield: amountFiat
enabled: !amountMax.checked
readOnly: amountMax.checked
color: readOnly
? Material.accentColor
: Material.foreground
onTextAsSatsChanged: {
invoice.amountOverride = textAsSats
if (!amountMax.checked)
invoice.amountOverride.satsInt = textAsSats.satsInt
}
Connections {
target: invoice.amountOverride
function onSatsIntChanged() {
console.log('amuontOverride satsIntChanged, sats=' + invoice.amountOverride.satsInt)
if (amountMax.checked) // amountOverride updated by max amount estimate
amountBtc.text = Config.formatSats(invoice.amountOverride.satsInt)
}
}
}

Expand All @@ -239,24 +253,48 @@ ElDialog {
visible: _canMax
checked: false
onCheckedChanged: {
if (activeFocus)
if (activeFocus) {
invoice.amountOverride.isMax = checked
if (checked) {
maxAmountMessage.text = ''
invoice.updateMaxAmount()
}
}
}
}

FiatField {
id: amountFiat
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
btcfield: amountBtc
visible: Daemon.fx.enabled && !amountMax.checked
enabled: !amountMax.checked
visible: Daemon.fx.enabled
readOnly: amountMax.checked
color: readOnly
? Material.accentColor
: Material.foreground
}

Label {
Layout.columnSpan: 2
visible: Daemon.fx.enabled && !amountMax.checked
visible: Daemon.fx.enabled
text: Daemon.fx.fiatCurrency
color: Material.accentColor
}

InfoTextArea {
Layout.topMargin: constants.paddingMedium
Layout.fillWidth: true
Layout.columnSpan: 3
id: maxAmountMessage
visible: amountMax.checked && text
compact: true
Connections {
target: invoice
function onMaxAmountMessage(message) {
maxAmountMessage.text = message
}
}
}
}
}

Expand Down Expand Up @@ -425,7 +463,9 @@ ElDialog {
enabled: !invoice.isSaved && invoice.canSave
onClicked: {
if (invoice.amount.isEmpty) {
invoice.amountOverride = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text)
invoice.amountOverride = Config.unitsToSats(amountBtc.text)
if (amountMax.checked)
invoice.amountOverride.isMax = true
}
invoice.saveInvoice()
app.stack.push(Qt.resolvedUrl('Invoices.qml'))
Expand All @@ -440,7 +480,9 @@ ElDialog {
enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay
onClicked: {
if (invoice.amount.isEmpty) {
invoice.amountOverride = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text)
invoice.amountOverride = Config.unitsToSats(amountBtc.text)
if (amountMax.checked)
invoice.amountOverride.isMax = true
}
if (!invoice.isSaved) {
// save invoice if newly parsed
Expand Down Expand Up @@ -468,4 +510,9 @@ ElDialog {
}
}
}

FontMetrics {
id: amountFontMetrics
font: amountBtc.font
}
}
56 changes: 48 additions & 8 deletions electrum/gui/qml/components/OpenChannelDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,25 @@ ElDialog {
}

BtcField {
id: amount
id: amountBtc
fiatfield: amountFiat
Layout.preferredWidth: parent.width /3
onTextChanged: channelopener.amount = Config.unitsToSats(amount.text)
enabled: !is_max.checked
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
onTextAsSatsChanged: {
if (!is_max.checked)
channelopener.amount.satsInt = amountBtc.textAsSats.satsInt
}
readOnly: is_max.checked
color: readOnly
? Material.accentColor
: Material.foreground

Connections {
target: channelopener.amount
function onSatsIntChanged() {
if (is_max.checked) // amount updated by max amount estimate
amountBtc.text = Config.formatSats(channelopener.amount.satsInt)
}
}
}

RowLayout {
Expand All @@ -184,7 +198,13 @@ ElDialog {
id: is_max
text: qsTr('Max')
onCheckedChanged: {
channelopener.amount = checked ? MAX : Config.unitsToSats(amount.text)
if (activeFocus) {
channelopener.amount.isMax = checked
if (checked) {
maxAmountMessage.text = ''
channelopener.updateMaxAmount()
}
}
}
}
}
Expand All @@ -193,10 +213,13 @@ ElDialog {

FiatField {
id: amountFiat
btcfield: amount
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
btcfield: amountBtc
visible: Daemon.fx.enabled
Layout.preferredWidth: parent.width /3
enabled: !is_max.checked
readOnly: is_max.checked
color: readOnly
? Material.accentColor
: Material.foreground
}

Label {
Expand All @@ -207,6 +230,16 @@ ElDialog {
}

Item { visible: Daemon.fx.enabled ; height: 1; width: 1 }

InfoTextArea {
Layout.topMargin: constants.paddingMedium
Layout.fillWidth: true
Layout.columnSpan: 3
id: maxAmountMessage
visible: is_max.checked && text
compact: true
}

}
}

Expand Down Expand Up @@ -288,6 +321,13 @@ ElDialog {
// TODO: handle incomplete TX
root.close()
}
onMaxAmountMessage: (message) => {
maxAmountMessage.text = message
}
}

FontMetrics {
id: amountFontMetrics
font: amountBtc.font
}
}
37 changes: 35 additions & 2 deletions electrum/gui/qml/qechannelopener.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import threading
from concurrent.futures import CancelledError
from asyncio.exceptions import TimeoutError
from typing import TYPE_CHECKING, Optional
from typing import Optional
import electrum_ecc as ecc

from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject

from electrum.i18n import _
from electrum.gui import messages
from electrum.util import bfh
from electrum.util import bfh, NotEnoughFunds, NoDynamicFeeEstimates
from electrum.lntransport import extract_nodeid, ConnStringFormatError
from electrum.bitcoin import DummyAddress
from electrum.lnworker import hardcoded_trampoline_nodes
Expand All @@ -29,6 +30,7 @@ class QEChannelOpener(QObject, AuthMixin):
channelOpenError = pyqtSignal([str], arguments=['message'])
channelOpenSuccess = pyqtSignal([str, bool, int, bool],
arguments=['cid', 'has_onchain_backup', 'min_depth', 'tx_complete'])
maxAmountMessage = pyqtSignal([str], arguments=['message'])

dataChanged = pyqtSignal() # generic notify signal

Expand All @@ -46,6 +48,8 @@ def __init__(self, parent=None):
self._node_pubkey = None
self._connect_str_resolved = None

self._updating_max = False

walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self):
Expand Down Expand Up @@ -228,3 +232,32 @@ def open_thread():
@pyqtSlot(str, result=str)
def channelBackup(self, cid):
return self._wallet.wallet.lnworker.export_channel_backup(bfh(cid))

@pyqtSlot()
def updateMaxAmount(self):
if self._updating_max:
return

self._updating_max = True

def calc_max():
try:
coins = self._wallet.wallet.get_spendable_coins(None, nonlocal_only=True)
dummy_nodeid = ecc.GENERATOR.get_public_key_bytes(compressed=True)
make_tx = lambda amt: self._wallet.wallet.lnworker.mktx_for_open_channel(
coins=coins,
funding_sat='!',
node_id=dummy_nodeid,
fee_est=None)

amount, message = self._wallet.determine_max(mktx=make_tx)
if amount is None:
self._amount.isMax = False
else:
self._amount.satsInt = amount
if message:
self.maxAmountMessage.emit(message)
finally:
self._updating_max = False

threading.Thread(target=calc_max, daemon=True).start()
40 changes: 39 additions & 1 deletion electrum/gui/qml/qeinvoice.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import threading
from enum import IntEnum
from typing import Optional, Dict, Any
from urllib.parse import urlparse
Expand All @@ -9,10 +10,12 @@
from electrum.invoices import (Invoice, PR_UNPAID, PR_EXPIRED, PR_UNKNOWN, PR_PAID, PR_INFLIGHT,
PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST, LN_EXPIRY_NEVER)
from electrum.transaction import PartialTxOutput, TxOutput
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
from electrum.lnutil import format_short_channel_id
from electrum.bitcoin import COIN
from electrum.bitcoin import COIN, address_to_script
from electrum.paymentrequest import PaymentRequest
from electrum.payment_identifier import (PaymentIdentifier, PaymentIdentifierState, PaymentIdentifierType)

from .qetypes import QEAmount
from .qewallet import QEWallet
from .util import status_update_timer_interval, QtEventListener, event_listener
Expand Down Expand Up @@ -42,6 +45,7 @@ class Status(IntEnum):
invoiceChanged = pyqtSignal()
invoiceSaved = pyqtSignal([str], arguments=['key'])
amountOverrideChanged = pyqtSignal()
maxAmountMessage = pyqtSignal([str], arguments=['message'])

def __init__(self, parent=None):
super().__init__(parent)
Expand All @@ -64,6 +68,8 @@ def __init__(self, parent=None):

self._amountOverride.valueChanged.connect(self._on_amountoverride_value_changed)

self._updating_max = False

self.register_callbacks()
self.destroyed.connect(lambda: self.on_destroy())

Expand Down Expand Up @@ -392,6 +398,38 @@ def get_max_spendable_onchain(self):
def get_max_spendable_lightning(self):
return self._wallet.wallet.lnworker.num_sats_can_send() if self._wallet.wallet.lnworker else 0

@pyqtSlot()
def updateMaxAmount(self):
if self._updating_max:
return

assert self.invoiceType == QEInvoice.Type.OnchainInvoice

# only single address invoice supported
invoice_address = self._effectiveInvoice.get_address()

self._updating_max = True

def calc_max(address):
try:
outputs = [PartialTxOutput(scriptpubkey=address_to_script(address), value='!')]
make_tx = lambda fee_est, *, confirmed_only=False: self._wallet.wallet.make_unsigned_transaction(
coins=self._wallet.wallet.get_spendable_coins(None),
outputs=outputs,
fee=fee_est,
is_sweep=False)
amount, message = self._wallet.determine_max(mktx=make_tx)
if amount is None:
self._amountOverride.isMax = False
else:
self._amountOverride.satsInt = amount
if message:
self.maxAmountMessage.emit(message)
finally:
self._updating_max = False

threading.Thread(target=calc_max, args=(invoice_address,), daemon=True).start()


class QEInvoiceParser(QEInvoice):
_logger = get_logger(__name__)
Expand Down
Loading