diff --git a/src/app/wallet/view.nim b/src/app/wallet/view.nim index 1d059542ec..0474ad7e55 100644 --- a/src/app/wallet/view.nim +++ b/src/app/wallet/view.nim @@ -275,16 +275,22 @@ QtObject: except RpcException as e: result = $(%* { "error": %* { "message": %e.msg }}) - proc sendTransaction*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, password: string): string {.slot.} = + proc transactionWasSent*(self: WalletView, txResult: string) {.signal.} + + proc transactionSent(self: WalletView, txResult: string) {.slot.} = + self.transactionWasSent(txResult) + + proc sendTransaction*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, password: string) {.slot.} = + let tokens = self.status.wallet.tokens try: - var response = "" - if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace: - response = self.status.wallet.sendTokenTransaction(from_addr, to, assetAddress, value, gas, gasPrice, password) - else: - response = self.status.wallet.sendTransaction(from_addr, to, value, gas, gasPrice, password) - result = $(%* { "result": %response }) + spawnAndSend(self, "transactionSent") do: + $(%status_wallet.sendTransaction(tokens, from_addr, to, assetAddress, value, gas, gasPrice, password)) except RpcException as e: - result = $(%* { "error": %* { "message": %e.msg }}) + self.transactionSent($(%*{ + "error": %*{ + "message": e.msg + } + })) proc getDefaultAccount*(self: WalletView): string {.slot.} = self.currentAccount.address diff --git a/src/status/libstatus/wallet.nim b/src/status/libstatus/wallet.nim index 12e7f6fce5..0bca3fbd51 100644 --- a/src/status/libstatus/wallet.nim +++ b/src/status/libstatus/wallet.nim @@ -1,5 +1,8 @@ -import json, json, options, json_serialization, stint, chronicles +import json, json, options, json_serialization, stint, chronicles, tables import core, types, utils, strutils, strformat +import eth/common/utils as eth_utils +import accounts/constants as constants +import ./coder as coder from nim_status import validateMnemonic, startWallet import ../wallet/account import ./eth/contracts as contractMethods @@ -65,6 +68,46 @@ proc getTransfersByAddress*(address: string): seq[types.Transaction] = let msg = getCurrentExceptionMsg() error "Failed getting wallet account transactions", msg +proc sendTransaction*(tx: EthSend, password: string): RpcResponse = + let responseStr = core.sendTransaction($(%tx), password) + result = Json.decode(responseStr, RpcResponse) + if not result.error.isNil: + raise newException(RpcException, "Error sending transaction: " & result.error.message) + + trace "Transaction sent succesfully", hash=result + +proc sendTransaction*(tokens: JsonNode, source, to, assetAddress, value, gas, gasPrice, password: string): RpcResponse = + var + weiValue = eth2Wei(parseFloat(value), 18) # ETH + data = "" + toAddr = eth_utils.parseAddress(to) + let gasPriceInWei = gwei2Wei(parseFloat(gasPrice)) + + # TODO: this code needs to be tested with testnet assets (to be implemented in + # a future PR + if assetAddress != constants.ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace: + let + token = tokens.first("address", assetAddress) + contract = contractMethods.getContract("snt") + transfer = coder.Transfer(to: toAddr, value: eth2Wei(parseFloat(value), token["decimals"].getInt)) + weiValue = 0.u256 + let transferThing = contract.methods["transfer"] + data = transferThing.encodeAbi(transfer) + toAddr = eth_utils.parseAddress(assetAddress) + + let tx = EthSend( + source: eth_utils.parseAddress(source), + to: toAddr.some, + gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some), + gasPrice: (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some), + value: weiValue.some, + data: data + ) + try: + result = sendTransaction(tx, password) + except RpcException as e: + raise + proc getBalance*(address: string): string = let payload = %* [address, "latest"] let response = parseJson(callPrivateRPC("eth_getBalance", payload)) diff --git a/ui/app/AppLayouts/Wallet/SendModal.qml b/ui/app/AppLayouts/Wallet/SendModal.qml index 600135701e..a9025eb564 100644 --- a/ui/app/AppLayouts/Wallet/SendModal.qml +++ b/ui/app/AppLayouts/Wallet/SendModal.qml @@ -8,6 +8,10 @@ import "../../../shared/status" import "./components" ModalPopup { + property bool sending: false + property string oldButtonText + property int oldButtonWidth + id: root //% "Send" @@ -36,26 +40,18 @@ ModalPopup { } function sendTransaction() { - let responseStr = walletModel.sendTransaction(selectFromAccount.selectedAccount.address, + sending = true + oldButtonText = btnNext.label + oldButtonWidth = btnNext.width + btnNext.label = "" + btnNext.width = oldButtonWidth + walletModel.sendTransaction(selectFromAccount.selectedAccount.address, selectRecipient.selectedRecipient.address, txtAmount.selectedAsset.address, txtAmount.selectedAmount, gasSelector.selectedGasLimit, gasSelector.selectedGasPrice, transactionSigner.enteredPassword) - let response = JSON.parse(responseStr) - - if (response.error) { - if (response.error.message.includes("could not decrypt key with given password")){ - transactionSigner.validationError = qsTr("Wrong password") - return - } - sendingError.text = response.error.message - return sendingError.open() - } - - sendingSuccess.text = qsTr("Transaction sent to the blockchain. You can watch the progress on Etherscan: %2/%1").arg(response.result).arg(walletModel.etherscanLink) - sendingSuccess.open() } TransactionStackView { @@ -258,7 +254,7 @@ ModalPopup { id: btnNext anchors.right: parent.right label: qsTr("Next") - disabled: !stack.currentGroup.isValid || stack.currentGroup.isPending + disabled: !stack.currentGroup.isValid || stack.currentGroup.isPending || root.sending onClicked: { const validity = stack.currentGroup.validate() if (validity.isValid && !validity.isPending) { @@ -268,6 +264,42 @@ ModalPopup { stack.next() } } + + Loader { + active: root.sending + sourceComponent: loadingImage + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + + Component { + id: loadingImage + LoadingImage {} + } + } + Connections { + target: walletModel + onTransactionWasSent: { + try { + root.sending = false + btnNext.label = root.oldButtonText + let response = JSON.parse(txResult) + + if (response.error) { + if (response.error.message.includes("could not decrypt key with given password")){ + transactionSigner.validationError = qsTr("Wrong password") + return + } + sendingError.text = response.error.message + return sendingError.open() + } + + sendingSuccess.text = qsTr("Transaction sent to the blockchain. You can watch the progress on Etherscan: %2/%1").arg(response.result).arg(walletModel.etherscanLink) + sendingSuccess.open() + } catch (e) { + console.error('WOW', e) + } + } } } }