From 4deea3461f57e58674887ae4cc0a3f1df170cc19 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 9 Jul 2024 20:49:00 +0300 Subject: [PATCH] feat(dapps): show estimated time for dApps requests Implemented using the fees from the transaction data sent by the dApp. Also fixed the nim status go wrapper to send proper formatted string as expected on the other side. Updates: #15192 --- .../modules/main/wallet_section/module.nim | 2 +- .../wallet_connect/controller.nim | 3 ++ .../service/transaction/service.nim | 2 +- .../service/wallet_connect/service.nim | 9 ++++ src/backend/backend.nim | 2 +- storybook/pages/DAppsWorkflowPage.qml | 6 ++- .../Wallet/panels/DAppsWorkflow.qml | 14 +++--- .../services/dapps/DAppsRequestHandler.qml | 43 ++++++++++++++++--- .../services/dapps/types/SessionRequest.qml | 1 + .../dapps/types/SessionRequestResolved.qml | 1 - .../panels/EstimatedTimeDisplay.qml | 23 ---------- .../shared/popups/walletconnect/panels/qmldir | 1 - ui/imports/shared/stores/DAppsStore.qml | 6 +++ ui/imports/utils/Constants.qml | 1 + 14 files changed, 73 insertions(+), 41 deletions(-) delete mode 100644 ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index f0b0cafc1c..9e9b959514 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -168,7 +168,7 @@ proc newModule*( result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events) result.filter = initFilter(result.controller) - result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService) + result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService, transactionService) result.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService) result.view = newView(result, result.activityController, result.tmpActivityControllers, result.activityDetailsController, result.collectibleDetailsController, result.walletConnectController) diff --git a/src/app/modules/shared_modules/wallet_connect/controller.nim b/src/app/modules/shared_modules/wallet_connect/controller.nim index 578b3d6121..ee76af38c8 100644 --- a/src/app/modules/shared_modules/wallet_connect/controller.nim +++ b/src/app/modules/shared_modules/wallet_connect/controller.nim @@ -70,3 +70,6 @@ QtObject: proc sendTransaction*(self: Controller, address: string, chainId: int, password: string, txJson: string): string {.slot.} = return self.service.sendTransaction(address, chainId, password, txJson) + + proc getEstimatedTimeMinutesInterval(self: Controller, chainId: int, maxFeePerGas: string): int {.slot.} = + return self.service.getEstimatedTimeMinutesInterval(chainId, maxFeePerGas).int \ No newline at end of file diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 56c23bfce9..bd75b140f3 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -632,7 +632,7 @@ QtObject: proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGas: string): EstimatedTime = try: - let response = backend.getTransactionEstimatedTime(chainId, maxFeePerGas.parseFloat).result.getInt + let response = backend.getTransactionEstimatedTime(chainId, maxFeePerGas).result.getInt return EstimatedTime(response) except Exception as e: error "Error estimating transaction time", message = e.msg diff --git a/src/app_service/service/wallet_connect/service.nim b/src/app_service/service/wallet_connect/service.nim index ae3cadef73..b9efdb67b4 100644 --- a/src/app_service/service/wallet_connect/service.nim +++ b/src/app_service/service/wallet_connect/service.nim @@ -1,4 +1,5 @@ import NimQml, chronicles, times, json +import strutils import backend/wallet_connect as status_go import backend/wallet @@ -6,6 +7,7 @@ import backend/wallet import app_service/service/settings/service as settings_service import app_service/common/wallet_constants from app_service/service/transaction/dto import PendingTransactionTypeDto +import app_service/service/transaction/service as tr import app/global/global_singleton @@ -30,6 +32,7 @@ QtObject: events: EventEmitter threadpool: ThreadPool settingsService: settings_service.Service + transactions: tr.Service authenticationCallback: AuthenticationResponseFn @@ -40,6 +43,7 @@ QtObject: events: EventEmitter, threadpool: ThreadPool, settingsService: settings_service.Service, + transactions: tr.Service, ): Service = new(result, delete) result.QObject.setup @@ -47,6 +51,7 @@ QtObject: result.events = events result.threadpool = threadpool result.settingsService = settings_service + result.transactions = transactions proc init*(self: Service) = self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args): @@ -202,3 +207,7 @@ QtObject: return "" return txResponse.getStr + + proc getEstimatedTimeMinutesInterval*(self: Service, chainId: int, maxFeePerGas: string): EstimatedTime = + let maxFeePerGasInt = parseHexInt(maxFeePerGas) + return self.transactions.getEstimatedTime(chainId, $(maxFeePerGasInt.float)) diff --git a/src/backend/backend.nim b/src/backend/backend.nim index ee30f546b4..f5ecf62695 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -156,7 +156,7 @@ rpc(startWallet, "wallet"): rpc(getTransactionEstimatedTime, "wallet"): chainId: int - maxFeePerGas: float + maxFeePerGas: string rpc(fetchPrices, "wallet"): symbols: seq[string] diff --git a/storybook/pages/DAppsWorkflowPage.qml b/storybook/pages/DAppsWorkflowPage.qml index 572aadf50c..79705e9ec9 100644 --- a/storybook/pages/DAppsWorkflowPage.qml +++ b/storybook/pages/DAppsWorkflowPage.qml @@ -20,6 +20,7 @@ import Storybook 1.0 import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.services.dapps 1.0 +import AppLayouts.Wallet.services.dapps.types 1.0 import SortFilterProxyModel 0.2 @@ -331,7 +332,6 @@ Item { return true } - function getDapps() { let dappsJson = JSON.stringify(d.persistedDapps) this.dappsListReceived(dappsJson) @@ -373,6 +373,10 @@ Item { console.info(`calling mocked DAppsStore.sendTransaction(${topic}, ${id}, ${address}, ${chainId}, ${password}, ${tx})`) return "0xf8672a8402fb7acf82520894e2d622c817878da5143bbe068" } + + function getEstimatedTime(chainId, maxFeePerGas) { + return Constants.TransactionEstimatedTime.LessThanThreeMins + } } walletRootStore: QObject { diff --git a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml index dd2c6d1201..0708def787 100644 --- a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml +++ b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml @@ -5,12 +5,13 @@ import QtQuick.Layouts 1.15 import StatusQ 0.1 import SortFilterProxyModel 0.2 +import AppLayouts.Wallet 1.0 import AppLayouts.Wallet.controls 1.0 - -import shared.popups.walletconnect 1.0 import AppLayouts.Wallet.services.dapps 1.0 import AppLayouts.Wallet.services.dapps.types 1.0 +import shared.popups.walletconnect 1.0 + import utils 1.0 DappsComboBox { @@ -119,6 +120,7 @@ DappsComboBox { property SessionRequestResolved request: null sourceComponent: DAppSignRequestModal { + id: dappRequestModal objectName: "dappsRequestModal" loginType: request.account.migragedToKeycard ? Constants.LoginType.Keycard : root.loginType visible: true @@ -138,7 +140,7 @@ DappsComboBox { currentCurrency: "" fiatFees: request.maxFeesText cryptoFees: request.maxFeesEthText - estimatedTime: request.estimatedTimeText + estimatedTime: "" feesLoading: !request.maxFeesText || !request.maxFeesEthText hasFees: signingTransaction enoughFundsForTransaction: request.enoughFunds @@ -199,14 +201,16 @@ DappsComboBox { } catch (e) { // ignore error in case of tests and storybook where we don't have access to globalUtils } + cryptoFees = ethStr enoughFundsForTransaction = haveEnoughFunds enoughFundsForFees = haveEnoughFunds feesLoading = false hasFees = !!maxFees } - function onEstimatedTimeUpdated(minMinutes, maxMinutes) { - estimatedTime = qsTr("%1-%2mins").arg(minMinutes).arg(maxMinutes) + + function onEstimatedTimeUpdated(estimatedTimeEnum) { + dappRequestModal.estimatedTime = WalletUtils.getLabelForEstimatedTxTime(estimatedTimeEnum) } } } diff --git a/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml b/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml index f865c38a2c..d44ba74c09 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml @@ -34,7 +34,8 @@ QObject { signal displayToastMessage(string message, bool error) signal sessionRequestResult(/*model entry of SessionRequestResolved*/ var request, bool isSuccess) signal maxFeesUpdated(real maxFees, int maxFeesWei, bool haveEnoughFunds, string symbol) - signal estimatedTimeUpdated(int minMinutes, int maxMinutes) + // Reports Constants.TransactionEstimatedTime values + signal estimatedTimeUpdated(int estimatedTimeEnum) Connections { target: sdk @@ -138,7 +139,6 @@ QObject { maxFeesText: "?", maxFeesEthText: "?", enoughFunds: false, - estimatedTimeText: "?" }) if (obj === null) { console.error("Error creating SessionRequestResolved for event") @@ -158,20 +158,21 @@ QObject { } obj.resolveDappInfoFromSession(session) root.sessionRequest(obj) + + let estimatedTimeEnum = getEstimatedTimeInterval(data, method, obj.network.chainId) + root.estimatedTimeUpdated(estimatedTimeEnum) + // TODO #15192: update maxFees if (!event.params.request.params[0].gasLimit || !event.params.request.params[0].gasPrice) { root.maxFeesUpdated(0, 0, true, "") - root.estimatedTimeUpdated(0, 0) return } - - + let gasLimit = parseFloat(parseInt(event.params.request.params[0].gasLimit, 16)); let gasPrice = parseFloat(parseInt(event.params.request.params[0].gasPrice, 16)); let maxFees = gasLimit * gasPrice root.maxFeesUpdated(maxFees/1000000000, maxFees, true, "Gwei") - // TODO #15192: update estimatedTime - root.estimatedTimeUpdated(1, 12) + }) return obj @@ -320,6 +321,34 @@ QObject { console.error("No password or pin provided to sign message") } } + + // Returns Constants.TransactionEstimatedTime + function getEstimatedTimeInterval(data, method, chainId) { + if (method != SessionRequest.methods.signTransaction.name + && method != SessionRequest.methods.sendTransaction.name) + { + return "" + } + + var tx = {} + if (method === SessionRequest.methods.signTransaction.name) { + tx = SessionRequest.methods.signTransaction.getTxObjFromData(data) + } else if (method === SessionRequest.methods.sendTransaction.name) { + tx = SessionRequest.methods.sendTransaction.getTxObjFromData(data) + } + + var maxFeePerGas = "" + if (!!tx.maxFeePerGas) { + maxFeePerGas = tx.maxFeePerGas + } else if (!!tx.gasPrice) { + maxFeePerGas = tx.gasPrice + } + if (!maxFeePerGas) { + return "" + } + + return root.store.getEstimatedTime(chainId, maxFeePerGas) + } } /// The queue is used to ensure that the events are processed in the order they are received but they could be diff --git a/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml b/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml index 80851508c8..7f25711803 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml @@ -5,6 +5,7 @@ import QtQml 2.15 import utils 1.0 QtObject { + /// Supported methods /// userString is used in the context `dapp.url #{userString} ` /// requestDisplay is used in the context `dApp wants you to ${requestDisplay} with ` diff --git a/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequestResolved.qml b/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequestResolved.qml index 03af1cee7e..d7ebecf1cc 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequestResolved.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequestResolved.qml @@ -31,7 +31,6 @@ QObject { readonly property string maxFeesText: "" readonly property string maxFeesEthText: "" readonly property bool enoughFunds: false - readonly property string estimatedTimeText: "" function resolveDappInfoFromSession(session) { let meta = session.peer.metadata diff --git a/ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml b/ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml deleted file mode 100644 index d291e91bdc..0000000000 --- a/ui/imports/shared/popups/walletconnect/panels/EstimatedTimeDisplay.qml +++ /dev/null @@ -1,23 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Layouts 1.15 - -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 - -ColumnLayout { - id: root - - property alias estimatedTimeText: contentText.text - - StatusBaseText { - text: qsTr("Est. time:") - font.pixelSize: 12 - color: Theme.palette.directColor1 - } - StatusBaseText { - id: contentText - - font.pixelSize: 16 - font.weight: Font.DemiBold - } -} diff --git a/ui/imports/shared/popups/walletconnect/panels/qmldir b/ui/imports/shared/popups/walletconnect/panels/qmldir index 24aa1916fb..16db2d9470 100644 --- a/ui/imports/shared/popups/walletconnect/panels/qmldir +++ b/ui/imports/shared/popups/walletconnect/panels/qmldir @@ -1,4 +1,3 @@ MaxFeesDisplay 1.0 MaxFeesDisplay.qml -EstimatedTimeDisplay 1.0 EstimatedTimeDisplay.qml IntentionPanel 1.0 IntentionPanel.qml ContentPanel 1.0 ContentPanel.qml diff --git a/ui/imports/shared/stores/DAppsStore.qml b/ui/imports/shared/stores/DAppsStore.qml index 236b5c8f22..cc0770b23d 100644 --- a/ui/imports/shared/stores/DAppsStore.qml +++ b/ui/imports/shared/stores/DAppsStore.qml @@ -63,6 +63,12 @@ QObject { if (txObj.value) { tx.value = stripLeadingZeros(txObj.value) } return tx } + + // Returns ui/imports/utils -> Constants.TransactionEstimatedTime values + function getEstimatedTime(chainId, maxFeePerGas) { + return controller.getEstimatedTime(chainId, maxFeePerGas) + } + // Returns the hex encoded signature of the transaction or empty string if error function signTransaction(topic, id, address, chainId, password, txObj) { let tx = prepareTxForStatusGo(txObj) diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 63bc720484..a8f7b3ba7d 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -1333,6 +1333,7 @@ QtObject { readonly property string paraswapUrl: "app.paraswap.io" } + // Mirrors src/app_service/service/transaction/service.nim -> EstimatedTime enum TransactionEstimatedTime { Unknown = 0, LessThanOneMin,