From 2df35ff0c9e4e2536eda9c2f7536803dfbd61fe9 Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Thu, 4 Jul 2024 00:08:03 +0200 Subject: [PATCH] feat(@desktop/wallet): Implements auto refresh fixes #14952 --- storybook/qmlTests/tests/tst_SwapModal.qml | 36 +++++++++++++------ .../StatusQ/Core/Utils/AmountsArithmetic.qml | 11 +++--- .../Wallet/panels/SwapInputPanel.qml | 4 +++ .../popups/swap/SwapInputParamsForm.qml | 12 +++---- .../Wallet/popups/swap/SwapModal.qml | 14 +++++++- .../Wallet/popups/swap/SwapModalAdaptor.qml | 9 +++-- .../Wallet/popups/swap/SwapOutputData.qml | 27 +++++++------- .../popups/swap/SwapSignApprovePopup.qml | 1 - 8 files changed, 74 insertions(+), 40 deletions(-) diff --git a/storybook/qmlTests/tests/tst_SwapModal.qml b/storybook/qmlTests/tests/tst_SwapModal.qml index 1b68890b36..e5e8a8624e 100644 --- a/storybook/qmlTests/tests/tst_SwapModal.qml +++ b/storybook/qmlTests/tests/tst_SwapModal.qml @@ -150,11 +150,7 @@ Item { // verify loading state was set and no errors currently verify(!root.swapAdaptor.validSwapProposalReceived) verify(root.swapAdaptor.swapProposalLoading) - compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "") - compare(root.swapAdaptor.swapOutputData.toTokenAmount, "") - compare(root.swapAdaptor.swapOutputData.totalFees, 0) - compare(root.swapAdaptor.swapOutputData.bestRoutes, []) - compare(root.swapAdaptor.swapOutputData.approvalNeeded, false) + compare(root.swapAdaptor.swapOutputData.rawPaths, []) compare(root.swapAdaptor.swapOutputData.hasError, false) // verfy input and output panels @@ -589,7 +585,6 @@ Item { compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "") compare(root.swapAdaptor.swapOutputData.toTokenAmount, "") compare(root.swapAdaptor.swapOutputData.totalFees, 0) - compare(root.swapAdaptor.swapOutputData.bestRoutes, []) compare(root.swapAdaptor.swapOutputData.approvalNeeded, false) compare(root.swapAdaptor.swapOutputData.hasError, true) verify(errorTag.visible) @@ -638,7 +633,6 @@ Item { let totalFees = root.swapAdaptor.currencyStore.getFiatValue(gasTimeEstimate.totalFeesInEth, Constants.ethToken) + totalTokenFeesInFiat compare(root.swapAdaptor.swapOutputData.totalFees, totalFees) - compare(root.swapAdaptor.swapOutputData.bestRoutes, txRoutes.suggestedRoutes) compare(root.swapAdaptor.swapOutputData.approvalNeeded, false) compare(root.swapAdaptor.swapOutputData.hasError, false) verify(!errorTag.visible) @@ -686,7 +680,6 @@ Item { totalFees = root.swapAdaptor.currencyStore.getFiatValue(gasTimeEstimate.totalFeesInEth, Constants.ethToken) + totalTokenFeesInFiat compare(root.swapAdaptor.swapOutputData.totalFees, totalFees) - compare(root.swapAdaptor.swapOutputData.bestRoutes, txRoutes2.suggestedRoutes) compare(root.swapAdaptor.swapOutputData.approvalNeeded, true) compare(root.swapAdaptor.swapOutputData.hasError, false) verify(!errorTag.visible) @@ -1333,7 +1326,6 @@ Item { SQUtils.AmountsArithmetic.fromString(txRoutes.amountToReceive), SQUtils.AmountsArithmetic.fromNumber(1, root.swapAdaptor.toToken.decimals)).toString()) compare(root.swapAdaptor.swapOutputData.totalFees, totalFees) - compare(root.swapAdaptor.swapOutputData.bestRoutes, txRoutes.suggestedRoutes) compare(root.swapAdaptor.swapOutputData.hasError, false) compare(root.swapAdaptor.swapOutputData.estimatedTime, bestPath.estimatedTime) compare(root.swapAdaptor.swapOutputData.txProviderName, bestPath.bridgeName) @@ -1414,7 +1406,7 @@ Item { let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval txHasRouteNoApproval.uuid = root.swapAdaptor.uuid - root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txHasRouteNoApproval) + root.swapStore.suggestedRoutesReady(txHasRouteNoApproval) verify(!root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) @@ -1571,5 +1563,29 @@ Item { closeAndVerfyModal() } + + function test_auto_refresh() { + // Asset chosen but no pay value set state ------------------------------------------------------------------------------- + root.swapFormData.fromTokenAmount = "0.0001" + root.swapFormData.selectedAccountAddress = "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" + root.swapFormData.selectedNetworkChainId = 11155111 + root.swapFormData.fromTokensKey = "ETH" + // for testing making it 1.5 seconds so as to not make tests running too long + root.swapFormData.autoRefreshTime = 1500 + + // Launch popup + launchAndVerfyModal() + + // check if fetchSuggestedRoutes called + fetchSuggestedRoutesCalled.wait() + + // emit routes ready + let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval + txHasRouteNoApproval.uuid = root.swapAdaptor.uuid + root.swapStore.suggestedRoutesReady(txHasRouteNoApproval) + + // check if fetch occurs automatically after 15 seconds + fetchSuggestedRoutesCalled.wait() + } } } diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml b/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml index 84e0af74e1..2d555af74b 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/AmountsArithmetic.qml @@ -87,13 +87,16 @@ QtObject { The obtained amount can be multiplied or compared. Provided number is assumed to be an amount in basic units, an integer. + + returns NaN in case the conversion fails. */ function fromString(numStr) { console.assert(typeof numStr === "string") - const amount = new Big.Big(numStr) - // TODO: restore assert when permissions handled as bigints - //console.assert(amount.eq(amount.round())) - return amount + try { + return new Big.Big(numStr) + } catch(e) { + return NaN + } } /*! diff --git a/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml b/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml index aba7cae1a4..d2043f6b91 100644 --- a/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml @@ -63,6 +63,10 @@ Control { property int swapExchangeButtonWidth: 44 property string caption: swapSide === SwapInputPanel.SwapSide.Pay ? qsTr("Pay") : qsTr("Receive") + function forceActiveFocus() { + amountToSendInput.input.forceActiveFocus() + } + enum SwapSide { Pay = 0, Receive = 1 diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapInputParamsForm.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapInputParamsForm.qml index bccc327ebf..07f232535a 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapInputParamsForm.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapInputParamsForm.qml @@ -19,6 +19,8 @@ QtObject { // default token key property string defaultToTokenKey: "" + // 15 seconds + property int autoRefreshTime: 15000 onSelectedAccountAddressChanged: root.formValuesChanged() onSelectedNetworkChainIdChanged: root.formValuesChanged() @@ -50,15 +52,13 @@ QtObject { } function isFormFilledCorrectly() { + let bigIntNumber = SQUtils.AmountsArithmetic.fromString(root.fromTokenAmount) return !!root.selectedAccountAddress && root.selectedNetworkChainId !== -1 && !!root.fromTokensKey && !!root.toTokenKey && - ((!!root.fromTokenAmount && - !isNaN(SQUtils.AmountsArithmetic.fromString(root.fromTokenAmount)) && - SQUtils.AmountsArithmetic.fromString(root.fromTokenAmount) > 0) || - (!!root.toTokenAmount && - !isNaN(SQUtils.AmountsArithmetic.fromString(root.toTokenAmount)) && - SQUtils.AmountsArithmetic.fromString(root.toTokenAmount) > 0 )) && + (!!root.fromTokenAmount && + !isNaN(bigIntNumber) && + SQUtils.AmountsArithmetic.cmp(bigIntNumber, 0) === 1) && root.selectedSlippage > 0 } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index debae23fb5..263691708d 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -36,13 +36,20 @@ StatusDialog { root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue) }) + property Timer autoRefreshTimer: Timer { + interval: root.swapInputParamsForm.autoRefreshTime + running: false + repeat: false + onTriggered: d.fetchSuggestedRoutes() + } + function fetchSuggestedRoutes() { if (payPanel.valueValid && root.swapInputParamsForm.isFormFilledCorrectly()) { root.swapAdaptor.validSwapProposalReceived = false root.swapAdaptor.swapProposalLoading = true root.swapAdaptor.approvalPending = false root.swapAdaptor.approvalSuccessful = false - root.swapAdaptor.swapOutputData.resetAllButReceivedTokenValuesForSwap() + root.swapAdaptor.swapOutputData.resetPathInfoAndError() debounceFetchSuggestedRoutes() } } @@ -71,6 +78,10 @@ StatusDialog { d.fetchSuggestedRoutes() } } + function onSuggestedRoutesReady() { + if(!root.swapAdaptor.swapProposalLoading) + d.autoRefreshTimer.restart() + } } Behavior on implicitHeight { @@ -257,6 +268,7 @@ StatusDialog { root.swapInputParamsForm.fromTokenAmount = !!root.swapAdaptor.swapOutputData.toTokenAmount ? root.swapAdaptor.swapOutputData.toTokenAmount : root.swapInputParamsForm.toTokenAmount root.swapInputParamsForm.toTokenKey = tempPayToken root.swapInputParamsForm.toTokenAmount = tempPayAmount + payPanel.forceActiveFocus() } } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml index 36228f2bd6..ba949eeac8 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml @@ -76,6 +76,8 @@ QObject { filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled } } + signal suggestedRoutesReady() + QtObject { id: d @@ -163,7 +165,6 @@ QObject { // if valid route was found if(txRoutes.suggestedRoutes.count === 1) { root.validSwapProposalReceived = true - root.swapOutputData.bestRoutes = txRoutes.suggestedRoutes root.swapOutputData.toTokenAmount = AmountsArithmetic.div(AmountsArithmetic.fromString(txRoutes.amountToReceive), AmountsArithmetic.fromNumber(1, root.toToken.decimals)).toString() let gasTimeEstimate = txRoutes.gasTimeEstimate @@ -171,7 +172,7 @@ QObject { if (!!root.fromToken && !!root.fromToken.marketDetails && !!root.fromToken.marketDetails.currencyPrice) totalTokenFeesInFiat = gasTimeEstimate.totalTokenFees * root.fromToken.marketDetails.currencyPrice.amount root.swapOutputData.totalFees = root.currencyStore.getFiatValue(gasTimeEstimate.totalFeesInEth, Constants.ethToken) + totalTokenFeesInFiat - let bestPath = ModelUtils.get(root.swapOutputData.bestRoutes, 0, "route") + let bestPath = ModelUtils.get(txRoutes.suggestedRoutes, 0, "route") root.swapOutputData.approvalNeeded = !!bestPath ? bestPath.approvalRequired: false root.swapOutputData.approvalGasFees = !!bestPath ? bestPath.approvalGasFees.toString() : "" root.swapOutputData.approvalAmountRequired = !!bestPath ? bestPath.approvalAmountRequired: "" @@ -182,6 +183,7 @@ QObject { else { root.swapOutputData.hasError = true } + root.suggestedRoutesReady() } function onTransactionSent(chainId, txHash, uuid, error) { @@ -247,12 +249,12 @@ QObject { } function fetchSuggestedRoutes(cryptoValueInRaw) { + root.swapFormData.toTokenAmount = "" if (root.swapFormData.isFormFilledCorrectly() && !!cryptoValueInRaw) { // Identify new swap with a different uuid d.uuid = Utils.uuid() root.swapProposalLoading = true - root.swapOutputData.reset() let account = selectedAccountEntry.item let accountAddress = account.address @@ -263,6 +265,7 @@ QObject { disabledChainIds, disabledChainIds, Constants.SendType.Swap, "") } else { root.swapProposalLoading = false + root.swapOutputData.reset() } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml index 01f64f6403..f02d6c8c6a 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml @@ -11,7 +11,6 @@ QtObject { property string toTokenAmount: "" // TODO: this should be string but backend gas_estimate_item.nim passes this as float property real totalFees: 0 - property var bestRoutes: [] property bool hasError property var rawPaths: [] // need to check how this is done in new router v2, right now it is Enum type @@ -22,24 +21,22 @@ QtObject { property string approvalAmountRequired property string approvalContractAddress + function resetPathInfoAndError() { + root.hasError = false + root.rawPaths = [] + } + function reset() { root.fromTokenAmount = "" root.toTokenAmount = "" - root.resetAllButReceivedTokenValuesForSwap() - } - - function resetAllButReceivedTokenValuesForSwap() { - root.totalFees = 0 - root.bestRoutes = [] - root.approvalNeeded = false - root.hasError = false - root.rawPaths = [] + root.txProviderName = "" root.estimatedTime = Constants.TransactionEstimatedTime.Unknown - txProviderName = "" - approvalNeeded = false - approvalGasFees = "" - approvalAmountRequired = "" - approvalContractAddress = "" + root.totalFees = 0 + root.approvalNeeded = false + root.approvalGasFees = "" + root.approvalAmountRequired = "" + root.approvalContractAddress = "" + resetPathInfoAndError() } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml index 5fd371347b..a4c03d258a 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml @@ -351,7 +351,6 @@ StatusDialog { Layout.maximumWidth: 60 titleText: qsTr("Max slippage:") infoText: "%1%".arg(LocaleUtils.numberToLocaleString(root.swapSignApproveInputForm.selectedSlippage)) - loading: root.loading visible: !d.isApproveTx } }