Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
98a679f
qt: fix weird inheritance tree FeerateEdit, move text render with dec…
accumulator Apr 15, 2026
6aa087a
qt: AmountEdit defaults to decimal_point = 0, declare FiatAmountEdit …
accumulator Apr 15, 2026
281b2e9
wallet: kwarg all parameters of wallet.create_request()
accumulator Apr 15, 2026
86033ea
invoices: add BaseInvoice.get_amount_sat_msat_precision() func
accumulator Apr 16, 2026
b283165
qt: millisat precision request tab, request list, invoice list
accumulator Apr 15, 2026
0153d36
lnworker: can_{pay,receive}_invoice millisat precision
accumulator Apr 16, 2026
9dc7838
lnurl,pi: use msat precision
accumulator Apr 16, 2026
a3b5990
qt: send_tab lnurl msat
accumulator Apr 20, 2026
2a1c8e7
qt: type hints
accumulator Apr 16, 2026
5e4141f
payment_identifier: mark pi invalid if multiline and any output uses …
accumulator Apr 16, 2026
827533f
qt: BTCAmountEdit allow dynamically changing precision, disable msat …
accumulator Apr 16, 2026
ef3b915
qml: in QEAmount, keep msat and sat synchronized, and be more strict …
accumulator Apr 17, 2026
d280d08
qml: qeconfig: add formatMilliSatsForEditing(),
accumulator Apr 17, 2026
1aa5785
qml: update for lnurl msat precision
accumulator Apr 17, 2026
914901d
qml: millisat precision ReceiveDetailsDialog, QERequestDetails, QEInv…
accumulator Apr 17, 2026
64ea9a9
qml: refactor QEConfig.unitsToSats to QEConfig.baseunitStrToAmount and
accumulator Apr 17, 2026
f48fefb
qml: only use QEAmount container instead of javascript/qml (q(u))int(64)
accumulator Apr 19, 2026
51e1372
qml: fix lnurlw
accumulator Apr 20, 2026
f7d2a9e
qml: take can-send into account for lnurlp
accumulator Apr 20, 2026
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/BalanceDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ Pane {
Layout.preferredWidth: 1
text: qsTr('Lightning swap');
visible: Daemon.currentWallet.isLightning
enabled: Daemon.currentWallet.lightningCanSend.satsInt > 0 || Daemon.currentWallet.lightningCanReceive.satsInt > 0
enabled: !Daemon.currentWallet.lightningCanSend.isEmpty || !Daemon.currentWallet.lightningCanReceive.isEmpty
icon.source: Qt.resolvedUrl('../../icons/update.png')
onClicked: app.startSwap()
}
Expand All @@ -224,7 +224,7 @@ Pane {
Layout.preferredWidth: 1
text: qsTr('Open Channel')
visible: Daemon.currentWallet.isLightning
enabled: Daemon.currentWallet.confirmedBalance.satsInt > 0
enabled: !Daemon.currentWallet.confirmedBalance.isEmpty
onClicked: {
var dialog = openChannelDialog.createObject(rootItem)
dialog.open()
Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qml/components/Channels.qml
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,16 @@ Pane {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Swap');
enabled: Daemon.currentWallet.lightningCanSend.satsInt > 0 ||
(Daemon.currentWallet.lightningCanReceive.satsInt > 0 && Daemon.currentWallet.confirmedBalance.satsInt > 0)
enabled: !Daemon.currentWallet.lightningCanSend.isEmpty ||
(!Daemon.currentWallet.lightningCanReceive.isEmpty && !Daemon.currentWallet.confirmedBalance.isEmpty)
icon.source: Qt.resolvedUrl('../../icons/update.png')
onClicked: app.startSwap()
}

FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
enabled: Daemon.currentWallet.canHaveLightning && Daemon.currentWallet.confirmedBalance.satsInt > 0
enabled: Daemon.currentWallet.canHaveLightning && !Daemon.currentWallet.confirmedBalance.isEmpty
text: qsTr('Open Channel')
onClicked: {
if (Daemon.currentWallet.channelModel.count == 0) {
Expand Down
12 changes: 6 additions & 6 deletions electrum/gui/qml/components/InvoiceDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,16 @@ ElDialog {
color: readOnly
? Material.accentColor
: Material.foreground
onTextAsSatsChanged: {
onValueChanged: {
if (!amountMax.checked)
invoice.amountOverride.copyFrom(textAsSats)
}
Connections {
target: invoice.amountOverride
function onSatsIntChanged() {
console.log('amountOverride satsIntChanged, sats=' + invoice.amountOverride.satsInt)
function onValueChanged() {
console.log('amountOverride valueChanged, sats=' + invoice.amountOverride.satsStr)
if (amountMax.checked) // amountOverride updated by max amount estimate
amountBtc.text = Config.formatSatsForEditing(invoice.amountOverride.satsInt)
amountBtc.text = Config.formatSatsForEditing(invoice.amountOverride)
}
}
}
Expand Down Expand Up @@ -472,7 +472,7 @@ ElDialog {
enabled: !invoice.isSaved && invoice.canSave
onClicked: {
if (invoice.amount.isEmpty) {
invoice.amountOverride = Config.unitsToSats(amountBtc.text)
invoice.amountOverride = Config.baseunitStrToAmount(amountBtc.text)
if (amountMax.checked)
invoice.amountOverride.isMax = true
}
Expand All @@ -490,7 +490,7 @@ ElDialog {
enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay
onClicked: {
if (invoice.amount.isEmpty) {
invoice.amountOverride = Config.unitsToSats(amountBtc.text)
invoice.amountOverride = Config.baseunitStrToAmount(amountBtc.text)
if (amountMax.checked)
invoice.amountOverride.isMax = true
}
Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qml/components/LightningPaymentDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Pane {
}

Label {
text: lnpaymentdetails.amount.msatsInt > 0
text: lnpaymentdetails.amount.positive
? qsTr('Amount received')
: qsTr('Amount sent')
color: Material.accentColor
Expand All @@ -64,13 +64,13 @@ Pane {
}

Label {
visible: lnpaymentdetails.amount.msatsInt < 0
visible: !lnpaymentdetails.amount.positive
text: qsTr('Transaction fee')
color: Material.accentColor
}

FormattedAmount {
visible: lnpaymentdetails.amount.msatsInt < 0
visible: !lnpaymentdetails.amount.positive
amount: lnpaymentdetails.fee
timestamp: lnpaymentdetails.timestamp
}
Expand Down
25 changes: 17 additions & 8 deletions electrum/gui/qml/components/LnurlPayRequestDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ 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: false
property bool valid: commentValid && amountValid

function isValidAmount() {
return amountBtc.textAsSats.gte(invoiceParser.lnurlData['min_sendable_msat'])
&& amountBtc.textAsSats.lte(invoiceParser.lnurlData['max_sendable_msat'])
&& amountBtc.textAsSats.lte(invoiceParser.wallet.lightningCanSend)
}

ColumnLayout {
width: parent.width

Expand All @@ -41,9 +46,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'].eq(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,12 +81,14 @@ 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'].eq(invoiceParser.lnurlData['max_sendable_msat'])
color: Material.foreground // override gray-out on disabled
fiatfield: amountFiat
onTextAsSatsChanged: {
msatPrecision: true
onValueChanged: {
invoiceParser.amountOverride = textAsSats
dialog.amountValid = isValidAmount()
}
}
Label {
Expand Down
77 changes: 49 additions & 28 deletions electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,62 @@ ElDialog {
title: qsTr('LNURL Withdraw request')
iconSource: '../../../icons/link.png'

property var wallet: Daemon.currentWallet // type: Wallet
property var requestDetails // type: RequestDetails
property var requestDetails
property var onemsat: Amount { Component.onCompleted: { msatsInt = 1 } }

padding: 0
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 effectiveMinWithdrawable: Math.max(providerMinWithdrawable, 1)
property int effectiveMaxWithdrawable: Math.min(providerMaxWithdrawable, walletCanReceive)
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
property var walletCanReceive: Amount {}
property var providerMinWithdrawable: requestDetails.lnurlData['min_withdrawable_msat']
property var providerMaxWithdrawable: requestDetails.lnurlData['max_withdrawable_msat']
property var effectiveMinWithdrawable: onemsat.max(providerMinWithdrawable, onemsat)
property var effectiveMaxWithdrawable: onemsat.min(providerMaxWithdrawable, requestDetails.wallet.lightningCanReceive)
property bool insufficientLiquidity: effectiveMinWithdrawable.gt(requestDetails.wallet.lightningCanReceive)
property bool liquidityWarning: providerMaxWithdrawable.gt(walletCanReceive)
property bool fixedAmount: false

property bool amountValid: isValidAmount()
property bool valid: amountValid

Component.onCompleted: {
dialog.walletCanReceive = wallet.lightningCanReceive.satsInt
updateLimits()
}

function isValidAmount() {
return !dialog.insufficientLiquidity
&& amountBtc.textAsSats.gte(dialog.effectiveMinWithdrawable)
&& amountBtc.textAsSats.lte(dialog.effectiveMaxWithdrawable)
}

function updateLimits() {
dialog.walletCanReceive.copyFrom(requestDetails.wallet.lightningCanReceive)
dialog.effectiveMaxWithdrawable = onemsat.min(dialog.providerMaxWithdrawable, requestDetails.wallet.lightningCanReceive)
dialog.insufficientLiquidity = dialog.effectiveMinWithdrawable.gt(requestDetails.wallet.lightningCanReceive)
dialog.liquidityWarning = dialog.providerMaxWithdrawable.gt(requestDetails.wallet.lightningCanReceive)
dialog.fixedAmount = dialog.providerMinWithdrawable.eq(dialog.providerMaxWithdrawable)
dialog.amountValid = isValidAmount()
}

Connections {
// assign walletCanReceive directly to prevent a binding loop
target: wallet
target: requestDetails.wallet
function onLightningCanReceiveChanged() {
if (!requestDetails.busy) {
// don't assign while busy to prevent the view from changing while receiving
// the incoming payment
dialog.walletCanReceive = wallet.lightningCanReceive.satsInt
updateLimits()
}
}
}

Connections {
target: amountBtc
function onValueChanged() {
dialog.amountValid = isValidAmount()
}
}

ColumnLayout {
width: parent.width
spacing: 0
Expand All @@ -69,10 +90,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 @@ -83,10 +104,10 @@ ElDialog {
Layout.columnSpan: 2
Layout.fillWidth: true
compact: true
visible: !dialog.insufficientLiquidity && dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable
visible: !dialog.insufficientLiquidity && !dialog.fixedAmount
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 +118,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 +156,11 @@ ElDialog {
BtcField {
id: amountBtc
Layout.preferredWidth: rootLayout.width / 3
text: Config.formatSatsForEditing(dialog.effectiveMaxWithdrawable)
enabled: !dialog.insufficientLiquidity && (dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable)
text: Config.formatMilliSatsForEditing(dialog.effectiveMaxWithdrawable)
enabled: !dialog.insufficientLiquidity && !dialog.fixedAmount
color: Material.foreground // override gray-out on disabled
fiatfield: amountFiat
msatPrecision: true
}
Label {
text: Config.baseUnit
Expand All @@ -154,7 +176,7 @@ ElDialog {
id: amountFiat
Layout.preferredWidth: rootLayout.width / 3
btcfield: amountBtc
enabled: !dialog.insufficientLiquidity && (dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable)
enabled: !dialog.insufficientLiquidity && !dialog.fixedAmount
color: Material.foreground
}
Label {
Expand All @@ -173,8 +195,7 @@ ElDialog {
icon.source: '../../icons/confirmed.png'
enabled: valid && !requestDetails.busy
onClicked: {
var satsAmount = amountBtc.textAsSats.satsInt;
requestDetails.lnurlRequestWithdrawal(satsAmount);
requestDetails.lnurlRequestWithdrawal(amountBtc.textAsSats);
dialog.close();
}
}
Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qml/components/OpenChannelDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ ElDialog {
id: amountBtc
fiatfield: amountFiat
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
onTextAsSatsChanged: {
onValueChanged: {
if (!is_max.checked)
channelopener.amount = amountBtc.textAsSats
}
Expand All @@ -177,9 +177,9 @@ 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)
amountBtc.text = Config.formatSatsForEditing(channelopener.amount)
}
}
}
Expand Down
Loading