From 4cfc03a81574ea5d84cce1d7c40d05cdae09d3d7 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Mon, 10 Aug 2026 10:42:24 -0300 Subject: [PATCH] feat(apps/amm): drive the Pools list from AMM_POOLS_CONFIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Pools page shipped with a hardcoded four-pair sample. Replace it with a config-driven "known pools" list, mirroring how the Swap token picker reads TOKENS_CONFIG: the app loads a flat JSON array from the AMM_POOLS_CONFIG environment variable and renders one row per entry. Adding pairs is a config edit — no app change. Pool discovery is an app concern, so the config is read in the backend (AmmUiBackend::poolList, Qt JSON) rather than the amm_module — the module is shedding app-specific view surface (tokenList/newPositionContext), so pools go where tokens are heading, not where they are today. poolList() fails soft to an empty list when AMM_POOLS_CONFIG is unset/unreadable/not an array, and skips individual entries missing tokenA/tokenB/a numeric feeBps. Each entry carries the display symbols (tokenA/tokenB), feeBps, and the on-chain identifiers (poolId, tokenADefinitionId, tokenBDefinitionId) so a row can later be resolved against chain state. PoolsPage takes injected backend/runtime and loads via runtime.watch(backend.poolList()); the Repeater renders entries generically. The AMM testnet setup script now emits amm-pools.json from a POOL_SPECS array (one line per seeded pool, currently the seeded TKA/TKB pool) and prints AMM_POOLS_CONFIG in the launch instructions. Adds amm-pools.json.example, a README section, and gitignores the runtime config files. --- apps/amm/.gitignore | 6 + apps/amm/README.md | 35 ++- apps/amm/VALIDATION.md | 2 +- apps/amm/amm-pools.json.example | 10 + apps/amm/qml/Main.qml | 7 + apps/amm/qml/NavBar.qml | 49 +++- apps/amm/qml/pages/PoolsPage.qml | 280 ++++++++++++++++++++ apps/amm/src/AmmUiBackend.cpp | 66 +++++ apps/amm/src/AmmUiBackend.h | 3 + apps/amm/src/AmmUiBackend.rep | 7 + apps/amm/tests/qml/tst_PoolsPage.qml | 82 ++++++ apps/amm/tests/testnet/setup-amm-testnet.sh | 42 +++ 12 files changed, 580 insertions(+), 9 deletions(-) create mode 100644 apps/amm/amm-pools.json.example create mode 100644 apps/amm/qml/pages/PoolsPage.qml create mode 100644 apps/amm/tests/qml/tst_PoolsPage.qml diff --git a/apps/amm/.gitignore b/apps/amm/.gitignore index 9ab39af..d58d972 100644 --- a/apps/amm/.gitignore +++ b/apps/amm/.gitignore @@ -6,6 +6,9 @@ modules/ # your own definitionId/holding accounts. Never commit your real accounts. amm-tokens.json +# Personal known-pools config (AMM_POOLS_CONFIG). Same rationale as above. +amm-pools.json + # Screenshot artifacts written by tests/swap.mjs tests/*.png @@ -14,3 +17,6 @@ tests/testnet/.wallet/ # Isolated token config written by tests/testnet/setup-amm-testnet.sh tests/testnet/amm-tokens.json + +# Isolated known-pools config written by tests/testnet/setup-amm-testnet.sh +tests/testnet/amm-pools.json diff --git a/apps/amm/README.md b/apps/amm/README.md index aceb206..a0ac951 100644 --- a/apps/amm/README.md +++ b/apps/amm/README.md @@ -211,11 +211,44 @@ picker stays empty (a `qWarning` naming the exact cause is logged to stderr; no swap can be started). `definitionId`/`holding` may be given as base58 (as the wallet/runbook display them) or hex — the app normalizes both to hex. -Full command with both variables set (absolute paths, from the repo root): +### Known-pools config (optional, for the Pools list) + +The Pools view is config-driven the same way: it reads a flat JSON list from the +`AMM_POOLS_CONFIG` environment variable (absolute path) and renders one row per +entry. `tokenA`/`tokenB` are the display symbols and `feeBps` the fee tier; +`poolId`/`tokenADefinitionId`/`tokenBDefinitionId` identify the pool on-chain. +Adding more pairs is purely a config edit — no app change: + +```json +[ + { + "tokenA": "TKA", + "tokenB": "TKB", + "feeBps": 1, + "poolId": "9qbX…", + "tokenADefinitionId": "4T69…", + "tokenBDefinitionId": "7Zc2…" + } +] +``` + +Copy the checked-in template to start (`amm-pools.json` is git-ignored): + +```bash +cp apps/amm/amm-pools.json.example apps/amm/amm-pools.json # then replace the REPLACE_… placeholders +``` + +If `AMM_POOLS_CONFIG` is unset, unreadable, or not a valid JSON array, the Pools +list shows its empty state. Entries missing `tokenA`, `tokenB`, or a numeric +`feeBps` are skipped individually. The AMM testnet setup script writes this file +for the pool(s) it seeds (see below). + +Full command with the variables set (absolute paths, from the repo root): ```bash AMM_PROGRAM_BIN=$(pwd)/programs/amm/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/amm.bin \ TOKENS_CONFIG=$(pwd)/apps/amm/amm-tokens.json \ +AMM_POOLS_CONFIG=$(pwd)/apps/amm/amm-pools.json \ nix run .#amm-ui ``` diff --git a/apps/amm/VALIDATION.md b/apps/amm/VALIDATION.md index 89216fb..ad30b64 100644 --- a/apps/amm/VALIDATION.md +++ b/apps/amm/VALIDATION.md @@ -19,7 +19,7 @@ amm_import="$amm_qml/lib/qml" test -f "$amm_import/Logos/Wallet/qmldir" "$qt_qml/bin/qmltestrunner" -import "$qt_qml/lib/qt-6/qml" -import "$logos_qml/lib" -import "$amm_import" -input apps/shared/wallet/tests/qml "$qt_qml/bin/qmltestrunner" -import "$qt_qml/lib/qt-6/qml" -import "$logos_qml/lib" -import "$amm_import" -input apps/amm/tests/qml -"$qt_qml/bin/qmllint" -I "$qt_qml/lib/qt-6/qml" -I "$logos_qml/lib" -I "$amm_qml/lib" -I "$amm_import" apps/amm/qml/pages/LiquidityPage.qml apps/amm/qml/components/liquidity/NewPositionForm.qml apps/amm/qml/components/liquidity/LiquidityConfirmationSummary.qml +"$qt_qml/bin/qmllint" -I "$qt_qml/lib/qt-6/qml" -I "$logos_qml/lib" -I "$amm_qml/lib" -I "$amm_import" apps/amm/qml/pages/LiquidityPage.qml apps/amm/qml/pages/PoolsPage.qml apps/amm/qml/components/liquidity/NewPositionForm.qml apps/amm/qml/components/liquidity/LiquidityConfirmationSummary.qml ``` When shared AMM program types or instruction signatures change, also run the diff --git a/apps/amm/amm-pools.json.example b/apps/amm/amm-pools.json.example new file mode 100644 index 0000000..004d0cb --- /dev/null +++ b/apps/amm/amm-pools.json.example @@ -0,0 +1,10 @@ +[ + { + "tokenA": "TKA", + "tokenB": "TKB", + "feeBps": 1, + "poolId": "REPLACE_WITH_POOL_PDA", + "tokenADefinitionId": "REPLACE_WITH_TOKEN_A_DEFINITION_ID", + "tokenBDefinitionId": "REPLACE_WITH_TOKEN_B_DEFINITION_ID" + } +] diff --git a/apps/amm/qml/Main.qml b/apps/amm/qml/Main.qml index b700043..aaf44b8 100644 --- a/apps/amm/qml/Main.qml +++ b/apps/amm/qml/Main.qml @@ -92,5 +92,12 @@ Item { runtime: logos visible: navbar.currentIndex === 1 } + + PoolsPage { + anchors.fill: parent + backend: root.ready ? root.backend : null + runtime: logos + visible: navbar.currentIndex === 2 + } } } diff --git a/apps/amm/qml/NavBar.qml b/apps/amm/qml/NavBar.qml index 3c06f91..d48d9a1 100644 --- a/apps/amm/qml/NavBar.qml +++ b/apps/amm/qml/NavBar.qml @@ -1,3 +1,5 @@ +pragma ComponentBehavior: Bound + import QtQuick 2.15 import QtQuick.Layouts 1.15 @@ -10,7 +12,7 @@ Item { id: root property int currentIndex: 0 - readonly property var tabs: ["Trade", "Liquidity"] + readonly property var tabs: [qsTr("Trade"), qsTr("Liquidity"), qsTr("Pools")] // Wallet wiring, passed down from Main.qml. property var backend: null @@ -44,7 +46,7 @@ Item { // App identity Text { - text: "Logos AMM" + text: qsTr("Logos AMM") color: Theme.palette.text font.pixelSize: 17 font.weight: Font.Bold @@ -54,28 +56,61 @@ Item { // Tab pills Row { + Accessible.role: Accessible.PageTabList + Accessible.name: qsTr("Primary navigation") + spacing: 4 Repeater { model: root.tabs delegate: Rectangle { + id: tabButton + + required property int index + required property string modelData + readonly property bool active: root.currentIndex === index height: 36 width: tabLabel.implicitWidth + 28 radius: 18 color: active ? Theme.palette.backgroundSecondary : "transparent" + border.width: activeFocus ? 1 : 0 + border.color: Theme.palette.text + activeFocusOnTab: true + Accessible.role: Accessible.PageTab + Accessible.name: tabLabel.text + + function activate() { + root.currentIndex = index + root.tabChanged(index) + } Behavior on color { ColorAnimation { duration: 150 } } + Keys.onReturnPressed: function(event) { + tabButton.activate() + event.accepted = true + } + + Keys.onEnterPressed: function(event) { + tabButton.activate() + event.accepted = true + } + + Keys.onSpacePressed: function(event) { + tabButton.activate() + event.accepted = true + } + Text { id: tabLabel anchors.centerIn: parent - text: modelData - color: active ? Theme.palette.text : Theme.palette.textSecondary + text: tabButton.modelData + color: tabButton.active ? Theme.palette.text : Theme.palette.textSecondary font.pixelSize: 14 - font.weight: active ? Font.Medium : Font.Normal + font.weight: tabButton.active ? Font.Medium : Font.Normal Behavior on color { ColorAnimation { duration: 150 } } } @@ -84,8 +119,8 @@ Item { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - root.currentIndex = index - root.tabChanged(index) + tabButton.forceActiveFocus() + tabButton.activate() } } } diff --git a/apps/amm/qml/pages/PoolsPage.qml b/apps/amm/qml/pages/PoolsPage.qml new file mode 100644 index 0000000..fcf3026 --- /dev/null +++ b/apps/amm/qml/pages/PoolsPage.qml @@ -0,0 +1,280 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../components/liquidity" +import "../components/swap/TokenVisuals.js" as TokenVisuals + +Item { + id: root + + // Real backend replica (logos.module("amm_ui")) and the watch runtime, + // wired from Main.qml. Null until the app is ready. + property var backend: null + property var runtime: null + + readonly property int pageMargin: width < 640 ? 16 : 24 + readonly property int contentMaxWidth: 760 + readonly property int poolCount: root.pools ? root.pools.length : 0 + readonly property bool showEmptyState: root.poolCount === 0 + + // Config-driven known pools, loaded from AmmUiBackend::poolList() (which + // reads the AMM_POOLS_CONFIG JSON file). Each entry is rendered generically, + // so adding pairs to the config needs no change here. Empty until the + // backend is ready and the call resolves. + property var pools: [] + + function loadPools() { + if (!root.backend || !root.runtime) + return + root.runtime.watch(root.backend.poolList(), + function(list) { root.pools = list }, + function(err) { console.warn("poolList error:", err) }) + } + + onBackendChanged: root.loadPools() + onRuntimeChanged: root.loadPools() + + AmmTheme { + id: theme + } + + function feeLabel(feeBps) { + var percentage = Number(feeBps) / 100 + return qsTr("%1%").arg(percentage.toLocaleString(Qt.locale(), "f", 2)) + } + + Rectangle { + anchors.fill: parent + color: theme.colors.background + } + + Flickable { + id: scroll + + anchors.fill: parent + clip: true + contentWidth: width + contentHeight: Math.max(height, pageContent.y + pageContent.implicitHeight + + root.pageMargin) + flickableDirection: Flickable.VerticalFlick + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } + + Item { + id: pageContent + + x: Math.max(root.pageMargin, (scroll.width - width) / 2) + y: root.width < 640 ? 24 : 40 + width: Math.max(0, Math.min(root.contentMaxWidth, + scroll.width - root.pageMargin * 2)) + implicitHeight: contentColumn.implicitHeight + + Column { + id: contentColumn + + width: parent.width + spacing: 24 + + Column { + width: parent.width + spacing: 6 + + Text { + text: qsTr("Pools") + color: theme.colors.textPrimary + font.pixelSize: 30 + font.weight: Font.Bold + } + + Text { + text: qsTr("Pools") + color: theme.colors.textSecondary + font.pixelSize: 13 + } + } + + Rectangle { + id: poolsList + + objectName: "poolsList" + width: parent.width + implicitHeight: !root.showEmptyState + ? listContent.implicitHeight : 144 + color: theme.colors.cardBg + radius: 16 + border.color: theme.colors.border + border.width: 1 + + Column { + id: listContent + + width: parent.width + visible: !root.showEmptyState + + Item { + width: parent.width + height: 48 + + Text { + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Pair") + color: theme.colors.textSecondary + font.pixelSize: 12 + font.weight: Font.DemiBold + } + + Text { + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Fee") + color: theme.colors.textSecondary + font.pixelSize: 12 + font.weight: Font.DemiBold + } + } + + Rectangle { + width: parent.width + height: 1 + color: theme.colors.divider + } + + Repeater { + model: root.pools || [] + + delegate: PoolRow { + width: listContent.width + showDivider: index < root.poolCount - 1 + objectName: "poolRow%1".arg(index) + } + } + } + + Text { + id: emptyState + + objectName: "poolsListEmptyState" + anchors.centerIn: parent + width: parent.width - 40 + visible: root.showEmptyState + text: qsTr("No pools configured.") + color: theme.colors.textSecondary + font.pixelSize: 14 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + } + } + } + } + } + + component PoolRow: Item { + id: row + + required property var modelData + required property int index + property bool showDivider: false + readonly property var pool: modelData + readonly property string pairText: qsTr("%1 / %2") + .arg(String(pool.tokenA || "")) + .arg(String(pool.tokenB || "")) + readonly property string feeText: root.feeLabel(pool.feeBps) + + height: 68 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 20 + anchors.rightMargin: 20 + spacing: 12 + + Item { + Layout.preferredWidth: 46 + Layout.preferredHeight: 30 + Accessible.ignored: true + + TokenAvatar { + x: 0 + y: 1 + symbol: String(row.pool.tokenA || "") + z: 1 + } + + TokenAvatar { + x: 18 + y: 1 + symbol: String(row.pool.tokenB || "") + } + } + + Text { + Layout.fillWidth: true + text: row.pairText + color: theme.colors.textPrimary + font.pixelSize: 16 + font.weight: Font.Medium + elide: Text.ElideRight + } + + Rectangle { + Layout.preferredWidth: feeText.implicitWidth + 20 + Layout.preferredHeight: 30 + radius: 6 + color: theme.colors.inputBg + border.color: theme.colors.borderStrong + border.width: 1 + + Text { + id: feeText + + anchors.centerIn: parent + text: row.feeText + color: theme.colors.textPrimary + font.pixelSize: 13 + font.weight: Font.Medium + } + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + visible: row.showDivider + height: 1 + color: theme.colors.divider + } + } + + component TokenAvatar: Rectangle { + id: avatar + + required property string symbol + + width: 28 + height: 28 + radius: 14 + color: TokenVisuals.colorFor(symbol) + border.color: theme.colors.cardBg + border.width: 2 + Accessible.ignored: true + + Text { + anchors.centerIn: parent + text: TokenVisuals.letterFor(avatar.symbol) + color: "#FFFFFF" + font.pixelSize: 10 + font.weight: Font.Bold + Accessible.ignored: true + } + } +} diff --git a/apps/amm/src/AmmUiBackend.cpp b/apps/amm/src/AmmUiBackend.cpp index 80a2800..8ba7a13 100644 --- a/apps/amm/src/AmmUiBackend.cpp +++ b/apps/amm/src/AmmUiBackend.cpp @@ -1,6 +1,10 @@ #include "AmmUiBackend.h" #include +#include +#include +#include +#include #include #include "LogosWalletProvider.h" @@ -9,6 +13,59 @@ #include "logos_sdk.h" namespace { + // Absolute path to the JSON known-pools config consumed by poolList(). + // Mirrors TOKENS_CONFIG for the token list; produced by the AMM testnet + // setup script (apps/amm/tests/testnet/setup-amm-testnet.sh). + constexpr char POOLS_CONFIG_ENV[] = "AMM_POOLS_CONFIG"; + + // Parses the AMM_POOLS_CONFIG JSON file into the QVariantList the Pools UI + // renders. Fails soft (empty list) when the env var is unset, the file is + // unreadable, or the payload is not a JSON array — one malformed entry is + // skipped rather than dropping the whole list. tokenA/tokenB (display + // symbols) and a numeric feeBps are required; the id fields pass through + // when present so the entry can later be resolved on-chain. + QVariantList readPoolsConfig() + { + QVariantList out; + + const QString path = qEnvironmentVariable(POOLS_CONFIG_ENV); + if (path.isEmpty()) + return out; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return out; + + const QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (!doc.isArray()) + return out; + + for (const QJsonValue& entry : doc.array()) { + if (!entry.isObject()) + continue; + const QJsonObject obj = entry.toObject(); + + const QString tokenA = obj.value(QStringLiteral("tokenA")).toString(); + const QString tokenB = obj.value(QStringLiteral("tokenB")).toString(); + const QJsonValue feeBps = obj.value(QStringLiteral("feeBps")); + if (tokenA.isEmpty() || tokenB.isEmpty() || !feeBps.isDouble()) + continue; + + QVariantMap pool; + pool.insert(QStringLiteral("tokenA"), tokenA); + pool.insert(QStringLiteral("tokenB"), tokenB); + pool.insert(QStringLiteral("feeBps"), feeBps.toInt()); + pool.insert(QStringLiteral("poolId"), + obj.value(QStringLiteral("poolId")).toString()); + pool.insert(QStringLiteral("tokenADefinitionId"), + obj.value(QStringLiteral("tokenADefinitionId")).toString()); + pool.insert(QStringLiteral("tokenBDefinitionId"), + obj.value(QStringLiteral("tokenBDefinitionId")).toString()); + out.append(pool); + } + return out; + } + // The new-position context placeholder published before the module // connection is up (matches the module's "loading" contextState). QVariantMap loadingContext() @@ -249,6 +306,15 @@ QVariantList AmmUiBackend::tokenHoldings() return m_logos->amm_module.tokenHoldings(isWalletOpen()); } +QVariantList AmmUiBackend::poolList() +{ + // Config-driven known pools. Read straight from AMM_POOLS_CONFIG on every + // call (the UI fetches this once on load); adding more pairs is a config + // edit, no app change. Pool discovery is an app concern, so this stays in + // the backend rather than the amm_module. + return readPoolsConfig(); +} + QVariantMap AmmUiBackend::createPool(QVariantMap request) { // Same connected-state submit guard as the swaps — this app's lock is diff --git a/apps/amm/src/AmmUiBackend.h b/apps/amm/src/AmmUiBackend.h index 1e38bee..03bfa65 100644 --- a/apps/amm/src/AmmUiBackend.h +++ b/apps/amm/src/AmmUiBackend.h @@ -82,6 +82,9 @@ public slots: QVariantMap addLiquidity(QVariantMap request) override; // Lists the wallet's fungible token holdings for the account selector. QVariantList tokenHoldings() override; + // Reads the known-pools list from AMM_POOLS_CONFIG (app config JSON, read + // here rather than in the amm_module — pool discovery is an app detail). + QVariantList poolList() override; private: void syncWalletState(); diff --git a/apps/amm/src/AmmUiBackend.rep b/apps/amm/src/AmmUiBackend.rep index 1553799..09440fb 100644 --- a/apps/amm/src/AmmUiBackend.rep +++ b/apps/amm/src/AmmUiBackend.rep @@ -137,4 +137,11 @@ class AmmUiBackend // definitionIdHex, balanceRaw }] — one row per holding account, every token, // including zero-balance holdings. The selector narrows to a token by id. SLOT(QVariantList tokenHoldings()) + // Reads the known-pools config at AMM_POOLS_CONFIG (absolute path, JSON array + // of { tokenA, tokenB, feeBps, poolId, tokenADefinitionId, tokenBDefinitionId }) + // and returns it as a QVariantList of QVariantMap entries. tokenA/tokenB are + // display symbols; the id fields identify the pool on-chain. Returns an empty + // list if AMM_POOLS_CONFIG is unset/unreadable/invalid. This is app config, not + // an on-chain read, so it lives in the backend rather than the amm_module. + SLOT(QVariantList poolList()) } diff --git a/apps/amm/tests/qml/tst_PoolsPage.qml b/apps/amm/tests/qml/tst_PoolsPage.qml new file mode 100644 index 0000000..58fc832 --- /dev/null +++ b/apps/amm/tests/qml/tst_PoolsPage.qml @@ -0,0 +1,82 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtTest + +import "../../qml/pages" as Pages + +TestCase { + id: testCase + + name: "PoolsPage" + + Component { + id: backendComponent + + QtObject { + property var poolListResult: [ + { "tokenA": "TKA", "tokenB": "TKB", "feeBps": 5 }, + { "tokenA": "TKC", "tokenB": "TKA", "feeBps": 30 } + ] + + function poolList() { + return poolListResult + } + } + } + + Component { + id: runtimeComponent + + QtObject { + function watch(value, succeeded, failed) { + if (value === undefined) + return + succeeded(value) + } + } + } + + Component { + id: pageComponent + + Pages.PoolsPage { + visible: false + width: 800 + height: 600 + } + } + + function test_rowsAreDrivenByBackendPoolList() { + var backend = createTemporaryObject(backendComponent, testCase) + var runtime = createTemporaryObject(runtimeComponent, testCase) + var page = createTemporaryObject(pageComponent, testCase, { + "backend": backend, + "runtime": runtime + }) + verify(backend) + verify(runtime) + verify(page) + + // The config drives the row count — no pair is hardcoded in the page, + // so adding entries to poolList() is all it takes to render more rows. + compare(page.poolCount, 2) + verify(page.feeLabel(5).endsWith("%")) + + var list = findChild(page, "poolsList") + var firstRow = findChild(page, "poolRow0") + verify(list) + verify(firstRow) + compare(firstRow.pairText, "TKA / TKB") + compare(firstRow.feeText, page.feeLabel(5)) + } + + function test_emptyPoolModelUsesTheEmptyState() { + var page = createTemporaryObject(pageComponent, testCase) + verify(page) + + // No backend wired: pools defaults to [] and the empty state shows. + compare(page.poolCount, 0) + compare(page.showEmptyState, true) + } +} diff --git a/apps/amm/tests/testnet/setup-amm-testnet.sh b/apps/amm/tests/testnet/setup-amm-testnet.sh index a1e23fa..736b3d5 100755 --- a/apps/amm/tests/testnet/setup-amm-testnet.sh +++ b/apps/amm/tests/testnet/setup-amm-testnet.sh @@ -100,6 +100,11 @@ POOL_DEADLINE="18446744073709551615" # TOKENS_CONFIG when launching the UI for a test run. TOKENS_CONFIG_OUT="apps/amm/tests/testnet/amm-tokens.json" +# Where the UI known-pools config is written (git-ignored, tests only). Pass this +# path as AMM_POOLS_CONFIG when launching the UI; the Pools page renders one row +# per entry. More seeded pools = more entries here, no app change. +POOLS_CONFIG_OUT="apps/amm/tests/testnet/amm-pools.json" + ############################################################################### # Helpers ############################################################################### @@ -423,6 +428,42 @@ cat > "$TOKENS_CONFIG_OUT" < $POOLS_CONFIG_OUT" +# One row per seeded pool: "SYMBOL_A SYMBOL_B FEE_BPS POOL_ID DEF_A DEF_B". +# Add a line here for each new seeded pool — nothing else (script or app) needs +# to change; the Pools page renders one row per entry generically. +POOL_SPECS=( + "$TOKEN_A_SYMBOL $TOKEN_B_SYMBOL $POOL_FEES $POOL $TOKEN_A_DEF $TOKEN_B_DEF" +) + +pool_entry() { + cat < "$POOLS_CONFIG_OUT" +kv "wrote" "$POOLS_CONFIG_OUT" + sec "Done" log "${GRN}✅ Setup complete.${RST}" kv "AMM program id" "$AMM_PID" @@ -433,6 +474,7 @@ log "Launch the UI against the ISOLATED test wallet + test token config:" log " ${DIM}LEE_WALLET_HOME_DIR=$TEST_WALLET_HOME \\${RST}" log " ${DIM} AMM_PROGRAM_BIN=$REPO_ROOT/$AMM_BIN \\${RST}" log " ${DIM} TOKENS_CONFIG=$REPO_ROOT/$TOKENS_CONFIG_OUT \\${RST}" +log " ${DIM} AMM_POOLS_CONFIG=$REPO_ROOT/$POOLS_CONFIG_OUT \\${RST}" log " ${DIM} nix run .#amm-ui${RST}" log "Then in another terminal: ${DIM}node apps/amm/tests/swap.mjs${RST} (swap A/B)" log " or: ${DIM}node apps/amm/tests/create-pool.mjs${RST} (create A/C pool)"