mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-23 12:08:53 +00:00
feat(wallet): transaction settings component added
Closes #16193 #16194
This commit is contained in:
parent
ed8564d228
commit
73d4f30dc4
@ -3,6 +3,7 @@ import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import Storybook 1.0
|
||||
import Models 1.0
|
||||
@ -32,6 +33,14 @@ SplitView {
|
||||
enabled: enabledCheckBox.checked
|
||||
input.edit.readOnly: readOnlyCheckBox.checked
|
||||
input.clearable: clearableCheckBox.checked
|
||||
label: "main label"
|
||||
secondaryLabel: "secondary label"
|
||||
labelIcon: "info"
|
||||
labelIconColor: Theme.palette.baseColor1
|
||||
labelIconClickable: true
|
||||
leftPadding: 10
|
||||
bottomLabelMessageRightCmp.text: "Current: 8.2 GWEI"
|
||||
bottomLabelMessageLeftCmp.text: "0.0031 ETH"
|
||||
}
|
||||
}
|
||||
|
||||
|
88
storybook/pages/TransactionSettingsPanelPage.qml
Normal file
88
storybook/pages/TransactionSettingsPanelPage.qml
Normal file
@ -0,0 +1,88 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import AppLayouts.Wallet.views 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
SplitView {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
orientation: Qt.Vertical
|
||||
|
||||
Rectangle {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
color: Theme.palette.baseColor3
|
||||
|
||||
TransactionSettings {
|
||||
id: txSettings
|
||||
anchors.centerIn: parent
|
||||
|
||||
currentBaseFee: "8.2"
|
||||
currentSuggestedMinPriorityFee: "0.06"
|
||||
currentSuggestedMaxPriorityFee: "5.1"
|
||||
currentGasAmount: "31500"
|
||||
currentNonce: 21
|
||||
|
||||
normalPrice: "1.45 EUR"
|
||||
normalTime: "~60s"
|
||||
fastPrice: "1.65 EUR"
|
||||
fastTime: "~40s"
|
||||
urgentPrice: "1.85 EUR"
|
||||
urgentTime: "~15s"
|
||||
|
||||
customBaseFee: "6.6"
|
||||
customPriorityFee: "7.7"
|
||||
customGasAmount: "35000"
|
||||
customNonce: "22"
|
||||
|
||||
selectedFeeMode: StatusFeeOption.Type.Normal
|
||||
|
||||
fnGetPriceInCurrencyForFee: function(feeInWei) {
|
||||
return "0.25 USD"
|
||||
}
|
||||
|
||||
onConfirmClicked: {
|
||||
logs.logEvent("confirm clicked...")
|
||||
logs.logEvent(`selected fee mode: ${txSettings.selectedFeeMode}`)
|
||||
if (selectedFeeMode === StatusFeeOption.Type.Custom) {
|
||||
logs.logEvent(`selected customBaseFee...${txSettings.customBaseFee}`)
|
||||
logs.logEvent(`selected customPriorityFee...${txSettings.customPriorityFee}`)
|
||||
logs.logEvent(`selected customGasAmount...${txSettings.customGasAmount}`)
|
||||
logs.logEvent(`selected customNonce...${txSettings.customNonce}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logs {
|
||||
id: logs
|
||||
}
|
||||
|
||||
LogsView {
|
||||
clip: true
|
||||
|
||||
SplitView.preferredHeight: 150
|
||||
SplitView.fillWidth: true
|
||||
|
||||
logText: logs.logText
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
SplitView.preferredWidth: 300
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// category: Panel
|
@ -96,6 +96,42 @@ Item {
|
||||
\endqml
|
||||
*/
|
||||
property alias errorMessageCmp: errorMessage
|
||||
/*!
|
||||
\qmlproperty bottomLabelMessageLeftCmp
|
||||
This property represents the bottomLabelMessageLeft shown on statusInput on the left of the input component.
|
||||
By default this component is hidden and doesn't have any text set, once the text is set it will become visible.
|
||||
Regardless of the set text and visibility bottomLabelMessageLeftCmp will be visible only if there is no error
|
||||
meaning no errorMessageCmp visible.
|
||||
|
||||
Examples of usage
|
||||
|
||||
\qml
|
||||
StatusInput {
|
||||
bottomLabelMessageLeftCmp.text: "some text"
|
||||
bottomLabelMessageLeftCmp.font.pixelSize: 15
|
||||
bottomLabelMessageLeftCmp.font.weight: Font.Medium
|
||||
}
|
||||
\endqml
|
||||
*/
|
||||
property alias bottomLabelMessageLeftCmp: bottomLabelMessageLeft
|
||||
/*!
|
||||
\qmlproperty bottomLabelMessageCmp
|
||||
This property represents the bottomLabelMessageRight shown on statusInput on the right of the input component.
|
||||
By default this component is hidden and doesn't have any text set, once the text is set it will become visible.
|
||||
Regardless of the set text and visibility bottomLabelMessageRightCmp will be visible only if there is no error
|
||||
meaning no errorMessageCmp visible.
|
||||
|
||||
Examples of usage
|
||||
|
||||
\qml
|
||||
StatusInput {
|
||||
bottomLabelMessageRightCmp.text: "some text"
|
||||
bottomLabelMessageRightCmp.font.pixelSize: 15
|
||||
bottomLabelMessageRightCmp.font.weight: Font.Medium
|
||||
}
|
||||
\endqml
|
||||
*/
|
||||
property alias bottomLabelMessageRightCmp: bottomLabelMessageRight
|
||||
/*!
|
||||
\qmlproperty int StatusInput::labelPadding
|
||||
This property sets the padding of the label text.
|
||||
@ -111,6 +147,21 @@ Item {
|
||||
This property sets the secondary label text.
|
||||
*/
|
||||
property string secondaryLabel: ""
|
||||
/*!
|
||||
\qmlproperty string StatusInput::labelIcon
|
||||
This property sets the icon displayd on the right of the label.
|
||||
*/
|
||||
property string labelIcon: ""
|
||||
/*!
|
||||
\qmlproperty string StatusInput::labelIconColor
|
||||
This property sets the color of the label icon.
|
||||
*/
|
||||
property string labelIconColor: Theme.palette.baseColor1
|
||||
/*!
|
||||
\qmlproperty string StatusInput::labelIconClickable
|
||||
This property sets if the label icon is clickable or not, if clickable labelIconClicked signal will be emitted.
|
||||
*/
|
||||
property bool labelIconClickable: false
|
||||
/*!
|
||||
\qmlproperty int StatusInput::charLimit
|
||||
This property sets the character limit of the text input.
|
||||
@ -205,6 +256,11 @@ Item {
|
||||
This signal is emitted when the icon is clicked.
|
||||
*/
|
||||
signal iconClicked()
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the label icon is clicked.
|
||||
*/
|
||||
signal labelIconClicked()
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when a hard key is pressed passing as parameter the keyboard event.
|
||||
@ -428,6 +484,23 @@ Item {
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
StatusIcon {
|
||||
id: labelIcon
|
||||
visible: !!root.labelIcon
|
||||
width: 16
|
||||
height: 16
|
||||
icon: root.labelIcon
|
||||
color: root.labelIconColor
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.labelIconClickable
|
||||
hoverEnabled: root.labelIconClickable
|
||||
cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.labelIconClicked()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@ -467,24 +540,47 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: errorMessage
|
||||
visible: {
|
||||
if (!text)
|
||||
return false;
|
||||
|
||||
if ((root.validationMode === StatusInput.ValidationMode.OnlyWhenDirty && statusBaseInput.dirty) ||
|
||||
root.validationMode === StatusInput.ValidationMode.Always)
|
||||
return !statusBaseInput.valid;
|
||||
|
||||
return false;
|
||||
}
|
||||
font.pixelSize: 12
|
||||
color: Theme.palette.dangerColor1
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
RowLayout {
|
||||
id: bottomRow
|
||||
Layout.topMargin: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.max(bottomLabelMessageLeft.height, errorMessage.height, bottomLabelMessageLeft.height)
|
||||
|
||||
StatusBaseText {
|
||||
id: bottomLabelMessageLeft
|
||||
Layout.fillWidth: true
|
||||
visible: !errorMessage.visible && !!text
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: errorMessage
|
||||
Layout.fillWidth: true
|
||||
visible: {
|
||||
if (!text)
|
||||
return false;
|
||||
|
||||
if ((root.validationMode === StatusInput.ValidationMode.OnlyWhenDirty && statusBaseInput.dirty) ||
|
||||
root.validationMode === StatusInput.ValidationMode.Always)
|
||||
return !statusBaseInput.valid;
|
||||
|
||||
return false;
|
||||
}
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
color: Theme.palette.dangerColor1
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: bottomLabelMessageRight
|
||||
visible: !errorMessage.visible && !!text
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ QObject {
|
||||
const totalMaxFees = Math.ceil(bestPath.gasFees.maxFeePerGasM) * bestPath.gasAmount
|
||||
const totalMaxFeesInEth = AmountsArithmetic.div(
|
||||
AmountsArithmetic.fromString(totalMaxFees),
|
||||
AmountsArithmetic.fromNumber(1, Constants.gweiExponent))
|
||||
AmountsArithmetic.fromNumber(1, Constants.ethTokenGWeiDecimals))
|
||||
root.swapOutputData.maxFeesToReserveRaw = AmountsArithmetic.times(totalMaxFeesInEth, AmountsArithmetic.fromExponent(Constants.ethTokenDecimals)).toString()
|
||||
|
||||
root.swapOutputData.approvalNeeded = !!bestPath ? bestPath.approvalRequired: false
|
||||
|
491
ui/app/AppLayouts/Wallet/views/TransactionSettings.qml
Normal file
491
ui/app/AppLayouts/Wallet/views/TransactionSettings.qml
Normal file
@ -0,0 +1,491 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
import shared.popups 1.0
|
||||
import utils 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property string currentBaseFee
|
||||
required property string currentSuggestedMinPriorityFee
|
||||
required property string currentSuggestedMaxPriorityFee
|
||||
required property string currentGasAmount
|
||||
required property int currentNonce
|
||||
|
||||
property alias normalPrice: optionNormal.subText
|
||||
property alias normalTime: optionNormal.additionalText
|
||||
|
||||
property alias fastPrice: optionFast.subText
|
||||
property alias fastTime: optionFast.additionalText
|
||||
|
||||
property alias urgentPrice: optionUrgent.subText
|
||||
property alias urgentTime: optionUrgent.additionalText
|
||||
|
||||
property alias customBaseFee: customBaseFeeInput.text
|
||||
property alias customBaseFeeDirty: customBaseFeeInput.input.dirty
|
||||
property alias customPriorityFee: customPriorityFeeInput.text
|
||||
property alias customPriorityFeeDirty: customPriorityFeeInput.input.dirty
|
||||
property alias customGasAmount: customGasAmountInput.text
|
||||
property alias customGasAmountDirty: customGasAmountInput.input.dirty
|
||||
property alias customNonce: customNonceInput.text
|
||||
property alias customNonceDirty: customNonceInput.input.dirty
|
||||
|
||||
required property int selectedFeeMode
|
||||
|
||||
required property var fnGetPriceInCurrencyForFee
|
||||
|
||||
signal confirmClicked()
|
||||
signal cancelClicked()
|
||||
|
||||
color: Theme.palette.statusModal.backgroundColor
|
||||
radius: Theme.radius
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
focus: true
|
||||
|
||||
Keys.onReleased: {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.cancelClicked()
|
||||
}
|
||||
}
|
||||
|
||||
function recalculateCustomPrice() {
|
||||
d.recalculateCustomPrice()
|
||||
}
|
||||
|
||||
Component.onCompleted: root.forceActiveFocus()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property bool customMode: root.selectedFeeMode === StatusFeeOption.Type.Custom
|
||||
|
||||
function showAlert(title, text, note, url) {
|
||||
infoBox.title = title
|
||||
infoBox.text = text
|
||||
infoBox.note = note
|
||||
infoBox.url = url
|
||||
infoBox.active = true
|
||||
}
|
||||
|
||||
function recalculateCustomBaseFeePrice() {
|
||||
if (!customBaseFeeInput.text) {
|
||||
customBaseFeeInput.bottomLabelMessageRightCmp.text = ""
|
||||
return
|
||||
}
|
||||
const weiValue = Utils.gweiToWei(customBaseFeeInput.text).toFixed()
|
||||
customBaseFeeInput.bottomLabelMessageRightCmp.text = root.fnGetPriceInCurrencyForFee(weiValue)
|
||||
}
|
||||
|
||||
function recalculateCustomPriorityFeePrice() {
|
||||
if (!customPriorityFeeInput.text) {
|
||||
customPriorityFeeInput.bottomLabelMessageRightCmp.text = ""
|
||||
return
|
||||
}
|
||||
const weiValue = Utils.gweiToWei(customPriorityFeeInput.text).toFixed()
|
||||
customPriorityFeeInput.bottomLabelMessageRightCmp.text = root.fnGetPriceInCurrencyForFee(weiValue)
|
||||
}
|
||||
|
||||
function recalculateCustomPrice() {
|
||||
if (!customBaseFeeInput.text || !customPriorityFeeInput.text || !customGasAmountInput.text) {
|
||||
optionCustom.subText = ""
|
||||
return
|
||||
}
|
||||
const baseFeeWei = Utils.gweiToWei(customBaseFeeInput.text)
|
||||
const priorityFeeWei = Utils.gweiToWei(customPriorityFeeInput.text)
|
||||
const totalFee = SQUtils.AmountsArithmetic.sum(baseFeeWei, priorityFeeWei)
|
||||
const feeInWei = SQUtils.AmountsArithmetic.times(totalFee, SQUtils.AmountsArithmetic.fromString(customGasAmountInput.text)).toFixed()
|
||||
optionCustom.subText = root.fnGetPriceInCurrencyForFee(feeInWei)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: infoBox
|
||||
anchors.centerIn: root
|
||||
active: false
|
||||
|
||||
property string title
|
||||
property string text
|
||||
property string note
|
||||
property string url
|
||||
|
||||
sourceComponent: AlertPopup {
|
||||
title: infoBox.title
|
||||
|
||||
width: root.width - 2 * 20
|
||||
|
||||
acceptBtnText: qsTr("Got it")
|
||||
cancelBtn.text: !!infoBox.url? qsTr("Read more") : ""
|
||||
cancelBtn.icon.name: "external-link"
|
||||
cancelBtn.visible: !!infoBox.url
|
||||
|
||||
alertLabel.text: infoBox.text
|
||||
alertNote.visible: !!infoBox.note
|
||||
alertNote.text: infoBox.note
|
||||
alertNote.color: Theme.palette.baseColor1
|
||||
|
||||
onCancelClicked: {
|
||||
Qt.openUrlExternally(infoBox.url)
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
infoBox.active = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
infoBox.item.open()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
ColumnLayout {
|
||||
Layout.margins: 20
|
||||
|
||||
spacing: 16
|
||||
|
||||
StatusBaseText {
|
||||
Layout.preferredWidth: parent.width
|
||||
text: qsTr("Transaction settings")
|
||||
font.pixelSize: Theme.secondaryAdditionalTextSize
|
||||
font.bold: true
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: options
|
||||
spacing: 12
|
||||
|
||||
StatusFeeOption {
|
||||
id: optionNormal
|
||||
type: StatusFeeOption.Type.Normal
|
||||
selected: root.selectedFeeMode === StatusFeeOption.Type.Normal
|
||||
showSubText: true
|
||||
// showAdditionalText: true // TODO: temoporary disabled until we figure out how to estimate time more granularly
|
||||
|
||||
onClicked: root.selectedFeeMode = StatusFeeOption.Type.Normal
|
||||
}
|
||||
|
||||
StatusFeeOption {
|
||||
id: optionFast
|
||||
type: StatusFeeOption.Type.Fast
|
||||
selected: root.selectedFeeMode === StatusFeeOption.Type.Fast
|
||||
showSubText: true
|
||||
// showAdditionalText: true // TODO: temoporary disabled until we figure out how to estimate time more granularly
|
||||
|
||||
onClicked: root.selectedFeeMode = StatusFeeOption.Type.Fast
|
||||
}
|
||||
|
||||
StatusFeeOption {
|
||||
id: optionUrgent
|
||||
type: StatusFeeOption.Type.Urgent
|
||||
selected: root.selectedFeeMode === StatusFeeOption.Type.Urgent
|
||||
showSubText: true
|
||||
// showAdditionalText: true // TODO: temoporary disabled until we figure out how to estimate time more granularly
|
||||
|
||||
onClicked: root.selectedFeeMode = StatusFeeOption.Type.Urgent
|
||||
}
|
||||
|
||||
StatusFeeOption {
|
||||
id: optionCustom
|
||||
type: StatusFeeOption.Type.Custom
|
||||
selected: root.selectedFeeMode === StatusFeeOption.Type.Custom
|
||||
showSubText: !!selected
|
||||
// showAdditionalText: !!selected // TODO: temoporary disabled until we figure out how to estimate time more granularly
|
||||
unselectedText: "Set your own fees & nonce"
|
||||
|
||||
onClicked: root.selectedFeeMode = StatusFeeOption.Type.Custom
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.preferredWidth: parent.width
|
||||
visible: !d.customMode
|
||||
text: qsTr("Increased base and priority fee, incentivising miners to confirm more quickly")
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: Theme.tertiaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
ShapeRectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: customLayout.height + customLayout.anchors.margins
|
||||
visible: d.customMode
|
||||
|
||||
ColumnLayout {
|
||||
id: customLayout
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 20
|
||||
width: parent.width - 2 * anchors.margins
|
||||
spacing: 16
|
||||
|
||||
StatusInput {
|
||||
id: customBaseFeeInput
|
||||
|
||||
readonly property bool displayLowBaseFeeWarning: {
|
||||
if (!customBaseFeeInput.text) {
|
||||
return
|
||||
}
|
||||
const weiCurrentValue = SQUtils.AmountsArithmetic.fromString(root.currentBaseFee)
|
||||
const decreasedCurrentValue = SQUtils.AmountsArithmetic.times(weiCurrentValue, SQUtils.AmountsArithmetic.fromString("0.9")) // up to -10% is acceptable
|
||||
const weiEnteredValue = Utils.gweiToWei(customBaseFeeInput.text)
|
||||
return decreasedCurrentValue.cmp(weiEnteredValue) === 1
|
||||
}
|
||||
|
||||
readonly property bool displayHighBaseFeeWarning: {
|
||||
if (!customBaseFeeInput.text) {
|
||||
return
|
||||
}
|
||||
const weiCurrentValue = SQUtils.AmountsArithmetic.fromString(root.currentBaseFee)
|
||||
const increasedCurrentValue = SQUtils.AmountsArithmetic.times(weiCurrentValue, SQUtils.AmountsArithmetic.fromString("1.2")) // up to 20% higher value is acceptable
|
||||
const weiEnteredValue = Utils.gweiToWei(customBaseFeeInput.text)
|
||||
return weiEnteredValue.cmp(increasedCurrentValue) === 1
|
||||
}
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 20
|
||||
label: qsTr("Max base fee")
|
||||
labelIcon: "info"
|
||||
labelIconColor: Theme.palette.baseColor1
|
||||
labelIconClickable: true
|
||||
bottomLabelMessageLeftCmp.color: customBaseFeeInput.displayLowBaseFeeWarning
|
||||
|| customBaseFeeInput.displayHighBaseFeeWarning?
|
||||
Theme.palette.miscColor6
|
||||
: Theme.palette.baseColor1
|
||||
bottomLabelMessageLeftCmp.text: customBaseFeeInput.displayLowBaseFeeWarning?
|
||||
qsTr("Lower than necessary (current %1)").arg(Utils.weiToGWei(root.currentBaseFee))
|
||||
: customBaseFeeInput.displayHighBaseFeeWarning?
|
||||
qsTr("Higher than necessary (current %1)").arg(Utils.weiToGWei(root.currentBaseFee))
|
||||
: qsTr("Current: %1 GWEI").arg(Utils.weiToGWei(root.currentBaseFee))
|
||||
rightPadding: leftPadding
|
||||
input.rightComponent: StatusBaseText {
|
||||
text: "GWEI"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
validators: [
|
||||
StatusRegularExpressionValidator {
|
||||
regularExpression: Constants.regularExpressions.positiveRealNumbers
|
||||
errorMessage: Constants.errorMessages.positiveRealNumbers
|
||||
}
|
||||
]
|
||||
|
||||
onTextChanged: Qt.callLater(() => {
|
||||
if (!customBaseFeeInput.valid) {
|
||||
return
|
||||
}
|
||||
d.recalculateCustomBaseFeePrice()
|
||||
d.recalculateCustomPrice()
|
||||
})
|
||||
|
||||
onLabelIconClicked: d.showAlert(label,
|
||||
qsTr("When your transaction gets included in the block, any difference between your max base fee and the actual base fee will be refunded.\n"),
|
||||
qsTr("Note: the ETH amount shown for this value is calculated:\nMax base fee (in GWEI) * Max gas amount"),
|
||||
"")
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
id: customPriorityFeeInput
|
||||
|
||||
readonly property bool displayHigherPriorityFeeWarning: {
|
||||
if (!customPriorityFeeInput.text) {
|
||||
return false
|
||||
}
|
||||
const weiCurrentValue = SQUtils.AmountsArithmetic.fromString(root.currentSuggestedMaxPriorityFee)
|
||||
const weiEnteredValue = Utils.gweiToWei(customPriorityFeeInput.text)
|
||||
return weiEnteredValue.cmp(weiCurrentValue) === 1
|
||||
}
|
||||
|
||||
readonly property bool displayHigherThanBaseFeeWarning: {
|
||||
if (!customPriorityFeeInput.text || !customBaseFeeInput.text) {
|
||||
return false
|
||||
}
|
||||
const weiBaseFeeValue = Utils.gweiToWei(customBaseFeeInput.text)
|
||||
const weiEnteredValue = Utils.gweiToWei(customPriorityFeeInput.text)
|
||||
return weiEnteredValue.cmp(weiBaseFeeValue) === 1
|
||||
}
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
label: qsTr("Priority fee")
|
||||
labelIcon: "info"
|
||||
labelIconColor: Theme.palette.baseColor1
|
||||
labelIconClickable: true
|
||||
bottomLabelMessageLeftCmp.color: customPriorityFeeInput.displayHigherThanBaseFeeWarning
|
||||
|| customPriorityFeeInput.displayHigherPriorityFeeWarning?
|
||||
Theme.palette.miscColor6
|
||||
: Theme.palette.baseColor1
|
||||
bottomLabelMessageLeftCmp.text: customPriorityFeeInput.displayHigherThanBaseFeeWarning?
|
||||
qsTr("Higher than max base fee: %1 GWEI").arg(customBaseFeeInput.text)
|
||||
: customPriorityFeeInput.displayHigherPriorityFeeWarning?
|
||||
qsTr("Higher than necessary (current %1 - %2)").arg(Utils.weiToGWei(root.currentSuggestedMinPriorityFee)).arg(Utils.weiToGWei(root.currentSuggestedMaxPriorityFee))
|
||||
: qsTr("Current: %1 - %2 GWEI").arg(Utils.weiToGWei(root.currentSuggestedMinPriorityFee)).arg(Utils.weiToGWei(root.currentSuggestedMaxPriorityFee))
|
||||
rightPadding: leftPadding
|
||||
input.rightComponent: StatusBaseText {
|
||||
text: "GWEI"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
validators: [
|
||||
StatusRegularExpressionValidator {
|
||||
regularExpression: Constants.regularExpressions.positiveRealNumbers
|
||||
errorMessage: Constants.errorMessages.positiveRealNumbers
|
||||
}
|
||||
]
|
||||
|
||||
onTextChanged: Qt.callLater(() => {
|
||||
if (!customPriorityFeeInput.valid) {
|
||||
return
|
||||
}
|
||||
d.recalculateCustomPriorityFeePrice()
|
||||
d.recalculateCustomPrice()
|
||||
})
|
||||
|
||||
onLabelIconClicked: d.showAlert(label,
|
||||
qsTr("AKA miner tip. A voluntary fee you can add to incentivise miners or validators to prioritise your transaction.\n\nThe higher the tip, the faster your transaction is likely to be processed, especially curing periods of higher network congestion.\n"),
|
||||
qsTr("Note: the ETH amount shown for this value is calculated: Priority fee (in GWEI) * Max gas amount"),
|
||||
"")
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
id: customGasAmountInput
|
||||
|
||||
readonly property bool displayTooLowAmountWarning: {
|
||||
if (!customGasAmountInput.text) {
|
||||
return false
|
||||
}
|
||||
const minValue = SQUtils.AmountsArithmetic.fromString(Constants.minGasForTx)
|
||||
const enteredValue = SQUtils.AmountsArithmetic.fromString(customGasAmountInput.text)
|
||||
return minValue.cmp(enteredValue) === 1
|
||||
}
|
||||
|
||||
readonly property bool displayTooHighAmountWarning: {
|
||||
if (!customGasAmountInput.text) {
|
||||
return false
|
||||
}
|
||||
const maxValue = SQUtils.AmountsArithmetic.fromString(Constants.maxGasForTx)
|
||||
const enteredValue = SQUtils.AmountsArithmetic.fromString(customGasAmountInput.text)
|
||||
return enteredValue.cmp(maxValue) === 1
|
||||
}
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
label: qsTr("Max gas amount")
|
||||
labelIcon: "info"
|
||||
labelIconColor: Theme.palette.baseColor1
|
||||
labelIconClickable: true
|
||||
bottomLabelMessageLeftCmp.color: customGasAmountInput.displayTooLowAmountWarning
|
||||
|| customGasAmountInput.displayTooHighAmountWarning?
|
||||
Theme.palette.dangerColor1
|
||||
: Theme.palette.baseColor1
|
||||
bottomLabelMessageLeftCmp.text: customGasAmountInput.displayTooLowAmountWarning?
|
||||
qsTr("Too low (should be between %1 and %2)").arg(Constants.minGasForTx).arg(Constants.maxGasForTx)
|
||||
: customGasAmountInput.displayTooHighAmountWarning?
|
||||
qsTr("Too high (should be between %1 and %2)").arg(Constants.minGasForTx).arg(Constants.maxGasForTx)
|
||||
: qsTr("Current: %1").arg(root.currentGasAmount)
|
||||
rightPadding: leftPadding
|
||||
input.rightComponent: StatusBaseText {
|
||||
text: "UNITS"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
validators: [
|
||||
StatusRegularExpressionValidator {
|
||||
regularExpression: Constants.regularExpressions.wholeNumbers
|
||||
errorMessage: Constants.errorMessages.wholeNumbers
|
||||
}
|
||||
]
|
||||
|
||||
onTextChanged: Qt.callLater(() => {
|
||||
if (!customGasAmountInput.valid) {
|
||||
return
|
||||
}
|
||||
d.recalculateCustomPrice()
|
||||
})
|
||||
|
||||
onLabelIconClicked: d.showAlert(qsTr("Gas amount"),
|
||||
qsTr("AKA gas limit. Refers to the maximum number of computational steps (or units of gas) that a transaction can consume. It represents the complexity or amount of work required to execute a transaction or smart contract.\n\nThe gas limit is a cap on how much work the transaction can do on the blockchain. If the gas limit is set too low, the transaction may fail due to insufficient gas."),
|
||||
"",
|
||||
"")
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
id: customNonceInput
|
||||
|
||||
readonly property bool displayHighNonceWarning: {
|
||||
if (!customNonceInput.text) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const expectedValue = parseInt(root.currentNonce)
|
||||
const enteredValue = parseInt(customNonceInput.text)
|
||||
return enteredValue > expectedValue
|
||||
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
label: qsTr("Nonce")
|
||||
labelIcon: "info"
|
||||
labelIconColor: Theme.palette.baseColor1
|
||||
labelIconClickable: true
|
||||
bottomLabelMessageLeftCmp.color: customNonceInput.displayHighNonceWarning?
|
||||
Theme.palette.miscColor6
|
||||
: Theme.palette.baseColor1
|
||||
bottomLabelMessageLeftCmp.text: {
|
||||
if (customNonceInput.displayHighNonceWarning) {
|
||||
return qsTr("Higher than suggested nonce of %1").arg(root.currentNonce)
|
||||
}
|
||||
let lastUsedNonce = root.currentNonce - 1
|
||||
if (lastUsedNonce < 0) {
|
||||
lastUsedNonce = "-"
|
||||
}
|
||||
return qsTr("Last transaction: %1").arg(lastUsedNonce)
|
||||
}
|
||||
rightPadding: leftPadding
|
||||
input.leftComponent: input.rightComponent // for the reference to the `input`
|
||||
|
||||
validators: [
|
||||
StatusRegularExpressionValidator {
|
||||
regularExpression: Constants.regularExpressions.wholeNumbers
|
||||
errorMessage: Constants.errorMessages.wholeNumbers
|
||||
}
|
||||
]
|
||||
|
||||
onLabelIconClicked: d.showAlert(label,
|
||||
qsTr("Transaction counter ensuring transactions from your account are processed in the correct order and can’t be replayed. Each new transaction increments the nonce by 1, ensuring uniqueness and preventing double-spending.\n\nIf a transaction with a lower nonce is pending, higher nonce transactions will remain in the queue until the earlier one is confirmed."),
|
||||
"",
|
||||
"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
Layout.preferredWidth: parent.width
|
||||
enabled: !d.customMode
|
||||
|| customBaseFeeInput.valid &&
|
||||
customPriorityFeeInput.valid &&
|
||||
customGasAmountInput.valid &&
|
||||
customNonceInput.valid &&
|
||||
!customGasAmountInput.displayTooHighAmountWarning &&
|
||||
!customGasAmountInput.displayTooLowAmountWarning
|
||||
text: qsTr("Confirm")
|
||||
onClicked: root.confirmClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,3 +8,4 @@ TokenSelectorSectionDelegate 1.0 TokenSelectorSectionDelegate.qml
|
||||
AccountContextMenu 1.0 AccountContextMenu.qml
|
||||
RecipientView 1.0 RecipientView.qml
|
||||
SendModalFooter 1.0 SendModalFooter.qml
|
||||
TransactionSettings 1.0 TransactionSettings.qml
|
@ -745,7 +745,7 @@ QtObject {
|
||||
readonly property var estimatedTimeAggregator: FunctionAggregator {
|
||||
model: !!handler.fetchedPathModel ?
|
||||
handler.fetchedPathModel: null
|
||||
initialValue: Constants.TransactionEstimatedTime.Unknown
|
||||
initialValue: -1
|
||||
roleName: "estimatedTime"
|
||||
|
||||
aggregateFunction: (aggr, value) => aggr < value? value : aggr
|
||||
|
@ -15,8 +15,11 @@ StatusDialog {
|
||||
id: root
|
||||
|
||||
property alias acceptBtnText: acceptBtn.text
|
||||
property alias acceptBtn: acceptBtn
|
||||
property alias cancelBtn: cancelBtn
|
||||
property alias alertText: contentTextItem.text
|
||||
property alias alertLabel: contentTextItem
|
||||
property alias alertNote: contentNoteItem
|
||||
property int acceptBtnType: StatusBaseButton.Type.Danger
|
||||
|
||||
property StatusAssetSettings asset: StatusAssetSettings {
|
||||
@ -36,12 +39,22 @@ StatusDialog {
|
||||
implicitWidth: 400 // by design
|
||||
topPadding: Theme.padding
|
||||
bottomPadding: topPadding
|
||||
contentItem: StatusBaseText {
|
||||
id: contentTextItem
|
||||
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
wrapMode: Text.WordWrap
|
||||
lineHeight: 1.2
|
||||
contentItem: Column {
|
||||
StatusBaseText {
|
||||
id: contentTextItem
|
||||
width: parent.width
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
wrapMode: Text.WordWrap
|
||||
lineHeight: 1.2
|
||||
}
|
||||
StatusBaseText {
|
||||
id: contentNoteItem
|
||||
visible: false
|
||||
width: parent.width
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
wrapMode: Text.WordWrap
|
||||
lineHeight: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
header: StatusDialogHeader {
|
||||
@ -61,6 +74,7 @@ StatusDialog {
|
||||
rightButtons: ObjectModel {
|
||||
|
||||
StatusButton {
|
||||
id: cancelBtn
|
||||
text: qsTr("Cancel")
|
||||
normalColor: "transparent"
|
||||
|
||||
@ -75,6 +89,8 @@ StatusDialog {
|
||||
|
||||
type: root.acceptBtnType
|
||||
|
||||
Component.onCompleted: acceptBtn.forceActiveFocus()
|
||||
|
||||
onClicked: {
|
||||
root.acceptClicked()
|
||||
close()
|
||||
|
@ -664,6 +664,8 @@ QtObject {
|
||||
readonly property var numerical: /^$|^[0-9]+$/
|
||||
readonly property var emoji: /\ud83c\udff4(\udb40[\udc61-\udc7a])+\udb40\udc7f|(\ud83c[\udde6-\uddff]){2}|([\#\*0-9]\ufe0f?\u20e3)|(\u00a9|\u00ae|[\u203c\u2049\u20e3\u2122\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23e9-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u261d\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u265f\u2660\u2663\u2665\u2666\u2668\u267b\u267e\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26ce\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299]|\ud83c[\udc04\udccf\udd70\udd71\udd7e\udd7f\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude02\ude1a\ude2f\ude32-\ude3a\ude50\ude51\udf00-\udf21\udf24-\udf93\udf96\udf97\udf99-\udf9b\udf9e-\udff0\udff3-\udff5\udff7-\udfff]|\ud83d[\udc00-\udcfd\udcff-\udd3d\udd49-\udd4e\udd50-\udd67\udd6f\udd70\udd73-\udd7a\udd87\udd8a-\udd8d\udd90\udd95\udd96\udda4\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa-\ude4f\ude80-\udec5\udecb-\uded2\uded5-\uded7\udedc-\udee5\udee9\udeeb\udeec\udef0\udef3-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0c-\udd3a\udd3c-\udd45\udd47-\ude7c\ude80-\ude88\ude90-\udebd\udebf-\udec5\udece-\udedb\udee0-\udee8\udef0-\udef8])((\ud83c[\udffb-\udfff])?(\ud83e[\uddb0-\uddb3])?(\ufe0f?\u200d([\u2000-\u3300]|[\ud83c-\ud83e][\ud000-\udfff])\ufe0f?)?)*/g;
|
||||
readonly property var asciiWithEmoji: /^[\u00a9\u00ae\u2000-\u3300\ud83c\ud000-\udfff\ud83d\ud000-\udfff\ud83e\ud000-\udfff\u0000-\u007F]+$/
|
||||
readonly property var wholeNumbers: /^(0|[1-9][0-9]*)$/
|
||||
readonly property var positiveRealNumbers: /^(0|[1-9][0-9]*)([.,][0-9]+)?$/
|
||||
}
|
||||
|
||||
readonly property QtObject errorMessages: QtObject {
|
||||
@ -673,6 +675,8 @@ QtObject {
|
||||
readonly property string alphanumericalWithSpaceRegExp: qsTr("Special characters are not allowed")
|
||||
readonly property string asciiRegExp: qsTr("Only letters, numbers and ASCII characters allowed")
|
||||
readonly property string emojRegExp: qsTr("Name is too cool (use A-Z and 0-9, single whitespace, hyphens and underscores only)")
|
||||
readonly property var wholeNumbers: qsTr("Whole numbers only")
|
||||
readonly property var positiveRealNumbers: qsTr("Positive real numbers only")
|
||||
}
|
||||
|
||||
readonly property QtObject socialLinkType: QtObject {
|
||||
@ -892,8 +896,12 @@ QtObject {
|
||||
readonly property string networkRopsten: "Ropsten"
|
||||
|
||||
readonly property string ethToken: "ETH"
|
||||
readonly property int ethTokenDecimals: 18
|
||||
readonly property int gweiExponent: 9
|
||||
|
||||
readonly property int ethTokenWeiDecimals: 18
|
||||
readonly property int ethTokenGWeiDecimals: 9
|
||||
|
||||
readonly property string minGasForTx: "21000"
|
||||
readonly property string maxGasForTx: "30000000"
|
||||
|
||||
readonly property QtObject networkShortChainNames: QtObject {
|
||||
readonly property string mainnet: "eth"
|
||||
|
@ -980,4 +980,24 @@ QtObject {
|
||||
|
||||
return typeName
|
||||
}
|
||||
|
||||
function weiToEth(value) {
|
||||
return StatusQUtils.AmountsArithmetic.div(StatusQUtils.AmountsArithmetic.fromString(value),
|
||||
StatusQUtils.AmountsArithmetic.fromNumber(1, Constants.ethTokenWeiDecimals))
|
||||
}
|
||||
|
||||
function ethToWei(value) {
|
||||
return StatusQUtils.AmountsArithmetic.times(StatusQUtils.AmountsArithmetic.fromString(value),
|
||||
StatusQUtils.AmountsArithmetic.fromNumber(1, Constants.ethTokenWeiDecimals))
|
||||
}
|
||||
|
||||
function weiToGWei(value) {
|
||||
return StatusQUtils.AmountsArithmetic.div(StatusQUtils.AmountsArithmetic.fromString(value),
|
||||
StatusQUtils.AmountsArithmetic.fromNumber(1, Constants.ethTokenGWeiDecimals))
|
||||
}
|
||||
|
||||
function gweiToWei(value) {
|
||||
return StatusQUtils.AmountsArithmetic.times(StatusQUtils.AmountsArithmetic.fromString(value),
|
||||
StatusQUtils.AmountsArithmetic.fromNumber(1, Constants.ethTokenGWeiDecimals))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user