Skip to content

Commit b29b777

Browse files
committed
qml: only use QEAmount container instead of javascript/qml (q(u))int(64)
Using javascript or QML `int` type is a long-standing issue, as there is not enough range to express millisats or even sats. A short summary of the issues: - integers in javascript context have a max range of +/- 2^54-1, which allows 21M BTC expressed in sats, but not in msats. - QML `int` properties have a max range of +/- 2^31-1, which only allows 21 BTC expressed in sats, 21 mBTC expressed in msats - `int` parameters declared in the `pyqtProperty` and `pyqtSlot` decorators have a max range of 2^31-1, although this is somewhat alleviated in the QML->Python direction by using `q(u)int64`. Returning a `q(u)int64` does not work around the `int` limitation In most of the QML code, `QEAmount` is already used for storing and passing around BTC values. The only exception is where amounts are compared (e.g. invoice amount < available balance etc), so the `<`, `>`, `<=`, `>=` and `!=` operators, and where these operators are implied, like `Math.min()` and `Math.max()` This commit delegates these operators to python scope.
1 parent 8f55a51 commit b29b777

21 files changed

Lines changed: 217 additions & 93 deletions

electrum/gui/qml/components/BalanceDetails.qml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ Pane {
214214
Layout.preferredWidth: 1
215215
text: qsTr('Lightning swap');
216216
visible: Daemon.currentWallet.isLightning
217-
enabled: Daemon.currentWallet.lightningCanSend.satsInt > 0 || Daemon.currentWallet.lightningCanReceive.satInt > 0
217+
enabled: !Daemon.currentWallet.lightningCanSend.isEmpty || !Daemon.currentWallet.lightningCanReceive.isEmpty
218218
icon.source: Qt.resolvedUrl('../../icons/update.png')
219219
onClicked: app.startSwap()
220220
}
@@ -224,7 +224,7 @@ Pane {
224224
Layout.preferredWidth: 1
225225
text: qsTr('Open Channel')
226226
visible: Daemon.currentWallet.isLightning
227-
enabled: Daemon.currentWallet.confirmedBalance.satsInt > 0
227+
enabled: !Daemon.currentWallet.confirmedBalance.isEmpty
228228
onClicked: {
229229
var dialog = openChannelDialog.createObject(rootItem)
230230
dialog.open()

electrum/gui/qml/components/Channels.qml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,16 @@ Pane {
125125
Layout.fillWidth: true
126126
Layout.preferredWidth: 1
127127
text: qsTr('Swap');
128-
enabled: Daemon.currentWallet.lightningCanSend.satsInt > 0 ||
129-
(Daemon.currentWallet.lightningCanReceive.satsInt > 0 && Daemon.currentWallet.confirmedBalance.satsInt > 0)
128+
enabled: !Daemon.currentWallet.lightningCanSend.isEmpty ||
129+
(!Daemon.currentWallet.lightningCanReceive.isEmpty && !Daemon.currentWallet.confirmedBalance.isEmpty)
130130
icon.source: Qt.resolvedUrl('../../icons/update.png')
131131
onClicked: app.startSwap()
132132
}
133133

134134
FlatButton {
135135
Layout.fillWidth: true
136136
Layout.preferredWidth: 1
137-
enabled: Daemon.currentWallet.canHaveLightning && Daemon.currentWallet.confirmedBalance.satsInt > 0
137+
enabled: Daemon.currentWallet.canHaveLightning && !Daemon.currentWallet.confirmedBalance.isEmpty
138138
text: qsTr('Open Channel')
139139
onClicked: {
140140
if (Daemon.currentWallet.channelModel.count == 0) {

electrum/gui/qml/components/InvoiceDialog.qml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,16 +229,16 @@ ElDialog {
229229
color: readOnly
230230
? Material.accentColor
231231
: Material.foreground
232-
onTextAsSatsChanged: {
232+
onValueChanged: {
233233
if (!amountMax.checked)
234234
invoice.amountOverride.copyFrom(textAsSats)
235235
}
236236
Connections {
237237
target: invoice.amountOverride
238238
function onValueChanged() {
239-
console.log('amountOverride valueChanged, sats=' + invoice.amountOverride.satsInt)
239+
console.log('amountOverride valueChanged, sats=' + invoice.amountOverride.satsStr)
240240
if (amountMax.checked) // amountOverride updated by max amount estimate
241-
amountBtc.text = Config.formatSatsForEditing(invoice.amountOverride.satsInt)
241+
amountBtc.text = Config.formatSatsForEditing(invoice.amountOverride)
242242
}
243243
}
244244
}

electrum/gui/qml/components/LightningPaymentDetails.qml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Pane {
5252
}
5353

5454
Label {
55-
text: lnpaymentdetails.amount.msatsInt > 0
55+
text: lnpaymentdetails.amount.positive
5656
? qsTr('Amount received')
5757
: qsTr('Amount sent')
5858
color: Material.accentColor
@@ -64,13 +64,13 @@ Pane {
6464
}
6565

6666
Label {
67-
visible: lnpaymentdetails.amount.msatsInt < 0
67+
visible: !lnpaymentdetails.amount.positive
6868
text: qsTr('Transaction fee')
6969
color: Material.accentColor
7070
}
7171

7272
FormattedAmount {
73-
visible: lnpaymentdetails.amount.msatsInt < 0
73+
visible: !lnpaymentdetails.amount.positive
7474
amount: lnpaymentdetails.fee
7575
timestamp: lnpaymentdetails.timestamp
7676
}

electrum/gui/qml/components/LnurlPayRequestDialog.qml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ ElDialog {
1919
needsSystemBarPadding: false
2020

2121
property bool commentValid: comment.text.length <= invoiceParser.lnurlData['comment_allowed']
22-
property bool amountValid: amountBtc.textAsSats.msatsInt >= parseInt(invoiceParser.lnurlData['min_sendable_msat'])
23-
&& amountBtc.textAsSats.msatsInt <= parseInt(invoiceParser.lnurlData['max_sendable_msat'])
22+
property bool amountValid: false
2423
property bool valid: commentValid && amountValid
2524

25+
function isValidAmount() {
26+
return amountBtc.textAsSats.gte(invoiceParser.lnurlData['min_sendable_msat'])
27+
&& amountBtc.textAsSats.lte(invoiceParser.lnurlData['max_sendable_msat'])
28+
}
29+
2630
ColumnLayout {
2731
width: parent.width
2832

@@ -41,7 +45,7 @@ ElDialog {
4145
Layout.columnSpan: 2
4246
Layout.fillWidth: true
4347
compact: true
44-
visible: invoiceParser.lnurlData['min_sendable_msat'] != invoiceParser.lnurlData['max_sendable_msat']
48+
visible: !invoiceParser.lnurlData['min_sendable_msat'].eq(invoiceParser.lnurlData['max_sendable_msat'])
4549
text: qsTr('Amount must be between %1 and %2 %3')
4650
.arg(Config.formatMilliSats(invoiceParser.lnurlData['min_sendable_msat']))
4751
.arg(Config.formatMilliSats(invoiceParser.lnurlData['max_sendable_msat']))
@@ -77,12 +81,13 @@ ElDialog {
7781
id: amountBtc
7882
Layout.preferredWidth: rootLayout.width /3
7983
text: Config.formatMilliSatsForEditing(invoiceParser.lnurlData['min_sendable_msat'])
80-
enabled: invoiceParser.lnurlData['min_sendable_msat'] != invoiceParser.lnurlData['max_sendable_msat']
84+
enabled: !invoiceParser.lnurlData['min_sendable_msat'].eq(invoiceParser.lnurlData['max_sendable_msat'])
8185
color: Material.foreground // override gray-out on disabled
8286
fiatfield: amountFiat
8387
msatPrecision: true
84-
onTextAsSatsChanged: {
88+
onValueChanged: {
8589
invoiceParser.amountOverride = textAsSats
90+
dialog.amountValid = isValidAmount()
8691
}
8792
}
8893
Label {

electrum/gui/qml/components/LnurlWithdrawRequestDialog.qml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,26 @@ ElDialog {
1515

1616
property Wallet wallet: Daemon.currentWallet
1717
property RequestDetails requestDetails
18+
property Amount onemsat: Amount { Component.onCompleted: { msatsInt = 1 } }
1819

1920
padding: 0
2021
needsSystemBarPadding: false
2122

22-
property int walletCanReceive: 0
23-
property int providerMinWithdrawable: parseInt(requestDetails.lnurlData['min_withdrawable_msat'])
24-
property int providerMaxWithdrawable: parseInt(requestDetails.lnurlData['max_withdrawable_msat'])
25-
property int effectiveMinWithdrawable: Math.max(providerMinWithdrawable, 1)
26-
property int effectiveMaxWithdrawable: Math.min(providerMaxWithdrawable, walletCanReceive)
27-
property bool insufficientLiquidity: effectiveMinWithdrawable > walletCanReceive
28-
property bool liquidityWarning: providerMaxWithdrawable > walletCanReceive
23+
property var walletCanReceive: Amount {}
24+
property var providerMinWithdrawable: requestDetails.lnurlData['min_withdrawable_msat']
25+
property var providerMaxWithdrawable: requestDetails.lnurlData['max_withdrawable_msat']
26+
property var effectiveMinWithdrawable: onemsat.max(providerMinWithdrawable, onemsat)
27+
property var effectiveMaxWithdrawable: onemsat.min(providerMaxWithdrawable, walletCanReceive)
28+
property bool insufficientLiquidity: effectiveMinWithdrawable.gt(walletCanReceive)
29+
property bool liquidityWarning: providerMaxWithdrawable.gt(walletCanReceive)
2930

3031
property bool amountValid: !dialog.insufficientLiquidity &&
31-
amountBtc.textAsSats.msatsInt >= dialog.effectiveMinWithdrawable &&
32-
amountBtc.textAsSats.msatsInt <= dialog.effectiveMaxWithdrawable
32+
amountBtc.textAsSats.gte(dialog.effectiveMinWithdrawable) &&
33+
amountBtc.textAsSats.lte(dialog.effectiveMaxWithdrawable)
3334
property bool valid: amountValid
3435

3536
Component.onCompleted: {
36-
dialog.walletCanReceive = wallet.lightningCanReceive.msatsInt
37+
dialog.walletCanReceive.copyFrom(wallet.lightningCanReceive)
3738
}
3839

3940
Connections {
@@ -43,7 +44,7 @@ ElDialog {
4344
if (!requestDetails.busy) {
4445
// don't assign while busy to prevent the view from changing while receiving
4546
// the incoming payment
46-
dialog.walletCanReceive = wallet.lightningCanReceive.msatsInt
47+
dialog.walletCanReceive.copyFrom(wallet.lightningCanReceive)
4748
}
4849
}
4950
}
@@ -83,9 +84,9 @@ ElDialog {
8384
compact: true
8485
visible: !dialog.insufficientLiquidity && dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable
8586
text: qsTr('Amount must be between %1 and %2 %3')
86-
.arg(Config.formatMilliSats(dialog.effectiveMinWithdrawable))
87-
.arg(Config.formatMilliSats(dialog.effectiveMaxWithdrawable))
88-
.arg(Config.baseUnit)
87+
.arg(Config.formatMilliSats(dialog.effectiveMinWithdrawable))
88+
.arg(Config.formatMilliSats(dialog.effectiveMaxWithdrawable))
89+
.arg(Config.baseUnit)
8990
}
9091

9192
InfoTextArea {
@@ -132,7 +133,7 @@ ElDialog {
132133
id: amountBtc
133134
Layout.preferredWidth: rootLayout.width / 3
134135
text: Config.formatMilliSatsForEditing(dialog.effectiveMaxWithdrawable)
135-
enabled: !dialog.insufficientLiquidity && (dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable)
136+
enabled: !dialog.insufficientLiquidity && !(dialog.providerMinWithdrawable.eq(dialog.providerMaxWithdrawable))
136137
color: Material.foreground // override gray-out on disabled
137138
fiatfield: amountFiat
138139
msatPrecision: true
@@ -151,7 +152,7 @@ ElDialog {
151152
id: amountFiat
152153
Layout.preferredWidth: rootLayout.width / 3
153154
btcfield: amountBtc
154-
enabled: !dialog.insufficientLiquidity && (dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable)
155+
enabled: !dialog.insufficientLiquidity && !(dialog.providerMinWithdrawable.eq(dialog.providerMaxWithdrawable))
155156
color: Material.foreground
156157
}
157158
Label {
@@ -168,8 +169,7 @@ ElDialog {
168169
icon.source: '../../icons/confirmed.png'
169170
enabled: valid && !requestDetails.busy
170171
onClicked: {
171-
var msatsAmount = amountBtc.textAsSats.msatsInt;
172-
requestDetails.lnurlRequestWithdrawal(msatsAmount);
172+
requestDetails.lnurlRequestWithdrawal(amountBtc.textAsSats);
173173
dialog.close();
174174
}
175175
}

electrum/gui/qml/components/OpenChannelDialog.qml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ ElDialog {
170170
id: amountBtc
171171
fiatfield: amountFiat
172172
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
173-
onTextAsSatsChanged: {
173+
onValueChanged: {
174174
if (!is_max.checked)
175175
channelopener.amount = amountBtc.textAsSats
176176
}
@@ -183,7 +183,7 @@ ElDialog {
183183
target: channelopener.amount
184184
function onValueChanged() {
185185
if (is_max.checked) // amount updated by max amount estimate
186-
amountBtc.text = Config.formatSatsForEditing(channelopener.amount.satsInt)
186+
amountBtc.text = Config.formatSatsForEditing(channelopener.amount)
187187
}
188188
}
189189
}

electrum/gui/qml/components/ReceiveDetailsDialog.qml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,16 @@ ElDialog {
110110
FlatButton {
111111
Layout.fillWidth: true
112112
Layout.preferredWidth: 1
113-
enabled: Daemon.currentWallet.isLightning && (Daemon.currentWallet.lightningCanReceive.msatsInt
114-
> amountBtc.textAsSats.msatsInt || Daemon.currentWallet.canGetZeroconfChannel)
113+
enabled: Daemon.currentWallet.isLightning && (Daemon.currentWallet.lightningCanReceive.gt(amountBtc.textAsSats)
114+
|| Daemon.currentWallet.canGetZeroconfChannel)
115115
text: qsTr('Lightning')
116116
icon.source: '../../icons/lightning.png'
117117
onClicked: {
118-
if (Daemon.currentWallet.lightningCanReceive.msatsInt > amountBtc.textAsSats.msatsInt) {
118+
if (Daemon.currentWallet.lightningCanReceive.gt(amountBtc.textAsSats)) {
119119
// can receive on existing channel
120120
dialog.isLightning = true
121121
doAccept()
122-
} else if (Daemon.currentWallet.canGetZeroconfChannel && amountBtc.textAsSats.msatsInt
123-
>= Daemon.currentWallet.minChannelFunding.msatsInt) {
122+
} else if (Daemon.currentWallet.canGetZeroconfChannel && amountBtc.textAsSats.gte(Daemon.currentWallet.minChannelFunding)) {
124123
// ask for confirmation of zeroconf channel to prevent fee surprise
125124
var confirmdialog = app.messageDialog.createObject(dialog, {
126125
title: qsTr('Confirm just-in-time channel'),
@@ -140,7 +139,7 @@ ElDialog {
140139
title: qsTr("Amount too low"),
141140
text: [qsTr("You don't have channels with enough inbound liquidity to receive this payment."),
142141
qsTr("Request at least %1 to open a channel just-in-time.").arg(
143-
Config.formatSats(Daemon.currentWallet.minChannelFunding.satsInt, true))].join(' ')
142+
Config.formatSats(Daemon.currentWallet.minChannelFunding, true))].join(' ')
144143
})
145144
confirmdialog.open()
146145
}

electrum/gui/qml/components/TxDetails.qml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ Pane {
106106
Label {
107107
Layout.preferredWidth: 1
108108
Layout.fillWidth: true
109-
visible: !txdetails.isUnrelated && txdetails.amount.satsInt != 0
110-
text: txdetails.amount.satsInt > 0
109+
visible: !txdetails.isUnrelated && !txdetails.amount.isEmpty
110+
text: txdetails.amount.positive
111111
? qsTr('Amount received onchain')
112112
: qsTr('Amount sent onchain')
113113
color: Material.accentColor
@@ -117,23 +117,23 @@ Pane {
117117
FormattedAmount {
118118
Layout.preferredWidth: 1
119119
Layout.fillWidth: true
120-
visible: !txdetails.isUnrelated && txdetails.amount.satsInt != 0
120+
visible: !txdetails.isUnrelated && !txdetails.amount.isEmpty
121121
amount: txdetails.amount
122122
timestamp: txdetails.timestamp
123123
}
124124

125125
Label {
126126
Layout.fillWidth: true
127-
visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt != 0
128-
text: txdetails.lnAmount.satsInt > 0
127+
visible: !txdetails.isUnrelated && !txdetails.lnAmount.isEmpty
128+
text: txdetails.lnAmount.positive
129129
? qsTr('Amount received in channels')
130130
: qsTr('Amount withdrawn from channels')
131131
color: Material.accentColor
132132
wrapMode: Text.Wrap
133133
}
134134

135135
FormattedAmount {
136-
visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt != 0
136+
visible: !txdetails.isUnrelated && !txdetails.lnAmount.isEmpty
137137
Layout.preferredWidth: 1
138138
Layout.fillWidth: true
139139
amount: txdetails.lnAmount.isEmpty ? txdetails.amount : txdetails.lnAmount

electrum/gui/qml/components/WalletMainView.qml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,9 +540,9 @@ Item {
540540
if (invoice.invoiceType == Invoice.LightningInvoice && invoice.address) {
541541
// ln invoice with fallback
542542
var amountToSend = invoice.amountOverride.isEmpty
543-
? invoice.amount.satsInt
544-
: invoice.amountOverride.satsInt
545-
if (amountToSend > Daemon.currentWallet.lightningCanSend.satsInt) {
543+
? invoice.amount
544+
: invoice.amountOverride
545+
if (amountToSend.gt(Daemon.currentWallet.lightningCanSend)) {
546546
lninvoiceButPayOnchain = true
547547
}
548548
}

0 commit comments

Comments
 (0)