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.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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import QtQml 2.15
import utils 1.0
QtObject {
/// Supported methods
/// 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>`

View File

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

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
EstimatedTimeDisplay 1.0 EstimatedTimeDisplay.qml
IntentionPanel 1.0 IntentionPanel.qml
ContentPanel 1.0 ContentPanel.qml

View File

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

View File

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