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.} =
raise newException(ValueError, "No implementation available")
method newSendModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method overviewModuleDidLoad*(self: AccessInterface) {.base.} =
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 ./overview/module as overview_module
import ./send/module as send_module
import ./send_new/module as new_send_module
import ./activity/controller as activityc
@ -70,6 +71,8 @@ type
allCollectiblesModule: all_collectibles_module.AccessInterface
assetsModule: assets_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
buySellCryptoModule: buy_sell_crypto_module.AccessInterface
overviewModule: overview_module.AccessInterface
@ -141,6 +144,7 @@ proc newModule*(
currencyService)
result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService,
transactionService, keycardService)
result.newSendModule = newSendModule.newModule(result, events, walletAccountService, networkService, transactionService, keycardService)
result.savedAddressesModule = saved_addresses_module.newModule(result, events, savedAddressService)
result.buySellCryptoModule = buy_sell_crypto_module.newModule(result, events, rampService)
result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService)
@ -185,6 +189,7 @@ method delete*(self: Module) =
self.savedAddressesModule.delete
self.buySellCryptoModule.delete
self.sendModule.delete
self.newSendModule.delete
self.controller.delete
self.viewVariant.delete
self.view.delete
@ -351,6 +356,7 @@ method load*(self: Module) =
self.buySellCryptoModule.load()
self.overviewModule.load()
self.sendModule.load()
self.newSendModule.load()
self.networksModule.load()
self.walletConnectService.init()
self.walletConnectController.init()
@ -384,6 +390,9 @@ proc checkIfModuleDidLoad(self: Module) =
if(not self.sendModule.isLoaded()):
return
if(not self.newSendModule.isLoaded()):
return
if(not self.networksModule.isLoaded()):
return
@ -432,6 +441,9 @@ method overviewModuleDidLoad*(self: Module) =
method sendModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method newSendModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method networksModuleDidLoad*(self: Module) =
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
amountOut*: UInt256
suggestedLevelsForMaxFeesPerGas*: SuggestedLevelsForMaxFeesPerGasDto
maxFeesPerGas*: UInt256
txBaseFee*: UInt256
txPriorityFee*: UInt256
txGasAmount*: uint64
txBonderFees*: UInt256
txTokenFees*: UInt256
txFee*: UInt256
txL1Fee*: UInt256
approvalRequired*: bool
approvalAmountRequired*: UInt256
@ -42,7 +44,9 @@ type
approvalBaseFee*: UInt256
approvalPriorityFee*: UInt256
approvalGasAmount*: uint64
approvalFee*: UInt256
approvalL1Fee*: UInt256
txTotalFee*: UInt256
estimatedTime*: int
proc toSuggestedLevelsForMaxFeesPerGasDto*(jsonObj: JsonNode): SuggestedLevelsForMaxFeesPerGasDto =
@ -66,11 +70,13 @@ proc toTransactionPathDtoV2*(jsonObj: JsonNode): TransactionPathDtoV2 =
discard jsonObj.getProp("AmountInLocked", result.amountInLocked)
result.amountOut = stint.fromHex(UInt256, jsonObj{"AmountOut"}.getStr)
result.suggestedLevelsForMaxFeesPerGas = jsonObj["SuggestedLevelsForMaxFeesPerGas"].toSuggestedLevelsForMaxFeesPerGasDto()
result.maxFeesPerGas = stint.fromHex(UInt256, jsonObj{"MaxFeesPerGas"}.getStr)
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.txFee = stint.fromHex(UInt256, jsonObj{"TxFee"}.getStr)
result.txL1Fee = stint.fromHex(UInt256, jsonObj{"TxL1Fee"}.getStr)
discard jsonObj.getProp("ApprovalRequired", result.approvalRequired)
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.approvalPriorityFee = stint.fromHex(UInt256, jsonObj{"ApprovalPriorityFee"}.getStr)
discard jsonObj.getProp("ApprovalGasAmount", result.approvalGasAmount)
result.approvalFee = stint.fromHex(UInt256, jsonObj{"ApprovalFee"}.getStr)
result.approvalL1Fee = stint.fromHex(UInt256, jsonObj{"ApprovalL1Fee"}.getStr)
result.txTotalFee = stint.fromHex(UInt256, jsonObj{"TxTotalFee"}.getStr)
result.estimatedTime = jsonObj{"EstimatedTime"}.getInt
proc toTransactionPathsDtoV2*(jsonObj: JsonNode): seq[TransactionPathDtoV2] =

View File

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

View File

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

View File

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

View File

@ -72,8 +72,7 @@ StatusDialog {
Only networks valid as per mainnet/testnet selection
**/
required property var networksModel
required property var savedAddressesModel
required property var recentRecipientsModel
/** Input property holds currently selected Fiat currency **/
required property string currentCurrency
/** 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 **/
property bool interactive: true
/** input property to decide if routes are being fetched **/
property bool routesLoading
/** input property to set estimated time **/
property string estimatedTime
/** input property to set estimated fees in fiat **/
@ -89,6 +91,8 @@ StatusDialog {
/** input property to set estimated fees in crypto **/
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 string selectedAccountAddress
/** property to set and expose currently selected network **/
@ -102,11 +106,19 @@ StatusDialog {
e.g. 1000000000000000000 for 1 ETH **/
readonly property string selectedAmountInBaseUnit: amountToSend.amount
/** property to scheck if form has been filled correctly **/
readonly property bool formCorrectlyFilled: d.allValuesFilledCorrectly()
/** property to check if the form is filled correctly **/
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
TBD under https://github.com/status-im/status-desktop/issues/16916 **/
required property var savedAddressesModel
required property var recentRecipientsModel
property alias selectedRecipientAddress: recipientsPanel.selectedRecipientAddress
/** Input function to resolve Ens Name **/
required property var fnResolveENS
@ -207,30 +219,14 @@ StatusDialog {
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
property var combinedPropertyChangedHandler: [
root.selectedAccountAddress,
root.selectedChainId,
root.selectedTokenKey,
root.selectedRecipientAddress,
root.selectedAmount,
amountToSend.markAsInvalid,
amountToSend.valid]
root.selectedAmount]
onCombinedPropertyChangedHandlerChanged: Qt.callLater(() => root.formChanged())
readonly property bool feesIsLoading: !root.estimatedCryptoFees &&
!root.estimatedFiatFees &&
!root.estimatedTime
}
width: 556
@ -467,9 +463,9 @@ StatusDialog {
cryptoFees: root.estimatedCryptoFees
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
estimatedFees: root.estimatedFiatFees
loading: d.feesIsLoading && d.allValuesFilledCorrectly()
loading: root.routesLoading && root.allValuesFilledCorrectly
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
SwapStore 1.0 SwapStore.qml
BuyCryptoStore 1.0 BuyCryptoStore.qml
TransactionStoreNew 1.0 TransactionStoreNew.qml

View File

@ -102,6 +102,8 @@ Item {
paymentRequestEnabled: featureFlags ? featureFlags.paymentRequestEnabled : 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
@ -646,6 +648,7 @@ Item {
loginType: appMain.rootStore.loginType
transactionStore: appMain.transactionStore
walletCollectiblesStore: appMain.walletCollectiblesStore
transactionStoreNew: appMain.transactionStoreNew
// for ens flows
ensRegisteredAddress: appMain.rootStore.profileSectionStore.ensUsernamesStore.getEnsRegisteredAddress()
@ -668,6 +671,7 @@ Item {
currentCurrency: appMain.currencyStore.currentCurrency
showCommunityAssetsInSend: appMain.tokensStore.showCommunityAssetsInSend
collectiblesBySymbolModel: WalletStores.RootStore.collectiblesStore.jointCollectiblesBySymbolModel
tokenBySymbolModel: appMain.tokensStore.plainTokensBySymbolModel
fnFormatCurrencyAmount: function(amount, symbol, options = null, locale = null) {
return appMain.currencyStore.formatCurrencyAmount(amount, symbol)
}

View File

@ -2,12 +2,14 @@ import QtQuick 2.15
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import AppLayouts.Wallet.stores 1.0 as WalletStores
import AppLayouts.Wallet.popups.simpleSend 1.0
import AppLayouts.Wallet.adaptors 1.0
import AppLayouts.Wallet 1.0
import shared.popups.send 1.0
import shared.stores.send 1.0
@ -21,6 +23,7 @@ QtObject {
required property int loginType
required property TransactionStore transactionStore
required property WalletStores.CollectiblesStore walletCollectiblesStore
required property WalletStores.TransactionStoreNew transactionStoreNew
/** for ens flows **/
required property string myPublicKey
@ -73,6 +76,13 @@ QtObject {
- accountAddress [string] - unique identifier of an account
**/
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:
- chainId: network chain id
@ -236,65 +246,148 @@ QtObject {
/** TODO: use the newly defined WalletAccountsSelectorAdaptor
in https://github.com/status-im/status-desktop/pull/16834 **/
accountsModel: root.walletAccountsModel
assetsModel: assetsSelectorViewAdaptor.outputAssetsModel
collectiblesModel: collectiblesSelectionAdaptor.model
networksModel: root.filteredFlatNetworksModel
assetsModel: backendHandler.assetsSelectorViewAdaptor.outputAssetsModel
collectiblesModel: backendHandler.collectiblesSelectionAdaptor.model
networksModel: backendHandler.filteredFlatNetworksModel
savedAddressesModel: root.savedAddressesModel
recentRecipientsModel: root.recentRecipientsModel
currentCurrency: root.currentCurrency
fnFormatCurrencyAmount: root.fnFormatCurrencyAmount
fnResolveENS: root.fnResolveENS
onClosed: destroy()
onClosed: {
destroy()
root.transactionStoreNew.stopUpdatesForSuggestedRoute()
}
onFormChanged: {
estimatedCryptoFees = ""
estimatedFiatFees = ""
estimatedTime = ""
if(formCorrectlyFilled) {
// TODO: call stores fetchSuggestedRoutes api
if(allValuesFilledCorrectly) {
backendHandler.uuid = Utils.uuid()
simpleSendModal.routesLoading = true
root.transactionStoreNew.fetchSuggestedRoutes(backendHandler.uuid,
sendType,
selectedChainId,
selectedAccountAddress,
selectedRecipientAddress,
selectedAmountInBaseUnit,
selectedTokenKey)
}
}
TokenSelectorViewAdaptor {
id: assetsSelectorViewAdaptor
// 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]
// TODO: this should be called from the Reiew and Sign Modal instead
onReviewSendClicked: {
root.transactionStoreNew.authenticateAndTransfer(uuid, selectedAccountAddress)
}
CollectiblesSelectionAdaptor {
id: collectiblesSelectionAdaptor
accountKey: simpleSendModal.selectedAccountAddress
enabledChainIds: [simpleSendModal.selectedChainId]
readonly property var backendHandler: QtObject {
property string uuid
property var fetchedPathModel
networksModel: root.filteredFlatNetworksModel
collectiblesModel: SortFilterProxyModel {
sourceModel: root.collectiblesBySymbolModel
filters: ValueFilter {
roleName: "soulbound"
value: false
readonly property var filteredFlatNetworksModel: SortFilterProxyModel {
sourceModel: root.flatNetworksModel
filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled }
}
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: {
root.ensNameResolved.connect(ensNameResolved)
readonly property var totalBalanceAggregator: FunctionAggregator {
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 }
}
}