feat(@desktop/wallet): Add Select Input and output params to swap modal

fixes #14826, #14825
This commit is contained in:
Khushboo Mehta 2024-06-13 02:45:33 +02:00 committed by Khushboo-dev-cpp
parent 4226eaed0b
commit 5c1e800f14
13 changed files with 787 additions and 178 deletions

View File

@ -35,6 +35,7 @@ SplitView {
fromTokenAmount: ctrlFromTokenAmount.text
toTokenKey: ctrlToTokenKey.text
toTokenAmount: ctrlToTokenAmount.text
selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
}
readonly property SwapModalAdaptor adaptor: SwapModalAdaptor {

View File

@ -63,64 +63,79 @@ SplitView {
Component.onCompleted: d.launchPopup()
SwapInputParamsForm {
id: swapInputForm
selectedAccountIndex: accountComboBox.currentIndex
selectedNetworkChainId: d.getNetwork()
fromTokensKey: fromTokenComboBox.currentValue
fromTokenAmount: swapInput.text
toTokenKey: toTokenComboBox.currentValue
toTokenAmount: swapOutputAmount.text
SwapStore {
id: dSwapStore
signal suggestedRoutesReady(var txRoutes)
readonly property var accounts: d.accountsModel
readonly property var flatNetworks: d.flatNetworksModel
readonly property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked
function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo,
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {
console.debug("fetchSuggestedRoutes called >> accountFrom = ",accountFrom, " accountTo =",
accountTo, "amount = ",amount, " tokenFrom = ",tokenFrom, " tokenTo = ", tokenTo,
" disabledFromChainIDs = ",disabledFromChainIDs, " disabledToChainIDs = ",disabledToChainIDs,
" preferredChainIDs = ",preferredChainIDs, " sendType =", sendType, " lockedInAmounts = ",lockedInAmounts)
}
function authenticateAndTransfer(uuid, accountFrom, accountTo, tokenFrom,
tokenTo, sendType, tokenName, tokenIsOwnerToken, paths) {
console.debug("authenticateAndTransfer called >> uuid ", uuid, " accountFrom = ",accountFrom, " accountTo =",
accountTo, "tokenFrom = ",tokenFrom, " tokenTo = ",tokenTo, " sendType = ", sendType,
" tokenName = ", tokenName, " tokenIsOwnerToken = ", tokenIsOwnerToken, " paths = ", paths)
}
function getWei2Eth(wei, decimals) {
return wei/(10**decimals)
}
}
SwapModalAdaptor {
id: swapModalAdaptor
swapStore: SwapStore {
signal suggestedRoutesReady(var txRoutes)
readonly property var accounts: d.accountsModel
readonly property var flatNetworks: d.flatNetworksModel
readonly property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked
function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo,
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {
console.debug("fetchSuggestedRoutes called >> accountFrom = ",accountFrom, " accountTo =",
accountTo, "amount = ",amount, " tokenFrom = ",tokenFrom, " tokenTo = ", tokenTo,
" disabledFromChainIDs = ",disabledFromChainIDs, " disabledToChainIDs = ",disabledToChainIDs,
" preferredChainIDs = ",preferredChainIDs, " sendType =", sendType, " lockedInAmounts = ",lockedInAmounts)
}
function authenticateAndTransfer(uuid, accountFrom, accountTo, tokenFrom,
tokenTo, sendType, tokenName, tokenIsOwnerToken, paths) {
console.debug("authenticateAndTransfer called >> uuid ", uuid, " accountFrom = ",accountFrom, " accountTo =",
accountTo, "tokenFrom = ",tokenFrom, " tokenTo = ",tokenTo, " sendType = ", sendType,
" tokenName = ", tokenName, " tokenIsOwnerToken = ", tokenIsOwnerToken, " paths = ", paths)
}
function getWei2Eth(wei, decimals) {
return wei/(10**decimals)
}
}
walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
readonly property var plainTokensBySymbolModel: TokensBySymbolModel {}
getDisplayAssetsBelowBalanceThresholdDisplayAmount: () => 0
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: swapInputForm
swapOutputData: SwapOutputData{}
TokensStore {
id: tokensStore
readonly property var plainTokensBySymbolModel: TokensBySymbolModel {}
getDisplayAssetsBelowBalanceThresholdDisplayAmount: () => 0
}
Component {
id: swapModal
SwapModal {
id: modal
visible: true
modal: false
closePolicy: Popup.CloseOnEscape
destroyOnClose: true
swapInputParamsForm: swapInputForm
swapAdaptor: swapModalAdaptor
swapInputParamsForm: SwapInputParamsForm {
selectedAccountAddress: {
if (accountComboBox.model.count > 0 && accountComboBox.currentIndex >= 0) {
return ModelUtils.get(accountComboBox.model, accountComboBox.currentIndex, "address")
}
return ""
}
selectedNetworkChainId: d.getNetwork()
fromTokensKey: fromTokenComboBox.currentValue
fromTokenAmount: swapInput.text
toTokenKey: toTokenComboBox.currentValue
}
swapAdaptor: SwapModalAdaptor {
swapStore: dSwapStore
walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: tokensStore
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: modal.swapInputParamsForm
swapOutputData: SwapOutputData{}
}
Binding {
target: swapInputParamsForm
property: "fromTokensKey"
value: fromTokenComboBox.currentValue
}
Binding {
target: swapInputParamsForm
property: "toTokenKey"
value: toTokenComboBox.currentValue
}
}
}
}
@ -157,9 +172,6 @@ SplitView {
sorters: RoleSorter { roleName: "position"; sortOrder: Qt.AscendingOrder }
}
currentIndex: 0
onCurrentIndexChanged: {
swapInputForm.selectedAccountIndex = currentIndex
}
}
StatusBaseText {
@ -171,7 +183,6 @@ SplitView {
model: d.filteredNetworksModel
currentIndex: 0
onCountChanged: currentIndex = 0
onCurrentIndexChanged: swapInputForm.selectedNetworkChainId = d.getNetwork()
}
StatusBaseText {
@ -182,14 +193,13 @@ SplitView {
textRole: "name"
valueRole: "key"
model: d.tokenBySymbolModel
currentIndex: 0
}
StatusInput {
id: swapInput
Layout.preferredWidth: 100
label: "Token amount to swap"
text: "100"
text: ""
}
StatusBaseText {
@ -206,21 +216,21 @@ SplitView {
Button {
text: "emit no routes found event"
onClicked: {
swapModalAdaptor.swapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txNoRoutes)
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txNoRoutes)
}
}
Button {
text: "emit no approval needed route"
onClicked: {
swapModalAdaptor.swapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRouteNoApproval)
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRouteNoApproval)
}
}
Button {
text: "emit approval needed route"
onClicked: {
swapModalAdaptor.swapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded)
dSwapStore.suggestedRoutesReady(d.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded)
}
}
}

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtTest 1.15
import StatusQ 0.1
import StatusQ.Core.Utils 0.1
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.panels 1.0
@ -37,7 +38,9 @@ Item {
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: SwapInputParamsForm {}
swapFormData: SwapInputParamsForm {
selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
}
swapOutputData: SwapOutputData {}
}
}
@ -135,7 +138,7 @@ Item {
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
tryCompare(amountToSendInput.input, "text", Number(tokenAmount).toLocaleString(Qt.locale(), 'f', -128))
tryCompare(amountToSendInput.input, "text", AmountsArithmetic.fromString(tokenAmount).toLocaleString(Qt.locale(), 'f', -128))
}
function test_enterTokenAmountLocalizedNumber() {

View File

@ -108,16 +108,31 @@ Item {
return accountsModalHeader
}
function verifyLoadingAndNoErrorsState() {
function verifyLoadingAndNoErrorsState(payPanel, receivePanel) {
// verify loading state was set and no errors currently
verify(!root.swapAdaptor.validSwapProposalReceived)
verify(root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "0")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "0")
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.totalFees, 0)
compare(root.swapAdaptor.swapOutputData.bestRoutes, [])
compare(root.swapAdaptor.swapOutputData.approvalNeeded, false)
compare(root.swapAdaptor.swapOutputData.hasError, false)
// verfy input and output panels
verify(!payPanel.loading)
compare(payPanel.selectedHoldingId, root.swapFormData.fromTokensKey)
compare(payPanel.cryptoValue, Number(root.swapFormData.fromTokenAmount))
compare(payPanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(root.swapFormData.fromTokenAmount, root.swapAdaptor.fromToken.decimals).toString())
verify(payPanel.cryptoValueValid)
verify(receivePanel.loading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
/* TODO: there is bug which prevents us from testing this right now
The value is not updated after setting tokenAmount to empty string in the receive input panel
https://github.com/status-im/status-desktop/issues/15162
compare(receivePanel.cryptoValue, 0)
compare(receivePanel.cryptoValueRaw, "0") */
}
// end helper functions -------------------------------------------------------------
@ -126,7 +141,7 @@ Item {
/* using a for loop set different accounts as default index and
check if the correct values are displayed in the floating header*/
for (let i = 0; i< swapAdaptor.nonWatchAccounts.count; i++) {
root.swapFormData.selectedAccountIndex = i
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(i).address
// Launch popup
launchAndVerfyModal()
@ -291,7 +306,7 @@ Item {
verify(accountsModalHeader.control.popup.closed)
// The input params form's slected Index should be updated as per this selection
compare(root.swapFormData.selectedAccountIndex, i)
compare(root.swapFormData.selectedAccountAddress, swapAdaptor.nonWatchAccounts.get(i).address)
// The comboBox item should reflect chosen account
const floatingHeaderBackground = findChild(accountsModalHeader, "headerBackground")
@ -417,8 +432,6 @@ Item {
}
function test_edit_slippage() {
// by default the max slippage button should show no values and the edit button shouldnt be visible
// Launch popup
launchAndVerfyModal()
@ -476,8 +489,6 @@ Item {
}
function test_modal_swap_proposal_setup() {
// by default the max slippage button should show no values and the edit button shouldnt be visible
root.swapAdaptor.reset()
// Launch popup
@ -495,6 +506,12 @@ Item {
const errorTag = findChild(controlUnderTest, "errorTag")
verify(!!errorTag)
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const receivePanel = findChild(controlUnderTest, "receivePanel")
verify(!!receivePanel)
// Check max fees values and sign button state when nothing is set
compare(maxFeesText.text, qsTr("Max fees:"))
compare(maxFeesValue.text, "--")
@ -506,16 +523,18 @@ Item {
compare(formValuesChanged.count, 1)
root.swapFormData.toTokenKey = root.swapAdaptor.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel.get(1).key
compare(formValuesChanged.count, 2)
root.swapFormData.fromTokenAmount = 10
root.swapFormData.fromTokenAmount = "0.001"
compare(formValuesChanged.count, 3)
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
compare(formValuesChanged.count, 4)
root.swapFormData.selectedAccountAddress = root.swapAdaptor.nonWatchAccounts.get(0).address
compare(formValuesChanged.count, 5)
// wait for fetchSuggestedRoutes function to be called
wait(1000)
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState()
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event that no routes were found
root.swapStore.suggestedRoutesReady(root.dummySwapTransactionRoutes.txNoRoutes)
@ -523,8 +542,8 @@ Item {
// verify loading state was removed and that error was displayed
verify(!root.swapAdaptor.validSwapProposalReceived)
verify(!root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "0")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "0")
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.totalFees, 0)
compare(root.swapAdaptor.swapOutputData.bestRoutes, [])
compare(root.swapAdaptor.swapOutputData.approvalNeeded, false)
@ -534,24 +553,32 @@ Item {
verify(!signButton.enabled)
compare(signButton.text, qsTr("Swap"))
// verfy input and output panels
verify(!payPanel.loading)
verify(!receivePanel.loading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.cryptoValue, 0)
compare(receivePanel.cryptoValueRaw, "0")
// edit some params to retry swap
root.swapFormData.fromTokenAmount = 11
compare(formValuesChanged.count, 5)
root.swapFormData.fromTokenAmount = "0.00011"
compare(formValuesChanged.count, 6)
// wait for fetchSuggestedRoutes function to be called
wait(1000)
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState()
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event with route that needs no approval
let txRoutes = root.dummySwapTransactionRoutes.txHasRouteNoApproval
root.swapStore.suggestedRoutesReady(txRoutes)
// verify loading state removed and data ius 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.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "0")
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, root.swapStore.getWei2Eth(txRoutes.amountToReceive, root.swapAdaptor.toToken.decimals).toString())
// calculation needed for total fees
@ -567,15 +594,24 @@ Item {
verify(signButton.enabled)
compare(signButton.text, qsTr("Swap"))
// verfy input and output panels
waitForRendering(receivePanel)
verify(payPanel.cryptoValueValid)
verify(!receivePanel.loading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.cryptoValue, root.swapStore.getWei2Eth(txRoutes.amountToReceive, root.swapAdaptor.toToken.decimals))
compare(receivePanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(root.swapAdaptor.swapOutputData.toTokenAmount, root.swapAdaptor.toToken.decimals).toString())
// edit some params to retry swap
root.swapFormData.fromTokenAmount = 1
compare(formValuesChanged.count, 6)
root.swapFormData.fromTokenAmount = "0.012"
compare(formValuesChanged.count, 7)
// wait for fetchSuggestedRoutes function to be called
wait(1000)
// verify loading state was set and no errors currently
verifyLoadingAndNoErrorsState()
verifyLoadingAndNoErrorsState(payPanel, receivePanel)
// emit event with route that needs no approval
let txRoutes2 = root.dummySwapTransactionRoutes.txHasRoutesApprovalNeeded
@ -584,7 +620,7 @@ Item {
// verify loading state removed and data ius displayed as expected on the Modal
verify(root.swapAdaptor.validSwapProposalReceived)
verify(!root.swapAdaptor.swapProposalLoading)
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "0")
compare(root.swapAdaptor.swapOutputData.fromTokenAmount, "")
compare(root.swapAdaptor.swapOutputData.toTokenAmount, root.swapStore.getWei2Eth(txRoutes2.amountToReceive, root.swapAdaptor.toToken.decimals).toString())
// calculation needed for total fees
@ -599,6 +635,497 @@ Item {
verify(!errorTag.visible)
verify(signButton.enabled)
compare(signButton.text, qsTr("Approve %1").arg(root.swapAdaptor.fromToken.symbol))
// verfy input and output panels
waitForRendering(receivePanel)
verify(payPanel.cryptoValueValid)
verify(!receivePanel.loading)
verify(!receivePanel.interactive)
compare(receivePanel.selectedHoldingId, root.swapFormData.toTokenKey)
compare(receivePanel.cryptoValue, root.swapStore.getWei2Eth(txRoutes.amountToReceive, root.swapAdaptor.toToken.decimals))
compare(receivePanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(root.swapAdaptor.swapOutputData.toTokenAmount, root.swapAdaptor.toToken.decimals).toString())
}
function test_modal_pay_input_default() {
// Launch popup
launchAndVerfyModal()
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
const holdingSelector = findChild(payPanel, "holdingSelector")
verify(!!holdingSelector)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
// check default states for the from input selector
compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, "")
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, undefined)
compare(holdingSelectorsContentItemText.text, qsTr("Select asset"))
compare(holdingSelectorsTokenIcon.image.source, "")
verify(!holdingSelectorsTokenIcon.visible)
verify(!maxTagButton.visible)
compare(payPanel.selectedHoldingId, "")
compare(payPanel.cryptoValue, 0)
compare(payPanel.cryptoValueRaw, "0")
verify(!payPanel.cryptoValueValid)
closeAndVerfyModal()
}
function test_modal_pay_input_presetValues() {
// try setting value before popup is launched and check values
let valueToExchange = 0.001
let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
// Launch popup
launchAndVerfyModal()
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
const holdingSelector = findChild(payPanel, "holdingSelector")
verify(!!holdingSelector)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, valueToExchangeString)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible)
verify(maxTagButton.visible)
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100, expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.cryptoValue, valueToExchange)
compare(payPanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString())
verify(payPanel.cryptoValueValid)
closeAndVerfyModal()
}
function test_modal_pay_input_wrong_value_1() {
let invalidValues = ["ABC", "0.0.010201", "12PASA", "100,9.01"]
for (let i =0; i<invalidValues.length; i++) {
let invalidValue = invalidValues[i]
// try setting value before popup is launched and check values
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.fromTokensKey =
root.swapFormData.fromTokenAmount = invalidValue
// Launch popup
launchAndVerfyModal()
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
const holdingSelector = findChild(payPanel, "holdingSelector")
verify(!!holdingSelector)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, null)
compare(holdingSelectorsContentItemText.text, "")
verify(!holdingSelectorsTokenIcon.visible)
verify(!maxTagButton.visible)
compare(payPanel.selectedHoldingId, invalidValue)
compare(payPanel.cryptoValue, 0)
compare(payPanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber("0", 0).toString())
verify(!payPanel.cryptoValueValid)
closeAndVerfyModal()
}
}
function test_modal_pay_input_wrong_value_2() {
// try setting value before popup is launched and check values
let valueToExchange = 100
let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
// Launch popup
launchAndVerfyModal()
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
const holdingSelector = findChild(payPanel, "holdingSelector")
verify(!!holdingSelector)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, valueToExchangeString)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0 * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible)
verify(maxTagButton.visible)
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100, expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.cryptoValue, 0)
compare(payPanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber("0", expectedToken.decimals).toString())
verify(!payPanel.cryptoValueValid)
closeAndVerfyModal()
}
function test_modal_pay_input_switching_networks() {
// try setting value before popup is launched and check values
root.swapFormData.resetFormData()
let valueToExchange = 0.3
let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
// Launch popup
launchAndVerfyModal()
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
for (let i=0; i< root.swapAdaptor.filteredFlatNetworksModel.count; i++) {
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(i).chainId
waitForRendering(payPanel)
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue, expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.cryptoValueValid, valueToExchange <= maxPossibleValue)
/* TODO: there is bug which prevents us from testing this right now
When value entered is greater than balance then fiat and crytpo values are not calculated
https://github.com/status-im/status-desktop/issues/15162
compare(payPanel.cryptoValue, valueToExchange)
compare(payPanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString()) */
}
closeAndVerfyModal()
}
function test_modal_receive_input_default() {
// Launch popup
launchAndVerfyModal()
const receivePanel = findChild(controlUnderTest, "receivePanel")
verify(!!receivePanel)
const amountToSendInput = findChild(receivePanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(receivePanel, "bottomItemText")
verify(!!bottomItemText)
const holdingSelector = findChild(receivePanel, "holdingSelector")
verify(!!holdingSelector)
const maxTagButton = findChild(receivePanel, "maxTagButton")
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(receivePanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
// check default states for the from input selector
compare(amountToSendInput.caption, qsTr("Receive"))
compare(amountToSendInput.input.text, "")
// TODO: this should be come interactive under https://github.com/status-im/status-desktop/issues/15095
verify(!amountToSendInput.interactive)
verify(!amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, undefined)
compare(holdingSelectorsContentItemText.text, qsTr("Select asset"))
verify(!maxTagButton.visible)
compare(receivePanel.selectedHoldingId, "")
compare(receivePanel.cryptoValue, 0)
compare(receivePanel.cryptoValueRaw, "0")
verify(!receivePanel.cryptoValueValid)
closeAndVerfyModal()
}
function test_modal_receive_input_presetValues() {
let valueToReceive = 0.001
let valueToReceiveString = valueToReceive.toString()
// try setting value before popup is launched and check values
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.toTokenKey = "STT"
root.swapFormData.toTokenAmount = valueToReceiveString
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "STT")
// Launch popup
launchAndVerfyModal()
const receivePanel = findChild(controlUnderTest, "receivePanel")
verify(!!receivePanel)
const amountToSendInput = findChild(receivePanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(receivePanel, "bottomItemText")
verify(!!bottomItemText)
const holdingSelector = findChild(receivePanel, "holdingSelector")
verify(!!holdingSelector)
const maxTagButton = findChild(receivePanel, "maxTagButton")
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(receivePanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(receivePanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(receivePanel)
compare(amountToSendInput.caption, qsTr("Receive"))
// TODO: this should be come interactive under https://github.com/status-im/status-desktop/issues/15095
verify(!amountToSendInput.interactive)
verify(!amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, valueToReceive.toLocaleString(Qt.locale(), 'f', -128))
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToReceive * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible)
verify(!maxTagButton.visible)
compare(receivePanel.selectedHoldingId, expectedToken.symbol)
compare(receivePanel.cryptoValue, valueToReceive)
compare(receivePanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(valueToReceiveString, expectedToken.decimals).toString())
verify(receivePanel.cryptoValueValid)
closeAndVerfyModal()
}
function test_modal_max_button_click_with_preset_pay_value() {
// Launch popup
launchAndVerfyModal()
// try setting value before popup is launched and check values
let valueToExchange = 0.2
let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
root.swapFormData.toTokenKey = "STT"
compare(formValuesChanged.count, 5)
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
waitForRendering(payPanel)
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
let truncmaxPossibleValue = Math.trunc(maxPossibleValue*100)/100
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(truncmaxPossibleValue, expectedToken.symbol, {noSymbol: true})))
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, valueToExchange.toLocaleString(Qt.locale(), 'f', -128))
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
// click on max button
maxTagButton.clicked()
waitForRendering(payPanel)
compare(formValuesChanged.count, 6)
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
closeAndVerfyModal()
}
function test_modal_max_button_click_with_no_preset_pay_value() {
// Launch popup
launchAndVerfyModal()
// try setting value before popup is launched and check values
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.toTokenKey = "STT"
compare(formValuesChanged.count, 4)
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
waitForRendering(payPanel)
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
let truncmaxPossibleValue = Math.trunc(maxPossibleValue*100)/100
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(truncmaxPossibleValue, expectedToken.symbol, {noSymbol: true})))
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, "")
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
// click on max button
maxTagButton.clicked()
waitForRendering(payPanel)
compare(formValuesChanged.count, 5)
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
closeAndVerfyModal()
}
function test_modal_pay_input_switching_accounts() {
// test with pay value being set and not set
let payValuesToTestWith = ["", "0.2"]
for (let index = 0; index < payValuesToTestWith.length; index ++) {
let valueToExchangeString = payValuesToTestWith[index]
let valueToExchange = Number(valueToExchangeString)
// Asset chosen but no pay value set state -------------------------------------------------------------------------------
root.swapFormData.fromTokenAmount = valueToExchangeString
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.fromTokensKey = "ETH"
// Launch popup
launchAndVerfyModal()
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const maxTagButton = findChild(payPanel, "maxTagButton")
verify(!!maxTagButton)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
verify(!!amountToSendInput)
const errorTag = findChild(controlUnderTest, "errorTag")
verify(!!errorTag)
for (let i=0; i< root.swapAdaptor.nonWatchAccounts.count; i++) {
root.swapFormData.selectedAccountAddress = root.swapAdaptor.nonWatchAccounts.get(i).address
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
waitForRendering(payPanel)
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100
compare(maxTagButton.text, qsTr("Max. %1").arg(maxPossibleValue === 0 ? Qt.locale().zeroDigit : root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue, expectedToken.symbol, {noSymbol: true, minDecimals: 0})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
/* TODO bug in max button not shown in red when max is 0 value and
bug in swapInputModal that in case value entered is greater than maxPossibleValue then value is reset to 0, making the cryptoValueValid to false
https://github.com/status-im/status-desktop/issues/15162 */
compare(payPanel.cryptoValueValid, (valueToExchangeString === amountToSendInput.input.text) && !!root.swapFormData.fromTokenAmount && valueToExchange <= maxPossibleValue)
/* TODO: there is bug which prevents us from testing this right now
The value is not updated after setting tokenAmount to empty string in the receive input panel
https://github.com/status-im/status-desktop/issues/15162
compare(payPanel.cryptoValue, valueToExchange)
compare(payPanel.cryptoValueRaw, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString())*/
/* TODO: check if tag is visible in case amount entered to exchange is greater than max balance to send
https://github.com/status-im/status-desktop/issues/15162 */
let errortext = /*valueToExchange > maxPossibleValue ? qsTr("Insufficient funds for swap"): */qsTr("An error has occured, please try again")
let buttonText = /*valueToExchange > maxPossibleValue ? qsTr("Buy crypto"):*/ ""
compare(errorTag.visible, false/*valueToExchange > maxPossibleValue*/)
compare(errorTag.text, errortext)
compare(errorTag.buttonText, buttonText)
}
closeAndVerfyModal()
}
}
}
}

View File

@ -136,7 +136,7 @@ Item {
}
property SwapInputParamsForm swapFormData: SwapInputParamsForm {
selectedAccountIndex: d.selectedAccountIndex
selectedAccountAddress: StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, d.selectedAccountIndex, "address")
selectedNetworkChainId: {
// Without this when we switch testnet mode, the correct network is not evaluated
RootStore.areTestNetworksEnabled
@ -216,7 +216,7 @@ Item {
hasFloatingButtons: true
})
onLaunchSwapModal: {
d.swapFormData.selectedAccountIndex = d.selectedAccountIndex
d.swapFormData.selectedAccountAddress = StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, d.selectedAccountIndex, "address")
d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId")
d.swapFormData.fromTokensKey = tokensKey
d.swapFormData.toTokenKey = RootStore.areTestNetworksEnabled ? Constants.swap.testStatusTokenKey : Constants.swap.mainnetStatusTokenKey
@ -335,7 +335,7 @@ Item {
}
onLaunchSwapModal: {
d.swapFormData.fromTokensKey = ""
d.swapFormData.selectedAccountIndex = d.selectedAccountIndex
d.swapFormData.selectedAccountAddress = StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, d.selectedAccountIndex, "address")
d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId")
if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) {
d.swapFormData.fromTokensKey = walletStore.currentViewedHoldingTokensKey

View File

@ -67,7 +67,7 @@ QtObject {
rationale: https://github.com/status-im/status-desktop/pull/14959#discussion_r1627110880
*/
function calculateMaxSafeSendAmount(value, symbol) {
if (symbol !== Constants.ethToken) {
if (symbol !== Constants.ethToken || value === 0) {
return value
}

View File

@ -29,25 +29,31 @@ Control {
required property var processedAssetsModel
property string tokenKey
onTokenKeyChanged: {
if (!!tokenKey)
Qt.callLater(d.setSelectedHoldingId, tokenKey, Constants.TokenType.ERC20)
}
onTokenKeyChanged: reevaluateSelectedId()
property string tokenAmount
onTokenAmountChanged: {
if (!!tokenAmount)
Qt.callLater(() => amountToSendInput.input.text = Number(tokenAmount).toLocaleString(Qt.locale(), 'f', -128))
Qt.callLater(() => amountToSendInput.input.text = SQUtils.AmountsArithmetic.fromString(tokenAmount).toLocaleString(locale, 'f', -128))
}
property int swapSide: SwapInputPanel.SwapSide.Pay
property bool fiatInputInteractive
property bool loading
property bool interactive: true
// output API
readonly property string selectedHoldingId: d.selectedHoldingId
readonly property double cryptoValue: amountToSendInput.cryptoValueToSendFloat
readonly property string cryptoValueRaw: amountToSendInput.cryptoValueToSend
readonly property bool cryptoValueValid: amountToSendInput.inputNumberValid
/* TODO: this does not work as expected because of bug -
https://github.com/status-im/status-desktop/issues/15162 */
readonly property bool amountEnteredGreaterThanBalance: cryptoValue > maxSendButton.maxSafeValue
function reevaluateSelectedId() {
if (!!tokenKey) {
Qt.callLater(d.setSelectedHoldingId, tokenKey, Constants.TokenType.ERC20)
}
}
// visual properties
property int swapExchangeButtonWidth: 44
@ -185,7 +191,7 @@ Control {
id: amountToSendInput
objectName: "amountToSendInput"
caption: root.caption
interactive: true
interactive: root.interactive
selectedHolding: d.selectedHolding
fiatInputInteractive: root.fiatInputInteractive
input.input.edit.color: !input.valid ? Theme.palette.dangerColor1 : maxSendButton.hovered ? Theme.palette.baseColor1

View File

@ -9,15 +9,15 @@ QtObject {
signal formValuesChanged()
property int selectedAccountIndex: 0
property string selectedAccountAddress: ""
property int selectedNetworkChainId: -1
property string fromTokensKey: ""
property string fromTokenAmount: "0"
property string fromTokenAmount: ""
property string toTokenKey: ""
property string toTokenAmount: "0"
property string toTokenAmount: ""
property double selectedSlippage: 0.5
onSelectedAccountIndexChanged: root.formValuesChanged()
onSelectedAccountAddressChanged: root.formValuesChanged()
onSelectedNetworkChainIdChanged: root.formValuesChanged()
onFromTokensKeyChanged: root.formValuesChanged()
onFromTokenAmountChanged: root.formValuesChanged()
@ -25,17 +25,17 @@ QtObject {
onToTokenAmountChanged: root.formValuesChanged()
function resetFormData() {
selectedAccountIndex = 0
selectedAccountAddress = ""
selectedNetworkChainId = -1
fromTokensKey = ""
fromTokenAmount = "0"
fromTokenAmount = ""
toTokenKey = ""
toTokenAmount = "0"
toTokenAmount = ""
selectedSlippage = 0.5
}
function isFormFilledCorrectly() {
return root.selectedAccountIndex >= 0 &&
return !!root.selectedAccountAddress &&
root.selectedNetworkChainId !== -1 &&
!!root.fromTokensKey && !!root.toTokenKey &&
((!!root.fromTokenAmount &&

View File

@ -15,6 +15,7 @@ import shared.popups.send.controls 1.0
import shared.controls 1.0
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.panels 1.0
StatusDialog {
id: root
@ -35,17 +36,29 @@ StatusDialog {
QtObject {
id: d
property var fetchSuggestedRoutes: Backpressure.debounce(root, 1000, function() {
root.swapAdaptor.fetchSuggestedRoutes()
property var debounceFetchSuggestedRoutes: Backpressure.debounce(root, 1000, function() {
root.swapAdaptor.fetchSuggestedRoutes(payPanel.cryptoValueRaw)
})
function fetchSuggestedRoutes() {
root.swapAdaptor.newFetchReset()
root.swapAdaptor.swapProposalLoading = true
debounceFetchSuggestedRoutes()
}
}
Connections {
target: root.swapInputParamsForm
function onFormValuesChanged() {
root.swapAdaptor.swapProposalLoading = true
d.fetchSuggestedRoutes()
}
// refresh the selected asset in payPanel when account/network changes
function onSelectedAccountAddressChanged() {
payPanel.reevaluateSelectedId()
}
function onSelectedNetworkChainIdChanged() {
payPanel.reevaluateSelectedId()
}
}
Behavior on implicitHeight {
@ -63,9 +76,9 @@ StatusDialog {
formatCurrencyAmount: root.swapAdaptor.formatCurrencyAmount
/* TODO: once the Account Header is reworked we simply should be
able to use an index and not this logic of selectedAccount being set */
selectedAccount: root.swapAdaptor.getSelectedAccount(root.swapInputParamsForm.selectedAccountIndex)
selectedAccount: root.swapAdaptor.getSelectedAccountByAddress(root.swapInputParamsForm.selectedAccountAddress)
onSelectedIndexChanged: {
root.swapInputParamsForm.selectedAccountIndex = selectedIndex
root.swapInputParamsForm.selectedAccountAddress = root.swapAdaptor.getSelectedAccountAddressByIndex(selectedIndex)
}
}
@ -115,60 +128,88 @@ StatusDialog {
}
}
// This is a temporary placeholder while each of the components are being added.
ColumnLayout {
Item {
Layout.fillWidth: true
spacing: 0
StatusBaseText {
text: "This area is a temporary placeholder"
font.bold: true
}
/* TODO: Will be swapped out under https://github.com/status-im/status-desktop/issues/14825
Will also handle that if one input is being edited the other one should be in loading state in that task */
StatusBaseText {
text: qsTr("Selected from token: %1").arg(swapInputParamsForm.fromTokensKey)
}
StatusInput {
id: fromTokenAmountInput
Layout.fillWidth: true
text: swapInputParamsForm.fromTokenAmount
onTextChanged: {
swapInputParamsForm.fromTokenAmount = text
Layout.topMargin: 2
Layout.preferredHeight: payPanel.height + receivePanel.height + 4
SwapInputPanel {
id: payPanel
objectName: "payPanel"
anchors {
left: parent.left
right: parent.right
top: parent.top
}
}
/* TODO: Will be swapped out under https://github.com/status-im/status-desktop/issues/14826
Will also handle that if one input is being edited the other one should be in loading state in that task */
StatusBaseText {
text: qsTr("Selected to token: %1").arg(swapInputParamsForm.toTokenKey)
}
StatusInput {
id: toTokenAmountInput
Layout.fillWidth: true
text: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.toToken ?
root.swapAdaptor.formatCurrencyAmount(root.swapAdaptor.swapOutputData.toTokenAmount,
root.swapAdaptor.toToken.symbol,
{"minDecimals": root.swapAdaptor.toToken.decimals,
"stripTrailingZeroes": true, "noSymbol": true}) :
root.swapInputParamsForm.toTokenAmount
onTextChanged: {
if (!root.swapAdaptor.validSwapProposalReceived) {
swapInputParamsForm.toTokenAmount = text
currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptor.filteredFlatNetworksModel
processedAssetsModel: root.swapAdaptor.processedAssetsModel
tokenKey: root.swapInputParamsForm.fromTokensKey
tokenAmount: {
// Only update if there is different in amount displayed
if (root.swapInputParamsForm.fromTokenAmount !==
SQUtils.AmountsArithmetic.fromString(cryptoValue).toLocaleString(locale, 'f', -128)){
return root.swapInputParamsForm.fromTokenAmount
}
return payPanel.tokenAmount
}
/* TODO: keep this input as disabled until the work for adding a param to handle to
and from tokens inputed is supported by backend */
input.edit.enabled: false
swapSide: SwapInputPanel.SwapSide.Pay
swapExchangeButtonWidth: swapButton.width
onSelectedHoldingIdChanged: root.swapInputParamsForm.fromTokensKey = selectedHoldingId
onCryptoValueChanged: root.swapInputParamsForm.fromTokenAmount = cryptoValue.toLocaleString(locale, 'f', -128)
}
/* Needed only till sign after approval is implemented under
https://github.com/status-im/status-desktop/issues/14833 */
StatusButton {
text: "Final Swap after Approval"
onClicked: {
swapAdaptor.sendSwapTx()
SwapInputPanel {
id: receivePanel
objectName: "receivePanel"
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptorfilteredFlatNetworksModel
processedAssetsModel: root.swapAdaptor.processedAssetsModel
tokenKey: root.swapInputParamsForm.toTokenKey
tokenAmount: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.toToken ? root.swapAdaptor.swapOutputData.toTokenAmount: root.swapInputParamsForm.toTokenAmount
swapSide: SwapInputPanel.SwapSide.Receive
swapExchangeButtonWidth: swapButton.width
loading: root.swapAdaptor.swapProposalLoading
onSelectedHoldingIdChanged: root.swapInputParamsForm.toTokenKey = selectedHoldingId
/* TODO: keep this input as disabled until the work for adding a param to handle to
and from tokens inputed is supported by backend under
https://github.com/status-im/status-desktop/issues/15095 */
interactive: false
}
SwapExchangeButton {
id: swapButton
anchors.centerIn: parent
}
}
/* TODO: remove! Needed only till sign after approval is implemented under
https://github.com/status-im/status-desktop/issues/14833 */
StatusButton {
text: "Final Swap after Approval"
visible: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.swapOutputData.approvalNeeded
onClicked: {
swapAdaptor.sendSwapTx()
close()
}
}
// End temporary placeholders
EditSlippagePanel {
id: editSlippagePanel
@ -186,10 +227,17 @@ StatusDialog {
ErrorTag {
objectName: "errorTag"
visible: root.swapAdaptor.swapOutputData.hasError
visible: root.swapAdaptor.swapOutputData.hasError || payPanel.amountEnteredGreaterThanBalance
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Style.current.smallPadding
text: qsTr("An error has occured, please try again")
text: {
if (payPanel.amountEnteredGreaterThanBalance) {
return qsTr("Insufficient funds for swap")
}
return qsTr("An error has occured, please try again")
}
buttonText: payPanel.amountEnteredGreaterThanBalance ? qsTr("Buy crypto"): ""
onButtonClicked: Global.openBuyCryptoModalRequested()
}
}
@ -265,7 +313,9 @@ StatusDialog {
qsTr("Approve %1").arg(!!root.swapAdaptor.fromToken ? root.swapAdaptor.fromToken.symbol: "") :
qsTr("Swap")
disabledColor: Theme.palette.directColor8
enabled: root.swapAdaptor.validSwapProposalReceived && editSlippagePanel.valid
enabled: root.swapAdaptor.validSwapProposalReceived &&
editSlippagePanel.valid &&
!payPanel.amountEnteredGreaterThanBalance
onClicked: {
if (root.swapAdaptor.validSwapProposalReceived ){
if(root.swapAdaptor.swapOutputData.approvalNeeded) {
@ -273,8 +323,8 @@ StatusDialog {
}
else {
swapAdaptor.sendSwapTx()
close()
}
close()
}
}
}

View File

@ -115,12 +115,11 @@ QObject {
roleName: "chainId"
value: root.swapFormData.selectedNetworkChainId
enabled: root.swapFormData.selectedNetworkChainId !== -1
}/*,
// TODO enable once AccountsModalHeader is reworked!!
},
ValueFilter {
roleName: "account"
value: root.selectedSenderAccount.address
}*/
value: root.swapFormData.selectedAccountAddress
}
]
}
}
@ -203,6 +202,13 @@ QObject {
root.swapProposalLoading = false
}
// this function will not reset input params but only the output ones and loading states
function newFetchReset() {
root.swapOutputData.reset()
root.validSwapProposalReceived = false
root.swapProposalLoading = false
}
function getNetworkShortNames(chainIds) {
var networkString = ""
let chainIdsArray = chainIds.split(":")
@ -239,34 +245,35 @@ QObject {
}
// TODO: remove once the AccountsModalHeader is reworked!!
function getSelectedAccount(index) {
function getSelectedAccountAddressByIndex(index) {
if (root.nonWatchAccounts.count > 0 && index >= 0) {
return ModelUtils.get(nonWatchAccounts, index)
return ModelUtils.get(nonWatchAccounts, index, "address")
}
return ""
}
function getSelectedAccountByAddress(address) {
if (root.nonWatchAccounts.count > 0 && !!address) {
return ModelUtils.getByKey(root.nonWatchAccounts, "address", address)
}
return null
}
function fetchSuggestedRoutes() {
let amount = !!root.swapFormData.fromTokenAmount ? AmountsArithmetic.fromString(root.swapFormData.fromTokenAmount): NaN
root.swapOutputData.reset()
if(!isNaN(amount) && !!root.fromToken && root.swapFormData.isFormFilledCorrectly()) {
let fromTokenAmountInWei = AmountsArithmetic.fromNumber(amount, !!root.fromToken ? root.fromToken.decimals: 18).toString()
function fetchSuggestedRoutes(cryptoValueRaw) {
if (root.swapFormData.isFormFilledCorrectly() && !!cryptoValueRaw) {
root.swapOutputData.reset()
root.validSwapProposalReceived = false
// Identify new swap with a different uuid
d.uuid = Utils.uuid()
let account = getSelectedAccount(root.swapFormData.selectedAccountIndex)
let account = getSelectedAccountByAddress(root.swapFormData.selectedAccountAddress)
let accountAddress = account.address
let disabledChainIds = getDisabledChainIds(root.swapFormData.selectedNetworkChainId)
let preferedChainIds = getAllChainIds()
// TODO #14825: amount should be in BigInt string representation (fromTokenAmount * 10^decimals)
// Make sure that's replaced when the input component is integrated
root.swapStore.fetchSuggestedRoutes(accountAddress, accountAddress,
fromTokenAmountInWei, root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey,
cryptoValueRaw, root.swapFormData.fromTokensKey, root.swapFormData.toTokenKey,
disabledChainIds, disabledChainIds, preferedChainIds,
Constants.SendType.Swap, "")
} else {
@ -276,7 +283,7 @@ QObject {
}
function sendApproveTx() {
let account = getSelectedAccount(root.swapFormData.selectedAccountIndex)
let account = getSelectedAccountByAddress(root.swapFormData.selectedAccountAddress)
let accountAddress = account.address
root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress,
@ -285,7 +292,7 @@ QObject {
}
function sendSwapTx() {
let account = getSelectedAccount(root.swapFormData.selectedAccountIndex)
let account = getSelectedAccountByAddress(root.swapFormData.selectedAccountAddress)
let accountAddress = account.address
root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress,

View File

@ -5,8 +5,8 @@ to the swap request can be placed here at one place. */
QtObject {
id: root
property string fromTokenAmount: "0"
property string toTokenAmount: "0"
property string fromTokenAmount: ""
property string toTokenAmount: ""
property real totalFees: 0
property var bestRoutes: []
property bool approvalNeeded
@ -14,8 +14,8 @@ QtObject {
property var rawPaths: []
function reset() {
root.fromTokenAmount = "0"
root.toTokenAmount = "0"
root.fromTokenAmount = ""
root.toTokenAmount = ""
root.totalFees = 0
root.bestRoutes = []
root.approvalNeeded = false

View File

@ -91,6 +91,7 @@ Item {
contentItem: RowLayout {
StatusRoundedImage {
id: tokenIcon
objectName: "holdingSelectorsTokenIcon"
Layout.preferredWidth: root.contentIconSize
Layout.preferredHeight: root.contentIconSize
visible: !!d.iconSource
@ -102,6 +103,7 @@ Item {
}
}
StatusBaseText {
objectName: "holdingSelectorsContentItemText"
Layout.fillWidth: true
font.pixelSize: root.contentTextSize
elide: Text.ElideRight

View File

@ -95,8 +95,11 @@ ColumnLayout {
d.cryptoValueToSend, root.multiplierIndex).toString()
}
readonly property string zeroString:
LocaleUtils.numberToLocaleString(0, 2, topAmountToSendInput.locale)
// Crypto value should be represented by 0 and fiat with 0.00
readonly property string zeroString: {
let decimals = root.inputIsFiat ? 2 : 0
LocaleUtils.numberToLocaleString(0, decimals, topAmountToSendInput.locale)
}
readonly property double parsedInput:
LocaleUtils.numberFromLocaleString(topAmountToSendInput.text,