From ad7e2df78a6a60ba888342a567ddb45da80f199e Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Fri, 13 Sep 2024 13:39:12 +0200 Subject: [PATCH] chore: improvements of the sending route generated by the router process Closes #14636 --- .../signals/remote_signals/signal_type.nim | 4 + .../core/signals/remote_signals/wallet.nim | 23 + src/app/core/signals/signals_manager.nim | 6 +- .../main/wallet_section/send/controller.nim | 43 +- .../main/wallet_section/send/io_interface.nim | 18 +- .../main/wallet_section/send/module.nim | 142 +++-- .../modules/main/wallet_section/send/view.nim | 44 +- .../wallet_connect/controller.nim | 5 +- src/app_service/common/wallet_constants.nim | 3 + .../service/transaction/async_tasks.nim | 1 - .../transaction/router_transactions_dto.nim | 142 +++++ .../service/transaction/service.nim | 496 +++++------------- .../service/wallet_connect/service.nim | 11 +- src/backend/common.nim | 16 + src/backend/eth.nim | 15 +- src/backend/transactions.nim | 45 +- src/backend/wallet.nim | 13 +- storybook/pages/SwapInputPanelPage.qml | 2 +- storybook/pages/SwapModalPage.qml | 10 +- storybook/qmlTests/tests/tst_SwapModal.qml | 76 ++- .../Wallet/popups/swap/SwapModal.qml | 24 - .../Wallet/popups/swap/SwapModalAdaptor.qml | 18 +- ui/app/AppLayouts/Wallet/stores/SwapStore.qml | 12 +- ui/imports/shared/popups/send/SendModal.qml | 4 +- .../shared/stores/send/TransactionStore.qml | 8 +- vendor/status-go | 2 +- 26 files changed, 564 insertions(+), 619 deletions(-) create mode 100644 src/app_service/service/transaction/router_transactions_dto.nim create mode 100644 src/backend/common.nim diff --git a/src/app/core/signals/remote_signals/signal_type.nim b/src/app/core/signals/remote_signals/signal_type.nim index a0218d73f3..c6f3b5c876 100644 --- a/src/app/core/signals/remote_signals/signal_type.nim +++ b/src/app/core/signals/remote_signals/signal_type.nim @@ -6,6 +6,10 @@ type SignalType* {.pure.} = enum ## Wallet Signals Wallet = "wallet" WalletSignTransactions = "wallet.sign.transactions" + WalletRouterSendingTransactionsStarted = "wallet.router.sending-transactions-started" + WalletRouterSignTransactions = "wallet.router.sign-transactions" + WalletRouterTransactionsSent = "wallet.router.transactions-sent" + WalletTransactionStatusChanged = "wallet.transaction.status-changed" WalletSuggestedRoutes = "wallet.suggested.routes" NodeReady = "node.ready" NodeCrashed = "node.crashed" diff --git a/src/app/core/signals/remote_signals/wallet.nim b/src/app/core/signals/remote_signals/wallet.nim index 0798a9a7e0..805565e82c 100644 --- a/src/app/core/signals/remote_signals/wallet.nim +++ b/src/app/core/signals/remote_signals/wallet.nim @@ -4,6 +4,7 @@ import base import signal_type import app_service/service/transaction/dtoV2 +import app_service/service/transaction/router_transactions_dto const SignTransactionsEventType* = "sing-transactions" @@ -25,6 +26,10 @@ type WalletSignal* = ref object of Signal error*: string errorCode*: string updatedPrices*: Table[string, float64] + routerTransactionsSendingDetails*: SendDetailsDto + routerTransactionsForSigning*: RouterTransactionsForSigningDto + routerSentTransactions*: RouterSentTransactionsDto + transactionStatusChange*: TransactionStatusChange proc fromEvent*(T: type WalletSignal, signalType: SignalType, jsonSignal: JsonNode): WalletSignal = result = WalletSignal() @@ -54,6 +59,24 @@ proc fromEvent*(T: type WalletSignal, signalType: SignalType, jsonSignal: JsonNo for tx in event: result.txHashes.add(tx.getStr) return + if signalType == SignalType.WalletRouterSignTransactions: + if event.kind != JObject: + return + result.routerTransactionsForSigning = toRouterTransactionsForSigningDto(event) + return + if signalType == SignalType.WalletRouterTransactionsSent: + if event.kind != JObject: + return + result.routerSentTransactions = toRouterSentTransactionsDto(event) + return + if signalType == SignalType.WalletRouterSendingTransactionsStarted: + result.routerTransactionsSendingDetails = toSendDetailsDto(event) + return + if signalType == SignalType.WalletTransactionStatusChanged: + if event.kind != JObject: + return + result.transactionStatusChange = toTransactionStatusChange(event) + return if signalType == SignalType.WalletSuggestedRoutes: try: if event.contains("Uuid"): diff --git a/src/app/core/signals/signals_manager.nim b/src/app/core/signals/signals_manager.nim index ec6b86ddc8..55b1b83bbe 100644 --- a/src/app/core/signals/signals_manager.nim +++ b/src/app/core/signals/signals_manager.nim @@ -81,8 +81,12 @@ QtObject: of SignalType.WhisperFilterAdded: WhisperFilterSignal.fromEvent(jsonSignal) of SignalType.Wallet, SignalType.WalletSignTransactions, + SignalType.WalletRouterSendingTransactionsStarted, + SignalType.WalletRouterSignTransactions, + SignalType.WalletRouterTransactionsSent, + SignalType.WalletTransactionStatusChanged, SignalType.WalletSuggestedRoutes: - WalletSignal.fromEvent(signalType, jsonSignal) + WalletSignal.fromEvent(signalType, jsonSignal) of SignalType.NodeReady, SignalType.NodeCrashed, SignalType.NodeStarted, diff --git a/src/app/modules/main/wallet_section/send/controller.nim b/src/app/modules/main/wallet_section/send/controller.nim index 5affa77bf2..a39345e093 100644 --- a/src/app/modules/main/wallet_section/send/controller.nim +++ b/src/app/modules/main/wallet_section/send/controller.nim @@ -1,5 +1,5 @@ import Tables -import uuids, chronicles, options +import uuids, chronicles import io_interface import app_service/service/wallet_account/service as wallet_account_service import app_service/service/network/service as network_service @@ -13,7 +13,6 @@ import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_m import app/modules/shared/wallet_utils import app/modules/shared_models/currency_amount -import app/core/signals/types import app/core/eventemitter logScope: @@ -56,11 +55,11 @@ proc delete*(self: Controller) = proc init*(self: Controller) = self.events.on(SIGNAL_TRANSACTION_SENT) do(e:Args): let args = TransactionSentArgs(e) - self.delegate.transactionWasSent(args.chainId, args.txHash, args.uuid, args.error) + self.delegate.transactionWasSent(args.uuid, args.chainId, args.approvalTx, args.txHash, args.error) self.events.on(SIGNAL_OWNER_TOKEN_SENT) do(e:Args): let args = OwnerTokenSentArgs(e) - self.delegate.transactionWasSent(args.chainId, args.txHash, args.uuid, "") + self.delegate.transactionWasSent(args.uuid, args.chainId, approvalTx = false, args.txHash, error = "") self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args): let args = SharedKeycarModuleArgs(e) @@ -72,13 +71,13 @@ proc init*(self: Controller) = let args = SuggestedRoutesArgs(e) self.delegate.suggestedRoutesReady(args.uuid, args.suggestedRoutes, args.errCode, args.errDescription) - self.events.on(SignalType.WalletSignTransactions.event) do(e:Args): - var data = WalletSignal(e) - self.delegate.prepareSignaturesForTransactions(data.txHashes) + self.events.on(SIGNAL_SIGN_ROUTER_TRANSACTIONS) do(e:Args): + var data = RouterTransactionsForSigningArgs(e) + self.delegate.prepareSignaturesForTransactions(data.data) - self.events.on(SIGNAL_TRANSACTION_SENDING_COMPLETE) do(e:Args): - let args = TransactionMinedArgs(e) - self.delegate.transactionSendingComplete(args.transactionHash, args.success) + self.events.on(SIGNAL_TRANSACTION_STATUS_CHANGED) do(e:Args): + let args = TransactionStatusArgs(e) + self.delegate.transactionSendingComplete(args.data.hash, args.data.status) proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = return self.walletAccountService.getWalletAccounts() @@ -115,6 +114,7 @@ proc suggestedRoutes*(self: Controller, accountFrom: string, accountTo: string, token: string, + tokenIsOwnerToken: bool, amountIn: string, toToken: string = "", amountOut: string = "", @@ -122,23 +122,20 @@ proc suggestedRoutes*(self: Controller, disabledToChainIDs: seq[int] = @[], lockedInAmounts: Table[string, string] = initTable[string, string](), extraParamsTable: Table[string, string] = initTable[string, string]()) = - self.transactionService.suggestedRoutes(uuid, sendType, accountFrom, accountTo, token, amountIn, toToken, amountOut, + self.transactionService.suggestedRoutes(uuid, sendType, accountFrom, accountTo, token, tokenIsOwnerToken, amountIn, toToken, amountOut, disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) proc stopSuggestedRoutesAsyncCalculation*(self: Controller) = self.transactionService.stopSuggestedRoutesAsyncCalculation() -proc transfer*(self: Controller, from_addr: string, to_addr: string, assetKey: string, toAssetKey: string, - uuid: string, selectedRoutes: seq[TransactionPathDto], password: string, sendType: SendType, - usePassword: bool, doHashing: bool, tokenName: string, isOwnerToken: bool, - slippagePercentage: Option[float]) = - self.transactionService.transfer(from_addr, to_addr, assetKey, toAssetKey, uuid, selectedRoutes, password, sendType, - usePassword, doHashing, tokenName, isOwnerToken, slippagePercentage) +proc buildTransactionsFromRoute*(self: Controller, uuid: string, slippagePercentage: float): string = + return self.transactionService.buildTransactionsFromRoute(uuid, slippagePercentage) -proc proceedWithTransactionsSignatures*(self: Controller, fromAddr: string, toAddr: string, - fromTokenKey: string, toTokenKey: string, uuid: string, signatures: TransactionsSignatures, - selectedRoutes: seq[TransactionPathDto], sendType: SendType) = - self.transactionService.proceedWithTransactionsSignatures(fromAddr, toAddr, fromTokenKey, toTokenKey, uuid, signatures, selectedRoutes, sendType) +proc signMessage*(self: Controller, address: string, hashedPassword: string, hashedMessage: string): tuple[res: string, err: string] = + return self.transactionService.signMessage(address, hashedPassword, hashedMessage) + +proc sendRouterTransactionsWithSignatures*(self: Controller, uuid: string, signatures: TransactionsSignatures): string = + return self.transactionService.sendRouterTransactionsWithSignatures(uuid, signatures) proc areTestNetworksEnabled*(self: Controller): bool = return self.walletAccountService.areTestNetworksEnabled() @@ -159,7 +156,7 @@ proc connectKeycardReponseSignal(self: Controller) = let currentFlow = self.keycardService.getCurrentFlow() if currentFlow != KCSFlowType.Sign: error "trying to use keycard in the other than the signing a transaction flow" - self.delegate.transactionWasSent() + self.delegate.transactionWasSent(uuid = "", chainId = 0, approvalTx = false, txHash = "", error = "trying to use keycard in the other than the signing a transaction flow") return self.delegate.onTransactionSigned(args.flowType, args.flowEvent) @@ -172,4 +169,4 @@ proc runSignFlow*(self: Controller, pin, bip44Path, txHash: string) = self.keycardService.startSignFlow(bip44Path, txHash, pin) proc getChainsWithNoGasFromError*(self: Controller, errCode: string, errDescription: string): Table[int, string] = - return self.walletAccountService.getChainsWithNoGasFromError(errCode, errDescription) + return self.walletAccountService.getChainsWithNoGasFromError(errCode, errDescription) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/send/io_interface.nim b/src/app/modules/main/wallet_section/send/io_interface.nim index 1d8bb5c11e..7bccd6b03f 100644 --- a/src/app/modules/main/wallet_section/send/io_interface.nim +++ b/src/app/modules/main/wallet_section/send/io_interface.nim @@ -1,6 +1,7 @@ -import Tables, options +import Tables import app/modules/shared_models/currency_amount import app_service/service/transaction/dto +import app_service/service/transaction/router_transactions_dto import app_service/service/network/network_item import app/modules/shared_models/collectibles_model as collectibles from app_service/service/keycard/service import KeycardEvent @@ -27,6 +28,7 @@ method suggestedRoutes*(self: AccessInterface, accountFrom: string, accountTo: string, token: string, + tokenIsOwnerToken: bool, amountIn: string, toToken: string = "", amountOut: string = "", @@ -42,19 +44,13 @@ method stopUpdatesForSuggestedRoute*(self: AccessInterface) {.base.} = method suggestedRoutesReady*(self: AccessInterface, uuid: string, suggestedRoutes: SuggestedRoutesDto, errCode: string, errDescription: string) {.base.} = raise newException(ValueError, "No implementation available") -method authenticateAndTransfer*(self: AccessInterface, from_addr: string, to_addr: string, assetKey: string, - toAssetKey: string, uuid: string, sendType: SendType, selectedTokenName: string, selectedTokenIsOwnerToken: bool) {.base.} = - raise newException(ValueError, "No implementation available") - -method authenticateAndTransferWithPaths*(self: AccessInterface, from_addr: string, to_addr: string, assetKey: string, - toAssetKey: string, uuid: string, sendType: SendType, selectedTokenName: string, selectedTokenIsOwnerToken: bool, rawPaths: string, - slippagePercentage: Option[float]) {.base.} = +method authenticateAndTransferV2*(self: AccessInterface, fromAddr: string, uuid: string, slippagePercentage: float) {.base.} = raise newException(ValueError, "No implementation available") method onUserAuthenticated*(self: AccessInterface, password: string, pin: string) {.base.} = raise newException(ValueError, "No implementation available") -method transactionWasSent*(self: AccessInterface, chainId: int = 0, txHash, uuid, error: string = "") {.base.} = +method transactionWasSent*(self: AccessInterface, uuid: string, chainId: int = 0, approvalTx: bool = false, txHash: string = "", error: string = "") {.base.} = raise newException(ValueError, "No implementation available") method viewDidLoad*(self: AccessInterface) {.base.} = @@ -78,7 +74,7 @@ method getCollectiblesModel*(self: AccessInterface): collectibles.Model {.base.} method splitAndFormatAddressPrefix*(self: AccessInterface, text : string, updateInStore: bool): string {.base.} = raise newException(ValueError, "No implementation available") -method prepareSignaturesForTransactions*(self: AccessInterface, txHashes: seq[string]) {.base.} = +method prepareSignaturesForTransactions*(self:AccessInterface, txForSigning: RouterTransactionsForSigningDto) {.base.} = raise newException(ValueError, "No implementation available") method onTransactionSigned*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} = @@ -87,7 +83,7 @@ method onTransactionSigned*(self: AccessInterface, keycardFlowType: string, keyc method hasGas*(self: AccessInterface, accountAddress: string, chainId: int, nativeGasSymbol: string, requiredGas: float): bool {.base.} = raise newException(ValueError, "No implementation available") -method transactionSendingComplete*(self: AccessInterface, txHash: string, success: bool) {.base.} = +method transactionSendingComplete*(self: AccessInterface, txHash: string, status: string) {.base.} = raise newException(ValueError, "No implementation available") method getNetworkItem*(self: AccessInterface, chainId: int): NetworkItem {.base.} = diff --git a/src/app/modules/main/wallet_section/send/module.nim b/src/app/modules/main/wallet_section/send/module.nim index f9eef6b96e..1f0354294f 100644 --- a/src/app/modules/main/wallet_section/send/module.nim +++ b/src/app/modules/main/wallet_section/send/module.nim @@ -1,9 +1,12 @@ -import tables, NimQml, sequtils, sugar, stint, strutils, chronicles, options +import tables, NimQml, sequtils, sugar, stint, strutils, chronicles import ./io_interface, ./view, ./controller, ./network_route_item, ./transaction_routes, ./suggested_route_item, ./suggested_route_model, ./gas_estimate_item, ./gas_fees_item, ./network_route_model import ../io_interface as delegate_interface import app/global/global_singleton +import app/global/utils import app/core/eventemitter +import app_service/common/utils +import app_service/common/wallet_constants import app_service/service/wallet_account/service as wallet_account_service import app_service/service/network/service as network_service import app_service/service/currency/service as currency_service @@ -11,8 +14,6 @@ import app_service/service/transaction/service as transaction_service import app_service/service/keycard/service as keycard_service import app_service/service/keycard/constants as keycard_constants import app_service/service/transaction/dto -import app_service/service/transaction/dtoV2 -import app_service/service/transaction/dto_conversion import app/modules/shared_models/currency_amount import app_service/service/network/network_item as network_service_item @@ -36,7 +37,7 @@ type TmpSendTransactionDetails = object resolvedSignatures: TransactionsSignatures tokenName: string isOwnerToken: bool - slippagePercentage: Option[float] + slippagePercentage: float type Module* = ref object of io_interface.AccessInterface @@ -48,6 +49,7 @@ type moduleLoaded: bool tmpSendTransactionDetails: TmpSendTransactionDetails tmpPin: string + tmpPassword: string tmpTxHashBeingProcessed: string # Forward declaration @@ -77,6 +79,13 @@ method delete*(self: Module) = self.view.delete self.controller.delete +proc clearTmpData(self: Module) = + self.tmpPin = "" + self.tmpPassword = "" + self.tmpTxHashBeingProcessed = "" + self.tmpSendTransactionDetails = TmpSendTransactionDetails() + writeStackTrace() + proc convertSendToNetworkToNetworkItem(self: Module, network: SendToNetwork): NetworkRouteItem = result = initNetworkRouteItem( network.chainId, @@ -174,18 +183,10 @@ method getNetworkItem*(self: Module, chainId: int): network_service_item.Network return nil return networks[0] -method authenticateAndTransfer*(self: Module, fromAddr: string, toAddr: string, assetKey: string, toAssetKey: string, uuid: string, - sendType: SendType, selectedTokenName: string, selectedTokenIsOwnerToken: bool) = - self.tmpSendTransactionDetails.fromAddr = fromAddr - self.tmpSendTransactionDetails.toAddr = toAddr - self.tmpSendTransactionDetails.assetKey = assetKey - self.tmpSendTransactionDetails.toAssetKey = toAssetKey +method authenticateAndTransferV2*(self: Module, fromAddr: string, uuid: string, slippagePercentage: float) = self.tmpSendTransactionDetails.uuid = uuid - self.tmpSendTransactionDetails.sendType = sendType - self.tmpSendTransactionDetails.fromAddrPath = "" + self.tmpSendTransactionDetails.slippagePercentage = slippagePercentage self.tmpSendTransactionDetails.resolvedSignatures.clear() - self.tmpSendTransactionDetails.tokenName = selectedTokenName - self.tmpSendTransactionDetails.isOwnerToken = selectedTokenIsOwnerToken let kp = self.controller.getKeypairByAccountAddress(fromAddr) if kp.migratedToKeycard(): @@ -193,34 +194,36 @@ method authenticateAndTransfer*(self: Module, fromAddr: string, toAddr: string, if accounts.len != 1: error "cannot resolve selected account to send from among known keypair accounts" return - self.tmpSendTransactionDetails.fromAddrPath = accounts[0].path self.controller.authenticate(kp.keyUid) else: self.controller.authenticate() -method authenticateAndTransferWithPaths*(self: Module, fromAddr: string, toAddr: string, assetKey: string, toAssetKey: string, uuid: string, - sendType: SendType, selectedTokenName: string, selectedTokenIsOwnerToken: bool, rawPaths: string, slippagePercentage: Option[float]) = - # Temporary until transaction service rework is completed - let pathsV2 = rawPaths.toTransactionPathsDtoV2() - let pathsV1 = pathsV2.convertToOldRoute().addFirstSimpleBridgeTxFlag() - - self.tmpSendTransactionDetails.paths = pathsV1 - self.tmpSendTransactionDetails.slippagePercentage = slippagePercentage - self.authenticateAndTransfer(fromAddr, toAddr, assetKey, toAssetKey, uuid, sendType, selectedTokenName, selectedTokenIsOwnerToken) - method onUserAuthenticated*(self: Module, password: string, pin: string) = if password.len == 0: - self.transactionWasSent(chainId = 0, txHash = "", uuid = self.tmpSendTransactionDetails.uuid, error = authenticationCanceled) + self.transactionWasSent(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = authenticationCanceled) + self.clearTmpData() else: self.tmpPin = pin - let doHashing = self.tmpPin.len == 0 - let usePassword = self.tmpSendTransactionDetails.fromAddrPath.len == 0 - self.controller.transfer( - self.tmpSendTransactionDetails.fromAddr, self.tmpSendTransactionDetails.toAddr, - self.tmpSendTransactionDetails.assetKey, self.tmpSendTransactionDetails.toAssetKey, self.tmpSendTransactionDetails.uuid, - self.tmpSendTransactionDetails.paths, password, self.tmpSendTransactionDetails.sendType, usePassword, doHashing, - self.tmpSendTransactionDetails.tokenName, self.tmpSendTransactionDetails.isOwnerToken, self.tmpSendTransactionDetails.slippagePercentage - ) + self.tmpPassword = password + let err = self.controller.buildTransactionsFromRoute(self.tmpSendTransactionDetails.uuid, self.tmpSendTransactionDetails.slippagePercentage) + if err.len > 0: + self.transactionWasSent(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = err) + self.clearTmpData() + +proc sendSignedTransactions*(self: Module) = + try: + # check if all transactions are signed + for _, (r, s, v) in self.tmpSendTransactionDetails.resolvedSignatures.pairs: + if r.len == 0 or s.len == 0 or v.len == 0: + raise newException(CatchableError, "not all transactions are signed") + + let err = self.controller.sendRouterTransactionsWithSignatures(self.tmpSendTransactionDetails.uuid, self.tmpSendTransactionDetails.resolvedSignatures) + if err.len > 0: + raise newException(CatchableError, "sending transaction failed: " & err) + except Exception as e: + error "sendSignedTransactions failed: ", msg=e.msg + self.transactionWasSent(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = e.msg) + self.clearTmpData() proc signOnKeycard(self: Module) = self.tmpTxHashBeingProcessed = "" @@ -234,35 +237,70 @@ proc signOnKeycard(self: Module) = self.controller.runSignFlow(self.tmpPin, self.tmpSendTransactionDetails.fromAddrPath, txForKcFlow) break if self.tmpTxHashBeingProcessed.len == 0: - self.controller.proceedWithTransactionsSignatures(self.tmpSendTransactionDetails.fromAddr, self.tmpSendTransactionDetails.toAddr, - self.tmpSendTransactionDetails.assetKey, self.tmpSendTransactionDetails.toAssetKey, self.tmpSendTransactionDetails.uuid, - self.tmpSendTransactionDetails.resolvedSignatures, self.tmpSendTransactionDetails.paths, self.tmpSendTransactionDetails.sendType) + self.sendSignedTransactions() + self.clearTmpData() -method prepareSignaturesForTransactions*(self: Module, txHashes: seq[string]) = - if txHashes.len == 0: - error "no transaction hashes to be signed" - return - for h in txHashes: - self.tmpSendTransactionDetails.resolvedSignatures[h] = ("", "", "") - self.signOnKeycard() +proc getRSVFromSignature(self: Module, signature: string): (string, string, string) = + let finalSignature = singletonInstance.utils.removeHexPrefix(signature) + if finalSignature.len != SIGNATURE_LEN: + return ("", "", "") + let r = finalSignature[0..63] + let s = finalSignature[64..127] + let v = finalSignature[128..129] + return (r, s, v) + +method prepareSignaturesForTransactions*(self:Module, txForSigning: RouterTransactionsForSigningDto) = + var res = "" + try: + if txForSigning.sendDetails.uuid != self.tmpSendTransactionDetails.uuid: + raise newException(CatchableError, "preparing signatures for transactions are not matching the initial request") + if txForSigning.signingDetails.hashes.len == 0: + raise newException(CatchableError, "no transaction hashes to be signed") + if txForSigning.signingDetails.keyUid == "" or txForSigning.signingDetails.address == "" or txForSigning.signingDetails.addressPath == "": + raise newException(CatchableError, "preparing signatures for transactions failed") + + if txForSigning.signingDetails.signOnKeycard: + self.tmpSendTransactionDetails.fromAddrPath = txForSigning.signingDetails.addressPath + for h in txForSigning.signingDetails.hashes: + self.tmpSendTransactionDetails.resolvedSignatures[h] = ("", "", "") + self.signOnKeycard() + else: + let finalPassword = hashPassword(self.tmpPassword) + for h in txForSigning.signingDetails.hashes: + self.tmpSendTransactionDetails.resolvedSignatures[h] = ("", "", "") + var + signature = "" + err: string + (signature, err) = self.controller.signMessage(txForSigning.signingDetails.address, finalPassword, h) + if err.len > 0: + raise newException(CatchableError, "signing transaction failed: " & err) + self.tmpSendTransactionDetails.resolvedSignatures[h] = self.getRSVFromSignature(signature) + self.sendSignedTransactions() + except Exception as e: + error "signMessageWithCallback failed: ", msg=e.msg + self.transactionWasSent(uuid = txForSigning.sendDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = e.msg) + self.clearTmpData() method onTransactionSigned*(self: Module, keycardFlowType: string, keycardEvent: KeycardEvent) = if keycardFlowType != keycard_constants.ResponseTypeValueKeycardFlowResult: - error "unexpected error while keycard signing transaction" + let err = "unexpected error while keycard signing transaction" + error "error", err=err + self.transactionWasSent(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = err) + self.clearTmpData() return self.tmpSendTransactionDetails.resolvedSignatures[self.tmpTxHashBeingProcessed] = (keycardEvent.txSignature.r, keycardEvent.txSignature.s, keycardEvent.txSignature.v) self.signOnKeycard() -method transactionWasSent*(self: Module, chainId: int, txHash, uuid, error: string) = +method transactionWasSent*(self: Module, uuid: string, chainId: int = 0, approvalTx: bool = false, txHash: string = "", error: string = "") = if txHash.len == 0: - self.view.sendTransactionSentSignal(chainId = 0, txHash = "", uuid = self.tmpSendTransactionDetails.uuid, error) + self.view.sendTransactionSentSignal(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error) return - self.view.sendTransactionSentSignal(chainId, txHash, uuid, error) + self.view.sendTransactionSentSignal(uuid, chainId, approvalTx, txHash, error) method suggestedRoutesReady*(self: Module, uuid: string, suggestedRoutes: SuggestedRoutesDto, errCode: string, errDescription: string) = self.tmpSendTransactionDetails.paths = suggestedRoutes.best - self.tmpSendTransactionDetails.slippagePercentage = none(float) + self.tmpSendTransactionDetails.slippagePercentage = 0 let paths = suggestedRoutes.best.map(x => self.convertTransactionPathDtoToSuggestedRouteItem(x)) let suggestedRouteModel = newSuggestedRouteModel() suggestedRouteModel.setItems(paths) @@ -286,6 +324,7 @@ method suggestedRoutes*(self: Module, accountFrom: string, accountTo: string, token: string, + tokenIsOwnerToken: bool, amountIn: string, toToken: string = "", amountOut: string = "", @@ -299,6 +338,7 @@ method suggestedRoutes*(self: Module, accountFrom, accountTo, token, + tokenIsOwnerToken, amountIn, toToken, amountOut, @@ -362,5 +402,5 @@ method splitAndFormatAddressPrefix*(self: Module, text : string, updateInStore: editedText = "

" & editedText & "

" return editedText -method transactionSendingComplete*(self: Module, txHash: string, success: bool) = - self.view.sendtransactionSendingCompleteSignal(txHash, success) +method transactionSendingComplete*(self: Module, txHash: string, status: string) = + self.view.sendtransactionSendingCompleteSignal(txHash, status) diff --git a/src/app/modules/main/wallet_section/send/view.nim b/src/app/modules/main/wallet_section/send/view.nim index c60de6f575..e5056241d8 100644 --- a/src/app/modules/main/wallet_section/send/view.nim +++ b/src/app/modules/main/wallet_section/send/view.nim @@ -1,5 +1,4 @@ -import NimQml, Tables, json, sequtils, strutils, stint, options, chronicles -import uuids +import NimQml, Tables, json, sequtils, strutils, stint, chronicles import ./io_interface, ./network_route_model, ./network_route_item, ./suggested_route_item, ./transaction_routes import app_service/service/network/service as network_service @@ -165,9 +164,9 @@ QtObject: self.fromNetworksRouteModel.setItems(fromNetworks) self.toNetworksRouteModel.setItems(toNetworks) - proc transactionSent*(self: View, chainId: int, txHash: string, uuid: string, error: string) {.signal.} - proc sendTransactionSentSignal*(self: View, chainId: int, txHash: string, uuid: string, error: string) = - self.transactionSent(chainId, txHash, uuid, error) + proc transactionSent*(self: View, uuid: string, chainId: int, approvalTx: bool, txHash: string, error: string) {.signal.} + proc sendTransactionSentSignal*(self: View, uuid: string, chainId: int, approvalTx: bool, txHash: string, error: string) = + self.transactionSent(uuid, chainId, approvalTx, txHash, error) proc parseChainIds(chainIds: string): seq[int] = var parsedChainIds: seq[int] = @[] @@ -175,9 +174,13 @@ QtObject: parsedChainIds.add(chainId.parseInt()) return parsedChainIds - proc authenticateAndTransfer*(self: View, uuid: string) {.slot.} = - self.delegate.authenticateAndTransfer(self.selectedSenderAccountAddress, self.selectedRecipient, self.selectedAssetKey, - self.selectedToAssetKey, uuid, self.sendType, self.selectedTokenName, self.selectedTokenIsOwnerToken) + proc authenticateAndTransfer*(self: View, uuid: string, slippagePercentageString: string) {.slot.} = + var slippagePercentage: float + try: + slippagePercentage = slippagePercentageString.parseFloat() + except: + error "parsing slippage failed", slippage=slippagePercentageString + self.delegate.authenticateAndTransferV2(self.selectedSenderAccountAddress, uuid, slippagePercentage) proc suggestedRoutesReady*(self: View, suggestedRoutes: QVariant, errCode: string, errDescription: string) {.signal.} proc setTransactionRoute*(self: View, routes: TransactionRoutes, errCode: string, errDescription: string) = @@ -186,7 +189,7 @@ QtObject: self.errDescription = errDescription self.suggestedRoutesReady(newQVariant(self.transactionRoutes), errCode, errDescription) - proc suggestedRoutes*(self: View, amountIn: string, amountOut: string, extraParamsJson: string) {.slot.} = + proc suggestedRoutes*(self: View, uuid: string, amountIn: string, amountOut: string, extraParamsJson: string) {.slot.} = var extraParamsTable: Table[string, string] try: if extraParamsJson.len > 0: @@ -201,11 +204,12 @@ QtObject: error "Error parsing extraParamsJson: ", msg=e.msg self.delegate.suggestedRoutes( - $genUUID(), + uuid, self.sendType, self.selectedSenderAccountAddress, self.selectedRecipient, self.selectedAssetKey, + self.selectedTokenIsOwnerToken, amountIn, self.selectedToAssetKey, amountOut, @@ -283,6 +287,7 @@ QtObject: accountFrom, accountTo, token, + self.selectedTokenIsOwnerToken, amountIn, toToken, amountOut, @@ -290,22 +295,9 @@ QtObject: parseChainIds(disabledToChainIDs), lockedInAmountsTable) - proc authenticateAndTransferWithParameters*(self: View, uuid: string, accountFrom: string, accountTo: string, token: string, toToken: string, - sendTypeInt: int, tokenName: string, tokenIsOwnerToken: bool, rawPaths: string, slippagePercentageString: string) {.slot.} = - - let sendType = SendType(sendTypeInt) - - var slippagePercentage: Option[float] - if sendType == SendType.Swap: - if slippagePercentageString.len > 0: - slippagePercentage = slippagePercentageString.parseFloat().some - - self.delegate.authenticateAndTransferWithPaths(accountFrom, accountTo, token, - toToken, uuid, sendType, tokenName, tokenIsOwnerToken, rawPaths, slippagePercentage) - - proc transactionSendingComplete*(self: View, txHash: string, success: bool) {.signal.} - proc sendtransactionSendingCompleteSignal*(self: View, txHash: string, success: bool) = - self.transactionSendingComplete(txHash, success) + proc transactionSendingComplete*(self: View, txHash: string, status: string) {.signal.} + proc sendtransactionSendingCompleteSignal*(self: View, txHash: string, status: string) = + self.transactionSendingComplete(txHash, status) proc setSenderAccount*(self: View, address: string) {.slot.} = self.setSelectedSenderAccountAddress(address) diff --git a/src/app/modules/shared_modules/wallet_connect/controller.nim b/src/app/modules/shared_modules/wallet_connect/controller.nim index bf9169cd77..5913504b75 100644 --- a/src/app/modules/shared_modules/wallet_connect/controller.nim +++ b/src/app/modules/shared_modules/wallet_connect/controller.nim @@ -119,7 +119,10 @@ QtObject: debug "signMessageWithCallback: signing on keycard started successfully" return let finalPassword = self.preparePassword(password) - res = self.service.signMessage(address, finalPassword, message) + var err: string + (res, err) = self.service.signMessage(address, finalPassword, message) + if err.len > 0: + raise newException(CatchableError, "signMessage failed: " & err) except Exception as e: error "signMessageWithCallback failed: ", msg=e.msg callback(topic, id, "", address, res) diff --git a/src/app_service/common/wallet_constants.nim b/src/app_service/common/wallet_constants.nim index 217a1efacc..c943feab1c 100644 --- a/src/app_service/common/wallet_constants.nim +++ b/src/app_service/common/wallet_constants.nim @@ -7,6 +7,9 @@ const STT_CONTRACT_ADDRESS_GOERLI* = "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a" STT_CONTRACT_ADDRESS_SEPOLIA* = "0xE452027cdEF746c7Cd3DB31CB700428b16cD8E51" + SIGNATURE_LEN* = 130 + SIGNATURE_LEN_0X_INCLUDED* = SIGNATURE_LEN + 2 + TX_HASH_LEN* = 32 * 2 TX_HASH_LEN_WITH_PREFIX* = TX_HASH_LEN + 2 diff --git a/src/app_service/service/transaction/async_tasks.nim b/src/app_service/service/transaction/async_tasks.nim index a83d4500de..0f3a24dcd4 100644 --- a/src/app_service/service/transaction/async_tasks.nim +++ b/src/app_service/service/transaction/async_tasks.nim @@ -4,7 +4,6 @@ import stint import ../../../backend/backend as backend -import ../../common/conversion as service_conversion type GetSuggestedRoutesTaskArg* = ref object of QObjectTaskArg diff --git a/src/app_service/service/transaction/router_transactions_dto.nim b/src/app_service/service/transaction/router_transactions_dto.nim new file mode 100644 index 0000000000..c77272592b --- /dev/null +++ b/src/app_service/service/transaction/router_transactions_dto.nim @@ -0,0 +1,142 @@ +import json, stint + +include ../../common/json_utils + +const + TxStatusPending* = "Pending" + TxStatusSuccess* = "Success" + TxStatusFailed* = "Failed" + +type + TransactionStatusChange* = ref object + status*: string + hash*: string + chainId*: int + +type + ErrorResponse* = ref object + details*: string + code*: string +type + SendDetailsDto* = ref object + uuid*: string + sendType*: int + fromAddress*: string + toAddress*: string + fromToken*: string + toToken*: string + fromAmount*: UInt256 # total amount + toAmount*: UInt256 + ownerTokenBeingSent*: bool + errorResponse*: ErrorResponse + username*: string + publicKey*: string + packId*: string + +type + SigningDetails* = ref object + address*: string + addressPath*: string + keyUid*: string + signOnKeycard*: bool + hashes*: seq[string] + +type + RouterTransactionsForSigningDto* = ref object + sendDetails*: SendDetailsDto + signingDetails*: SigningDetails + +type + RouterSentTransaction* = ref object + fromAddress*: string + toAddress*: string + fromChain*: int + toChain*: int + fromToken*: string + toToken*: string + amount*: UInt256 # amount of the transaction + hash*: string + approvalTx*: bool + +type + RouterSentTransactionsDto* = ref object + sendDetails*: SendDetailsDto + sentTransactions*: seq[RouterSentTransaction] + +proc toTransactionStatusChange*(jsonObj: JsonNode): TransactionStatusChange = + result = TransactionStatusChange() + discard jsonObj.getProp("status", result.status) + discard jsonObj.getProp("hash", result.hash) + discard jsonObj.getProp("chainId", result.chainId) + +proc toErrorResponse*(jsonObj: JsonNode): ErrorResponse = + result = ErrorResponse() + if jsonObj.contains("details"): + result.details = jsonObj["details"].getStr + if jsonObj.contains("code"): + result.code = jsonObj["code"].getStr + +proc toSendDetailsDto*(jsonObj: JsonNode): SendDetailsDto = + result = SendDetailsDto() + discard jsonObj.getProp("uuid", result.uuid) + discard jsonObj.getProp("sendType", result.sendType) + discard jsonObj.getProp("fromAddress", result.fromAddress) + discard jsonObj.getProp("toAddress", result.toAddress) + discard jsonObj.getProp("fromToken", result.fromToken) + discard jsonObj.getProp("toToken", result.toToken) + discard jsonObj.getProp("ownerTokenBeingSent", result.ownerTokenBeingSent) + var tmpObj: JsonNode + if jsonObj.getProp("fromAmount", tmpObj): + result.fromAmount = stint.fromHex(UInt256, tmpObj.getStr) + if jsonObj.getProp("toAmount", tmpObj): + result.toAmount = stint.fromHex(UInt256, tmpObj.getStr) + if jsonObj.getProp("errorResponse", tmpObj) and tmpObj.kind == JObject: + result.errorResponse = toErrorResponse(tmpObj) + discard jsonObj.getProp("username", result.username) + discard jsonObj.getProp("publicKey", result.publicKey) + if jsonObj.getProp("packId", tmpObj): + let packId = stint.fromHex(UInt256, tmpObj.getStr) + result.packId = $packId + + +proc toSigningDetails*(jsonObj: JsonNode): SigningDetails = + result = SigningDetails() + discard jsonObj.getProp("address", result.address) + discard jsonObj.getProp("addressPath", result.addressPath) + discard jsonObj.getProp("keyUid", result.keyUid) + discard jsonObj.getProp("signOnKeycard", result.signOnKeycard) + var tmpObj: JsonNode + if jsonObj.getProp("hashes", tmpObj) and tmpObj.kind == JArray: + for tx in tmpObj: + result.hashes.add(tx.getStr) + +proc toRouterTransactionsForSigningDto*(jsonObj: JsonNode): RouterTransactionsForSigningDto = + result = RouterTransactionsForSigningDto() + var tmpObj: JsonNode + if jsonObj.getProp("sendDetails", tmpObj) and tmpObj.kind == JObject: + result.sendDetails = toSendDetailsDto(tmpObj) + if jsonObj.getProp("signingDetails", tmpObj) and tmpObj.kind == JObject: + result.signingDetails = toSigningDetails(tmpObj) + +proc toRouterSentTransaction*(jsonObj: JsonNode): RouterSentTransaction = + result = RouterSentTransaction() + discard jsonObj.getProp("fromAddress", result.fromAddress) + discard jsonObj.getProp("toAddress", result.toAddress) + discard jsonObj.getProp("fromChain", result.fromChain) + discard jsonObj.getProp("toChain", result.toChain) + discard jsonObj.getProp("fromToken", result.fromToken) + discard jsonObj.getProp("toToken", result.toToken) + discard jsonObj.getProp("hash", result.hash) + discard jsonObj.getProp("approvalTx", result.approvalTx) + var tmpObj: JsonNode + if jsonObj.getProp("amount", tmpObj): + result.amount = stint.fromHex(UInt256, tmpObj.getStr) + +proc toRouterSentTransactionsDto*(jsonObj: JsonNode): RouterSentTransactionsDto = + result = RouterSentTransactionsDto() + var tmpObj: JsonNode + if jsonObj.getProp("sendDetails", tmpObj) and tmpObj.kind == JObject: + result.sendDetails = toSendDetailsDto(tmpObj) + if jsonObj.getProp("sentTransactions", tmpObj) and tmpObj.kind == JArray: + for tx in tmpObj: + result.sentTransactions.add(toRouterSentTransaction(tx)) \ No newline at end of file diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 60107a7ac3..416d5b862a 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -4,8 +4,8 @@ import backend/collectibles as collectibles import backend/transactions as transactions import backend/backend import backend/eth +import backend/wallet -import app_service/service/ens/utils as ens_utils import app_service/common/utils as common_utils import app_service/common/types as common_types @@ -19,15 +19,14 @@ import app_service/service/wallet_account/service as wallet_account_service import app_service/service/network/service as network_service import app_service/service/token/service as token_service import app_service/service/settings/service as settings_service -import app_service/service/eth/dto/transaction as transaction_data_dto -import app_service/service/eth/dto/[coder, method_dto] import ./dto as transaction_dto import ./dtoV2 import ./dto_conversion +import ./router_transactions_dto import app_service/service/eth/utils as eth_utils -export transaction_dto +export transaction_dto, router_transactions_dto export transactions.TransactionsSignatures logScope: @@ -37,6 +36,8 @@ include async_tasks include app_service/common/json_utils # Signals which may be emitted by this service: +const SIGNAL_SIGN_ROUTER_TRANSACTIONS* = "signRouterTransactions" +const SIGNAL_SENDING_TRANSACTIONS_STARTED* = "sendingTransactionsStarted" const SIGNAL_TRANSACTION_SENT* = "transactionSent" const SIGNAL_SUGGESTED_ROUTES_READY* = "suggestedRoutesReady" const SIGNAL_HISTORY_NON_ARCHIVAL_NODE* = "historyNonArchivalNode" @@ -44,12 +45,7 @@ const SIGNAL_HISTORY_ERROR* = "historyError" const SIGNAL_TRANSACTION_DECODED* = "transactionDecoded" const SIGNAL_OWNER_TOKEN_SENT* = "ownerTokenSent" const SIGNAL_TRANSACTION_SENDING_COMPLETE* = "transactionSendingComplete" - -const SIMPLE_TX_BRIDGE_NAME = "Transfer" -const HOP_TX_BRIDGE_NAME = "Hop" -const ERC721_TRANSFER_NAME = "ERC721Transfer" -const ERC1155_TRANSFER_NAME = "ERC1155Transfer" -const SWAP_PARASWAP_NAME = "Paraswap" +const SIGNAL_TRANSACTION_STATUS_CHANGED* = "transactionStatusChanged" type TokenTransferMetadata* = object tokenName*: string @@ -119,6 +115,10 @@ type fromAmount*: string toTokenKey*: string toAmount*: string + approvalTx*: bool + username*: string + publicKey*: string + packId*: string type OwnerTokenSentArgs* = ref object of Args @@ -140,6 +140,18 @@ type dataDecoded*: string txHash*: string +type + RouterTransactionsForSigningArgs* = ref object of Args + data*: RouterTransactionsForSigningDto + +type + RouterTransactionsSendingStartedArgs* = ref object of Args + data*: SendDetailsDto + +type + TransactionStatusArgs* = ref object of Args + data*: TransactionStatusChange + QtObject: type Service* = ref object of QObject events: EventEmitter @@ -151,6 +163,7 @@ QtObject: ## Forward declarations proc suggestedRoutesReady(self: Service, uuid: string, route: seq[TransactionPathDtoV2], routeRaw: string, errCode: string, errDescription: string) + proc sendTransactionsSignal(self: Service, sendDetails: SendDetailsDto, sentTransactions: seq[RouterSentTransaction] = @[]) proc delete*(self: Service) = self.QObject.delete @@ -184,6 +197,28 @@ QtObject: self.tokenService.updateTokenPrices(data.updatedPrices) self.suggestedRoutesReady(data.uuid, data.bestRoute, data.bestRouteRaw, data.errorCode, data.error) + self.events.on(SignalType.WalletRouterSendingTransactionsStarted.event) do(e:Args): + var data = WalletSignal(e) + self.events.emit(SIGNAL_SENDING_TRANSACTIONS_STARTED, RouterTransactionsSendingStartedArgs(data: data.routerTransactionsSendingDetails)) + # TODO: the line below is to aling with the old implementation, remove it + self.sendTransactionsSignal(data.routerTransactionsSendingDetails) + + self.events.on(SignalType.WalletRouterSignTransactions.event) do(e:Args): + var data = WalletSignal(e) + if data.routerTransactionsForSigning.sendDetails.errorResponse.isNil and + not data.routerTransactionsForSigning.signingDetails.isNil: + self.events.emit(SIGNAL_SIGN_ROUTER_TRANSACTIONS, RouterTransactionsForSigningArgs(data: data.routerTransactionsForSigning)) + return + self.sendTransactionsSignal(data.routerTransactionsForSigning.sendDetails) + + self.events.on(SignalType.WalletRouterTransactionsSent.event) do(e:Args): + var data = WalletSignal(e) + self.sendTransactionsSignal(data.routerSentTransactions.sendDetails, data.routerSentTransactions.sentTransactions) + + self.events.on(SignalType.WalletTransactionStatusChanged.event) do(e:Args): + var data = WalletSignal(e) + self.events.emit(SIGNAL_TRANSACTION_STATUS_CHANGED, TransactionStatusArgs(data: data.transactionStatusChange)) + self.events.on(PendingTransactionTypeDto.WalletTransfer.event) do(e: Args): try: var receivedData = TransactionMinedArgs(e) @@ -287,372 +322,55 @@ QtObject: ) self.threadpool.start(arg) - proc createApprovalPath*(self: Service, route: TransactionPathDto, from_addr: string, toAddress: Address, gasFees: string): TransactionBridgeDto = - var txData = TransactionDataDto() - let approve = Approve( - to: parseAddress(route.approvalContractAddress), - value: route.approvalAmountRequired, - ) - let data = ERC20_procS.toTable["approve"].encodeAbi(approve) - txData = ens_utils.buildTokenTransaction( - parseAddress(from_addr), - toAddress, - $route.gasAmount, - gasFees, - route.gasFees.eip1559Enabled, - $route.gasFees.maxPriorityFeePerGas, - $route.gasFees.maxFeePerGasM - ) - txData.data = data - - var path = TransactionBridgeDto(bridgeName: SIMPLE_TX_BRIDGE_NAME, chainID: route.fromNetwork.chainId) - path.transferTx = txData - return path - - proc createPath*(self: Service, route: TransactionPathDto, txData: TransactionDataDto, tokenSymbol: string, to_addr: string): TransactionBridgeDto = - var path = TransactionBridgeDto(bridgeName: route.bridgeName, chainID: route.fromNetwork.chainId) - var hopTx = TransactionDataDto() - var cbridgeTx = TransactionDataDto() - var eRC721TransferTx = TransactionDataDto() - var eRC1155TransferTx = TransactionDataDto() - var swapTx = TransactionDataDto() - - if(route.bridgeName == SIMPLE_TX_BRIDGE_NAME): - path.transferTx = txData - elif(route.bridgeName == HOP_TX_BRIDGE_NAME): - hopTx = txData - hopTx.chainID = route.fromNetwork.chainId.some - hopTx.chainIDTo = route.toNetwork.chainId.some - hopTx.symbol = tokenSymbol.some - hopTx.recipient = parseAddress(to_addr).some - hopTx.amount = route.amountIn.some - hopTx.bonderFee = route.txBonderFees.some - path.hopTx = hopTx - elif(route.bridgeName == ERC721_TRANSFER_NAME): - eRC721TransferTx = txData - eRC721TransferTx.chainID = route.toNetwork.chainId.some - eRC721TransferTx.recipient = parseAddress(to_addr).some - eRC721TransferTx.tokenID = stint.u256(tokenSymbol).some - path.eRC721TransferTx = eRC721TransferTx - elif(route.bridgeName == ERC1155_TRANSFER_NAME): - eRC1155TransferTx = txData - eRC1155TransferTx.chainID = route.toNetwork.chainId.some - eRC1155TransferTx.recipient = parseAddress(to_addr).some - eRC1155TransferTx.tokenID = stint.u256(tokenSymbol).some - eRC1155TransferTx.amount = route.amountIn.some - path.eRC1155TransferTx = eRC1155TransferTx - elif(route.bridgeName == SWAP_PARASWAP_NAME): - swapTx = txData - swapTx.chainID = route.fromNetwork.chainId.some - swapTx.chainIDTo = route.toNetwork.chainId.some - swapTx.tokenIdFrom = route.fromToken.symbol.some - swapTx.tokenIdTo = route.toToken.symbol.some - path.swapTx = swapTx - else: - cbridgeTx = txData - cbridgeTx.chainID = route.toNetwork.chainId.some - cbridgeTx.symbol = tokenSymbol.some - cbridgeTx.recipient = parseAddress(to_addr).some - cbridgeTx.amount = route.amountIn.some - path.cbridgeTx = cbridgeTx - return path - - proc sendTransactionSentSignal(self: Service, txType: SendType, fromAddr: string, toAddr: string, - fromTokenKey: string, fromAmount: string, toTokenKey: string, toAmount: string, uuid: string, - routes: seq[TransactionPathDto], response: RpcResponse[JsonNode], err: string = "", tokenName = "", isOwnerToken=false) = + proc sendTransactionsSignal(self: Service, sendDetails: SendDetailsDto, sentTransactions: seq[RouterSentTransaction] = @[]) = # While preparing the tx in the Send modal user cannot see the address, it's revealed once the tx is sent # (there are few places where we display the toast from and link to the etherscan where the address can be seen) # that's why we need to mark the addresses as shown here (safer). - self.events.emit(MARK_WALLET_ADDRESSES_AS_SHOWN, WalletAddressesArgs(addresses: @[fromAddr, toAddr])) + self.events.emit(MARK_WALLET_ADDRESSES_AS_SHOWN, WalletAddressesArgs(addresses: @[sendDetails.fromAddress, sendDetails.toAddress])) - if err.len > 0: - self.events.emit(SIGNAL_TRANSACTION_SENT, TransactionSentArgs(uuid: uuid, error: err, txType: txType, - fromAddress: fromAddr, toAddress: toAddr, fromTokenKey: fromTokenKey, fromAmount: fromAmount, - toTokenKey: toTokenKey, toAmount: toAmount)) - if txType == SendType.Swap: - singletonInstance.globalEvents.addCentralizedMetricIfEnabled("swap", $(%*{"subEvent": "tx error"})) - elif response.result{"hashes"} != nil: - for route in routes: - for hash in response.result["hashes"][$route.fromNetwork.chainID]: - if isOwnerToken: - self.events.emit(SIGNAL_OWNER_TOKEN_SENT, OwnerTokenSentArgs(chainId: route.fromNetwork.chainID, txHash: hash.getStr, - uuid: uuid, tokenName: tokenName, status: ContractTransactionStatus.InProgress)) - else: - self.events.emit(SIGNAL_TRANSACTION_SENT, TransactionSentArgs(chainId: route.fromNetwork.chainID, txHash: hash.getStr, uuid: uuid , - error: "", txType: txType, fromAddress: fromAddr, toAddress: toAddr, fromTokenKey: fromTokenKey, fromAmount: fromAmount, toTokenKey: toTokenKey, toAmount: toAmount)) + if not sendDetails.errorResponse.isNil and sendDetails.errorResponse.details.len > 0: + self.events.emit(SIGNAL_TRANSACTION_SENT, TransactionSentArgs( + uuid: sendDetails.uuid, + error: sendDetails.errorResponse.details, + txType: SendType(sendDetails.sendType), + fromAddress: sendDetails.fromAddress, + toAddress: sendDetails.toAddress, + fromTokenKey: sendDetails.fromToken, + fromAmount: sendDetails.fromAmount.toString(10), + toTokenKey: sendDetails.toToken, + toAmount: sendDetails.toAmount.toString(10), + username: sendDetails.username, + publicKey: sendDetails.publicKey, + packId: sendDetails.packId + )) + return - let metadata = TokenTransferMetadata(tokenName: tokenName, isOwnerToken: isOwnerToken) - self.watchTransaction(hash.getStr, fromAddr, toAddr, $PendingTransactionTypeDto.WalletTransfer, $(%metadata), route.fromNetwork.chainID, - fromTokenKey, fromAmount, toTokenKey, toAmount, txType) - - if txType == SendType.Swap: - singletonInstance.globalEvents.addCentralizedMetricIfEnabled("swap", $(%*{"subEvent": "tx success"})) - - proc isCollectiblesTransfer(self: Service, sendType: SendType): bool = - return sendType == ERC721Transfer or sendType == ERC1155Transfer - - proc sendTypeToMultiTxType(sendType: SendType): transactions.MultiTransactionType = - case sendType - of SendType.Swap: - return transactions.MultiTransactionType.MultiTransactionSwap - of SendType.Approve: - return transactions.MultiTransactionType.MultiTransactionApprove - of SendType.Bridge: - return transactions.MultiTransactionType.MultiTransactionBridge - else: - return transactions.MultiTransactionType.MultiTransactionSend - - proc transferEth( - self: Service, - from_addr: string, - to_addr: string, - tokenSymbol: string, - toTokenSymbol: string, - uuid: string, - routes: seq[TransactionPathDto], - password: string, - sendType: SendType, - slippagePercentage: Option[float] - ) = - try: - var paths: seq[TransactionBridgeDto] = @[] - var totalAmountToSend: UInt256 - var totalAmountToReceive: UInt256 - let toAddress = parseAddress(to_addr) - - for route in routes: - var txData = TransactionDataDto() - var gasFees: string = "" - - if( not route.gasFees.eip1559Enabled): - gasFees = $route.gasFees.gasPrice - - if route.approvalRequired: - paths.add(self.createApprovalPath(route, from_addr, toAddress, gasFees)) - - totalAmountToSend += route.amountIn - totalAmountToReceive += route.amountOut - txData = ens_utils.buildTransaction(parseAddress(from_addr), route.amountIn, - $route.gasAmount, gasFees, route.gasFees.eip1559Enabled, $route.gasFees.maxPriorityFeePerGas, $route.gasFees.maxFeePerGasM) - txData.to = parseAddress(to_addr).some - if sendType == SendType.Swap: - txData.slippagePercentage = slippagePercentage - - paths.add(self.createPath(route, txData, tokenSymbol, to_addr)) - - var mtCommand = MultiTransactionCommandDto( - fromAddress: from_addr, - toAddress: to_addr, - fromAsset: tokenSymbol, - toAsset: toTokenSymbol, - fromAmount: "0x" & totalAmountToSend.toHex, - multiTxType: sendTypeToMultiTxType(sendType), - ) - - if sendType == SendType.Swap: - mtCommand.toAmount = "0x" & totalAmountToReceive.toHex - singletonInstance.globalEvents.addCentralizedMetricIfEnabled("swap", $(%*{"subEvent": "send tx"})) - - let response = transactions.createMultiTransaction( - mtCommand, - paths, - password, - ) - - if password != "": - self.sendTransactionSentSignal(sendType, from_addr, to_addr, tokenSymbol, totalAmountToSend.toString(10), - toTokenSymbol, totalAmountToReceive.toString(10), uuid, routes, response) - except Exception as e: - self.sendTransactionSentSignal(sendType, from_addr, to_addr, tokenSymbol, "", - toTokenSymbol, "", uuid, @[], RpcResponse[JsonNode](), self.extractRpcErrorMessage(e.msg)) - - proc mustIgnoreApprovalRequests(sendType: SendType): bool = - # Swap requires approvals to be done in advance in a separate Tx - return sendType == SendType.Swap - - # in case of collectibles transfer, assetKey is used to get the contract address and token id - # in case of asset transfer, asset is valid and used to get the asset symbol and contract address - proc transferToken( - self: Service, - from_addr: string, - to_addr: string, - assetKey: string, - asset: TokenBySymbolItem, - toAssetKey: string, - toAsset: TokenBySymbolItem, - uuid: string, - routes: seq[TransactionPathDto], - password: string, - sendType: SendType, - tokenName: string, - isOwnerToken: bool, - slippagePercentage: Option[float] - ) = - var - toContractAddress: Address - paths: seq[TransactionBridgeDto] = @[] - totalAmountToSend: UInt256 - totalAmountToReceive: UInt256 - mtCommand = MultiTransactionCommandDto( - fromAddress: from_addr, - toAddress: to_addr, - fromAsset: if not asset.isNil: asset.symbol else: assetKey, - toAsset: if not toAsset.isNil: toAsset.symbol else: toAssetKey, - multiTxType: sendTypeToMultiTxType(sendType), - ) - - # if collectibles transfer ... - if asset.isNil: - let contract_tokenId = assetKey.split(":") - if contract_tokenId.len == 2: - toContractAddress = parseAddress(contract_tokenId[0]) - mtCommand.fromAsset = contract_tokenId[1] - mtCommand.toAsset = contract_tokenId[1] + for tx in sentTransactions: + if sendDetails.ownerTokenBeingSent: + self.events.emit(SIGNAL_OWNER_TOKEN_SENT, OwnerTokenSentArgs( + chainId: tx.fromChain, + txHash: tx.hash, + uuid: sendDetails.uuid, + tokenName: tx.fromToken, + status: ContractTransactionStatus.InProgress + )) else: - error "Invalid assetKey for collectibles transfer", assetKey=assetKey - return - - try: - for route in routes: - var txData = TransactionDataDto() - var gasFees: string = "" - - # If not collectible ... - if not asset.isNil: - var foundAddress = false - for addressPerChain in asset.addressPerChainId: - if addressPerChain.chainId == route.fromNetwork.chainId: - toContractAddress = parseAddress(addressPerChain.address) - foundAddress = true - break - if not foundAddress: - error "Contract address not found for asset", assetKey=assetKey - return - - if not route.gasFees.eip1559Enabled: - gasFees = $route.gasFees.gasPrice - - if route.approvalRequired and not mustIgnoreApprovalRequests(sendType): - let approvalPath = self.createApprovalPath(route, mtCommand.fromAddress, toContractAddress, gasFees) - paths.add(approvalPath) - - totalAmountToSend += route.amountIn - totalAmountToReceive += route.amountOut - - if sendType == SendType.Approve: - # We only do the approvals - continue - - let transfer = Transfer( - to: parseAddress(mtCommand.toAddress), - value: route.amountIn, - ) - let data = ERC20_procS.toTable["transfer"].encodeAbi(transfer) - - txData = ens_utils.buildTokenTransaction( - parseAddress(mtCommand.fromAddress), - toContractAddress, - $route.gasAmount, - gasFees, - route.gasFees.eip1559Enabled, - $route.gasFees.maxPriorityFeePerGas, - $route.gasFees.maxFeePerGasM - ) - txData.data = data - if sendType == SendType.Swap: - txData.slippagePercentage = slippagePercentage - - let path = self.createPath(route, txData, mtCommand.toAsset, mtCommand.toAddress) - paths.add(path) - - mtCommand.fromAmount = "0x" & totalAmountToSend.toHex - if sendType == SendType.Swap: - mtCommand.toAmount = "0x" & totalAmountToReceive.toHex - singletonInstance.globalEvents.addCentralizedMetricIfEnabled("swap", $(%*{"subEvent": "send tx"})) - - let response = transactions.createMultiTransaction(mtCommand, paths, password) - - if password != "": - self.sendTransactionSentSignal(sendType, mtCommand.fromAddress, mtCommand.toAddress, - assetKey, totalAmountToSend.toString(10), toAssetKey, totalAmountToReceive.toString(10), - uuid, routes, response, err="", tokenName, isOwnerToken) - - except Exception as e: - self.sendTransactionSentSignal(sendType, mtCommand.fromAddress, mtCommand.toAddress, - assetKey, "", toAssetKey, "", - uuid, @[], RpcResponse[JsonNode](), self.extractRpcErrorMessage(e.msg)) - - proc transfer*( - self: Service, - fromAddr: string, - toAddr: string, - assetKey: string, - toAssetKey: string, - uuid: string, - selectedRoutes: seq[TransactionPathDto], - password: string, - sendType: SendType, - usePassword: bool, - doHashing: bool, - tokenName: string, - isOwnerToken: bool, - slippagePercentage: Option[float] - ) = - var finalPassword = "" - if usePassword: - finalPassword = password - if doHashing: - finalPassword = common_utils.hashPassword(password) - try: - var chainID = 0 - - if(selectedRoutes.len > 0): - chainID = selectedRoutes[0].fromNetwork.chainID - - # asset == nil means transferToken is executed for a collectibles transfer - var - asset: TokenBySymbolItem - toAsset: TokenBySymbolItem - if not self.isCollectiblesTransfer(sendType): - asset = self.tokenService.getTokenBySymbolByTokensKey(assetKey) - if asset.isNil: - error "Asset not found for", assetKey=assetKey - return - - toAsset = asset - if sendType == Swap: - toAsset = self.tokenService.getTokenBySymbolByTokensKey(toAssetKey) - if toAsset.isNil: - error "Asset not found for", assetKey=assetKey - return - - let network = self.networkService.getNetworkByChainId(chainID) - if not network.isNil and network.nativeCurrencySymbol == asset.symbol: - self.transferEth(fromAddr, toAddr, asset.symbol, toAsset.symbol, uuid, selectedRoutes, finalPassword, sendType, slippagePercentage) - return - - self.transferToken(fromAddr, toAddr, assetKey, asset, toAssetKey, toAsset, uuid, selectedRoutes, finalPassword, - sendType, tokenName, isOwnerToken, slippagePercentage) - - except Exception as e: - self.sendTransactionSentSignal(sendType, fromAddr, toAddr, assetKey, "", toAssetKey, "", uuid, @[], RpcResponse[JsonNode](), self.extractRpcErrorMessage(e.msg)) - - proc proceedWithTransactionsSignatures*(self: Service, fromAddr: string, toAddr: string, - fromTokenKey: string, toTokenKey: string, uuid: string, signatures: TransactionsSignatures, - selectedRoutes: seq[TransactionPathDto], sendType: SendType) = - try: - let response = transactions.proceedWithTransactionsSignatures(signatures) - - var totalAmountToSend: UInt256 - var totalAmountToReceive: UInt256 - for route in selectedRoutes: - totalAmountToSend += route.amountIn - totalAmountToReceive += route.amountOut - - self.sendTransactionSentSignal(sendType, fromAddr, toAddr, fromTokenKey, totalAmountToSend.toString(10), toTokenKey, totalAmountToReceive.toString(10), uuid, selectedRoutes, response) - except Exception as e: - self.sendTransactionSentSignal(sendType, fromAddr, toAddr, fromTokenKey, "", toTokenKey, "", - uuid, @[], RpcResponse[JsonNode](), fmt"Error proceeding with transactions signatures: {e.msg}") + self.events.emit(SIGNAL_TRANSACTION_SENT, TransactionSentArgs( + chainId: tx.fromChain, + txHash: tx.hash, + uuid: sendDetails.uuid, + txType: SendType(sendDetails.sendType), + fromAddress: tx.fromAddress, + toAddress: tx.toAddress, + fromTokenKey: tx.fromToken, + fromAmount: tx.amount.toString(10), + toTokenKey: tx.toToken, + toAmount: tx.amount.toString(10), + approvalTx: tx.approvalTx, + username: sendDetails.username, + publicKey: sendDetails.publicKey, + packId: sendDetails.packId + )) proc suggestedFees*(self: Service, chainId: int): SuggestedFeesDto = try: @@ -688,6 +406,7 @@ QtObject: accountFrom: string, accountTo: string, token: string, + tokenIsOwnerToken: bool, amountIn: string, toToken: string = "", amountOut: string = "", @@ -706,7 +425,7 @@ QtObject: try: let res = eth.suggestedRoutesAsync(uuid, ord(sendType), accountFrom, accountTo, amountInHex, amountOutHex, token, - toToken, disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) + tokenIsOwnerToken, toToken, disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) except CatchableError as e: error "suggestedRoutes", exception=e.msg @@ -747,3 +466,38 @@ proc getMultiTransactions*(transactionIDs: seq[int]): seq[MultiTransactionDto] = let errDescription = e.msg error "error: ", errDescription return + +proc signMessage*(self: Service, address: string, hashedPassword: string, hashedMessage: string): tuple[res: string, err: string] = + var signMsgRes: JsonNode + let err = wallet.signMessage(signMsgRes, + hashedMessage, + address, + hashedPassword) + if err.len > 0: + error "status-go - wallet_signMessage failed", err=err + result.err = err + return + result.res = signMsgRes.getStr + return + +proc buildTransactionsFromRoute*(self: Service, uuid: string, slippagePercentage: float): string = + var response: JsonNode + let err = transactions.buildTransactionsFromRoute(response, uuid, slippagePercentage) + if err.len > 0: + error "status-go - transfer failed", err=err + return err + if response.kind != JNull: + error "unexpected transfer response" + return "unexpected transfer response" + return "" + +proc sendRouterTransactionsWithSignatures*(self: Service, uuid: string, signatures: TransactionsSignatures): string = + var response: JsonNode + let err = transactions.sendRouterTransactionsWithSignatures(response, uuid, signatures) + if err.len > 0: + error "status-go - wallet_sendRouterTransactionsWithSignatures failed", err=err + return err + if response.kind != JNull: + error "unexpected sending transactions response" + return "unexpected sending transactions response" + return "" diff --git a/src/app_service/service/wallet_connect/service.nim b/src/app_service/service/wallet_connect/service.nim index e8c791b999..98926fda87 100644 --- a/src/app_service/service/wallet_connect/service.nim +++ b/src/app_service/service/wallet_connect/service.nim @@ -138,15 +138,8 @@ QtObject: return "" return hashRes.result.getStr() - proc signMessage*(self: Service, address: string, hashedPassword: string, hashedMessage: string): string = - var signMsgRes: JsonNode - let err = wallet.signMessage(signMsgRes, - hashedMessage, - address, - hashedPassword) - if err.len > 0: - error "status-go - wallet_signMessage failed", err=err - return signMsgRes.getStr + proc signMessage*(self: Service, address: string, hashedPassword: string, hashedMessage: string): tuple[res: string, err: string] = + return self.transactions.signMessage(address, hashedPassword, hashedMessage) proc buildTransaction*(self: Service, chainId: int, txJson: string): tuple[txToSign: string, txData: JsonNode] = var buildTxResponse: JsonNode diff --git a/src/backend/common.nim b/src/backend/common.nim new file mode 100644 index 0000000000..e2b0b071bc --- /dev/null +++ b/src/backend/common.nim @@ -0,0 +1,16 @@ +import json +import response_type + +const NO_RESULT* = "no result" + +proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool = + if not rpcResponse.error.isNil: + return true + return false + +proc prepareResponse(resultOut: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string = + if isErrorResponse(rpcResponse): + return rpcResponse.error.message + if rpcResponse.result.isNil: + return NO_RESULT + resultOut = rpcResponse.result \ No newline at end of file diff --git a/src/backend/eth.nim b/src/backend/eth.nim index 3463e0ff4b..4f39084665 100644 --- a/src/backend/eth.nim +++ b/src/backend/eth.nim @@ -39,7 +39,7 @@ proc suggestedFees*(chainId: int): RpcResponse[JsonNode] = return core.callPrivateRPC("wallet_getSuggestedFees", payload) proc prepareDataForSuggestedRoutes(uuid: string, sendType: int, accountFrom: string, accountTo: string, amountIn: string, amountOut: string, - token: string, toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], + token: string, tokenIsOwnerToken: bool, toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], extraParamsTable: Table[string, string]): JsonNode = let data = %* { @@ -50,6 +50,7 @@ proc prepareDataForSuggestedRoutes(uuid: string, sendType: int, accountFrom: str "amountIn": amountIn, "amountOut": amountOut, "tokenID": token, + "tokenIDIsOwnerToken": tokenIsOwnerToken, "toTokenID": toToken, "disabledFromChainIDs": disabledFromChainIDs, "disabledToChainIDs": disabledToChainIDs, @@ -69,19 +70,19 @@ proc prepareDataForSuggestedRoutes(uuid: string, sendType: int, accountFrom: str return %* [data] proc suggestedRoutes*(sendType: int, accountFrom: string, accountTo: string, amountIn: string, amountOut: string, token: string, - toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], + tokenIsOwnerToken: bool, toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], extraParamsTable: Table[string, string]): RpcResponse[JsonNode] {.raises: [RpcException].} = - let payload = prepareDataForSuggestedRoutes(uuid = "", sendType, accountFrom, accountTo, amountIn, amountOut, token, toToken, disabledFromChainIDs, - disabledToChainIDs, lockedInAmounts, extraParamsTable) + let payload = prepareDataForSuggestedRoutes(uuid = "", sendType, accountFrom, accountTo, amountIn, amountOut, token, tokenIsOwnerToken, toToken, + disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) if payload.isNil: raise newException(RpcException, "Invalid key in extraParamsTable") return core.callPrivateRPC("wallet_getSuggestedRoutes", payload) proc suggestedRoutesAsync*(uuid: string, sendType: int, accountFrom: string, accountTo: string, amountIn: string, amountOut: string, token: string, - toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], + tokenIsOwnerToken: bool, toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], extraParamsTable: Table[string, string]): RpcResponse[JsonNode] {.raises: [RpcException].} = - let payload = prepareDataForSuggestedRoutes(uuid, sendType, accountFrom, accountTo, amountIn, amountOut, token, toToken, disabledFromChainIDs, - disabledToChainIDs, lockedInAmounts, extraParamsTable) + let payload = prepareDataForSuggestedRoutes(uuid, sendType, accountFrom, accountTo, amountIn, amountOut, token, tokenIsOwnerToken, toToken, + disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) if payload.isNil: raise newException(RpcException, "Invalid key in extraParamsTable") return core.callPrivateRPC("wallet_getSuggestedRoutesAsync", payload) diff --git a/src/backend/transactions.nim b/src/backend/transactions.nim index abbda242b5..a8f790f2b7 100644 --- a/src/backend/transactions.nim +++ b/src/backend/transactions.nim @@ -1,8 +1,9 @@ -import Tables, json, stint, json_serialization, stew/shims/strformat +import Tables, json, stint, json_serialization, stew/shims/strformat, logging -import ../app_service/service/eth/dto/transaction import ./core as core +include common + type TransactionsSignatures* = Table[string, tuple[r: string, s: string, v: string]] @@ -82,18 +83,6 @@ proc getTransfersByAddress*(chainId: int, address: string, toBlock: Uint256, lim proc getTransactionReceipt*(chainId: int, transactionHash: string): RpcResponse[JsonNode] = core.callPrivateRPCWithChainId("eth_getTransactionReceipt", chainId, %* [transactionHash]) -proc createMultiTransaction*(multiTransactionCommand: MultiTransactionCommandDto, data: seq[TransactionBridgeDto], password: string): RpcResponse[JsonNode] = - let payload = %* [multiTransactionCommand, data, password] - result = core.callPrivateRPC("wallet_createMultiTransaction", payload) - -proc proceedWithTransactionsSignatures*(signatures: TransactionsSignatures): RpcResponse[JsonNode] = - var data = %* {} - for key, value in signatures: - data[key] = %* { "r": value.r, "s": value.s, "v": value.v } - - var payload = %* [data] - result = core.callPrivateRPC("wallet_proceedWithTransactionsSignatures", payload) - proc getMultiTransactions*(transactionIDs: seq[int]): RpcResponse[JsonNode] = let payload = %* [transactionIDs] result = core.callPrivateRPC("wallet_getMultiTransactions", payload) @@ -101,3 +90,31 @@ proc getMultiTransactions*(transactionIDs: seq[int]): RpcResponse[JsonNode] = proc watchTransaction*(chainId: int, hash: string): RpcResponse[JsonNode] = let payload = %* [chainId, hash] core.callPrivateRPC("wallet_watchTransactionByChainID", payload) + +proc buildTransactionsFromRoute*(resultOut: var JsonNode, uuid: string, slippagePercentage: float): string = + try: + let payload = %* [{ + "uuid": uuid, + "slippagePercentage": slippagePercentage + }] + let response = core.callPrivateRPC("wallet_buildTransactionsFromRoute", payload) + return prepareResponse(resultOut, response) + except Exception as e: + warn e.msg + return e.msg + +proc sendRouterTransactionsWithSignatures*(resultOut: var JsonNode, uuid: string, signatures: TransactionsSignatures): string = + try: + var jsonSignatures = %* {} + for key, value in signatures: + jsonSignatures[key] = %* { "r": value.r, "s": value.s, "v": value.v } + + var payload = %* [{ + "uuid": uuid, + "signatures": jsonSignatures + }] + let response = core.callPrivateRPC("wallet_sendRouterTransactionsWithSignatures", payload) + return prepareResponse(resultOut, response) + except Exception as e: + warn e.msg + return e.msg \ No newline at end of file diff --git a/src/backend/wallet.nim b/src/backend/wallet.nim index dcc891e039..ed59a7a04d 100644 --- a/src/backend/wallet.nim +++ b/src/backend/wallet.nim @@ -4,6 +4,8 @@ from ./gen import rpc import status_go +include common + rpc(signMessage, "wallet"): message: string address: string @@ -24,17 +26,6 @@ rpc(sendTransactionWithSignature, "wallet"): sendTxArgsJson: string signature: string -proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool = - if not rpcResponse.error.isNil: - return true - return false - -proc prepareResponse(resultOut: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string = - if isErrorResponse(rpcResponse): - return rpcResponse.error.message - if rpcResponse.result.isNil: - return "no result" - resultOut = rpcResponse.result ## Signs the provided message with the provided account using the provided hashed password, performs `crypto.Sign` ## `resultOut` represents a json object that contains the signature if the call was successful, or `nil` diff --git a/storybook/pages/SwapInputPanelPage.qml b/storybook/pages/SwapInputPanelPage.qml index 071bcc76b1..c39b92bbdd 100644 --- a/storybook/pages/SwapInputPanelPage.qml +++ b/storybook/pages/SwapInputPanelPage.qml @@ -71,7 +71,7 @@ SplitView { readonly property bool areTestNetworksEnabled: true signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription) signal transactionSent(var chainId, var txHash, var uuid, var error) - signal transactionSendingComplete(var txHash, var success) + signal transactionSendingComplete(var txHash, var status) } walletAssetsStore: WalletAssetsStore { id: thisWalletAssetStore diff --git a/storybook/pages/SwapModalPage.qml b/storybook/pages/SwapModalPage.qml index 0852e6c773..fe17a95798 100644 --- a/storybook/pages/SwapModalPage.qml +++ b/storybook/pages/SwapModalPage.qml @@ -75,7 +75,7 @@ SplitView { id: dSwapStore signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription) signal transactionSent(var chainId, var txHash, var uuid, var error) - signal transactionSendingComplete(var txHash, var success) + signal transactionSendingComplete(var txHash, var status) readonly property var accounts: WalletAccountsModel {} readonly property var flatNetworks: NetworksModel.flatNetworks @@ -312,7 +312,7 @@ SplitView { dSwapStore.transactionSent(networksComboBox.currentValue, "0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", d.uuid, "") })() Backpressure.debounce(this, 2000, () => { - dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", true) + dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Success") })() fetchSuggestedRoutesSpy.wait() Backpressure.debounce(this, 1000, () => { @@ -352,7 +352,7 @@ SplitView { dSwapStore.transactionSent(networksComboBox.currentValue, "0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", d.uuid, "") })() Backpressure.debounce(this, 2000, () => { - dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", false) + dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Failed") })() } } @@ -433,7 +433,7 @@ SplitView { Button { text: "emit approval completed successfully" onClicked: { - dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", true) + dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Success") } visible: advancedSignalsCheckBox.checked } @@ -441,7 +441,7 @@ SplitView { Button { text: "emit approval completed with failure" onClicked: { - dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", false) + dSwapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Failed") } visible: advancedSignalsCheckBox.checked } diff --git a/storybook/qmlTests/tests/tst_SwapModal.qml b/storybook/qmlTests/tests/tst_SwapModal.qml index 7d2ee63465..fd4ba1cb65 100644 --- a/storybook/qmlTests/tests/tst_SwapModal.qml +++ b/storybook/qmlTests/tests/tst_SwapModal.qml @@ -29,7 +29,7 @@ Item { readonly property var swapStore: SwapStore { signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription) signal transactionSent(var chainId,var txHash, var uuid, var error) - signal transactionSendingComplete(var txHash, var success) + signal transactionSendingComplete(var txHash, var status) readonly property var accounts: WalletAccountsModel {} readonly property var flatNetworks: NetworksModel.flatNetworks @@ -1548,7 +1548,7 @@ Item { root.swapAdaptor.currencyStore.currentCurrency)) // simulate user click on approve button and approval failed - root.swapStore.transactionSent(root.swapFormData.selectedNetworkChainId, "0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", root.swapAdaptor.uuid, "") + root.swapStore.transactionSent(root.swapAdaptor.uuid, root.swapFormData.selectedNetworkChainId, true, "0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "") verify(root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) @@ -1562,7 +1562,7 @@ Item { root.swapAdaptor.currencyStore.currentCurrency)) // simulate approval tx was unsuccessful - root.swapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", false) + root.swapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Failed") verify(!root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) @@ -1577,7 +1577,7 @@ Item { // simulate user click on approve button and successful approval tx made signButton.clicked() - root.swapStore.transactionSent(root.swapFormData.selectedNetworkChainId, "0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", root.swapAdaptor.uuid, "") + root.swapStore.transactionSent(root.swapAdaptor.uuid, root.swapFormData.selectedNetworkChainId, true, "0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "") verify(root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalSuccessful) @@ -1590,30 +1590,29 @@ Item { root.swapAdaptor.swapOutputData.totalFees, root.swapAdaptor.currencyStore.currentCurrency)) + root.swapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Success") + // simulate approval tx was successful signButton.clicked() - root.swapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", true) - // check if fetchSuggestedRoutes called - fetchSuggestedRoutesCalled.wait() - - // verify loading state was set and no errors currently - verifyLoadingAndNoErrorsState(payPanel, receivePanel) + root.swapStore.transactionSendingComplete("0x877ffe47fc29340312611d4e833ab189fe4f4152b01cc9a05bb4125b81b2a89a", "Success") verify(!root.swapAdaptor.approvalPending) - verify(!root.swapAdaptor.approvalSuccessful) + verify(root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) - verify(!signButton.interactive) + verify(signButton.interactive) verify(!signButton.loadingWithText) compare(signButton.text, qsTr("Swap")) - compare(maxFeesValue.text, Constants.dummyText) + compare(maxFeesValue.text, root.swapAdaptor.currencyStore.formatCurrencyAmount( + root.swapAdaptor.swapOutputData.totalFees, + root.swapAdaptor.currencyStore.currentCurrency)) let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval txHasRouteNoApproval.uuid = root.swapAdaptor.uuid root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "") verify(!root.swapAdaptor.approvalPending) - verify(!root.swapAdaptor.approvalSuccessful) + verify(root.swapAdaptor.approvalSuccessful) verify(!errorTag.visible) verify(signButton.enabled) verify(!signButton.loadingWithText) @@ -1784,9 +1783,6 @@ Item { let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval txHasRouteNoApproval.uuid = root.swapAdaptor.uuid root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "") - - // check if fetch occurs automatically after 15 seconds - fetchSuggestedRoutesCalled.wait() } function test_deleteing_input_characters_data() { @@ -1833,38 +1829,38 @@ Item { root.swapFormData.autoRefreshTime = 1200 // Launch popup - launchAndVerfyModal() +// launchAndVerfyModal() - // check if fetchSuggestedRoutes called - tryCompare(fetchSuggestedRoutesCalled, "count", 1) +// // check if fetchSuggestedRoutes called +// tryCompare(fetchSuggestedRoutesCalled, "count", 1) // no new calls to fetch new proposal should be made as the proposal is still loading - wait(root.swapFormData.autoRefreshTime*2) - compare(fetchSuggestedRoutesCalled.count, 1) +// wait(root.swapFormData.autoRefreshTime*2) +// compare(fetchSuggestedRoutesCalled.count, 1) - // emit routes ready - let txHasRouteApproval = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded - txHasRouteApproval.uuid = root.swapAdaptor.uuid - root.swapStore.suggestedRoutesReady(txHasRouteApproval, "", "") +// // emit routes ready +// let txHasRouteApproval = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded +// txHasRouteApproval.uuid = root.swapAdaptor.uuid +// root.swapStore.suggestedRoutesReady(txHasRouteApproval, "", "") - // now refresh can occur as no propsal or signing is pending - tryCompare(fetchSuggestedRoutesCalled, "count", 2) +// // now refresh can occur as no propsal or signing is pending +// tryCompare(fetchSuggestedRoutesCalled, "count", 2) - // emit routes ready - txHasRouteApproval.uuid = root.swapAdaptor.uuid - root.swapStore.suggestedRoutesReady(txHasRouteApproval, "", "") +// // emit routes ready +// txHasRouteApproval.uuid = root.swapAdaptor.uuid +// root.swapStore.suggestedRoutesReady(txHasRouteApproval, "", "") - verify(root.swapAdaptor.swapOutputData.approvalNeeded) - verify(!root.swapAdaptor.approvalPending) +// verify(root.swapAdaptor.swapOutputData.approvalNeeded) +// verify(!root.swapAdaptor.approvalPending) - // sign approval and check that auto refresh doesnt occur - root.swapAdaptor.sendApproveTx() +// // sign approval and check that auto refresh doesnt occur +// root.swapAdaptor.sendApproveTx() - // no new calls to fetch new proposal should be made as the approval is pending - verify(root.swapAdaptor.swapOutputData.approvalNeeded) - verify(root.swapAdaptor.approvalPending) - wait(root.swapFormData.autoRefreshTime*2) - compare(fetchSuggestedRoutesCalled.count, 2) +// // no new calls to fetch new proposal should be made as the approval is pending +// verify(root.swapAdaptor.swapOutputData.approvalNeeded) +// verify(root.swapAdaptor.approvalPending) +// wait(root.swapFormData.autoRefreshTime*2) +// compare(fetchSuggestedRoutesCalled.count, 2) } } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index cab54f7dc7..36df35f321 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -39,17 +39,6 @@ StatusDialog { root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue) }) - property Timer autoRefreshTimer: Timer { - interval: root.swapInputParamsForm.autoRefreshTime - running: false - repeat: false - onTriggered: { - if(!root.swapAdaptor.swapProposalLoading && !root.swapAdaptor.approvalPending) { - d.fetchSuggestedRoutes() - } - } - } - function fetchSuggestedRoutes() { if (root.swapInputParamsForm.isFormFilledCorrectly()) { root.swapAdaptor.swapProposalLoading = true @@ -91,19 +80,6 @@ StatusDialog { } } - Connections { - target: root.swapAdaptor - function onApprovalSuccessfulChanged() { - // perform a recalculation to make sure expected outcome shown is accurate - if(root.swapAdaptor.approvalSuccessful) { - d.fetchSuggestedRoutes() - } - } - function onSuggestedRoutesReady() { - d.autoRefreshTimer.restart() - } - } - // needed as the first time the value not loaded correctly without this Binding Binding { target: root.swapAdaptor diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml index 13a59cf320..6810de26ea 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml @@ -264,7 +264,7 @@ QObject { root.suggestedRoutesReady() } - function onTransactionSent(chainId, txHash, uuid, error) { + function onTransactionSent(uuid, chainId, approvalTx, txHash, error) { if(root.swapOutputData.approvalNeeded) { if (uuid !== d.uuid || !!error) { root.approvalPending = false @@ -276,11 +276,15 @@ QObject { } } - function onTransactionSendingComplete(txHash, success) { + function onTransactionSendingComplete(txHash, status) { if(d.txHash === txHash && root.swapOutputData.approvalNeeded && root.approvalPending) { root.approvalPending = false - root.approvalSuccessful = success + root.approvalSuccessful = status == "Success" // TODO: make a all tx statuses Constants (success, pending, failed) d.txHash = "" + + if (root.approvalSuccessful) { + root.swapOutputData.approvalNeeded = false + } } } } @@ -342,16 +346,12 @@ QObject { root.approvalPending = true const accountAddress = root.swapFormData.selectedAccountAddress - root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress, - root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, - Constants.SendType.Approve, "", false, root.swapOutputData.rawPaths, "") + root.swapStore.authenticateAndTransfer(d.uuid, "") } function sendSwapTx() { const accountAddress = root.swapFormData.selectedAccountAddress - root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress, - root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, - Constants.SendType.Swap, "", false, root.swapOutputData.rawPaths, root.swapFormData.selectedSlippage) + root.swapStore.authenticateAndTransfer(d.uuid, root.swapFormData.selectedSlippage) } } diff --git a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml index 2106628dd9..a1e3211c38 100644 --- a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml @@ -17,7 +17,7 @@ QtObject { readonly property var walletSectionSendInst: walletSectionSend signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription) - signal transactionSent(var chainId, var txHash, var uuid, var error) + signal transactionSent(var uuid, var chainId, var approvalTx, var txHash, var error) signal transactionSendingComplete(var txHash, var success) readonly property Connections walletSectionSendConnections: Connections { @@ -25,8 +25,8 @@ QtObject { function onSuggestedRoutesReady(txRoutes, errCode, errDescription) { root.suggestedRoutesReady(txRoutes, errCode, errDescription) } - function onTransactionSent(chainId, txHash, uuid, error) { - root.transactionSent(chainId, txHash, uuid, error) + function onTransactionSent(uuid, chainId, approvalTx, txHash, error) { + root.transactionSent(uuid, chainId, approvalTx, txHash, error) } function onTransactionSendingComplete(txHash, success) { root.transactionSendingComplete(txHash, success) @@ -45,10 +45,8 @@ QtObject { root.walletSectionSendInst.stopUpdatesForSuggestedRoute() } - function authenticateAndTransfer(uuid, accountFrom, accountTo, - tokenFrom, tokenTo, sendType, tokenName, tokenIsOwnerToken, paths, slippagePercentage) { - root.walletSectionSendInst.authenticateAndTransferWithParameters(uuid, accountFrom, accountTo, - tokenFrom, tokenTo, sendType, tokenName, tokenIsOwnerToken, paths, slippagePercentage) + function authenticateAndTransfer(uuid, slippagePercentage) { + root.walletSectionSendInst.authenticateAndTransfer(uuid, slippagePercentage) } function getWei2Eth(wei, decimals) { diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index 63da2ec428..d3433bff5d 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -78,7 +78,7 @@ StatusDialog { popup.isLoading = true d.routerError = "" d.routerErrorDetails = "" - popup.store.suggestedRoutes(d.isCollectiblesTransfer ? "1" : amountToSend.amount, "0", d.extraParamsJson) + popup.store.suggestedRoutes(d.uuid, d.isCollectiblesTransfer ? "1" : amountToSend.amount, "0", d.extraParamsJson) } }) @@ -788,7 +788,7 @@ StatusDialog { popup.isLoading = false } - function onTransactionSent(chainId: int, txHash: string, uuid: string, error: string) { + function onTransactionSent(uuid: string, chainId: int, approvalTx: bool, txHash: string, error: string) { d.isPendingTx = false if (uuid !== d.uuid) return if (!!error) { diff --git a/ui/imports/shared/stores/send/TransactionStore.qml b/ui/imports/shared/stores/send/TransactionStore.qml index a4f3b6bc28..04a6fa3646 100644 --- a/ui/imports/shared/stores/send/TransactionStore.qml +++ b/ui/imports/shared/stores/send/TransactionStore.qml @@ -58,14 +58,14 @@ QtObject { return networksModule.getBlockExplorerURL(chainID) } - function authenticateAndTransfer(uuid) { - walletSectionSendInst.authenticateAndTransfer(uuid) + function authenticateAndTransfer(uuid, slippagePercentage = "") { + walletSectionSendInst.authenticateAndTransfer(uuid, slippagePercentage) } - function suggestedRoutes(amountIn, amountOut = "0", extraParamsJson = "") { + function suggestedRoutes(uuid, amountIn, amountOut = "0", extraParamsJson = "") { const valueIn = AmountsArithmetic.fromNumber(amountIn) const valueOut = AmountsArithmetic.fromNumber(amountOut) - walletSectionSendInst.suggestedRoutes(valueIn.toFixed(), valueOut.toFixed(), extraParamsJson) + walletSectionSendInst.suggestedRoutes(uuid, valueIn.toFixed(), valueOut.toFixed(), extraParamsJson) } function stopUpdatesForSuggestedRoute() { diff --git a/vendor/status-go b/vendor/status-go index 2f71d9d9f2..107e2cb8da 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 2f71d9d9f2529cfa21cabb216be7992a5f9f6717 +Subproject commit 107e2cb8daef1b89b5f6b8dac6f0aa70d901f711