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
This commit is contained in:
Stefan 2024-07-09 20:49:00 +03:00 committed by Stefan Dunca
parent be1c6ba2ad
commit 4deea3461f
14 changed files with 73 additions and 41 deletions

View File

@ -168,7 +168,7 @@ proc newModule*(
result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events) result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events)
result.filter = initFilter(result.controller) 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.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService)
result.view = newView(result, result.activityController, result.tmpActivityControllers, result.activityDetailsController, result.collectibleDetailsController, result.walletConnectController) result.view = newView(result, result.activityController, result.tmpActivityControllers, result.activityDetailsController, result.collectibleDetailsController, result.walletConnectController)

View File

@ -70,3 +70,6 @@ QtObject:
proc sendTransaction*(self: Controller, address: string, chainId: int, password: string, txJson: string): string {.slot.} = proc sendTransaction*(self: Controller, address: string, chainId: int, password: string, txJson: string): string {.slot.} =
return self.service.sendTransaction(address, chainId, password, txJson) 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

View File

@ -632,7 +632,7 @@ QtObject:
proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGas: string): EstimatedTime = proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGas: string): EstimatedTime =
try: try:
let response = backend.getTransactionEstimatedTime(chainId, maxFeePerGas.parseFloat).result.getInt let response = backend.getTransactionEstimatedTime(chainId, maxFeePerGas).result.getInt
return EstimatedTime(response) return EstimatedTime(response)
except Exception as e: except Exception as e:
error "Error estimating transaction time", message = e.msg error "Error estimating transaction time", message = e.msg

View File

@ -1,4 +1,5 @@
import NimQml, chronicles, times, json import NimQml, chronicles, times, json
import strutils
import backend/wallet_connect as status_go import backend/wallet_connect as status_go
import backend/wallet import backend/wallet
@ -6,6 +7,7 @@ import backend/wallet
import app_service/service/settings/service as settings_service import app_service/service/settings/service as settings_service
import app_service/common/wallet_constants import app_service/common/wallet_constants
from app_service/service/transaction/dto import PendingTransactionTypeDto from app_service/service/transaction/dto import PendingTransactionTypeDto
import app_service/service/transaction/service as tr
import app/global/global_singleton import app/global/global_singleton
@ -30,6 +32,7 @@ QtObject:
events: EventEmitter events: EventEmitter
threadpool: ThreadPool threadpool: ThreadPool
settingsService: settings_service.Service settingsService: settings_service.Service
transactions: tr.Service
authenticationCallback: AuthenticationResponseFn authenticationCallback: AuthenticationResponseFn
@ -40,6 +43,7 @@ QtObject:
events: EventEmitter, events: EventEmitter,
threadpool: ThreadPool, threadpool: ThreadPool,
settingsService: settings_service.Service, settingsService: settings_service.Service,
transactions: tr.Service,
): Service = ): Service =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
@ -47,6 +51,7 @@ QtObject:
result.events = events result.events = events
result.threadpool = threadpool result.threadpool = threadpool
result.settingsService = settings_service result.settingsService = settings_service
result.transactions = transactions
proc init*(self: Service) = proc init*(self: Service) =
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args): self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
@ -202,3 +207,7 @@ QtObject:
return "" return ""
return txResponse.getStr return txResponse.getStr
proc getEstimatedTimeMinutesInterval*(self: Service, chainId: int, maxFeePerGas: string): EstimatedTime =
let maxFeePerGasInt = parseHexInt(maxFeePerGas)
return self.transactions.getEstimatedTime(chainId, $(maxFeePerGasInt.float))

View File

@ -156,7 +156,7 @@ rpc(startWallet, "wallet"):
rpc(getTransactionEstimatedTime, "wallet"): rpc(getTransactionEstimatedTime, "wallet"):
chainId: int chainId: int
maxFeePerGas: float maxFeePerGas: string
rpc(fetchPrices, "wallet"): rpc(fetchPrices, "wallet"):
symbols: seq[string] symbols: seq[string]

View File

@ -20,6 +20,7 @@ import Storybook 1.0
import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.services.dapps 1.0 import AppLayouts.Wallet.services.dapps 1.0
import AppLayouts.Wallet.services.dapps.types 1.0
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
@ -331,7 +332,6 @@ Item {
return true return true
} }
function getDapps() { function getDapps() {
let dappsJson = JSON.stringify(d.persistedDapps) let dappsJson = JSON.stringify(d.persistedDapps)
this.dappsListReceived(dappsJson) this.dappsListReceived(dappsJson)
@ -373,6 +373,10 @@ Item {
console.info(`calling mocked DAppsStore.sendTransaction(${topic}, ${id}, ${address}, ${chainId}, ${password}, ${tx})`) console.info(`calling mocked DAppsStore.sendTransaction(${topic}, ${id}, ${address}, ${chainId}, ${password}, ${tx})`)
return "0xf8672a8402fb7acf82520894e2d622c817878da5143bbe068" return "0xf8672a8402fb7acf82520894e2d622c817878da5143bbe068"
} }
function getEstimatedTime(chainId, maxFeePerGas) {
return Constants.TransactionEstimatedTime.LessThanThreeMins
}
} }
walletRootStore: QObject { walletRootStore: QObject {

View File

@ -5,12 +5,13 @@ import QtQuick.Layouts 1.15
import StatusQ 0.1 import StatusQ 0.1
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.controls 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 1.0
import AppLayouts.Wallet.services.dapps.types 1.0 import AppLayouts.Wallet.services.dapps.types 1.0
import shared.popups.walletconnect 1.0
import utils 1.0 import utils 1.0
DappsComboBox { DappsComboBox {
@ -119,6 +120,7 @@ DappsComboBox {
property SessionRequestResolved request: null property SessionRequestResolved request: null
sourceComponent: DAppSignRequestModal { sourceComponent: DAppSignRequestModal {
id: dappRequestModal
objectName: "dappsRequestModal" objectName: "dappsRequestModal"
loginType: request.account.migragedToKeycard ? Constants.LoginType.Keycard : root.loginType loginType: request.account.migragedToKeycard ? Constants.LoginType.Keycard : root.loginType
visible: true visible: true
@ -138,7 +140,7 @@ DappsComboBox {
currentCurrency: "" currentCurrency: ""
fiatFees: request.maxFeesText fiatFees: request.maxFeesText
cryptoFees: request.maxFeesEthText cryptoFees: request.maxFeesEthText
estimatedTime: request.estimatedTimeText estimatedTime: ""
feesLoading: !request.maxFeesText || !request.maxFeesEthText feesLoading: !request.maxFeesText || !request.maxFeesEthText
hasFees: signingTransaction hasFees: signingTransaction
enoughFundsForTransaction: request.enoughFunds enoughFundsForTransaction: request.enoughFunds
@ -199,14 +201,16 @@ DappsComboBox {
} catch (e) { } catch (e) {
// ignore error in case of tests and storybook where we don't have access to globalUtils // ignore error in case of tests and storybook where we don't have access to globalUtils
} }
cryptoFees = ethStr cryptoFees = ethStr
enoughFundsForTransaction = haveEnoughFunds enoughFundsForTransaction = haveEnoughFunds
enoughFundsForFees = haveEnoughFunds enoughFundsForFees = haveEnoughFunds
feesLoading = false feesLoading = false
hasFees = !!maxFees hasFees = !!maxFees
} }
function onEstimatedTimeUpdated(minMinutes, maxMinutes) {
estimatedTime = qsTr("%1-%2mins").arg(minMinutes).arg(maxMinutes) function onEstimatedTimeUpdated(estimatedTimeEnum) {
dappRequestModal.estimatedTime = WalletUtils.getLabelForEstimatedTxTime(estimatedTimeEnum)
} }
} }
} }

View File

@ -34,7 +34,8 @@ QObject {
signal displayToastMessage(string message, bool error) signal displayToastMessage(string message, bool error)
signal sessionRequestResult(/*model entry of SessionRequestResolved*/ var request, bool isSuccess) signal sessionRequestResult(/*model entry of SessionRequestResolved*/ var request, bool isSuccess)
signal maxFeesUpdated(real maxFees, int maxFeesWei, bool haveEnoughFunds, string symbol) 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 { Connections {
target: sdk target: sdk
@ -138,7 +139,6 @@ QObject {
maxFeesText: "?", maxFeesText: "?",
maxFeesEthText: "?", maxFeesEthText: "?",
enoughFunds: false, enoughFunds: false,
estimatedTimeText: "?"
}) })
if (obj === null) { if (obj === null) {
console.error("Error creating SessionRequestResolved for event") console.error("Error creating SessionRequestResolved for event")
@ -158,20 +158,21 @@ QObject {
} }
obj.resolveDappInfoFromSession(session) obj.resolveDappInfoFromSession(session)
root.sessionRequest(obj) root.sessionRequest(obj)
let estimatedTimeEnum = getEstimatedTimeInterval(data, method, obj.network.chainId)
root.estimatedTimeUpdated(estimatedTimeEnum)
// TODO #15192: update maxFees // TODO #15192: update maxFees
if (!event.params.request.params[0].gasLimit || !event.params.request.params[0].gasPrice) { if (!event.params.request.params[0].gasLimit || !event.params.request.params[0].gasPrice) {
root.maxFeesUpdated(0, 0, true, "") root.maxFeesUpdated(0, 0, true, "")
root.estimatedTimeUpdated(0, 0)
return return
} }
let gasLimit = parseFloat(parseInt(event.params.request.params[0].gasLimit, 16)); let gasLimit = parseFloat(parseInt(event.params.request.params[0].gasLimit, 16));
let gasPrice = parseFloat(parseInt(event.params.request.params[0].gasPrice, 16)); let gasPrice = parseFloat(parseInt(event.params.request.params[0].gasPrice, 16));
let maxFees = gasLimit * gasPrice let maxFees = gasLimit * gasPrice
root.maxFeesUpdated(maxFees/1000000000, maxFees, true, "Gwei") root.maxFeesUpdated(maxFees/1000000000, maxFees, true, "Gwei")
// TODO #15192: update estimatedTime
root.estimatedTimeUpdated(1, 12)
}) })
return obj return obj
@ -320,6 +321,34 @@ QObject {
console.error("No password or pin provided to sign message") 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 /// The queue is used to ensure that the events are processed in the order they are received but they could be

View File

@ -5,6 +5,7 @@ import QtQml 2.15
import utils 1.0 import utils 1.0
QtObject { QtObject {
/// Supported methods /// Supported methods
/// userString is used in the context `dapp.url #{userString} <accepted/rejected>` /// userString is used in the context `dapp.url #{userString} <accepted/rejected>`
/// requestDisplay is used in the context `dApp wants you to ${requestDisplay} with <Account Name Here>` /// requestDisplay is used in the context `dApp wants you to ${requestDisplay} with <Account Name Here>`

View File

@ -31,7 +31,6 @@ QObject {
readonly property string maxFeesText: "" readonly property string maxFeesText: ""
readonly property string maxFeesEthText: "" readonly property string maxFeesEthText: ""
readonly property bool enoughFunds: false readonly property bool enoughFunds: false
readonly property string estimatedTimeText: ""
function resolveDappInfoFromSession(session) { function resolveDappInfoFromSession(session) {
let meta = session.peer.metadata let meta = session.peer.metadata

View File

@ -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
}
}

View File

@ -1,4 +1,3 @@
MaxFeesDisplay 1.0 MaxFeesDisplay.qml MaxFeesDisplay 1.0 MaxFeesDisplay.qml
EstimatedTimeDisplay 1.0 EstimatedTimeDisplay.qml
IntentionPanel 1.0 IntentionPanel.qml IntentionPanel 1.0 IntentionPanel.qml
ContentPanel 1.0 ContentPanel.qml ContentPanel 1.0 ContentPanel.qml

View File

@ -63,6 +63,12 @@ QObject {
if (txObj.value) { tx.value = stripLeadingZeros(txObj.value) } if (txObj.value) { tx.value = stripLeadingZeros(txObj.value) }
return tx 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 // Returns the hex encoded signature of the transaction or empty string if error
function signTransaction(topic, id, address, chainId, password, txObj) { function signTransaction(topic, id, address, chainId, password, txObj) {
let tx = prepareTxForStatusGo(txObj) let tx = prepareTxForStatusGo(txObj)

View File

@ -1333,6 +1333,7 @@ QtObject {
readonly property string paraswapUrl: "app.paraswap.io" readonly property string paraswapUrl: "app.paraswap.io"
} }
// Mirrors src/app_service/service/transaction/service.nim -> EstimatedTime
enum TransactionEstimatedTime { enum TransactionEstimatedTime {
Unknown = 0, Unknown = 0,
LessThanOneMin, LessThanOneMin,