From 60a704661b549f945fb46ef9a822a7b688aa5f23 Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:24:17 +0200 Subject: [PATCH] refactor(amm): drop the `Raw` suffix from amount/value field names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `*Raw` suffix on the module's amount/price/balance/LP fields was redundant — every such field is already a base-unit integer, and there was no formatted sibling to disambiguate from. Drop it across the whole wire contract in lockstep: the amm_ffi request/response fields (snake_case `amount_in_raw` → `amount_in`, serde `rename_all="camelCase"` keeps the JSON keys mapped), the C++ module API, the QtRO `.rep`, the QML/app that consumes it, the mjs tests, and the module README. Examples: expectedOutRaw→expectedOut, minReceivedRaw→minReceived, maxInRaw→maxIn, requiredInRaw→requiredIn, priceRaw→price, reserve{A,B}Raw→ reserve{A,B}, amount{In,Out}Raw→amount{In,Out}, expectedLpRaw→expectedLp, lpAmountRaw→lpAmount, {max,min,minimum,actual}Amount{A,B}Raw, minimumLpRaw, minLpRaw, selectedBalance*Raw, totalSupplyRaw, quote*Raw. This also unifies a pre-existing inconsistency where resolvePoolAccount already emitted `reserveA` and resolveTokens already emitted `balance`. Kept where a formatted UI sibling of the same base name exists, so `Raw` still disambiguates the base-unit value: amountARaw / amountBRaw (vs the user-input `amountA`/`amountB`), balanceRaw (vs display `balance`), and initialPriceRaw (vs formatted `initialPrice`). Also kept the format-boundary helpers formatRaw / rawLpText / probeRaw / displayRaw / displayQuoteRaw / boundRaw. BREAKING: the `amm_module` public API field names change (logoscore / Basecamp / QtRO consumers must update). --- apps/amm/README.md | 2 +- .../components/liquidity/NewPositionForm.qml | 120 +++++----- .../liquidity/RemoveLiquidityDialog.qml | 48 ++-- .../components/liquidity/TokenAmountInput.qml | 6 +- apps/amm/qml/components/swap/SwapCard.qml | 48 ++-- apps/amm/qml/state/NewPositionFlow.qml | 50 ++-- apps/amm/src/AmmUiBackend.rep | 38 +-- apps/amm/tests/qml/tst_NewPositionForm.qml | 132 +++++------ apps/amm/tests/swap.mjs | 4 +- .../wallet/qml/ProgramAccountSelector.qml | 2 +- .../tests/qml/tst_ProgramAccountSelector.qml | 4 +- modules/amm/README.md | 55 +++-- modules/amm/ffi/src/api/liquidity.rs | 224 +++++++++--------- modules/amm/ffi/src/api/request.rs | 40 ++-- modules/amm/ffi/src/api/swap.rs | 48 ++-- modules/amm/ffi/tests/public_api.rs | 8 +- modules/amm/src/amm_module_impl.cpp | 82 +++---- modules/amm/src/amm_module_impl.h | 42 ++-- 18 files changed, 481 insertions(+), 472 deletions(-) diff --git a/apps/amm/README.md b/apps/amm/README.md index b248c92..812897f 100644 --- a/apps/amm/README.md +++ b/apps/amm/README.md @@ -296,7 +296,7 @@ app through a QML inspector: each test connects to the inspector's TCP server, finds elements, clicks them, and asserts on the resulting state. `swap.mjs` selects two tokens, enters a sell amount, submits a swap end-to-end, and then verifies the pool reserves actually changed on-chain (read back from the -sequencer via the app's `resolvePool`). +sequencer via the app's `resolvePoolAccount`). > **For the fully isolated, script-driven test flow** (a dedicated wallet from a > fixed mnemonic + auto-created pool + isolated token config, touching nothing in diff --git a/apps/amm/qml/components/liquidity/NewPositionForm.qml b/apps/amm/qml/components/liquidity/NewPositionForm.qml index bebd5e4..e81079c 100644 --- a/apps/amm/qml/components/liquidity/NewPositionForm.qml +++ b/apps/amm/qml/components/liquidity/NewPositionForm.qml @@ -29,8 +29,8 @@ AmmActionCard { property var holdings: [] readonly property string selectedHoldingAId: tokenAInput.selectedHoldingId readonly property string selectedHoldingBId: tokenBInput.selectedHoldingId - readonly property string selectedBalanceARaw: tokenAInput.selectedBalanceRaw - readonly property string selectedBalanceBRaw: tokenBInput.selectedBalanceRaw + readonly property string selectedBalanceA: tokenAInput.selectedBalance + readonly property string selectedBalanceB: tokenBInput.selectedBalance property string selectedTokenAId: "" property string selectedTokenBId: "" property int selectedFeeBps: 30 @@ -39,8 +39,8 @@ AmmActionCard { property string amountB: "" property string priceAmountA: "1" property string priceAmountB: "1" - property string minimumAmountARaw: "" - property string minimumAmountBRaw: "" + property string minimumAmountA: "" + property string minimumAmountB: "" property var localErrors: [] property string resolvingTokenId: "" property string resolvingTokenSide: "" @@ -144,7 +144,7 @@ AmmActionCard { // Per-side funding check, decoupled from buildQuoteRequest/the quote: the deposit each side // spends must fit its selected holding's balance (the lean createPoolQuote / addLiquidityQuote // ops never compare amount to balance, so a submit would otherwise fail on an - // insufficient-balance transfer). amountA / selectedBalanceARaw are both the display token-A + // insufficient-balance transfer). amountA / selectedBalanceA are both the display token-A // side, so no canonical reorientation is needed. readonly property bool fundingSufficient: root.fundingError("A").length === 0 && root.fundingError("B").length === 0 @@ -557,14 +557,14 @@ AmmActionCard { LabelValueRow { label: qsTr("Expected LP") - value: root.rawLpText(root.quotePayload.expectedLpRaw) + value: root.rawLpText(root.quotePayload.expectedLp) } LabelValueRow { label: root.activePool ? qsTr("Minimum LP") : qsTr("Locked LP") value: root.rawLpText(root.activePool - ? root.quotePayload.minimumLpRaw - : root.quotePayload.lockedLpRaw) + ? root.quotePayload.minimumLp + : root.quotePayload.lockedLp) } } @@ -791,9 +791,9 @@ AmmActionCard { var amount = root.amountA root.amountA = root.amountB root.amountB = amount - var minimum = root.minimumAmountARaw - root.minimumAmountARaw = root.minimumAmountBRaw - root.minimumAmountBRaw = minimum + var minimum = root.minimumAmountA + root.minimumAmountA = root.minimumAmountB + root.minimumAmountB = minimum var priceAmount = root.priceAmountA root.priceAmountA = root.priceAmountB root.priceAmountB = priceAmount @@ -824,8 +824,8 @@ AmmActionCard { root.amountB = "" root.priceAmountA = "1" root.priceAmountB = "1" - root.minimumAmountARaw = "" - root.minimumAmountBRaw = "" + root.minimumAmountA = "" + root.minimumAmountB = "" root.localErrors = [] // The pair changed: the pool is unknown until re-resolved. Reset poolExists BEFORE // requestQuote so activePool is false and the empty-amount short-circuit doesn't fire @@ -845,8 +845,8 @@ AmmActionCard { || !root.quoteMatchesPair()) { return } - var reserveA = String(root.quotePayload.reserveARaw || "") - var reserveB = String(root.quotePayload.reserveBRaw || "") + var reserveA = String(root.quotePayload.reserveA || "") + var reserveB = String(root.quotePayload.reserveB || "") if (AmountMath.isUnsigned(reserveA) && reserveA !== "0" && AmountMath.isUnsigned(reserveB) && reserveB !== "0") { root.activePoolQuote = root.quotePayload @@ -934,8 +934,8 @@ AmmActionCard { if (!parsedB.ok) errors.push(root.localIssue(parsedB.code, ["amountB"])) if (errors.length === 0) { - request.maxAmountARaw = root.displayIsCanonical ? parsedA.raw : parsedB.raw - request.maxAmountBRaw = root.displayIsCanonical ? parsedB.raw : parsedA.raw + request.maxAmountA = root.displayIsCanonical ? parsedA.raw : parsedB.raw + request.maxAmountB = root.displayIsCanonical ? parsedB.raw : parsedA.raw request.slippageBps = root.slippageBps } } else { @@ -947,7 +947,7 @@ AmmActionCard { root.displayIsCanonical) if (!price.ok) errors.push(root.localIssue(price.code, ["initialPrice"])) - if (root.missingPool && root.minimumAmountARaw.length > 0) { + if (root.missingPool && root.minimumAmountA.length > 0) { var parsedMissingA = AmountMath.parseHuman(root.amountA, root.decimalsA) var parsedMissingB = AmountMath.parseHuman(root.amountB, root.decimalsB) if (!parsedMissingA.ok) @@ -955,9 +955,9 @@ AmmActionCard { if (!parsedMissingB.ok) errors.push(root.localIssue(parsedMissingB.code, ["amountB"])) if (parsedMissingA.ok && parsedMissingB.ok) { - if (AmountMath.compare(parsedMissingA.raw, root.minimumAmountARaw) < 0) + if (AmountMath.compare(parsedMissingA.raw, root.minimumAmountA) < 0) errors.push(root.localIssue("amount_too_low", ["amountA"])) - if (AmountMath.compare(parsedMissingB.raw, root.minimumAmountBRaw) < 0) + if (AmountMath.compare(parsedMissingB.raw, root.minimumAmountB) < 0) errors.push(root.localIssue("amount_too_low", ["amountB"])) var pairedB = AmountMath.pairAmount(parsedMissingA.raw, true, @@ -981,9 +981,9 @@ AmmActionCard { errors.push(root.localIssue("deposit_ratio_mismatch", ["amountA", "amountB"])) } if (errors.length === 0) { - request.amountARaw = root.displayIsCanonical + request.amountA = root.displayIsCanonical ? parsedMissingA.raw : parsedMissingB.raw - request.amountBRaw = root.displayIsCanonical + request.amountB = root.displayIsCanonical ? parsedMissingB.raw : parsedMissingA.raw var actualPrice = AmountMath.ratioToQ64(root.amountA, root.amountB, @@ -991,7 +991,7 @@ AmmActionCard { root.canonicalDecimalsB, root.displayIsCanonical) if (actualPrice.ok) { - request.priceRaw = actualPrice.raw + request.price = actualPrice.raw priceFromAmounts = true } else { errors.push(root.localIssue(actualPrice.code, ["initialPrice"])) @@ -1000,13 +1000,13 @@ AmmActionCard { } } if (price.ok && !priceFromAmounts) - request.priceRaw = price.raw + request.price = price.raw if (!root.missingPool) { var probeA = root.probeRaw(root.tokenA, root.decimalsA) var probeB = root.probeRaw(root.tokenB, root.decimalsB) - request.maxAmountARaw = root.displayIsCanonical ? probeA : probeB - request.maxAmountBRaw = root.displayIsCanonical ? probeB : probeA + request.maxAmountA = root.displayIsCanonical ? probeA : probeB + request.maxAmountB = root.displayIsCanonical ? probeB : probeA request.slippageBps = root.slippageBps } } @@ -1047,8 +1047,8 @@ AmmActionCard { probe[field] = request[field] var amountA = root.probeRaw(root.tokenA, root.decimalsA) var amountB = root.probeRaw(root.tokenB, root.decimalsB) - probe.maxAmountARaw = root.displayIsCanonical ? amountA : amountB - probe.maxAmountBRaw = root.displayIsCanonical ? amountB : amountA + probe.maxAmountA = root.displayIsCanonical ? amountA : amountB + probe.maxAmountB = root.displayIsCanonical ? amountB : amountA probe.slippageBps = root.slippageBps return probe } @@ -1066,15 +1066,15 @@ AmmActionCard { return root.displayIsCanonical ? "tokenAId" : "tokenBId" if (field === "tokenBId") return root.displayIsCanonical ? "tokenBId" : "tokenAId" - if (field === "maxAmountARaw") + if (field === "maxAmountA") return root.displayIsCanonical ? "amountA" : "amountB" - if (field === "maxAmountBRaw") + if (field === "maxAmountB") return root.displayIsCanonical ? "amountB" : "amountA" - if (field === "amountARaw") + if (field === "amountA") return root.displayIsCanonical ? "amountA" : "amountB" - if (field === "amountBRaw") + if (field === "amountB") return root.displayIsCanonical ? "amountB" : "amountA" - if (field === "priceRaw") + if (field === "price") return "initialPrice" return field } @@ -1088,7 +1088,7 @@ AmmActionCard { return "" var amount = side === "A" ? root.amountA : root.amountB var decimals = side === "A" ? root.decimalsA : root.decimalsB - var balanceRaw = side === "A" ? root.selectedBalanceARaw : root.selectedBalanceBRaw + var balanceRaw = side === "A" ? root.selectedBalanceA : root.selectedBalanceB var parsed = AmountMath.parseHuman(amount, decimals) if (parsed.ok && AmountMath.compare(parsed.raw, balanceRaw) > 0) return "amount_exceeds_balance" @@ -1238,8 +1238,8 @@ AmmActionCard { root.priceAmountA = value else root.priceAmountB = value - root.minimumAmountARaw = "" - root.minimumAmountBRaw = "" + root.minimumAmountA = "" + root.minimumAmountB = "" root.amountA = "" root.amountB = "" root.noteDraftChanged() @@ -1299,12 +1299,12 @@ AmmActionCard { return if (root.missingPool) { - var rawA = root.displayRaw("actualAmountARaw", "actualAmountBRaw", "A") - var rawB = root.displayRaw("actualAmountARaw", "actualAmountBRaw", "B") - var minimumA = root.displayRaw("minimumAmountARaw", "minimumAmountBRaw", "A") - var minimumB = root.displayRaw("minimumAmountARaw", "minimumAmountBRaw", "B") - root.minimumAmountARaw = minimumA.length > 0 ? minimumA : rawA - root.minimumAmountBRaw = minimumB.length > 0 ? minimumB : rawB + var rawA = root.displayRaw("actualAmountA", "actualAmountB", "A") + var rawB = root.displayRaw("actualAmountA", "actualAmountB", "B") + var minimumA = root.displayRaw("minimumAmountA", "minimumAmountB", "A") + var minimumB = root.displayRaw("minimumAmountA", "minimumAmountB", "B") + root.minimumAmountA = minimumA.length > 0 ? minimumA : rawA + root.minimumAmountB = minimumB.length > 0 ? minimumB : rawB if (rawA.length > 0 && rawB.length > 0) { root.amountA = AmountMath.formatRaw(rawA, root.decimalsA) root.amountB = AmountMath.formatRaw(rawB, root.decimalsB) @@ -1325,12 +1325,12 @@ AmmActionCard { } function poolReserve(side) { - var reserve = root.displayRaw("reserveARaw", "reserveBRaw", side) + var reserve = root.displayRaw("reserveA", "reserveB", side) if (AmountMath.isUnsigned(reserve) && reserve !== "0") return reserve if (!root.quoteMatchesSelectedPair(root.activePoolQuote)) return "" - return root.displayQuoteRaw(root.activePoolQuote, "reserveARaw", "reserveBRaw", side) + return root.displayQuoteRaw(root.activePoolQuote, "reserveA", "reserveB", side) } function quoteAmount(canonicalAField, canonicalBField, side) { @@ -1343,8 +1343,8 @@ AmmActionCard { } function depositSummary() { - var amountA = root.quoteAmount("actualAmountARaw", "actualAmountBRaw", "A") - var amountB = root.quoteAmount("actualAmountARaw", "actualAmountBRaw", "B") + var amountA = root.quoteAmount("actualAmountA", "actualAmountB", "A") + var amountB = root.quoteAmount("actualAmountA", "actualAmountB", "B") return amountA + " + " + amountB } @@ -1366,10 +1366,10 @@ AmmActionCard { } function activePriceValue() { - var priceRaw = String(root.quotePayload.priceRaw || "") - if (priceRaw.length === 0 && root.quoteMatchesSelectedPair(root.activePoolQuote)) - priceRaw = String(root.activePoolQuote.priceRaw || "") - return AmountMath.priceFromQ64(priceRaw, + var price = String(root.quotePayload.price || "") + if (price.length === 0 && root.quoteMatchesSelectedPair(root.activePoolQuote)) + price = String(root.activePoolQuote.price || "") + return AmountMath.priceFromQ64(price, root.canonicalDecimalsA, root.canonicalDecimalsB, root.displayIsCanonical) @@ -1398,7 +1398,7 @@ AmmActionCard { return { "request": built.request, // Canonical-order holdings for the createPool / addLiquidity calls: the - // request's tokenAId/amountARaw are canonical, so holdingAId must be the + // request's tokenAId/amountA are canonical, so holdingAId must be the // canonical token A's holding too (the module re-canonicalizes as a no-op). // The user picks these via the per-side account selectors; selectedHoldingA // is display token A's holding, so it aligns with tokenA the same way. @@ -1406,12 +1406,12 @@ AmmActionCard { "holdingBId": String(root.displayIsCanonical ? root.selectedHoldingBId : root.selectedHoldingAId), // The add path's slippage floor on the LP minted (orientation-independent), // taken from the active-pool quote; ignored by the create path. - "minLpRaw": String(root.quotePayload.minimumLpRaw || ""), + "minLp": String(root.quotePayload.minimumLp || ""), "pairText": qsTr("%1 / %2").arg(root.shortTokenName(root.tokenA)).arg(root.shortTokenName(root.tokenB)), "feeText": root.feeLabel(root.selectedFeeBps), - "depositAText": root.quoteAmount("actualAmountARaw", "actualAmountBRaw", "A"), - "depositBText": root.quoteAmount("actualAmountARaw", "actualAmountBRaw", "B"), - "expectedLpText": root.rawLpText(root.quotePayload.expectedLpRaw), + "depositAText": root.quoteAmount("actualAmountA", "actualAmountB", "A"), + "depositBText": root.quoteAmount("actualAmountA", "actualAmountB", "B"), + "expectedLpText": root.rawLpText(root.quotePayload.expectedLp), // Confirm-dialog action derives from the resolved pool state (add vs create). "poolExists": root.activePool } @@ -1455,17 +1455,17 @@ AmmActionCard { function depositScaleValue() { var parsed = AmountMath.parseHuman(root.amountA, root.decimalsA) - if (!parsed.ok || !AmountMath.isUnsigned(root.minimumAmountARaw) - || root.minimumAmountARaw === "0" - || AmountMath.compare(parsed.raw, root.minimumAmountARaw) < 0) { + if (!parsed.ok || !AmountMath.isUnsigned(root.minimumAmountA) + || root.minimumAmountA === "0" + || AmountMath.compare(parsed.raw, root.minimumAmountA) < 0) { return "" } return AmountMath.divide(AmountMath.multiply(parsed.raw, "10000"), - root.minimumAmountARaw).quotient + root.minimumAmountA).quotient } function minimumAmountText(side) { - var raw = side === "A" ? root.minimumAmountARaw : root.minimumAmountBRaw + var raw = side === "A" ? root.minimumAmountA : root.minimumAmountB var decimals = side === "A" ? root.decimalsA : root.decimalsB return raw.length > 0 ? qsTr("Min %1").arg(AmountMath.formatRaw(raw, decimals)) : "" diff --git a/apps/amm/qml/components/liquidity/RemoveLiquidityDialog.qml b/apps/amm/qml/components/liquidity/RemoveLiquidityDialog.qml index f6afaa5..4ccb5ca 100644 --- a/apps/amm/qml/components/liquidity/RemoveLiquidityDialog.qml +++ b/apps/amm/qml/components/liquidity/RemoveLiquidityDialog.qml @@ -40,16 +40,16 @@ Popup { property int slippageBps: 50 // 1..100. Percent rather than a raw amount: it is what the presets and the - // slider both drive, and it keeps "all of it" exact (see lpAmountRaw). + // slider both drive, and it keeps "all of it" exact (see lpAmount). property int percent: 50 // ── Quote state (backend.removeLiquidityQuote) ─────────────────────────── property bool quoteLoading: false property string quoteError: "" - property string amountARaw: "0" - property string amountBRaw: "0" - property string minimumAmountARaw: "0" - property string minimumAmountBRaw: "0" + property string amountA: "0" + property string amountB: "0" + property string minimumAmountA: "0" + property string minimumAmountB: "0" property bool quoteReady: false property bool submitting: false @@ -63,12 +63,12 @@ Popup { // 100% burns the whole balance exactly; anything else floors, so the dust // stays in the position rather than rounding the request above the balance. - readonly property string lpAmountRaw: root.percent >= 100 + readonly property string lpAmount: root.percent >= 100 ? AmountMath.normalize(root.lpBalance) : AmountMath.mulDivFloor(root.lpBalance, String(root.percent), "100") - readonly property bool hasAmount: AmountMath.isUnsigned(root.lpAmountRaw) - && AmountMath.normalize(root.lpAmountRaw) !== "0" + readonly property bool hasAmount: AmountMath.isUnsigned(root.lpAmount) + && AmountMath.normalize(root.lpAmount) !== "0" readonly property bool canSubmit: root.hasAmount && root.quoteReady && !root.quoteLoading @@ -97,10 +97,10 @@ Popup { root.quoteError = "" root.submitError = "" root.quoteReady = false - root.amountARaw = "0" - root.amountBRaw = "0" - root.minimumAmountARaw = "0" - root.minimumAmountBRaw = "0" + root.amountA = "0" + root.amountB = "0" + root.minimumAmountA = "0" + root.minimumAmountB = "0" root.open() root.requestQuote() } @@ -132,7 +132,7 @@ Popup { root.runtime.watch(root.backend.removeLiquidityQuote({ "tokenAId": root.tokenAId, "tokenBId": root.tokenBId, - "lpAmountRaw": root.lpAmountRaw, + "lpAmount": root.lpAmount, "slippageBps": root.slippageBps }), function(quote) { @@ -140,10 +140,10 @@ Popup { return root.quoteLoading = false if (quote && quote.status === "ok") { - root.amountARaw = String(quote.amountARaw || "0") - root.amountBRaw = String(quote.amountBRaw || "0") - root.minimumAmountARaw = String(quote.minimumAmountARaw || "0") - root.minimumAmountBRaw = String(quote.minimumAmountBRaw || "0") + root.amountA = String(quote.amountA || "0") + root.amountB = String(quote.amountB || "0") + root.minimumAmountA = String(quote.minimumAmountA || "0") + root.minimumAmountB = String(quote.minimumAmountB || "0") root.quoteError = "" root.quoteReady = true return @@ -173,11 +173,11 @@ Popup { "holdingAId": root.holdingAId, "holdingBId": root.holdingBId, "lpHoldingId": root.lpHoldingId, - "lpAmountRaw": root.lpAmountRaw, + "lpAmount": root.lpAmount, // The floors the quote computed for this exact amount, so the submit // enforces the slippage the preview promised. - "minAmountARaw": root.minimumAmountARaw, - "minAmountBRaw": root.minimumAmountBRaw, + "minAmountA": root.minimumAmountA, + "minAmountB": root.minimumAmountB, // u64-max sentinel = no deadline, same as the other submits. "deadlineMs": "18446744073709551615" }), @@ -367,21 +367,21 @@ Popup { AmountLine { objectName: "removeReceiveA" symbol: root.symbolA - amount: root.quoteReady ? root.amountText(root.amountARaw) : qsTr("—") + amount: root.quoteReady ? root.amountText(root.amountA) : qsTr("—") } AmountLine { objectName: "removeReceiveB" symbol: root.symbolB - amount: root.quoteReady ? root.amountText(root.amountBRaw) : qsTr("—") + amount: root.quoteReady ? root.amountText(root.amountB) : qsTr("—") } Text { Layout.fillWidth: true visible: root.quoteReady text: qsTr("At least %1 %2 and %3 %4 after slippage.") - .arg(root.amountText(root.minimumAmountARaw)).arg(root.symbolA) - .arg(root.amountText(root.minimumAmountBRaw)).arg(root.symbolB) + .arg(root.amountText(root.minimumAmountA)).arg(root.symbolA) + .arg(root.amountText(root.minimumAmountB)).arg(root.symbolB) color: root.theme.colors.textPlaceholder font.pixelSize: 11 wrapMode: Text.Wrap diff --git a/apps/amm/qml/components/liquidity/TokenAmountInput.qml b/apps/amm/qml/components/liquidity/TokenAmountInput.qml index 75b13dd..ecb0b7b 100644 --- a/apps/amm/qml/components/liquidity/TokenAmountInput.qml +++ b/apps/amm/qml/components/liquidity/TokenAmountInput.qml @@ -34,8 +34,8 @@ AmmTokenAmountSurface { ? String(root.footerItem.selectedAccountId) : "" // Base-unit balance of the selected funding holding (the lean quotes don't check funding, // so the form compares this against the entered amount). "0" when nothing is selected. - readonly property string selectedBalanceRaw: root.footerItem && root.footerItem.selectedBalanceRaw - ? String(root.footerItem.selectedBalanceRaw) : "0" + readonly property string selectedBalance: root.footerItem && root.footerItem.selectedBalance + ? String(root.footerItem.selectedBalance) : "0" footer: root.showHoldingSelector ? accountFooter : null footerHeight: root.footerItem ? root.footerItem.implicitHeight : 0 @@ -95,7 +95,7 @@ AmmTokenAmountSurface { implicitHeight: footerSelector.implicitHeight property alias selectedAccountId: footerSelector.selectedAccountId - property alias selectedBalanceRaw: footerSelector.selectedBalanceRaw + property alias selectedBalance: footerSelector.selectedBalance ProgramAccountSelector { id: footerSelector diff --git a/apps/amm/qml/components/swap/SwapCard.qml b/apps/amm/qml/components/swap/SwapCard.qml index b7615e1..ede95d5 100644 --- a/apps/amm/qml/components/swap/SwapCard.qml +++ b/apps/amm/qml/components/swap/SwapCard.qml @@ -47,15 +47,15 @@ Rectangle { // ── Exact-input quote (backend.swapExactInQuote) ──────────────────────── property bool quoteInLoading: false property string quoteInError: "" - property string quoteExpectedOutRaw: "0" - property string quoteMinReceivedRaw: "0" + property string quoteExpectedOut: "0" + property string quoteMinReceived: "0" property int quotePriceImpactBps: 0 // ── Exact-output quote (backend.swapExactOutQuote) ────────────────────── property bool quoteOutLoading: false property string quoteOutError: "" - property string quoteRequiredInRaw: "0" - property string quoteMaxInRaw: "0" + property string quoteRequiredIn: "0" + property string quoteMaxIn: "0" property int quoteOutPriceImpactBps: 0 // ── Swap submission (backend.swapExactInput) ──────────────────────────── @@ -162,8 +162,8 @@ Rectangle { } function resetQuoteIn() { - root.quoteExpectedOutRaw = "0" - root.quoteMinReceivedRaw = "0" + root.quoteExpectedOut = "0" + root.quoteMinReceived = "0" root.quotePriceImpactBps = 0 } @@ -217,8 +217,8 @@ Rectangle { return root.quoteInLoading = false if (quote && quote.status === "ok") { - root.quoteExpectedOutRaw = quote.expectedOutRaw || "0" - root.quoteMinReceivedRaw = quote.minReceivedRaw || "0" + root.quoteExpectedOut = quote.expectedOut || "0" + root.quoteMinReceived = quote.minReceived || "0" root.quotePriceImpactBps = quote.priceImpactBps || 0 root.quoteInError = "" } else { @@ -247,8 +247,8 @@ Rectangle { } function resetQuoteOut() { - root.quoteRequiredInRaw = "0" - root.quoteMaxInRaw = "0" + root.quoteRequiredIn = "0" + root.quoteMaxIn = "0" root.quoteOutPriceImpactBps = 0 } @@ -311,8 +311,8 @@ Rectangle { return root.quoteOutLoading = false if (quote && quote.status === "ok") { - root.quoteRequiredInRaw = quote.requiredInRaw || "0" - root.quoteMaxInRaw = quote.maxInRaw || "0" + root.quoteRequiredIn = quote.requiredIn || "0" + root.quoteMaxIn = quote.maxIn || "0" root.quoteOutPriceImpactBps = quote.priceImpactBps || 0 root.quoteOutError = "" } else { @@ -348,11 +348,11 @@ Rectangle { // exact figures shown and submitted come from the raw quote strings directly. readonly property real parsedSellAmount: editingSide === "sell" ? parsedSellInput - : (Number(root.quoteRequiredInRaw) || 0) + : (Number(root.quoteRequiredIn) || 0) readonly property real parsedBuyAmount: editingSide === "buy" ? parsedBuyInput - : (Number(root.quoteExpectedOutRaw) || 0) + : (Number(root.quoteExpectedOut) || 0) readonly property real feeAmount: swapState.feeAmount(parsedSellAmount) @@ -362,7 +362,7 @@ Rectangle { // The quote's exact-integer bound, verbatim (no Number()/double round-trip, // which would lose precision on large u128 values and diverge from execution): // min received (exact input) or max sent (exact output). - readonly property string boundRaw: editingSide === "sell" ? root.quoteMinReceivedRaw : root.quoteMaxInRaw + readonly property string bound: editingSide === "sell" ? root.quoteMinReceived : root.quoteMaxIn readonly property string boundSymbol: editingSide === "sell" ? (buyToken ? buyToken.symbol : "") : (sellToken ? sellToken.symbol : "") @@ -432,11 +432,11 @@ Rectangle { // output in the Sell direction. readonly property string sellDisplay: editingSide === "sell" ? sellInput - : ((root.quoteRequiredInRaw && root.quoteRequiredInRaw !== "0") ? root.quoteRequiredInRaw : "") + : ((root.quoteRequiredIn && root.quoteRequiredIn !== "0") ? root.quoteRequiredIn : "") readonly property string buyDisplay: editingSide === "buy" ? buyInput - : ((root.quoteExpectedOutRaw && root.quoteExpectedOutRaw !== "0") ? root.quoteExpectedOutRaw : "") + : ((root.quoteExpectedOut && root.quoteExpectedOut !== "0") ? root.quoteExpectedOut : "") // Confirmation-dialog preview. The typed side is exact; the quoted side and // the slippage bound come from the quote's exact-integer strings. boundValue @@ -446,9 +446,9 @@ Rectangle { return { "sellToken": sellToken ? sellToken.symbol : "", "buyToken": buyToken ? buyToken.symbol : "", - "sellAmount": isExactIn ? root.sellInput : root.quoteRequiredInRaw, - "buyAmount": isExactIn ? root.quoteExpectedOutRaw : root.buyInput, - "boundValue": isExactIn ? root.quoteMinReceivedRaw : root.quoteMaxInRaw, + "sellAmount": isExactIn ? root.sellInput : root.quoteRequiredIn, + "buyAmount": isExactIn ? root.quoteExpectedOut : root.buyInput, + "boundValue": isExactIn ? root.quoteMinReceived : root.quoteMaxIn, "feeAmount": swapState.formatTokenAmount(feeAmount, sellToken ? sellToken.symbol : ""), "priceImpactPercent": swapState.formatPercent(priceImpactPercent), "priceImpactPercentValue": priceImpactPercent, @@ -477,13 +477,13 @@ Rectangle { var outHolding = root.buyHolding // The on-chain guard is the quote's exact-integer bound: the exact-input - // floor (minReceivedRaw) or the exact-output ceiling (maxInRaw). The typed + // floor (minReceived) or the exact-output ceiling (maxIn). The typed // side (sellInput / buyInput) is the exact amount for that direction. var pending = root.editingSide === "sell" ? root.backend.swapExactInput(inDef, outDef, inHolding, outHolding, - root.sellInput, root.quoteMinReceivedRaw, deadline) + root.sellInput, root.quoteMinReceived, deadline) : root.backend.swapExactOutput(inDef, outDef, inHolding, outHolding, - root.buyInput, root.quoteMaxInRaw, deadline) + root.buyInput, root.quoteMaxIn, deadline) logos.watch(pending, function (txHash) { @@ -647,7 +647,7 @@ Rectangle { priceImpactText: swapState.formatPercent(root.priceImpactPercent) priceImpactPercent: root.priceImpactPercent boundLabel: root.boundLabel - boundText: root.boundSymbol ? (root.boundRaw + " " + root.boundSymbol) : root.boundRaw + boundText: root.boundSymbol ? (root.bound + " " + root.boundSymbol) : root.bound } SlippageToleranceControl { diff --git a/apps/amm/qml/state/NewPositionFlow.qml b/apps/amm/qml/state/NewPositionFlow.qml index 02926bf..4edcdd3 100644 --- a/apps/amm/qml/state/NewPositionFlow.qml +++ b/apps/amm/qml/state/NewPositionFlow.qml @@ -129,8 +129,8 @@ QtObject { root.runtime.watch(root.backend.addLiquidityQuote({ "tokenAId": built.request.tokenAId, "tokenBId": built.request.tokenBId, - "maxAmountARaw": built.request.maxAmountARaw, - "maxAmountBRaw": built.request.maxAmountBRaw, + "maxAmountA": built.request.maxAmountA, + "maxAmountB": built.request.maxAmountB, "slippageBps": built.request.slippageBps }), function(quote) { @@ -187,32 +187,32 @@ QtObject { "status": "ok", "tokenAId": built.request.tokenAId, "tokenBId": built.request.tokenBId, - "actualAmountARaw": String(quote.actualAmountARaw || "0"), - "actualAmountBRaw": String(quote.actualAmountBRaw || "0"), - "minimumAmountARaw": String(quote.minimumAmountARaw || "0"), - "minimumAmountBRaw": String(quote.minimumAmountBRaw || "0"), - "expectedLpRaw": String(quote.expectedLpRaw || "0"), - "lockedLpRaw": String(quote.lockedLpRaw || "0"), - "priceRaw": String(quote.priceRaw || "0") + "actualAmountA": String(quote.actualAmountA || "0"), + "actualAmountB": String(quote.actualAmountB || "0"), + "minimumAmountA": String(quote.minimumAmountA || "0"), + "minimumAmountB": String(quote.minimumAmountB || "0"), + "expectedLp": String(quote.expectedLp || "0"), + "lockedLp": String(quote.lockedLp || "0"), + "price": String(quote.price || "0") } } // Maps addLiquidityQuote + the pool read into the quote shape NewPositionForm reads for an // active pool. Amounts/reserves are in the request's (canonical) order, matching the form's - // displayIsCanonical mapping; minimumLpRaw is the slippage floor the module computed. + // displayIsCanonical mapping; minimumLp is the slippage floor the module computed. function assembleAddQuote(built, pool, quote) { return { "status": "ok", "tokenAId": built.request.tokenAId, "tokenBId": built.request.tokenBId, - "actualAmountARaw": String(quote.amountARaw || "0"), - "actualAmountBRaw": String(quote.amountBRaw || "0"), - "expectedLpRaw": String(quote.expectedLpRaw || "0"), - "minimumLpRaw": String(quote.minimumLpRaw || "0"), - "reserveARaw": String(pool.reserveA || "0"), - "reserveBRaw": String(pool.reserveB || "0"), + "actualAmountA": String(quote.amountA || "0"), + "actualAmountB": String(quote.amountB || "0"), + "expectedLp": String(quote.expectedLp || "0"), + "minimumLp": String(quote.minimumLp || "0"), + "reserveA": String(pool.reserveA || "0"), + "reserveB": String(pool.reserveB || "0"), "poolFeeBps": pool.feeBps, - "priceRaw": String(quote.priceRaw || "0") + "price": String(quote.price || "0") } } @@ -227,12 +227,12 @@ QtObject { return } - // Route by pool state: creation (priceRaw is set only on the missing-pool + // Route by pool state: creation (price is set only on the missing-pool // path) goes through createPool; the active-pool branch through addLiquidity. Both // mint a fresh LP holding then submit via the lean module ops (hex ids, // caller-provided accounts). Quoting for both branches is now on the lean ops // (createPoolQuote / addLiquidityQuote), routed by resolvePool in requestQuoteNow. - if (snapshot.request.priceRaw !== undefined) + if (snapshot.request.price !== undefined) root.createPool(snapshot) else root.addLiquidity(snapshot) @@ -262,8 +262,8 @@ QtObject { "holdingAId": snapshot.holdingAId, "holdingBId": snapshot.holdingBId, "lpHoldingId": lpHoldingId, - "amountARaw": snapshot.request.amountARaw, - "amountBRaw": snapshot.request.amountBRaw, + "amountA": snapshot.request.amountA, + "amountB": snapshot.request.amountB, "feeBps": snapshot.request.feeBps, // u64-max sentinel = no deadline, same as the swap submits. "deadlineMs": "18446744073709551615" @@ -291,7 +291,7 @@ QtObject { // Add liquidity to an existing pool via the new addLiquidity op. Like createPool a fresh // LP holding receives the minted LP, so create one then submit. The submit reuses the - // addLiquidityQuote result (maxAmounts + minimumLpRaw) carried on the snapshot. No + // addLiquidityQuote result (maxAmounts + minimumLp) carried on the snapshot. No // confirmation poll yet. function addLiquidity(snapshot) { root.runtime.watch(root.backend.createAccountPublic(), @@ -314,9 +314,9 @@ QtObject { "holdingAId": snapshot.holdingAId, "holdingBId": snapshot.holdingBId, "lpHoldingId": lpHoldingId, - "maxAmountARaw": snapshot.request.maxAmountARaw, - "maxAmountBRaw": snapshot.request.maxAmountBRaw, - "minLpRaw": snapshot.minLpRaw, + "maxAmountA": snapshot.request.maxAmountA, + "maxAmountB": snapshot.request.maxAmountB, + "minLp": snapshot.minLp, // u64-max sentinel = no deadline, same as the swap submits. "deadlineMs": "18446744073709551615" } diff --git a/apps/amm/src/AmmUiBackend.rep b/apps/amm/src/AmmUiBackend.rep index cc9bd42..3e95f2f 100644 --- a/apps/amm/src/AmmUiBackend.rep +++ b/apps/amm/src/AmmUiBackend.rep @@ -76,7 +76,7 @@ class AmmUiBackend // (no pool, unreadable AMM_PROGRAM_BIN, bad inputs, or a failed tx). SLOT(QString swapExactInput(QString defAHex, QString defBHex, QString userInputHoldingHex, QString userOutputHoldingHex, QString amountInDecimal, QString minOutDecimal, QString deadlineDecimal)) // Server-side SwapExactInput preview for (tokenInHex, tokenOutHex): reads the - // pool and returns { status:"ok", error:"", expectedOutRaw, minReceivedRaw, + // pool and returns { status:"ok", error:"", expectedOut, minReceived, // priceImpactBps }, oriented and priced via the shared on-chain formula. // amountInDecimal is a decimal-string base-unit amount; slippageBps is basis // points. On failure { status:"error", error: } — no_pool, @@ -84,7 +84,7 @@ class AmmUiBackend // backend_error. Read-only, no submission. SLOT(QVariantMap swapExactInQuote(QString tokenInHex, QString tokenOutHex, QString amountInDecimal, int slippageBps)) // Server-side SwapExactOutput preview for (tokenInHex, tokenOutHex): reads the - // pool and returns { status:"ok", error:"", requiredInRaw, maxInRaw, + // pool and returns { status:"ok", error:"", requiredIn, maxIn, // priceImpactBps } — the input needed for the desired output and the slippage // ceiling on it — oriented and priced via the shared on-chain formula. // amountOutDecimal is a decimal-string base-unit amount; slippageBps is basis @@ -107,9 +107,9 @@ class AmmUiBackend SLOT(QVariantList tokenList()) // Server-side create-pool preview from the two deposit amounts. `request` - // carries { tokenAId, tokenBId, amountARaw, amountBRaw } (ids hex or base58; + // carries { tokenAId, tokenBId, amountA, amountB } (ids hex or base58; // amounts decimal-string base units). Returns { status:"ok", error:"", - // amountARaw, amountBRaw, expectedLpRaw, lockedLpRaw, initialPriceRaw } — the + // amountA, amountB, expectedLp, lockedLp, initialPrice } — the // LP the creator receives and the opening price, via the shared on-chain math. // On failure { status:"error", error: } — invalid_token_id, // same_token_pair, amount_too_low, amount_required, bad_amount, backend_error. @@ -117,17 +117,17 @@ class AmmUiBackend // PDA nor the pricing). SLOT(QVariantMap createPoolQuote(QVariantMap request)) // Server-side add-liquidity preview from the two max deposit amounts. `request` - // carries { tokenAId, tokenBId, maxAmountARaw, maxAmountBRaw, slippageBps } (ids hex or - // base58). Reads the pool and returns { status:"ok", error:"", amountARaw, amountBRaw - // (the actual ratio-matched deposits, display order), expectedLpRaw, minimumLpRaw (the - // slippage floor on the LP minted — the submit's min_amount_liquidity), priceRaw }. On + // carries { tokenAId, tokenBId, maxAmountA, maxAmountB, slippageBps } (ids hex or + // base58). Reads the pool and returns { status:"ok", error:"", amountA, amountB + // (the actual ratio-matched deposits, display order), expectedLp, minimumLp (the + // slippage floor on the LP minted — the submit's min_amount_liquidity), price }. On // failure { status:"error", error: } — no_pool, pair_mismatch, invalid_token_id, // invalid_slippage, amount_too_low, minimum_lp_zero, bad_amount, backend_error. // Read-only, no submission. SLOT(QVariantMap addLiquidityQuote(QVariantMap request)) // Submits a NewDefinition transaction creating the pool for the request's pair. // `request` carries { tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, - // amountARaw, amountBRaw, feeBps, deadlineMs } (ids hex or base58; amounts/deadline + // amountA, amountB, feeBps, deadlineMs } (ids hex or base58; amounts/deadline // decimal strings). A new pool has no existing LP holding, so the caller supplies // lpHoldingId — a fresh account it created via createAccountPublic(); the backend // just forwards to the module and creates no wallet accounts here. Returns @@ -137,28 +137,28 @@ class AmmUiBackend // wallet_submission_failed, backend_error). SLOT(QVariantMap createPool(QVariantMap request)) // Submits an AddLiquidity transaction into the request's existing pool. `request` - // carries { tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, maxAmountARaw, - // maxAmountBRaw, minLpRaw, deadlineMs } (ids hex or base58; amounts/deadline decimal - // strings). minLpRaw is the slippage floor on the LP minted; lpHoldingId is a fresh + // carries { tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, maxAmountA, + // maxAmountB, minLp, deadlineMs } (ids hex or base58; amounts/deadline decimal + // strings). minLp is the slippage floor on the LP minted; lpHoldingId is a fresh // holding the caller supplies (the flow creates it) to receive the minted LP — the // backend forwards and creates no wallet accounts. Returns // { status:"ok", error:"", transactionId: } on success, else // { status:"error", error: } (wallet_unavailable, config_missing, // invalid_account_id, bad_amount, no_pool, wallet_submission_failed, backend_error). SLOT(QVariantMap addLiquidity(QVariantMap request)) - // Server-side remove-liquidity preview: burning `lpAmountRaw` returns the + // Server-side remove-liquidity preview: burning `lpAmount` returns the // proportional share of each reserve. `request` carries { tokenAId, tokenBId, - // lpAmountRaw, slippageBps } (ids hex or base58). Reads the pool and returns - // { status:"ok", error:"", amountARaw, amountBRaw (the withdrawals, display - // order), minimumAmountARaw, minimumAmountBRaw (the slippage floors the submit - // enforces), priceRaw }. On failure { status:"error", error: } — + // lpAmount, slippageBps } (ids hex or base58). Reads the pool and returns + // { status:"ok", error:"", amountA, amountB (the withdrawals, display + // order), minimumAmountA, minimumAmountB (the slippage floors the submit + // enforces), price }. On failure { status:"error", error: } — // no_pool, pair_mismatch, invalid_token_id, invalid_slippage, // insufficient_pool_liquidity, amount_too_low, minimum_amount_zero, // bad_amount, config_missing, backend_error. Read-only, no submission. SLOT(QVariantMap removeLiquidityQuote(QVariantMap request)) // Submits a RemoveLiquidity transaction against the request's pool. `request` - // carries { tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, lpAmountRaw, - // minAmountARaw, minAmountBRaw, deadlineMs } (ids hex or base58; + // carries { tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, lpAmount, + // minAmountA, minAmountB, deadlineMs } (ids hex or base58; // amounts/deadline decimal strings). Unlike createPool/addLiquidity nothing // fresh is created: lpHoldingId is the existing holding burned from, and the // token a/b holdings receive the withdrawal. minAmount*Raw are the slippage diff --git a/apps/amm/tests/qml/tst_NewPositionForm.qml b/apps/amm/tests/qml/tst_NewPositionForm.qml index dee7a26..4e8bb74 100644 --- a/apps/amm/tests/qml/tst_NewPositionForm.qml +++ b/apps/amm/tests/qml/tst_NewPositionForm.qml @@ -44,14 +44,14 @@ TestCase { { "definitionId": tokenLow, "name": "Low", - "totalSupplyRaw": "1000000", + "totalSupply": "1000000", "balanceRaw": "1000", "selectable": true }, { "definitionId": tokenHigh, "name": "High", - "totalSupplyRaw": "1000000000000", + "totalSupply": "1000000000000", "balanceRaw": "5000000000", "selectable": true } @@ -66,14 +66,14 @@ TestCase { { "definitionId": tokenLow, "name": "Sir Mints-a-Lot", - "totalSupplyRaw": "1000000000000", + "totalSupply": "1000000000000", "balanceRaw": "1000000000", "selectable": true }, { "definitionId": tokenHigh, "name": "Aurora", - "totalSupplyRaw": "1000000000000", + "totalSupply": "1000000000000", "balanceRaw": "1000000000", "selectable": true } @@ -148,10 +148,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "2", - "minimumAmountBRaw": "3", - "actualAmountARaw": "2", - "actualAmountBRaw": "3" + "minimumAmountA": "2", + "minimumAmountB": "3", + "actualAmountA": "2", + "actualAmountB": "3" } var form = createForm() form.priceAmountA = "3" @@ -170,8 +170,8 @@ TestCase { var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, "4") - compare(built.request.amountBRaw, "6") + compare(built.request.amountA, "4") + compare(built.request.amountB, "6") } function test_missingPoolAcceptsLargeDirectAmountsFromEitherSide() { @@ -183,10 +183,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "26", - "minimumAmountBRaw": "39", - "actualAmountARaw": "26", - "actualAmountBRaw": "39" + "minimumAmountA": "26", + "minimumAmountB": "39", + "actualAmountA": "26", + "actualAmountB": "39" }) wait(0) @@ -195,9 +195,9 @@ TestCase { compare(form.amountB, "100") var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, "100") - compare(built.request.amountBRaw, "150") - compare(built.request.priceRaw, "27670116110564327424") + compare(built.request.amountA, "100") + compare(built.request.amountB, "150") + compare(built.request.price, "27670116110564327424") verify(!built.request.hasOwnProperty("depositScaleBps")) form.finishMissingAmount("B", "200") @@ -205,8 +205,8 @@ TestCase { compare(form.amountB, "200") built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, "200") - compare(built.request.amountBRaw, "300") + compare(built.request.amountA, "200") + compare(built.request.amountB, "300") } function test_missingPoolRoundsPairedRawAmounts() { @@ -218,10 +218,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "1", - "minimumAmountBRaw": "1", - "actualAmountARaw": "1", - "actualAmountBRaw": "1" + "minimumAmountA": "1", + "minimumAmountB": "1", + "actualAmountA": "1", + "actualAmountB": "1" }) wait(0) @@ -236,8 +236,8 @@ TestCase { compare(form.amountB, cases[i].paired) var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.amountARaw, cases[i].rawA) - compare(built.request.amountBRaw, cases[i].rawB) + compare(built.request.amountA, cases[i].rawA) + compare(built.request.amountB, cases[i].rawB) } form.finishMissingAmount("A", "1.1234567") @@ -253,10 +253,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "2000000", - "minimumAmountBRaw": "3", - "actualAmountARaw": "2000000", - "actualAmountBRaw": "3" + "minimumAmountA": "2000000", + "minimumAmountB": "3", + "actualAmountA": "2000000", + "actualAmountB": "3" }) wait(0) @@ -290,10 +290,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "2000000", - "minimumAmountBRaw": "3", - "actualAmountARaw": "2000000", - "actualAmountBRaw": "3" + "minimumAmountA": "2000000", + "minimumAmountB": "3", + "actualAmountA": "2000000", + "actualAmountB": "3" }) wait(0) var amountAInput = findChild(form, "tokenAAmountInput") @@ -319,10 +319,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "2000000", - "minimumAmountBRaw": "3", - "actualAmountARaw": "2000000", - "actualAmountBRaw": "3" + "minimumAmountA": "2000000", + "minimumAmountB": "3", + "actualAmountA": "2000000", + "actualAmountB": "3" } var form = createForm() form.flowState = flowState(quote) @@ -336,10 +336,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "missing_pool", - "minimumAmountARaw": "2000000", - "minimumAmountBRaw": "3", - "actualAmountARaw": "2000000", - "actualAmountBRaw": "3" + "minimumAmountA": "2000000", + "minimumAmountB": "3", + "actualAmountA": "2000000", + "actualAmountB": "3" }, "contextLoading": false, "quoteLoading": false, @@ -357,13 +357,13 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "active_pool", - "reserveARaw": "2", - "reserveBRaw": "10", - "maxAmountARaw": "4", - "maxAmountBRaw": "20", + "reserveA": "2", + "reserveB": "10", + "maxAmountA": "4", + "maxAmountB": "20", "errors": [{ "code": "amount_exceeds_balance", - "blockingFields": ["maxAmountARaw"] + "blockingFields": ["maxAmountA"] }] } var form = createForm() @@ -387,10 +387,10 @@ TestCase { "tokenAId": tokenHigh, "tokenBId": tokenLow, "poolStatus": "active_pool", - "reserveARaw": "2", - "reserveBRaw": "10", - "maxAmountARaw": "4", - "maxAmountBRaw": "20" + "reserveA": "2", + "reserveB": "10", + "maxAmountA": "4", + "maxAmountB": "20" } var form = createForm() form.flowState = flowState(quote) @@ -411,8 +411,8 @@ TestCase { var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.maxAmountARaw, "1") - compare(built.request.maxAmountBRaw, "5") + compare(built.request.maxAmountA, "1") + compare(built.request.maxAmountB, "5") form.finishActiveAmount("B", "1.1234567") compare(form.amountB, "1.1234567") @@ -427,10 +427,10 @@ TestCase { "tokenBId": tokenLow, "poolStatus": "active_pool", "poolFeeBps": 30, - "reserveARaw": "2", - "reserveBRaw": "10", - "maxAmountARaw": "4", - "maxAmountBRaw": "20" + "reserveA": "2", + "reserveB": "10", + "maxAmountA": "4", + "maxAmountB": "20" }) wait(0) @@ -450,8 +450,8 @@ TestCase { var built = form.buildQuoteRequest() verify(built.ok) - compare(built.request.maxAmountARaw, "1") - compare(built.request.maxAmountBRaw, "5") + compare(built.request.maxAmountA, "1") + compare(built.request.maxAmountB, "5") } function test_quoteStateChangeDoesNotRequestAnotherQuote() { @@ -493,8 +493,8 @@ TestCase { compare(form.amountB, "") compare(quoteRequestedSpy.count, 1) verify(quoteRequestedSpy.signalArguments[0][1].ok) - compare(quoteRequestedSpy.signalArguments[0][1].request.maxAmountARaw, "5000000000") - compare(quoteRequestedSpy.signalArguments[0][1].request.maxAmountBRaw, "1000") + compare(quoteRequestedSpy.signalArguments[0][1].request.maxAmountA, "5000000000") + compare(quoteRequestedSpy.signalArguments[0][1].request.maxAmountB, "1000") } function test_contextFailureFinishesTokenResolution() { @@ -539,8 +539,8 @@ TestCase { var form = createForm() form.amountA = "12" form.amountB = "34" - form.minimumAmountARaw = "12" - form.minimumAmountBRaw = "34" + form.minimumAmountA = "12" + form.minimumAmountB = "34" form.confirmedPoolStatus = "active_pool" form.newPositionContext = { @@ -549,14 +549,14 @@ TestCase { { "definitionId": tokenHigh, "name": "High", - "totalSupplyRaw": "1000000000000", + "totalSupply": "1000000000000", "balanceRaw": "5000000000", "selectable": true }, { "definitionId": tokenThird, "name": "Third", - "totalSupplyRaw": "1000000", + "totalSupply": "1000000", "balanceRaw": "100", "selectable": true } @@ -568,8 +568,8 @@ TestCase { compare(form.selectedTokenBId, tokenHigh) compare(form.amountA, "") compare(form.amountB, "") - compare(form.minimumAmountARaw, "") - compare(form.minimumAmountBRaw, "") + compare(form.minimumAmountA, "") + compare(form.minimumAmountB, "") compare(form.confirmedPoolStatus, "") } diff --git a/apps/amm/tests/swap.mjs b/apps/amm/tests/swap.mjs index ab9180c..49981cf 100644 --- a/apps/amm/tests/swap.mjs +++ b/apps/amm/tests/swap.mjs @@ -104,8 +104,8 @@ async function selectAccount(app, selectorObjectName) { // max without needing BigInt. await app.inspector.send("evaluate", { expression: - "(function(){var r=matchingAccounts,b=r[0],bb=String(valueFor(b,'balanceRaw')||'0');" - + "for(var i=1;ibb.length||(v.length===bb.length&&v>bb)){b=r[i];bb=v;}}" + "setSelection(accountIdFor(b),false);})()", objectId: id, diff --git a/apps/shared/wallet/qml/ProgramAccountSelector.qml b/apps/shared/wallet/qml/ProgramAccountSelector.qml index 7c4e922..124fb74 100644 --- a/apps/shared/wallet/qml/ProgramAccountSelector.qml +++ b/apps/shared/wallet/qml/ProgramAccountSelector.qml @@ -50,7 +50,7 @@ Item { || (root.selectionMode === ProgramAccountSelector.Output && root.createNewSelected)) readonly property var selectedAccount: root.accountById(root.selectedAccountId) - readonly property string selectedBalanceRaw: root.selectedAccount + readonly property string selectedBalance: root.selectedAccount ? String(root.valueFor( root.selectedAccount, "balanceRaw") || "0") diff --git a/apps/shared/wallet/tests/qml/tst_ProgramAccountSelector.qml b/apps/shared/wallet/tests/qml/tst_ProgramAccountSelector.qml index 2d8a21b..9da1595 100644 --- a/apps/shared/wallet/tests/qml/tst_ProgramAccountSelector.qml +++ b/apps/shared/wallet/tests/qml/tst_ProgramAccountSelector.qml @@ -72,7 +72,7 @@ Item { compare(selector.showCombo, false) compare(selector.hasFunds, true) compare(selector.ready, true) - compare(selector.selectedBalanceRaw, "120") + compare(selector.selectedBalance, "120") } function test_inputMultipleHoldingsRequiresSelection() { @@ -87,7 +87,7 @@ Item { selector.setSelection("holding-b", false) compare(selector.selectedAccountId, "holding-b") - compare(selector.selectedBalanceRaw, "80") + compare(selector.selectedBalance, "80") compare(selector.ready, true) } diff --git a/modules/amm/README.md b/modules/amm/README.md index ff67c63..bfacb69 100644 --- a/modules/amm/README.md +++ b/modules/amm/README.md @@ -16,6 +16,7 @@ transport-independent JSON FFI), and this module sequences those pure ops with chain I/O delegated to the `logos_execution_zone` wallet module. Its public methods (the module API is generated from the header) are: +**Reads** - `resolvePoolAccount(defAHex, defBHex)` — derives the pool PDA and reads/decodes the pool account (reserves in canonical `a`/`b` order, fee tier). On success `{ status: "ok", error: "", poolId, defAHex, defBHex, vaultAId, vaultBId, @@ -23,20 +24,30 @@ methods (the module API is generated from the header) are: uninitialized pool or one with no liquidity is `{ status: "error", error: "no_pool", poolId }` (other codes: `no_program_bin`, `amm_not_initialized`, `bad_config`). -- `swapExactInput(defAHex, defBHex, userInputHoldingHex, userOutputHoldingHex, amountIn, minOut, deadline)` - — submits an on-chain `SwapExactInput` transaction (defA = token in, - defB = token out); returns the tx hash (or empty on failure). See - **Amount / id conventions** below. +- `configAccount()` — decodes the singleton AMM config (authority + the + token/oracle program ids it was initialized with). +- `feeTiers()` — the AMM's supported fee tiers as raw basis points `[1, 5, 30, 100]`. +- `tokenHoldings(walletOpen)` — the connected wallet's fungible token holdings. - `resolveTokens(request, walletOpen)` — resolves an app-provided set of token - ids into selector rows (definition + wallet holding per id). The lean, - stateless successor to the removed `newPositionContext` path: the app owns the + ids into selector rows (definition + wallet holding per id). The app owns the id set, so there is no network envelope or process-cached wallet state here. -- `feeTiers()` — the AMM's supported fee tiers as raw basis points. -- `createPoolQuote(request)` / `createPool(request)` and - `addLiquidityQuote(request)` / `addLiquidity(request)` — the add-liquidity - preview (read-only) and submit paths. The submit forwards the app-supplied - fresh LP holding id; the app backend, which owns the wallet keyset, creates - that account. + +**Quotes** (read-only pricing; no submit) +- `swapExactInQuote` / `swapExactOutQuote` — price a swap. +- `createPoolQuote` / `addLiquidityQuote` / `removeLiquidityQuote` — price a + liquidity op. + +**Submits** (on-chain transactions; return `{ status, error, transactionId }`, +except the two swaps which return a bare tx hash) +- `swapExactInput` / `swapExactOutput` (defA = token in, defB = token out). +- `createPool` / `addLiquidity` / `removeLiquidity` — the create/add paths take a + fresh `lpHoldingId` the app supplies (the module never creates wallet accounts). +- `syncReserves` — permissionless keeper op refreshing stored reserves + TWAP tick. +- `createPriceObservations` / `createOraclePriceAccount` — seed a pool's TWAP feed. +- `transferOwnership` — admin-only `UpdateConfig` handing over the authority. + +See **Amount / id conventions** below, and the +[full `logoscore` runbook](../../docs/module/amm.md) for a worked call per method. ## How it fits together @@ -197,11 +208,11 @@ logoscore load-module amm_module Every other op reads on-chain through the wallet module's `get_account_public`, which fails on a null wallet handle (surfacing as an absent pool), so open the -wallet first — `resolvePool` then works: +wallet first — `resolvePoolAccount` then works: ```bash logoscore call logos_execution_zone open ~/.lee/wallet/wallet_config.json ~/.lee/wallet/storage.json -logoscore call amm_module resolvePool +logoscore call amm_module resolvePoolAccount ``` `swapExactInput` reuses that open wallet but additionally needs it **synced** @@ -230,8 +241,8 @@ wallet's `storage.json` may keep a stale `last_synced_block` ahead of the new chain — transactions then reference dead state and the sequencer rejects them (reserves don't move). Reset the cursor (`last_synced_block: 0`, keep `key_chain`/`labels`) and re-`open` + `sync_to_block ` to re-sync from -genesis. `resolvePool` is a **live** sequencer read, so the stale cursor doesn't -affect it — but it still needs the wallet **open**: the read goes through the +genesis. `resolvePoolAccount` is a **live** sequencer read, so the stale cursor +doesn't affect it — but it still needs the wallet **open**: the read goes through the wallet's sequencer connection (not its private keys), which only exists once the wallet is opened. @@ -252,11 +263,9 @@ It requires the wallet module built with the byte-string `instruction` param — the fork pinned as the `logos_execution_zone` input. See `docs/amm-swap-qtro-serialization-bug.md`. -## Known follow-ups +## Full API runbook -- **`swapExactOutput` is not exposed yet.** The on-chain program supports it - (`amm_core::Instruction::SwapExactOutput`, identical account layout to - `SwapExactInput`), but the client path was only ever built for exact-input: - `amm_ffi` has no exact-output op and neither the UI nor this module has a - `swapExactOutput` method. Adding it is a near-copy of the exact-input path — an - `amm_swap_exact_output_*` op in the crate plus a `swapExactOutput` method here. +The method list above is a curated subset. For a complete, worked `logoscore` +walkthrough of **every** `amm_module` API — reads, swaps, add/remove liquidity, +the keeper `syncReserves`, oracle setup, and admin — see +[`docs/module/amm.md`](../../docs/module/amm.md). diff --git a/modules/amm/ffi/src/api/liquidity.rs b/modules/amm/ffi/src/api/liquidity.rs index 0acb040..48638b1 100644 --- a/modules/amm/ffi/src/api/liquidity.rs +++ b/modules/amm/ffi/src/api/liquidity.rs @@ -91,7 +91,7 @@ fn plan_response( /// /// The opening price *is* the deposit ratio. With **amounts** supplied, the op uses them and /// derives the price (`spot_price_q64_64`); **price-only** (no amounts), it takes -/// `price_raw` (Q64.64, canonical) and uses `minimum_opening_pair` — the smallest +/// `price` (Q64.64, canonical) and uses `minimum_opening_pair` — the smallest /// deposit at that price that clears the permanently-locked `MINIMUM_LIQUIDITY`. Either way it /// also returns that `minimum*` pair (the form validates entered amounts against it) and /// `expected_lp = floor(sqrt(a·b)) - MINIMUM_LIQUIDITY` (LP is orientation-independent — the @@ -106,17 +106,17 @@ pub(super) fn create_pool_quote(request: CreatePoolQuoteRequest) -> Result spot_price_q64_64(amount_a, amount_b), - None => positive_amount(request.price_raw.as_deref())?, + None => positive_amount(request.price.as_deref())?, }; let (minimum_a, minimum_b) = minimum_opening_pair(price)?; let (actual_a, actual_b) = amounts.unwrap_or((minimum_a, minimum_b)); @@ -130,13 +130,13 @@ pub(super) fn create_pool_quote(request: CreatePoolQuoteRequest) -> Result Result Result Result= FEE_BPS_DENOMINATOR { return Err(String::from("invalid_slippage")); } @@ -302,11 +302,11 @@ pub(super) fn add_liquidity_quote(request: AddLiquidityQuoteRequest) -> Result Result Result= FEE_BPS_DENOMINATOR { return Err(String::from("invalid_slippage")); } @@ -481,11 +481,11 @@ pub(super) fn remove_liquidity_quote( let price = spot_price_q64_64(reserve_display_a, reserve_display_b); Ok(json!({ - "amountARaw": display_a.to_string(), - "amountBRaw": display_b.to_string(), - "minimumAmountARaw": minimum_display_a.to_string(), - "minimumAmountBRaw": minimum_display_b.to_string(), - "priceRaw": price.to_string(), + "amountA": display_a.to_string(), + "amountB": display_b.to_string(), + "minimumAmountA": minimum_display_a.to_string(), + "minimumAmountB": minimum_display_b.to_string(), + "price": price.to_string(), })) } @@ -508,9 +508,9 @@ pub(super) fn remove_liquidity_plan(request: RemoveLiquidityPlanRequest) -> Resu let holding_b = account_id_from_hex(&request.user_holding_b_id, "user holding B id")?; let user_lp = account_id_from_hex(&request.user_holding_lp_id, "user LP holding id")?; - let lp_amount = positive_amount(Some(&request.lp_amount_raw))?; - let min_a = positive_amount(Some(&request.min_amount_a_raw))?; - let min_b = positive_amount(Some(&request.min_amount_b_raw))?; + let lp_amount = positive_amount(Some(&request.lp_amount))?; + let min_a = positive_amount(Some(&request.min_amount_a))?; + let min_b = positive_amount(Some(&request.min_amount_b))?; let deadline = parse_u64(&request.deadline_ms, "deadlineMs")?; // config / pool / current_tick / clock are order-independent PDAs, so derive_pair takes the @@ -639,9 +639,9 @@ mod tests { CreatePoolQuoteRequest { token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - price_raw: None, - amount_a_raw: Some(String::from("1000000")), - amount_b_raw: Some(String::from("4000000")), + price: None, + amount_a: Some(String::from("1000000")), + amount_b: Some(String::from("4000000")), } } @@ -676,21 +676,21 @@ mod tests { let value = create_pool_quote(quote_request(token_a, token_b)).unwrap(); // Amounts supplied ⇒ actual == the amounts; the price is derived from them. - assert_eq!(value["actualAmountARaw"], "1000000"); - assert_eq!(value["actualAmountBRaw"], "4000000"); - assert_eq!(value["lockedLpRaw"], MINIMUM_LIQUIDITY.to_string()); + assert_eq!(value["actualAmountA"], "1000000"); + assert_eq!(value["actualAmountB"], "4000000"); + assert_eq!(value["lockedLp"], MINIMUM_LIQUIDITY.to_string()); // initial_lp = isqrt(1_000_000 * 4_000_000) = 2_000_000; creator LP = minus lock. let initial_lp = isqrt_product(1_000_000, 4_000_000); assert_eq!( - value["expectedLpRaw"], + value["expectedLp"], (initial_lp - MINIMUM_LIQUIDITY).to_string() ); let price = spot_price_q64_64(1_000_000, 4_000_000); - assert_eq!(value["priceRaw"], price.to_string()); + assert_eq!(value["price"], price.to_string()); // The minimum opening deposit for that price is echoed for the form to validate against. let (min_a, min_b) = minimum_opening_pair(price).unwrap(); - assert_eq!(value["minimumAmountARaw"], min_a.to_string()); - assert_eq!(value["minimumAmountBRaw"], min_b.to_string()); + assert_eq!(value["minimumAmountA"], min_a.to_string()); + assert_eq!(value["minimumAmountB"], min_b.to_string()); // Lean preview — no commitment / status / submittability fields. assert!(value.get("quoteHash").is_none()); assert!(value.get("canSubmit").is_none()); @@ -707,18 +707,18 @@ mod tests { let value = create_pool_quote(CreatePoolQuoteRequest { token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - price_raw: Some(price.to_string()), - amount_a_raw: None, - amount_b_raw: None, + price: Some(price.to_string()), + amount_a: None, + amount_b: None, }) .unwrap(); // Price-only ⇒ the actual deposit is the minimum opening pair for that price. - assert_eq!(value["actualAmountARaw"], min_a.to_string()); - assert_eq!(value["actualAmountBRaw"], min_b.to_string()); - assert_eq!(value["minimumAmountARaw"], min_a.to_string()); - assert_eq!(value["minimumAmountBRaw"], min_b.to_string()); - assert_eq!(value["priceRaw"], price.to_string()); + assert_eq!(value["actualAmountA"], min_a.to_string()); + assert_eq!(value["actualAmountB"], min_b.to_string()); + assert_eq!(value["minimumAmountA"], min_a.to_string()); + assert_eq!(value["minimumAmountB"], min_b.to_string()); + assert_eq!(value["price"], price.to_string()); } #[test] @@ -728,10 +728,10 @@ mod tests { let ab = create_pool_quote(quote_request(token_a, token_b)).unwrap(); // Swap display order and the paired amounts: the LP figure is symmetric. let mut ba = quote_request(token_b, token_a); - ba.amount_a_raw = Some(String::from("4000000")); - ba.amount_b_raw = Some(String::from("1000000")); + ba.amount_a = Some(String::from("4000000")); + ba.amount_b = Some(String::from("1000000")); let ba = create_pool_quote(ba).unwrap(); - assert_eq!(ab["expectedLpRaw"], ba["expectedLpRaw"]); + assert_eq!(ab["expectedLp"], ba["expectedLp"]); } #[test] @@ -745,8 +745,8 @@ mod tests { // isqrt(1 * 1) = 1 ≤ MINIMUM_LIQUIDITY ⇒ the pool can't open. let token_b = AccountId::new([0xBB; 32]); let mut tiny = quote_request(token, token_b); - tiny.amount_a_raw = Some(String::from("1")); - tiny.amount_b_raw = Some(String::from("1")); + tiny.amount_a = Some(String::from("1")); + tiny.amount_b = Some(String::from("1")); assert_eq!(create_pool_quote(tiny), Err(String::from("amount_too_low"))); } @@ -771,8 +771,8 @@ mod tests { config: valid_config(amm), token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - amount_a_raw: Some(String::from("1000000")), // deposit for display token_a - amount_b_raw: Some(String::from("4000000")), // deposit for display token_b + amount_a: Some(String::from("1000000")), // deposit for display token_a + amount_b: Some(String::from("4000000")), // deposit for display token_b fee_bps: 30, deadline_ms: String::from("1000"), user_holding_a_id: account_id_hex(holding_a), @@ -839,8 +839,8 @@ mod tests { config: read_failed(), token_a_id: account_id_hex(token), token_b_id: account_id_hex(token), - amount_a_raw: Some(String::from("1")), - amount_b_raw: Some(String::from("1")), + amount_a: Some(String::from("1")), + amount_b: Some(String::from("1")), fee_bps: 30, deadline_ms: String::from("1"), user_holding_a_id: account_id_hex(token), @@ -873,39 +873,39 @@ mod tests { let ab = add_liquidity_quote(AddLiquidityQuoteRequest { token_a_id: account_id_hex(def_a), token_b_id: account_id_hex(def_b), - max_amount_a_raw: String::from("10000"), - max_amount_b_raw: String::from("100000"), + max_amount_a: String::from("10000"), + max_amount_b: String::from("100000"), slippage_bps: 50, pool_data: pool_hex(&pool), }) .unwrap(); - assert_eq!(ab["amountARaw"], "10000"); - assert_eq!(ab["amountBRaw"], "20000"); - assert_eq!(ab["expectedLpRaw"], "10000"); - // minimumLpRaw = floor(10000 * (10000 - 50) / 10000) = 9950 (slippage floor on LP). - assert_eq!(ab["minimumLpRaw"], "9950"); + assert_eq!(ab["amountA"], "10000"); + assert_eq!(ab["amountB"], "20000"); + assert_eq!(ab["expectedLp"], "10000"); + // minimumLp = floor(10000 * (10000 - 50) / 10000) = 9950 (slippage floor on LP). + assert_eq!(ab["minimumLp"], "9950"); assert_eq!( - ab["priceRaw"], + ab["price"], spot_price_q64_64(1_000_000, 2_000_000).to_string() ); - assert!(ab.get("lockedLpRaw").is_none()); - assert!(ab.get("initialPriceRaw").is_none()); + assert!(ab.get("lockedLp").is_none()); + assert!(ab.get("initialPrice").is_none()); // Reverse display order: the actual amounts and the price flip to display order. let ba = add_liquidity_quote(AddLiquidityQuoteRequest { token_a_id: account_id_hex(def_b), token_b_id: account_id_hex(def_a), - max_amount_a_raw: String::from("100000"), - max_amount_b_raw: String::from("10000"), + max_amount_a: String::from("100000"), + max_amount_b: String::from("10000"), slippage_bps: 50, pool_data: pool_hex(&pool), }) .unwrap(); - assert_eq!(ba["amountARaw"], "20000"); // display token def_b side - assert_eq!(ba["amountBRaw"], "10000"); // display token def_a side - assert_eq!(ba["expectedLpRaw"], "10000"); + assert_eq!(ba["amountA"], "20000"); // display token def_b side + assert_eq!(ba["amountB"], "10000"); // display token def_a side + assert_eq!(ba["expectedLp"], "10000"); assert_eq!( - ba["priceRaw"], + ba["price"], spot_price_q64_64(2_000_000, 1_000_000).to_string() ); } @@ -928,8 +928,8 @@ mod tests { AddLiquidityQuoteRequest { token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - max_amount_a_raw: max_a.into(), - max_amount_b_raw: max_b.into(), + max_amount_a: max_a.into(), + max_amount_b: max_b.into(), slippage_bps: 50, pool_data: data, } @@ -978,8 +978,8 @@ mod tests { add_liquidity_quote(AddLiquidityQuoteRequest { token_a_id: account_id_hex(def_a), token_b_id: account_id_hex(def_b), - max_amount_a_raw: String::from("10000"), - max_amount_b_raw: String::from("10000"), + max_amount_a: String::from("10000"), + max_amount_b: String::from("10000"), slippage_bps: 10_000, pool_data: pool_hex(&pool), }), @@ -1027,9 +1027,9 @@ mod tests { config: valid_config(amm), token_a_id: ta, token_b_id: tb, - max_amount_a_raw: ma.to_string(), - max_amount_b_raw: mb.to_string(), - min_lp_raw: String::from("500"), + max_amount_a: ma.to_string(), + max_amount_b: mb.to_string(), + min_lp: String::from("500"), deadline_ms: String::from("1000"), user_holding_a_id: ha, user_holding_b_id: hb, @@ -1109,9 +1109,9 @@ mod tests { config: read_failed(), token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - max_amount_a_raw: String::from("1"), - max_amount_b_raw: String::from("1"), - min_lp_raw: String::from("1"), + max_amount_a: String::from("1"), + max_amount_b: String::from("1"), + min_lp: String::from("1"), deadline_ms: String::from("1"), user_holding_a_id: account_id_hex(token_a), user_holding_b_id: account_id_hex(token_b), @@ -1154,18 +1154,18 @@ mod tests { let ab = remove_liquidity_quote(RemoveLiquidityQuoteRequest { token_a_id: account_id_hex(def_a), token_b_id: account_id_hex(def_b), - lp_amount_raw: String::from("100000"), + lp_amount: String::from("100000"), slippage_bps: 50, pool_data: pool_hex(&pool), }) .unwrap(); - assert_eq!(ab["amountARaw"], "100000"); // floor(1_000_000 * 100_000 / 1_000_000) - assert_eq!(ab["amountBRaw"], "200000"); // floor(2_000_000 * 100_000 / 1_000_000) - // minimum = floor(withdraw * (10000 - 50) / 10000) — the slippage floor per side. - assert_eq!(ab["minimumAmountARaw"], "99500"); - assert_eq!(ab["minimumAmountBRaw"], "199000"); + assert_eq!(ab["amountA"], "100000"); // floor(1_000_000 * 100_000 / 1_000_000) + assert_eq!(ab["amountB"], "200000"); // floor(2_000_000 * 100_000 / 1_000_000) + // minimum = floor(withdraw * (10000 - 50) / 10000) — the slippage floor per side. + assert_eq!(ab["minimumAmountA"], "99500"); + assert_eq!(ab["minimumAmountB"], "199000"); assert_eq!( - ab["priceRaw"], + ab["price"], spot_price_q64_64(1_000_000, 2_000_000).to_string() ); @@ -1173,17 +1173,17 @@ mod tests { let ba = remove_liquidity_quote(RemoveLiquidityQuoteRequest { token_a_id: account_id_hex(def_b), token_b_id: account_id_hex(def_a), - lp_amount_raw: String::from("100000"), + lp_amount: String::from("100000"), slippage_bps: 50, pool_data: pool_hex(&pool), }) .unwrap(); - assert_eq!(ba["amountARaw"], "200000"); // display token def_b side - assert_eq!(ba["amountBRaw"], "100000"); // display token def_a side - assert_eq!(ba["minimumAmountARaw"], "199000"); - assert_eq!(ba["minimumAmountBRaw"], "99500"); + assert_eq!(ba["amountA"], "200000"); // display token def_b side + assert_eq!(ba["amountB"], "100000"); // display token def_a side + assert_eq!(ba["minimumAmountA"], "199000"); + assert_eq!(ba["minimumAmountB"], "99500"); assert_eq!( - ba["priceRaw"], + ba["price"], spot_price_q64_64(2_000_000, 1_000_000).to_string() ); } @@ -1205,7 +1205,7 @@ mod tests { RemoveLiquidityQuoteRequest { token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - lp_amount_raw: lp.into(), + lp_amount: lp.into(), slippage_bps: 50, pool_data: data, } @@ -1247,7 +1247,7 @@ mod tests { remove_liquidity_quote(RemoveLiquidityQuoteRequest { token_a_id: account_id_hex(def_a), token_b_id: account_id_hex(def_b), - lp_amount_raw: String::from("100000"), + lp_amount: String::from("100000"), slippage_bps: 10_000, pool_data: pool_hex(&pool), }), @@ -1318,9 +1318,9 @@ mod tests { config: valid_config(amm), token_a_id: ta, token_b_id: tb, - lp_amount_raw: String::from("100000"), - min_amount_a_raw: min_a.to_string(), - min_amount_b_raw: min_b.to_string(), + lp_amount: String::from("100000"), + min_amount_a: min_a.to_string(), + min_amount_b: min_b.to_string(), deadline_ms: String::from("1000"), user_holding_a_id: ha, user_holding_b_id: hb, @@ -1408,9 +1408,9 @@ mod tests { config: read_failed(), token_a_id: account_id_hex(token_a), token_b_id: account_id_hex(token_b), - lp_amount_raw: String::from("1"), - min_amount_a_raw: String::from("1"), - min_amount_b_raw: String::from("1"), + lp_amount: String::from("1"), + min_amount_a: String::from("1"), + min_amount_b: String::from("1"), deadline_ms: String::from("1"), user_holding_a_id: account_id_hex(token_a), user_holding_b_id: account_id_hex(token_b), diff --git a/modules/amm/ffi/src/api/request.rs b/modules/amm/ffi/src/api/request.rs index 59119d6..5f4629c 100644 --- a/modules/amm/ffi/src/api/request.rs +++ b/modules/amm/ffi/src/api/request.rs @@ -99,7 +99,7 @@ pub struct ResolvePoolRequest { pub struct SwapExactInQuoteRequest { pub token_in_id: String, pub token_out_id: String, - pub amount_in_raw: String, + pub amount_in: String, pub slippage_bps: u32, /// Pool account data (hex Borsh `PoolDefinition`). Empty / undecodable ⇒ the /// op returns the `no_pool` error. @@ -111,7 +111,7 @@ pub struct SwapExactInQuoteRequest { pub struct SwapExactOutQuoteRequest { pub token_in_id: String, pub token_out_id: String, - pub amount_out_raw: String, + pub amount_out: String, pub slippage_bps: u32, /// Pool account data (hex Borsh `PoolDefinition`). Empty / undecodable ⇒ the /// op returns the `no_pool` error. @@ -171,11 +171,11 @@ pub struct CreatePoolQuoteRequest { /// order). Required only in the price-only mode (no `amount_*_raw`), where it drives the /// minimum opening deposit; when amounts are supplied the op derives the price from them. #[serde(default)] - pub price_raw: Option, + pub price: Option, #[serde(default)] - pub amount_a_raw: Option, + pub amount_a: Option, #[serde(default)] - pub amount_b_raw: Option, + pub amount_b: Option, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] @@ -190,9 +190,9 @@ pub struct CreatePoolPlanRequest { pub token_a_id: String, pub token_b_id: String, #[serde(default)] - pub amount_a_raw: Option, + pub amount_a: Option, #[serde(default)] - pub amount_b_raw: Option, + pub amount_b: Option, pub fee_bps: u32, pub deadline_ms: String, pub user_holding_a_id: String, @@ -208,17 +208,17 @@ pub struct CreatePoolPlanRequest { pub struct AddLiquidityQuoteRequest { pub token_a_id: String, pub token_b_id: String, - pub max_amount_a_raw: String, - pub max_amount_b_raw: String, - /// Slippage tolerance in basis points — the quote returns `minimumLpRaw`, the LP floor - /// the submit accepts (like the swap quotes take `slippageBps` → `minReceivedRaw`). + pub max_amount_a: String, + pub max_amount_b: String, + /// Slippage tolerance in basis points — the quote returns `minimumLp`, the LP floor + /// the submit accepts (like the swap quotes take `slippageBps` → `minReceived`). #[serde(default)] pub slippage_bps: u32, pub pool_data: String, } /// Builds the `AddLiquidity` submission — the add counterpart of `CreatePoolPlanRequest`. -/// `min_lp_raw` is the caller's slippage floor on the LP minted (the guest's +/// `min_lp` is the caller's slippage floor on the LP minted (the guest's /// `min_amount_liquidity`, applied at submit like the swap plans' `min_out`); `pool_data` /// supplies the stored vault / LP-definition ids the guest asserts against. #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] @@ -231,9 +231,9 @@ pub struct AddLiquidityPlanRequest { pub config: AccountRead, pub token_a_id: String, pub token_b_id: String, - pub max_amount_a_raw: String, - pub max_amount_b_raw: String, - pub min_lp_raw: String, + pub max_amount_a: String, + pub max_amount_b: String, + pub min_lp: String, pub deadline_ms: String, pub user_holding_a_id: String, pub user_holding_b_id: String, @@ -241,7 +241,7 @@ pub struct AddLiquidityPlanRequest { pub pool_data: String, } -/// Prices burning `lp_amount_raw` of an existing pool's LP. `slippage_bps` sets the +/// Prices burning `lp_amount` of an existing pool's LP. `slippage_bps` sets the /// `minimumAmount*Raw` floors the submit enforces (the guest requires both nonzero and /// `withdraw >= min`). `pool_data` is the hex Borsh `PoolDefinition` (empty ⇒ no pool). #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] @@ -249,7 +249,7 @@ pub struct AddLiquidityPlanRequest { pub struct RemoveLiquidityQuoteRequest { pub token_a_id: String, pub token_b_id: String, - pub lp_amount_raw: String, + pub lp_amount: String, #[serde(default)] pub slippage_bps: u32, pub pool_data: String, @@ -271,9 +271,9 @@ pub struct RemoveLiquidityPlanRequest { pub config: AccountRead, pub token_a_id: String, pub token_b_id: String, - pub lp_amount_raw: String, - pub min_amount_a_raw: String, - pub min_amount_b_raw: String, + pub lp_amount: String, + pub min_amount_a: String, + pub min_amount_b: String, pub deadline_ms: String, pub user_holding_a_id: String, pub user_holding_b_id: String, diff --git a/modules/amm/ffi/src/api/swap.rs b/modules/amm/ffi/src/api/swap.rs index db641dd..df883cb 100644 --- a/modules/amm/ffi/src/api/swap.rs +++ b/modules/amm/ffi/src/api/swap.rs @@ -146,7 +146,7 @@ pub(super) fn swap_exact_in_quote(request: SwapExactInQuoteRequest) -> Result= FEE_BPS_DENOMINATOR { return Err(String::from("invalid_slippage")); } @@ -192,8 +192,8 @@ pub(super) fn swap_exact_in_quote(request: SwapExactInQuoteRequest) -> Result Result< if token_in == token_out { return Err(String::from("same_token_pair")); } - let amount_out = parse_u128(&request.amount_out_raw, "amountOutRaw")?; + let amount_out = parse_u128(&request.amount_out, "amountOut")?; if amount_out == 0 { // The guest's exact_output_swap_logic rejects a zero output before any // transfer, so a zero-output preview would claim an unexecutable quote @@ -278,8 +278,8 @@ pub(super) fn swap_exact_out_quote(request: SwapExactOutQuoteRequest) -> Result< }; Ok(json!({ - "requiredInRaw": required_in.to_string(), - "maxInRaw": max_in.to_string(), + "requiredIn": required_in.to_string(), + "maxIn": max_in.to_string(), "priceImpactBps": price_impact_bps, })) } @@ -630,21 +630,21 @@ mod tests { let ab = swap_exact_in_quote(SwapExactInQuoteRequest { token_in_id: account_id_hex(def_a), token_out_id: account_id_hex(def_b), - amount_in_raw: "10000".into(), + amount_in: "10000".into(), slippage_bps: 50, pool_data: pool_data_hex(&pool), }) .unwrap(); // expectedOut comes from the shared on-chain formula (single source of truth). let (_, expected_out) = swap_exact_in_amounts(10_000, 1_000_000, 2_000_000, 30); - assert_eq!(ab["expectedOutRaw"], expected_out.to_string()); + assert_eq!(ab["expectedOut"], expected_out.to_string()); assert_eq!( - ab["minReceivedRaw"], + ab["minReceived"], (expected_out * (FEE_BPS_DENOMINATOR - 50) / FEE_BPS_DENOMINATOR).to_string() ); assert!(ab["priceImpactBps"].is_number()); // Only the priced results are echoed — no pool metadata. - assert!(ab.get("reserveInRaw").is_none()); + assert!(ab.get("reserveIn").is_none()); assert!(ab.get("feeBps").is_none()); assert!(ab.get("poolStatus").is_none()); @@ -652,13 +652,13 @@ mod tests { let ba = swap_exact_in_quote(SwapExactInQuoteRequest { token_in_id: account_id_hex(def_b), token_out_id: account_id_hex(def_a), - amount_in_raw: "10000".into(), + amount_in: "10000".into(), slippage_bps: 50, pool_data: pool_data_hex(&pool), }) .unwrap(); let (_, expected_out_ba) = swap_exact_in_amounts(10_000, 2_000_000, 1_000_000, 30); - assert_eq!(ba["expectedOutRaw"], expected_out_ba.to_string()); + assert_eq!(ba["expectedOut"], expected_out_ba.to_string()); } #[test] @@ -668,7 +668,7 @@ mod tests { let req = |pool_data: String| SwapExactInQuoteRequest { token_in_id: account_id_hex(def_a), token_out_id: account_id_hex(def_b), - amount_in_raw: "10000".into(), + amount_in: "10000".into(), slippage_bps: 50, pool_data, }; @@ -706,7 +706,7 @@ mod tests { let req = |amount: &str| SwapExactInQuoteRequest { token_in_id: account_id_hex(def_a), token_out_id: account_id_hex(def_b), - amount_in_raw: amount.into(), + amount_in: amount.into(), slippage_bps: 50, pool_data: pool_data_hex(&pool), }; @@ -722,7 +722,7 @@ mod tests { Err(String::from("amount_too_small")) ); // A normal amount above the fee-rounding floor still quotes. - assert!(swap_exact_in_quote(req("10000")).unwrap()["expectedOutRaw"].is_string()); + assert!(swap_exact_in_quote(req("10000")).unwrap()["expectedOut"].is_string()); } #[test] @@ -744,12 +744,12 @@ mod tests { let quote = swap_exact_in_quote(SwapExactInQuoteRequest { token_in_id: account_id_hex(def_a), token_out_id: account_id_hex(def_b), - amount_in_raw: "2".into(), + amount_in: "2".into(), slippage_bps: 50, pool_data: pool_data_hex(&pool), }) .unwrap(); - assert!(quote["expectedOutRaw"].is_string()); + assert!(quote["expectedOut"].is_string()); assert!( quote["priceImpactBps"].as_u64().unwrap() <= u64::try_from(FEE_BPS_DENOMINATOR).unwrap() @@ -769,10 +769,10 @@ mod tests { fees: 30, ..Default::default() }; - let req = |amount_out_raw: &str| SwapExactOutQuoteRequest { + let req = |amount_out: &str| SwapExactOutQuoteRequest { token_in_id: account_id_hex(def_a), token_out_id: account_id_hex(def_b), - amount_out_raw: amount_out_raw.into(), + amount_out: amount_out.into(), slippage_bps: 50, pool_data: pool_data_hex(&pool), }; @@ -780,16 +780,16 @@ mod tests { // Sell A to receive exactly 10_000 B. let q = swap_exact_out_quote(req("10000")).unwrap(); let (_, required_in) = swap_exact_out_amounts(10_000, 1_000_000, 2_000_000, 30).unwrap(); - assert_eq!(q["requiredInRaw"], required_in.to_string()); + assert_eq!(q["requiredIn"], required_in.to_string()); // maxIn = required_in * (10000 + 50) / 10000, rounded up. assert_eq!( - q["maxInRaw"], + q["maxIn"], (required_in * 10_050).div_ceil(10_000).to_string() ); assert!(q["priceImpactBps"].is_number()); // Only the input-side results are echoed — no output/reserves. - assert!(q.get("expectedOutRaw").is_none()); - assert!(q.get("reserveInRaw").is_none()); + assert!(q.get("expectedOut").is_none()); + assert!(q.get("reserveIn").is_none()); // Zero requested output is rejected — the guest rejects exact_amount_out // == 0, so a zero-output preview would claim an unexecutable quote. @@ -819,7 +819,7 @@ mod tests { swap_exact_out_quote(SwapExactOutQuoteRequest { token_in_id: account_id_hex(def_a), token_out_id: account_id_hex(def_b), - amount_out_raw: "10000".into(), + amount_out: "10000".into(), slippage_bps: 50, pool_data: pool_data_hex(&empty_side), }), diff --git a/modules/amm/ffi/tests/public_api.rs b/modules/amm/ffi/tests/public_api.rs index 9cbe074..87c5596 100644 --- a/modules/amm/ffi/tests/public_api.rs +++ b/modules/amm/ffi/tests/public_api.rs @@ -22,12 +22,12 @@ fn create_pool_surface_is_reexported_from_crate_root() { let quote = create_pool_quote(CreatePoolQuoteRequest { token_a_id: "11".repeat(32), token_b_id: "22".repeat(32), - price_raw: None, // amounts supplied ⇒ the op derives the price - amount_a_raw: Some("1000000".into()), - amount_b_raw: Some("4000000".into()), + price: None, // amounts supplied ⇒ the op derives the price + amount_a: Some("1000000".into()), + amount_b: Some("4000000".into()), }) .expect("a valid pure create-pool quote should succeed"); - assert_eq!(quote["actualAmountARaw"], "1000000"); + assert_eq!(quote["actualAmountA"], "1000000"); let _plan: fn(CreatePoolPlanRequest) -> AmmResult = create_pool_plan; } diff --git a/modules/amm/src/amm_module_impl.cpp b/modules/amm/src/amm_module_impl.cpp index 5dda201..5137581 100644 --- a/modules/amm/src/amm_module_impl.cpp +++ b/modules/amm/src/amm_module_impl.cpp @@ -595,14 +595,14 @@ LogosMap AmmModuleImpl::swapExactInQuote(const std::string& token_in_hex, const FfiResult quoteResult = call(amm_swap_exact_in_quote, json{ {"tokenInId", token_in}, {"tokenOutId", token_out}, - {"amountInRaw", amount_in_decimal}, + {"amountIn", amount_in_decimal}, {"slippageBps", slippage_bps}, {"poolData", pool_data}, }); if (!quoteResult.ok) return error(quoteResult.error.empty() ? "backend_error" : quoteResult.error); - // Success: wrap the priced payload { expectedOutRaw, minReceivedRaw, + // Success: wrap the priced payload { expectedOut, minReceived, // priceImpactBps } in the standard envelope. LogosMap out = quoteResult.value; out["status"] = "ok"; @@ -646,14 +646,14 @@ LogosMap AmmModuleImpl::swapExactOutQuote(const std::string& token_in_hex, const FfiResult quoteResult = call(amm_swap_exact_out_quote, json{ {"tokenInId", token_in}, {"tokenOutId", token_out}, - {"amountOutRaw", amount_out_decimal}, + {"amountOut", amount_out_decimal}, {"slippageBps", slippage_bps}, {"poolData", pool_data}, }); if (!quoteResult.ok) return error(quoteResult.error.empty() ? "backend_error" : quoteResult.error); - // Success: wrap { requiredInRaw, maxInRaw, priceImpactBps } in the envelope. + // Success: wrap { requiredIn, maxIn, priceImpactBps } in the envelope. LogosMap out = quoteResult.value; out["status"] = "ok"; out["error"] = ""; @@ -856,7 +856,7 @@ LogosMap AmmModuleImpl::createPoolQuote(const LogosMap& request) { if (token_a.empty() || token_b.empty()) return error("invalid_token_id"); - // amountARaw/amountBRaw arrive as a JSON number (CLI) or decimal string (UI); + // amountA/amountB arrive as a JSON number (CLI) or decimal string (UI); // coerce to canonical decimal strings (rejects floats — see jsonAmountToDecimal). // If an amount field is present but malformed, return bad_amount; otherwise leave it // out so the FFI returns amount_required. @@ -864,30 +864,30 @@ LogosMap AmmModuleImpl::createPoolQuote(const LogosMap& request) { {"tokenAId", token_a}, {"tokenBId", token_b}, }; - // priceRaw is the Q64.64 opening price; used when no amounts are supplied + // price is the Q64.64 opening price; used when no amounts are supplied // (price-only ⇒ the op returns the minimum opening deposit). Left out if absent. std::string price_decimal; - if (jsonAmountToDecimal(request.value("priceRaw", json()), price_decimal)) - quoteRequest["priceRaw"] = price_decimal; - if (request.contains("amountARaw")) { + if (jsonAmountToDecimal(request.value("price", json()), price_decimal)) + quoteRequest["price"] = price_decimal; + if (request.contains("amountA")) { std::string amount_a_decimal; - if (!jsonAmountToDecimal(request.at("amountARaw"), amount_a_decimal)) + if (!jsonAmountToDecimal(request.at("amountA"), amount_a_decimal)) return error("bad_amount"); - quoteRequest["amountARaw"] = amount_a_decimal; + quoteRequest["amountA"] = amount_a_decimal; } - if (request.contains("amountBRaw")) { + if (request.contains("amountB")) { std::string amount_b_decimal; - if (!jsonAmountToDecimal(request.at("amountBRaw"), amount_b_decimal)) + if (!jsonAmountToDecimal(request.at("amountB"), amount_b_decimal)) return error("bad_amount"); - quoteRequest["amountBRaw"] = amount_b_decimal; + quoteRequest["amountB"] = amount_b_decimal; } const FfiResult quoteResult = call(amm_create_pool_quote, quoteRequest); if (!quoteResult.ok) return error(quoteResult.error.empty() ? "backend_error" : quoteResult.error); - // Success: wrap { actualAmountARaw, actualAmountBRaw, minimumAmountARaw, - // minimumAmountBRaw, expectedLpRaw, lockedLpRaw, priceRaw } in the envelope. + // Success: wrap { actualAmountA, actualAmountB, minimumAmountA, + // minimumAmountB, expectedLp, lockedLp, price } in the envelope. LogosMap out = quoteResult.value; out["status"] = "ok"; out["error"] = ""; @@ -928,8 +928,8 @@ LogosMap AmmModuleImpl::createPool(const LogosMap& request) { std::string amount_a_decimal; std::string amount_b_decimal; std::string deadline_decimal; - if (!jsonAmountToDecimal(request.value("amountARaw", json()), amount_a_decimal) - || !jsonAmountToDecimal(request.value("amountBRaw", json()), amount_b_decimal) + if (!jsonAmountToDecimal(request.value("amountA", json()), amount_a_decimal) + || !jsonAmountToDecimal(request.value("amountB", json()), amount_b_decimal) || !jsonAmountToDecimal(request.value("deadlineMs", json()), deadline_decimal)) return error("bad_amount"); @@ -947,8 +947,8 @@ LogosMap AmmModuleImpl::createPool(const LogosMap& request) { {"config", config}, {"tokenAId", token_a}, {"tokenBId", token_b}, - {"amountARaw", amount_a_decimal}, - {"amountBRaw", amount_b_decimal}, + {"amountA", amount_a_decimal}, + {"amountB", amount_b_decimal}, {"feeBps", fee_val}, {"deadlineMs", deadline_decimal}, {"userHoldingAId", holding_a}, @@ -996,8 +996,8 @@ LogosMap AmmModuleImpl::addLiquidityQuote(const LogosMap& request) { std::string max_a_decimal; std::string max_b_decimal; - if (!jsonAmountToDecimal(request.value("maxAmountARaw", json()), max_a_decimal) - || !jsonAmountToDecimal(request.value("maxAmountBRaw", json()), max_b_decimal)) + if (!jsonAmountToDecimal(request.value("maxAmountA", json()), max_a_decimal) + || !jsonAmountToDecimal(request.value("maxAmountB", json()), max_b_decimal)) return error("bad_amount"); // Derive the pool id (config-free) and read the pool account; its raw data is handed @@ -1013,7 +1013,7 @@ LogosMap AmmModuleImpl::addLiquidityQuote(const LogosMap& request) { const std::string pool_data = jStr(pool.value("account", json::object()), "data"); // slippageBps is a fraction of 100% in basis points; the pricing op uses it to derive - // minimumLpRaw (the LP floor the submit accepts). Require an integer JSON number and reject + // minimumLp (the LP floor the submit accepts). Require an integer JSON number and reject // everything else with a stable invalid_slippage: is_number() would also accept a float // (and get() on a number_float THROWS, terminating the module), while a string / // bool would otherwise fall through to a silent 0. A missing field defaults to 0 (no @@ -1028,15 +1028,15 @@ LogosMap AmmModuleImpl::addLiquidityQuote(const LogosMap& request) { const FfiResult quoteResult = call(amm_add_liquidity_quote, json{ {"tokenAId", token_a}, {"tokenBId", token_b}, - {"maxAmountARaw", max_a_decimal}, - {"maxAmountBRaw", max_b_decimal}, + {"maxAmountA", max_a_decimal}, + {"maxAmountB", max_b_decimal}, {"slippageBps", slippage_bps}, {"poolData", pool_data}, }); if (!quoteResult.ok) return error(quoteResult.error.empty() ? "backend_error" : quoteResult.error); - // Success: wrap { amountARaw, amountBRaw, expectedLpRaw, minimumLpRaw, priceRaw }. + // Success: wrap { amountA, amountB, expectedLp, minimumLp, price }. LogosMap out = quoteResult.value; out["status"] = "ok"; out["error"] = ""; @@ -1076,9 +1076,9 @@ LogosMap AmmModuleImpl::addLiquidity(const LogosMap& request) { std::string max_b_decimal; std::string min_lp_decimal; std::string deadline_decimal; - if (!jsonAmountToDecimal(request.value("maxAmountARaw", json()), max_a_decimal) - || !jsonAmountToDecimal(request.value("maxAmountBRaw", json()), max_b_decimal) - || !jsonAmountToDecimal(request.value("minLpRaw", json()), min_lp_decimal) + if (!jsonAmountToDecimal(request.value("maxAmountA", json()), max_a_decimal) + || !jsonAmountToDecimal(request.value("maxAmountB", json()), max_b_decimal) + || !jsonAmountToDecimal(request.value("minLp", json()), min_lp_decimal) || !jsonAmountToDecimal(request.value("deadlineMs", json()), deadline_decimal)) return error("bad_amount"); @@ -1101,9 +1101,9 @@ LogosMap AmmModuleImpl::addLiquidity(const LogosMap& request) { {"config", config}, {"tokenAId", token_a}, {"tokenBId", token_b}, - {"maxAmountARaw", max_a_decimal}, - {"maxAmountBRaw", max_b_decimal}, - {"minLpRaw", min_lp_decimal}, + {"maxAmountA", max_a_decimal}, + {"maxAmountB", max_b_decimal}, + {"minLp", min_lp_decimal}, {"deadlineMs", deadline_decimal}, {"userHoldingAId", holding_a}, {"userHoldingBId", holding_b}, @@ -1149,7 +1149,7 @@ LogosMap AmmModuleImpl::removeLiquidityQuote(const LogosMap& request) { return error("config_missing"); std::string lp_amount_decimal; - if (!jsonAmountToDecimal(request.value("lpAmountRaw", json()), lp_amount_decimal)) + if (!jsonAmountToDecimal(request.value("lpAmount", json()), lp_amount_decimal)) return error("bad_amount"); // Derive the pool id (config-free) and read the pool account; its raw data is handed to @@ -1180,14 +1180,14 @@ LogosMap AmmModuleImpl::removeLiquidityQuote(const LogosMap& request) { const FfiResult quoteResult = call(amm_remove_liquidity_quote, json{ {"tokenAId", token_a}, {"tokenBId", token_b}, - {"lpAmountRaw", lp_amount_decimal}, + {"lpAmount", lp_amount_decimal}, {"slippageBps", slippage_bps}, {"poolData", pool_data}, }); if (!quoteResult.ok) return error(quoteResult.error.empty() ? "backend_error" : quoteResult.error); - // Success: wrap { amountARaw, amountBRaw, minimumAmountARaw, minimumAmountBRaw, priceRaw }. + // Success: wrap { amountA, amountB, minimumAmountA, minimumAmountB, price }. LogosMap out = quoteResult.value; out["status"] = "ok"; out["error"] = ""; @@ -1227,9 +1227,9 @@ LogosMap AmmModuleImpl::removeLiquidity(const LogosMap& request) { std::string min_a_decimal; std::string min_b_decimal; std::string deadline_decimal; - if (!jsonAmountToDecimal(request.value("lpAmountRaw", json()), lp_amount_decimal) - || !jsonAmountToDecimal(request.value("minAmountARaw", json()), min_a_decimal) - || !jsonAmountToDecimal(request.value("minAmountBRaw", json()), min_b_decimal) + if (!jsonAmountToDecimal(request.value("lpAmount", json()), lp_amount_decimal) + || !jsonAmountToDecimal(request.value("minAmountA", json()), min_a_decimal) + || !jsonAmountToDecimal(request.value("minAmountB", json()), min_b_decimal) || !jsonAmountToDecimal(request.value("deadlineMs", json()), deadline_decimal)) return error("bad_amount"); @@ -1252,9 +1252,9 @@ LogosMap AmmModuleImpl::removeLiquidity(const LogosMap& request) { {"config", config}, {"tokenAId", token_a}, {"tokenBId", token_b}, - {"lpAmountRaw", lp_amount_decimal}, - {"minAmountARaw", min_a_decimal}, - {"minAmountBRaw", min_b_decimal}, + {"lpAmount", lp_amount_decimal}, + {"minAmountA", min_a_decimal}, + {"minAmountB", min_b_decimal}, {"deadlineMs", deadline_decimal}, {"userHoldingAId", holding_a}, {"userHoldingBId", holding_b}, diff --git a/modules/amm/src/amm_module_impl.h b/modules/amm/src/amm_module_impl.h index bd4eec3..63121c1 100644 --- a/modules/amm/src/amm_module_impl.h +++ b/modules/amm/src/amm_module_impl.h @@ -66,8 +66,8 @@ public: LogosMap createOraclePriceAccount(const LogosMap& request); /// Prices a `SwapExactInput` for the (token_in_hex, token_out_hex) pair: - /// reads the pool and returns `{ status:"ok", error:"", expectedOutRaw, - /// minReceivedRaw, priceImpactBps }`, oriented and computed server-side via + /// reads the pool and returns `{ status:"ok", error:"", expectedOut, + /// minReceived, priceImpactBps }`, oriented and computed server-side via /// the shared on-chain formula. `amount_in` accepts a JSON integer or a /// decimal string (JSON floats rejected); `slippage_bps` is basis points. /// On failure: `{ status:"error", error: }` — `no_pool` (no pool / @@ -81,8 +81,8 @@ public: int64_t slippage_bps); /// Prices a `SwapExactOutput` for the (token_in_hex, token_out_hex) pair: - /// reads the pool and returns `{ status:"ok", error:"", requiredInRaw, - /// maxInRaw, priceImpactBps }`, oriented and computed server-side via the + /// reads the pool and returns `{ status:"ok", error:"", requiredIn, + /// maxIn, priceImpactBps }`, oriented and computed server-side via the /// shared on-chain formula. `amount_out` accepts a JSON integer or a decimal /// string (JSON floats rejected); `slippage_bps` is basis points. On failure: /// `{ status:"error", error: }` — `no_pool` (no pool / liquidity), @@ -129,10 +129,10 @@ public: /// Prices creating a pool for (tokenAId, tokenBId) from the two deposit amounts. /// A pure preview — no chain reads, and no fee needed (the fee is not part of the /// pool PDA and doesn't affect the opening LP/price). Returns `{ status:"ok", - /// error:"", amountARaw, amountBRaw, expectedLpRaw, lockedLpRaw, initialPriceRaw }` - /// computed via the shared `amm_core` opening-LP math, so `expectedLpRaw` is + /// error:"", amountA, amountB, expectedLp, lockedLp, initialPrice }` + /// computed via the shared `amm_core` opening-LP math, so `expectedLp` is /// exactly what the guest mints. `request` carries `{ tokenAId, tokenBId, - /// amountARaw, amountBRaw }` (ids hex or base58, normalized to hex; amounts a JSON + /// amountA, amountB }` (ids hex or base58, normalized to hex; amounts a JSON /// integer or decimal string). On failure: `{ status:"error", error: }` — /// `invalid_token_id`, `same_token_pair`, `bad_amount` (an amount field is present /// but not a valid integer — e.g. a float, from `jsonAmountToDecimal`), @@ -146,7 +146,7 @@ public: /// Submits a `NewDefinition` transaction creating the pool for the request's pair. /// `request` carries `{ tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, - /// amountARaw, amountBRaw, feeBps, deadlineMs }` (ids hex or base58, normalized to + /// amountA, amountB, feeBps, deadlineMs }` (ids hex or base58, normalized to /// hex; amounts/deadline a JSON integer or decimal string, deadline a u64 unix-ms). /// The caller provides `lpHoldingId` — a fresh (empty) account the guest initializes /// and mints the creator's LP tokens into; a new pool has no pre-existing LP holding, @@ -162,20 +162,20 @@ public: /// Prices an `AddLiquidity` into the existing pool for (tokenAId, tokenBId) from the /// two max deposit amounts. Reads the pool server-side (like the swap quotes) and runs /// the guest's proportional-deposit math. Returns the same shape as `createPoolQuote` - /// minus the create-only locked LP: `{ status:"ok", error:"", amountARaw, amountBRaw, - /// expectedLpRaw, priceRaw }` — the actual ratio-matched deposits (display order), the + /// minus the create-only locked LP: `{ status:"ok", error:"", amountA, amountB, + /// expectedLp, price }` — the actual ratio-matched deposits (display order), the /// LP minted, and the pool's spot price. Slippage is applied at submit, not here. - /// `request` carries `{ tokenAId, tokenBId, maxAmountARaw, maxAmountBRaw }` (ids hex or + /// `request` carries `{ tokenAId, tokenBId, maxAmountA, maxAmountB }` (ids hex or /// base58, normalized to hex; amounts a JSON integer or decimal string). On failure: /// `{ status:"error", error: }` — `invalid_token_id`, `config_missing`, /// `bad_amount`, `no_pool`, `pair_mismatch`, `amount_too_low`, or `backend_error`. LogosMap addLiquidityQuote(const LogosMap& request); /// Submits an `AddLiquidity` transaction into the request's pool. `request` carries - /// `{ tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, maxAmountARaw, - /// maxAmountBRaw, minLpRaw, deadlineMs }` (ids hex or base58, normalized to hex; - /// amounts/deadline a JSON integer or decimal string). `minLpRaw` is the caller's - /// slippage floor on the LP minted (the UI derives it from the quote's expectedLpRaw + /// `{ tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, maxAmountA, + /// maxAmountB, minLp, deadlineMs }` (ids hex or base58, normalized to hex; + /// amounts/deadline a JSON integer or decimal string). `minLp` is the caller's + /// slippage floor on the LP minted (the UI derives it from the quote's expectedLp /// and its slippage control). `lpHoldingId` is the holding that receives the minted LP. /// On success: `{ status:"ok", error:"", transactionId: }`. On failure: /// `{ status:"error", error: }` — `config_missing`, `backend_error`, @@ -185,12 +185,12 @@ public: LogosMap addLiquidity(const LogosMap& request); /// Prices a `RemoveLiquidity` from the existing pool for (tokenAId, tokenBId): burning - /// `lpAmountRaw` returns the proportional share of each reserve. Reads the pool + /// `lpAmount` returns the proportional share of each reserve. Reads the pool /// server-side (like the add quote) and runs the guest's `floor(reserve·lp/supply)` math. - /// Returns `{ status:"ok", error:"", amountARaw, amountBRaw, minimumAmountARaw, - /// minimumAmountBRaw, priceRaw }` — the withdrawals (display order), the slippage floors + /// Returns `{ status:"ok", error:"", amountA, amountB, minimumAmountA, + /// minimumAmountB, price }` — the withdrawals (display order), the slippage floors /// the submit enforces, and the pool's spot price. `request` carries `{ tokenAId, tokenBId, - /// lpAmountRaw, slippageBps }` (ids hex or base58, normalized to hex; amount a JSON integer + /// lpAmount, slippageBps }` (ids hex or base58, normalized to hex; amount a JSON integer /// or decimal string). On failure: `{ status:"error", error: }` — `invalid_token_id`, /// `config_missing`, `bad_amount`, `invalid_slippage`, `no_pool`, `pair_mismatch`, /// `insufficient_pool_liquidity`, `amount_too_low`, `minimum_amount_zero`, or @@ -198,8 +198,8 @@ public: LogosMap removeLiquidityQuote(const LogosMap& request); /// Submits a `RemoveLiquidity` transaction against the request's pool. `request` carries - /// `{ tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, lpAmountRaw, minAmountARaw, - /// minAmountBRaw, deadlineMs }` (ids hex or base58, normalized to hex; amounts/deadline a + /// `{ tokenAId, tokenBId, holdingAId, holdingBId, lpHoldingId, lpAmount, minAmountA, + /// minAmountB, deadlineMs }` (ids hex or base58, normalized to hex; amounts/deadline a /// JSON integer or decimal string). `lpHoldingId` is the existing holding burned; the token /// a/b holdings receive the withdrawal (no fresh account, unlike add/create). `minAmount*Raw` /// are the caller's slippage floors on the tokens withdrawn. On success: