feat(swap): added more explicit error messages

Closes #14956
This commit is contained in:
Dario Gabriel Lipicar 2024-07-19 00:36:36 -03:00 committed by dlipicar
parent 6eeb917cd7
commit 83db905e28
16 changed files with 283 additions and 52 deletions

View File

@ -70,7 +70,7 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_SUGGESTED_ROUTES_READY) do(e:Args): self.events.on(SIGNAL_SUGGESTED_ROUTES_READY) do(e:Args):
let args = SuggestedRoutesArgs(e) let args = SuggestedRoutesArgs(e)
self.delegate.suggestedRoutesReady(args.uuid, args.suggestedRoutes) self.delegate.suggestedRoutesReady(args.uuid, args.suggestedRoutes, args.errCode, args.errDescription)
self.events.on(SignalType.WalletSignTransactions.event) do(e:Args): self.events.on(SignalType.WalletSignTransactions.event) do(e:Args):
var data = WalletSignal(e) var data = WalletSignal(e)

View File

@ -37,7 +37,7 @@ method suggestedRoutes*(self: AccessInterface,
extraParamsTable: Table[string, string] = initTable[string, string]()) {.base.} = extraParamsTable: Table[string, string] = initTable[string, string]()) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method suggestedRoutesReady*(self: AccessInterface, uuid: string, suggestedRoutes: SuggestedRoutesDto) {.base.} = method suggestedRoutesReady*(self: AccessInterface, uuid: string, suggestedRoutes: SuggestedRoutesDto, errCode: string, errDescription: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method authenticateAndTransfer*(self: AccessInterface, from_addr: string, to_addr: string, assetKey: string, method authenticateAndTransfer*(self: AccessInterface, from_addr: string, to_addr: string, assetKey: string,

View File

@ -276,7 +276,7 @@ method transactionWasSent*(self: Module, chainId: int, txHash, uuid, error: stri
return return
self.view.sendTransactionSentSignal(chainId, txHash, uuid, error) self.view.sendTransactionSentSignal(chainId, txHash, uuid, error)
method suggestedRoutesReady*(self: Module, uuid: string, suggestedRoutes: SuggestedRoutesDto) = method suggestedRoutesReady*(self: Module, uuid: string, suggestedRoutes: SuggestedRoutesDto, errCode: string, errDescription: string) =
self.tmpSendTransactionDetails.paths = suggestedRoutes.best self.tmpSendTransactionDetails.paths = suggestedRoutes.best
self.tmpSendTransactionDetails.slippagePercentage = none(float) self.tmpSendTransactionDetails.slippagePercentage = none(float)
let paths = suggestedRoutes.best.map(x => self.convertTransactionPathDtoToSuggestedRouteItem(x)) let paths = suggestedRoutes.best.map(x => self.convertTransactionPathDtoToSuggestedRouteItem(x))
@ -294,7 +294,7 @@ method suggestedRoutesReady*(self: Module, uuid: string, suggestedRoutes: Sugges
amountToReceive = suggestedRoutes.amountToReceive, amountToReceive = suggestedRoutes.amountToReceive,
toNetworksRouteModel = toNetworksRouteModel, toNetworksRouteModel = toNetworksRouteModel,
rawPaths = suggestedRoutes.rawBest) rawPaths = suggestedRoutes.rawBest)
self.view.setTransactionRoute(transactionRoutes) self.view.setTransactionRoute(transactionRoutes, errCode, errDescription)
method suggestedRoutes*(self: Module, method suggestedRoutes*(self: Module,
uuid: string, uuid: string,

View File

@ -18,6 +18,8 @@ QtObject:
fromNetworksRouteModel: NetworkRouteModel fromNetworksRouteModel: NetworkRouteModel
toNetworksRouteModel: NetworkRouteModel toNetworksRouteModel: NetworkRouteModel
transactionRoutes: TransactionRoutes transactionRoutes: TransactionRoutes
errCode: string
errDescription: string
selectedAssetKey: string selectedAssetKey: string
selectedToAssetKey: string selectedToAssetKey: string
showUnPreferredChains: bool showUnPreferredChains: bool
@ -194,10 +196,12 @@ QtObject:
self.delegate.authenticateAndTransfer(self.selectedSenderAccountAddress, self.selectedRecipient, self.selectedAssetKey, self.delegate.authenticateAndTransfer(self.selectedSenderAccountAddress, self.selectedRecipient, self.selectedAssetKey,
self.selectedToAssetKey, uuid, self.sendType, self.selectedTokenName, self.selectedTokenIsOwnerToken) self.selectedToAssetKey, uuid, self.sendType, self.selectedTokenName, self.selectedTokenIsOwnerToken)
proc suggestedRoutesReady*(self: View, suggestedRoutes: QVariant) {.signal.} proc suggestedRoutesReady*(self: View, suggestedRoutes: QVariant, errCode: string, errDescription: string) {.signal.}
proc setTransactionRoute*(self: View, routes: TransactionRoutes) = proc setTransactionRoute*(self: View, routes: TransactionRoutes, errCode: string, errDescription: string) =
self.transactionRoutes = routes self.transactionRoutes = routes
self.suggestedRoutesReady(newQVariant(self.transactionRoutes)) self.errCode = errCode
self.errDescription = errDescription
self.suggestedRoutesReady(newQVariant(self.transactionRoutes), errCode, errDescription)
proc suggestedRoutes*(self: View, amountIn: string, amountOut: string, extraParamsJson: string) {.slot.} = proc suggestedRoutes*(self: View, amountIn: string, amountOut: string, extraParamsJson: string) {.slot.} =
var extraParamsTable: Table[string, string] var extraParamsTable: Table[string, string]

View File

@ -113,6 +113,8 @@ type
SuggestedRoutesArgs* = ref object of Args SuggestedRoutesArgs* = ref object of Args
uuid*: string uuid*: string
suggestedRoutes*: SuggestedRoutesDto suggestedRoutes*: SuggestedRoutesDto
errCode*: string
errDescription*: string
type type
CryptoServicesArgs* = ref object of Args CryptoServicesArgs* = ref object of Args
@ -131,7 +133,7 @@ QtObject:
tokenService: token_service.Service tokenService: token_service.Service
## Forward declarations ## Forward declarations
proc suggestedRoutesV2Ready(self: Service, uuid: string, route: seq[TransactionPathDtoV2], routeRaw: string, error: string, errCode: string) proc suggestedRoutesV2Ready(self: Service, uuid: string, route: seq[TransactionPathDtoV2], routeRaw: string, errCode: string, errDescription: string)
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
@ -162,7 +164,7 @@ QtObject:
self.events.on(SignalType.WalletSuggestedRoutes.event) do(e:Args): self.events.on(SignalType.WalletSuggestedRoutes.event) do(e:Args):
var data = WalletSignal(e) var data = WalletSignal(e)
self.suggestedRoutesV2Ready(data.uuid, data.bestRoute, data.bestRouteRaw, data.error, data.errorCode) self.suggestedRoutesV2Ready(data.uuid, data.bestRoute, data.bestRouteRaw, data.errorCode, data.error)
self.events.on(PendingTransactionTypeDto.WalletTransfer.event) do(e: Args): self.events.on(PendingTransactionTypeDto.WalletTransfer.event) do(e: Args):
try: try:
@ -578,7 +580,7 @@ QtObject:
except Exception as e: except Exception as e:
error "Error getting suggested fees", msg = e.msg error "Error getting suggested fees", msg = e.msg
proc suggestedRoutesV2Ready(self: Service, uuid: string, route: seq[TransactionPathDtoV2], routeRaw: string, error: string, errCode: string) = proc suggestedRoutesV2Ready(self: Service, uuid: string, route: seq[TransactionPathDtoV2], routeRaw: string, errCode: string, errDescription: string) =
# TODO: refactor sending modal part of the app, but for now since we're integrating the router v2 just map params to the old dto # TODO: refactor sending modal part of the app, but for now since we're integrating the router v2 just map params to the old dto
var oldRoute = convertToOldRoute(route) var oldRoute = convertToOldRoute(route)
@ -590,7 +592,12 @@ QtObject:
amountToReceive: getTotalAmountToReceive(oldRoute), amountToReceive: getTotalAmountToReceive(oldRoute),
toNetworks: getToNetworksList(oldRoute), toNetworks: getToNetworksList(oldRoute),
) )
self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs(uuid: uuid, suggestedRoutes: suggestedDto)) self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs(
uuid: uuid,
suggestedRoutes: suggestedDto,
errCode: errCode,
errDescription: errDescription
))
proc suggestedRoutes*(self: Service, proc suggestedRoutes*(self: Service,
uuid: string, uuid: string,

View File

@ -241,9 +241,11 @@ SplitView {
amountToReceive: txStore.amountToSend - (txStore.amountToSend*5/100), amountToReceive: txStore.amountToSend - (txStore.amountToSend*5/100),
toNetworksRouteModel: dummyEventData.toModel toNetworksRouteModel: dummyEventData.toModel
} }
let errCode = ""
let errDescription = ""
txStore.fromNetworksRouteModel.updateFromNetworks(dummyEventData.suggestesRoutes) txStore.fromNetworksRouteModel.updateFromNetworks(dummyEventData.suggestesRoutes)
txStore.toNetworksRouteModel.updateToNetworks(dummyEventData.suggestesRoutes) txStore.toNetworksRouteModel.updateToNetworks(dummyEventData.suggestesRoutes)
txStore.walletSectionSendInst.suggestedRoutesReady(txRoutes) txStore.walletSectionSendInst.suggestedRoutesReady(txRoutes, errCode, errDescription)
txStore.suggestedRoutesCalled = false txStore.suggestedRoutesCalled = false
} }
} }

View File

@ -72,7 +72,7 @@ SplitView {
SwapStore { SwapStore {
id: dSwapStore id: dSwapStore
signal suggestedRoutesReady(var txRoutes) signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription)
signal transactionSent(var chainId, var txHash, var uuid, var error) signal transactionSent(var chainId, var txHash, var uuid, var error)
signal transactionSendingComplete(var txHash, var success) signal transactionSendingComplete(var txHash, var success)
@ -278,7 +278,7 @@ SplitView {
swapInput.text = "0.2" swapInput.text = "0.2"
fetchSuggestedRoutesSpy.wait() fetchSuggestedRoutesSpy.wait()
Backpressure.debounce(this, 250, () => { Backpressure.debounce(this, 250, () => {
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRouteNoApproval) dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRouteNoApproval, "", "")
})() })()
} }
} }
@ -292,7 +292,7 @@ SplitView {
swapInput.text = "0.1" swapInput.text = "0.1"
fetchSuggestedRoutesSpy.wait() fetchSuggestedRoutesSpy.wait()
Backpressure.debounce(this, 1000, () => { Backpressure.debounce(this, 1000, () => {
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded) dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded, "", "")
})() })()
Backpressure.debounce(this, 1500, () => {approveTxButton.clicked()})() Backpressure.debounce(this, 1500, () => {approveTxButton.clicked()})()
authenticateAndTransferSpy.wait() authenticateAndTransferSpy.wait()
@ -304,7 +304,7 @@ SplitView {
})() })()
fetchSuggestedRoutesSpy.wait() fetchSuggestedRoutesSpy.wait()
Backpressure.debounce(this, 1000, () => { Backpressure.debounce(this, 1000, () => {
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRouteNoApproval) dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRouteNoApproval, "", "")
})() })()
} }
} }
@ -317,7 +317,7 @@ SplitView {
swapInput.text = "0.2" swapInput.text = "0.2"
fetchSuggestedRoutesSpy.wait() fetchSuggestedRoutesSpy.wait()
Backpressure.debounce(this, 250, () => { Backpressure.debounce(this, 250, () => {
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txNoRoutes) dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txNoRoutes, "ERR-123", "Fetching proposal error")
})() })()
} }
@ -332,7 +332,7 @@ SplitView {
swapInput.text = "0.1" swapInput.text = "0.1"
fetchSuggestedRoutesSpy.wait() fetchSuggestedRoutesSpy.wait()
Backpressure.debounce(this, 1000, () => { Backpressure.debounce(this, 1000, () => {
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded) dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded, "", "")
})() })()
Backpressure.debounce(this, 1500, () => {approveTxButton.clicked()})() Backpressure.debounce(this, 1500, () => {approveTxButton.clicked()})()
authenticateAndTransferSpy.wait() authenticateAndTransferSpy.wait()
@ -351,12 +351,26 @@ SplitView {
checked: false checked: false
} }
ComboBox {
id: routerErrorComboBox
model: [
{name: "errNotEnoughTokenBalance", value: Constants.swap.errorCodes.errNotEnoughTokenBalance},
{name: "errNotEnoughNativeBalance", value: Constants.swap.errorCodes.errNotEnoughNativeBalance},
{name: "errPriceTimeout", value: Constants.swap.errorCodes.errPriceTimeout},
{name: "errNotEnoughLiquidity", value: Constants.swap.errorCodes.errNotEnoughLiquidity}
]
textRole: "name"
valueRole: "value"
currentIndex: 0
visible: advancedSignalsCheckBox.checked
}
Button { Button {
text: "emit no routes found event" text: "emit no routes found event with error"
onClicked: { onClicked: {
const txRoutes = d.dummySwapTransactionRoutes.txNoRoutes const txRoutes = d.dummySwapTransactionRoutes.txNoRoutes
txRoutes.uuid = d.uuid txRoutes.uuid = d.uuid
dSwapStore.suggestedRoutesReady(txRoutes) dSwapStore.suggestedRoutesReady(txRoutes, routerErrorComboBox.currentValue, "")
} }
visible: advancedSignalsCheckBox.checked visible: advancedSignalsCheckBox.checked
} }
@ -366,7 +380,7 @@ SplitView {
onClicked: { onClicked: {
const txRoutes = d.dummySwapTransactionRoutes.txHasRouteNoApproval const txRoutes = d.dummySwapTransactionRoutes.txHasRouteNoApproval
txRoutes.uuid = d.uuid txRoutes.uuid = d.uuid
dSwapStore.suggestedRoutesReady(txRoutes) dSwapStore.suggestedRoutesReady(txRoutes, "", "")
} }
visible: advancedSignalsCheckBox.checked visible: advancedSignalsCheckBox.checked
} }
@ -376,7 +390,7 @@ SplitView {
onClicked: { onClicked: {
const txRoutes = d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded const txRoutes = d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded
txRoutes.uuid = d.uuid txRoutes.uuid = d.uuid
dSwapStore.suggestedRoutesReady(txRoutes) dSwapStore.suggestedRoutesReady(txRoutes, "", "")
} }
visible: advancedSignalsCheckBox.checked visible: advancedSignalsCheckBox.checked
} }

View File

@ -27,7 +27,7 @@ Item {
readonly property var dummySwapTransactionRoutes: SwapTransactionRoutes {} readonly property var dummySwapTransactionRoutes: SwapTransactionRoutes {}
readonly property var swapStore: SwapStore { readonly property var swapStore: SwapStore {
signal suggestedRoutesReady(var txRoutes) signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription)
signal transactionSent(var chainId,var txHash, var uuid, var error) signal transactionSent(var chainId,var txHash, var uuid, var error)
signal transactionSendingComplete(var txHash, var success) signal transactionSendingComplete(var txHash, var success)
@ -596,8 +596,8 @@ Item {
// verify loading state was set and no errors currently // verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState(payPanel, receivePanel) verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event that no routes were found // emit event that no routes were found with unknown error
root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes) root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes, "NO_ROUTES", "No routes found")
// verify loading state was removed and that error was displayed // verify loading state was removed and that error was displayed
verify(!root.swapAdaptor.validSwapProposalReceived) verify(!root.swapAdaptor.validSwapProposalReceived)
@ -633,9 +633,155 @@ Item {
// verify loading state was set and no errors currently // verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState(payPanel, receivePanel) verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event that no routes were found due to not enough token balance
root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes, Constants.swap.errorCodes.errNotEnoughTokenBalance, "errNotEnoughTokenBalance")
// verify loading state was removed and that error was displayed
verify(!root.swapAdaptor.validSwapProposalReceived)
verify(!root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.totalFees, 0)
compare(root.swapAdaptor.swapOutputData.approvalNeeded, false)
compare(root.swapAdaptor.swapOutputData.hasError, true)
verify(errorTag.visible)
verify(errorTag.text, qsTr("Insufficient funds for swap"))
verify(!signButton.enabled)
compare(signButton.text, qsTr("Swap"))
// verfy input and output panels
verify(!payPanel.mainInputLoading)
verify(!payPanel.bottomTextLoading)
verify(!receivePanel.mainInputLoading)
verify(!receivePanel.bottomTextLoading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.value, 0)
compare(receivePanel.rawValue, "0")
// edit some params to retry swap
root.swapFormData.fromTokenAmount = "0.00012"
waitForRendering(receivePanel)
formValuesChanged.wait()
// wait for fetchSuggestedRoutes function to be called
fetchSuggestedRoutesCalled.wait()
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event that no routes were found due to not enough eth balance
root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes, Constants.swap.errorCodes.errNotEnoughNativeBalance, "errNotEnoughNativeBalance")
// verify loading state was removed and that error was displayed
verify(!root.swapAdaptor.validSwapProposalReceived)
verify(!root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.totalFees, 0)
compare(root.swapAdaptor.swapOutputData.approvalNeeded, false)
compare(root.swapAdaptor.swapOutputData.hasError, true)
verify(errorTag.visible)
verify(errorTag.text, qsTr("Insufficient funds to pay gas fees"))
verify(!signButton.enabled)
compare(signButton.text, qsTr("Swap"))
// verfy input and output panels
verify(!payPanel.mainInputLoading)
verify(!payPanel.bottomTextLoading)
verify(!receivePanel.mainInputLoading)
verify(!receivePanel.bottomTextLoading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.value, 0)
compare(receivePanel.rawValue, "0")
// edit some params to retry swap
root.swapFormData.fromTokenAmount = "0.00013"
waitForRendering(receivePanel)
formValuesChanged.wait()
// wait for fetchSuggestedRoutes function to be called
fetchSuggestedRoutesCalled.wait()
// wait for fetchSuggestedRoutes function to be called
fetchSuggestedRoutesCalled.wait()
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event that no routes were found due to price timeout
root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes, Constants.swap.errorCodes.errPriceTimeout, "errPriceTimeout")
// verify loading state was removed and that error was displayed
verify(!root.swapAdaptor.validSwapProposalReceived)
verify(!root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.totalFees, 0)
compare(root.swapAdaptor.swapOutputData.approvalNeeded, false)
compare(root.swapAdaptor.swapOutputData.hasError, true)
verify(errorTag.visible)
verify(errorTag.text, qsTr("Fetching the price took longer than expected. Please, try again later."))
verify(!signButton.enabled)
compare(signButton.text, qsTr("Swap"))
// verfy input and output panels
verify(!payPanel.mainInputLoading)
verify(!payPanel.bottomTextLoading)
verify(!receivePanel.mainInputLoading)
verify(!receivePanel.bottomTextLoading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.value, 0)
compare(receivePanel.rawValue, "0")
// edit some params to retry swap
root.swapFormData.fromTokenAmount = "0.00013"
waitForRendering(receivePanel)
formValuesChanged.wait()
// wait for fetchSuggestedRoutes function to be called
fetchSuggestedRoutesCalled.wait()
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event that no routes were found due to not enough liquidity
root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes, Constants.swap.errorCodes.errNotEnoughLiquidity, "errNotEnoughLiquidity")
// verify loading state was removed and that error was displayed
verify(!root.swapAdaptor.validSwapProposalReceived)
verify(!root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.totalFees, 0)
compare(root.swapAdaptor.swapOutputData.approvalNeeded, false)
compare(root.swapAdaptor.swapOutputData.hasError, true)
verify(errorTag.visible)
verify(errorTag.text, qsTr("Not enough liquidity. Lower token amount or try again later."))
verify(!signButton.enabled)
compare(signButton.text, qsTr("Swap"))
// verfy input and output panels
verify(!payPanel.mainInputLoading)
verify(!payPanel.bottomTextLoading)
verify(!receivePanel.mainInputLoading)
verify(!receivePanel.bottomTextLoading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.value, 0)
compare(receivePanel.rawValue, "0")
// edit some params to retry swap
root.swapFormData.fromTokenAmount = "0.00014"
waitForRendering(receivePanel)
formValuesChanged.wait()
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event with route that needs no approval // emit event with route that needs no approval
let txRoutes = root.dummySwapTransactionRoutes.txHasRouteNoApproval let txRoutes = root.dummySwapTransactionRoutes.txHasRouteNoApproval
root.swapStore.suggestedRoutesReady(txRoutes) root.swapStore.suggestedRoutesReady(txRoutes, "", "")
// verify loading state removed and data is displayed as expected on the Modal // verify loading state removed and data is displayed as expected on the Modal
verify(root.swapAdaptor.validSwapProposalReceived) verify(root.swapAdaptor.validSwapProposalReceived)
@ -684,7 +830,7 @@ Item {
// emit event with route that needs no approval // emit event with route that needs no approval
let txRoutes2 = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded let txRoutes2 = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded
root.swapStore.suggestedRoutesReady(txRoutes2) root.swapStore.suggestedRoutesReady(txRoutes2, "", "")
// verify loading state removed and data ius displayed as expected on the Modal // verify loading state removed and data ius displayed as expected on the Modal
verify(root.swapAdaptor.validSwapProposalReceived) verify(root.swapAdaptor.validSwapProposalReceived)
@ -1330,7 +1476,7 @@ Item {
// emit event with route that needs no approval // emit event with route that needs no approval
let txRoutes = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded let txRoutes = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded
txRoutes.uuid = root.swapAdaptor.uuid txRoutes.uuid = root.swapAdaptor.uuid
root.swapStore.suggestedRoutesReady(txRoutes) root.swapStore.suggestedRoutesReady(txRoutes, "", "")
// calculation needed for total fees // calculation needed for total fees
let gasTimeEstimate = txRoutes.gasTimeEstimate let gasTimeEstimate = txRoutes.gasTimeEstimate
@ -1426,7 +1572,7 @@ Item {
let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval
txHasRouteNoApproval.uuid = root.swapAdaptor.uuid txHasRouteNoApproval.uuid = root.swapAdaptor.uuid
root.swapStore.suggestedRoutesReady(txHasRouteNoApproval) root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "")
verify(!root.swapAdaptor.approvalPending) verify(!root.swapAdaptor.approvalPending)
verify(!root.swapAdaptor.approvalSuccessful) verify(!root.swapAdaptor.approvalSuccessful)
@ -1601,7 +1747,7 @@ Item {
// emit routes ready // emit routes ready
let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval
txHasRouteNoApproval.uuid = root.swapAdaptor.uuid txHasRouteNoApproval.uuid = root.swapAdaptor.uuid
root.swapStore.suggestedRoutesReady(txHasRouteNoApproval) root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "")
// check if fetch occurs automatically after 15 seconds // check if fetch occurs automatically after 15 seconds
fetchSuggestedRoutesCalled.wait() fetchSuggestedRoutesCalled.wait()

View File

@ -48,7 +48,7 @@ QtObject {
readonly property QtObject walletSectionSendInst: QtObject { readonly property QtObject walletSectionSendInst: QtObject {
signal transactionSent(var chainId, var txHash, var uuid, var error) signal transactionSent(var chainId, var txHash, var uuid, var error)
signal suggestedRoutesReady(var txRoutes) signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription)
} }
readonly property QtObject mainModuleInst: QtObject { readonly property QtObject mainModuleInst: QtObject {
signal resolvedENS(var resolvedPubKey, var resolvedAddress, var uuid) signal resolvedENS(var resolvedPubKey, var resolvedAddress, var uuid)

View File

@ -55,6 +55,8 @@ StatusDialog {
root.swapAdaptor.swapOutputData.resetPathInfoAndError() root.swapAdaptor.swapOutputData.resetPathInfoAndError()
debounceFetchSuggestedRoutes() debounceFetchSuggestedRoutes()
} }
readonly property bool isError: root.swapAdaptor.errorMessage !== ""
} }
Connections { Connections {
@ -227,6 +229,9 @@ StatusDialog {
root.swapInputParamsForm.fromTokenAmount = amount root.swapInputParamsForm.fromTokenAmount = amount
} }
} }
onAmountEnteredGreaterThanBalanceChanged: {
root.swapAdaptor.amountEnteredGreaterThanBalance = payPanel.amountEnteredGreaterThanBalance
}
} }
SwapInputPanel { SwapInputPanel {
@ -301,18 +306,12 @@ StatusDialog {
ErrorTag { ErrorTag {
objectName: "errorTag" objectName: "errorTag"
visible: root.swapAdaptor.swapOutputData.hasError || payPanel.amountEnteredGreaterThanBalance visible: d.isError
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Style.current.smallPadding Layout.topMargin: Style.current.smallPadding
text: { text: root.swapAdaptor.errorMessage
if (root.swapAdaptor.swapOutputData.hasError) { buttonText: root.swapAdaptor.isTokenBalanceInsufficient ? qsTr("Buy crypto") : qsTr("Buy ETH")
return qsTr("An error has occured, please try again") buttonVisible: visible && (root.swapAdaptor.isTokenBalanceInsufficient || root.swapAdaptor.isEthBalanceInsufficient)
} else if (payPanel.amountEnteredGreaterThanBalance) {
return qsTr("Insufficient funds for swap")
}
}
buttonText: qsTr("Buy crypto")
buttonVisible: !root.swapAdaptor.swapOutputData.hasError && payPanel.amountEnteredGreaterThanBalance
onButtonClicked: Global.openBuyCryptoModalRequested() onButtonClicked: Global.openBuyCryptoModalRequested()
} }
} }
@ -407,7 +406,7 @@ StatusDialog {
disabledColor: Theme.palette.directColor8 disabledColor: Theme.palette.directColor8
enabled: root.swapAdaptor.validSwapProposalReceived && enabled: root.swapAdaptor.validSwapProposalReceived &&
editSlippagePanel.valid && editSlippagePanel.valid &&
!payPanel.amountEnteredGreaterThanBalance && !d.isError &&
!root.swapAdaptor.approvalPending !root.swapAdaptor.approvalPending
onClicked: { onClicked: {
if (root.swapAdaptor.validSwapProposalReceived) { if (root.swapAdaptor.validSwapProposalReceived) {

View File

@ -27,6 +27,9 @@ QObject {
property bool approvalPending: false property bool approvalPending: false
property bool approvalSuccessful: false property bool approvalSuccessful: false
// the below property holds internal checks done by the SwapModal
property bool amountEnteredGreaterThanBalance: false
// To expose the selected from and to Token from the SwapModal // To expose the selected from and to Token from the SwapModal
readonly property var fromToken: fromTokenEntry.item readonly property var fromToken: fromTokenEntry.item
readonly property var toToken: toTokenEntry.item readonly property var toToken: toTokenEntry.item
@ -89,6 +92,10 @@ QObject {
filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled } filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled }
} }
readonly property string errorMessage: d.errorMessage
readonly property bool isEthBalanceInsufficient: d.isEthBalanceInsufficient
readonly property bool isTokenBalanceInsufficient: d.isTokenBalanceInsufficient
signal suggestedRoutesReady() signal suggestedRoutesReady()
QtObject { QtObject {
@ -141,6 +148,45 @@ QObject {
formattedBalance: "0 %1".arg(root.fromToken.symbol) formattedBalance: "0 %1".arg(root.fromToken.symbol)
} }
} }
// Properties to handle error states
readonly property bool isRouteEthBalanceInsufficient: root.validSwapProposalReceived && root.swapOutputData.errCode === Constants.swap.errorCodes.errNotEnoughNativeBalance
readonly property bool isRouteTokenBalanceInsufficient: root.validSwapProposalReceived && root.swapOutputData.errCode === Constants.swap.errorCodes.errNotEnoughTokenBalance
readonly property bool isTokenBalanceInsufficient: {
return (root.amountEnteredGreaterThanBalance || isRouteTokenBalanceInsufficient) &&
root.fromToken.symbol !== Constants.ethToken
}
readonly property bool isEthBalanceInsufficient: {
return (root.amountEnteredGreaterThanBalance && root.fromToken.symbol === Constants.ethToken) ||
isRouteEthBalanceInsufficient
}
readonly property bool isBalanceInsufficientForSwap: {
return (root.amountEnteredGreaterThanBalance && root.fromToken.symbol === Constants.ethToken) ||
(isTokenBalanceInsufficient && root.fromToken.symbol !== Constants.ethToken)
}
readonly property bool isBalanceInsufficientForFees: !isBalanceInsufficientForSwap && isEthBalanceInsufficient
property string errorMessage: {
if (isBalanceInsufficientForSwap) {
return qsTr("Insufficient funds for swap")
} else if (isBalanceInsufficientForFees) {
return qsTr("Insufficient funds to pay gas fees")
} else if (root.swapOutputData.hasError) {
switch (root.swapOutputData.errCode) {
case Constants.swap.errorCodes.errPriceTimeout:
return qsTr("Fetching the price took longer than expected. Please, try again later.")
case Constants.swap.errorCodes.errNotEnoughLiquidity:
return qsTr("Not enough liquidity. Lower token amount or try again later.")
}
return qsTr("Something went wrong. Change amount, token or try again later.")
}
return ""
}
} }
ModelEntry { ModelEntry {
@ -166,7 +212,7 @@ QObject {
Connections { Connections {
target: root.swapStore target: root.swapStore
function onSuggestedRoutesReady(txRoutes) { function onSuggestedRoutesReady(txRoutes, errCode, errDescription) {
if (txRoutes.uuid !== d.uuid) { if (txRoutes.uuid !== d.uuid) {
// Suggested routes for a different fetch, ignore // Suggested routes for a different fetch, ignore
return return
@ -175,8 +221,10 @@ QObject {
root.validSwapProposalReceived = false root.validSwapProposalReceived = false
root.swapProposalLoading = false root.swapProposalLoading = false
root.swapOutputData.rawPaths = txRoutes.rawPaths root.swapOutputData.rawPaths = txRoutes.rawPaths
root.swapOutputData.errCode = errCode
root.swapOutputData.errDescription = errDescription
// if valid route was found // if valid route was found
if(txRoutes.suggestedRoutes.count === 1) { if(txRoutes.suggestedRoutes.count > 0) {
root.validSwapProposalReceived = true root.validSwapProposalReceived = true
root.swapOutputData.toTokenAmount = AmountsArithmetic.div(AmountsArithmetic.fromString(txRoutes.amountToReceive), AmountsArithmetic.fromNumber(1, root.toToken.decimals)).toString() root.swapOutputData.toTokenAmount = AmountsArithmetic.div(AmountsArithmetic.fromString(txRoutes.amountToReceive), AmountsArithmetic.fromNumber(1, root.toToken.decimals)).toString()
@ -190,12 +238,12 @@ QObject {
root.swapOutputData.approvalGasFees = !!bestPath ? bestPath.approvalGasFees.toString() : "" root.swapOutputData.approvalGasFees = !!bestPath ? bestPath.approvalGasFees.toString() : ""
root.swapOutputData.approvalAmountRequired = !!bestPath ? bestPath.approvalAmountRequired: "" root.swapOutputData.approvalAmountRequired = !!bestPath ? bestPath.approvalAmountRequired: ""
root.swapOutputData.approvalContractAddress = !!bestPath ? bestPath.approvalContractAddress: "" root.swapOutputData.approvalContractAddress = !!bestPath ? bestPath.approvalContractAddress: ""
root.swapOutputData.estimatedTime = !!bestPath ? bestPath.estimatedTime: Constants.TransactionEstimatedTime.Unknown root.swapOutputData.estimatedTime = !!bestPath ? bestPath.estimatedTime: Constants.TransactionEstimatedTime.Unknown
root.swapOutputData.txProviderName = !!bestPath ? bestPath.bridgeName: "" root.swapOutputData.txProviderName = !!bestPath ? bestPath.bridgeName: ""
} } else {
else {
root.swapOutputData.hasError = true root.swapOutputData.hasError = true
} }
root.swapOutputData.hasError = root.swapOutputData.hasError || root.swapOutputData.errCode !== ""
root.suggestedRoutesReady() root.suggestedRoutesReady()
} }

View File

@ -12,6 +12,8 @@ QtObject {
// TODO: this should be string but backend gas_estimate_item.nim passes this as float // TODO: this should be string but backend gas_estimate_item.nim passes this as float
property real totalFees: 0 property real totalFees: 0
property bool hasError property bool hasError
property string errCode
property string errDescription
property var rawPaths: [] property var rawPaths: []
// need to check how this is done in new router v2, right now it is Enum type // need to check how this is done in new router v2, right now it is Enum type
property int estimatedTime property int estimatedTime
@ -23,6 +25,8 @@ QtObject {
function resetPathInfoAndError() { function resetPathInfoAndError() {
root.hasError = false root.hasError = false
root.errCode = ""
root.errDescription = ""
root.rawPaths = [] root.rawPaths = []
} }

View File

@ -16,14 +16,14 @@ QtObject {
Remove these and use the new TransactorStore in SwapModalAdaptor when that happens. */ Remove these and use the new TransactorStore in SwapModalAdaptor when that happens. */
readonly property var walletSectionSendInst: walletSectionSend readonly property var walletSectionSendInst: walletSectionSend
signal suggestedRoutesReady(var txRoutes) signal suggestedRoutesReady(var txRoutes, string errCode, string errDescription)
signal transactionSent(var chainId, var txHash, var uuid, var error) signal transactionSent(var chainId, var txHash, var uuid, var error)
signal transactionSendingComplete(var txHash, var success) signal transactionSendingComplete(var txHash, var success)
readonly property Connections walletSectionSendConnections: Connections { readonly property Connections walletSectionSendConnections: Connections {
target: root.walletSectionSendInst target: root.walletSectionSendInst
function onSuggestedRoutesReady(txRoutes) { function onSuggestedRoutesReady(txRoutes, errCode, errDescription) {
root.suggestedRoutesReady(txRoutes) root.suggestedRoutesReady(txRoutes, errCode, errDescription)
} }
function onTransactionSent(chainId, txHash, uuid, error) { function onTransactionSent(chainId, txHash, uuid, error) {
root.transactionSent(chainId, txHash, uuid, error) root.transactionSent(chainId, txHash, uuid, error)

View File

@ -644,7 +644,7 @@ StatusDialog {
Connections { Connections {
target: popup.store.walletSectionSendInst target: popup.store.walletSectionSendInst
function onSuggestedRoutesReady(txRoutes) { function onSuggestedRoutesReady(txRoutes, errCode, errDescription) {
popup.bestRoutes = txRoutes.suggestedRoutes popup.bestRoutes = txRoutes.suggestedRoutes
let gasTimeEstimate = txRoutes.gasTimeEstimate let gasTimeEstimate = txRoutes.gasTimeEstimate
d.totalTimeEstimate = WalletUtils.getLabelForEstimatedTxTime(gasTimeEstimate.totalTime) d.totalTimeEstimate = WalletUtils.getLabelForEstimatedTxTime(gasTimeEstimate.totalTime)

View File

@ -1335,6 +1335,13 @@ QtObject {
this list dynamically */ this list dynamically */
readonly property string paraswapIcon: "paraswap" readonly property string paraswapIcon: "paraswap"
readonly property string paraswapUrl: "app.paraswap.io" readonly property string paraswapUrl: "app.paraswap.io"
readonly property QtObject errorCodes: QtObject {
readonly property string errNotEnoughTokenBalance: "WR-016"
readonly property string errNotEnoughNativeBalance: "WR-017"
readonly property string errPriceTimeout: "WPP-037"
readonly property string errNotEnoughLiquidity: "WPP-038"
}
} }
// Mirrors src/app_service/service/transaction/service.nim -> EstimatedTime // Mirrors src/app_service/service/transaction/service.nim -> EstimatedTime

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit d07f9b5b162c078f71bf19a189e2e26c118a0db4 Subproject commit afc6e7bcb9e70e8ece8744dfab1b23ba5d087e6e