Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
64cdd69
qt: fix weird inheritance tree FeerateEdit, move text render with dec…
accumulator Apr 15, 2026
6b2d390
qt: AmountEdit defaults to decimal_point = 0, declare FiatAmountEdit …
accumulator Apr 15, 2026
3a1d016
wallet: kwarg all parameters of wallet.create_request()
accumulator Apr 15, 2026
9341607
invoices: add BaseInvoice.get_amount_sat_msat_precision() func
accumulator Apr 16, 2026
8c688a7
qt: millisat precision request tab, request list, invoice list
accumulator Apr 15, 2026
40886d8
lnworker: can_{pay,receive}_invoice millisat precision
accumulator Apr 16, 2026
7611cb6
lnurl,pi: use msat precision
accumulator Apr 16, 2026
889f959
qt: type hints
accumulator Apr 16, 2026
7eeabbd
qt: BTCAmountEdit allow dynamically changing precision
accumulator Apr 16, 2026
b4050fc
payment_identifier: mark pi invalid if multiline and any output uses …
accumulator Apr 16, 2026
d88ac23
qt: send_tab: enable millisat precision, unless recipient is onchain
accumulator Apr 16, 2026
6493317
qml: in QEAmount, keep msat and sat synchronized, and be more strict …
accumulator Apr 17, 2026
26c2c3f
qml: qeconfig: add formatMilliSatsForEditing()
accumulator Apr 17, 2026
54d70ab
qml: update for lnurl msat precision
accumulator Apr 17, 2026
31079a3
qml: bind to explicit QEAmount.valueChanged, not to implicit property…
accumulator Apr 17, 2026
06ffd88
qml: controls millisat precision
accumulator Apr 17, 2026
a311a55
qml: payment request dialog millisat precision
accumulator Apr 17, 2026
5f1f397
qml: QERequestDetails millisat precision
accumulator Apr 17, 2026
96dc729
qml: QEInvoiceParser saveInvoice amount override msat precision
accumulator Apr 17, 2026
cc1eeb7
qml: qeinvoice imports, typing hints, asserts
accumulator Apr 17, 2026
d354a95
qml: formatMilliSats accepts ints
accumulator Apr 17, 2026
023be96
ChannelBar followup
accumulator Apr 17, 2026
0971820
qml: QEConfig.format(Milli)Sats allow None, return ''
accumulator Apr 17, 2026
2f2fb7d
qml: qefx always use QEAmount.msatInt
accumulator Apr 17, 2026
5e853ff
qml: qewallet: msat precision balances
accumulator Apr 17, 2026
a524ea7
qml: refactor QEConfig.unitsToSats to QEConfig.baseunitStrToAmount and
accumulator Apr 17, 2026
998f94b
qml: only use QEAmount container instead of javascript/qml (q(u))int(64)
accumulator Apr 19, 2026
ca73c3d
qt: fix lnurlw
accumulator Apr 20, 2026
a0596e2
qml: fix lnurlw
accumulator Apr 20, 2026
cb87a68
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.satInt > 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 @@ -125,16 +125,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 @@ -229,16 +229,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 @@ -469,7 +469,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 @@ -487,7 +487,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
24 changes: 17 additions & 7 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,8 +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)
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 @@ -73,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
80 changes: 52 additions & 28 deletions electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,65 @@ ElDialog {
title: qsTr('LNURL Withdraw request')
iconSource: '../../../icons/link.png'

property Wallet wallet: Daemon.currentWallet
property RequestDetails requestDetails
property Amount 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 {
onValueChanged: console.log('wallet can receive ' + msatsStr)
}
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
console.log('UPDATING')
updateLimits()
}
}
}

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

ColumnLayout {
width: parent.width

Expand All @@ -68,10 +92,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 @@ -81,11 +105,11 @@ 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.baseUnit)
.arg(Config.formatMilliSats(dialog.effectiveMinWithdrawable))
.arg(Config.formatMilliSats(dialog.effectiveMaxWithdrawable))
.arg(Config.baseUnit)
}

InfoTextArea {
Expand All @@ -94,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 @@ -131,10 +155,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 @@ -150,7 +175,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 @@ -167,8 +192,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 @@ -170,7 +170,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 @@ -181,9 +181,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