From caf53d0409b84bbf9987389614a7bc03bd1b502f Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Tue, 14 Jul 2026 10:29:55 +0200 Subject: [PATCH] fix(amm): order swap holdings/reserves by pool token order; accept base58 ids --- apps/amm/qml/components/swap/SwapCard.qml | 18 ++++++--- apps/amm/src/AmmUiBackend.cpp | 46 +++++++++++++++++++++-- apps/amm/src/AmmUiBackend.h | 4 ++ 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/apps/amm/qml/components/swap/SwapCard.qml b/apps/amm/qml/components/swap/SwapCard.qml index 5e19632..8c2f56f 100644 --- a/apps/amm/qml/components/swap/SwapCard.qml +++ b/apps/amm/qml/components/swap/SwapCard.qml @@ -24,15 +24,16 @@ Rectangle { property real slippageTolerancePercent: 0.5 // ── Pool resolution (backend.resolvePool) ─────────────────────────────── - // sellToken is always passed as defA and buyToken as defB, so reserveA - // always corresponds to sellToken and reserveB to buyToken — no separate - // "direction" bookkeeping is needed (unlike a generic A/B picker): the - // sell/buy assignment *is* the direction. + // A pool's PoolDefinition stores def_a/def_b in the pool CREATOR's order + // (from NewDefinition), not sorted, and not necessarily in (sellToken, + // buyToken) order. reserveA/reserveB mirror that same canonical order, so + // they must be mapped to sell/buy via poolDefAHex below — never assumed. property bool poolLoading: false property bool poolResolved: false property bool poolExists: false property string poolReserveA: "0" property string poolReserveB: "0" + property string poolDefAHex: "" property int poolFeeBps: 30 property string poolError: "" @@ -95,6 +96,7 @@ Rectangle { root.poolExists = !!(pool && pool.exists) root.poolReserveA = (pool && pool.reserveA) || "0" root.poolReserveB = (pool && pool.reserveB) || "0" + root.poolDefAHex = (pool && pool.defAHex) || "" root.poolFeeBps = (pool && pool.feeBps) || 30 root.poolError = "" }, @@ -111,8 +113,12 @@ Rectangle { // drive the *estimate* (expected output / min received / price impact), // never the actual swap amount — the sell amount sent to the backend is // the user's raw input text, passed through verbatim. - readonly property real sellReserveNum: Number(root.poolReserveA) || 0 - readonly property real buyReserveNum: Number(root.poolReserveB) || 0 + // The pool's reserveA/reserveB follow the pool's canonical def_a/def_b + // order (see poolDefAHex above), which may or may not match sell/buy — + // map them explicitly rather than assuming reserveA == sell. + readonly property bool sellIsPoolA: !!root.sellToken && root.sellToken.definitionId === root.poolDefAHex + readonly property real sellReserveNum: Number(sellIsPoolA ? root.poolReserveA : root.poolReserveB) || 0 + readonly property real buyReserveNum: Number(sellIsPoolA ? root.poolReserveB : root.poolReserveA) || 0 readonly property real parsedSellInput: { var amt = parseFloat(sellInput) diff --git a/apps/amm/src/AmmUiBackend.cpp b/apps/amm/src/AmmUiBackend.cpp index 922eb79..2af2503 100644 --- a/apps/amm/src/AmmUiBackend.cpp +++ b/apps/amm/src/AmmUiBackend.cpp @@ -1,5 +1,8 @@ #include "AmmUiBackend.h" +#include +#include + #include #include #include @@ -484,6 +487,26 @@ void AmmUiBackend::copyToClipboard(QString text) QGuiApplication::clipboard()->setText(text); } +QString AmmUiBackend::normalizeAccountId(const QString& id) +{ + const QString t = id.trimmed(); + // Already 64 hex chars? + if (t.size() == 64) { + bool allHex = true; + for (const QChar c : t) { + if (!std::isxdigit(static_cast(c.toLatin1()))) { + allHex = false; + break; + } + } + if (allHex) + return t.toLower(); + } + // Try base58 -> hex via the wallet module. + const QString hex = m_logos->logos_execution_zone.account_id_from_base58(t); + return hex.toLower(); // account_id_from_base58 returns "" on failure +} + QVariantMap AmmUiBackend::resolvePool(QString defAHex, QString defBHex) { QVariantMap out; @@ -574,6 +597,8 @@ QVariantMap AmmUiBackend::resolvePool(QString defAHex, QString defBHex) out[QStringLiteral("exists")] = true; out[QStringLiteral("configHex")] = configHex; out[QStringLiteral("poolIdHex")] = poolHex; + out[QStringLiteral("defAHex")] = bytes32ToHex(poolView.def_a); + out[QStringLiteral("defBHex")] = bytes32ToHex(poolView.def_b); out[QStringLiteral("vaultAHex")] = bytes32ToHex(poolView.vault_a); out[QStringLiteral("vaultBHex")] = bytes32ToHex(poolView.vault_b); out[QStringLiteral("currentTickHex")] = bytes32ToHex(currentTickPda); @@ -713,11 +738,26 @@ QVariantList AmmUiBackend::tokenList() continue; } const QJsonObject obj = entry.toObject(); + const QString symbol = obj.value(QStringLiteral("symbol")).toString(); + + // TOKENS_CONFIG entries may give definitionId/holding as hex or + // base58 (the wallet/runbook display base58) — normalize both to + // lowercase hex here so every downstream consumer (resolvePool, + // swapExactInput, and the QML's hex comparisons) can assume hex. + const QString definitionId = + normalizeAccountId(obj.value(QStringLiteral("definitionId")).toString()); + const QString holding = normalizeAccountId(obj.value(QStringLiteral("holding")).toString()); + if (definitionId.isEmpty() || holding.isEmpty()) { + qWarning() << "AmmUiBackend::tokenList: skipping token" << symbol + << "— cannot normalize definitionId/holding to hex (not valid hex or base58)"; + continue; + } + QVariantMap token; - token[QStringLiteral("symbol")] = obj.value(QStringLiteral("symbol")).toString(); + token[QStringLiteral("symbol")] = symbol; token[QStringLiteral("name")] = obj.value(QStringLiteral("name")).toString(); - token[QStringLiteral("definitionId")] = obj.value(QStringLiteral("definitionId")).toString(); - token[QStringLiteral("holding")] = obj.value(QStringLiteral("holding")).toString(); + token[QStringLiteral("definitionId")] = definitionId; + token[QStringLiteral("holding")] = holding; token[QStringLiteral("decimals")] = obj.value(QStringLiteral("decimals")).toInt(); out.append(token); } diff --git a/apps/amm/src/AmmUiBackend.h b/apps/amm/src/AmmUiBackend.h index 8690937..48daeda 100644 --- a/apps/amm/src/AmmUiBackend.h +++ b/apps/amm/src/AmmUiBackend.h @@ -68,6 +68,10 @@ private: void persistConfigPath(const QString& path); void persistStoragePath(const QString& path); + // Normalizes an account id given as either 64-char lowercase/uppercase hex + // or base58 to lowercase hex. Returns an empty QString if `id` is neither + // (or the base58 decode fails), so callers can detect and skip it. + QString normalizeAccountId(const QString& id); void openOrAdoptWallet(); // True when the shared core already has a wallet open — including a freshly // created one with zero accounts. See the definition for why list_accounts()