feat(@desktop/wallet): Create a new send module to clear out old logic and switch the old one to the new one later, once the old sendModal is not used anymore

fixes #16919
This commit is contained in:
Khushboo Mehta 2024-12-19 12:46:12 +01:00 committed by Khushboo-dev-cpp
parent 751f27498c
commit 37a06fc3be
18 changed files with 1289 additions and 65 deletions

View File

@ -70,6 +70,9 @@ method buySellCryptoModuleDidLoad*(self: AccessInterface) {.base.} =
method sendModuleDidLoad*(self: AccessInterface) {.base.} = method sendModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method newSendModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method overviewModuleDidLoad*(self: AccessInterface) {.base.} = method overviewModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -13,6 +13,7 @@ import ./buy_sell_crypto/module as buy_sell_crypto_module
import ./networks/module as networks_module import ./networks/module as networks_module
import ./overview/module as overview_module import ./overview/module as overview_module
import ./send/module as send_module import ./send/module as send_module
import ./send_new/module as new_send_module
import ./activity/controller as activityc import ./activity/controller as activityc
@ -70,6 +71,8 @@ type
allCollectiblesModule: all_collectibles_module.AccessInterface allCollectiblesModule: all_collectibles_module.AccessInterface
assetsModule: assets_module.AccessInterface assetsModule: assets_module.AccessInterface
sendModule: send_module.AccessInterface sendModule: send_module.AccessInterface
# TODO: replace this with sendModule when old one is removed
newSendModule: new_send_module.AccessInterface
savedAddressesModule: saved_addresses_module.AccessInterface savedAddressesModule: saved_addresses_module.AccessInterface
buySellCryptoModule: buy_sell_crypto_module.AccessInterface buySellCryptoModule: buy_sell_crypto_module.AccessInterface
overviewModule: overview_module.AccessInterface overviewModule: overview_module.AccessInterface
@ -141,6 +144,7 @@ proc newModule*(
currencyService) currencyService)
result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService, result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService,
transactionService, keycardService) transactionService, keycardService)
result.newSendModule = newSendModule.newModule(result, events, walletAccountService, networkService, transactionService, keycardService)
result.savedAddressesModule = saved_addresses_module.newModule(result, events, savedAddressService) result.savedAddressesModule = saved_addresses_module.newModule(result, events, savedAddressService)
result.buySellCryptoModule = buy_sell_crypto_module.newModule(result, events, rampService) result.buySellCryptoModule = buy_sell_crypto_module.newModule(result, events, rampService)
result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService) result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService)
@ -185,6 +189,7 @@ method delete*(self: Module) =
self.savedAddressesModule.delete self.savedAddressesModule.delete
self.buySellCryptoModule.delete self.buySellCryptoModule.delete
self.sendModule.delete self.sendModule.delete
self.newSendModule.delete
self.controller.delete self.controller.delete
self.viewVariant.delete self.viewVariant.delete
self.view.delete self.view.delete
@ -351,6 +356,7 @@ method load*(self: Module) =
self.buySellCryptoModule.load() self.buySellCryptoModule.load()
self.overviewModule.load() self.overviewModule.load()
self.sendModule.load() self.sendModule.load()
self.newSendModule.load()
self.networksModule.load() self.networksModule.load()
self.walletConnectService.init() self.walletConnectService.init()
self.walletConnectController.init() self.walletConnectController.init()
@ -384,6 +390,9 @@ proc checkIfModuleDidLoad(self: Module) =
if(not self.sendModule.isLoaded()): if(not self.sendModule.isLoaded()):
return return
if(not self.newSendModule.isLoaded()):
return
if(not self.networksModule.isLoaded()): if(not self.networksModule.isLoaded()):
return return
@ -432,6 +441,9 @@ method overviewModuleDidLoad*(self: Module) =
method sendModuleDidLoad*(self: Module) = method sendModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()
method newSendModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method networksModuleDidLoad*(self: Module) = method networksModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()

View File

@ -0,0 +1,148 @@
import Tables
import uuids, chronicles
import io_interface
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/network/service as network_service
import app_service/service/transaction/service as transaction_service
import app_service/service/keycard/service as keycard_service
import app_service/service/network/network_item
import app/core/eventemitter
logScope:
topics = "wallet-send-controller"
const UNIQUE_WALLET_SECTION_SEND_MODULE_IDENTIFIER* = "WalletSection-NewSendModule"
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
walletAccountService: wallet_account_service.Service
networkService: network_service.Service
transactionService: transaction_service.Service
keycardService: keycard_service.Service
connectionKeycardResponse: UUID
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
walletAccountService: wallet_account_service.Service,
networkService: network_service.Service,
transactionService: transaction_service.Service,
keycardService: keycard_service.Service
): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.walletAccountService = walletAccountService
result.networkService = networkService
result.transactionService = transactionService
result.keycardService = keycardService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.events.on(SIGNAL_TRANSACTION_SENT) do(e:Args):
let args = TransactionArgs(e)
var
txHash = ""
isApprovalTx = false
if not args.sentTransaction.isNil:
txHash = args.sentTransaction.hash
isApprovalTx = args.sentTransaction.approvalTx
self.delegate.transactionWasSent(
args.sendDetails.uuid,
args.sendDetails.fromChain,
isApprovalTx,
txHash,
if not args.sendDetails.errorResponse.isNil: args.sendDetails.errorResponse.details else: ""
)
self.events.on(SIGNAL_OWNER_TOKEN_SENT) do(e:Args):
let args = OwnerTokenSentArgs(e)
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)
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_SEND_MODULE_IDENTIFIER:
return
self.delegate.onUserAuthenticated(args.password, args.pin)
self.events.on(SIGNAL_SUGGESTED_ROUTES_READY) do(e:Args):
let args = SuggestedRoutesArgs(e)
self.delegate.suggestedRoutesReady(args.uuid, args.routes, args.errCode, args.errDescription)
self.events.on(SIGNAL_SIGN_ROUTER_TRANSACTIONS) do(e:Args):
var data = RouterTransactionsForSigningArgs(e)
self.delegate.prepareSignaturesForTransactions(data.data)
self.events.on(SIGNAL_TRANSACTION_STATUS_CHANGED) do(e:Args):
let args = TransactionArgs(e)
self.delegate.transactionSendingComplete(args.sentTransaction.hash, args.status)
proc authenticate*(self: Controller, keyUid = "") =
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_WALLET_SECTION_SEND_MODULE_IDENTIFIER,
keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
proc suggestedRoutes*(self: Controller,
uuid: string,
sendType: SendType,
accountFrom: string,
accountTo: string,
token: string,
tokenIsOwnerToken: bool,
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(uuid, sendType, accountFrom, accountTo, token, tokenIsOwnerToken, amountIn, toToken, amountOut,
disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable)
proc stopSuggestedRoutesAsyncCalculation*(self: Controller) =
self.transactionService.stopSuggestedRoutesAsyncCalculation()
proc getCurrentNetworks*(self: Controller): seq[NetworkItem] =
return self.networkService.getCurrentNetworks()
proc buildTransactionsFromRoute*(self: Controller, uuid: string, slippagePercentage: float): string =
return self.transactionService.buildTransactionsFromRoute(uuid, slippagePercentage)
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 getKeypairByAccountAddress*(self: Controller, address: string): KeypairDto =
return self.walletAccountService.getKeypairByAccountAddress(address)
proc disconnectKeycardReponseSignal(self: Controller) =
self.events.disconnect(self.connectionKeycardResponse)
proc connectKeycardReponseSignal(self: Controller) =
self.connectionKeycardResponse = self.events.onWithUUID(SIGNAL_KEYCARD_RESPONSE) do(e: Args):
let args = KeycardLibArgs(e)
self.disconnectKeycardReponseSignal()
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(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)
proc cancelCurrentFlow*(self: Controller) =
self.keycardService.cancelCurrentFlow()
proc runSignFlow*(self: Controller, pin, bip44Path, txHash: string) =
self.cancelCurrentFlow()
self.connectKeycardReponseSignal()
self.keycardService.startSignFlow(bip44Path, txHash, pin)

View File

@ -0,0 +1,66 @@
import Tables
import app_service/service/transaction/dto
import app_service/service/transaction/router_transactions_dto
import app_service/service/transaction/dtoV2
from app_service/service/keycard/service import KeycardEvent
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method suggestedRoutes*(self: AccessInterface,
uuid: string,
sendType: SendType,
chainId: int,
accountFrom: string,
accountTo: string,
token: string,
tokenIsOwnerToken: bool,
amountIn: string,
toToken: string = "",
amountOut: string = "",
extraParamsTable: Table[string, string] = initTable[string, string]()) {.base.} =
raise newException(ValueError, "No implementation available")
method stopUpdatesForSuggestedRoute*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method authenticateAndTransfer*(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 suggestedRoutesReady*(self: AccessInterface, uuid: string, routes: seq[TransactionPathDtoV2], errCode: string, errDescription: string) {.base.} =
raise newException(ValueError, "No implementation available")
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.} =
raise newException(ValueError, "No implementation available")
method authenticateUser*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onUserAuthenticated*(self: AccessInterface, pin: string, password: string, keyUid: string) {.base.} =
raise newException(ValueError, "No implementation available")
method prepareSignaturesForTransactions*(self:AccessInterface, txForSigning: RouterTransactionsForSigningDto) {.base.} =
raise newException(ValueError, "No implementation available")
method onTransactionSigned*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} =
raise newException(ValueError, "No implementation available")
method transactionSendingComplete*(self: AccessInterface, txHash: string, status: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,50 @@
import NimQml
QtObject:
type MaxFeeLevelsItem* = ref object of QObject
low: string
medium: string
high: string
proc setup*(self: MaxFeeLevelsItem,
low: string,
medium: string,
high: string
) =
self.QObject.setup
self.low = low
self.medium = medium
self.high = high
proc delete*(self: MaxFeeLevelsItem) =
self.QObject.delete
proc newMaxFeeLevelsItem*(
low: string,
medium: string,
high: string
): MaxFeeLevelsItem =
new(result, delete)
result.setup(low, medium, high)
proc `$`*(self: MaxFeeLevelsItem): string =
result = "MaxFeeLevelsItem("
result = result & "\low: " & $self.low
result = result & "\nmedium " & $self.medium
result = result & "\nhigh: " & $self.high
result = result & ")"
proc getLow*(self: MaxFeeLevelsItem): string {.slot.} =
return self.low
QtProperty[string] low:
read = getLow
proc getMedium*(self: MaxFeeLevelsItem): string {.slot.} =
return self.medium
QtProperty[string] medium:
read = getMedium
proc getHigh*(self: MaxFeeLevelsItem): string {.slot.} =
return self.high
QtProperty[string] high:
read = getHigh

View File

@ -0,0 +1,314 @@
import tables, NimQml, sequtils, sugar, strutils, chronicles, stint
import ./io_interface, ./view, ./controller, ./max_fee_levels_item, ./path_item
import ../io_interface as delegate_interface
import app/global/global_singleton
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/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/token/utils
export io_interface
logScope:
topics = "wallet-send-module"
const authenticationCanceled* = "authenticationCanceled"
# Shouldn't be public ever, use only within this module.
type TmpSendTransactionDetails = object
fromAddrPath: string
uuid: string
sendType: SendType
pin: string
password: string
txHashBeingProcessed: string
resolvedSignatures: TransactionsSignatures
slippagePercentage: float
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
events: EventEmitter
view: View
viewVariant: QVariant
controller: controller.Controller
moduleLoaded: bool
tmpSendTransactionDetails: TmpSendTransactionDetails
tmpKeepPinPass: bool
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
walletAccountService: wallet_account_service.Service,
networkService: network_service.Service,
transactionService: transaction_service.Service,
keycardService: keycard_service.Service
): Module =
result = Module()
result.delegate = delegate
result.events = events
result.controller = controller.newController(result, events, walletAccountService,
networkService, transactionService, keycardService)
result.view = newView(result)
result.viewVariant = newQVariant(result.view)
result.moduleLoaded = false
method delete*(self: Module) =
self.viewVariant.delete
self.view.delete
self.controller.delete
proc clearTmpData(self: Module, keepPinPass = false) =
if keepPinPass:
self.tmpSendTransactionDetails = TmpSendTransactionDetails(
sendType: self.tmpSendTransactionDetails.sendType,
pin: self.tmpSendTransactionDetails.pin,
password: self.tmpSendTransactionDetails.password
)
return
self.tmpSendTransactionDetails = TmpSendTransactionDetails()
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("walletSectionSendNew", self.viewVariant)
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.sendModuleDidLoad()
proc convertFeeLevelsDtoToMaxFeeLevelsItem(self: Module, feeLevels: SuggestedLevelsForMaxFeesPerGasDto): MaxFeeLevelsItem =
result = newMaxFeeLevelsItem(
low = $feeLevels.low,
medium = $feeLevels.medium,
high = $feeLevels.high
)
proc convertTransactionPathDtoV2ToPathItem(self: Module, txPath: TransactionPathDtoV2): PathItem =
var fromChainId = 0
var toChainid = 0
var fromTokenSymbol = ""
var toTokenSymbol = ""
if not txPath.fromChain.isNil:
fromChainId = txPath.fromChain.chainId
if not txPath.toChain.isNil:
toChainId = txPath.toChain.chainId
if not txPath.fromToken.isNil:
fromTokenSymbol = txPath.fromToken.bySymbolModelKey()
if not txPath.toToken.isNil:
toTokenSymbol = txPath.toToken.bySymbolModelKey()
result = newPathItem(
processorName = txPath.processorName,
fromChain = fromChainId,
toChain = toChainId,
fromToken = fromTokenSymbol,
toToken = toTokenSymbol,
amountIn = $txPath.amountIn,
amountInLocked = txPath.amountInLocked,
amountOut = $txPath.amountOut,
suggestedLevelsForMaxFeesPerGas = self.convertFeeLevelsDtoToMaxFeeLevelsItem(txPath.suggestedLevelsForMaxFeesPerGas),
maxFeesPerGas = $txPath.maxFeesPerGas,
txBaseFee = $txPath.txBaseFee,
txPriorityFee = $txPath.txPriorityFee,
txGasAmount = $txPath.txGasAmount,
txBonderFees = $txPath.txBonderFees,
txTokenFees = $txPath.txTokenFees,
txFee = $txPath.txFee,
txL1Fee = $txPath.txL1Fee,
approvalRequired = txPath.approvalRequired,
approvalAmountRequired = $txPath.approvalAmountRequired,
approvalContractAddress = txPath.approvalContractAddress,
approvalBaseFee = $txPath.approvalBaseFee,
approvalPriorityFee = $txPath.approvalPriorityFee,
approvalGasAmount = $txPath.approvalGasAmount,
approvalFee = $txPath.approvalFee,
approvalL1Fee = $txPath.approvalL1Fee,
txTotalFee = $txPath.txTotalFee,
estimatedTime = txPath.estimatedTime
)
proc buildTransactionsFromRoute(self: Module) =
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()
method authenticateAndTransfer*(self: Module, uuid: string, fromAddr: string, slippagePercentage: float) =
self.tmpSendTransactionDetails.uuid = uuid
self.tmpSendTransactionDetails.slippagePercentage = slippagePercentage
self.tmpSendTransactionDetails.resolvedSignatures.clear()
if self.tmpKeepPinPass:
# no need to authenticate again, just send a swap tx
self.buildTransactionsFromRoute()
return
let kp = self.controller.getKeypairByAccountAddress(fromAddr)
if kp.migratedToKeycard():
let accounts = kp.accounts.filter(acc => cmpIgnoreCase(acc.address, fromAddr) == 0)
if accounts.len != 1:
error "cannot resolve selected account to send from among known keypair accounts"
return
self.controller.authenticate(kp.keyUid)
else:
self.controller.authenticate()
method onUserAuthenticated*(self: Module, password: string, pin: string) =
if password.len == 0:
self.transactionWasSent(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = authenticationCanceled)
self.clearTmpData()
else:
self.tmpSendTransactionDetails.pin = pin
self.tmpSendTransactionDetails.password = password
self.buildTransactionsFromRoute()
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.tmpSendTransactionDetails.txHashBeingProcessed = ""
for h, (r, s, v) in self.tmpSendTransactionDetails.resolvedSignatures.pairs:
if r.len != 0 and s.len != 0 and v.len != 0:
continue
self.tmpSendTransactionDetails.txHashBeingProcessed = h
var txForKcFlow = self.tmpSendTransactionDetails.txHashBeingProcessed
if txForKcFlow.startsWith("0x"):
txForKcFlow = txForKcFlow[2..^1]
self.controller.runSignFlow(self.tmpSendTransactionDetails.pin, self.tmpSendTransactionDetails.fromAddrPath, txForKcFlow)
break
if self.tmpSendTransactionDetails.txHashBeingProcessed.len == 0:
self.sendSignedTransactions()
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:
var finalPassword = self.tmpSendTransactionDetails.password
if not singletonInstance.userProfile.getIsKeycardUser():
finalPassword = hashPassword(self.tmpSendTransactionDetails.password)
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:
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.tmpSendTransactionDetails.txHashBeingProcessed] = (keycardEvent.txSignature.r,
keycardEvent.txSignature.s, keycardEvent.txSignature.v)
self.signOnKeycard()
method transactionWasSent*(self: Module, uuid: string, chainId: int = 0, approvalTx: bool = false, txHash: string = "", error: string = "") =
self.tmpKeepPinPass = approvalTx # need to automate the swap flow with approval
defer:
self.clearTmpData(self.tmpKeepPinPass)
if txHash.len == 0:
self.view.sendTransactionSentSignal(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error)
return
self.view.sendTransactionSentSignal(uuid, chainId, approvalTx, txHash, error)
method suggestedRoutesReady*(self: Module, uuid: string, routes: seq[TransactionPathDtoV2], errCode: string, errDescription: string) =
let paths = routes.map(x => self.convertTransactionPathDtoV2ToPathItem(x))
self.view.setTransactionRoute(uuid, paths, errCode, errDescription)
method suggestedRoutes*(self: Module,
uuid: string,
sendType: SendType,
chainId: int,
accountFrom: string,
accountTo: string,
token: string,
tokenIsOwnerToken: bool,
amountIn: string,
toToken: string = "",
amountOut: string = "",
extraParamsTable: Table[string, string] = initTable[string, string]()) =
# maybe not needed
# self.tmpSendTransactionDetails.sendType = sendType
var lockedInAmountsTable = Table[string, string] : initTable[string, string]()
let networks = self.controller.getCurrentNetworks()
let disabledNetworks = networks.filter(x => x.chainId != chainId).map(x => x.chainId)
self.controller.suggestedRoutes(
uuid,
sendType,
accountFrom,
accountTo,
token,
tokenIsOwnerToken,
amountIn,
toToken,
amountOut,
disabledNetworks,
disabledNetworks,
lockedInAmountsTable,
extraParamsTable
)
method stopUpdatesForSuggestedRoute*(self: Module) =
self.controller.stopSuggestedRoutesAsyncCalculation()
method transactionSendingComplete*(self: Module, txHash: string, status: string) =
self.view.sendtransactionSendingCompleteSignal(txHash, status)

View File

@ -0,0 +1,243 @@
import NimQml
import ./max_fee_levels_item
QtObject:
type PathItem* = ref object of QObject
processorName: string
fromChain: int
toChain: int
fromToken: string
toToken: string
amountIn: string
amountInLocked: bool
amountOut: string
suggestedLevelsForMaxFeesPerGas: MaxFeeLevelsItem
maxFeesPerGas: string
txBaseFee: string
txPriorityFee: string
txGasAmount: string
txBonderFees: string
txTokenFees: string
txFee: string
txL1Fee: string
txTotalFee: string
estimatedTime: int
approvalRequired: bool
approvalAmountRequired : string
approvalContractAddress: string
approvalBaseFee: string
approvalPriorityFee: string
approvalGasAmount: string
approvalFee: string
approvalL1Fee: string
proc setup*(self: PathItem,
processorName: string,
fromChain: int,
toChain: int,
fromToken: string,
toToken: string,
amountIn: string,
amountInLocked: bool,
amountOut: string,
suggestedLevelsForMaxFeesPerGas: MaxFeeLevelsItem,
maxFeesPerGas: string,
txBaseFee: string,
txPriorityFee: string,
txGasAmount: string,
txBonderFees: string,
txTokenFees: string,
txFee: string,
txL1Fee: string,
txTotalFee: string,
estimatedTime: int,
approvalRequired: bool,
approvalAmountRequired: string,
approvalContractAddress: string,
approvalBaseFee: string,
approvalPriorityFee: string,
approvalGasAmount: string,
approvalFee: string,
approvalL1Fee: string
) =
self.QObject.setup
self.processorName = processorName
self.fromChain = fromChain
self.toChain = toChain
self.fromToken = fromToken
self.toToken = toToken
self.amountIn = amountIn
self.amountInLocked = amountInLocked
self.amountOut = amountOut
self.suggestedLevelsForMaxFeesPerGas = suggestedLevelsForMaxFeesPerGas
self.maxFeesPerGas = maxFeesPerGas
self.txBaseFee = txBaseFee
self.txPriorityFee = txPriorityFee
self.txGasAmount = txGasAmount
self.txBonderFees = txBonderFees
self.txTokenFees = txTokenFees
self.txFee = txFee
self.txL1Fee = txL1Fee
self.txTotalFee = txTotalFee
self.estimatedTime = estimatedTime
self.approvalRequired = approvalRequired
self.approvalAmountRequired = approvalAmountRequired
self.approvalContractAddress = approvalContractAddress
self.approvalBaseFee = approvalBaseFee
self.approvalPriorityFee = approvalPriorityFee
self.approvalGasAmount = approvalGasAmount
self.approvalFee = approvalFee
self.approvalL1Fee = approvalL1Fee
proc delete*(self: PathItem) =
self.QObject.delete
proc newPathItem*(
processorName: string,
fromChain: int,
toChain: int,
fromToken: string,
toToken: string,
amountIn: string,
amountInLocked: bool,
amountOut: string,
suggestedLevelsForMaxFeesPerGas: MaxFeeLevelsItem,
maxFeesPerGas: string,
txBaseFee: string,
txPriorityFee: string,
txGasAmount: string,
txBonderFees: string,
txTokenFees: string,
txFee: string,
txL1Fee: string,
txTotalFee: string,
estimatedTime: int,
approvalRequired: bool,
approvalAmountRequired: string,
approvalContractAddress: string,
approvalBaseFee: string,
approvalPriorityFee: string,
approvalGasAmount: string,
approvalFee: string,
approvalL1Fee: string
): PathItem =
new(result, delete)
result.setup(processorName, fromChain, toChain, fromToken, toToken,
amountIn, amountInLocked, amountOut, suggestedLevelsForMaxFeesPerGas,
maxFeesPerGas, txBaseFee,txPriorityFee, txGasAmount, txBonderFees,
txTokenFees, txFee, txL1Fee, txTotalFee, estimatedTime, approvalRequired,
approvalAmountRequired, approvalContractAddress, approvalBaseFee,
approvalPriorityFee, approvalGasAmount, approvalFee, approvalL1Fee)
proc `$`*(self: PathItem): string =
result = "PathItem("
result &= "\nprocessorName: " & $self.processorName
result &= "\nfromChain: " & $self.fromChain
result &= "\ntoChain: " & $self.toChain
result &= "\nfromToken: " & $self.fromToken
result &= "\ntoToken: " & $self.toToken
result &= "\namountIn: " & $self.amountIn
result &= "\namountInLocked: " & $self.amountInLocked
result &= "\namountOut: " & $self.amountOut
result &= "\nsuggestedLevelsForMaxFeesPerGas: " & $self.suggestedLevelsForMaxFeesPerGas
result &= "\nmaxFeesPerGas: " & $self.maxFeesPerGas
result &= "\ntxBaseFee: " & $self.txBaseFee
result &= "\ntxPriorityFee: " & $self.txPriorityFee
result &= "\ntxGasAmount: " & $self.txGasAmount
result &= "\ntxBonderFees: " & $self.txBonderFees
result &= "\ntxTokenFees: " & $self.txTokenFees
result &= "\ntxFee: " & $self.txFee
result &= "\ntxL1Fee: " & $self.txL1Fee
result &= "\ntxTotalFee: " & $self.txTotalFee
result &= "\nestimatedTime: " & $self.estimatedTime
result &= "\napprovalRequired: " & $self.approvalRequired
result &= "\napprovalAmountRequired: " & $self.approvalAmountRequired
result &= "\napprovalContractAddress: " & $self.approvalContractAddress
result &= "\napprovalBaseFee: " & $self.approvalBaseFee
result &= "\napprovalPriorityFee: " & $self.approvalPriorityFee
result &= "\napprovalGasAmount: " & $self.approvalGasAmount
result &= "\napprovalFee: " & $self.approvalFee
result &= "\napprovalL1Fee: " & $self.approvalL1Fee
result &= ")"
proc processorName*(self: PathItem): string =
return self.processorName
proc fromChain*(self: PathItem): int =
return self.fromChain
proc toChain*(self: PathItem): int =
return self.toChain
proc fromToken*(self: PathItem): string =
return self.fromToken
proc toToken*(self: PathItem): string =
return self.toToken
proc amountIn*(self: PathItem): string =
return self.amountIn
proc amountInLocked*(self: PathItem): bool =
return self.amountInLocked
proc amountOut*(self: PathItem): string =
return self.amountOut
proc suggestedLevelsForMaxFeesPerGas*(self: PathItem): MaxFeeLevelsItem =
return self.suggestedLevelsForMaxFeesPerGas
proc maxFeesPerGas*(self: PathItem): string =
return self.maxFeesPerGas
proc txBaseFee*(self: PathItem): string =
return self.txBaseFee
proc txPriorityFee*(self: PathItem): string =
return self.txPriorityFee
proc txGasAmount*(self: PathItem): string =
return self.txGasAmount
proc txBonderFees*(self: PathItem): string =
return self.txBonderFees
proc txTokenFees*(self: PathItem): string =
return self.txTokenFees
proc txFee*(self: PathItem): string =
return self.txFee
proc txL1Fee*(self: PathItem): string =
return self.txL1Fee
proc txTotalFee*(self: PathItem): string =
return self.txTotalFee
proc estimatedTime*(self: PathItem): int =
return self.estimatedTime
proc approvalRequired*(self: PathItem): bool =
return self.approvalRequired
proc approvalAmountRequired*(self: PathItem): string =
return self.approvalAmountRequired
proc approvalContractAddress*(self: PathItem): string =
return self.approvalContractAddress
proc approvalBaseFee*(self: PathItem): string =
return self.approvalBaseFee
proc approvalPriorityFee*(self: PathItem): string =
return self.approvalPriorityFee
proc approvalGasAmount*(self: PathItem): string =
return self.approvalGasAmount
proc approvalFee*(self: PathItem): string =
return self.approvalFee
proc approvalL1Fee*(self: PathItem): string =
return self.approvalL1Fee

View File

@ -0,0 +1,160 @@
import NimQml, Tables, strutils, stew/shims/strformat
import ./path_item
type
ModelRole {.pure.} = enum
ProcessorName = UserRole + 1,
FromChain,
ToChain,
FromToken,
ToToken,
AmountIn,
AmountInLocked,
AmountOut,
SuggestedLevelsForMaxFeesPerGas,
MaxFeesPerGas,
TxBaseFee,
TxPriorityFee,
TxGasAmount,
TxBonderFees,
TxTokenFees,
TxFee,
TxL1Fee,
TxTotalFee,
EstimatedTime,
ApprovalRequired,
ApprovalAmountRequired,
ApprovalContractAddress,
ApprovalBaseFee,
ApprovalPriorityFee,
ApprovalGasAmount,
ApprovalFee,
ApprovalL1Fee
QtObject:
type
PathModel* = ref object of QAbstractListModel
items*: seq[PathItem]
proc delete(self: PathModel) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: PathModel) =
self.QAbstractListModel.setup
proc newPathModel*(): PathModel =
new(result, delete)
result.setup
proc `$`*(self: PathModel): string =
for i in 0 ..< self.items.len:
result &= fmt"""[{i}]:({$self.items[i]})"""
method rowCount(self: PathModel, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: PathModel): Table[int, string] =
{
ModelRole.ProcessorName.int: "processorName",
ModelRole.FromChain.int: "fromChain",
ModelRole.ToChain.int: "toChain",
ModelRole.FromToken.int: "fromToken",
ModelRole.ToToken.int: "toToken",
ModelRole.AmountIn.int: "amountIn",
ModelRole.AmountInLocked.int: "amountInLocked",
ModelRole.AmountOut.int: "amountOut",
ModelRole.SuggestedLevelsForMaxFeesPerGas.int: "suggestedLevelsForMaxFeesPerGas",
ModelRole.MaxFeesPerGas.int: "maxFeesPerGas",
ModelRole.TxBaseFee.int: "txBaseFee",
ModelRole.TxPriorityFee.int: "txPriorityFee",
ModelRole.TxGasAmount.int: "txGasAmount",
ModelRole.TxBonderFees.int: "txBonderFees",
ModelRole.TxTokenFees.int: "txTokenFees",
ModelRole.TxFee.int: "txFee",
ModelRole.TxL1Fee.int: "txL1Fee",
ModelRole.TxTotalFee.int: "txTotalFee",
ModelRole.EstimatedTime.int: "estimatedTime",
ModelRole.ApprovalRequired.int: "approvalRequired",
ModelRole.ApprovalAmountRequired.int: "approvalAmountRequired",
ModelRole.ApprovalContractAddress.int: "approvalContractAddress",
ModelRole.ApprovalBaseFee.int: "approvalBaseFee",
ModelRole.ApprovalPriorityFee.int: "approvalPriorityFee",
ModelRole.ApprovalGasAmount.int: "approvalGasAmount",
ModelRole.ApprovalFee.int: "approvalFee",
ModelRole.ApprovalL1Fee.int: "approvalL1Fee",
}.toTable
proc setItems*(self: PathModel, items: seq[PathItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
method data(self: PathModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.ProcessorName:
result = newQVariant(item.processorName)
of ModelRole.FromChain:
result = newQVariant(item.fromChain)
of ModelRole.ToChain:
result = newQVariant(item.toChain)
of ModelRole.FromToken:
result = newQVariant(item.fromToken)
of ModelRole.ToToken:
result = newQVariant(item.toToken)
of ModelRole.AmountIn:
result = newQVariant(item.amountIn)
of ModelRole.AmountInLocked:
result = newQVariant(item.amountInLocked)
of ModelRole.AmountOut:
result = newQVariant(item.amountOut)
of ModelRole.SuggestedLevelsForMaxFeesPerGas:
result = newQVariant(item.suggestedLevelsForMaxFeesPerGas)
of ModelRole.MaxFeesPerGas:
result = newQVariant(item.maxFeesPerGas)
of ModelRole.TxBaseFee:
result = newQVariant(item.txBaseFee)
of ModelRole.TxPriorityFee:
result = newQVariant(item.txPriorityFee)
of ModelRole.TxGasAmount:
result = newQVariant(item.txGasAmount)
of ModelRole.TxBonderFees:
result = newQVariant(item.txBonderFees)
of ModelRole.TxTokenFees:
result = newQVariant(item.txTokenFees)
of ModelRole.TxFee:
result = newQVariant(item.txFee)
of ModelRole.TxL1Fee:
result = newQVariant(item.txL1Fee)
of ModelRole.TxTotalFee:
result = newQVariant(item.txTotalFee)
of ModelRole.EstimatedTime:
result = newQVariant(item.estimatedTime)
of ModelRole.ApprovalRequired:
result = newQVariant(item.approvalRequired)
of ModelRole.ApprovalAmountRequired:
result = newQVariant(item.approvalAmountRequired)
of ModelRole.ApprovalContractAddress:
result = newQVariant(item.approvalContractAddress)
of ModelRole.ApprovalBaseFee:
result = newQVariant(item.approvalBaseFee)
of ModelRole.ApprovalPriorityFee:
result = newQVariant(item.approvalPriorityFee)
of ModelRole.ApprovalGasAmount:
result = newQVariant(item.approvalGasAmount)
of ModelRole.ApprovalFee:
result = newQVariant(item.approvalFee)
of ModelRole.ApprovalL1Fee:
result = newQVariant(item.approvalL1Fee)
else:
discard

View File

@ -0,0 +1,88 @@
import NimQml, Tables, json, sequtils, strutils, stint, chronicles
import ./io_interface, ./path_model, ./path_item
import app_service/common/utils as common_utils
import app_service/service/eth/utils as eth_utils
import app_service/service/transaction/dto as transaction_dto
from backend/eth import ExtraKeyPackId
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
pathModel: PathModel
proc delete*(self: View) =
self.pathModel.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.pathModel = newPathModel()
proc load*(self: View) =
self.delegate.viewDidLoad()
proc fetchSuggestedRoutes*(self: View,
uuid: string,
sendType: int,
chainId: int,
accountFrom: string,
accountTo: string,
amountIn: string,
token: string,
amountOut: string,
toToken: string,
extraParamsJson: string) {.slot.} =
var extraParamsTable: Table[string, string]
self.pathModel.setItems(@[])
try:
if extraParamsJson.len > 0:
for key, value in parseJson(extraParamsJson):
if key == ExtraKeyPackId:
let bigPackId = common_utils.stringToUint256(value.getStr())
let packIdHex = "0x" & eth_utils.stripLeadingZeros(bigPackId.toHex)
extraParamsTable[key] = packIdHex
else:
extraParamsTable[key] = value.getStr()
except Exception as e:
error "Error parsing extraParamsJson: ", msg=e.msg
self.delegate.suggestedRoutes(
uuid,
SendType(sendType),
chainId,
accountFrom,
accountTo,
token,
false, #tokenIsOwnerToken
amountIn,
toToken,
amountOut,
extraParamsTable)
proc stopUpdatesForSuggestedRoute*(self: View) {.slot.} =
self.delegate.stopUpdatesForSuggestedRoute()
proc authenticateAndTransfer*(self: View, uuid: string, fromAddr: string, slippagePercentageString: string) {.slot.} =
var slippagePercentage: float
try:
slippagePercentage = slippagePercentageString.parseFloat()
except:
error "parsing slippage failed", slippage=slippagePercentageString
self.delegate.authenticateAndTransfer(uuid, fromAddr, slippagePercentage)
proc suggestedRoutesReady*(self: View, uuid: string, pathModel: QVariant, errCode: string, errDescription: string) {.signal.}
proc setTransactionRoute*(self: View, uuid: string, paths: seq[PathItem], errCode: string, errDescription: string) =
self.pathModel.setItems(paths)
self.suggestedRoutesReady(uuid, newQVariant(self.pathModel), errCode, errDescription)
proc transactionSendingComplete*(self: View, txHash: string, status: string) {.signal.}
proc sendtransactionSendingCompleteSignal*(self: View, txHash: string, status: string) =
self.transactionSendingComplete(txHash, status)
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)

View File

@ -30,11 +30,13 @@ type
amountInLocked*: bool amountInLocked*: bool
amountOut*: UInt256 amountOut*: UInt256
suggestedLevelsForMaxFeesPerGas*: SuggestedLevelsForMaxFeesPerGasDto suggestedLevelsForMaxFeesPerGas*: SuggestedLevelsForMaxFeesPerGasDto
maxFeesPerGas*: UInt256
txBaseFee*: UInt256 txBaseFee*: UInt256
txPriorityFee*: UInt256 txPriorityFee*: UInt256
txGasAmount*: uint64 txGasAmount*: uint64
txBonderFees*: UInt256 txBonderFees*: UInt256
txTokenFees*: UInt256 txTokenFees*: UInt256
txFee*: UInt256
txL1Fee*: UInt256 txL1Fee*: UInt256
approvalRequired*: bool approvalRequired*: bool
approvalAmountRequired*: UInt256 approvalAmountRequired*: UInt256
@ -42,7 +44,9 @@ type
approvalBaseFee*: UInt256 approvalBaseFee*: UInt256
approvalPriorityFee*: UInt256 approvalPriorityFee*: UInt256
approvalGasAmount*: uint64 approvalGasAmount*: uint64
approvalFee*: UInt256
approvalL1Fee*: UInt256 approvalL1Fee*: UInt256
txTotalFee*: UInt256
estimatedTime*: int estimatedTime*: int
proc toSuggestedLevelsForMaxFeesPerGasDto*(jsonObj: JsonNode): SuggestedLevelsForMaxFeesPerGasDto = proc toSuggestedLevelsForMaxFeesPerGasDto*(jsonObj: JsonNode): SuggestedLevelsForMaxFeesPerGasDto =
@ -66,11 +70,13 @@ proc toTransactionPathDtoV2*(jsonObj: JsonNode): TransactionPathDtoV2 =
discard jsonObj.getProp("AmountInLocked", result.amountInLocked) discard jsonObj.getProp("AmountInLocked", result.amountInLocked)
result.amountOut = stint.fromHex(UInt256, jsonObj{"AmountOut"}.getStr) result.amountOut = stint.fromHex(UInt256, jsonObj{"AmountOut"}.getStr)
result.suggestedLevelsForMaxFeesPerGas = jsonObj["SuggestedLevelsForMaxFeesPerGas"].toSuggestedLevelsForMaxFeesPerGasDto() result.suggestedLevelsForMaxFeesPerGas = jsonObj["SuggestedLevelsForMaxFeesPerGas"].toSuggestedLevelsForMaxFeesPerGasDto()
result.maxFeesPerGas = stint.fromHex(UInt256, jsonObj{"MaxFeesPerGas"}.getStr)
result.txBaseFee = stint.fromHex(UInt256, jsonObj{"TxBaseFee"}.getStr) result.txBaseFee = stint.fromHex(UInt256, jsonObj{"TxBaseFee"}.getStr)
result.txPriorityFee = stint.fromHex(UInt256, jsonObj{"TxPriorityFee"}.getStr) result.txPriorityFee = stint.fromHex(UInt256, jsonObj{"TxPriorityFee"}.getStr)
discard jsonObj.getProp("TxGasAmount", result.txGasAmount) discard jsonObj.getProp("TxGasAmount", result.txGasAmount)
result.txBonderFees = stint.fromHex(UInt256, jsonObj{"TxBonderFees"}.getStr) result.txBonderFees = stint.fromHex(UInt256, jsonObj{"TxBonderFees"}.getStr)
result.txTokenFees = stint.fromHex(UInt256, jsonObj{"TxTokenFees"}.getStr) result.txTokenFees = stint.fromHex(UInt256, jsonObj{"TxTokenFees"}.getStr)
result.txFee = stint.fromHex(UInt256, jsonObj{"TxFee"}.getStr)
result.txL1Fee = stint.fromHex(UInt256, jsonObj{"TxL1Fee"}.getStr) result.txL1Fee = stint.fromHex(UInt256, jsonObj{"TxL1Fee"}.getStr)
discard jsonObj.getProp("ApprovalRequired", result.approvalRequired) discard jsonObj.getProp("ApprovalRequired", result.approvalRequired)
result.approvalAmountRequired = stint.fromHex(UInt256, jsonObj{"ApprovalAmountRequired"}.getStr) result.approvalAmountRequired = stint.fromHex(UInt256, jsonObj{"ApprovalAmountRequired"}.getStr)
@ -78,7 +84,9 @@ proc toTransactionPathDtoV2*(jsonObj: JsonNode): TransactionPathDtoV2 =
result.approvalBaseFee = stint.fromHex(UInt256, jsonObj{"ApprovalBaseFee"}.getStr) result.approvalBaseFee = stint.fromHex(UInt256, jsonObj{"ApprovalBaseFee"}.getStr)
result.approvalPriorityFee = stint.fromHex(UInt256, jsonObj{"ApprovalPriorityFee"}.getStr) result.approvalPriorityFee = stint.fromHex(UInt256, jsonObj{"ApprovalPriorityFee"}.getStr)
discard jsonObj.getProp("ApprovalGasAmount", result.approvalGasAmount) discard jsonObj.getProp("ApprovalGasAmount", result.approvalGasAmount)
result.approvalFee = stint.fromHex(UInt256, jsonObj{"ApprovalFee"}.getStr)
result.approvalL1Fee = stint.fromHex(UInt256, jsonObj{"ApprovalL1Fee"}.getStr) result.approvalL1Fee = stint.fromHex(UInt256, jsonObj{"ApprovalL1Fee"}.getStr)
result.txTotalFee = stint.fromHex(UInt256, jsonObj{"TxTotalFee"}.getStr)
result.estimatedTime = jsonObj{"EstimatedTime"}.getInt result.estimatedTime = jsonObj{"EstimatedTime"}.getInt
proc toTransactionPathsDtoV2*(jsonObj: JsonNode): seq[TransactionPathDtoV2] = proc toTransactionPathsDtoV2*(jsonObj: JsonNode): seq[TransactionPathDtoV2] =

View File

@ -115,6 +115,8 @@ type
SuggestedRoutesArgs* = ref object of Args SuggestedRoutesArgs* = ref object of Args
uuid*: string uuid*: string
suggestedRoutes*: SuggestedRoutesDto suggestedRoutes*: SuggestedRoutesDto
# this should be the only one used when old send modal code is removed
routes*: seq[TransactionPathDtoV2]
errCode*: string errCode*: string
errDescription*: string errDescription*: string
@ -362,6 +364,7 @@ QtObject:
self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs( self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs(
uuid: uuid, uuid: uuid,
suggestedRoutes: suggestedDto, suggestedRoutes: suggestedDto,
routes: route,
errCode: errCode, errCode: errCode,
errDescription: errDescription errDescription: errDescription
)) ))

View File

@ -62,6 +62,7 @@ SplitView {
} }
property var setFees: Backpressure.debounce(root, 1500, function () { property var setFees: Backpressure.debounce(root, 1500, function () {
simpleSend.routesLoading = false
simpleSend.estimatedTime = "~60s" simpleSend.estimatedTime = "~60s"
simpleSend.estimatedFiatFees = "1.45 EUR" simpleSend.estimatedFiatFees = "1.45 EUR"
simpleSend.estimatedCryptoFees = "0.0007 ETH" simpleSend.estimatedCryptoFees = "0.0007 ETH"
@ -125,8 +126,9 @@ SplitView {
estimatedCryptoFees = "" estimatedCryptoFees = ""
estimatedFiatFees = "" estimatedFiatFees = ""
estimatedTime = "" estimatedTime = ""
if(formCorrectlyFilled) { if(allValuesFilledCorrectly) {
console.log("Fetch fees...") console.log("Fetch fees...")
routesLoading = true
d.setFees() d.setFees()
} }
} }

View File

@ -64,7 +64,7 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
loading: root.loading loading: root.loading || !root.cryptoFees
customColor: root.error ? Theme.palette.dangerColor1: customColor: root.error ? Theme.palette.dangerColor1:
Theme.palette.baseColor1 Theme.palette.baseColor1
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
@ -79,7 +79,7 @@ Control {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
loading: root.loading loading: root.loading || !root.fiatFees
customColor: root.error ? Theme.palette.dangerColor1: customColor: root.error ? Theme.palette.dangerColor1:
Theme.palette.baseColor1 Theme.palette.baseColor1
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight

View File

@ -72,8 +72,7 @@ StatusDialog {
Only networks valid as per mainnet/testnet selection Only networks valid as per mainnet/testnet selection
**/ **/
required property var networksModel required property var networksModel
required property var savedAddressesModel
required property var recentRecipientsModel
/** Input property holds currently selected Fiat currency **/ /** Input property holds currently selected Fiat currency **/
required property string currentCurrency required property string currentCurrency
/** Input function to format currency amount to locale string **/ /** Input function to format currency amount to locale string **/
@ -82,6 +81,9 @@ StatusDialog {
/** input property to decide if send modal is interactive or prefilled **/ /** input property to decide if send modal is interactive or prefilled **/
property bool interactive: true property bool interactive: true
/** input property to decide if routes are being fetched **/
property bool routesLoading
/** input property to set estimated time **/ /** input property to set estimated time **/
property string estimatedTime property string estimatedTime
/** input property to set estimated fees in fiat **/ /** input property to set estimated fees in fiat **/
@ -89,6 +91,8 @@ StatusDialog {
/** input property to set estimated fees in crypto **/ /** input property to set estimated fees in crypto **/
property string estimatedCryptoFees property string estimatedCryptoFees
/** property to set currently selected send type **/
property int sendType: Constants.SendType.Transfer
/** property to set and expose currently selected account **/ /** property to set and expose currently selected account **/
property string selectedAccountAddress property string selectedAccountAddress
/** property to set and expose currently selected network **/ /** property to set and expose currently selected network **/
@ -102,11 +106,19 @@ StatusDialog {
e.g. 1000000000000000000 for 1 ETH **/ e.g. 1000000000000000000 for 1 ETH **/
readonly property string selectedAmountInBaseUnit: amountToSend.amount readonly property string selectedAmountInBaseUnit: amountToSend.amount
/** property to scheck if form has been filled correctly **/ /** property to check if the form is filled correctly **/
readonly property bool formCorrectlyFilled: d.allValuesFilledCorrectly() readonly property bool allValuesFilledCorrectly: !!root.selectedAccountAddress &&
root.selectedChainId !== 0 &&
!!root.selectedTokenKey &&
!!root.selectedRecipientAddress &&
!!root.selectedAmount &&
!amountToSend.markAsInvalid &&
amountToSend.valid
/** TODO: replace with new and improved recipient selector StatusDateRangePicker /** TODO: replace with new and improved recipient selector StatusDateRangePicker
TBD under https://github.com/status-im/status-desktop/issues/16916 **/ TBD under https://github.com/status-im/status-desktop/issues/16916 **/
required property var savedAddressesModel
required property var recentRecipientsModel
property alias selectedRecipientAddress: recipientsPanel.selectedRecipientAddress property alias selectedRecipientAddress: recipientsPanel.selectedRecipientAddress
/** Input function to resolve Ens Name **/ /** Input function to resolve Ens Name **/
required property var fnResolveENS required property var fnResolveENS
@ -207,30 +219,14 @@ StatusDialog {
return WalletUtils.calculateMaxSafeSendAmount(maxCryptoBalance, d.selectedCryptoTokenSymbol) return WalletUtils.calculateMaxSafeSendAmount(maxCryptoBalance, d.selectedCryptoTokenSymbol)
} }
function allValuesFilledCorrectly() {
return !!root.selectedAccountAddress &&
root.selectedChainId !== 0 &&
!!root.selectedTokenKey &&
!!root.selectedRecipientAddress &&
!!root.selectedAmount &&
!amountToSend.markAsInvalid &&
amountToSend.valid
}
// handle multiple property changes from single changed signal // handle multiple property changes from single changed signal
property var combinedPropertyChangedHandler: [ property var combinedPropertyChangedHandler: [
root.selectedAccountAddress, root.selectedAccountAddress,
root.selectedChainId, root.selectedChainId,
root.selectedTokenKey, root.selectedTokenKey,
root.selectedRecipientAddress, root.selectedRecipientAddress,
root.selectedAmount, root.selectedAmount]
amountToSend.markAsInvalid,
amountToSend.valid]
onCombinedPropertyChangedHandlerChanged: Qt.callLater(() => root.formChanged()) onCombinedPropertyChangedHandlerChanged: Qt.callLater(() => root.formChanged())
readonly property bool feesIsLoading: !root.estimatedCryptoFees &&
!root.estimatedFiatFees &&
!root.estimatedTime
} }
width: 556 width: 556
@ -467,9 +463,9 @@ StatusDialog {
cryptoFees: root.estimatedCryptoFees cryptoFees: root.estimatedCryptoFees
fiatFees: root.estimatedFiatFees fiatFees: root.estimatedFiatFees
loading: d.feesIsLoading && d.allValuesFilledCorrectly() loading: root.routesLoading && root.allValuesFilledCorrectly
} }
visible: d.allValuesFilledCorrectly() visible: root.allValuesFilledCorrectly
} }
} }
} }
@ -481,7 +477,7 @@ StatusDialog {
estimatedTime: root.estimatedTime estimatedTime: root.estimatedTime
estimatedFees: root.estimatedFiatFees estimatedFees: root.estimatedFiatFees
loading: d.feesIsLoading && d.allValuesFilledCorrectly() loading: root.routesLoading && root.allValuesFilledCorrectly
onReviewSendClicked: root.reviewSendClicked() onReviewSendClicked: root.reviewSendClicked()
} }

View File

@ -0,0 +1,33 @@
import QtQuick 2.15
QtObject {
id: root
property var _walletSectionSendInst: walletSectionSendNew
signal suggestedRoutesReady(string uuid, var pathModel, string errCode, string errDescription)
signal transactionSent(string uuid, int chainId, bool approvalTx, string txHash, string error)
function authenticateAndTransfer(uuid, fromAddr, slippagePercentage = "") {
_walletSectionSendInst.authenticateAndTransfer(uuid, fromAddr, slippagePercentage)
}
function fetchSuggestedRoutes(uuid, sendType, chainId, accountFrom,
accountTo, amountIn, token,
amountOut = "0", toToken = "",
extraParamsJson = "") {
_walletSectionSendInst.fetchSuggestedRoutes(uuid, sendType, chainId, accountFrom,
accountTo, amountIn, token,
amountOut, toToken, extraParamsJson)
}
function stopUpdatesForSuggestedRoute() {
_walletSectionSendInst.stopUpdatesForSuggestedRoute()
}
Component.onCompleted: {
_walletSectionSendInst.suggestedRoutesReady.connect(suggestedRoutesReady)
_walletSectionSendInst.transactionSent.connect(transactionSent)
}
}

View File

@ -5,3 +5,4 @@ TokensStore 1.0 TokensStore.qml
WalletAssetsStore 1.0 WalletAssetsStore.qml WalletAssetsStore 1.0 WalletAssetsStore.qml
SwapStore 1.0 SwapStore.qml SwapStore 1.0 SwapStore.qml
BuyCryptoStore 1.0 BuyCryptoStore.qml BuyCryptoStore 1.0 BuyCryptoStore.qml
TransactionStoreNew 1.0 TransactionStoreNew.qml

View File

@ -102,6 +102,8 @@ Item {
paymentRequestEnabled: featureFlags ? featureFlags.paymentRequestEnabled : false paymentRequestEnabled: featureFlags ? featureFlags.paymentRequestEnabled : false
simpleSendEnabled: featureFlags ? featureFlags.simpleSendEnabled : false simpleSendEnabled: featureFlags ? featureFlags.simpleSendEnabled : false
} }
// TODO: Only until the old send modal transaction store can be replaced with this one
readonly property WalletStores.TransactionStoreNew transactionStoreNew: WalletStores.TransactionStoreNew {}
required property bool isCentralizedMetricsEnabled required property bool isCentralizedMetricsEnabled
@ -646,6 +648,7 @@ Item {
loginType: appMain.rootStore.loginType loginType: appMain.rootStore.loginType
transactionStore: appMain.transactionStore transactionStore: appMain.transactionStore
walletCollectiblesStore: appMain.walletCollectiblesStore walletCollectiblesStore: appMain.walletCollectiblesStore
transactionStoreNew: appMain.transactionStoreNew
// for ens flows // for ens flows
ensRegisteredAddress: appMain.rootStore.profileSectionStore.ensUsernamesStore.getEnsRegisteredAddress() ensRegisteredAddress: appMain.rootStore.profileSectionStore.ensUsernamesStore.getEnsRegisteredAddress()
@ -668,6 +671,7 @@ Item {
currentCurrency: appMain.currencyStore.currentCurrency currentCurrency: appMain.currencyStore.currentCurrency
showCommunityAssetsInSend: appMain.tokensStore.showCommunityAssetsInSend showCommunityAssetsInSend: appMain.tokensStore.showCommunityAssetsInSend
collectiblesBySymbolModel: WalletStores.RootStore.collectiblesStore.jointCollectiblesBySymbolModel collectiblesBySymbolModel: WalletStores.RootStore.collectiblesStore.jointCollectiblesBySymbolModel
tokenBySymbolModel: appMain.tokensStore.plainTokensBySymbolModel
fnFormatCurrencyAmount: function(amount, symbol, options = null, locale = null) { fnFormatCurrencyAmount: function(amount, symbol, options = null, locale = null) {
return appMain.currencyStore.formatCurrencyAmount(amount, symbol) return appMain.currencyStore.formatCurrencyAmount(amount, symbol)
} }

View File

@ -2,12 +2,14 @@ import QtQuick 2.15
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Core.Utils 0.1 as SQUtils
import AppLayouts.Wallet.stores 1.0 as WalletStores import AppLayouts.Wallet.stores 1.0 as WalletStores
import AppLayouts.Wallet.popups.simpleSend 1.0 import AppLayouts.Wallet.popups.simpleSend 1.0
import AppLayouts.Wallet.adaptors 1.0 import AppLayouts.Wallet.adaptors 1.0
import AppLayouts.Wallet 1.0
import shared.popups.send 1.0 import shared.popups.send 1.0
import shared.stores.send 1.0 import shared.stores.send 1.0
@ -21,6 +23,7 @@ QtObject {
required property int loginType required property int loginType
required property TransactionStore transactionStore required property TransactionStore transactionStore
required property WalletStores.CollectiblesStore walletCollectiblesStore required property WalletStores.CollectiblesStore walletCollectiblesStore
required property WalletStores.TransactionStoreNew transactionStoreNew
/** for ens flows **/ /** for ens flows **/
required property string myPublicKey required property string myPublicKey
@ -73,6 +76,13 @@ QtObject {
- accountAddress [string] - unique identifier of an account - accountAddress [string] - unique identifier of an account
**/ **/
required property var collectiblesBySymbolModel required property var collectiblesBySymbolModel
/** Expected model structure:
- key [string] - unique identifier of an asset
- decimals [int] - decimals of the token
- marketDetails [QObject] - collectible's contract address
- currencyPrice [CurrencyAmount] - assets market price in CurrencyAmount
**/
required property var tokenBySymbolModel
/** /**
Expected model structure: Expected model structure:
- chainId: network chain id - chainId: network chain id
@ -236,65 +246,148 @@ QtObject {
/** TODO: use the newly defined WalletAccountsSelectorAdaptor /** TODO: use the newly defined WalletAccountsSelectorAdaptor
in https://github.com/status-im/status-desktop/pull/16834 **/ in https://github.com/status-im/status-desktop/pull/16834 **/
accountsModel: root.walletAccountsModel accountsModel: root.walletAccountsModel
assetsModel: assetsSelectorViewAdaptor.outputAssetsModel assetsModel: backendHandler.assetsSelectorViewAdaptor.outputAssetsModel
collectiblesModel: collectiblesSelectionAdaptor.model collectiblesModel: backendHandler.collectiblesSelectionAdaptor.model
networksModel: root.filteredFlatNetworksModel networksModel: backendHandler.filteredFlatNetworksModel
savedAddressesModel: root.savedAddressesModel savedAddressesModel: root.savedAddressesModel
recentRecipientsModel: root.recentRecipientsModel recentRecipientsModel: root.recentRecipientsModel
currentCurrency: root.currentCurrency currentCurrency: root.currentCurrency
fnFormatCurrencyAmount: root.fnFormatCurrencyAmount fnFormatCurrencyAmount: root.fnFormatCurrencyAmount
fnResolveENS: root.fnResolveENS fnResolveENS: root.fnResolveENS
onClosed: destroy() onClosed: {
destroy()
root.transactionStoreNew.stopUpdatesForSuggestedRoute()
}
onFormChanged: { onFormChanged: {
estimatedCryptoFees = "" estimatedCryptoFees = ""
estimatedFiatFees = "" estimatedFiatFees = ""
estimatedTime = "" estimatedTime = ""
if(formCorrectlyFilled) { if(allValuesFilledCorrectly) {
// TODO: call stores fetchSuggestedRoutes api backendHandler.uuid = Utils.uuid()
simpleSendModal.routesLoading = true
root.transactionStoreNew.fetchSuggestedRoutes(backendHandler.uuid,
sendType,
selectedChainId,
selectedAccountAddress,
selectedRecipientAddress,
selectedAmountInBaseUnit,
selectedTokenKey)
} }
} }
TokenSelectorViewAdaptor { // TODO: this should be called from the Reiew and Sign Modal instead
id: assetsSelectorViewAdaptor onReviewSendClicked: {
root.transactionStoreNew.authenticateAndTransfer(uuid, selectedAccountAddress)
// TODO: remove all store dependecies and add specific properties to the handler instead
assetsModel: root.groupedAccountAssetsModel
flatNetworksModel: root.flatNetworksModel
currentCurrency: root.currentCurrency
showCommunityAssets: root.showCommunityAssetsInSend
accountAddress: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
} }
CollectiblesSelectionAdaptor {
id: collectiblesSelectionAdaptor
accountKey: simpleSendModal.selectedAccountAddress readonly property var backendHandler: QtObject {
enabledChainIds: [simpleSendModal.selectedChainId] property string uuid
property var fetchedPathModel
networksModel: root.filteredFlatNetworksModel readonly property var filteredFlatNetworksModel: SortFilterProxyModel {
collectiblesModel: SortFilterProxyModel { sourceModel: root.flatNetworksModel
sourceModel: root.collectiblesBySymbolModel filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled }
filters: ValueFilter { }
roleName: "soulbound"
value: false function routesFetched(returnedUuid, pathModel, errCode, errDescription) {
simpleSendModal.routesLoading = false
if(returnedUuid !== uuid) {
// Suggested routes for a different fetch, ignore
return
}
fetchedPathModel = pathModel
}
function transactionSent(returnedUuid, chainId, approvalTx, txHash, error) {
if(returnedUuid !== uuid) {
// Suggested routes for a different fetch, ignore
return
}
if (!!error) {
if (error.includes(Constants.walletSection.authenticationCanceled)) {
return
}
// TODO: handle error here
return
}
close()
}
readonly property var assetsSelectorViewAdaptor: TokenSelectorViewAdaptor {
// TODO: remove all store dependecies and add specific properties to the handler instead
assetsModel: root.groupedAccountAssetsModel
flatNetworksModel: root.flatNetworksModel
currentCurrency: root.currentCurrency
showCommunityAssets: root.showCommunityAssetsInSend
accountAddress: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
}
readonly property var collectiblesSelectionAdaptor: CollectiblesSelectionAdaptor {
accountKey: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
networksModel: backendHandler.filteredFlatNetworksModel
collectiblesModel: SortFilterProxyModel {
sourceModel: root.collectiblesBySymbolModel
filters: ValueFilter {
roleName: "soulbound"
value: false
}
} }
} }
}
Component.onCompleted: { readonly property var totalBalanceAggregator: FunctionAggregator {
root.ensNameResolved.connect(ensNameResolved) model: !!backendHandler.fetchedPathModel ?
backendHandler.fetchedPathModel: null
initialValue: "0"
roleName: "txTotalFee"
aggregateFunction: (aggr, value) => SQUtils.AmountsArithmetic.sum(
SQUtils.AmountsArithmetic.fromString(aggr),
SQUtils.AmountsArithmetic.fromString(value)).toString()
onValueChanged: {
let decimals = !!backendHandler.ethTokenEntry.item ? backendHandler.ethTokenEntry.item.decimals: 18
let ethFiatValue = !!backendHandler.ethTokenEntry.item ? backendHandler.ethTokenEntry.item.marketDetails.currencyPrice.amount: 1
let totalFees = SQUtils.AmountsArithmetic.div(SQUtils.AmountsArithmetic.fromString(value), SQUtils.AmountsArithmetic.fromNumber(1, decimals))
let totalFeesInFiat = root.fnFormatCurrencyAmount(ethFiatValue*totalFees, root.currentCurrency).toString()
simpleSendModal.estimatedCryptoFees = root.fnFormatCurrencyAmount(totalFees.toString(), Constants.ethToken)
simpleSendModal.estimatedFiatFees = totalFeesInFiat
}
}
readonly property var estimatedTimeAggregator: SumAggregator {
model: !!backendHandler.fetchedPathModel ?
backendHandler.fetchedPathModel: null
roleName: "estimatedTime"
onValueChanged: {
simpleSendModal.estimatedTime = WalletUtils.getLabelForEstimatedTxTime(value)
}
}
readonly property var selectedTokenEntry: ModelEntry {
sourceModel: root.tokenBySymbolModel
key: "key"
value: simpleSendModal.selectedTokenKey
}
readonly property var ethTokenEntry: ModelEntry {
sourceModel: root.tokenBySymbolModel
key: "key"
value: Constants.ethToken
}
Component.onCompleted: {
root.ensNameResolved.connect(ensNameResolved)
root.transactionStoreNew.suggestedRoutesReady.connect(routesFetched)
root.transactionStoreNew.transactionSent.connect(transactionSent)
}
} }
} }
} }
readonly property var filteredFlatNetworksModel: SortFilterProxyModel {
sourceModel: root.flatNetworksModel
filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled }
}
} }