diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 266498ec0d..6a3b278046 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -142,7 +142,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.walletAccountService = wallet_account_service.newService(statusFoundation.events, result.settingsService, result.accountsService, result.tokenService) result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool, - result.walletAccountService) + result.walletAccountService, result.ethService, result.networkService, result.settingsService) result.bookmarkService = bookmark_service.newService() result.profileService = profile_service.newService() result.stickersService = stickers_service.newService( diff --git a/src/app/modules/main/wallet_section/transactions/controller.nim b/src/app/modules/main/wallet_section/transactions/controller.nim index 05f88eeb7b..853d416be3 100644 --- a/src/app/modules/main/wallet_section/transactions/controller.nim +++ b/src/app/modules/main/wallet_section/transactions/controller.nim @@ -75,3 +75,6 @@ method getAccountByAddress*(self: Controller, address: string): WalletAccountDto method loadTransactions*(self: Controller, address: string, toBlock: Uint256, limit: int = 20, loadMore: bool = false) = self.transactionService.loadTransactions(address, toBlock, limit, loadMore) + +method estimateGas*(self: Controller, from_addr: string, to: string, assetAddress: string, value: string, data: string): string = + result = self.transactionService.estimateGas(from_addr, to, assetAddress, value, data) diff --git a/src/app/modules/main/wallet_section/transactions/controller_interface.nim b/src/app/modules/main/wallet_section/transactions/controller_interface.nim index d094dd0f6f..1f4f2fdfaf 100644 --- a/src/app/modules/main/wallet_section/transactions/controller_interface.nim +++ b/src/app/modules/main/wallet_section/transactions/controller_interface.nim @@ -29,6 +29,9 @@ method loadTransactions*(self: AccessInterface, address: string, toBlock: Uint25 method setTrxHistoryResult*(self: AccessInterface, historyJSON: string) {.base.} = raise newException(ValueError, "No implementation available") +method estimateGas*(self: AccessInterface, from_addr: string, to: string, assetAddress: string, value: string, data: string): string {.base.} = + raise newException(ValueError, "No implementation available") + type ## Abstract class (concept) which must be implemented by object/s used in this ## module. diff --git a/src/app/modules/main/wallet_section/transactions/io_interface.nim b/src/app/modules/main/wallet_section/transactions/io_interface.nim index 645e2efa4e..ef5e87c630 100644 --- a/src/app/modules/main/wallet_section/transactions/io_interface.nim +++ b/src/app/modules/main/wallet_section/transactions/io_interface.nim @@ -40,6 +40,9 @@ method setHistoryFetchState*(self: AccessInterface, addresses: seq[string], isFe method setIsNonArchivalNode*(self: AccessInterface, isNonArchivalNode: bool) {.base.} = raise newException(ValueError, "No implementation available") +method estimateGas*(self: AccessInterface, from_addr: string, to: string, assetAddress: string, value: string, data: string): string {.base.} = + raise newException(ValueError, "No implementation available") + # View Delegate Interface # Delegate for the view must be declared here due to use of QtObject and multi # inheritance, which is not well supported in Nim. diff --git a/src/app/modules/main/wallet_section/transactions/module.nim b/src/app/modules/main/wallet_section/transactions/module.nim index b960f35f79..45bd0c4d07 100644 --- a/src/app/modules/main/wallet_section/transactions/module.nim +++ b/src/app/modules/main/wallet_section/transactions/module.nim @@ -80,5 +80,8 @@ method setTrxHistoryResult*(self: Module, transactions: seq[TransactionDto], add method setHistoryFetchState*(self: Module, addresses: seq[string], isFetching: bool) = self.view.setHistoryFetchStateForAccounts(addresses, isFetching) +method estimateGas*(self: Module, from_addr: string, to: string, assetAddress: string, value: string, data: string): string = + result = self.controller.estimateGas(from_addr, to, assetAddress, value, data) + method setIsNonArchivalNode*(self: Module, isNonArchivalNode: bool) = self.view.setIsNonArchivalNode(isNonArchivalNode) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/transactions/view.nim b/src/app/modules/main/wallet_section/transactions/view.nim index fcf528b2b8..57fd70f15d 100644 --- a/src/app/modules/main/wallet_section/transactions/view.nim +++ b/src/app/modules/main/wallet_section/transactions/view.nim @@ -103,3 +103,6 @@ QtObject: read = getIsNonArchivalNode notify = isNonArchivalNodeChanged + + proc estimateGas*(self: View, from_addr: string, to: string, assetAddress: string, value: string, data: string): string {.slot.} = + result = self.delegate.estimateGas(from_addr, to, assetAddress, value, data) diff --git a/src/app_service/service/ens/utils.nim b/src/app_service/service/ens/utils.nim index 938a7ed3b0..7cb73cee2c 100644 --- a/src/app_service/service/ens/utils.nim +++ b/src/app_service/service/ens/utils.nim @@ -186,8 +186,16 @@ proc owner*(username: string): string = return "" result = "0x" & ownerAddr.substr(26) -proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", isEIP1559Enabled = false, - maxPriorityFeePerGas = "", maxFeePerGas = "", data = ""): TransactionDataDto = +proc buildTransaction*( + source: Address, + value: Uint256, + gas = "", + gasPrice = "", + isEIP1559Enabled = false, + maxPriorityFeePerGas = "", + maxFeePerGas = "", + data = "" + ): TransactionDataDto = result = TransactionDataDto( source: source, value: value.some, diff --git a/src/app_service/service/eth/dto/contract.nim b/src/app_service/service/eth/dto/contract.nim index f2d2b090f9..939115b029 100644 --- a/src/app_service/service/eth/dto/contract.nim +++ b/src/app_service/service/eth/dto/contract.nim @@ -71,4 +71,7 @@ proc tokenName*(contract: ContractDto): string = getTokenString(contract, "name") proc tokenSymbol*(contract: ContractDto): string = - getTokenString(contract, "symbol") \ No newline at end of file + getTokenString(contract, "symbol") + +proc getMethod*(contract: ContractDto, methodName: string): MethodDto = + return contract.methods["methodName"] \ No newline at end of file diff --git a/src/app_service/service/provider/service.nim b/src/app_service/service/provider/service.nim index 9daaafff2b..b900d50cb4 100644 --- a/src/app_service/service/provider/service.nim +++ b/src/app_service/service/provider/service.nim @@ -9,7 +9,10 @@ import ../ens/utils as ens_utils import service_interface import status/permissions as status_go_permissions import status/core as status_go_core +import status/eth as status_eth import ../../common/utils as status_utils +import ../eth/utils as eth_utils +import ../eth/dto/transaction as transaction_data_dto from stew/base32 import nil from stew/base58 import nil import stew/byteutils @@ -134,17 +137,13 @@ proc process(self: Service, data: Web3SendAsyncReadOnly): string = var success: bool var errorMessage = "" - var response = "" + var response: RpcResponse[JsonNode] var validInput: bool = true - - # TODO: use the transaction service to send the trx - - #[ - let eip1559Enabled = self.wallet.isEIP1559Enabled() + let eip1559Enabled: bool = self.settingsService.isEIP1559Enabled() try: - validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy") + eth_utils.validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy") except Exception as e: validInput = false success = false @@ -152,39 +151,43 @@ proc process(self: Service, data: Web3SendAsyncReadOnly): string = if validInput: # TODO make this async - response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, eip1559Enabled, selectedTipLimit, selectedOverallLimit, password, success, txData) - errorMessage = if not success: - if response == "": - "web3-response-error" + var tx = ens_utils.buildTransaction( + parseAddress(fromAddress), + eth2Wei(parseFloat(value), 18), + selectedGasLimit, + selectedGasPrice, + eip1559Enabled, + selectedTipLimit, + selectedOverallLimit, + txData + ) + tx.to = parseAddress(to).some + + try: + # TODO: use the transaction service to send the trx + let json: JsonNode = %tx + response = status_eth.sendTransaction($json, password) + if response.error != nil: + success = false + errorMessage = response.error.message else: - response - else: - "" - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": errorMessage, - "result": { - "jsonrpc": "2.0", - "id": data.payload.id, - "result": if (success): response else: "" - } - } - - ]# - # TODO: delete this: - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": "", - "result": { - "jsonrpc": "2.0", - "id": data.payload.id, - "result": "" - } - } + success = true + errorMessage = "" + except Exception as e: + error "Error sending transaction", msg = e.msg + errorMessage = e.msg + success = false + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": errorMessage, + "result": { + "jsonrpc": "2.0", + "id": data.payload.id, + "result": if (success): response.result.getStr else: "" + } + } except Exception as e: error "Error sending the transaction", msg = e.msg diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index cb236c904a..512b217cde 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -1,12 +1,22 @@ -import NimQml, chronicles, sequtils, sugar, stint, strutils, json +import NimQml, chronicles, sequtils, sugar, stint, strutils, json, strformat import status/transactions as transactions import status/wallet as status_wallet +import status/eth + +import ../ens/utils as ens_utils +from ../../common/account_constants import ZERO_ADDRESS import ../../../app/core/[main] import ../../../app/core/tasks/[qt, threadpool] import ../wallet_account/service as wallet_account_service +import ../eth/service as eth_service +import ../network/service as network_service +import ../settings/service as settings_service +import ../eth/dto/transaction as transaction_data_dto +import ../eth/dto/[contract, method_dto] import ./dto as transaction_dto import ../eth/utils as eth_utils +import ../../common/conversion export transaction_dto @@ -37,16 +47,29 @@ QtObject: events: EventEmitter threadpool: ThreadPool walletAccountService: wallet_account_service.ServiceInterface + ethService: eth_service.ServiceInterface + networkService: network_service.ServiceInterface + settingsService: settings_service.ServiceInterface proc delete*(self: Service) = self.QObject.delete - proc newService*(events: EventEmitter, threadpool: ThreadPool, walletAccountService: wallet_account_service.ServiceInterface): Service = + proc newService*( + events: EventEmitter, + threadpool: ThreadPool, + walletAccountService: wallet_account_service.ServiceInterface, + ethService: eth_service.ServiceInterface, + networkService: network_service.ServiceInterface, + settingsService: settings_service.ServiceInterface + ): Service = new(result, delete) result.QObject.setup result.events = events result.threadpool = threadpool result.walletAccountService = walletAccountService + result.ethService = ethService + result.networkService = networkService + result.settingsService = settingsService proc init*(self: Service) = discard @@ -118,4 +141,40 @@ QtObject: limit: limit, loadMore: loadMore ) - self.threadpool.start(arg) \ No newline at end of file + self.threadpool.start(arg) + + proc estimateGas*(self: Service, from_addr: string, to: string, assetAddress: string, value: string, data: string = ""): string {.slot.} = + var response: RpcResponse[JsonNode] + try: + if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace: + var tx = buildTokenTransaction( + parseAddress(from_addr), + parseAddress(assetAddress) + ) + let networkType = self.settingsService.getCurrentNetwork().toNetworkType() + let network = self.networkService.getNetwork(networkType) + let contract = self.ethService.findErc20Contract(network.chainId, assetAddress) + if contract == nil: + raise newException(ValueError, fmt"Could not find ERC-20 contract with address '{assetAddress}' for the current network") + + let transfer = Transfer(to: parseAddress(to), value: conversion.eth2Wei(parseFloat(value), contract.decimals)) + let methodThing = contract.getMethod("transfer") + var success: bool + let gas = methodThing.estimateGas(tx, transfer, success) + + result = $(%* { "result": gas, "success": success }) + else: + var tx = ens_utils.buildTransaction( + parseAddress(from_addr), + eth2Wei(parseFloat(value), 18), + data = data + ) + tx.to = parseAddress(to).some + response = eth.estimateGas(%*[%tx]) + + let res = fromHex[int](response.result.getStr) + result = $(%* { "result": %res, "success": true }) + except Exception as e: + error "Error estimating gas", msg = e.msg + result = $(%* { "result": "-1", "success": false, "error": { "message": e.msg } }) + diff --git a/ui/app/AppLayouts/stores/RootStore.qml b/ui/app/AppLayouts/stores/RootStore.qml index 2b554c7038..e909872f6d 100644 --- a/ui/app/AppLayouts/stores/RootStore.qml +++ b/ui/app/AppLayouts/stores/RootStore.qml @@ -54,4 +54,17 @@ QtObject { function generateIdenticon(pk) { return globalUtils.generateIdenticon(pk); } + + property string currentCurrency: walletSection.currentCurrency + function estimateGas(from_addr, to, assetAddress, value, data) { + return walletSectionTransactions.estimateGas(from_addr, to, assetAddress, value, data) + } + // TODO change this to use a better store once it is moved out of the ENS module + property string gasPrice: profileSectionStore.ensUsernamesStore.gasPrice + function getFiatValue(balance, cryptoSymbo, fiatSymbol) { + return profileSectionStore.ensUsernamesStore.getFiatValue(balance, cryptoSymbo, fiatSymbol) + } + function getGasEthValue(gweiValue, gasLimit) { + return profileSectionStore.ensUsernamesStore.getGasEthValue(gweiValue, gasLimit) + } } diff --git a/ui/imports/shared/popups/SendModal.qml b/ui/imports/shared/popups/SendModal.qml index 3818fb4b47..a42244683f 100644 --- a/ui/imports/shared/popups/SendModal.qml +++ b/ui/imports/shared/popups/SendModal.qml @@ -155,26 +155,26 @@ ModalPopup { width: stack.width property var estimateGas: Backpressure.debounce(gasSelector, 600, function() { // Not Refactored Yet -// if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && -// selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && -// txtAmount.selectedAsset && txtAmount.selectedAsset.address && -// txtAmount.selectedAmount)) return + if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && + selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && + txtAmount.selectedAsset && txtAmount.selectedAsset.address && + txtAmount.selectedAmount)) return -// let gasEstimate = JSON.parse(walletModel.gasView.estimateGas( -// selectFromAccount.selectedAccount.address, -// selectRecipient.selectedRecipient.address, -// txtAmount.selectedAsset.address, -// txtAmount.selectedAmount, -// "")) + let gasEstimate = JSON.parse(walletModel.gasView.estimateGas( + selectFromAccount.selectedAccount.address, + selectRecipient.selectedRecipient.address, + txtAmount.selectedAsset.address, + txtAmount.selectedAmount, + "")) -// if (!gasEstimate.success) { -// //% "Error estimating gas: %1" -// console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message)) -// return -// } + if (!gasEstimate.success) { + //% "Error estimating gas: %1" + console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message)) + return + } -// selectedGasLimit = gasEstimate.result -// defaultGasLimit = selectedGasLimit + selectedGasLimit = gasEstimate.result + defaultGasLimit = selectedGasLimit }) } GasValidator { @@ -256,7 +256,6 @@ ModalPopup { StatusButton { id: btnNext - anchors.right: parent.right //% "Next" text: qsTrId("next") enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending diff --git a/ui/imports/shared/popups/SignTransactionModal.qml b/ui/imports/shared/popups/SignTransactionModal.qml index e8bf0f9210..90b134607c 100644 --- a/ui/imports/shared/popups/SignTransactionModal.qml +++ b/ui/imports/shared/popups/SignTransactionModal.qml @@ -105,7 +105,7 @@ StatusModal { // //% "Authorize %1 %2" // return qsTrId("authorize--1--2").arg(approveData.amount).arg(approveData.symbol) // } - return qsTrId("command-button-send"); + return qsTr("Send"); } //% "Continue" footerText: qsTrId("continue") @@ -119,7 +119,7 @@ StatusModal { id: selectFromAccount // Not Refactored Yet // accounts: root.store.walletModelInst.accountsView.accounts -// currency: root.store.walletModelInst.balanceView.defaultCurrency + currency: root.store.currentCurrency width: stack.width selectedAccount: root.selectedAccount //% "Choose account" @@ -150,37 +150,35 @@ StatusModal { GasSelector { id: gasSelector anchors.topMargin: Style.current.padding - // Not Refactored Yet -// gasPrice: parseFloat(root.store.walletModelInst.gasView.gasPrice) -// getGasEthValue: root.store.walletModelInst.gasView.getGasEthValue -// getFiatValue: root.store.walletModelInst.balanceView.getFiatValue -// defaultCurrency: root.store.walletModelInst.balanceView.defaultCurrency + gasPrice: parseFloat(root.store.gasPrice) + getGasEthValue: root.store.getGasEthValue + getFiatValue: root.store.getFiatValue + defaultCurrency: root.store.currentCurrency width: stack.width property var estimateGas: Backpressure.debounce(gasSelector, 600, function() { - // Not Refactored Yet -// if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && -// selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && -// root.selectedAsset && root.selectedAsset.address && -// root.selectedAmount)) { -// selectedGasLimit = 250000 -// defaultGasLimit = selectedGasLimit -// return -// } + if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && + selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && + root.selectedAsset && root.selectedAsset.address && + root.selectedAmount)) { + selectedGasLimit = 250000 + defaultGasLimit = selectedGasLimit + return + } -// let gasEstimate = JSON.parse(root.store.walletModelInst.gasView.estimateGas( -// selectFromAccount.selectedAccount.address, -// selectRecipient.selectedRecipient.address, -// root.selectedAsset.address, -// root.selectedAmount, -// trxData)) + let gasEstimate = JSON.parse(root.store.estimateGas( + selectFromAccount.selectedAccount.address, + selectRecipient.selectedRecipient.address, + root.selectedAsset.address, + root.selectedAmount, + trxData)) -// if (!gasEstimate.success) { -// let message = qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message) -// root.openGasEstimateErrorPopup(message); -// } -// selectedGasLimit = gasEstimate.result -// defaultGasLimit = selectedGasLimit + if (!gasEstimate.success) { + let message = qsTr("Error estimating gas: %1").arg(gasEstimate.error.message) + root.openGasEstimateErrorPopup(message); + } + selectedGasLimit = gasEstimate.result + defaultGasLimit = selectedGasLimit }) } GasValidator { @@ -217,8 +215,7 @@ StatusModal { toAccount: selectRecipient.selectedRecipient asset: root.selectedAsset amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount } - // Not Refactored Yet -// currency: root.store.walletModelInst.balanceView.defaultCurrency + currency: root.store.currentCurrency isFromEditable: false trxData: root.trxData isGasEditable: true