From 69c97839552f9126e51e1e0ba216c76110001d37 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Fri, 17 Jul 2026 20:45:42 -0300 Subject: [PATCH] fix(amm): prevent swaps with identical tokens --- apps/amm/qml/components/swap/SwapCard.qml | 25 +++++++++++++++--- apps/amm/tests/qml/tst_SwapPage.qml | 31 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/apps/amm/qml/components/swap/SwapCard.qml b/apps/amm/qml/components/swap/SwapCard.qml index f27434e..5bcbcc6 100644 --- a/apps/amm/qml/components/swap/SwapCard.qml +++ b/apps/amm/qml/components/swap/SwapCard.qml @@ -24,9 +24,26 @@ Rectangle { signal requestTokenSelect(string side) signal previewRequested(var snapshot) + function isSameToken(left, right) { + if (!left || !right) + return false + if (left.address && right.address) + return String(left.address) === String(right.address) + return left === right + } + function setToken(side, token) { - if (side === "sell") root.sellToken = token - else root.buyToken = token + if (side === "sell") { + const previousSellToken = root.sellToken + if (root.isSameToken(token, root.buyToken)) + root.buyToken = previousSellToken + root.sellToken = token + } else { + const previousBuyToken = root.buyToken + if (root.isSameToken(token, root.sellToken)) + root.sellToken = previousBuyToken + root.buyToken = token + } } readonly property real sellReserve: sellToken ? (sellToken.reserve || 0) : 0 @@ -65,11 +82,13 @@ Rectangle { readonly property bool hasAmount: editingSide === "sell" ? parsedSellInput > 0 : parsedBuyInput > 0 readonly property bool tokensSelected: sellToken !== null && buyToken !== null + readonly property bool sameTokenSelected: isSameToken(sellToken, buyToken) readonly property bool insufficientBalance: hasAmount && sellToken !== null && parsedSellAmount > (sellToken.balance || 0) readonly property bool insufficientLiquidity: hasAmount && buyToken !== null && parsedBuyAmount > (buyToken.reserve || 0) - readonly property bool canSubmit: tokensSelected && hasAmount && parsedSellAmount > 0 && parsedBuyAmount > 0 && !insufficientBalance && !insufficientLiquidity + readonly property bool canSubmit: tokensSelected && !sameTokenSelected && hasAmount && parsedSellAmount > 0 && parsedBuyAmount > 0 && !insufficientBalance && !insufficientLiquidity readonly property string submitButtonText: { + if (sameTokenSelected) return qsTr("Select different tokens") if (!hasAmount || !tokensSelected) return qsTr("Enter an amount") if (insufficientBalance) return qsTr("Insufficient balance") if (insufficientLiquidity) return qsTr("Insufficient liquidity") diff --git a/apps/amm/tests/qml/tst_SwapPage.qml b/apps/amm/tests/qml/tst_SwapPage.qml index 1675e77..101072d 100644 --- a/apps/amm/tests/qml/tst_SwapPage.qml +++ b/apps/amm/tests/qml/tst_SwapPage.qml @@ -107,5 +107,36 @@ Item { verify(snapshot.minReceived.length > 0) verify(Number(snapshot.minReceived) < Number(snapshot.buyAmount)) } + + function test_reselectingOppositeTokenSwapsPair() { + const page = createTemporaryObject(pageComponent, root) + verify(page) + + const card = findChild(page, "swapCard") + verify(card) + card.setToken("sell", page.tokens[0]) + card.setToken("buy", page.tokens[1]) + card.setToken("buy", page.tokens[0]) + + compare(card.sellToken.address, page.tokens[1].address) + compare(card.buyToken.address, page.tokens[0].address) + verify(!card.sameTokenSelected) + } + + function test_sameTokenPairCannotBePreviewed() { + const page = createTemporaryObject(pageComponent, root) + verify(page) + + const card = findChild(page, "swapCard") + verify(card) + card.sellToken = page.tokens[0] + card.buyToken = page.tokens[0] + card.sellInput = "1" + card.editingSide = "sell" + + verify(card.sameTokenSelected) + verify(!card.canSubmit) + compare(card.submitButtonText, "Select different tokens") + } } }