Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion electrum/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1380,7 +1380,7 @@ async def add_request(self, amount, memo='', expiry=3600, lightning=False, force
else:
addr = None
expiry = int(expiry) if expiry else None
key = wallet.create_request(amount, memo, expiry, addr)
key = wallet.create_request(amount_sat=amount, message=memo, exp_delay=expiry, address=addr)
req = wallet.get_request(key)
return wallet.export_request(req)

Expand Down
4 changes: 2 additions & 2 deletions electrum/gui/qml/components/InvoiceDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ ElDialog {
}
Connections {
target: invoice.amountOverride
function onSatsIntChanged() {
console.log('amountOverride satsIntChanged, sats=' + invoice.amountOverride.satsInt)
function onValueChanged() {
console.log('amountOverride valueChanged, sats=' + invoice.amountOverride.satsInt)
if (amountMax.checked) // amountOverride updated by max amount estimate
amountBtc.text = Config.formatSatsForEditing(invoice.amountOverride.satsInt)
}
Expand Down
17 changes: 10 additions & 7 deletions electrum/gui/qml/components/LnurlPayRequestDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ ElDialog {
needsSystemBarPadding: false

property bool commentValid: comment.text.length <= invoiceParser.lnurlData['comment_allowed']
property bool amountValid: amountBtc.textAsSats.satsInt >= parseInt(invoiceParser.lnurlData['min_sendable_sat'])
&& amountBtc.textAsSats.satsInt <= parseInt(invoiceParser.lnurlData['max_sendable_sat'])
property bool amountValid: amountBtc.textAsSats.msatsInt >= parseInt(invoiceParser.lnurlData['min_sendable_msat'])
&& amountBtc.textAsSats.msatsInt <= parseInt(invoiceParser.lnurlData['max_sendable_msat'])
property bool valid: commentValid && amountValid

ColumnLayout {
Expand All @@ -41,9 +41,11 @@ ElDialog {
Layout.columnSpan: 2
Layout.fillWidth: true
compact: true
visible: invoiceParser.lnurlData['min_sendable_sat'] != invoiceParser.lnurlData['max_sendable_sat']
text: qsTr('Amount must be between %1 and %2 %3').arg(Config.formatSats(invoiceParser.lnurlData['min_sendable_sat'])).arg(Config.formatSats(invoiceParser.lnurlData['max_sendable_sat'])).arg(Config.baseUnit)
backgroundColor: constants.darkerDialogBackground
visible: invoiceParser.lnurlData['min_sendable_msat'] != invoiceParser.lnurlData['max_sendable_msat']
text: qsTr('Amount must be between %1 and %2 %3')
.arg(Config.formatMilliSats(invoiceParser.lnurlData['min_sendable_msat']))
.arg(Config.formatMilliSats(invoiceParser.lnurlData['max_sendable_msat']))
.arg(Config.baseUnit)
}

Label {
Expand Down Expand Up @@ -74,10 +76,11 @@ ElDialog {
BtcField {
id: amountBtc
Layout.preferredWidth: rootLayout.width /3
text: Config.formatSatsForEditing(invoiceParser.lnurlData['min_sendable_sat'])
enabled: invoiceParser.lnurlData['min_sendable_sat'] != invoiceParser.lnurlData['max_sendable_sat']
text: Config.formatMilliSatsForEditing(invoiceParser.lnurlData['min_sendable_msat'])
enabled: invoiceParser.lnurlData['min_sendable_msat'] != invoiceParser.lnurlData['max_sendable_msat']
color: Material.foreground // override gray-out on disabled
fiatfield: amountFiat
msatPrecision: true
onTextAsSatsChanged: {
invoiceParser.amountOverride = textAsSats
}
Expand Down
31 changes: 16 additions & 15 deletions electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ ElDialog {
needsSystemBarPadding: false

property int walletCanReceive: 0
property int providerMinWithdrawable: parseInt(requestDetails.lnurlData['min_withdrawable_sat'])
property int providerMaxWithdrawable: parseInt(requestDetails.lnurlData['max_withdrawable_sat'])
property int providerMinWithdrawable: parseInt(requestDetails.lnurlData['min_withdrawable_msat'])
property int providerMaxWithdrawable: parseInt(requestDetails.lnurlData['max_withdrawable_msat'])
property int effectiveMinWithdrawable: Math.max(providerMinWithdrawable, 1)
property int effectiveMaxWithdrawable: Math.min(providerMaxWithdrawable, walletCanReceive)
Comment thread
accumulator marked this conversation as resolved.
Outdated
property bool insufficientLiquidity: effectiveMinWithdrawable > walletCanReceive
property bool liquidityWarning: providerMaxWithdrawable > walletCanReceive

property bool amountValid: !dialog.insufficientLiquidity &&
amountBtc.textAsSats.satsInt >= dialog.effectiveMinWithdrawable &&
amountBtc.textAsSats.satsInt <= dialog.effectiveMaxWithdrawable
amountBtc.textAsSats.msatsInt >= dialog.effectiveMinWithdrawable &&
amountBtc.textAsSats.msatsInt <= dialog.effectiveMaxWithdrawable
property bool valid: amountValid

Component.onCompleted: {
dialog.walletCanReceive = wallet.lightningCanReceive.satsInt
dialog.walletCanReceive = wallet.lightningCanReceive.msatsInt
}

Connections {
Expand All @@ -43,7 +43,7 @@ ElDialog {
if (!requestDetails.busy) {
// don't assign while busy to prevent the view from changing while receiving
// the incoming payment
dialog.walletCanReceive = wallet.lightningCanReceive.satsInt
dialog.walletCanReceive = wallet.lightningCanReceive.msatsInt
}
}
}
Expand All @@ -69,10 +69,10 @@ ElDialog {
text: qsTr('Too little incoming liquidity to satisfy this withdrawal request.')
+ '\n\n'
+ qsTr('Can receive: %1')
.arg(Config.formatSats(dialog.walletCanReceive) + ' ' + Config.baseUnit)
.arg(Config.formatMilliSats(dialog.walletCanReceive) + ' ' + Config.baseUnit)
+ '\n'
+ qsTr('Minimum withdrawal amount: %1')
.arg(Config.formatSats(dialog.providerMinWithdrawable) + ' ' + Config.baseUnit)
.arg(Config.formatMilliSats(dialog.providerMinWithdrawable) + ' ' + Config.baseUnit)
+ '\n\n'
+ qsTr('Do a submarine swap in the \'Channels\' tab to get more incoming liquidity.')
iconStyle: InfoTextArea.IconStyle.Error
Expand All @@ -85,8 +85,8 @@ ElDialog {
compact: true
visible: !dialog.insufficientLiquidity && dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable
text: qsTr('Amount must be between %1 and %2 %3')
.arg(Config.formatSats(dialog.effectiveMinWithdrawable))
.arg(Config.formatSats(dialog.effectiveMaxWithdrawable))
.arg(Config.formatMilliSats(dialog.effectiveMinWithdrawable))
.arg(Config.formatMilliSats(dialog.effectiveMaxWithdrawable))
.arg(Config.baseUnit)
backgroundColor: constants.darkerDialogBackground
}
Expand All @@ -97,8 +97,8 @@ ElDialog {
compact: true
visible: dialog.liquidityWarning && !dialog.insufficientLiquidity
text: qsTr('The maximum withdrawable amount (%1) is larger than what your channels can receive (%2).')
.arg(Config.formatSats(dialog.providerMaxWithdrawable) + ' ' + Config.baseUnit)
.arg(Config.formatSats(dialog.walletCanReceive) + ' ' + Config.baseUnit)
.arg(Config.formatMilliSats(dialog.providerMaxWithdrawable) + ' ' + Config.baseUnit)
.arg(Config.formatMilliSats(dialog.walletCanReceive) + ' ' + Config.baseUnit)
+ ' '
+ qsTr('You may need to do a submarine swap to increase your incoming liquidity.')
iconStyle: InfoTextArea.IconStyle.Warn
Expand Down Expand Up @@ -135,10 +135,11 @@ ElDialog {
BtcField {
id: amountBtc
Layout.preferredWidth: rootLayout.width / 3
text: Config.formatSatsForEditing(dialog.effectiveMaxWithdrawable)
text: Config.formatMilliSatsForEditing(dialog.effectiveMaxWithdrawable)
enabled: !dialog.insufficientLiquidity && (dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable)
color: Material.foreground // override gray-out on disabled
fiatfield: amountFiat
msatPrecision: true
}
Label {
text: Config.baseUnit
Expand Down Expand Up @@ -173,8 +174,8 @@ ElDialog {
icon.source: '../../icons/confirmed.png'
enabled: valid && !requestDetails.busy
onClicked: {
var satsAmount = amountBtc.textAsSats.satsInt;
requestDetails.lnurlRequestWithdrawal(satsAmount);
var msatsAmount = amountBtc.textAsSats.msatsInt;
requestDetails.lnurlRequestWithdrawal(msatsAmount);
dialog.close();
}
}
Expand Down
2 changes: 1 addition & 1 deletion electrum/gui/qml/components/OpenChannelDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ ElDialog {

Connections {
target: channelopener.amount
function onSatsIntChanged() {
function onValueChanged() {
if (is_max.checked) // amount updated by max amount estimate
amountBtc.text = Config.formatSatsForEditing(channelopener.amount.satsInt)
}
Expand Down
12 changes: 7 additions & 5 deletions electrum/gui/qml/components/ReceiveDetailsDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ ElDialog {
id: amountBtc
fiatfield: amountFiat
Layout.fillWidth: true
msatPrecision: true
}

Label {
Expand Down Expand Up @@ -103,22 +104,23 @@ ElDialog {
Layout.preferredWidth: 1
text: qsTr('Onchain')
icon.source: '../../icons/bitcoin.png'
enabled: !amountBtc.textAsSats.hasMsatPrecision
onClicked: { dialog.isLightning = false; doAccept() }
}
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
enabled: Daemon.currentWallet.isLightning && (Daemon.currentWallet.lightningCanReceive.satsInt
> amountBtc.textAsSats.satsInt || Daemon.currentWallet.canGetZeroconfChannel)
enabled: Daemon.currentWallet.isLightning && (Daemon.currentWallet.lightningCanReceive.msatsInt

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Lightning button doesn't react to user input anymore, when entering an amount exceeding lightningCanReceive the button stays enabled.

> amountBtc.textAsSats.msatsInt || Daemon.currentWallet.canGetZeroconfChannel)
text: qsTr('Lightning')
icon.source: '../../icons/lightning.png'
onClicked: {
if (Daemon.currentWallet.lightningCanReceive.satsInt > amountBtc.textAsSats.satsInt) {
if (Daemon.currentWallet.lightningCanReceive.msatsInt > amountBtc.textAsSats.msatsInt) {
// can receive on existing channel
dialog.isLightning = true
doAccept()
} else if (Daemon.currentWallet.canGetZeroconfChannel && amountBtc.textAsSats.satsInt
>= Daemon.currentWallet.minChannelFunding.satsInt) {
} else if (Daemon.currentWallet.canGetZeroconfChannel && amountBtc.textAsSats.msatsInt
>= Daemon.currentWallet.minChannelFunding.msatsInt) {
// ask for confirmation of zeroconf channel to prevent fee surprise
var confirmdialog = app.messageDialog.createObject(dialog, {
title: qsTr('Confirm just-in-time channel'),
Expand Down
2 changes: 1 addition & 1 deletion electrum/gui/qml/components/controls/BtcField.qml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ TextField {
Connections {
target: Config
function onBaseUnitChanged() {
amount.text = amount.textAsSats.satsInt != 0
amount.text = amount.textAsSats.msatsInt != 0
? Config.satsToUnits(amount.textAsSats)
: ''
}
Expand Down
10 changes: 5 additions & 5 deletions electrum/gui/qml/components/controls/ChannelBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Item {
}

function do_update() {
var cap = capacity.satsInt * 1000
var cap = capacity.msatsInt
var twocap = cap * 2
l1.width = width * (cap - localCapacity.msatsInt) / twocap
if (frozenForSending) {
Expand All @@ -48,22 +48,22 @@ Item {

Connections {
target: localCapacity
function onMsatsIntChanged() { update() }
function onValueChanged() { update() }
}

Connections {
target: remoteCapacity
function onMsatsIntChanged() { update() }
function onValueChanged() { update() }
}

Connections {
target: canSend
function onMsatsIntChanged() { update() }
function onValueChanged() { update() }
}

Connections {
target: canReceive
function onMsatsIntChanged() { update() }
function onValueChanged() { update() }
}

Rectangle {
Expand Down
2 changes: 1 addition & 1 deletion electrum/gui/qml/components/controls/FormattedAmount.qml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ GridLayout {
}
Label {
visible: valid
text: amount.msatsInt != 0 ? Config.formatMilliSats(amount) : Config.formatSats(amount)
text: Config.formatMilliSats(amount)

@f321x f321x Jun 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This passes None to Config.formatMilliSats() and raises an Exception, e.g. when creating a lightning invoice:

  3.70 | I | lnworker.LNWallet.[default_wallet] | creating bolt11 invoice with routing_hints: [('r', [('02c3be1bcedffe1675617fc7ae9e4c43ce0f54cc3e58c48ef51398dc87a78e0ce0', '10052759x15511793x59990', 1000, 1, 144)]), ('r', [('02c3be1bcedffe1675617fc7ae9e4c43ce0f54cc3e58c48ef51398dc87a78e0ce0', '8427041x8597475x4058', 1000, 1, 144)]), ('r', [('02465ed5be53d04fde66c9418ff14a5f2267723810176c9212b722e542dc1afb1b', '16000000x0x18483', 1000, 1, 80)])], sat: 1898
  3.71 | E | gui.qml.qeapp.Exception_Hook | exception caught by crash reporter
Traceback (most recent call last):
  File "/var/home/user/code/code_vm/electrum/electrum/gui/qml/qeconfig.py", line 400, in formatMilliSats
    raise Exception(f"Unknown amount type: {str(type(amount))}")
Exception: Unknown amount type: <class 'NoneType'>
  3.73 | D | gui.qml.qerequestdetails | key=f3cd542ad64f230e65dfe4e7312d3a00394a3ce651ed4a5ab1c8072535c10061

font.family: FixedFont
}
Label {
Expand Down
4 changes: 2 additions & 2 deletions electrum/gui/qml/components/controls/HistoryItemDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ Item {
font.pixelSize: constants.fontSizeMedium
Layout.alignment: Qt.AlignRight
font.bold: true
color: model.value.satsInt >= 0 ? constants.colorCredit : constants.colorDebit
color: model.value.msatsInt >= 0 ? constants.colorCredit : constants.colorDebit

function updateText() {
text = Config.formatSats(model.value)
text = Config.formatMilliSats(model.value)
}
Component.onCompleted: updateText()
}
Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qml/components/controls/InvoiceDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ ItemDelegate {
? ''
: model.amount.isMax
? 'MAX'
: Config.formatSats(model.amount)
: Config.formatMilliSats(model.amount)
font.pixelSize: constants.fontSizeMedium
font.family: FixedFont
}
Expand Down Expand Up @@ -142,10 +142,10 @@ ItemDelegate {
Connections {
target: Config
function onBaseUnitChanged() {
amount.text = model.amount.isEmpty ? '' : Config.formatSats(model.amount)
amount.text = model.amount.isEmpty ? '' : Config.formatMilliSats(model.amount)
}
function onThousandsSeparatorChanged() {
amount.text = model.amount.isEmpty ? '' : Config.formatSats(model.amount)
amount.text = model.amount.isEmpty ? '' : Config.formatMilliSats(model.amount)
}
}
Connections {
Expand Down
36 changes: 28 additions & 8 deletions electrum/gui/qml/qeconfig.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import copy
from decimal import Decimal
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

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

Expand Down Expand Up @@ -360,6 +360,21 @@ def formatSatsForEditing(self, satoshis):
add_thousands_sep=False,
)

@pyqtSlot('qint64', result=str)
@pyqtSlot(QEAmount, result=str)
def formatMilliSatsForEditing(self, msatoshis):
if isinstance(msatoshis, QEAmount):
satoshis = Decimal(msatoshis.msatsInt) / 1000
elif isinstance(msatoshis, int):
satoshis = Decimal(msatoshis) / 1000

precision = 3 # config.amt_precision_post_satoshi is not exposed in preferences
return self.config.format_amount(
satoshis,
add_thousands_sep=False,
precision=precision,
)

@pyqtSlot('qint64', result=str)
@pyqtSlot('qint64', bool, result=str)
@pyqtSlot(QEAmount, result=str)
Expand All @@ -372,16 +387,23 @@ def formatSats(self, satoshis, with_unit=False):
else:
return self.config.format_amount(satoshis)

@pyqtSlot('qint64', result=str)
@pyqtSlot('qint64', bool, result=str)
@pyqtSlot(QEAmount, result=str)
@pyqtSlot(QEAmount, bool, result=str)
def formatMilliSats(self, amount, with_unit=False):
assert isinstance(amount, QEAmount), f"unexpected type for amount: {type(amount)}"
msats = amount.msatsInt
if isinstance(amount, QEAmount):
msats = amount.msatsInt
elif isinstance(amount, int):
msats = amount
else:
raise Exception(f"Unknown amount type: {str(type(amount))}")
sats = Decimal(msats) / 1000
precision = 3 # config.amt_precision_post_satoshi is not exposed in preferences
if with_unit:
return self.config.format_amount_and_units(msats/1000, precision=precision)
return self.config.format_amount_and_units(sats, precision=precision)
else:
return self.config.format_amount(msats/1000, precision=precision)
return self.config.format_amount(sats, precision=precision)

@pyqtSlot(str, result=QEAmount)
def unitsToSats(self, unitAmount):
Expand All @@ -391,11 +413,9 @@ def unitsToSats(self, unitAmount):
except Exception:
return self._amount

sat_max_precision = self.config.BTC_AMOUNTS_DECIMAL_POINT
msat_max_precision = self.config.BTC_AMOUNTS_DECIMAL_POINT + 3
sat_max_prec_amount = int(pow(10, sat_max_precision) * x)
msat_max_prec_amount = int(pow(10, msat_max_precision) * x)
self._amount = QEAmount(amount_sat=sat_max_prec_amount, amount_msat=msat_max_prec_amount)
self._amount = QEAmount(amount_msat=msat_max_prec_amount)
return self._amount

@pyqtSlot('quint64', result=float)
Expand Down
4 changes: 2 additions & 2 deletions electrum/gui/qml/qefx.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def enabled(self, enable):
def fiatValue(self, satoshis, plain=True):
rate = self.fx.exchange_rate()
if isinstance(satoshis, QEAmount):
satoshis = satoshis.msatsInt / 1000 if satoshis.msatsInt != 0 else satoshis.satsInt
satoshis = Decimal(satoshis.msatsInt) / 1000
else:
try:
sd = Decimal(satoshis)
Expand All @@ -132,7 +132,7 @@ def fiatValue(self, satoshis, plain=True):
@pyqtSlot(QEAmount, str, bool, result=str)
def fiatValueHistoric(self, satoshis, timestamp, plain=True):
if isinstance(satoshis, QEAmount):
satoshis = satoshis.msatsInt / 1000 if satoshis.msatsInt != 0 else satoshis.satsInt
satoshis = Decimal(satoshis.msatsInt) / 1000
else:
try:
sd = Decimal(satoshis)
Expand Down
Loading