From 7e9f680e0befd08581720c7ca426e40c1924ea66 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Wed, 26 Jun 2024 01:03:19 +0200 Subject: [PATCH] chore: router v2 integration Closes: #15204 --- .../signals/remote_signals/signal_type.nim | 3 + .../core/signals/remote_signals/wallet.nim | 42 +++-- src/app/core/signals/signals_manager.nim | 7 +- .../main/wallet_section/send/controller.nim | 24 ++- .../main/wallet_section/send/io_interface.nim | 16 +- .../main/wallet_section/send/module.nim | 31 +++- .../wallet_section/send/network_model.nim | 14 +- .../modules/main/wallet_section/send/view.nim | 75 ++++++--- src/app_service/common/utils.nim | 17 ++- .../service/transaction/async_tasks.nim | 42 +---- src/app_service/service/transaction/dto.nim | 20 +-- src/app_service/service/transaction/dtoV2.nim | 79 ++++++++++ .../service/transaction/service.nim | 144 +++++++++++++----- src/backend/eth.nim | 67 +++++++- .../Wallet/popups/swap/SwapModalAdaptor.qml | 18 +-- ui/app/AppLayouts/Wallet/stores/SwapStore.qml | 11 +- .../popups/send/controls/GasSelector.qml | 6 +- .../shared/stores/send/TransactionStore.qml | 7 +- vendor/status-go | 2 +- 19 files changed, 436 insertions(+), 189 deletions(-) create mode 100644 src/app_service/service/transaction/dtoV2.nim diff --git a/src/app/core/signals/remote_signals/signal_type.nim b/src/app/core/signals/remote_signals/signal_type.nim index 4a1d6bb7ba..da35daad41 100644 --- a/src/app/core/signals/remote_signals/signal_type.nim +++ b/src/app/core/signals/remote_signals/signal_type.nim @@ -3,7 +3,10 @@ type SignalType* {.pure.} = enum Message = "messages.new" MessageDelivered = "message.delivered" + ## Wallet Signals Wallet = "wallet" + WalletSignTransactions = "wallet.sign.transactions" + WalletSuggestedRoutes = "wallet.suggested.routes" NodeReady = "node.ready" NodeCrashed = "node.crashed" NodeStarted = "node.started" diff --git a/src/app/core/signals/remote_signals/wallet.nim b/src/app/core/signals/remote_signals/wallet.nim index 51e63bc9d2..eb5825a6f8 100644 --- a/src/app/core/signals/remote_signals/wallet.nim +++ b/src/app/core/signals/remote_signals/wallet.nim @@ -1,8 +1,10 @@ -import json, options +import json, options, sequtils, sugar, chronicles import base import signal_type +import app_service/service/transaction/dtoV2 + const SignTransactionsEventType* = "sing-transactions" type WalletSignal* = ref object of Signal @@ -17,20 +19,20 @@ type WalletSignal* = ref object of Signal message*: string requestId*: Option[int] txHashes*: seq[string] + uuid*: string + bestRoute*: seq[TransactionPathDtoV2] + error*: string + errorCode*: string -proc fromEvent*(T: type WalletSignal, jsonSignal: JsonNode): WalletSignal = +proc fromEvent*(T: type WalletSignal, signalType: SignalType, jsonSignal: JsonNode): WalletSignal = result = WalletSignal() result.signalType = SignalType.Wallet - result.content = $jsonSignal let event = jsonSignal["event"] - if event.kind != JNull: + if event.kind == JNull: + return + if signalType == SignalType.Wallet: + result.content = $jsonSignal result.eventType = event["type"].getStr - if result.eventType == SignTransactionsEventType: - if event["transactions"].kind != JArray: - return - for tx in event["transactions"]: - result.txHashes.add(tx.getStr) - return result.blockNumber = event{"blockNumber"}.getInt result.erc20 = event{"erc20"}.getBool result.accounts = @[] @@ -43,3 +45,23 @@ proc fromEvent*(T: type WalletSignal, jsonSignal: JsonNode): WalletSignal = const requestIdName = "requestId" if event.contains(requestIdName): result.requestId = some(event[requestIdName].getInt()) + return + if signalType == SignalType.WalletSignTransactions: + if event.kind != JArray: + return + for tx in event: + result.txHashes.add(tx.getStr) + return + if signalType == SignalType.WalletSuggestedRoutes: + try: + if event.contains("Uuid"): + result.uuid = event["Uuid"].getStr() + if event.contains("Best"): + result.bestRoute = event["Best"].getElems().map(x => x.toTransactionPathDtoV2()) + if event.contains("details"): + result.error = event["details"].getStr + if event.contains("code"): + result.errorCode = event["code"].getStr + except: + error "Error parsing best route" + return diff --git a/src/app/core/signals/signals_manager.nim b/src/app/core/signals/signals_manager.nim index 933b86aa6d..ee0577b48c 100644 --- a/src/app/core/signals/signals_manager.nim +++ b/src/app/core/signals/signals_manager.nim @@ -26,7 +26,7 @@ QtObject: result.setup() result.events = events - # This method might be called with `ChroniclesLogs` event from `nim_status_client`. + # This method might be called with `ChroniclesLogs` event from `nim_status_client`. # In such case we must not log anything to prevent recursive calls. # I tried to make a better solution, but ended up with such workaround, check out PR for more details. proc processSignal(self: SignalsManager, statusSignal: string, allowLogging: bool) = @@ -79,7 +79,10 @@ QtObject: of SignalType.EnvelopeSent: EnvelopeSentSignal.fromEvent(jsonSignal) of SignalType.EnvelopeExpired: EnvelopeExpiredSignal.fromEvent(jsonSignal) of SignalType.WhisperFilterAdded: WhisperFilterSignal.fromEvent(jsonSignal) - of SignalType.Wallet: WalletSignal.fromEvent(jsonSignal) + of SignalType.Wallet, + SignalType.WalletSignTransactions, + SignalType.WalletSuggestedRoutes: + 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 b8d8db9caf..833e475cc2 100644 --- a/src/app/modules/main/wallet_section/send/controller.nim +++ b/src/app/modules/main/wallet_section/send/controller.nim @@ -1,4 +1,4 @@ -import sugar, sequtils, stint +import Tables, sugar, sequtils import uuids, chronicles, options import io_interface import app_service/service/wallet_account/service as wallet_account_service @@ -71,10 +71,8 @@ proc init*(self: Controller) = self.events.on(SIGNAL_SUGGESTED_ROUTES_READY) do(e:Args): self.delegate.suggestedRoutesReady(SuggestedRoutesArgs(e).suggestedRoutes) - self.events.on(SignalType.Wallet.event) do(e:Args): + self.events.on(SignalType.WalletSignTransactions.event) do(e:Args): var data = WalletSignal(e) - if data.eventType != SignTransactionsEventType: - return self.delegate.prepareSignaturesForTransactions(data.txHashes) proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = @@ -109,10 +107,20 @@ proc authenticate*(self: Controller, keyUid = "") = keyUid: keyUid) self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) -proc suggestedRoutes*(self: Controller, accountFrom: string, accountTo: string, amount: Uint256, token: string, toToken: string, - disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[int], sendType: SendType, lockedInAmounts: string) = - self.transactionService.suggestedRoutes(accountFrom, accountTo, amount, token, toToken, disabledFromChainIDs, - disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) +proc suggestedRoutes*(self: Controller, + sendType: SendType, + accountFrom: string, + accountTo: string, + token: string, + amountIn: string, + toToken: string = "", + amountOut: string = "", + disabledFromChainIDs: seq[int] = @[], + disabledToChainIDs: seq[int] = @[], + lockedInAmounts: Table[string, string] = initTable[string, string](), + extraParamsTable: Table[string, string] = initTable[string, string]()) = + self.transactionService.suggestedRoutes(sendType, accountFrom, accountTo, token, amountIn, toToken, amountOut, + disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) proc transfer*(self: Controller, from_addr: string, to_addr: string, assetKey: string, toAssetKey: string, uuid: string, selectedRoutes: seq[TransactionPathDto], password: string, sendType: SendType, 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 a378516654..a489007714 100644 --- a/src/app/modules/main/wallet_section/send/io_interface.nim +++ b/src/app/modules/main/wallet_section/send/io_interface.nim @@ -1,4 +1,4 @@ -import stint, options +import Tables, options import app/modules/shared_models/currency_amount import app_service/service/transaction/dto import app/modules/shared_models/collectibles_model as collectibles @@ -24,8 +24,18 @@ method refreshWalletAccounts*(self: AccessInterface) {.base.} = method getTokenBalance*(self: AccessInterface, address: string, chainId: int, tokensKey: string): CurrencyAmount {.base.} = raise newException(ValueError, "No implementation available") -method suggestedRoutes*(self: AccessInterface, accountFrom: string, accountTo: string, amount: UInt256, token: string, toToken: string, - disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[int], sendType: SendType, lockedInAmounts: string) {.base.} = +method suggestedRoutes*(self: AccessInterface, + sendType: SendType, + accountFrom: string, + accountTo: string, + token: string, + amountIn: string, + toToken: string = "", + amountOut: string = "", + disabledFromChainIDs: seq[int] = @[], + disabledToChainIDs: seq[int] = @[], + lockedInAmounts: Table[string, string] = initTable[string, string](), + extraParamsTable: Table[string, string] = initTable[string, string]()) {.base.} = raise newException(ValueError, "No implementation available") method suggestedRoutesReady*(self: AccessInterface, suggestedRoutes: SuggestedRoutesDto) {.base.} = diff --git a/src/app/modules/main/wallet_section/send/module.nim b/src/app/modules/main/wallet_section/send/module.nim index 2500e3dcde..6685536310 100644 --- a/src/app/modules/main/wallet_section/send/module.nim +++ b/src/app/modules/main/wallet_section/send/module.nim @@ -339,11 +339,6 @@ method transactionWasSent*(self: Module, chainId: int, txHash, uuid, error: stri return self.view.sendTransactionSentSignal(chainId, txHash, uuid, error) -method suggestedRoutes*(self: Module, accountFrom: string, accountTo: string, amount: UInt256, token: string, toToken: string, - disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[int], sendType: SendType, lockedInAmounts: string) = - self.controller.suggestedRoutes(accountFrom, accountTo, amount, token, toToken, disabledFromChainIDs, - disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) - method suggestedRoutesReady*(self: Module, suggestedRoutes: SuggestedRoutesDto) = self.tmpSendTransactionDetails.paths = suggestedRoutes.best self.tmpSendTransactionDetails.slippagePercentage = none(float) @@ -363,6 +358,32 @@ method suggestedRoutesReady*(self: Module, suggestedRoutes: SuggestedRoutesDto) rawPaths = suggestedRoutes.rawBest) self.view.setTransactionRoute(transactionRoutes) +method suggestedRoutes*(self: Module, + sendType: SendType, + accountFrom: string, + accountTo: string, + token: string, + amountIn: string, + toToken: string = "", + amountOut: string = "", + disabledFromChainIDs: seq[int] = @[], + disabledToChainIDs: seq[int] = @[], + lockedInAmounts: Table[string, string] = initTable[string, string](), + extraParamsTable: Table[string, string] = initTable[string, string]()) = + self.controller.suggestedRoutes( + sendType, + accountFrom, + accountTo, + token, + amountIn, + toToken, + amountOut, + disabledFromChainIDs, + disabledToChainIDs, + lockedInAmounts, + extraParamsTable + ) + method filterChanged*(self: Module, addresses: seq[string], chainIds: seq[int]) = if addresses.len == 0: return diff --git a/src/app/modules/main/wallet_section/send/network_model.nim b/src/app/modules/main/wallet_section/send/network_model.nim index 3fbc3e1f0a..486ffa7c0a 100644 --- a/src/app/modules/main/wallet_section/send/network_model.nim +++ b/src/app/modules/main/wallet_section/send/network_model.nim @@ -205,12 +205,12 @@ QtObject: disbaledChains.add(item.getChainId()) return disbaledChains - proc getRouteLockedChainIds*(self: NetworkModel): string = - var jsonObject = newJObject() + proc getRouteLockedChainIds*(self: NetworkModel): Table[string, string] = + var lockedChains: Table[string, string] for item in self.items: if item.getLocked(): - jsonObject[$item.getChainId()] = %* ("0x" & item.getLockedAmount()) - return $jsonObject + lockedChains[$item.getChainId()] = "0x" & item.getLockedAmount() + return lockedChains proc updateRoutePreferredChains*(self: NetworkModel, chainIds: string) = try: @@ -232,12 +232,6 @@ QtObject: except: discard - proc getRoutePreferredNetworkChainIds*(self: NetworkModel): seq[int] = - var preferredChains: seq[int] = @[] - for item in self.items: - if item.getIsPreferred(): - preferredChains.add(item.getChainId()) - return preferredChains proc disableRouteUnpreferredChains*(self: NetworkModel) = for i in 0 ..< self.items.len: diff --git a/src/app/modules/main/wallet_section/send/view.nim b/src/app/modules/main/wallet_section/send/view.nim index be757c0137..27ab2876bd 100644 --- a/src/app/modules/main/wallet_section/send/view.nim +++ b/src/app/modules/main/wallet_section/send/view.nim @@ -1,4 +1,4 @@ -import NimQml, sequtils, strutils, stint, sugar, options +import NimQml, Tables, json, sequtils, strutils, stint, sugar, options, chronicles import ./io_interface, ./accounts_model, ./account_item, ./network_model, ./network_item, ./suggested_route_item, ./transaction_routes import app/modules/shared_models/collectibles_model as collectibles @@ -218,14 +218,6 @@ QtObject: proc sendTransactionSentSignal*(self: View, chainId: int, txHash: string, uuid: string, error: string) = self.transactionSent(chainId, txHash, uuid, error) - proc parseAmount(amount: string): Uint256 = - var parsedAmount = stint.u256(0) - try: - parsedAmount = amount.parse(Uint256) - except Exception as e: - discard - return parsedAmount - proc parseChainIds(chainIds: string): seq[int] = var parsedChainIds: seq[int] = @[] for chainId in chainIds.split(':'): @@ -241,11 +233,28 @@ QtObject: self.transactionRoutes = routes self.suggestedRoutesReady(newQVariant(self.transactionRoutes)) - proc suggestedRoutes*(self: View, amount: string) {.slot.} = - self.delegate.suggestedRoutes(self.selectedSenderAccount.address(), self.selectedRecipient, - parseAmount(amount), self.selectedAssetKey, self.selectedToAssetKey, self.fromNetworksModel.getRouteDisabledNetworkChainIds(), - self.toNetworksModel.getRouteDisabledNetworkChainIds(), self.toNetworksModel.getRoutePreferredNetworkChainIds(), - self.sendType, self.fromNetworksModel.getRouteLockedChainIds()) + proc suggestedRoutes*(self: View, amountIn: string, amountOut: string, extraParamsJson: string) {.slot.} = + var extraParamsTable: Table[string, string] + try: + if extraParamsJson.len > 0: + for key, value in parseJson(extraParamsJson): + extraParamsTable[key] = value.getStr() + except Exception as e: + error "Error parsing extraParamsJson: ", msg=e.msg + + self.delegate.suggestedRoutes( + self.sendType, + self.selectedSenderAccount.address(), + self.selectedRecipient, + self.selectedAssetKey, + amountIn, + self.selectedToAssetKey, + amountOut, + self.fromNetworksModel.getRouteDisabledNetworkChainIds(), + self.toNetworksModel.getRouteDisabledNetworkChainIds(), + self.fromNetworksModel.getRouteLockedChainIds(), + extraParamsTable + ) proc switchSenderAccountByAddress*(self: View, address: string) {.slot.} = let (account, index) = self.senderAccounts.getItemByAddress(address) @@ -332,13 +341,37 @@ QtObject: return self.fromNetworksModel.getIconUrl(chainId) # "Stateless" methods - proc fetchSuggestedRoutesWithParameters*(self: View, accountFrom: string, accountTo: string, amount: string, token: string, toToken: string, - disabledFromChainIDs: string, disabledToChainIDs: string, preferredChainIDs: string, sendType: int, lockedInAmounts: string) {.slot.} = - self.delegate.suggestedRoutes(accountFrom, accountTo, - parseAmount(amount), token, toToken, - parseChainIds(disabledFromChainIDs), parseChainIds(disabledToChainIDs), parseChainIds(preferredChainIDs), - SendType(sendType), lockedInAmounts) - + proc fetchSuggestedRoutesWithParameters*(self: View, + accountFrom: string, + accountTo: string, + amountIn: string, + amountOut: string, + token: string, + toToken: string, + disabledFromChainIDs: string, + disabledToChainIDs: string, + sendType: int, + lockedInAmounts: string) {.slot.} = + # Prepare lockedInAmountsTable + var lockedInAmountsTable = Table[string, string] : initTable[string, string]() + try: + for chainId, lockedAmount in parseJson(lockedInAmounts): + lockedInAmountsTable[chainId] = lockedAmount.getStr + except: + discard + # Resolve the best route + self.delegate.suggestedRoutes( + SendType(sendType), + accountFrom, + accountTo, + token, + amountIn, + toToken, + amountOut, + parseChainIds(disabledFromChainIDs), + 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.} = diff --git a/src/app_service/common/utils.nim b/src/app_service/common/utils.nim index 42caa91207..aefac4fb81 100644 --- a/src/app_service/common/utils.nim +++ b/src/app_service/common/utils.nim @@ -1,4 +1,5 @@ -import json, times, strutils, sugar, os, re, chronicles +import json, times, stint, strutils, sugar, os, re, chronicles + import nimcrypto import account_constants @@ -7,12 +8,12 @@ import ../../constants as main_constants const STATUS_DOMAIN* = ".stateofus.eth" const ETH_DOMAIN* = ".eth" -proc arrayContains*[T](arr: seq[T], value: T): bool = +proc arrayContains*[T](arr: seq[T], value: T): bool = return arr.any(x => x == value) proc hashPassword*(password: string, lower: bool = true): string = let hashed = "0x" & $keccak_256.digest(password) - + if lower: return hashed.toLowerAscii() @@ -71,7 +72,7 @@ proc validateLink*(link: string): bool = proc isPathOutOfTheDefaultStatusDerivationTree*(path: string): bool = if not path.startsWith(account_constants.PATH_WALLET_ROOT&"/") or path.count("'") != 3 or - path.count("/") != 5: + path.count("/") != 5: return true return false @@ -82,3 +83,11 @@ proc intersectSeqs*[T](seq1, seq2: seq[T]): seq[T] = for item in seq1: if item in seq2: result.add(item) + +proc stringToUint256*(value: string): Uint256 = + var parsedValue = stint.u256(0) + try: + parsedValue = value.parse(Uint256) + except Exception as e: + discard + return parsedValue \ No newline at end of file diff --git a/src/app_service/service/transaction/async_tasks.nim b/src/app_service/service/transaction/async_tasks.nim index 091bca1c37..fda982b27b 100644 --- a/src/app_service/service/transaction/async_tasks.nim +++ b/src/app_service/service/transaction/async_tasks.nim @@ -41,6 +41,7 @@ proc getFeesTotal*(paths: seq[TransactionPathDto]): FeesDto = fees.totalFeesInEth += getGasEthValue(optimalPrice, path.gasAmount) fees.totalFeesInEth += parseFloat(service_conversion.wei2Eth(service_conversion.gwei2Wei(path.gasFees.l1GasFee))) + fees.totalFeesInEth += path.approvalGasFees fees.totalTokenFees += path.tokenFees fees.totalTime += path.estimatedTime return fees @@ -77,47 +78,6 @@ proc addFirstSimpleBridgeTxFlag(paths: seq[TransactionPathDto]) : seq[Transactio return txPaths -proc getSuggestedRoutesTask*(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[GetSuggestedRoutesTaskArg](argEncoded) - - try: - let amountAsHex = "0x" & eth_utils.stripLeadingZeros(arg.amount.toHex) - var lockedInAmounts = Table[string, string] : initTable[string, string]() - - try: - for chainId, lockedAmount in parseJson(arg.lockedInAmounts): - lockedInAmounts[chainId] = lockedAmount.getStr - except: - discard - - let response = eth.suggestedRoutes(arg.accountFrom, arg.accountTo, amountAsHex, arg.token, arg.toToken, arg.disabledFromChainIDs, - arg.disabledToChainIDs, arg.preferredChainIDs, ord(arg.sendType), lockedInAmounts).result - var bestPaths = response["Best"].getElems().map(x => x.toTransactionPathDto()) - - # retry along with unpreferred chains incase no route is possible with preferred chains - if arg.sendType != SendType.Swap and bestPaths.len == 0 and arg.preferredChainIDs.len > 0: - let response = eth.suggestedRoutes(arg.accountFrom, arg.accountTo, amountAsHex, arg.token, arg.toToken, arg.disabledFromChainIDs, - arg.disabledToChainIDs, @[], ord(arg.sendType), lockedInAmounts).result - bestPaths = response["Best"].getElems().map(x => x.toTransactionPathDto()) - - bestPaths.sort(sortAsc[TransactionPathDto]) - - let output = %*{ - "suggestedRoutes": SuggestedRoutesDto( - best: addFirstSimpleBridgeTxFlag(bestPaths), - gasTimeEstimate: getFeesTotal(bestPaths), - amountToReceive: getTotalAmountToReceive(bestPaths), - toNetworks: getToNetworksList(bestPaths)), - "error": "" - } - arg.finish(output) - - except Exception as e: - let output = %* { - "suggestedRoutes": SuggestedRoutesDto(best: @[], gasTimeEstimate: FeesDto(), amountToReceive: stint.u256(0), toNetworks: @[]), - "error": fmt"Error getting suggested routes: {e.msg}" - } - arg.finish(output) type diff --git a/src/app_service/service/transaction/dto.nim b/src/app_service/service/transaction/dto.nim index f1e7391f6e..e195b03f30 100644 --- a/src/app_service/service/transaction/dto.nim +++ b/src/app_service/service/transaction/dto.nim @@ -1,4 +1,4 @@ -import json, strutils, stint, json_serialization, stew/shims/strformat, sugar, sequtils +import json, strutils, stint, json_serialization, stew/shims/strformat import web3/ethtypes @@ -334,7 +334,7 @@ proc convertToTransactionPathsDto*(jsonObj: JsonNode): seq[TransactionPathDto] = proc convertToTransactionPathsDto*(paths: string): seq[TransactionPathDto] = return paths.parseJson.convertToTransactionPathsDto() - + type FeesDto* = ref object totalFeesInEth*: float @@ -384,19 +384,3 @@ type amountToReceive*: UInt256 toNetworks*: seq[SendToNetwork] -proc `$`*(self: SuggestedRoutesDto): string = - return fmt"""SuggestedRoutesDto( - best:{self.best}, - rawBest:{self.rawBest}, - gasTimeEstimate:{self.gasTimeEstimate}, - amountToReceive:{self.amountToReceive}, - toNetworks:{self.toNetworks}, - )""" - -proc convertToSuggestedRoutesDto*(jsonObj: JsonNode): SuggestedRoutesDto = - result = SuggestedRoutesDto() - result.rawBest = $jsonObj["suggestedRoutes"]["best"] - result.best = result.rawBest.convertToTransactionPathsDto() - result.gasTimeEstimate = jsonObj["suggestedRoutes"]["gasTimeEstimate"].convertToFeesDto() - result.amountToReceive = stint.u256(jsonObj["suggestedRoutes"]["amountToReceive"].getStr) - result.toNetworks = jsonObj["suggestedRoutes"]["toNetworks"].getElems().map(x => x.convertSendToNetwork()) diff --git a/src/app_service/service/transaction/dtoV2.nim b/src/app_service/service/transaction/dtoV2.nim new file mode 100644 index 0000000000..7185b555eb --- /dev/null +++ b/src/app_service/service/transaction/dtoV2.nim @@ -0,0 +1,79 @@ +# import json, json_serialization, stint + +# import ../network/dto, ../token/dto + +# include app_service/common/json_utils + +import json, strutils, stint, json_serialization + +import + web3/ethtypes + +include ../../common/json_utils +import ../network/dto, ../token/dto + +type + SuggestedLevelsForMaxFeesPerGasDto* = ref object + low*: UInt256 + medium*: UInt256 + high*: UInt256 + +type + TransactionPathDtoV2* = ref object + processorName*: string + fromChain*: NetworkDto + toChain*: NetworkDto + fromToken*: TokenDto + amountIn*: UInt256 + amountInLocked*: bool + amountOut*: UInt256 + suggestedLevelsForMaxFeesPerGas*: SuggestedLevelsForMaxFeesPerGasDto + txBaseFee*: UInt256 + txPriorityFee*: UInt256 + txGasAmount*: uint64 + txBonderFees*: UInt256 + txTokenFees*: UInt256 + txL1Fee*: UInt256 + approvalRequired*: bool + approvalAmountRequired*: UInt256 + approvalContractAddress*: string + approvalBaseFee*: UInt256 + approvalPriorityFee*: UInt256 + approvalGasAmount*: uint64 + approvalL1Fee*: UInt256 + estimatedTime*: int + +proc toSuggestedLevelsForMaxFeesPerGasDto*(jsonObj: JsonNode): SuggestedLevelsForMaxFeesPerGasDto = + result = SuggestedLevelsForMaxFeesPerGasDto() + var value: string + if jsonObj.getProp("low", value): + result.low = stint.fromHex(UInt256, $value) + if jsonObj.getProp("medium", value): + result.medium = stint.fromHex(UInt256, $value) + if jsonObj.getProp("high", value): + result.high = stint.fromHex(UInt256, $value) + +proc toTransactionPathDtoV2*(jsonObj: JsonNode): TransactionPathDtoV2 = + result = TransactionPathDtoV2() + discard jsonObj.getProp("ProcessorName", result.processorName) + result.fromChain = Json.decode($jsonObj["FromChain"], NetworkDto, allowUnknownFields = true) + result.toChain = Json.decode($jsonObj["ToChain"], NetworkDto, allowUnknownFields = true) + result.fromToken = Json.decode($jsonObj["FromToken"], TokenDto, allowUnknownFields = true) + result.amountIn = stint.fromHex(UInt256, jsonObj{"AmountIn"}.getStr) + discard jsonObj.getProp("AmountInLocked", result.amountInLocked) + result.amountOut = stint.fromHex(UInt256, jsonObj{"AmountOut"}.getStr) + result.suggestedLevelsForMaxFeesPerGas = jsonObj["SuggestedLevelsForMaxFeesPerGas"].toSuggestedLevelsForMaxFeesPerGasDto() + result.txBaseFee = stint.fromHex(UInt256, jsonObj{"TxBaseFee"}.getStr) + result.txPriorityFee = stint.fromHex(UInt256, jsonObj{"TxPriorityFee"}.getStr) + discard jsonObj.getProp("TxGasAmount", result.txGasAmount) + result.txBonderFees = stint.fromHex(UInt256, jsonObj{"TxBonderFees"}.getStr) + result.txTokenFees = stint.fromHex(UInt256, jsonObj{"TxTokenFees"}.getStr) + result.txL1Fee = stint.fromHex(UInt256, jsonObj{"TxL1Fee"}.getStr) + discard jsonObj.getProp("ApprovalRequired", result.approvalRequired) + result.approvalAmountRequired = stint.fromHex(UInt256, jsonObj{"ApprovalAmountRequired"}.getStr) + discard jsonObj.getProp("ApprovalContractAddress", result.approvalContractAddress) + result.approvalBaseFee = stint.fromHex(UInt256, jsonObj{"ApprovalBaseFee"}.getStr) + result.approvalPriorityFee = stint.fromHex(UInt256, jsonObj{"ApprovalPriorityFee"}.getStr) + discard jsonObj.getProp("ApprovalGasAmount", result.approvalGasAmount) + result.approvalL1Fee = stint.fromHex(UInt256, jsonObj{"ApprovalL1Fee"}.getStr) + result.estimatedTime = jsonObj{"EstimatedTime"}.getInt diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 32c17723ea..6d6edc979a 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -1,4 +1,4 @@ -import Tables, NimQml, chronicles, sequtils, sugar, stint, strutils, json, stew/shims/strformat, algorithm +import Tables, NimQml, chronicles, sequtils, sugar, stint, strutils, json, algorithm, uuids, stew/shims/strformat import backend/collectibles as collectibles import backend/transactions as transactions @@ -23,6 +23,7 @@ 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 ./cryptoRampDto import app_service/service/eth/utils as eth_utils import app_service/common/conversion @@ -128,6 +129,9 @@ QtObject: settingsService: settings_service.Service tokenService: token_service.Service + ## Forward declarations + proc suggestedRoutesV2Ready(self: Service, uuid: string, route: seq[TransactionPathDtoV2], error: string, errCode: string) + proc delete*(self: Service) = self.QObject.delete @@ -155,6 +159,10 @@ QtObject: of transactions.EventFetchingHistoryError: self.events.emit(SIGNAL_HISTORY_ERROR, Args()) + self.events.on(SignalType.WalletSuggestedRoutes.event) do(e:Args): + var data = WalletSignal(e) + self.suggestedRoutesV2Ready(data.uuid, data.bestRoute, data.error, data.errorCode) + self.events.on(PendingTransactionTypeDto.WalletTransfer.event) do(e: Args): try: var receivedData = TransactionMinedArgs(e) @@ -561,46 +569,106 @@ QtObject: except Exception as e: error "Error getting suggested fees", msg = e.msg - proc suggestedRoutesReady*(self: Service, suggestedRoutes: string) {.slot.} = - var suggestedRoutesDto: SuggestedRoutesDto = SuggestedRoutesDto() - try: - let responseObj = suggestedRoutes.parseJson - suggestedRoutesDto = responseObj.convertToSuggestedRoutesDto() - except Exception as e: - error "error handling suggestedRoutesReady response", errDesription=e.msg - self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs(suggestedRoutes: suggestedRoutesDto)) + proc convertToOldRoute(route: seq[TransactionPathDtoV2]): seq[TransactionPathDto] = + const + gweiDecimals = 9 + ethDecimals = 18 + for p in route: + var + fees = SuggestedFeesDto() + trPath = TransactionPathDto() - proc suggestedRoutes*(self: Service, accountFrom: string, accountTo: string, amount: Uint256, token: string, toToken: string, - disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[int], sendType: SendType, lockedInAmounts: string) = - var - tokenId: string - toTokenId: string + try: + # prepare fees + fees.gasPrice = 0 + var value = conversion.wei2Eth(input = p.txBaseFee, decimals = gweiDecimals) + fees.baseFee = parseFloat(value) + value = conversion.wei2Eth(input = p.txPriorityFee, decimals = gweiDecimals) + fees.maxPriorityFeePerGas = parseFloat(value) + value = conversion.wei2Eth(input = p.suggestedLevelsForMaxFeesPerGas.low, decimals = gweiDecimals) + fees.maxFeePerGasL = parseFloat(value) + value = conversion.wei2Eth(input = p.suggestedLevelsForMaxFeesPerGas.medium, decimals = gweiDecimals) + fees.maxFeePerGasM = parseFloat(value) + value = conversion.wei2Eth(input = p.suggestedLevelsForMaxFeesPerGas.high, decimals = gweiDecimals) + fees.maxFeePerGasH = parseFloat(value) + value = conversion.wei2Eth(input = p.txL1Fee, decimals = gweiDecimals) + fees.l1GasFee = parseFloat(value) + fees.eip1559Enabled = true - if self.isCollectiblesTransfer(sendType): - tokenId = token - else: - let token = self.tokenService.getTokenBySymbolByTokensKey(token) - if token != nil: - tokenId = token.symbol - let toToken = self.tokenService.getTokenBySymbolByTokensKey(toToken) - if toToken != nil: - toTokenId = toToken.symbol - let arg = GetSuggestedRoutesTaskArg( - tptr: getSuggestedRoutesTask, - vptr: cast[ByteAddress](self.vptr), - slot: "suggestedRoutesReady", - accountFrom: accountFrom, - accountTo: accountTo, - amount: amount, - token: tokenId, - toToken: toTokenId, - disabledFromChainIDs: disabledFromChainIDs, - disabledToChainIDs: disabledToChainIDs, - preferredChainIDs: preferredChainIDs, - sendType: sendType, - lockedInAmounts: lockedInAmounts + # prepare tx path + trPath.bridgeName = p.processorName + trPath.fromNetwork = p.fromChain + trPath.toNetwork = p.toChain + trPath.gasFees = fees + # trPath.cost = not in use for old approach in the desktop app + value = conversion.wei2Eth(input = p.txTokenFees, decimals = p.fromToken.decimals) + trPath.tokenFees = parseFloat(value) + value = conversion.wei2Eth(input = p.txBonderFees, decimals = p.fromToken.decimals) + trPath.bonderFees = value + trPath.tokenFees += parseFloat(value) # we add bonder fees to the token fees cause in the UI, atm, we show only token fees + trPath.maxAmountIn = stint.fromHex(UInt256, "0x0") + trPath.amountIn = p.amountIn + trPath.amountOut = p.amountOut + trPath.approvalRequired = p.approvalRequired + trPath.approvalAmountRequired = p.approvalAmountRequired + trPath.approvalContractAddress = p.approvalContractAddress + trPath.amountInLocked = p.amountInLocked + trPath.estimatedTime = p.estimatedTime + trPath.gasAmount = p.txGasAmount + + value = conversion.wei2Eth(p.suggestedLevelsForMaxFeesPerGas.medium, decimals = ethDecimals) + trPath.approvalGasFees = parseFloat(value) * float64(p.approvalGasAmount) + value = conversion.wei2Eth(p.approvalL1Fee, decimals = ethDecimals) + trPath.approvalGasFees += parseFloat(value) + + trPath.isFirstSimpleTx = false + trPath.isFirstBridgeTx = false + except Exception as e: + error "Error converting to old path", msg = e.msg + + # add tx path to the list + result.add(trPath) + + result.sort(sortAsc[TransactionPathDto]) + + proc suggestedRoutesV2Ready(self: Service, uuid: string, route: seq[TransactionPathDtoV2], error: string, errCode: string) = + # TODO: refactor sending modal part of the app, but for now since we're integrating the router v2 just map params to the old dto + + var oldRoute = convertToOldRoute(route) + + let suggestedDto = SuggestedRoutesDto( + best: addFirstSimpleBridgeTxFlag(oldRoute), + gasTimeEstimate: getFeesTotal(oldRoute), + amountToReceive: getTotalAmountToReceive(oldRoute), + toNetworks: getToNetworksList(oldRoute), ) - self.threadpool.start(arg) + self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs(suggestedRoutes: suggestedDto)) + + proc suggestedRoutes*(self: Service, + sendType: SendType, + accountFrom: string, + accountTo: string, + token: string, + amountIn: string, + toToken: string = "", + amountOut: string = "", + disabledFromChainIDs: seq[int] = @[], + disabledToChainIDs: seq[int] = @[], + lockedInAmounts: Table[string, string] = initTable[string, string](), + extraParamsTable: Table[string, string] = initTable[string, string]()) = + + let + bigAmountIn = common_utils.stringToUint256(amountIn) + bigAmountOut = common_utils.stringToUint256(amountOut) + amountInHex = "0x" & eth_utils.stripLeadingZeros(bigAmountIn.toHex) + amountOutHex = "0x" & eth_utils.stripLeadingZeros(bigAmountOut.toHex) + + try: + let uuid = $genUUID() + let res = eth.suggestedRoutesV2Async(uuid, ord(sendType), accountFrom, accountTo, amountInHex, amountOutHex, token, + toToken, disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) + except CatchableError as e: + error "suggestedRoutes", exception=e.msg proc onFetchCryptoServices*(self: Service, response: string) {.slot.} = let cryptoServices = parseJson(response){"result"}.getElems().map(x => x.toCryptoRampDto()) diff --git a/src/backend/eth.nim b/src/backend/eth.nim index 1b55ef8feb..f048c4aae1 100644 --- a/src/backend/eth.nim +++ b/src/backend/eth.nim @@ -4,6 +4,18 @@ from ./gen import rpc export response_type +const + GasFeeLow* = 0 + GasFeeMedium* = 1 + GasFeeHigh* = 2 + +const + ExtraKeyUsername* = "username" + ExtraKeyPublicKey* = "publicKey" + ExtraKeyPackId* = "packID" + + ExtraKeys = @[ExtraKeyUsername, ExtraKeyPublicKey, ExtraKeyPackId] + proc getAccounts*(): RpcResponse[JsonNode] = return core.callPrivateRPC("eth_accounts") @@ -26,11 +38,56 @@ proc suggestedFees*(chainId: int): RpcResponse[JsonNode] = let payload = %* [chainId] return core.callPrivateRPC("wallet_getSuggestedFees", payload) -proc suggestedRoutes*(accountFrom: string, accountTo: string, amount: string, token: string, toToken: string, disabledFromChainIDs, - disabledToChainIDs, preferredChainIDs: seq[int], sendType: int, lockedInAmounts: var Table[string, string]): RpcResponse[JsonNode] = - let payload = %* [sendType, accountFrom, accountTo, amount, token, toToken, disabledFromChainIDs, disabledToChainIDs, - preferredChainIDs, 1, lockedInAmounts] - return core.callPrivateRPC("wallet_getSuggestedRoutes", payload) +proc prepareDataForSuggestedRoutesV2(uuid: string, sendType: int, accountFrom: string, accountTo: string, amountIn: string, amountOut: string, + token: string, toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], + extraParamsTable: Table[string, string]): JsonNode = + + let data = %* { + "uuid": uuid, + "sendType": sendType, + "addrFrom": accountFrom, + "addrTo": accountTo, + "amountIn": amountIn, + "amountOut": amountOut, + "tokenID": token, + "toTokenID": toToken, + "disabledFromChainIDs": disabledFromChainIDs, + "disabledToChainIDs": disabledToChainIDs, + "gasFeeMode": GasFeeMedium, + "fromLockedAmount": lockedInAmounts + } + + # `extraParamsTable` is used for send types like EnsRegister, EnsRelease, EnsSetPubKey, StickersBuy + # keys that can be used in `extraParamsTable` are: + # "username", "publicKey", "packID" + for key, value in extraParamsTable: + if key in ExtraKeys: + data[key] = %* value + else: + return nil + + return %* [data] + +proc suggestedRoutesV2*(sendType: int, accountFrom: string, accountTo: string, amountIn: string, amountOut: string, token: string, + toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], + extraParamsTable: Table[string, string]): RpcResponse[JsonNode] {.raises: [RpcException].} = + let payload = prepareDataForSuggestedRoutesV2(uuid = "", sendType, accountFrom, accountTo, amountIn, amountOut, token, toToken, disabledFromChainIDs, + disabledToChainIDs, lockedInAmounts, extraParamsTable) + if payload.isNil: + raise newException(RpcException, "Invalid key in extraParamsTable") + return core.callPrivateRPC("wallet_getSuggestedRoutesV2", payload) + +proc suggestedRoutesV2Async*(uuid: string, sendType: int, accountFrom: string, accountTo: string, amountIn: string, amountOut: string, token: string, + toToken: string, disabledFromChainIDs, disabledToChainIDs: seq[int], lockedInAmounts: Table[string, string], + extraParamsTable: Table[string, string]): RpcResponse[JsonNode] {.raises: [RpcException].} = + let payload = prepareDataForSuggestedRoutesV2(uuid, sendType, accountFrom, accountTo, amountIn, amountOut, token, toToken, disabledFromChainIDs, + disabledToChainIDs, lockedInAmounts, extraParamsTable) + if payload.isNil: + raise newException(RpcException, "Invalid key in extraParamsTable") + return core.callPrivateRPC("wallet_getSuggestedRoutesV2Async", payload) + +proc stopSuggestedRoutesV2AsyncCalcualtion*() : RpcResponse[JsonNode] = + return core.callPrivateRPC("wallet_stopSuggestedRoutesV2AsyncCalcualtion") rpc(getEstimatedLatestBlockNumber, "wallet"): chainId: int diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml index 7703dc1d8f..6d622bf46d 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml @@ -188,10 +188,6 @@ QObject { return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options) } - function getAllChainIds() { - return ModelUtils.joinModelEntries(root.filteredFlatNetworksModel, "chainId", ":") - } - function getDisabledChainIds(enabledChainId) { let disabledChainIds = [] let chainIds = ModelUtils.modelToFlatArray(root.filteredFlatNetworksModel, "chainId") @@ -203,8 +199,8 @@ QObject { return disabledChainIds.join(":") } - function fetchSuggestedRoutes(cryptoValueRaw) { - if (root.swapFormData.isFormFilledCorrectly() && !!cryptoValueRaw) { + function fetchSuggestedRoutes(cryptoValueInRaw) { + if (root.swapFormData.isFormFilledCorrectly() && !!cryptoValueInRaw) { root.swapProposalLoading = true root.swapOutputData.reset() @@ -214,12 +210,10 @@ QObject { let account = selectedAccountEntry.item let accountAddress = account.address let disabledChainIds = getDisabledChainIds(root.swapFormData.selectedNetworkChainId) - let preferedChainIds = getAllChainIds() root.swapStore.fetchSuggestedRoutes(accountAddress, accountAddress, - cryptoValueRaw, root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, - disabledChainIds, disabledChainIds, preferedChainIds, - Constants.SendType.Swap, "") + cryptoValueInRaw, "0", root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, + disabledChainIds, disabledChainIds, Constants.SendType.Swap, "") } else { root.swapProposalLoading = false } @@ -230,7 +224,7 @@ QObject { let accountAddress = account.address root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress, - root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, + root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, Constants.SendType.Approve, "", false, root.swapOutputData.rawPaths, "") } @@ -239,7 +233,7 @@ QObject { let accountAddress = account.address root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress, - root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, + root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey, Constants.SendType.Swap, "", false, root.swapOutputData.rawPaths, root.swapFormData.selectedSlippage) } } diff --git a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml index bca1234a0a..2a0d2cc820 100644 --- a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml @@ -25,11 +25,12 @@ QtObject { } } - function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo, - disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) { - const value = AmountsArithmetic.fromNumber(amount) - root.walletSectionSendInst.fetchSuggestedRoutesWithParameters(accountFrom, accountTo, value.toFixed(), - tokenFrom, tokenTo, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) + function fetchSuggestedRoutes(accountFrom, accountTo, amountIn, amountOut, tokenFrom, tokenTo, + disabledFromChainIDs, disabledToChainIDs, sendType, lockedInAmounts) { + const valueIn = AmountsArithmetic.fromNumber(amountIn) + const valueOut = AmountsArithmetic.fromNumber(amountOut) + root.walletSectionSendInst.fetchSuggestedRoutesWithParameters(accountFrom, accountTo, valueIn.toFixed(), valueOut.toFixed(), + tokenFrom, tokenTo, disabledFromChainIDs, disabledToChainIDs, sendType, lockedInAmounts) } function authenticateAndTransfer(uuid, accountFrom, accountTo, diff --git a/ui/imports/shared/popups/send/controls/GasSelector.qml b/ui/imports/shared/popups/send/controls/GasSelector.qml index 349ee1bbec..20f59f6aeb 100644 --- a/ui/imports/shared/popups/send/controls/GasSelector.qml +++ b/ui/imports/shared/popups/send/controls/GasSelector.qml @@ -57,9 +57,9 @@ Item { return fee } property double totalGasAmountL1Eth: { - let maxFees = modelData.gasFees.maxFeePerGasM - let gasPrice = modelData.gasFees.eip1559Enabled? maxFees : modelData.gasFees.gasPrice - return root.getGasEthValue(gasPrice , modelData.gasFees.l1GasFee) + const l1FeeInGWei = modelData.gasFees.l1GasFee + const l1FeeInEth = globalUtils.wei2Eth(l1FeeInGWei, 9) + return l1FeeInEth } property double totalGasAmountEth: { diff --git a/ui/imports/shared/stores/send/TransactionStore.qml b/ui/imports/shared/stores/send/TransactionStore.qml index b1a444c269..284f2a4bc0 100644 --- a/ui/imports/shared/stores/send/TransactionStore.qml +++ b/ui/imports/shared/stores/send/TransactionStore.qml @@ -66,9 +66,10 @@ QtObject { walletSectionSendInst.authenticateAndTransfer(uuid) } - function suggestedRoutes(amount) { - const value = AmountsArithmetic.fromNumber(amount) - walletSectionSendInst.suggestedRoutes(value.toFixed()) + function suggestedRoutes(amountIn, amountOut = "0", extraParamsJson = "") { + const valueIn = AmountsArithmetic.fromNumber(amountIn) + const valueOut = AmountsArithmetic.fromNumber(amountOut) + walletSectionSendInst.suggestedRoutes(valueIn.toFixed(), valueOut.toFixed(), extraParamsJson) } function resolveENS(value) { diff --git a/vendor/status-go b/vendor/status-go index e6bf7e7df9..4d7c2683f5 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit e6bf7e7df9939efb05b0349a87c163540fe08dd9 +Subproject commit 4d7c2683f5536c2b5a10a3b7d2230ec9c8860f0f