fix(Dapps): Fixing fees in transaction requests
Fixes: 1. Fixing the laggy scrolling on transaction requiests popups. The root cause of this issue was the fees request and also the estimated time request. These periodic requests were blocking. Now we'll call these API async. 2. Fixing the max fees: The fees computation was using 21k as gasLimit. This value was hardcoded in WC. Now we're requesting the gasLimit if it's not provided by the dApp. This call is also async. 3. Fixing the periodicity of the fees computation. The fees were computed by the client only if the tx object didn't already provide the fees. But the tx could fail if when the fees are highly volatile because it was not being overridden. Now Status is computing the fees periodically for all tx requests. 4. Fixing an issue where the loading state of the fees text in the modal was showing text underneath the loading animation. Fixed by updating the AnimatedText to support a custom target property. The text component used for session requests is using `cusomColor` property to set the text color and the `color` for the text must not be overriden.
This commit is contained in:
parent
f60c3321ce
commit
f42f342731
|
@ -176,7 +176,7 @@ proc newModule*(
|
||||||
result.filter = initFilter(result.controller)
|
result.filter = initFilter(result.controller)
|
||||||
|
|
||||||
result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService, transactionService, keycardService)
|
result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService, transactionService, keycardService)
|
||||||
result.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService)
|
result.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService, result.events)
|
||||||
|
|
||||||
result.dappsConnectorService = connector_service.newService(result.events)
|
result.dappsConnectorService = connector_service.newService(result.events)
|
||||||
result.dappsConnectorController = connector_controller.newController(result.dappsConnectorService, result.events)
|
result.dappsConnectorController = connector_controller.newController(result.dappsConnectorService, result.events)
|
||||||
|
@ -360,6 +360,7 @@ method load*(self: Module) =
|
||||||
self.sendModule.load()
|
self.sendModule.load()
|
||||||
self.networksModule.load()
|
self.networksModule.load()
|
||||||
self.walletConnectService.init()
|
self.walletConnectService.init()
|
||||||
|
self.walletConnectController.init()
|
||||||
self.dappsConnectorService.init()
|
self.dappsConnectorService.init()
|
||||||
|
|
||||||
method isLoaded*(self: Module): bool =
|
method isLoaded*(self: Module): bool =
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import NimQml
|
import NimQml
|
||||||
import chronicles, times, json
|
import chronicles, times, json
|
||||||
|
|
||||||
|
import app/core/eventemitter
|
||||||
import app/global/global_singleton
|
import app/global/global_singleton
|
||||||
import app_service/common/utils
|
import app_service/common/utils
|
||||||
import app_service/service/wallet_connect/service as wallet_connect_service
|
import app_service/service/wallet_connect/service as wallet_connect_service
|
||||||
|
@ -19,18 +20,40 @@ QtObject:
|
||||||
Controller* = ref object of QObject
|
Controller* = ref object of QObject
|
||||||
service: wallet_connect_service.Service
|
service: wallet_connect_service.Service
|
||||||
walletAccountService: wallet_account_service.Service
|
walletAccountService: wallet_account_service.Service
|
||||||
|
events: EventEmitter
|
||||||
|
|
||||||
proc delete*(self: Controller) =
|
proc delete*(self: Controller) =
|
||||||
self.QObject.delete
|
self.QObject.delete
|
||||||
|
|
||||||
proc newController*(service: wallet_connect_service.Service, walletAccountService: wallet_account_service.Service): Controller =
|
proc newController*(
|
||||||
|
service: wallet_connect_service.Service,
|
||||||
|
walletAccountService: wallet_account_service.Service,
|
||||||
|
events: EventEmitter): Controller =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
|
|
||||||
result.service = service
|
result.service = service
|
||||||
result.walletAccountService = walletAccountService
|
result.walletAccountService = walletAccountService
|
||||||
|
result.events = events
|
||||||
|
|
||||||
result.QObject.setup
|
result.QObject.setup
|
||||||
|
|
||||||
|
proc estimatedTimeResponse*(self: Controller, topic: string, estimatedTime: int) {.signal.}
|
||||||
|
proc suggestedFeesResponse*(self: Controller, topic: string, suggestedFeesJson: string) {.signal.}
|
||||||
|
proc estimatedGasResponse*(self: Controller, topic: string, gasEstimate: string) {.signal.}
|
||||||
|
|
||||||
|
proc init*(self: Controller) =
|
||||||
|
self.events.on(SIGNAL_ESTIMATED_TIME_RESPONSE) do(e: Args):
|
||||||
|
let args = EstimatedTimeArgs(e)
|
||||||
|
self.estimatedTimeResponse(args.topic, args.estimatedTime)
|
||||||
|
|
||||||
|
self.events.on(SIGNAL_SUGGESTED_FEES_RESPONSE) do(e: Args):
|
||||||
|
let args = SuggestedFeesArgs(e)
|
||||||
|
self.suggestedFeesResponse(args.topic, $(args.suggestedFees))
|
||||||
|
|
||||||
|
self.events.on(SIGNAL_ESTIMATED_GAS_RESPONSE) do(e: Args):
|
||||||
|
let args = EstimatedGasArgs(e)
|
||||||
|
self.estimatedGasResponse(args.topic, args.estimatedGas)
|
||||||
|
|
||||||
## signals emitted by this controller
|
## signals emitted by this controller
|
||||||
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool, password: string, pin: string, payload: string) {.signal.}
|
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool, password: string, pin: string, payload: string) {.signal.}
|
||||||
proc signingResultReceived*(self: Controller, topic: string, id: string, data: string) {.signal.}
|
proc signingResultReceived*(self: Controller, topic: string, id: string, data: string) {.signal.}
|
||||||
|
@ -195,12 +218,15 @@ QtObject:
|
||||||
error "sendTransaction failed: ", msg=e.msg
|
error "sendTransaction failed: ", msg=e.msg
|
||||||
self.signingResultReceived(topic, id, res)
|
self.signingResultReceived(topic, id, res)
|
||||||
|
|
||||||
proc getEstimatedTime(self: Controller, chainId: int, maxFeePerGasHex: string): int {.slot.} =
|
proc requestEstimatedTime(self: Controller, topic: string, chainId: int, maxFeePerGasHex: string) {.slot.} =
|
||||||
return self.service.getEstimatedTime(chainId, maxFeePerGasHex).int
|
self.service.getEstimatedTime(topic, chainId, maxFeePerGasHex)
|
||||||
|
|
||||||
proc getSuggestedFeesJson(self: Controller, chainId: int): string {.slot.} =
|
proc requestSuggestedFeesJson(self: Controller, topic: string, chainId: int) {.slot.} =
|
||||||
let dto = self.service.getSuggestedFees(chainId)
|
self.service.requestSuggestedFees(topic, chainId)
|
||||||
return dto.toJson()
|
|
||||||
|
proc requestGasEstimate(self: Controller, topic: string, chainId: int, txJson: string) {.slot.} =
|
||||||
|
let txObj = parseJson(txJson)
|
||||||
|
self.service.requestGasEstimate(topic, txObj, chainId)
|
||||||
|
|
||||||
proc hexToDecBigString*(self: Controller, hex: string): string {.slot.} =
|
proc hexToDecBigString*(self: Controller, hex: string): string {.slot.} =
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import backend/backend
|
||||||
|
import backend/eth
|
||||||
|
|
||||||
|
type
|
||||||
|
AsyncGetEstimatedTimeArgs = ref object of QObjectTaskArg
|
||||||
|
topic: string
|
||||||
|
chainId: int
|
||||||
|
maxFeePerGasHex: string
|
||||||
|
|
||||||
|
AsyncSuggestedFeesArgs = ref object of QObjectTaskArg
|
||||||
|
topic: string
|
||||||
|
chainId: int
|
||||||
|
|
||||||
|
AsyncEstimateGasArgs = ref object of QObjectTaskArg
|
||||||
|
topic: string
|
||||||
|
chainId: int
|
||||||
|
txJson: string
|
||||||
|
|
||||||
|
proc asyncGetEstimatedTimeTask(argsEncoded: string) {.gcsafe, nimcall.} =
|
||||||
|
let arg = decode[AsyncGetEstimatedTimeArgs](argsEncoded)
|
||||||
|
let result = %*{
|
||||||
|
"topic": arg.topic,
|
||||||
|
"chainId": arg.chainId,
|
||||||
|
"estimatedTime": EstimatedTime.Unknown,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
var maxFeePerGas: float64
|
||||||
|
if arg.maxFeePerGasHex.isEmptyOrWhitespace:
|
||||||
|
let chainFeesResult = eth.suggestedFees(arg.chainId).result
|
||||||
|
let chainFees = chainFeesResult.toSuggestedFeesDto()
|
||||||
|
if chainFees.isNil:
|
||||||
|
arg.finish(result)
|
||||||
|
|
||||||
|
# For non-EIP-1559 chains, we use the high fee
|
||||||
|
if chainFees.eip1559Enabled:
|
||||||
|
maxFeePerGas = chainFees.maxFeePerGasM
|
||||||
|
else:
|
||||||
|
maxFeePerGas = chainFees.maxFeePerGasL
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
let maxFeePerGasInt = parseHexInt(arg.maxFeePerGasHex)
|
||||||
|
maxFeePerGas = maxFeePerGasInt.float
|
||||||
|
except ValueError:
|
||||||
|
error "failed to parse maxFeePerGasHex", msg = arg.maxFeePerGasHex
|
||||||
|
arg.finish(result)
|
||||||
|
|
||||||
|
let estimatedTime = backend.getTransactionEstimatedTime(arg.chainId, $(maxFeePerGas)).result.getInt
|
||||||
|
result["estimatedTime"] = %estimatedTime
|
||||||
|
arg.finish(result)
|
||||||
|
except Exception as e:
|
||||||
|
error "asyncGetEstimatedTime failed: ", msg=e.msg
|
||||||
|
arg.finish(result)
|
||||||
|
|
||||||
|
proc asyncSuggestedFeesTask(argsEncoded: string) {.gcsafe, nimcall.} =
|
||||||
|
let arg = decode[AsyncSuggestedFeesArgs](argsEncoded)
|
||||||
|
let result = %*{
|
||||||
|
"topic": arg.topic,
|
||||||
|
"chainId": arg.chainId,
|
||||||
|
"suggestedFees": %*{},
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
let response = eth.suggestedFees(arg.chainId)
|
||||||
|
result["suggestedFees"] = response.result
|
||||||
|
arg.finish(result)
|
||||||
|
except Exception as e:
|
||||||
|
error "asyncSuggestedFees failed: ", msg=e.msg
|
||||||
|
arg.finish(result)
|
||||||
|
|
||||||
|
proc asyncEstimateGasTask(argsEncoded: string) {.gcsafe, nimcall.} =
|
||||||
|
let arg = decode[AsyncEstimateGasArgs](argsEncoded)
|
||||||
|
let result = %*{
|
||||||
|
"topic": arg.topic,
|
||||||
|
"chainId": arg.chainId,
|
||||||
|
"estimatedGas": "",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
let tx = parseJson(arg.txJson)
|
||||||
|
let transaction = %*{
|
||||||
|
"from": tx["from"].getStr,
|
||||||
|
"to": tx["to"].getStr,
|
||||||
|
"data": tx["data"].getStr
|
||||||
|
}
|
||||||
|
if tx.hasKey("value"):
|
||||||
|
transaction["value"] = tx["value"]
|
||||||
|
|
||||||
|
let response = eth.estimateGas(arg.chainId, %* [transaction])
|
||||||
|
result["estimatedGas"] = response.result
|
||||||
|
arg.finish(result)
|
||||||
|
except Exception as e:
|
||||||
|
error "asyncGasLimit failed: ", msg=e.msg
|
||||||
|
arg.finish(result)
|
|
@ -14,21 +14,45 @@ import app/global/global_singleton
|
||||||
|
|
||||||
import app/core/eventemitter
|
import app/core/eventemitter
|
||||||
import app/core/signals/types
|
import app/core/signals/types
|
||||||
import app/core/tasks/[threadpool]
|
import app/core/[main]
|
||||||
|
import app/core/tasks/[qt, threadpool]
|
||||||
|
|
||||||
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
|
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||||
|
|
||||||
|
include app_service/common/json_utils
|
||||||
|
include app/core/tasks/common
|
||||||
|
include async_tasks
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "wallet-connect-service"
|
topics = "wallet-connect-service"
|
||||||
|
|
||||||
# include async_tasks
|
# include async_tasks
|
||||||
|
|
||||||
const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule"
|
const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule"
|
||||||
|
const SIGNAL_ESTIMATED_TIME_RESPONSE* = "estimatedTimeResponse"
|
||||||
|
const SIGNAL_SUGGESTED_FEES_RESPONSE* = "suggestedFeesResponse"
|
||||||
|
const SIGNAL_ESTIMATED_GAS_RESPONSE* = "estimatedGasResponse"
|
||||||
|
|
||||||
type
|
type
|
||||||
AuthenticationResponseFn* = proc(keyUid: string, password: string, pin: string)
|
AuthenticationResponseFn* = proc(keyUid: string, password: string, pin: string)
|
||||||
SignResponseFn* = proc(keyUid: string, signature: string)
|
SignResponseFn* = proc(keyUid: string, signature: string)
|
||||||
|
|
||||||
|
type
|
||||||
|
EstimatedTimeArgs* = ref object of Args
|
||||||
|
topic*: string
|
||||||
|
chainId*: int
|
||||||
|
estimatedTime*: int
|
||||||
|
|
||||||
|
SuggestedFeesArgs* = ref object of Args
|
||||||
|
topic*: string
|
||||||
|
chainId*: int
|
||||||
|
suggestedFees*: JsonNode
|
||||||
|
|
||||||
|
EstimatedGasArgs* = ref object of Args
|
||||||
|
topic*: string
|
||||||
|
chainId*: int
|
||||||
|
estimatedGas*: string
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type Service* = ref object of QObject
|
type Service* = ref object of QObject
|
||||||
events: EventEmitter
|
events: EventEmitter
|
||||||
|
@ -206,35 +230,55 @@ QtObject:
|
||||||
return response.getStr
|
return response.getStr
|
||||||
|
|
||||||
# empty maxFeePerGasHex will fetch the current chain's maxFeePerGas
|
# empty maxFeePerGasHex will fetch the current chain's maxFeePerGas
|
||||||
proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGasHex: string): EstimatedTime =
|
proc getEstimatedTime*(self: Service, topic: string, chainId: int, maxFeePerGasHex: string) =
|
||||||
var maxFeePerGas: float64
|
let request = AsyncGetEstimatedTimeArgs(
|
||||||
if maxFeePerGasHex.isEmptyOrWhitespace:
|
tptr: asyncGetEstimatedTimeTask,
|
||||||
let chainFees = self.transactions.suggestedFees(chainId)
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
if chainFees.isNil:
|
slot: "estimatedTimeResponse",
|
||||||
return EstimatedTime.Unknown
|
topic: topic,
|
||||||
|
chainId: chainId,
|
||||||
|
maxFeePerGasHex: maxFeePerGasHex
|
||||||
|
)
|
||||||
|
self.threadpool.start(request)
|
||||||
|
|
||||||
# For non-EIP-1559 chains, we use the high fee
|
proc estimatedTimeResponse*(self: Service, response: string) {.slot.} =
|
||||||
if chainFees.eip1559Enabled:
|
|
||||||
maxFeePerGas = chainFees.maxFeePerGasM
|
|
||||||
else:
|
|
||||||
maxFeePerGas = chainFees.maxFeePerGasL
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
let maxFeePerGasInt = parseHexInt(maxFeePerGasHex)
|
let responseObj = response.parseJson
|
||||||
maxFeePerGas = maxFeePerGasInt.float
|
let args = EstimatedTimeArgs(
|
||||||
except ValueError:
|
topic: responseObj["topic"].getStr,
|
||||||
error "failed to parse maxFeePerGasHex", maxFeePerGasHex
|
chainId: responseObj["chainId"].getInt,
|
||||||
return EstimatedTime.Unknown
|
estimatedTime: responseObj["estimatedTime"].getInt
|
||||||
|
)
|
||||||
|
self.events.emit(SIGNAL_ESTIMATED_TIME_RESPONSE, args)
|
||||||
|
except Exception as e:
|
||||||
|
error "failed to parse estimated time response", msg = e.msg
|
||||||
|
|
||||||
return self.transactions.getEstimatedTime(chainId, $(maxFeePerGas))
|
proc requestSuggestedFees*(self: Service, topic: string, chainId: int) =
|
||||||
|
let request = AsyncSuggestedFeesArgs(
|
||||||
|
tptr: asyncSuggestedFeesTask,
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "suggestedFeesResponse",
|
||||||
|
topic: topic,
|
||||||
|
chainId: chainId
|
||||||
|
)
|
||||||
|
self.threadpool.start(request)
|
||||||
|
|
||||||
proc getSuggestedFees*(self: Service, chainId: int): SuggestedFeesDto =
|
proc suggestedFeesResponse*(self: Service, response: string) {.slot.} =
|
||||||
return self.transactions.suggestedFees(chainId)
|
try:
|
||||||
|
let responseObj = response.parseJson
|
||||||
|
let args = SuggestedFeesArgs(
|
||||||
|
topic: responseObj["topic"].getStr,
|
||||||
|
chainId: responseObj["chainId"].getInt,
|
||||||
|
suggestedFees: responseObj["suggestedFees"]
|
||||||
|
)
|
||||||
|
self.events.emit(SIGNAL_SUGGESTED_FEES_RESPONSE, args)
|
||||||
|
except Exception as e:
|
||||||
|
error "failed to parse suggested fees response", msg = e.msg
|
||||||
|
|
||||||
proc disconnectKeycardReponseSignal(self: Service) =
|
proc disconnectKeycardReponseSignal(self: Service) =
|
||||||
self.events.disconnect(self.connectionKeycardResponse)
|
self.events.disconnect(self.connectionKeycardResponse)
|
||||||
|
|
||||||
proc connectKeycardReponseSignal(self: Service) =
|
proc connectKeycardReponseSignal(self: Service) =
|
||||||
self.connectionKeycardResponse = self.events.onWithUUID(SIGNAL_KEYCARD_RESPONSE) do(e: Args):
|
self.connectionKeycardResponse = self.events.onWithUUID(SIGNAL_KEYCARD_RESPONSE) do(e: Args):
|
||||||
let args = KeycardLibArgs(e)
|
let args = KeycardLibArgs(e)
|
||||||
self.disconnectKeycardReponseSignal()
|
self.disconnectKeycardReponseSignal()
|
||||||
|
@ -254,10 +298,10 @@ proc connectKeycardReponseSignal(self: Service) =
|
||||||
singletonInstance.utils.removeHexPrefix(args.flowEvent.txSignature.v)
|
singletonInstance.utils.removeHexPrefix(args.flowEvent.txSignature.v)
|
||||||
self.signCallback(args.flowEvent.keyUid, signature)
|
self.signCallback(args.flowEvent.keyUid, signature)
|
||||||
|
|
||||||
proc cancelCurrentFlow*(self: Service) =
|
proc cancelCurrentFlow*(self: Service) =
|
||||||
self.keycardService.cancelCurrentFlow()
|
self.keycardService.cancelCurrentFlow()
|
||||||
|
|
||||||
proc runSigningOnKeycard*(self: Service, keyUid: string, path: string, hashedMessageToSign: string, pin: string, callback: SignResponseFn): bool =
|
proc runSigningOnKeycard*(self: Service, keyUid: string, path: string, hashedMessageToSign: string, pin: string, callback: SignResponseFn): bool =
|
||||||
if pin.len == 0:
|
if pin.len == 0:
|
||||||
return false
|
return false
|
||||||
if self.signCallback != nil:
|
if self.signCallback != nil:
|
||||||
|
@ -267,3 +311,26 @@ proc runSigningOnKeycard*(self: Service, keyUid: string, path: string, hashedMes
|
||||||
self.connectKeycardReponseSignal()
|
self.connectKeycardReponseSignal()
|
||||||
self.keycardService.startSignFlow(path, hashedMessageToSign, pin)
|
self.keycardService.startSignFlow(path, hashedMessageToSign, pin)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
proc requestGasEstimate*(self: Service, topic: string, tx: JsonNode, chainId: int) =
|
||||||
|
let request = AsyncEstimateGasArgs(
|
||||||
|
tptr: asyncEstimateGasTask,
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "estimatedGasResponse",
|
||||||
|
topic: topic,
|
||||||
|
chainId: chainId,
|
||||||
|
txJson: $tx
|
||||||
|
)
|
||||||
|
self.threadpool.start(request)
|
||||||
|
|
||||||
|
proc estimatedGasResponse*(self: Service, response: string) {.slot.} =
|
||||||
|
try:
|
||||||
|
let responseObj = response.parseJson
|
||||||
|
let args = EstimatedGasArgs(
|
||||||
|
topic: responseObj["topic"].getStr,
|
||||||
|
chainId: responseObj["chainId"].getInt,
|
||||||
|
estimatedGas: responseObj["estimatedGas"].getStr
|
||||||
|
)
|
||||||
|
self.events.emit(SIGNAL_ESTIMATED_GAS_RESPONSE, args)
|
||||||
|
except Exception as e:
|
||||||
|
error "failed to parse estimated gas response", msg = e.msg
|
|
@ -44,9 +44,10 @@ SplitView {
|
||||||
cryptoFees: "0.001"
|
cryptoFees: "0.001"
|
||||||
estimatedTime: "3-5 minutes"
|
estimatedTime: "3-5 minutes"
|
||||||
feesLoading: feesLoading.checked
|
feesLoading: feesLoading.checked
|
||||||
|
estimatedTimeLoading: feesLoading.checked
|
||||||
hasFees: hasFees.checked
|
hasFees: hasFees.checked
|
||||||
enoughFundsForTransaction: enoughFeesForTransaction.checked
|
enoughFundsForTransaction: enoughFeesForTransaction.checked
|
||||||
enoughFundsForFees: enoughFeesForGas.checked
|
enoughFundsForFees: enoughFeesForGas.checked || !feesLoading.checked
|
||||||
|
|
||||||
// sun emoji
|
// sun emoji
|
||||||
accountEmoji: "\u2600"
|
accountEmoji: "\u2600"
|
||||||
|
@ -133,7 +134,7 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce nibh. Etiam quis
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: feesLoading
|
id: feesLoading
|
||||||
text: "Fees loading"
|
text: "Fees loading"
|
||||||
checked: false
|
checked: true
|
||||||
}
|
}
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: hasFees
|
id: hasFees
|
||||||
|
|
|
@ -78,8 +78,11 @@ Item {
|
||||||
walletConnectEnabled: wcService.walletConnectFeatureEnabled
|
walletConnectEnabled: wcService.walletConnectFeatureEnabled
|
||||||
connectorEnabled: wcService.connectorFeatureEnabled
|
connectorEnabled: wcService.connectorFeatureEnabled
|
||||||
|
|
||||||
//formatBigNumber: (number, symbol, noSymbolOption) => wcService.walletRootStore.currencyStore.formatBigNumber(number, symbol, noSymbolOption)
|
formatBigNumber: (number, symbol, noSymbolOption) => {
|
||||||
|
print ("formatBigNumber", number, symbol, noSymbolOption)
|
||||||
|
return parseFloat(number).toLocaleString(Qt.locale(), 'f', 2)
|
||||||
|
+ (noSymbolOption ? "" : " " + (symbol || Qt.locale().currencySymbol(Locale.CurrencyIsoCode)))
|
||||||
|
}
|
||||||
onDisconnectRequested: (connectionId) => wcService.disconnectDapp(connectionId)
|
onDisconnectRequested: (connectionId) => wcService.disconnectDapp(connectionId)
|
||||||
onPairingRequested: (uri) => wcService.pair(uri)
|
onPairingRequested: (uri) => wcService.pair(uri)
|
||||||
onPairingValidationRequested: (uri) => wcService.validatePairingUri(uri)
|
onPairingValidationRequested: (uri) => wcService.validatePairingUri(uri)
|
||||||
|
@ -87,6 +90,7 @@ Item {
|
||||||
onConnectionDeclined: (pairingId) => wcService.rejectPairSession(pairingId)
|
onConnectionDeclined: (pairingId) => wcService.rejectPairSession(pairingId)
|
||||||
onSignRequestAccepted: (connectionId, requestId) => wcService.sign(connectionId, requestId)
|
onSignRequestAccepted: (connectionId, requestId) => wcService.sign(connectionId, requestId)
|
||||||
onSignRequestRejected: (connectionId, requestId) => wcService.rejectSign(connectionId, requestId, false /*hasError*/)
|
onSignRequestRejected: (connectionId, requestId) => wcService.rejectSign(connectionId, requestId, false /*hasError*/)
|
||||||
|
onSignRequestIsLive: (connectionId, requestId) => wcService.signRequestIsLive(connectionId, requestId)
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: dappsWorkflow.wcService
|
target: dappsWorkflow.wcService
|
||||||
|
@ -170,7 +174,7 @@ Item {
|
||||||
model: dappsService.sessionRequestsModel
|
model: dappsService.sessionRequestsModel
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
StatusBaseText {
|
StatusBaseText {
|
||||||
text: SQUtils.Utils.elideAndFormatWalletAddress(model.topic, 6, 4)
|
text: SQUtils.Utils.elideAndFormatWalletAddress(model.requestItem.topic, 6, 4)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -393,6 +397,10 @@ Item {
|
||||||
signal userAuthenticationFailed(string topic, string id)
|
signal userAuthenticationFailed(string topic, string id)
|
||||||
signal signingResult(string topic, string id, string data)
|
signal signingResult(string topic, string id, string data)
|
||||||
signal activeSessionsReceived(var activeSessionsJsonObj, bool success)
|
signal activeSessionsReceived(var activeSessionsJsonObj, bool success)
|
||||||
|
// Fees and gas
|
||||||
|
signal estimatedTimeResponse(string topic, int timeCategory, bool success)
|
||||||
|
signal suggestedFeesResponse(string topic, var suggestedFeesJsonObj, bool success)
|
||||||
|
signal estimatedGasResponse(string topic, string gasEstimate, bool success)
|
||||||
|
|
||||||
function addWalletConnectSession(sessionJson) {
|
function addWalletConnectSession(sessionJson) {
|
||||||
console.info("Add Persisted Session", sessionJson)
|
console.info("Add Persisted Session", sessionJson)
|
||||||
|
@ -479,8 +487,17 @@ Item {
|
||||||
signingResult(topic, id, "0xf8672a8402fb7acf82520894e2d622c817878da5143bbe068")
|
signingResult(topic, id, "0xf8672a8402fb7acf82520894e2d622c817878da5143bbe068")
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEstimatedTime(chainId, maxFeePerGas) {
|
function requestEstimatedTime(topic, chainId, maxFeePerGasHex) {
|
||||||
return Constants.TransactionEstimatedTime.LessThanThreeMins
|
estimatedTimeResponse(topic, Constants.TransactionEstimatedTime.LessThanThreeMins, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestSuggestedFees(topic, chainId) {
|
||||||
|
const suggestedFees = getSuggestedFees()
|
||||||
|
suggestedFeesResponse(topic, suggestedFees, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestGasEstimate(topic, chainId, txObj) {
|
||||||
|
estimatedGasResponse(topic, "0x5208", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSuggestedFees() {
|
function getSuggestedFees() {
|
||||||
|
@ -488,9 +505,9 @@ Item {
|
||||||
gasPrice: 2.0,
|
gasPrice: 2.0,
|
||||||
baseFee: 5.0,
|
baseFee: 5.0,
|
||||||
maxPriorityFeePerGas: 2.0,
|
maxPriorityFeePerGas: 2.0,
|
||||||
maxFeePerGasL: 1.0,
|
maxFeePerGasLow: 1.0,
|
||||||
maxFeePerGasM: 1.1,
|
maxFeePerGasMedium: 1.1,
|
||||||
maxFeePerGasH: 1.2,
|
maxFeePerGasHigh: 1.2,
|
||||||
l1GasFee: 4.0,
|
l1GasFee: 4.0,
|
||||||
eip1559Enabled: true
|
eip1559Enabled: true
|
||||||
}
|
}
|
||||||
|
@ -569,7 +586,8 @@ Item {
|
||||||
sessions.forEach(function(session) {
|
sessions.forEach(function(session) {
|
||||||
sessionsModel.append(session)
|
sessionsModel.append(session)
|
||||||
|
|
||||||
let firstIconUrl = session.peer.metadata.icons.length > 0 ? session.peer.metadata.icons[0] : ""
|
let firstIconUrl = !!session.peer.metadata.icons && session.peer.metadata.icons.length > 0 ?
|
||||||
|
session.peer.metadata.icons[0] : ""
|
||||||
let persistedDapp = {
|
let persistedDapp = {
|
||||||
"name": session.peer.metadata.name,
|
"name": session.peer.metadata.name,
|
||||||
"url": session.peer.metadata.url,
|
"url": session.peer.metadata.url,
|
||||||
|
|
|
@ -150,6 +150,11 @@ Item {
|
||||||
signal userAuthenticationFailed(string topic, string id)
|
signal userAuthenticationFailed(string topic, string id)
|
||||||
signal signingResult(string topic, string id, string data)
|
signal signingResult(string topic, string id, string data)
|
||||||
|
|
||||||
|
signal estimatedTimeResponse(string topic, int timeCategory, bool success)
|
||||||
|
signal suggestedFeesResponse(string topic, var suggestedFeesJsonObj, bool success)
|
||||||
|
signal estimatedGasResponse(string topic, string gasEstimate, bool success)
|
||||||
|
|
||||||
|
|
||||||
// By default, return no dapps in store
|
// By default, return no dapps in store
|
||||||
function getDapps() {
|
function getDapps() {
|
||||||
dappsListReceived(dappsListReceivedJsonStr)
|
dappsListReceived(dappsListReceivedJsonStr)
|
||||||
|
@ -181,23 +186,27 @@ Item {
|
||||||
updateWalletConnectSessionsCalls.push({activeTopicsJson})
|
updateWalletConnectSessionsCalls.push({activeTopicsJson})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEstimatedTime(chainId, maxFeePerGas) {
|
function requestEstimatedTime(topic, chainId, maxFeePerGas) {
|
||||||
return Constants.TransactionEstimatedTime.LessThanThreeMins
|
estimatedTimeResponse(topic, Constants.TransactionEstimatedTime.LessThanThreeMins, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
property var mockedSuggestedFees: ({
|
property var mockedSuggestedFees: ({
|
||||||
gasPrice: 2.0,
|
gasPrice: 2.0,
|
||||||
baseFee: 5.0,
|
baseFee: 5.0,
|
||||||
maxPriorityFeePerGas: 2.0,
|
maxPriorityFeePerGas: 2.0,
|
||||||
maxFeePerGasL: 1.0,
|
maxFeePerGasLow: 1.0,
|
||||||
maxFeePerGasM: 1.1,
|
maxFeePerGasMedium: 1.1,
|
||||||
maxFeePerGasH: 1.2,
|
maxFeePerGasHigh: 1.2,
|
||||||
l1GasFee: 0.0,
|
l1GasFee: 0.0,
|
||||||
eip1559Enabled: true
|
eip1559Enabled: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function getSuggestedFees() {
|
function requestSuggestedFees(topic, chainId) {
|
||||||
return mockedSuggestedFees
|
suggestedFeesResponse(topic, mockedSuggestedFees, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestGasEstimate(topic, chainId, tx) {
|
||||||
|
estimatedGasResponse(topic, "0x5208", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function hexToDec(hex) {
|
function hexToDec(hex) {
|
||||||
|
@ -447,7 +456,7 @@ Item {
|
||||||
|
|
||||||
// Override the suggestedFees
|
// Override the suggestedFees
|
||||||
if (!!data.maxFeePerGasM) {
|
if (!!data.maxFeePerGasM) {
|
||||||
handler.store.mockedSuggestedFees.maxFeePerGasM = data.maxFeePerGasM
|
handler.store.mockedSuggestedFees.maxFeePerGasMedium = data.maxFeePerGasM
|
||||||
}
|
}
|
||||||
if (!!data.l1GasFee) {
|
if (!!data.l1GasFee) {
|
||||||
handler.store.mockedSuggestedFees.l1GasFee = data.l1GasFee
|
handler.store.mockedSuggestedFees.l1GasFee = data.l1GasFee
|
||||||
|
@ -473,10 +482,11 @@ Item {
|
||||||
callback({"b536a": JSON.parse(Testing.formatApproveSessionResponse([chainId, 7], [testAddress]))})
|
callback({"b536a": JSON.parse(Testing.formatApproveSessionResponse([chainId, 7], [testAddress]))})
|
||||||
|
|
||||||
let request = handler.requestsModel.findById(session.id)
|
let request = handler.requestsModel.findById(session.id)
|
||||||
|
request.setActive()
|
||||||
verify(!!request, "expected request to be found")
|
verify(!!request, "expected request to be found")
|
||||||
compare(request.fiatMaxFees.toFixed(), data.expect.fee, "expected ethMaxFees to be set")
|
compare(request.fiatMaxFees.toFixed(), data.expect.fee, "expected fiatMaxFees to be set")
|
||||||
// storybook's CurrenciesStore mock up getFiatValue returns the balance
|
// storybook's CurrenciesStore mock up getFiatValue returns the balance
|
||||||
compare(request.ethMaxFees, data.expect.fee, "expected fiatMaxFees to be set")
|
compare(request.ethMaxFees, data.expect.fee, "expected ethMaxFees to be set")
|
||||||
verify(request.haveEnoughFunds, "expected haveEnoughFunds to be set")
|
verify(request.haveEnoughFunds, "expected haveEnoughFunds to be set")
|
||||||
compare(request.haveEnoughFees, data.expect.haveEnoughForFees, "expected haveEnoughForFees to be set")
|
compare(request.haveEnoughFees, data.expect.haveEnoughForFees, "expected haveEnoughForFees to be set")
|
||||||
verify(!!request.feesInfo, "expected feesInfo to be set")
|
verify(!!request.feesInfo, "expected feesInfo to be set")
|
||||||
|
|
|
@ -47,6 +47,10 @@ Item {
|
||||||
signal userAuthenticationFailed(string topic, string id)
|
signal userAuthenticationFailed(string topic, string id)
|
||||||
signal signingResult(string topic, string id, string data)
|
signal signingResult(string topic, string id, string data)
|
||||||
|
|
||||||
|
signal estimatedTimeResponse(string topic, int timeCategory, bool success)
|
||||||
|
signal suggestedFeesResponse(string topic, var suggestedFeesJsonObj, bool success)
|
||||||
|
signal estimatedGasResponse(string topic, string gasEstimate, bool success)
|
||||||
|
|
||||||
function hexToDec(hex) {
|
function hexToDec(hex) {
|
||||||
return parseInt(hex, 16)
|
return parseInt(hex, 16)
|
||||||
}
|
}
|
||||||
|
@ -122,7 +126,6 @@ Item {
|
||||||
address: "0x123"
|
address: "0x123"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentCurrency: "USD"
|
|
||||||
requests: SessionRequestsModel {}
|
requests: SessionRequestsModel {}
|
||||||
getFiatValue: (balance, cryptoSymbol) => {
|
getFiatValue: (balance, cryptoSymbol) => {
|
||||||
return parseFloat(balance)
|
return parseFloat(balance)
|
||||||
|
|
|
@ -323,6 +323,7 @@ DappsComboBox {
|
||||||
cryptoFees: request.ethMaxFees ? request.ethMaxFees.toFixed() : ""
|
cryptoFees: request.ethMaxFees ? request.ethMaxFees.toFixed() : ""
|
||||||
estimatedTime: WalletUtils.getLabelForEstimatedTxTime(request.estimatedTimeCategory)
|
estimatedTime: WalletUtils.getLabelForEstimatedTxTime(request.estimatedTimeCategory)
|
||||||
feesLoading: hasFees && (!fiatFees || !cryptoFees)
|
feesLoading: hasFees && (!fiatFees || !cryptoFees)
|
||||||
|
estimatedTimeLoading: request.estimatedTimeCategory === Constants.TransactionEstimatedTime.Unknown
|
||||||
hasFees: signingTransaction
|
hasFees: signingTransaction
|
||||||
enoughFundsForTransaction: request.haveEnoughFunds
|
enoughFundsForTransaction: request.haveEnoughFunds
|
||||||
enoughFundsForFees: request.haveEnoughFees
|
enoughFundsForFees: request.haveEnoughFees
|
||||||
|
|
|
@ -10,6 +10,8 @@ import StatusQ.Core.Utils 0.1 as SQUtils
|
||||||
import shared.stores 1.0
|
import shared.stores 1.0
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
|
|
||||||
|
import "./internal"
|
||||||
|
|
||||||
/// Component that provides the dapps integration for the wallet.
|
/// Component that provides the dapps integration for the wallet.
|
||||||
/// It provides the following features:
|
/// It provides the following features:
|
||||||
/// - WalletConnect integration
|
/// - WalletConnect integration
|
||||||
|
@ -263,6 +265,12 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The fees broker to handle all fees requests for all components and connections
|
||||||
|
TransactionFeesBroker {
|
||||||
|
id: feesBroker
|
||||||
|
store: root.store
|
||||||
|
}
|
||||||
|
|
||||||
// bcSignRequestPlugin and wcSignRequestPlugin are used to handle sign requests
|
// bcSignRequestPlugin and wcSignRequestPlugin are used to handle sign requests
|
||||||
// Almost identical, and it's worth extracting in an inline component, but Qt5.15.2 doesn't support it
|
// Almost identical, and it's worth extracting in an inline component, but Qt5.15.2 doesn't support it
|
||||||
SignRequestPlugin {
|
SignRequestPlugin {
|
||||||
|
@ -272,10 +280,10 @@ SQUtils.QObject {
|
||||||
groupedAccountAssetsModel: root.groupedAccountAssetsModel
|
groupedAccountAssetsModel: root.groupedAccountAssetsModel
|
||||||
networksModel: root.networksModel
|
networksModel: root.networksModel
|
||||||
accountsModel: root.accountsModel
|
accountsModel: root.accountsModel
|
||||||
currentCurrency: root.currenciesStore.currentCurrency
|
|
||||||
store: root.store
|
store: root.store
|
||||||
requests: root.requestsModel
|
requests: root.requestsModel
|
||||||
dappsModel: root.dappsModel
|
dappsModel: root.dappsModel
|
||||||
|
feesBroker: feesBroker
|
||||||
|
|
||||||
getFiatValue: (value, currency) => {
|
getFiatValue: (value, currency) => {
|
||||||
return root.currenciesStore.getFiatValue(value, currency)
|
return root.currenciesStore.getFiatValue(value, currency)
|
||||||
|
@ -293,10 +301,10 @@ SQUtils.QObject {
|
||||||
groupedAccountAssetsModel: root.groupedAccountAssetsModel
|
groupedAccountAssetsModel: root.groupedAccountAssetsModel
|
||||||
networksModel: root.networksModel
|
networksModel: root.networksModel
|
||||||
accountsModel: root.accountsModel
|
accountsModel: root.accountsModel
|
||||||
currentCurrency: root.currenciesStore.currentCurrency
|
|
||||||
store: root.store
|
store: root.store
|
||||||
requests: root.requestsModel
|
requests: root.requestsModel
|
||||||
dappsModel: root.dappsModel
|
dappsModel: root.dappsModel
|
||||||
|
feesBroker: feesBroker
|
||||||
|
|
||||||
getFiatValue: (value, currency) => {
|
getFiatValue: (value, currency) => {
|
||||||
return root.currenciesStore.getFiatValue(value, currency)
|
return root.currenciesStore.getFiatValue(value, currency)
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import AppLayouts.Wallet.services.dapps.types 1.0
|
||||||
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||||
|
|
||||||
|
import shared.stores 1.0
|
||||||
|
|
||||||
|
// The TransactionFeesBroker is responsible for managing the subscriptions to the estimated time, fees and gas limit for a transaction
|
||||||
|
// It will bundle the same requests to optimise the backend load and will notify the subscribers when the data is ready
|
||||||
|
// It can only work with a TransactionFeesSubscriber
|
||||||
|
SQUtils.QObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property DAppsStore store
|
||||||
|
property int interval: 5000
|
||||||
|
|
||||||
|
function subscribe(subscriberObj) {
|
||||||
|
if (!(subscriberObj instanceof TransactionFeesSubscriber)) {
|
||||||
|
console.error("Invalid subscriber object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const estimatedTimeSub = estimatedTimeSubscription.createObject(subscriberObj, {
|
||||||
|
subscriber: subscriberObj
|
||||||
|
})
|
||||||
|
const feesSub = feesSubscription.createObject(subscriberObj, {
|
||||||
|
subscriber: subscriberObj
|
||||||
|
})
|
||||||
|
const gasLimitSub = gasLimitSubscription.createObject(subscriberObj, {
|
||||||
|
subscriber: subscriberObj
|
||||||
|
})
|
||||||
|
if (subscriberObj.txObject && (subscriberObj.txObject.gas || subscriberObj.txObject.gasLimit)) {
|
||||||
|
subscriberObj.setGas(subscriberObj.txObject.gas || subscriberObj.txObject.gasLimit)
|
||||||
|
}
|
||||||
|
broker.subscribe(estimatedTimeSub)
|
||||||
|
broker.subscribe(feesSub)
|
||||||
|
broker.subscribe(gasLimitSub)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error subscribing to estimated time: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SubscriptionType {
|
||||||
|
Fees,
|
||||||
|
GasLimit,
|
||||||
|
EstimatedTime
|
||||||
|
}
|
||||||
|
|
||||||
|
SQUtils.SubscriptionBroker {
|
||||||
|
id: broker
|
||||||
|
|
||||||
|
active: Qt.application.state === Qt.ApplicationActive
|
||||||
|
onRequest: d.computeFees(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
SQUtils.QObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
function computeFees(topic) {
|
||||||
|
try {
|
||||||
|
const args = JSON.parse(topic)
|
||||||
|
switch (args.type) {
|
||||||
|
case TransactionFeesBroker.SubscriptionType.Fees:
|
||||||
|
root.store.requestSuggestedFees(topic, args.chainId)
|
||||||
|
break
|
||||||
|
case TransactionFeesBroker.SubscriptionType.GasLimit:
|
||||||
|
root.store.requestGasEstimate(topic, args.chainId, args.tx)
|
||||||
|
break
|
||||||
|
case TransactionFeesBroker.SubscriptionType.EstimatedTime:
|
||||||
|
root.store.requestEstimatedTime(topic, args.chainId, args.maxFeePerGasHex)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error computing fees: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: feesSubscription
|
||||||
|
SQUtils.Subscription {
|
||||||
|
required property TransactionFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.SubscriptionType.Fees,
|
||||||
|
chainId: subscriber.chainId
|
||||||
|
})
|
||||||
|
isReady: subscriber.active
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: {
|
||||||
|
if (!response || !response.success)
|
||||||
|
return
|
||||||
|
subscriber.setFees(response.suggestedFees)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: estimatedTimeSubscription
|
||||||
|
SQUtils.Subscription {
|
||||||
|
required property TransactionFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.SubscriptionType.EstimatedTime,
|
||||||
|
chainId: subscriber.chainId,
|
||||||
|
maxFeePerGasHex: subscriber.txObject ? (subscriber.txObject.maxFeePerGas || subscriber.txObject.gasPrice || "") :
|
||||||
|
""
|
||||||
|
})
|
||||||
|
isReady: subscriber.active
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: {
|
||||||
|
if (!response || !response.success)
|
||||||
|
return
|
||||||
|
subscriber.setEstimatedTime(response.estimatedTime)
|
||||||
|
}
|
||||||
|
notificationInterval: root.interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: gasLimitSubscription
|
||||||
|
SQUtils.Subscription {
|
||||||
|
required property TransactionFeesSubscriber subscriber
|
||||||
|
readonly property var requestArgs: ({
|
||||||
|
type: TransactionFeesBroker.SubscriptionType.GasLimit,
|
||||||
|
chainId: subscriber.chainId,
|
||||||
|
tx: subscriber.txObject
|
||||||
|
})
|
||||||
|
isReady: subscriber.active && !!subscriber.txObject && !!subscriber.chainId && !subscriber.gasLimit /*Ask for gas just once*/
|
||||||
|
topic: isReady ? JSON.stringify(requestArgs) : ""
|
||||||
|
onResponseChanged: {
|
||||||
|
if (!response || !response.success)
|
||||||
|
return
|
||||||
|
subscriber.setGas(response.gasEstimate)
|
||||||
|
}
|
||||||
|
notificationInterval: root.interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
id: storeConnections
|
||||||
|
target: root.store
|
||||||
|
|
||||||
|
function onEstimatedTimeResponse(topic, timeCategory, success) {
|
||||||
|
broker.response(topic, { estimatedTime: timeCategory, success})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSuggestedFeesResponse(topic, suggestedFeesJson, success) {
|
||||||
|
broker.response(topic, { suggestedFees: suggestedFeesJson, success: success })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEstimatedGasResponse(topic, gasEstimate, success) {
|
||||||
|
broker.response(topic, { gasEstimate, success })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||||
|
|
||||||
|
import utils 1.0
|
||||||
|
|
||||||
|
SQUtils.QObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
/*
|
||||||
|
Input properties
|
||||||
|
*/
|
||||||
|
// standard transaction object
|
||||||
|
required property var txObject
|
||||||
|
// subscriber id
|
||||||
|
required property string key
|
||||||
|
// chainId -> chain id for the transaction
|
||||||
|
required property int chainId
|
||||||
|
// active specifies if the subscriber has an active subscription
|
||||||
|
required property bool active
|
||||||
|
// selectedFeesMode -> selected fees mode. Defaults to Constants.TransactionFeesMode.Medium
|
||||||
|
property int selectedFeesMode: Constants.TransactionFeesMode.Medium
|
||||||
|
|
||||||
|
// Required function to be implemented by the subscriber
|
||||||
|
required property var hexToDec /*(hexValue) => decValue*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Published properties
|
||||||
|
*/
|
||||||
|
// estimatedTimeResponse -> maps to Constants.TransactionEstimatedTime
|
||||||
|
readonly property int estimatedTimeResponse: d.estimatedTimeResponse
|
||||||
|
// maxEthFee -> Big number in Gwei. Represens the total fees for the transaction
|
||||||
|
readonly property var maxEthFee: d.computedFees
|
||||||
|
// feesInfo -> status-go fees info with updated maxFeePerGas based on selectedFeesMode
|
||||||
|
readonly property var feesInfo: d.computedFeesInfo
|
||||||
|
// gasLimit -> gas limit for the transaction
|
||||||
|
readonly property var gasLimit: d.gasResponse
|
||||||
|
|
||||||
|
function setFees(fees) {
|
||||||
|
if (d.feesResponse === fees) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.feesResponse = fees
|
||||||
|
d.resetFees()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGas(gas) {
|
||||||
|
if (d.gasResponse === gas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.gasResponse = gas
|
||||||
|
d.resetFees()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEstimatedTime(estimatedTime) {
|
||||||
|
if(!estimatedTime) {
|
||||||
|
estimatedTime = Constants.TransactionEstimatedTime.Unknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.estimatedTimeResponse = estimatedTime
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
property int estimatedTimeResponse: Constants.TransactionEstimatedTime.Unknown
|
||||||
|
property var feesResponse
|
||||||
|
property var gasResponse
|
||||||
|
// Eth max fee in Gwei
|
||||||
|
property var computedFees
|
||||||
|
// feesResponse with additional `maxFeePerGas` property based on selectedFeesMode
|
||||||
|
property var computedFeesInfo
|
||||||
|
|
||||||
|
function resetFees() {
|
||||||
|
if (!d.feesResponse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!gasResponse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
d.computedFees = getEstimatedMaxFees()
|
||||||
|
d.computedFeesInfo = d.feesResponse
|
||||||
|
d.computedFeesInfo.maxFeePerGas = getFeesForFeesMode(d.feesResponse)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to compute fees", e, e.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeesForFeesMode(feesObj) {
|
||||||
|
if (!(feesObj.hasOwnProperty("maxFeePerGasLow") &&
|
||||||
|
feesObj.hasOwnProperty("maxFeePerGasMedium") &&
|
||||||
|
feesObj.hasOwnProperty("maxFeePerGasHigh"))) {
|
||||||
|
print ("feesObj", JSON.stringify(feesObj))
|
||||||
|
throw new Error("inappropriate fees object provided")
|
||||||
|
}
|
||||||
|
const BigOps = SQUtils.AmountsArithmetic
|
||||||
|
if (!feesObj.eip1559Enabled && !!feesObj.gasPrice) {
|
||||||
|
return feesObj.gasPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (root.selectedFeesMode) {
|
||||||
|
case Constants.FeesMode.Low:
|
||||||
|
return feesObj.maxFeePerGasLow
|
||||||
|
case Constants.FeesMode.Medium:
|
||||||
|
return feesObj.maxFeePerGasMedium
|
||||||
|
case Constants.FeesMode.High:
|
||||||
|
return feesObj.maxFeePerGasHigh
|
||||||
|
default:
|
||||||
|
throw new Error("unknown selected mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEstimatedMaxFees() {
|
||||||
|
// Note: Use the received fee arguments only once!
|
||||||
|
// Complete what's missing with the suggested fees
|
||||||
|
const BigOps = SQUtils.AmountsArithmetic
|
||||||
|
const gasLimitStr = root.hexToDec(d.gasResponse)
|
||||||
|
|
||||||
|
const gasLimit = BigOps.fromString(gasLimitStr)
|
||||||
|
const maxFeesPerGas = BigOps.fromNumber(getFeesForFeesMode(d.feesResponse))
|
||||||
|
const l1GasFee = d.feesResponse.l1GasFee ? BigOps.fromNumber(d.feesResponse.l1GasFee)
|
||||||
|
: BigOps.fromNumber(0)
|
||||||
|
|
||||||
|
let maxGasFee = BigOps.times(gasLimit, maxFeesPerGas).plus(l1GasFee)
|
||||||
|
return maxGasFee
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
resetFees()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import StatusQ.Core.Utils 0.1 as SQUtils
|
||||||
import shared.stores 1.0
|
import shared.stores 1.0
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
|
|
||||||
|
import "../internal"
|
||||||
|
|
||||||
/// Plugin that listens for session requests and manages the lifecycle of the request.
|
/// Plugin that listens for session requests and manages the lifecycle of the request.
|
||||||
SQUtils.QObject {
|
SQUtils.QObject {
|
||||||
id: root
|
id: root
|
||||||
|
@ -34,11 +36,14 @@ SQUtils.QObject {
|
||||||
/// Expected to have the following roles:
|
/// Expected to have the following roles:
|
||||||
/// - address
|
/// - address
|
||||||
required property var accountsModel
|
required property var accountsModel
|
||||||
/// App currency
|
|
||||||
required property string currentCurrency
|
|
||||||
// SessionRequestsModel where the requests are stored
|
// SessionRequestsModel where the requests are stored
|
||||||
// This component will append and remove requests from this model
|
// This component will append and remove requests from this model
|
||||||
required property SessionRequestsModel requests
|
required property SessionRequestsModel requests
|
||||||
|
// The fees broker that provides the updated fees
|
||||||
|
property TransactionFeesBroker feesBroker: TransactionFeesBroker {
|
||||||
|
id: feesBroker
|
||||||
|
store: root.store
|
||||||
|
}
|
||||||
// Function to transform the eth value to fiat
|
// Function to transform the eth value to fiat
|
||||||
property var getFiatValue: (maxFeesEthStr, token /*Constants.ethToken*/) => console.error("getFiatValue not implemented")
|
property var getFiatValue: (maxFeesEthStr, token /*Constants.ethToken*/) => console.error("getFiatValue not implemented")
|
||||||
|
|
||||||
|
@ -56,7 +61,13 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestResolved(topic, id) {
|
function requestResolved(topic, id) {
|
||||||
|
const request = root.requests.findRequest(topic, id)
|
||||||
|
if (!request) {
|
||||||
|
console.error("Error finding request for topic", topic, "id", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
root.requests.removeRequest(topic, id)
|
root.requests.removeRequest(topic, id)
|
||||||
|
request.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestExpired(sessionId) {
|
function requestExpired(sessionId) {
|
||||||
|
@ -77,7 +88,12 @@ SQUtils.QObject {
|
||||||
SessionRequestWithAuth {
|
SessionRequestWithAuth {
|
||||||
id: request
|
id: request
|
||||||
store: root.store
|
store: root.store
|
||||||
|
estimatedTimeCategory: feesSubscriber.estimatedTimeResponse
|
||||||
|
feesInfo: feesSubscriber.feesInfo
|
||||||
|
haveEnoughFunds: d.hasEnoughEth(request.chainId, request.accountAddress, request.value)
|
||||||
|
haveEnoughFees: haveEnoughFunds && d.hasEnoughEth(request.chainId, request.accountAddress, request.ethMaxFees)
|
||||||
|
ethMaxFees: feesSubscriber.maxEthFee ? SQUtils.AmountsArithmetic.div(feesSubscriber.maxEthFee, SQUtils.AmountsArithmetic.fromNumber(1, 9)) : null
|
||||||
|
fiatMaxFees: ethMaxFees ? SQUtils.AmountsArithmetic.fromString(root.getFiatValue(ethMaxFees.toString(), Constants.ethToken)) : null
|
||||||
function signedHandler(topic, id, data) {
|
function signedHandler(topic, id, data) {
|
||||||
if (topic != request.topic || id != request.requestId) {
|
if (topic != request.topic || id != request.requestId) {
|
||||||
return
|
return
|
||||||
|
@ -93,26 +109,29 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active === false) {
|
if (active) {
|
||||||
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
feesBroker.subscribe(feesSubscriber)
|
||||||
}
|
|
||||||
if (active === true) {
|
|
||||||
d.subscribeForFeeUpdates(request.topic, request.requestId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onExpired: {
|
||||||
|
active = false
|
||||||
|
}
|
||||||
|
|
||||||
onRejected: (hasError) => {
|
onRejected: (hasError) => {
|
||||||
|
active = false
|
||||||
root.rejected(request.topic, request.requestId, hasError)
|
root.rejected(request.topic, request.requestId, hasError)
|
||||||
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAuthFailed: () => {
|
onAuthFailed: () => {
|
||||||
root.rejected(request.topic, request.requestId, true /*hasError*/)
|
root.rejected(request.topic, request.requestId, true /*hasError*/)
|
||||||
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onExecute: (password, pin) => {
|
onExecute: (password, pin) => {
|
||||||
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
||||||
root.store.signingResult.connect(request.signedHandler)
|
root.store.signingResult.connect(request.signedHandler)
|
||||||
let executed = false
|
let executed = false
|
||||||
try {
|
try {
|
||||||
|
@ -125,6 +144,17 @@ SQUtils.QObject {
|
||||||
root.rejected(request.topic, request.requestId, true /*hasError*/)
|
root.rejected(request.topic, request.requestId, true /*hasError*/)
|
||||||
root.store.signingResult.disconnect(request.signedHandler)
|
root.store.signingResult.disconnect(request.signedHandler)
|
||||||
}
|
}
|
||||||
|
active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionFeesSubscriber {
|
||||||
|
id: feesSubscriber
|
||||||
|
key: request.requestId
|
||||||
|
chainId: request.chainId
|
||||||
|
txObject: SessionRequest.getTxObject(request.method, request.data)
|
||||||
|
active: request.active && !!txObject
|
||||||
|
selectedFeesMode: Constants.FeesMode.Medium
|
||||||
|
hexToDec: root.store.hexToDec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +222,7 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
root.requests.enqueue(res.obj)
|
root.requests.enqueue(res.obj)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error processing session request event", e)
|
console.error("Error processing session request event", e, e.stack)
|
||||||
root.rejected(event.topic, event.id, true)
|
root.rejected(event.topic, event.id, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +241,6 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
request.setExpired()
|
request.setExpired()
|
||||||
d.unsubscribeForFeeUpdates(request.topic, request.requestId)
|
|
||||||
}
|
}
|
||||||
// returns {
|
// returns {
|
||||||
// obj: obj or nil
|
// obj: obj or nil
|
||||||
|
@ -225,13 +254,6 @@ SQUtils.QObject {
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return { obj: null, code: SessionRequest.RuntimeError }
|
return { obj: null, code: SessionRequest.RuntimeError }
|
||||||
}
|
}
|
||||||
const mainNet = lookupMainnetNetwork()
|
|
||||||
if (!mainNet) {
|
|
||||||
console.error("Mainnet network not found")
|
|
||||||
return { obj: null, code: SessionRequest.RuntimeError }
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFeesOnPreparedData(request)
|
|
||||||
|
|
||||||
let obj = sessionRequestComponent.createObject(null, {
|
let obj = sessionRequestComponent.createObject(null, {
|
||||||
event: request.event,
|
event: request.event,
|
||||||
|
@ -254,76 +276,50 @@ SQUtils.QObject {
|
||||||
return { obj: null, code: SessionRequest.RuntimeError }
|
return { obj: null, code: SessionRequest.RuntimeError }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.transaction) {
|
|
||||||
obj.haveEnoughFunds = true
|
|
||||||
return { obj: obj, code: SessionRequest.NoError }
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFeesParamsToPassedObj(obj)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
obj: obj,
|
obj: obj,
|
||||||
code: SessionRequest.NoError
|
code: SessionRequest.NoError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasEnoughEth(chainId, accountAddress, requiredEth) {
|
||||||
// Updates the fees to a SessionRequestResolved
|
if (!requiredEth) {
|
||||||
function updateFeesParamsToPassedObj(requestItem) {
|
return true
|
||||||
if (!(requestItem instanceof SessionRequestResolved)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if (!SessionRequest.isTransactionMethod(requestItem.method)) {
|
if (!accountAddress || !chainId) {
|
||||||
return
|
console.error("No account or chain provided to check funds", accountAddress, chainId)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainNet = lookupMainnetNetwork()
|
const token = SQUtils.ModelUtils.getByKey(root.groupedAccountAssetsModel, "tokensKey", Constants.ethToken)
|
||||||
if (!mainNet) {
|
const balance = getBalance(chainId, accountAddress, token)
|
||||||
console.error("Mainnet network not found")
|
|
||||||
return { obj: null, code: SessionRequest.RuntimeError }
|
if (!balance) {
|
||||||
|
console.error("Error fetching balance for account", accountAddress, "on chain", chainId)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const tx = SessionRequest.getTxObject(requestItem.method, requestItem.data)
|
const BigOps = SQUtils.AmountsArithmetic
|
||||||
requestItem.estimatedTimeCategory = root.store.getEstimatedTime(requestItem.chainId, tx.maxFeePerGas || tx.gasPrice || "")
|
const haveEnoughFunds = BigOps.cmp(balance, requiredEth) >= 0
|
||||||
|
return haveEnoughFunds
|
||||||
let st = getEstimatedFeesStatus(tx, requestItem.method, requestItem.chainId, mainNet.chainId)
|
|
||||||
let fundsStatus = checkFundsStatus(st.feesInfo.maxFees, st.feesInfo.l1GasFee, requestItem.accountAddress, requestItem.chainId, mainNet.chainId, requestItem.value)
|
|
||||||
requestItem.fiatMaxFees = st.fiatMaxFees
|
|
||||||
requestItem.ethMaxFees = st.maxFeesEth
|
|
||||||
requestItem.haveEnoughFunds = fundsStatus.haveEnoughFunds
|
|
||||||
requestItem.haveEnoughFees = fundsStatus.haveEnoughForFees
|
|
||||||
requestItem.feesInfo = st.feesInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the fee in the transaction preview on a JS Object built by SessionRequest
|
function getBalance(chainId, address, token) {
|
||||||
function updateFeesOnPreparedData(request) {
|
if (!token || !token.balances) {
|
||||||
if (!request.transaction && !request.preparedData instanceof Object) {
|
console.error("Error token balances lookup", token)
|
||||||
return
|
return null
|
||||||
|
}
|
||||||
|
const BigOps = SQUtils.AmountsArithmetic
|
||||||
|
const accEth = SQUtils.ModelUtils.getFirstModelEntryIf(token.balances, (balance) => {
|
||||||
|
return balance.account.toLowerCase() === address.toLowerCase() && balance.chainId == chainId
|
||||||
|
})
|
||||||
|
if (!accEth) {
|
||||||
|
console.error("Error balance lookup for account ", address, " on chain ", chainId)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let fees = root.store.getSuggestedFees(request.chainId)
|
const accountFundsWei = BigOps.fromString(accEth.balance)
|
||||||
if (!request.preparedData.maxFeePerGas
|
return BigOps.div(accountFundsWei, BigOps.fromNumber(1, 18))
|
||||||
&& request.preparedData.hasOwnProperty("maxFeePerGas")
|
|
||||||
&& fees.eip1559Enabled) {
|
|
||||||
request.preparedData.maxFeePerGas = d.getFeesForFeesMode(fees)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.preparedData.maxPriorityFeePerGas
|
|
||||||
&& request.preparedData.hasOwnProperty("maxPriorityFeePerGas")
|
|
||||||
&& fees.eip1559Enabled) {
|
|
||||||
request.preparedData.maxPriorityFeePerGas = fees.maxPriorityFeePerGas
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.preparedData.gasPrice
|
|
||||||
&& request.preparedData.hasOwnProperty("gasPrice")
|
|
||||||
&& !fees.eip1559Enabled) {
|
|
||||||
request.preparedData.gasPrice = fees.gasPrice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns null if the network is not found
|
|
||||||
function lookupMainnetNetwork() {
|
|
||||||
return SQUtils.ModelUtils.getByKey(root.networksModel, "layer", 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeSessionRequest(request, password, pin, payload) {
|
function executeSessionRequest(request, password, pin, payload) {
|
||||||
|
@ -364,24 +360,7 @@ SQUtils.QObject {
|
||||||
password,
|
password,
|
||||||
pin)
|
pin)
|
||||||
} else if (SessionRequest.isTransactionMethod(request.method)) {
|
} else if (SessionRequest.isTransactionMethod(request.method)) {
|
||||||
let txObj = SessionRequest.getTxObject(request.method, request.data)
|
const txObj = prepareTxForStatusGo(SessionRequest.getTxObject(request.method, request.data), payload)
|
||||||
if (!!payload) {
|
|
||||||
let hexFeesJson = root.store.convertFeesInfoToHex(JSON.stringify(payload))
|
|
||||||
if (!!hexFeesJson) {
|
|
||||||
let feesInfo = JSON.parse(hexFeesJson)
|
|
||||||
if (feesInfo.maxFeePerGas) {
|
|
||||||
txObj.maxFeePerGas = feesInfo.maxFeePerGas
|
|
||||||
}
|
|
||||||
if (feesInfo.maxPriorityFeePerGas) {
|
|
||||||
txObj.maxPriorityFeePerGas = feesInfo.maxPriorityFeePerGas
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete txObj.gasLimit
|
|
||||||
delete txObj.gasPrice
|
|
||||||
}
|
|
||||||
// Remove nonce from txObj to be auto-filled by the wallet
|
|
||||||
delete txObj.nonce
|
|
||||||
|
|
||||||
if (request.method === SessionRequest.methods.signTransaction.name) {
|
if (request.method === SessionRequest.methods.signTransaction.name) {
|
||||||
root.store.signTransaction(request.topic,
|
root.store.signTransaction(request.topic,
|
||||||
request.requestId,
|
request.requestId,
|
||||||
|
@ -405,267 +384,26 @@ SQUtils.QObject {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns {
|
function prepareTxForStatusGo(txObj, feesInfo) {
|
||||||
// maxFees -> Big number in Gwei
|
if (!!feesInfo) {
|
||||||
// maxFeePerGas
|
let hexFeesJson = root.store.convertFeesInfoToHex(JSON.stringify(feesInfo))
|
||||||
// maxPriorityFeePerGas
|
if (!!hexFeesJson) {
|
||||||
// gasPrice
|
let feesInfo = JSON.parse(hexFeesJson)
|
||||||
// }
|
if (feesInfo.maxFeePerGas) {
|
||||||
function getEstimatedMaxFees(tx, method, chainId, mainNetChainId) {
|
txObj.maxFeePerGas = feesInfo.maxFeePerGas
|
||||||
const BigOps = SQUtils.AmountsArithmetic
|
|
||||||
const gasLimit = BigOps.fromString("21000")
|
|
||||||
const parsedTransaction = SessionRequest.parseTransaction(tx, root.store.hexToDec)
|
|
||||||
let gasPrice = BigOps.fromString(parsedTransaction.maxFeePerGas)
|
|
||||||
let maxFeePerGas = BigOps.fromString(parsedTransaction.maxFeePerGas)
|
|
||||||
let maxPriorityFeePerGas = BigOps.fromString(parsedTransaction.maxPriorityFeePerGas)
|
|
||||||
let l1GasFee = BigOps.fromNumber(0)
|
|
||||||
|
|
||||||
if (!maxFeePerGas || !maxPriorityFeePerGas || !gasPrice) {
|
|
||||||
const suggesteFees = getSuggestedFees(chainId)
|
|
||||||
maxFeePerGas = suggesteFees.maxFeePerGas
|
|
||||||
maxPriorityFeePerGas = suggesteFees.maxPriorityFeePerGas
|
|
||||||
gasPrice = suggesteFees.gasPrice
|
|
||||||
l1GasFee = suggesteFees.l1GasFee
|
|
||||||
}
|
}
|
||||||
|
if (feesInfo.maxPriorityFeePerGas) {
|
||||||
let maxFees = BigOps.times(gasLimit, gasPrice)
|
txObj.maxPriorityFeePerGas = feesInfo.maxPriorityFeePerGas
|
||||||
return {maxFees, maxFeePerGas, maxPriorityFeePerGas, gasPrice, l1GasFee}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSuggestedFees(chainId) {
|
|
||||||
const BigOps = SQUtils.AmountsArithmetic
|
|
||||||
const fees = root.store.getSuggestedFees(chainId)
|
|
||||||
const maxPriorityFeePerGas = fees.maxPriorityFeePerGas
|
|
||||||
let maxFeePerGas
|
|
||||||
let gasPrice
|
|
||||||
if (fees.eip1559Enabled) {
|
|
||||||
if (!!fees.maxFeePerGasM) {
|
|
||||||
gasPrice = BigOps.fromNumber(fees.maxFeePerGasM)
|
|
||||||
maxFeePerGas = fees.maxFeePerGasM
|
|
||||||
} else if(!!tx.maxFeePerGas) {
|
|
||||||
let maxFeePerGasDec = root.store.hexToDec(tx.maxFeePerGas)
|
|
||||||
gasPrice = BigOps.fromString(maxFeePerGasDec)
|
|
||||||
maxFeePerGas = maxFeePerGasDec
|
|
||||||
} else {
|
|
||||||
console.error("Error fetching maxFeePerGas from fees or tx objects")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!!fees.gasPrice) {
|
|
||||||
gasPrice = BigOps.fromNumber(fees.gasPrice)
|
|
||||||
} else {
|
|
||||||
console.error("Error fetching suggested fees")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const l1GasFee = BigOps.fromNumber(fees.l1GasFee)
|
|
||||||
return {maxFeePerGas, maxPriorityFeePerGas, gasPrice, l1GasFee}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returned values are Big numbers
|
|
||||||
function getEstimatedFeesStatus(tx, method, chainId, mainNetChainId) {
|
|
||||||
const BigOps = SQUtils.AmountsArithmetic
|
|
||||||
|
|
||||||
const feesInfo = getEstimatedMaxFees(tx, method, chainId, mainNetChainId)
|
|
||||||
|
|
||||||
const totalMaxFees = BigOps.sum(feesInfo.maxFees, feesInfo.l1GasFee)
|
|
||||||
const maxFeesEth = BigOps.div(totalMaxFees, BigOps.fromNumber(1, 9))
|
|
||||||
|
|
||||||
const maxFeesEthStr = maxFeesEth.toString()
|
|
||||||
const fiatMaxFeesStr = root.getFiatValue(maxFeesEthStr, Constants.ethToken)
|
|
||||||
const fiatMaxFees = BigOps.fromString(fiatMaxFeesStr)
|
|
||||||
const symbol = root.currentCurrency
|
|
||||||
|
|
||||||
return {fiatMaxFees, maxFeesEth, symbol, feesInfo}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBalanceInEth(balances, address, chainId) {
|
|
||||||
const BigOps = SQUtils.AmountsArithmetic
|
|
||||||
const accEth = SQUtils.ModelUtils.getFirstModelEntryIf(balances, (balance) => {
|
|
||||||
return balance.account.toLowerCase() === address.toLowerCase() && balance.chainId == chainId
|
|
||||||
})
|
|
||||||
if (!accEth) {
|
|
||||||
console.error("Error balance lookup for account ", address, " on chain ", chainId)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const accountFundsWei = BigOps.fromString(accEth.balance)
|
|
||||||
return BigOps.div(accountFundsWei, BigOps.fromNumber(1, 18))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns {haveEnoughForFees, haveEnoughFunds} and true in case of error not to block request
|
|
||||||
function checkFundsStatus(maxFees, l1GasFee, address, chainId, mainNetChainId, value) {
|
|
||||||
const BigOps = SQUtils.AmountsArithmetic
|
|
||||||
let valueEth = BigOps.fromString(value)
|
|
||||||
let haveEnoughForFees = true
|
|
||||||
let haveEnoughFunds = true
|
|
||||||
|
|
||||||
let token = SQUtils.ModelUtils.getByKey(root.groupedAccountAssetsModel, "tokensKey", Constants.ethToken)
|
|
||||||
if (!token || !token.balances) {
|
|
||||||
console.error("Error token balances lookup for ETH", SQUtils.ModelUtils.modelToArray(root.groupedAccountAssetsModel))
|
|
||||||
console.error("Looking for tokensKey: ", Constants.ethToken)
|
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
|
||||||
}
|
|
||||||
|
|
||||||
let chainBalance = getBalanceInEth(token.balances, address, chainId)
|
|
||||||
if (!chainBalance) {
|
|
||||||
console.error("Error fetching chain balance")
|
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
|
||||||
}
|
|
||||||
haveEnoughFunds = BigOps.cmp(chainBalance, valueEth) >= 0
|
|
||||||
if (haveEnoughFunds) {
|
|
||||||
chainBalance = BigOps.sub(chainBalance, valueEth)
|
|
||||||
|
|
||||||
if (chainId == mainNetChainId) {
|
|
||||||
const finalFees = BigOps.sum(maxFees, l1GasFee)
|
|
||||||
let feesEth = BigOps.div(finalFees, BigOps.fromNumber(1, 9))
|
|
||||||
haveEnoughForFees = BigOps.cmp(chainBalance, feesEth) >= 0
|
|
||||||
} else {
|
|
||||||
const feesChain = BigOps.div(maxFees, BigOps.fromNumber(1, 9))
|
|
||||||
const haveEnoughOnChain = BigOps.cmp(chainBalance, feesChain) >= 0
|
|
||||||
|
|
||||||
const mainBalance = getBalanceInEth(token.balances, address, mainNetChainId)
|
|
||||||
if (!mainBalance) {
|
|
||||||
console.error("Error fetching mainnet balance")
|
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
|
||||||
}
|
|
||||||
const feesMain = BigOps.div(l1GasFee, BigOps.fromNumber(1, 9))
|
|
||||||
const haveEnoughOnMain = BigOps.cmp(mainBalance, feesMain) >= 0
|
|
||||||
|
|
||||||
haveEnoughForFees = haveEnoughOnChain && haveEnoughOnMain
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
haveEnoughForFees = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
|
||||||
}
|
|
||||||
|
|
||||||
property int selectedFeesMode: Constants.FeesMode.Medium
|
|
||||||
|
|
||||||
function getFeesForFeesMode(feesObj) {
|
|
||||||
if (!(feesObj.hasOwnProperty("maxFeePerGasL") &&
|
|
||||||
feesObj.hasOwnProperty("maxFeePerGasM") &&
|
|
||||||
feesObj.hasOwnProperty("maxFeePerGasH"))) {
|
|
||||||
throw new Error("inappropriate fees object provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (d.selectedFeesMode) {
|
|
||||||
case Constants.FeesMode.Low:
|
|
||||||
return feesObj.maxFeePerGasL
|
|
||||||
case Constants.FeesMode.Medium:
|
|
||||||
return feesObj.maxFeePerGasM
|
|
||||||
case Constants.FeesMode.High:
|
|
||||||
return feesObj.maxFeePerGasH
|
|
||||||
default:
|
|
||||||
throw new Error("unknown selected mode")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property var feesSubscriptions: []
|
|
||||||
|
|
||||||
function findSubscriptionIndex(topic, id) {
|
|
||||||
for (let i = 0; i < d.feesSubscriptions.length; i++) {
|
|
||||||
const subscription = d.feesSubscriptions[i]
|
|
||||||
if (subscription.topic == topic && subscription.id == id) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function findChainIndex(chainId) {
|
|
||||||
for (let i = 0; i < feesSubscription.chainIds.length; i++) {
|
|
||||||
if (feesSubscription.chainIds[i] == chainId) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribeForFeeUpdates(topic, id) {
|
|
||||||
const request = requests.findRequest(topic, id)
|
|
||||||
if (request === null) {
|
|
||||||
console.error("Error finding event for subscribing for fees for topic", topic, "id", id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = d.findSubscriptionIndex(topic, id)
|
|
||||||
if (index >= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d.feesSubscriptions.push({
|
|
||||||
topic: topic,
|
|
||||||
id: id,
|
|
||||||
chainId: request.chainId
|
|
||||||
})
|
|
||||||
|
|
||||||
for (let i = 0; i < feesSubscription.chainIds.length; i++) {
|
|
||||||
if (feesSubscription.chainIds == request.chainId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
feesSubscription.chainIds.push(request.chainId)
|
|
||||||
feesSubscription.restart()
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsubscribeForFeeUpdates(topic, id) {
|
|
||||||
const index = d.findSubscriptionIndex(topic, id)
|
|
||||||
if (index == -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const chainId = d.feesSubscriptions[index].chainId
|
|
||||||
d.feesSubscriptions.splice(index, 1)
|
|
||||||
|
|
||||||
const chainIndex = d.findChainIndex(chainId)
|
|
||||||
if (index == -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let found = false
|
|
||||||
for (let i = 0; i < d.feesSubscriptions.length; i++) {
|
|
||||||
if (d.feesSubscriptions[i].chainId == chainId) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
feesSubscription.chainIds.splice(chainIndex, 1)
|
|
||||||
if (feesSubscription.chainIds.length == 0) {
|
|
||||||
feesSubscription.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: feesSubscription
|
|
||||||
|
|
||||||
property var chainIds: []
|
|
||||||
|
|
||||||
interval: 5000
|
|
||||||
repeat: true
|
|
||||||
running: Qt.application.state === Qt.ApplicationActive
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
for (let i = 0; i < chainIds.length; i++) {
|
|
||||||
for (let j = 0; j < d.feesSubscriptions.length; j++) {
|
|
||||||
let subscription = d.feesSubscriptions[j]
|
|
||||||
if (subscription.chainId == chainIds[i]) {
|
|
||||||
let request = requests.findRequest(subscription.topic, subscription.id)
|
|
||||||
if (request === null) {
|
|
||||||
console.error("Error updating fees for topic", subscription.topic, "id", subscription.id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
d.updateFeesParamsToPassedObj(request)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
delete txObj.gasLimit
|
||||||
|
delete txObj.gasPrice
|
||||||
|
delete txObj.gas
|
||||||
|
delete txObj.type
|
||||||
}
|
}
|
||||||
|
// Remove nonce from txObj to be auto-filled by the wallet
|
||||||
|
delete txObj.nonce
|
||||||
|
return txObj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -296,12 +296,12 @@ QtObject {
|
||||||
}
|
}
|
||||||
case methods.signTypedData_v4.name: {
|
case methods.signTypedData_v4.name: {
|
||||||
const stringPayload = methods.signTypedData_v4.getMessageFromData(data)
|
const stringPayload = methods.signTypedData_v4.getMessageFromData(data)
|
||||||
payload = JSON.stringify(JSON.parse(stringPayload), null, 2)
|
payload = JSON.parse(stringPayload)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case methods.signTypedData.name: {
|
case methods.signTypedData.name: {
|
||||||
const stringPayload = methods.signTypedData.getMessageFromData(data)
|
const stringPayload = methods.signTypedData.getMessageFromData(data)
|
||||||
payload = JSON.stringify(JSON.parse(stringPayload), null, 2)
|
payload = JSON.parse(stringPayload)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case methods.signTransaction.name:
|
case methods.signTransaction.name:
|
||||||
|
@ -332,23 +332,35 @@ QtObject {
|
||||||
function parseTransaction(tx, hexToDec) {
|
function parseTransaction(tx, hexToDec) {
|
||||||
let parsedTransaction = Object.assign({}, tx)
|
let parsedTransaction = Object.assign({}, tx)
|
||||||
if (parsedTransaction.hasOwnProperty("value")) {
|
if (parsedTransaction.hasOwnProperty("value")) {
|
||||||
parsedTransaction.value = hexToEth(parsedTransaction.value, hexToDec).toString()
|
parsedTransaction.value = hexToEth(parsedTransaction.value, hexToDec)
|
||||||
}
|
|
||||||
if (parsedTransaction.hasOwnProperty("maxFeePerGas")) {
|
|
||||||
parsedTransaction.maxFeePerGas = hexToGwei(parsedTransaction.maxFeePerGas, hexToDec).toString()
|
|
||||||
}
|
|
||||||
if (parsedTransaction.hasOwnProperty("maxPriorityFeePerGas")) {
|
|
||||||
parsedTransaction.maxPriorityFeePerGas = hexToGwei(parsedTransaction.maxPriorityFeePerGas, hexToDec).toString()
|
|
||||||
}
|
|
||||||
if (parsedTransaction.hasOwnProperty("gasPrice")) {
|
|
||||||
parsedTransaction.gasPrice = hexToGwei(parsedTransaction.gasPrice, hexToDec)
|
|
||||||
}
|
}
|
||||||
if (parsedTransaction.hasOwnProperty("gasLimit")) {
|
if (parsedTransaction.hasOwnProperty("gasLimit")) {
|
||||||
parsedTransaction.gasLimit = parseInt(hexToDec(parsedTransaction.gasLimit))
|
parsedTransaction.gasLimit = parseInt(hexToDec(parsedTransaction.gasLimit))
|
||||||
}
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("gas")) {
|
||||||
|
parsedTransaction.gas = parseInt(hexToDec(parsedTransaction.gas))
|
||||||
|
}
|
||||||
if (parsedTransaction.hasOwnProperty("nonce")) {
|
if (parsedTransaction.hasOwnProperty("nonce")) {
|
||||||
parsedTransaction.nonce = parseInt(hexToDec(parsedTransaction.nonce))
|
parsedTransaction.nonce = parseInt(hexToDec(parsedTransaction.nonce))
|
||||||
}
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("from")) {
|
||||||
|
parsedTransaction.from = parsedTransaction.from
|
||||||
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("to")) {
|
||||||
|
parsedTransaction.to = parsedTransaction.to
|
||||||
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("data")) {
|
||||||
|
parsedTransaction.data = parsedTransaction.data
|
||||||
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("maxFeePerGas")) {
|
||||||
|
delete parsedTransaction.maxFeePerGas
|
||||||
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("maxPriorityFeePerGas")) {
|
||||||
|
delete parsedTransaction.maxPriorityFeePerGas
|
||||||
|
}
|
||||||
|
if (parsedTransaction.hasOwnProperty("gasPrice")) {
|
||||||
|
delete parsedTransaction.gasPrice
|
||||||
|
}
|
||||||
return parsedTransaction
|
return parsedTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ QObject {
|
||||||
|
|
||||||
/// maps to Constants.TransactionEstimatedTime values
|
/// maps to Constants.TransactionEstimatedTime values
|
||||||
property int estimatedTimeCategory: 0
|
property int estimatedTimeCategory: 0
|
||||||
|
signal expired()
|
||||||
|
|
||||||
function isExpired() {
|
function isExpired() {
|
||||||
return !!expirationTimestamp && expirationTimestamp > 0 && Math.floor(Date.now() / 1000) >= expirationTimestamp
|
return !!expirationTimestamp && expirationTimestamp > 0 && Math.floor(Date.now() / 1000) >= expirationTimestamp
|
||||||
|
@ -54,6 +55,7 @@ QObject {
|
||||||
|
|
||||||
function setExpired() {
|
function setExpired() {
|
||||||
expirationTimestamp = Math.floor(Date.now() / 1000)
|
expirationTimestamp = Math.floor(Date.now() / 1000)
|
||||||
|
expired()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActive() {
|
function setActive() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ SequentialAnimation {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var target: null
|
property var target: null
|
||||||
|
property string targetProperty: "color"
|
||||||
property color fromColor: Theme.palette.directColor1
|
property color fromColor: Theme.palette.directColor1
|
||||||
property color toColor: Theme.palette.getColor(fromColor, 0.1)
|
property color toColor: Theme.palette.getColor(fromColor, 0.1)
|
||||||
property int duration: 500 // in milliseconds
|
property int duration: 500 // in milliseconds
|
||||||
|
@ -14,7 +15,7 @@ SequentialAnimation {
|
||||||
|
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
target: root.target
|
target: root.target
|
||||||
property: "color"
|
property: root.targetProperty
|
||||||
from: root.fromColor
|
from: root.fromColor
|
||||||
to: root.toColor
|
to: root.toColor
|
||||||
duration: root.duration
|
duration: root.duration
|
||||||
|
@ -22,7 +23,7 @@ SequentialAnimation {
|
||||||
|
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
target: root.target
|
target: root.target
|
||||||
property: "color"
|
property: root.targetProperty
|
||||||
from: root.toColor
|
from: root.toColor
|
||||||
to: root.fromColor
|
to: root.fromColor
|
||||||
duration: root.duration
|
duration: root.duration
|
||||||
|
|
|
@ -38,6 +38,7 @@ SignTransactionModalBase {
|
||||||
required property string cryptoFees
|
required property string cryptoFees
|
||||||
required property string estimatedTime
|
required property string estimatedTime
|
||||||
required property bool hasFees
|
required property bool hasFees
|
||||||
|
required property bool estimatedTimeLoading
|
||||||
|
|
||||||
property bool enoughFundsForTransaction: true
|
property bool enoughFundsForTransaction: true
|
||||||
property bool enoughFundsForFees: false
|
property bool enoughFundsForFees: false
|
||||||
|
@ -98,15 +99,17 @@ SignTransactionModalBase {
|
||||||
objectName: "footerFiatFeesText"
|
objectName: "footerFiatFeesText"
|
||||||
text: formatBigNumber(root.fiatFees, root.currentCurrency)
|
text: formatBigNumber(root.fiatFees, root.currentCurrency)
|
||||||
loading: root.feesLoading && root.hasFees
|
loading: root.feesLoading && root.hasFees
|
||||||
customColor: !root.hasFees || root.enoughFundsForFees ? Theme.palette.directColor1 : Theme.palette.dangerColor1
|
|
||||||
elide: Qt.ElideMiddle
|
elide: Qt.ElideMiddle
|
||||||
Binding on text {
|
Binding on text {
|
||||||
when: !root.hasFees
|
when: !root.hasFees
|
||||||
value: qsTr("No fees")
|
value: qsTr("No fees")
|
||||||
}
|
}
|
||||||
|
Binding on customColor {
|
||||||
|
value: !root.hasFees || root.enoughFundsForFees ? Theme.palette.directColor1 : Theme.palette.dangerColor1
|
||||||
|
}
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (text === "") {
|
if (text === "" || loading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
maxFeesAnimation.restart()
|
maxFeesAnimation.restart()
|
||||||
|
@ -115,6 +118,8 @@ SignTransactionModalBase {
|
||||||
AnimatedText {
|
AnimatedText {
|
||||||
id: maxFeesAnimation
|
id: maxFeesAnimation
|
||||||
target: maxFees
|
target: maxFees
|
||||||
|
targetProperty: "customColor"
|
||||||
|
running: !maxFees.loading && root.hasFees
|
||||||
fromColor: maxFees.customColor
|
fromColor: maxFees.customColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,10 +136,10 @@ SignTransactionModalBase {
|
||||||
id: estimatedTime
|
id: estimatedTime
|
||||||
objectName: "footerEstimatedTime"
|
objectName: "footerEstimatedTime"
|
||||||
text: root.estimatedTime
|
text: root.estimatedTime
|
||||||
loading: root.feesLoading
|
loading: root.estimatedTimeLoading
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (text === "") {
|
if (text === "" || loading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
estimatedTimeAnimation.restart()
|
estimatedTimeAnimation.restart()
|
||||||
|
@ -143,6 +148,8 @@ SignTransactionModalBase {
|
||||||
AnimatedText {
|
AnimatedText {
|
||||||
id: estimatedTimeAnimation
|
id: estimatedTimeAnimation
|
||||||
target: estimatedTime
|
target: estimatedTime
|
||||||
|
targetProperty: "customColor"
|
||||||
|
running: !estimatedTime.loading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,10 +210,12 @@ SignTransactionModalBase {
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
font.pixelSize: Theme.additionalTextSize
|
font.pixelSize: Theme.additionalTextSize
|
||||||
loading: root.feesLoading
|
loading: root.feesLoading
|
||||||
customColor: root.enoughFundsForFees ? Theme.palette.directColor1 : Theme.palette.dangerColor1
|
|
||||||
|
|
||||||
|
Binding on customColor {
|
||||||
|
value: root.enoughFundsForFees ? Theme.palette.directColor1 : Theme.palette.dangerColor1
|
||||||
|
}
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (text === "") {
|
if (text === "" || loading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fiatFeesAnimation.restart()
|
fiatFeesAnimation.restart()
|
||||||
|
@ -215,6 +224,7 @@ SignTransactionModalBase {
|
||||||
AnimatedText {
|
AnimatedText {
|
||||||
id: fiatFeesAnimation
|
id: fiatFeesAnimation
|
||||||
target: fiatFees
|
target: fiatFees
|
||||||
|
targetProperty: "customColor"
|
||||||
fromColor: fiatFees.customColor
|
fromColor: fiatFees.customColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,11 +235,13 @@ SignTransactionModalBase {
|
||||||
text: formatBigNumber(root.cryptoFees, Constants.ethToken)
|
text: formatBigNumber(root.cryptoFees, Constants.ethToken)
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
font.pixelSize: Theme.additionalTextSize
|
font.pixelSize: Theme.additionalTextSize
|
||||||
customColor: root.enoughFundsForFees ? Theme.palette.baseColor1 : Theme.palette.dangerColor1
|
|
||||||
loading: root.feesLoading
|
loading: root.feesLoading
|
||||||
|
|
||||||
|
Binding on customColor {
|
||||||
|
value: root.enoughFundsForFees ? Theme.palette.baseColor1 : Theme.palette.dangerColor1
|
||||||
|
}
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (text === "") {
|
if (text === "" || loading) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cryptoFeesAnimation.restart()
|
cryptoFeesAnimation.restart()
|
||||||
|
@ -239,6 +251,8 @@ SignTransactionModalBase {
|
||||||
id: cryptoFeesAnimation
|
id: cryptoFeesAnimation
|
||||||
target: cryptoFees
|
target: cryptoFees
|
||||||
fromColor: cryptoFees.customColor
|
fromColor: cryptoFees.customColor
|
||||||
|
targetProperty: "customColor"
|
||||||
|
running: !maxFees.loading && root.hasFees
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,10 @@ QObject {
|
||||||
|
|
||||||
signal signingResult(string topic, string id, string data)
|
signal signingResult(string topic, string id, string data)
|
||||||
|
|
||||||
|
signal estimatedTimeResponse(string topic, int timeCategory, bool success)
|
||||||
|
signal suggestedFeesResponse(string topic, var suggestedFeesJsonObj, bool success)
|
||||||
|
signal estimatedGasResponse(string topic, string gasEstimate, bool success)
|
||||||
|
|
||||||
function addWalletConnectSession(sessionJson) {
|
function addWalletConnectSession(sessionJson) {
|
||||||
return controller.addWalletConnectSession(sessionJson)
|
return controller.addWalletConnectSession(sessionJson)
|
||||||
}
|
}
|
||||||
|
@ -80,14 +84,24 @@ QObject {
|
||||||
|
|
||||||
// Empty maxFeePerGas will fetch the current chain's maxFeePerGas
|
// Empty maxFeePerGas will fetch the current chain's maxFeePerGas
|
||||||
// Returns ui/imports/utils -> Constants.TransactionEstimatedTime values
|
// Returns ui/imports/utils -> Constants.TransactionEstimatedTime values
|
||||||
function getEstimatedTime(chainId, maxFeePerGasHex) {
|
function requestEstimatedTime(topic, chainId, maxFeePerGasHex) {
|
||||||
return controller.getEstimatedTime(chainId, maxFeePerGasHex)
|
controller.requestEstimatedTime(topic, chainId, maxFeePerGasHex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns nim's SuggestedFeesDto; see src/app_service/service/transaction/dto.nim
|
// Returns nim's SuggestedFeesDto; see src/app_service/service/transaction/dto.nim
|
||||||
// Returns all value initialized to 0 if error
|
// Returns all value initialized to 0 if error
|
||||||
function getSuggestedFees(chainId) {
|
function requestSuggestedFees(topic, chainId) {
|
||||||
return JSON.parse(controller.getSuggestedFeesJson(chainId))
|
controller.requestSuggestedFeesJson(topic, chainId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestGasEstimate(topic, chainId, txObj) {
|
||||||
|
try {
|
||||||
|
let tx = prepareTxForStatusGo(txObj)
|
||||||
|
controller.requestGasEstimate(topic, chainId, JSON.stringify(tx))
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to prepare tx for status-go", e)
|
||||||
|
root.estimatedGasResponse(topic, "", false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function signTransaction(topic, id, address, chainId, password, txObj) {
|
function signTransaction(topic, id, address, chainId, password, txObj) {
|
||||||
|
@ -150,5 +164,24 @@ QObject {
|
||||||
function onSigningResultReceived(topic, id, data) {
|
function onSigningResultReceived(topic, id, data) {
|
||||||
root.signingResult(topic, id, data)
|
root.signingResult(topic, id, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEstimatedTimeResponse(topic, timeCategory) {
|
||||||
|
root.estimatedTimeResponse(topic, timeCategory, !!timeCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSuggestedFeesResponse(topic, suggestedFeesJson) {
|
||||||
|
try {
|
||||||
|
const jsonObj = JSON.parse(suggestedFeesJson)
|
||||||
|
root.suggestedFeesResponse(topic, jsonObj, true)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse suggestedFeesJson", e)
|
||||||
|
root.suggestedFeesResponse(topic, {}, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEstimatedGasResponse(topic, gasEstimate) {
|
||||||
|
root.estimatedGasResponse(topic, gasEstimate, !!gasEstimate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue