From cbb75c38fd4a21a9d2d8f3c60b1df7042e1b809b Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:19:40 +0200 Subject: [PATCH] refactor(amm): remove the dead Network/context machinery The liquidity token surface moved app-side (resolveTokens + custom tokens), leaving the whole newPositionContext path dormant. Delete it end to end and point the swap methods at the same lean program-id helper everything else uses. --- apps/amm/qml/state/NewPositionFlow.qml | 100 +-------- apps/amm/src/AmmUiBackend.cpp | 50 +---- apps/amm/src/AmmUiBackend.h | 11 +- apps/amm/src/AmmUiBackend.rep | 7 - apps/amm/tests/qml/tst_LiquidityPage.qml | 121 +---------- modules/amm/README.md | 21 +- modules/amm/ffi/include/amm_ffi.h | 4 - modules/amm/ffi/src/account.rs | 4 - modules/amm/ffi/src/api/context.rs | 262 ++--------------------- modules/amm/ffi/src/api/mod.rs | 20 +- modules/amm/ffi/src/api/quote_error.rs | 11 - modules/amm/ffi/src/api/request.rs | 42 +--- modules/amm/ffi/src/api/tests.rs | 106 +-------- modules/amm/ffi/src/ffi.rs | 16 +- modules/amm/ffi/src/lib.rs | 15 +- modules/amm/src/amm_module_impl.cpp | 173 +++------------ modules/amm/src/amm_module_impl.h | 48 +---- 17 files changed, 88 insertions(+), 923 deletions(-) delete mode 100644 modules/amm/ffi/src/api/quote_error.rs diff --git a/apps/amm/qml/state/NewPositionFlow.qml b/apps/amm/qml/state/NewPositionFlow.qml index fccb056..9024f07 100644 --- a/apps/amm/qml/state/NewPositionFlow.qml +++ b/apps/amm/qml/state/NewPositionFlow.qml @@ -9,14 +9,8 @@ QtObject { readonly property bool walletStateReady: root.backend !== null && root.backend.walletStateReady === true - readonly property var newPositionContext: root.walletStateReady - && root.backend.newPositionContext - ? root.backend.newPositionContext - : root.loadingContext() readonly property var viewState: ({ "quote": root.newPositionQuote, - "contextLoading": root.contextLoading || !root.walletStateReady - || root.newPositionContext.status === "loading", "quoteLoading": root.quoteLoading, "quoteStale": root.quoteStale, "submitting": root.submitting, @@ -24,29 +18,22 @@ QtObject { // Create-vs-add routing signal, from the resolvePool read: true = add (pool exists), // false = create, undefined = not resolved yet (a new pair, still resolving). "poolExists": root.poolExists, - "errorCode": root.flowErrorCode || root.contextErrorCode - || root.quoteErrorCode + "errorCode": root.flowErrorCode || root.quoteErrorCode }) property var newPositionQuote: ({}) // Whether the selected pair's pool exists (from resolvePool); drives create-vs-add. // undefined until the first resolve for the current pair lands. property var poolExists: undefined - property var resolvedTokenIds: [] - property int contextSerial: 0 property int quoteSerial: 0 - property bool contextLoading: false property bool quoteLoading: false property bool quoteStale: true property bool submitting: false property string transactionId: "" property string flowErrorCode: "" - property string contextErrorCode: "" property string quoteErrorCode: "" property var pendingQuoteRequest: ({ "ok": false, "request": ({}) }) - signal tokenResolutionFinished(bool finalResponse) - signal tokenResolutionFailed(string code) signal quoteRefreshRequested(bool immediate) signal submitSucceeded signal submitFailed @@ -59,14 +46,7 @@ QtObject { onTriggered: root.requestQuoteNow(root.quoteSerial) } - onNewPositionContextChanged: root.invalidateQuote() - - onWalletStateReadyChanged: { - ++root.contextSerial - if (!root.walletStateReady) - root.contextLoading = false - root.invalidateQuote() - } + onWalletStateReadyChanged: root.invalidateQuote() onActiveChanged: { if (!root.active) @@ -77,72 +57,6 @@ QtObject { }) } - function contextHints(refreshWalletAccounts) { - const request = root.pendingQuoteRequest.request || {} - const recent = [] - if (request.tokenAId) - recent.push(request.tokenAId) - if (request.tokenBId && request.tokenBId !== request.tokenAId) - recent.push(request.tokenBId) - return { - "recentTokenIds": recent, - "resolvedTokenIds": root.resolvedTokenIds, - "refreshWalletAccounts": refreshWalletAccounts === true - } - } - - function refreshContext(refreshWalletAccounts, completed) { - const serial = ++root.contextSerial - root.contextLoading = true - if (!root.walletStateReady || root.runtime === null) { - root.contextLoading = false - return - } - - root.runtime.watch(root.backend.refreshNewPositionContext( - root.contextHints(refreshWalletAccounts)), - function() { - root.finishContextRefresh(serial, completed) - }, - function(error) { - root.failContextRefresh(serial) - }) - } - - function finishContextRefresh(serial, completed) { - if (serial !== root.contextSerial) - return - root.contextLoading = false - root.contextErrorCode = "" - Qt.callLater(function() { - if (serial !== root.contextSerial) - return - root.tokenResolutionFinished(true) - if (completed) - completed() - }) - } - - function failContextRefresh(serial) { - if (serial !== root.contextSerial) - return - root.contextLoading = false - root.contextErrorCode = "backend_error" - root.tokenResolutionFailed("backend_error") - } - - function resolveToken(tokenId) { - const value = String(tokenId || "").trim() - if (value.length === 0) - return - if (root.resolvedTokenIds.indexOf(value) < 0) { - const next = root.resolvedTokenIds.slice(0) - next.push(value) - root.resolvedTokenIds = next - } - root.refreshContext(false) - } - function scheduleQuote(immediate, quoteRequest) { ++root.quoteSerial root.pendingQuoteRequest = quoteRequest @@ -361,7 +275,6 @@ QtObject { root.submitting = false root.transactionId = String(result.transactionId) root.flowErrorCode = "" - root.contextErrorCode = "" root.quoteErrorCode = "" root.invalidateQuote() root.submitSucceeded() @@ -414,7 +327,6 @@ QtObject { root.submitting = false root.transactionId = String(result.transactionId) root.flowErrorCode = "" - root.contextErrorCode = "" root.quoteErrorCode = "" root.invalidateQuote() root.submitSucceeded() @@ -443,7 +355,6 @@ QtObject { root.invalidateQuote() root.transactionId = "" root.flowErrorCode = "" - root.contextErrorCode = "" root.quoteErrorCode = "" } @@ -463,13 +374,6 @@ QtObject { root.quoteStale = true } - function loadingContext() { - return { - "status": "loading", - "tokens": [] - } - } - function quoteError(code) { return { "status": "error", diff --git a/apps/amm/src/AmmUiBackend.cpp b/apps/amm/src/AmmUiBackend.cpp index 44e43d9..c4ec371 100644 --- a/apps/amm/src/AmmUiBackend.cpp +++ b/apps/amm/src/AmmUiBackend.cpp @@ -71,22 +71,9 @@ namespace { } return out; } - - // The new-position context placeholder published before the module - // connection is up (matches the module's "loading" contextState). - QVariantMap loadingContext() - { - return QVariantMap { - { QStringLiteral("status"), QStringLiteral("loading") }, - { QStringLiteral("networkId"), QStringLiteral("lez") }, - { QStringLiteral("networkFingerprint"), QString() }, - { QStringLiteral("tokens"), QVariantList() }, - { QStringLiteral("feeTiers"), QVariantList() }, - { QStringLiteral("warnings"), QVariantList() }, - }; - } } + AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent) : AmmUiBackendSimpleSource(parent), m_logosAPI(logosAPI ? logosAPI : new LogosAPI("amm_ui", this)), @@ -148,7 +135,6 @@ void AmmUiBackend::disconnectWallet() { m_walletController->disconnect(); setWalletStateReady(true); - refreshNewPositionContext(QVariantMap()); } QString AmmUiBackend::createAccountPublic() @@ -176,28 +162,6 @@ QString AmmUiBackend::getBalance(QString accountIdHex, bool isPublic) return m_walletController->balance(accountIdHex, isPublic); } -QVariantMap AmmUiBackend::refreshNewPositionContext(QVariantMap request) -{ - const bool refreshWalletAccounts = - request.take(QStringLiteral("refreshWalletAccounts")).toBool(); - if (request.contains(QStringLiteral("recentTokenIds")) - || request.contains(QStringLiteral("resolvedTokenIds"))) { - m_newPositionHints = request; - } - else { - request = m_newPositionHints; - } - if (!walletStateReady()) { - const QVariantMap context = loadingContext(); - setNewPositionContext(context); - return context; - } - const QVariantMap context = m_logos->amm_module.newPositionContext( - request, isWalletOpen(), refreshWalletAccounts); - setNewPositionContext(context); - return context; -} - void AmmUiBackend::syncWalletState() { const WalletUiState& state = m_walletController->state(); @@ -211,18 +175,6 @@ void AmmUiBackend::syncWalletState() setCurrentBlockHeight(state.currentBlockHeight); setSequencerAddr(state.sequencerAddress); setSequencerReachable(state.sequencerReachable); - - publishNetworkContext(); -} - -void AmmUiBackend::publishNetworkContext() -{ - if (!walletStateReady()) { - setNewPositionContext(loadingContext()); - return; - } - setNewPositionContext(m_logos->amm_module.newPositionContext( - m_newPositionHints, isWalletOpen(), false)); } QVariantMap AmmUiBackend::resolvePool(QString defAHex, QString defBHex) diff --git a/apps/amm/src/AmmUiBackend.h b/apps/amm/src/AmmUiBackend.h index a2c70d3..d6932d5 100644 --- a/apps/amm/src/AmmUiBackend.h +++ b/apps/amm/src/AmmUiBackend.h @@ -46,7 +46,6 @@ public slots: void refreshAccounts() override; void refreshBalances() override; QString getBalance(QString accountIdHex, bool isPublic) override; - QVariantMap refreshNewPositionContext(QVariantMap request) override; // Return the new wallet's BIP39 mnemonic (empty string on failure) so the // UI can force a one-time seed-phrase backup step. QString createNewDefault(QString password) override; @@ -101,24 +100,16 @@ private: QStringList loadCustomTokenIds() const; bool saveCustomTokenIds(const QStringList& ids) const; QString customTokenStorePath() const; - // Publishes the new-position context PROP: a local "loading" placeholder - // until wallet state (and thus the module connection) is ready, then the - // module's newPositionContext for the current hints. - void publishNetworkContext(); LogosAPI* m_logosAPI; // Handle for the amm_module core module (resolvePool / swapExactInput / - // tokenList / new-position). The module wraps the amm_ffi brain and + // tokenList / resolveTokens). The module wraps the amm_ffi brain and // reaches the shared wallet through its own logos_execution_zone dependency; // this backend keeps a thin LogosModules over the same LogosAPI as the // wallet provider so both resolve that one shared wallet instance. std::unique_ptr m_logos; std::unique_ptr m_wallet; std::unique_ptr m_walletController; - - // Sticky new-position hints (recent/resolved token ids) so a bare - // republish (wallet-state change) keeps the user's last selection. - QVariantMap m_newPositionHints; }; #endif // AMM_UI_BACKEND_H diff --git a/apps/amm/src/AmmUiBackend.rep b/apps/amm/src/AmmUiBackend.rep index 7da0d15..dfd7a31 100644 --- a/apps/amm/src/AmmUiBackend.rep +++ b/apps/amm/src/AmmUiBackend.rep @@ -26,13 +26,6 @@ class AmmUiBackend SLOT(void refreshBalances()) SLOT(QString getBalance(QString accountIdHex, bool isPublic)) - // New Position backend surface. QML calls these through logos.watch(...). - // The QVariant payloads are stable maps/lists so the UI never assembles AMM - // transactions or duplicates quote state. - PROP(QVariantMap newPositionContext READONLY) - // Return the published context so the QML refresh watcher always settles. - SLOT(QVariantMap refreshNewPositionContext(QVariantMap request)) - // Wallet lifecycle. createNewDefault() is the happy path: it creates a // fresh wallet at the canonical walletHome with no path picking. createNew() // keeps explicit paths for an "advanced" flow. Both return the new wallet's diff --git a/apps/amm/tests/qml/tst_LiquidityPage.qml b/apps/amm/tests/qml/tst_LiquidityPage.qml index f72befc..69f8984 100644 --- a/apps/amm/tests/qml/tst_LiquidityPage.qml +++ b/apps/amm/tests/qml/tst_LiquidityPage.qml @@ -11,21 +11,12 @@ TestCase { name: "LiquidityPage" Component { - id: backendComponent + id: pageComponent - QtObject { - property bool walletStateReady: false - property int contextRefreshCalls: 0 - property var newPositionContext: ({ - "status": "ready", - "tokens": [], - "feeTiers": [] - }) - - function refreshNewPositionContext(request) { - ++contextRefreshCalls - return newPositionContext - } + Pages.LiquidityPage { + visible: false + width: 800 + height: 600 } } @@ -54,106 +45,4 @@ TestCase { verify(form.width > 0) verify(form.width <= page.width - 32) } - - Component { - id: runtimeComponent - - QtObject { - function watch(value, succeeded, failed) { - if (value === undefined) - return - succeeded(value) - } - } - } - - Component { - id: pageComponent - - Pages.LiquidityPage { - visible: false - width: 800 - height: 600 - } - } - - function test_contextWaitsForWalletState() { - var backend = createTemporaryObject(backendComponent, testCase) - var page = createTemporaryObject(pageComponent, testCase, { - "backend": backend - }) - verify(backend) - verify(page) - - compare(page.flow.newPositionContext.status, "loading") - - backend.walletStateReady = true - tryCompare(page.flow.newPositionContext, "status", "ready") - } - - function test_contextRefreshControlsWalletScan() { - var backend = createTemporaryObject(backendComponent, testCase, { - "walletStateReady": true - }) - var page = createTemporaryObject(pageComponent, testCase, { - "backend": backend - }) - verify(backend) - verify(page) - - compare(page.flow.contextHints(false).refreshWalletAccounts, false) - compare(page.flow.contextHints(true).refreshWalletAccounts, true) - } - - function test_refreshPositionCompletesAndReenablesTokenSelection() { - var backend = createTemporaryObject(backendComponent, testCase, { - "walletStateReady": true - }) - var runtime = createTemporaryObject(runtimeComponent, testCase) - var page = createTemporaryObject(pageComponent, testCase, { - "backend": backend, - "runtime": runtime - }) - verify(backend) - verify(runtime) - verify(page) - - var refreshButton = findChild(page, "refreshPositionButton") - var tokenAInput = findChild(page, "tokenAAmountInput") - verify(refreshButton) - verify(tokenAInput) - - refreshButton.clicked() - - tryCompare(backend, "contextRefreshCalls", 1) - tryCompare(page.flow, "contextLoading", false) - compare(refreshButton.enabled, true) - compare(tokenAInput.tokenSelectionEnabled, true) - } - - function test_staleContextCompletionCannotFinishNewerRefresh() { - var backend = createTemporaryObject(backendComponent, testCase, { - "walletStateReady": true - }) - var page = createTemporaryObject(pageComponent, testCase, { - "backend": backend - }) - verify(backend) - verify(page) - - page.flow.contextSerial = 2 - page.flow.contextLoading = true - page.flow.contextErrorCode = "newer_request_pending" - - page.flow.finishContextRefresh(1, null) - page.flow.failContextRefresh(1) - - compare(page.flow.contextLoading, true) - compare(page.flow.contextErrorCode, "newer_request_pending") - - page.flow.finishContextRefresh(2, null) - compare(page.flow.contextLoading, false) - compare(page.flow.contextErrorCode, "") - } - } diff --git a/modules/amm/README.md b/modules/amm/README.md index d82e08b..a181191 100644 --- a/modules/amm/README.md +++ b/modules/amm/README.md @@ -26,17 +26,16 @@ methods (the module API is generated from the header) are: **Amount / id conventions** below. - `tokenList()` — reads the `TOKENS_CONFIG` JSON array and returns it with `definitionId`/`holding` normalized to hex. -- `newPositionContext(request, walletOpen, refreshWalletAccounts)` — the - add-liquidity view state (available tokens, fee tiers, warnings) as a - context map. -- `quoteNewPosition(request, walletOpen)` — prices an add-liquidity request - against current on-chain state (read-only). -- `submitNewPosition(request, quoteHash, walletOpen, freshLpId)` — submits an - add-liquidity transaction. When the quote needs a fresh LP holding and - `freshLpId` is empty, returns `{ status: "requires_fresh_lp" }` **without** - submitting: the caller (the app backend, which owns the wallet keyset) creates - the account and calls again with its id. Headless callers pre-create an LP - holding and pass it. +- `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 + 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. ## How it fits together diff --git a/modules/amm/ffi/include/amm_ffi.h b/modules/amm/ffi/include/amm_ffi.h index 1d44a56..1d9c60c 100644 --- a/modules/amm/ffi/include/amm_ffi.h +++ b/modules/amm/ffi/include/amm_ffi.h @@ -16,12 +16,8 @@ extern "C" { char *amm_config_id(const char *request_json); -char *amm_token_ids(const char *request_json); - char *amm_pair_ids(const char *request_json); -char *amm_context(const char *request_json); - char *amm_resolve_tokens(const char *request_json); char *amm_swap_pair(const char *request_json); diff --git a/modules/amm/ffi/src/account.rs b/modules/amm/ffi/src/account.rs index 2a8d020..5c66097 100644 --- a/modules/amm/ffi/src/account.rs +++ b/modules/amm/ffi/src/account.rs @@ -60,10 +60,6 @@ pub(crate) fn program_id_hex(program_id: ProgramId) -> String { hex::encode(bytes) } -pub(crate) fn program_id_base58(program_id: ProgramId) -> String { - AccountId::new(program_id_bytes(program_id)).to_string() -} - pub(crate) fn program_id_bytes(program_id: ProgramId) -> [u8; 32] { let mut bytes = [0_u8; 32]; for (chunk, word) in bytes.chunks_exact_mut(4).zip(program_id) { diff --git a/modules/amm/ffi/src/api/context.rs b/modules/amm/ffi/src/api/context.rs index d772a54..514d609 100644 --- a/modules/amm/ffi/src/api/context.rs +++ b/modules/amm/ffi/src/api/context.rs @@ -1,143 +1,20 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; -use amm_core::{ - FEE_TIER_BPS_1, FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY, -}; use nssa_core::{account::AccountId, program::ProgramId}; use serde_json::{json, Value}; use token_core::TokenDefinition; use super::{ config::load_config, - holding::{select_holding, wallet_holdings, SelectedHolding}, - quote_error::issue, - ContextRequest, ResolveTokensRequest, TokenIdsRequest, -}; -use crate::account::{ - account_id_from_hex, account_id_hex, decode_account, parse_base58_id, parse_program_id, - program_id_base58, AccountRead, + holding::{select_holding, wallet_holdings}, + ResolveTokensRequest, }; +use crate::account::{account_id_from_hex, decode_account, parse_program_id, AccountRead}; -pub(super) fn token_ids(request: TokenIdsRequest) -> Result { - let amm_program = parse_program_id(&request.amm_program_id)?; - let Ok(config) = load_config(amm_program, &request.config) else { - return Ok(manifest_error("config_unavailable")); - }; - - let holdings = wallet_holdings(&request.wallet_accounts, config.token_program_id); - let mut token_ids = BTreeSet::new(); - for id in &request.configured_token_ids { - if let Ok(id) = account_id_from_hex(id, "configured token id") { - token_ids.insert(id); - } - } - for id in request - .recent_token_ids - .iter() - .chain(&request.resolved_token_ids) - { - if let Ok(id) = parse_base58_id(id, "token id") { - token_ids.insert(id); - } - } - token_ids.extend(holdings.into_iter().map(|holding| holding.definition_id)); - - Ok(json!({ - "status": "ok", - "tokenIds": token_ids.into_iter().map(account_id_hex).collect::>(), - })) -} - -fn manifest_error(code: &str) -> Value { - json!({ "status": "error", "code": code, "tokenIds": [] }) -} - -pub(super) fn context(request: ContextRequest) -> Result { - let amm_program = parse_program_id(&request.amm_program_id)?; - let Ok(config) = load_config(amm_program, &request.config) else { - return Ok(context_error(&request, "config_unavailable")); - }; - - let holdings = wallet_holdings(&request.wallet_accounts, config.token_program_id); - let source_map = token_sources(&request, &holdings); - let mut rows = Vec::new(); - let mut warnings = Vec::new(); - - for (token_id, sources) in source_map { - let read = request - .token_definitions - .iter() - .find(|read| account_id_from_hex(&read.id, "token definition id") == Ok(token_id)); - let (name, total_supply, metadata_id) = - match fungible_definition(read, token_id, config.token_program_id) { - Ok(definition) => definition, - Err(error) => { - rows.push(unavailable_token_row(token_id, sources, error.code)); - if error.warn { - warnings.push(issue( - error.code, - "Token definition could not be read.", - &[], - json!({ "tokenId": token_id.to_string() }), - )); - } - continue; - } - }; - - let selected = select_holding(&holdings, token_id); - let mut row = json!({ - "definitionId": token_id.to_string(), - "name": name, - "metadataId": metadata_id.map(|id| id.to_string()), - "totalSupplyRaw": total_supply.to_string(), - "ownerProgramId": program_id_base58(config.token_program_id), - "public": true, - "fungible": true, - "selectable": true, - "status": "available", - "code": "available", - "sources": sources, - }); - if let Some(selected) = selected { - row["holdingId"] = json!(selected.id.to_string()); - row["balanceRaw"] = json!(selected.balance.to_string()); - } - rows.push(row); - } - - rows.sort_by(|left, right| { - let left_holding = left.get("holdingId").is_some(); - let right_holding = right.get("holdingId").is_some(); - right_holding.cmp(&left_holding).then_with(|| { - left["definitionId"] - .as_str() - .cmp(&right["definitionId"].as_str()) - }) - }); - - Ok(json!({ - "status": if request.wallet_available { "ready" } else { "no_wallet" }, - "networkId": request.network_id, - "networkFingerprint": request.network_fingerprint, - "walletAvailable": request.wallet_available, - "minimumLiquidityRaw": MINIMUM_LIQUIDITY.to_string(), - "programIds": { - "amm": program_id_base58(amm_program), - "token": program_id_base58(config.token_program_id), - "twapOracle": program_id_base58(config.twap_oracle_program_id), - }, - "tokens": rows, - "feeTiers": fee_tiers(), - "warnings": warnings, - })) -} - -/// Resolves an explicit, app-provided set of token ids into selector rows — the lean, -/// stateless successor to `context`. The app owns the id set (its configured tokens plus any -/// custom/pasted ids it remembers), so there is no network envelope and no process-cached wallet -/// state here: the module reads the definitions + wallet fresh and passes them in, exactly like -/// `context` did per token. +/// Resolves an explicit, app-provided set of token ids into selector rows. The app owns the +/// id set (its configured tokens plus any custom/pasted ids it remembers), so there is no +/// network envelope, no status, and no process-cached wallet state here: the module reads the +/// definitions + wallet fresh and passes them in. /// /// `token_ids` are hex (the module normalizes base58→hex at the boundary); `token_definitions` /// are the corresponding read accounts, keyed by hex id. Every returned row has the same shape — @@ -154,7 +31,7 @@ pub(super) fn resolve_tokens(request: ResolveTokensRequest) -> Result Result Result Value { - json!({ - "status": "error", - "code": code, - "networkId": request.network_id, - "networkFingerprint": request.network_fingerprint, - "walletAvailable": request.wallet_available, - "tokens": [], - "feeTiers": fee_tiers(), - "warnings": [], - }) -} - -fn fee_tiers() -> Value { - json!([ - { "feeBps": FEE_TIER_BPS_1, "label": "0.01%", "enabled": true }, - { "feeBps": FEE_TIER_BPS_5, "label": "0.05%", "enabled": true }, - { "feeBps": FEE_TIER_BPS_30, "label": "0.30%", "enabled": true }, - { "feeBps": FEE_TIER_BPS_100, "label": "1.00%", "enabled": true }, - ]) -} - -fn token_sources( - request: &ContextRequest, - holdings: &[SelectedHolding], -) -> BTreeMap> { - let mut sources: BTreeMap> = BTreeMap::new(); - for id in &request.configured_token_ids { - if let Ok(id) = account_id_from_hex(id, "configured token id") { - sources - .entry(id) - .or_default() - .insert(String::from("config")); - } - } - for (ids, source) in [ - (&request.recent_token_ids, "recent"), - (&request.resolved_token_ids, "resolved"), - ] { - for id in ids { - if let Ok(id) = parse_base58_id(id, "token id") { - sources.entry(id).or_default().insert(String::from(source)); - } - } - } - for holding in holdings { - sources - .entry(holding.definition_id) - .or_default() - .insert(String::from("holding")); - } - sources - .into_iter() - .map(|(id, values)| (id, values.into_iter().collect())) - .collect() -} - -fn unavailable_token_row(token_id: AccountId, sources: Vec, code: &str) -> Value { - json!({ - "definitionId": token_id.to_string(), - "name": "", - "metadataId": Value::Null, - "totalSupplyRaw": "0", - "selectable": false, - "status": code, - "code": code, - "sources": sources, - }) -} - -pub(super) struct DefinitionError { - pub(super) code: &'static str, - pub(super) warn: bool, -} - -pub(super) fn fungible_definition( +/// Decodes a token definition read as a fungible `(name, total_supply)`, or `None` when it is +/// unreadable, owned by a different program, its id mismatches, or it isn't fungible. +fn fungible_definition( read: Option<&AccountRead>, token_id: AccountId, token_program: ProgramId, -) -> Result<(String, u128, Option), DefinitionError> { - let Some(read) = read else { - return Err(DefinitionError { - code: "token_definition_unreadable", - warn: true, - }); - }; - let Ok((id, account)) = decode_account(read) else { - return Err(DefinitionError { - code: "token_definition_unreadable", - warn: true, - }); - }; - if id != token_id { - return Err(DefinitionError { - code: "token_definition_unreadable", - warn: false, - }); - } - if account.program_owner != token_program { - return Err(DefinitionError { - code: "token_program_mismatch", - warn: false, - }); +) -> Option<(String, u128)> { + let (id, account) = decode_account(read?).ok()?; + if id != token_id || account.program_owner != token_program { + return None; } match TokenDefinition::try_from(&account.data) { Ok(TokenDefinition::Fungible { - name, - total_supply, - metadata_id, - .. - }) => Ok((name, total_supply, metadata_id)), - _ => Err(DefinitionError { - code: "token_not_fungible", - warn: false, - }), + name, total_supply, .. + }) => Some((name, total_supply)), + _ => None, } } diff --git a/modules/amm/ffi/src/api/mod.rs b/modules/amm/ffi/src/api/mod.rs index f54fb95..bc83e0d 100644 --- a/modules/amm/ffi/src/api/mod.rs +++ b/modules/amm/ffi/src/api/mod.rs @@ -7,7 +7,6 @@ mod holding; mod liquidity; mod pair; mod quote; -mod quote_error; mod request; mod swap; mod token_holdings; @@ -18,12 +17,11 @@ mod tests; use std::{error::Error, fmt}; pub use request::{ - AddLiquidityPlanRequest, AddLiquidityQuoteRequest, ConfigIdRequest, ContextRequest, - CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, - ProgramIdRequest, RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest, + AddLiquidityPlanRequest, AddLiquidityQuoteRequest, ConfigIdRequest, CreatePoolPlanRequest, + CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest, + RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest, SyncReservesPlanRequest, TokenHoldingsRequest, - TokenIdsRequest, }; use serde_json::Value; @@ -68,22 +66,12 @@ pub fn config_id(request: ConfigIdRequest) -> AmmResult { config::config_id(request).map_err(Into::into) } -/// Discovers token definition IDs available to the active wallet and app. -pub fn token_ids(request: TokenIdsRequest) -> AmmResult { - context::token_ids(request).map_err(Into::into) -} - /// Derives canonical accounts for one token pair. pub fn pair_ids(request: PairIdsRequest) -> AmmResult { pair::pair_ids(request).map_err(Into::into) } -/// Builds network, token, holding, and fee-tier context. -pub fn context(request: ContextRequest) -> AmmResult { - context::context(request).map_err(Into::into) -} - -/// Resolves an app-provided set of token ids into selector rows (lean successor to `context`). +/// Resolves an app-provided set of token ids into liquidity selector rows. pub fn resolve_tokens(request: ResolveTokensRequest) -> AmmResult { context::resolve_tokens(request).map_err(Into::into) } diff --git a/modules/amm/ffi/src/api/quote_error.rs b/modules/amm/ffi/src/api/quote_error.rs deleted file mode 100644 index 435f51f..0000000 --- a/modules/amm/ffi/src/api/quote_error.rs +++ /dev/null @@ -1,11 +0,0 @@ -use serde_json::{json, Value}; - -pub(super) fn issue(code: &str, message: &str, fields: &[&str], details: Value) -> Value { - json!({ - "code": code, - "message": message, - "details": details, - "recoverable": true, - "blockingFields": fields, - }) -} diff --git a/modules/amm/ffi/src/api/request.rs b/modules/amm/ffi/src/api/request.rs index ebedbb6..9a590ad 100644 --- a/modules/amm/ffi/src/api/request.rs +++ b/modules/amm/ffi/src/api/request.rs @@ -8,45 +8,9 @@ pub struct ConfigIdRequest { pub amm_program_id: String, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct TokenIdsRequest { - pub amm_program_id: String, - pub config: AccountRead, - #[serde(default)] - pub wallet_accounts: Vec, - #[serde(default)] - pub configured_token_ids: Vec, - #[serde(default)] - pub recent_token_ids: Vec, - #[serde(default)] - pub resolved_token_ids: Vec, -} - -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ContextRequest { - pub network_id: String, - pub network_fingerprint: String, - pub amm_program_id: String, - pub wallet_available: bool, - pub config: AccountRead, - #[serde(default)] - pub wallet_accounts: Vec, - #[serde(default)] - pub token_definitions: Vec, - #[serde(default)] - pub configured_token_ids: Vec, - #[serde(default)] - pub recent_token_ids: Vec, - #[serde(default)] - pub resolved_token_ids: Vec, -} - -/// Resolves an app-provided set of token ids into selector rows (the lean successor to -/// `ContextRequest`). `token_ids` are hex — the module normalizes base58→hex and reads each -/// definition into `token_definitions` (keyed by hex id) plus the wallet accounts; the FFI is -/// stateless and reads nothing itself. +/// Resolves an app-provided set of token ids into selector rows. `token_ids` are hex — the +/// module normalizes base58→hex and reads each definition into `token_definitions` (keyed by +/// hex id) plus the wallet accounts; the FFI is stateless and reads nothing itself. #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ResolveTokensRequest { diff --git a/modules/amm/ffi/src/api/tests.rs b/modules/amm/ffi/src/api/tests.rs index 09e728b..7410c84 100644 --- a/modules/amm/ffi/src/api/tests.rs +++ b/modules/amm/ffi/src/api/tests.rs @@ -14,13 +14,12 @@ use token_core::{TokenDefinition, TokenHolding}; use twap_oracle_core::compute_current_tick_account_pda; use super::{ - context::{context, resolve_tokens, token_ids}, + context::resolve_tokens, holding::{select_holding, SelectedHolding}, pair::{is_canonical_pair, pair_ids, PairIds}, quote::{div_ceil_u256, minimum_opening_pair, Q64}, swap::{swap_exact_in_plan, swap_exact_out_plan}, - ContextRequest, PairIdsRequest, ResolveTokensRequest, SwapExactInPlanRequest, - SwapExactOutPlanRequest, TokenIdsRequest, + PairIdsRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactOutPlanRequest, }; use crate::{ account::{account_id_hex, account_read, decode_account, program_id_bytes}, @@ -190,107 +189,6 @@ fn pair_manifest_reports_unavailable_config_as_domain_error() { assert_eq!(result["code"], "config_unavailable"); } -#[test] -fn token_manifest_includes_compatible_wallet_holdings() { - let config_id = compute_config_pda(AMM_PROGRAM); - let configured = AccountId::new([1; 32]); - let held = AccountId::new([2; 32]); - let recent = AccountId::new([3; 32]); - let resolved = AccountId::new([4; 32]); - - let value = token_ids(TokenIdsRequest { - amm_program_id: amm_program_id(), - config: account_read(config_id, &config_account()), - wallet_accounts: vec![account_read( - AccountId::new([5; 32]), - &token_holding(held, 9), - )], - configured_token_ids: vec![account_id_hex(configured)], - recent_token_ids: vec![recent.to_string()], - resolved_token_ids: vec![resolved.to_string()], - }) - .unwrap(); - - assert_eq!( - value["tokenIds"], - json!([ - account_id_hex(configured), - account_id_hex(held), - account_id_hex(recent), - account_id_hex(resolved), - ]) - ); -} - -#[test] -fn wrong_program_holdings_do_not_contribute_token_candidates() { - let config_id = compute_config_pda(AMM_PROGRAM); - let config = config_account(); - let definition = AccountId::new([2; 32]); - let wrong_owner_holding = account( - [99; 8], - Data::from(&TokenHolding::Fungible { - definition_id: definition, - balance: 9, - }), - ); - let wallet_accounts = vec![account_read(AccountId::new([3; 32]), &wrong_owner_holding)]; - - let manifest = token_ids(TokenIdsRequest { - amm_program_id: amm_program_id(), - config: account_read(config_id, &config), - wallet_accounts: wallet_accounts.clone(), - configured_token_ids: Vec::new(), - recent_token_ids: Vec::new(), - resolved_token_ids: Vec::new(), - }) - .unwrap(); - assert_eq!(manifest["tokenIds"], json!([])); - - let value = context(ContextRequest { - network_id: String::from("testnet"), - network_fingerprint: String::from("block10:abc"), - amm_program_id: amm_program_id(), - wallet_available: true, - config: account_read(config_id, &config), - wallet_accounts, - token_definitions: vec![account_read( - definition, - &token_definition("Token", 1_000_000), - )], - configured_token_ids: Vec::new(), - recent_token_ids: Vec::new(), - resolved_token_ids: Vec::new(), - }) - .unwrap(); - assert_eq!(value["tokens"], json!([])); -} - -#[test] -fn context_selects_tokens_without_holdings() { - let token_id = AccountId::new([3; 32]); - let config_id = compute_config_pda(AMM_PROGRAM); - let value = context(ContextRequest { - network_id: String::from("testnet"), - network_fingerprint: String::from("block10:abc"), - amm_program_id: amm_program_id(), - wallet_available: true, - config: account_read(config_id, &config_account()), - wallet_accounts: Vec::new(), - token_definitions: vec![account_read( - token_id, - &token_definition("Token", 1_000_000), - )], - configured_token_ids: vec![account_id_hex(token_id)], - recent_token_ids: Vec::new(), - resolved_token_ids: Vec::new(), - }) - .unwrap(); - assert_eq!(value["tokens"][0]["selectable"], true); - assert_eq!(value["tokens"][0]["sources"], json!(["config"])); - assert!(value["tokens"][0].get("holdingId").is_none()); -} - #[test] fn resolve_tokens_returns_lean_rows_held_first_and_omits_unresolvable() { let held = AccountId::new([2; 32]); diff --git a/modules/amm/ffi/src/ffi.rs b/modules/amm/ffi/src/ffi.rs index 46635a1..8bb9abc 100644 --- a/modules/amm/ffi/src/ffi.rs +++ b/modules/amm/ffi/src/ffi.rs @@ -7,11 +7,11 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::api::{ self, AddLiquidityPlanRequest, AddLiquidityQuoteRequest, AmmApiError, AmmResult, - ConfigIdRequest, ContextRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, - FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest, RemoveLiquidityPlanRequest, + ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest, + PairIdsRequest, PoolIdRequest, ProgramIdRequest, RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest, - SyncReservesPlanRequest, TokenHoldingsRequest, TokenIdsRequest, + SyncReservesPlanRequest, TokenHoldingsRequest, }; #[derive(Serialize)] @@ -85,21 +85,11 @@ pub extern "C" fn amm_config_id(request_json: *const c_char) -> *mut c_char { call::(request_json, api::config_id) } -#[unsafe(no_mangle)] -pub extern "C" fn amm_token_ids(request_json: *const c_char) -> *mut c_char { - call::(request_json, api::token_ids) -} - #[unsafe(no_mangle)] pub extern "C" fn amm_pair_ids(request_json: *const c_char) -> *mut c_char { call::(request_json, api::pair_ids) } -#[unsafe(no_mangle)] -pub extern "C" fn amm_context(request_json: *const c_char) -> *mut c_char { - call::(request_json, api::context) -} - #[unsafe(no_mangle)] pub extern "C" fn amm_resolve_tokens(request_json: *const c_char) -> *mut c_char { call::(request_json, api::resolve_tokens) diff --git a/modules/amm/ffi/src/lib.rs b/modules/amm/ffi/src/lib.rs index 10ffad9..fba637f 100644 --- a/modules/amm/ffi/src/lib.rs +++ b/modules/amm/ffi/src/lib.rs @@ -6,12 +6,11 @@ mod ffi; pub mod api; pub use api::{ - config_id, context, create_pool_plan, create_pool_quote, fee_tiers, pair_ids, pool_id, - program_id, resolve_pool, resolve_tokens, swap_exact_in_plan, swap_exact_in_quote, - swap_exact_out_plan, swap_exact_out_quote, swap_pair, token_ids, AccountRead, AmmApiError, - AmmResponse, AmmResult, ConfigIdRequest, ContextRequest, CreatePoolPlanRequest, - CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest, - ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest, - SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest, TokenIdsRequest, - WalletAccount, + config_id, create_pool_plan, create_pool_quote, fee_tiers, pair_ids, pool_id, program_id, + resolve_pool, resolve_tokens, swap_exact_in_plan, swap_exact_in_quote, swap_exact_out_plan, + swap_exact_out_quote, swap_pair, AccountRead, AmmApiError, AmmResponse, AmmResult, + ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest, + PairIdsRequest, PoolIdRequest, ProgramIdRequest, ResolvePoolRequest, ResolveTokensRequest, + SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest, + SwapExactOutQuoteRequest, SwapPairRequest, WalletAccount, }; diff --git a/modules/amm/src/amm_module_impl.cpp b/modules/amm/src/amm_module_impl.cpp index 2ccbe55..28e7cfb 100644 --- a/modules/amm/src/amm_module_impl.cpp +++ b/modules/amm/src/amm_module_impl.cpp @@ -237,32 +237,6 @@ json publicError(const std::string& code, }; } -json contextState(const std::string& status, - const std::string& network_id, - const std::string& network_fingerprint, - const std::string& code = {}) { - json state = { - {"status", status}, - {"networkId", network_id}, - {"networkFingerprint", network_fingerprint}, - {"tokens", json::array()}, - {"feeTiers", json::array()}, - {"warnings", json::array()}, - }; - if (!code.empty()) state["code"] = code; - return state; -} - -// A json array of the strings at `obj[key]` (empty array when absent/wrong type). -json stringArray(const json& obj, const char* key) { - const auto it = obj.find(key); - if (it == obj.end() || !it->is_array()) return json::array(); - json out = json::array(); - for (const auto& v : *it) - if (v.is_string()) out.push_back(v); - return out; -} - } // namespace std::vector AmmModuleImpl::loadAmmElf() { @@ -288,37 +262,6 @@ std::string AmmModuleImpl::ammProgramId() { return jStr(r.value, "programId"); } -AmmModuleImpl::Network AmmModuleImpl::network() { - // AMM_PROGRAM_BIN / TOKENS_CONFIG are fixed for the process lifetime and this - // runs on the hot reply path, so resolve the program id + token ids once. - if (!networkResolved) { - const std::string id = ammProgramId(); - if (id.empty()) { - // Not resolvable yet (AMM_PROGRAM_BIN unset/unreadable). Don't cache a - // transient miss — a later call retries. - Network net; - net.status = "config_missing"; - return net; - } - programId = id; - tokenIds.clear(); - for (const auto& token : tokenList()) { - const std::string token_id = jStr(token, "definitionId"); - if (!token_id.empty()) tokenIds.push_back(token_id); - } - networkResolved = true; - } - - Network net; - net.amm_program_id = programId; - // The program id changes per deployment, so it doubles as the network - // fingerprint (a quote can't be replayed against a different program). - net.fingerprint = programId; - net.token_ids = tokenIds; - net.status = "ready"; - return net; -} - std::string AmmModuleImpl::normalizeAccountId(const std::string& id) { size_t start = 0; size_t end = id.size(); @@ -369,21 +312,15 @@ nlohmann::json AmmModuleImpl::readPublicAccount(const std::string& account_id) { return result; } -nlohmann::json AmmModuleImpl::walletAccountReads(bool wallet_open, bool refresh) { - if (!wallet_open) { - walletAccounts = json(); // invalidate — nothing to read while closed - return json::array(); - } - // Each readPublicAccount is a live sequencer round-trip, so serve the cached - // set unless the caller forces a reload (submit / explicit UI refresh). - if (!refresh && !walletAccounts.is_null()) - return walletAccounts; +nlohmann::json AmmModuleImpl::walletAccountReads(bool wallet_open) { + if (!wallet_open) + return json::array(); // nothing to read while closed // Normalize the wallet module's [any] return (a vector or a json array) // through json so we can iterate/type-check it uniformly. json accounts = modules().logos_execution_zone.list_accounts(); if (!accounts.is_array()) - return json::array(); // transient — don't cache + return json::array(); json out = json::array(); for (const auto& entry : accounts) { @@ -394,13 +331,12 @@ nlohmann::json AmmModuleImpl::walletAccountReads(bool wallet_open, bool refresh) if (id.empty()) continue; out.push_back(readPublicAccount(id)); } - walletAccounts = out; return out; } -nlohmann::json AmmModuleImpl::readConfig(const Network& net) { +nlohmann::json AmmModuleImpl::readConfig(const std::string& amm_program_id) { const FfiResult configResult = - call(amm_config_id, json{{"ammProgramId", net.amm_program_id}}); + call(amm_config_id, json{{"ammProgramId", amm_program_id}}); if (!configResult.ok) return json(); // null: config_id op failed return readPublicAccount(jStr(configResult.value, "configId")); } @@ -416,12 +352,12 @@ LogosMap AmmModuleImpl::resolvePool(const std::string& def_a_hex, return LogosMap{{"exists", false}, {"error", error}}; }; - const Network net = network(); - if (net.status != "ready") - // config_missing == no program id from AMM_PROGRAM_BIN (unset/unreadable/bad). + const std::string amm_program_id = ammProgramId(); + if (amm_program_id.empty()) + // no program id from AMM_PROGRAM_BIN (unset/unreadable/bad). return failed("no_program_bin"); - const json config = readConfig(net); + const json config = readConfig(amm_program_id); if (config.is_null()) return failed("bad_config"); // amm_config_id op failed (malformed program id) @@ -431,7 +367,7 @@ LogosMap AmmModuleImpl::resolvePool(const std::string& def_a_hex, const std::string token_b = normalizeAccountId(def_b_hex); const FfiResult pairResult = call(amm_swap_pair, json{ - {"ammProgramId", net.amm_program_id}, + {"ammProgramId", amm_program_id}, {"tokenInId", token_a}, {"tokenOutId", token_b}, {"config", config}, @@ -584,13 +520,13 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex, return {}; } - const Network net = network(); - if (net.status != "ready") { - AMM_TRACE("swapExactInput: FAIL network not ready (" << net.status << ")"); + const std::string amm_program_id = ammProgramId(); + if (amm_program_id.empty()) { + AMM_TRACE("swapExactInput: FAIL no program id (AMM_PROGRAM_BIN unset/unreadable)"); return {}; } - const json config = readConfig(net); + const json config = readConfig(amm_program_id); if (config.is_null()) { AMM_TRACE("swapExactInput: FAIL config_id op failed"); return {}; @@ -599,7 +535,7 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex, // Read the pool so the plan can use its stored vault ids (the guest asserts // the vaults in the pool's creation order — see amm_swap_exact_in_plan). const FfiResult poolId = call(amm_pool_id, json{ - {"ammProgramId", net.amm_program_id}, + {"ammProgramId", amm_program_id}, {"tokenInId", def_a_hex}, {"tokenOutId", def_b_hex}, }); @@ -613,7 +549,7 @@ std::string AmmModuleImpl::swapExactInput(const std::string& def_a_hex, // amm_swap_exact_in_plan resolves the pool accounts, encodes SwapExactInput, // and returns a ready-to-submit plan. const FfiResult planResult = call(amm_swap_exact_in_plan, json{ - {"ammProgramId", net.amm_program_id}, + {"ammProgramId", amm_program_id}, {"tokenInId", def_a_hex}, {"tokenOutId", def_b_hex}, {"config", config}, @@ -667,13 +603,13 @@ std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex, return {}; } - const Network net = network(); - if (net.status != "ready") { - AMM_TRACE("swapExactOutput: FAIL network not ready (" << net.status << ")"); + const std::string amm_program_id = ammProgramId(); + if (amm_program_id.empty()) { + AMM_TRACE("swapExactOutput: FAIL no program id (AMM_PROGRAM_BIN unset/unreadable)"); return {}; } - const json config = readConfig(net); + const json config = readConfig(amm_program_id); if (config.is_null()) { AMM_TRACE("swapExactOutput: FAIL config_id op failed"); return {}; @@ -682,7 +618,7 @@ std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex, // Read the pool so the plan can use its stored vault ids (the guest asserts // the vaults in the pool's creation order — see amm_swap_exact_out_plan). const FfiResult poolId = call(amm_pool_id, json{ - {"ammProgramId", net.amm_program_id}, + {"ammProgramId", amm_program_id}, {"tokenInId", def_a_hex}, {"tokenOutId", def_b_hex}, }); @@ -696,7 +632,7 @@ std::string AmmModuleImpl::swapExactOutput(const std::string& def_a_hex, // amm_swap_exact_out_plan resolves the pool accounts, encodes SwapExactOutput, // and returns a ready-to-submit plan. const FfiResult planResult = call(amm_swap_exact_out_plan, json{ - {"ammProgramId", net.amm_program_id}, + {"ammProgramId", amm_program_id}, {"tokenInId", def_a_hex}, {"tokenOutId", def_b_hex}, {"config", config}, @@ -739,7 +675,7 @@ LogosMap AmmModuleImpl::createPoolQuote(const LogosMap& request) { }; // Pure preview — no program id / chain reads / fee. Normalize the pair to hex (the - // liquidity UI still sources base58 ids from newPositionContext; transitional). + // liquidity UI sources base58 ids from resolveTokens). const std::string token_a = normalizeAccountId(jStr(request, "tokenAId")); const std::string token_b = normalizeAccountId(jStr(request, "tokenBId")); if (token_a.empty() || token_b.empty()) @@ -1297,7 +1233,7 @@ LogosList AmmModuleImpl::tokenHoldings(bool wallet_open) { const json config = readPublicAccount(jStr(configResult.value, "configId")); // Fresh wallet read each call — the selector wants current holdings/balances. - const json wallet_accounts = walletAccountReads(wallet_open, /*refresh=*/true); + const json wallet_accounts = walletAccountReads(wallet_open); const FfiResult result = call(amm_token_holdings, json{ {"ammProgramId", amm_program_id}, @@ -1365,7 +1301,7 @@ LogosList AmmModuleImpl::resolveTokens(const LogosMap& request, bool wallet_open } // Fresh wallet read — the selector wants current holdings/balances. - const json wallet_accounts = walletAccountReads(wallet_open, /*refresh=*/true); + const json wallet_accounts = walletAccountReads(wallet_open); const FfiResult result = call(amm_resolve_tokens, json{ {"ammProgramId", amm_program_id}, @@ -1384,60 +1320,3 @@ LogosList AmmModuleImpl::resolveTokens(const LogosMap& request, bool wallet_open return out; } -LogosMap AmmModuleImpl::newPositionContext(const LogosMap& request, - bool wallet_open, - bool refresh_wallet_accounts) { - const Network net = network(); - if (net.status != "ready") - return contextState(net.status, net.id, net.fingerprint); - - const json walletAccounts = walletAccountReads(wallet_open, refresh_wallet_accounts); - - const FfiResult configResult = - call(amm_config_id, json{{"ammProgramId", net.amm_program_id}}); - if (!configResult.ok) - return contextState("error", net.id, net.fingerprint, "backend_error"); - const json config = readPublicAccount(jStr(configResult.value, "configId")); - - json configured = json::array(); - for (const auto& id : net.token_ids) configured.push_back(id); - const json recent = stringArray(request, "recentTokenIds"); - const json resolved = stringArray(request, "resolvedTokenIds"); - - const FfiResult tokenResult = call(amm_token_ids, json{ - {"ammProgramId", net.amm_program_id}, - {"config", config}, - {"walletAccounts", walletAccounts}, - {"configuredTokenIds", configured}, - {"recentTokenIds", recent}, - {"resolvedTokenIds", resolved}, - }); - const json tokenManifest = tokenResult.value; - if (!tokenResult.ok || jStr(tokenManifest, "status") != "ok") { - const std::string code = - tokenResult.ok ? jStr(tokenManifest, "code") : std::string("backend_error"); - return contextState("error", net.id, net.fingerprint, - code.empty() ? "backend_error" : code); - } - - json definitions = json::array(); - for (const auto& id : tokenManifest.value("tokenIds", json::array())) - if (id.is_string()) definitions.push_back(readPublicAccount(id.get())); - - const FfiResult contextResult = call(amm_context, json{ - {"networkId", net.id}, - {"networkFingerprint", net.fingerprint}, - {"ammProgramId", net.amm_program_id}, - {"walletAvailable", wallet_open}, - {"config", config}, - {"walletAccounts", walletAccounts}, - {"tokenDefinitions", definitions}, - {"configuredTokenIds", configured}, - {"recentTokenIds", recent}, - {"resolvedTokenIds", resolved}, - }); - return contextResult.ok - ? contextResult.value - : contextState("error", net.id, net.fingerprint, "backend_error"); -} - diff --git a/modules/amm/src/amm_module_impl.h b/modules/amm/src/amm_module_impl.h index bec1492..0464b5e 100644 --- a/modules/amm/src/amm_module_impl.h +++ b/modules/amm/src/amm_module_impl.h @@ -231,33 +231,7 @@ public: /// universal-module glue only supports map/scalar inputs.) LogosList resolveTokens(const LogosMap& request, bool wallet_open); - /// New-position (add-liquidity) view state: reads the AMM config + the - /// user's wallet accounts and returns the new-position context map the - /// UI renders (available tokens, fee tiers, warnings). `wallet_open` gates - /// whether wallet accounts are included; `refresh_wallet_accounts` forces a - /// fresh read rather than a cached one. - LogosMap newPositionContext(const LogosMap& request, - bool wallet_open, - bool refresh_wallet_accounts); - private: - // Off-chain "network" context, derived from the process env (the same - // sources the app backend used): AMM deployment id from AMM_PROGRAM_BIN, - // configured token set from TOKENS_CONFIG. `status` is "ready" once the - // program id resolves, else "config_missing". - struct Network { - std::string id = "lez"; - std::string status; - std::string fingerprint; // == amm_program_id (binds a quote to the deploy) - std::string amm_program_id; // 64-char lowercase hex - std::vector token_ids; - }; - // AMM_PROGRAM_BIN / TOKENS_CONFIG are fixed for the process lifetime, and - // this runs on the hot reply path (every op), so it resolves the program id - // + token ids ONCE and caches them (networkResolved). Cached only on - // success, so a startup miss (bin not readable yet) retries. - Network network(); - // 64-char lowercase-hex AMM program id via the amm_ffi `program_id` op // over the AMM_PROGRAM_BIN bytes (empty if unset/unreadable/bad). std::string ammProgramId(); @@ -272,7 +246,7 @@ private: // Derives the config account id (amm_config_id) and reads it, returning the // account-read shape the amm_ffi ops embed. Null json when the config_id // op itself fails (readPublicAccount always yields at least {id,status}). - nlohmann::json readConfig(const Network& net); + nlohmann::json readConfig(const std::string& amm_program_id); // Reads a public account through the wallet module and returns the // { id, status, account:{ program_owner, balance, nonce, data } } shape the @@ -280,21 +254,7 @@ private: // omitted when the read has no data (uninitialized/nonexistent). nlohmann::json readPublicAccount(const std::string& account_id); - // The user's own public account reads (empty when the wallet is closed). - // Cached across calls (walletAccounts); `refresh` reloads instead of serving - // the cache — quote reuses it, submit forces fresh — since each read is a - // live sequencer round-trip. - nlohmann::json walletAccountReads(bool wallet_open, bool refresh); - - // Process-lifetime network config, resolved once (see network()). Serialized - // module dispatch means no locking is needed; there is no invalidation, as - // runtime env reload is not supported. - bool networkResolved = false; - std::string programId; - std::vector tokenIds; - - // Cache of the user's public account reads for the context/quote path (each - // read is a live sequencer round-trip). Null until first read; `refresh` - // reloads it, and it's dropped when the wallet closes. See walletAccountReads. - nlohmann::json walletAccounts; + // The user's own public account reads, fresh each call (empty when the wallet + // is closed). Each read is a live sequencer round-trip. + nlohmann::json walletAccountReads(bool wallet_open); };