qml: FeePicker: restrict abs/rate editing to mimic wallet.bump_fee/cpfp
What changed, and why it matters
This commit tightens the user interface for fee editing in Electrum's mobile/QML wallet. Previously, users could freely type either a fee rate or a total absolute fee when bumping or cancelling transactions. Now, the app restricts which of those two inputs is shown depending on the operation, matching the rules already enforced by the underlying wallet code. This reduces the chance that a user accidentally creates an invalid or exploitable transaction by entering a fee value the wallet logic does not expect.
Review the underlying wallet.bump_fee and wallet.cpfp implementations to confirm they reject unexpected fee inputs, since this patch only restricts the GUI. Consider whether any other dialogs or flows using FeePicker should also restrict absolute or rate editing. No urgent action is required; this appears to be a defensive UI-hardening change.
Security signals we found
UI input restriction to match backend transaction finalizer logic
Prevents user from supplying absolute fee where only fee rate is expected, and vice versa
Reduces risk of fee-related transaction construction errors or edge-case abuse
No backend validation changes; relies on existing wallet.bump_fee/cpfp behavior
Evidence from the diff
The patch modifies the QML FeePicker control to expose two new boolean properties: allowPickerAbsFees and allowPickerFeeRates. It then sets these to false in the relevant dialogs: RbfBumpFeeDialog and RbfCancelDialog disable absolute fee editing, while CpfpBumpFeeDialog disables fee-rate editing. The UI layout is refactored so the Rate and Total input rows are independently shown or hidden. The commit message explicitly states the goal is to ‘mimic wallet.bump_fee/cpfp’, i.e., align the GUI with the backend’s expectations and avoid mismatched fee inputs.
Changed components
electrum/gui/qml/components/controls/FeePicker.qmlelectrum/gui/qml/components/RbfBumpFeeDialog.qmlelectrum/gui/qml/components/RbfCancelDialog.qmlelectrum/gui/qml/components/CpfpBumpFeeDialog.qmlInspect captured patch +31 / −22
diff --git a/electrum/gui/qml/components/CpfpBumpFeeDialog.qml b/electrum/gui/qml/components/CpfpBumpFeeDialog.qml
index d68f9ed..1c8f7b1 100644
--- a/electrum/gui/qml/components/CpfpBumpFeeDialog.qml
+++ b/electrum/gui/qml/components/CpfpBumpFeeDialog.qml
@@ -147,6 +147,7 @@ ElDialog {
Layout.fillWidth: true
finalizer: dialog.cpfpfeebumper
showTxInfo: false
+ allowPickerFeeRates: false
}
}
}
diff --git a/electrum/gui/qml/components/RbfBumpFeeDialog.qml b/electrum/gui/qml/components/RbfBumpFeeDialog.qml
index a96add2..4d9fdcf 100644
--- a/electrum/gui/qml/components/RbfBumpFeeDialog.qml
+++ b/electrum/gui/qml/components/RbfBumpFeeDialog.qml
@@ -116,7 +116,7 @@ ElDialog {
id: feepicker
width: parent.width
finalizer: dialog.rbffeebumper
-
+ allowPickerAbsFees: false
}
}
diff --git a/electrum/gui/qml/components/RbfCancelDialog.qml b/electrum/gui/qml/components/RbfCancelDialog.qml
index b391004..b8832b1 100644
--- a/electrum/gui/qml/components/RbfCancelDialog.qml
+++ b/electrum/gui/qml/components/RbfCancelDialog.qml
@@ -88,7 +88,7 @@ ElDialog {
id: feepicker
width: parent.width
finalizer: dialog.txcanceller
-
+ allowPickerAbsFees: false
}
}
diff --git a/electrum/gui/qml/components/controls/FeePicker.qml b/electrum/gui/qml/components/controls/FeePicker.qml
index df97d5b..0fe96b7 100644
--- a/electrum/gui/qml/components/controls/FeePicker.qml
+++ b/electrum/gui/qml/components/controls/FeePicker.qml
@@ -18,6 +18,8 @@ Item {
property bool showTxInfo: true
property bool showPicker: true
+ property bool allowPickerAbsFees: true
+ property bool allowPickerFeeRates: true
property bool manualFeeEntry: finalizer.method == FeeSlider.FSMethod.MANUAL
@@ -121,24 +123,22 @@ Item {
}
}
- Label {
+ RowLayout {
+ Layout.columnSpan: 2
Layout.fillWidth: true
- Layout.preferredWidth: 1
- text: qsTr('Rate')
- color: Material.accentColor
- visible: showPicker && manualFeeEntry
- }
+ visible: showPicker && manualFeeEntry && allowPickerFeeRates
- GridLayout {
- Layout.preferredWidth: 2
- Layout.rowSpan: 2
- visible: showPicker && manualFeeEntry
- columns: 2
- columnSpacing: constants.paddingMedium
+ Label {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+ text: qsTr('Rate')
+ color: Material.accentColor
+ }
TextField {
id: rate
Layout.fillWidth: true
+ Layout.preferredWidth: 2
text: finalizer.userFeerate
color: finalizer.isUserFeerateLast ? Material.foreground : Material.accentColor
inputMethodHints: Qt.ImhDigitsOnly
@@ -152,13 +152,28 @@ Item {
Label {
Layout.fillWidth: true
+ Layout.preferredWidth: 1
color: Material.accentColor
text: UI_UNIT_NAME.FEERATE_SAT_PER_VBYTE
}
+ }
+
+ RowLayout {
+ Layout.columnSpan: 2
+ Layout.fillWidth: true
+ visible: showPicker && manualFeeEntry && allowPickerAbsFees
+
+ Label {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+ color: Material.accentColor
+ text: qsTr('Total')
+ }
TextField {
id: absolute
Layout.fillWidth: true
+ Layout.preferredWidth: 2
text: finalizer.userFee
color: finalizer.isUserFeerateLast ? Material.accentColor : Material.foreground
inputMethodHints: Qt.ImhDigitsOnly
@@ -172,18 +187,11 @@ Item {
Label {
Layout.fillWidth: true
+ Layout.preferredWidth: 1
color: Material.accentColor
text: UI_UNIT_NAME.FIXED_SAT
}
}
- Label {
- Layout.fillWidth: true
- Layout.preferredWidth: 1
- visible: showPicker && manualFeeEntry
- color: Material.accentColor
- text: qsTr('Total')
- }
-
}
}
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.