Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
53457f5
qt: fix weird inheritance tree FeerateEdit, move text render with dec…
accumulator Apr 15, 2026
958cc28
qml: in QEAmount, keep msat and sat synchronized, and be more strict …
accumulator Apr 17, 2026
3e35c93
wallet: kwarg all parameters of wallet.create_request()
accumulator Apr 15, 2026
482fe2c
invoices: add BaseInvoice.get_amount_sat_msat_precision() func
accumulator Apr 16, 2026
fef0639
qt: millisat precision request tab, request list, invoice list
accumulator Apr 15, 2026
e1fb917
lnworker: can_{pay,receive}_invoice millisat precision
accumulator Apr 16, 2026
8edb598
lnurl, pi: use msat precision
accumulator Apr 16, 2026
045d300
qml: millisat precision ReceiveDetailsDialog, QERequestDetails, QEInv…
accumulator Apr 17, 2026
86bf687
qml: refactor QEConfig.unitsToSats to QEConfig.baseunitStrToAmount and
accumulator Apr 17, 2026
26b92ec
qml: only use QEAmount container instead of javascript/qml (q(u))int(64)
accumulator Apr 19, 2026
3a81aa5
qml: fix lnurlw
accumulator Apr 20, 2026
2286d14
qml: take can-send into account for lnurlp
accumulator Apr 20, 2026
d2605bd
qt: BTCAmountEdit allow dynamically changing precision, disable msat …
accumulator Apr 16, 2026
7c98653
util: format_satoshis_plain: remove TODO, hard-fail on float
accumulator Jun 11, 2026
ab87a41
qml: a FiatField containing a zero effective value during editing set…
accumulator Jun 29, 2026
3b9f4e0
qml: expose amount in ReceiveDetailsDialog as an Amount type instead …
accumulator Jun 29, 2026
4dd3456
qml: use a stable instance for amount in QERequestDetails
accumulator Jun 29, 2026
2d84ca1
qml: ReceiveDetailsDialog fix can pay check on edit
accumulator Jun 29, 2026
ec453a2
qml: QEAmount remove warning on amount_msat is None
accumulator Jun 29, 2026
c809a35
qml: QEAmount for slots that return a QEAmount instance, return a par…
accumulator Jul 10, 2026
531bf95
qt: fix erroneous cast to int in liquidity check comparison
accumulator Jul 10, 2026
671221b
qml: QEAmount remove checks against isEmpty which are unneeded and as…
accumulator Jul 10, 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
4 changes: 2 additions & 2 deletions electrum/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def satoshis(amount):
return int(COIN*to_decimal(amount)) if amount is not None else None


def format_satoshis(x: Union[float, int, Decimal, None]) -> Optional[str]:
def format_satoshis(x: Union[int, Decimal, None]) -> Optional[str]:
"""
input: satoshis as a Number
output: str formatted as bitcoin amount
Expand Down 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 @@ -231,16 +231,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 @@ -473,7 +473,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 @@ -491,7 +491,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)
backgroundColor: constants.darkerDialogBackground
}

Expand Down Expand Up @@ -74,12 +82,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)
Comment thread
f321x marked this conversation as resolved.
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