Wire the Buy direction to submit: AmmUiBackend gains a swapExactOutput slot
(guarded like swapExactInput), SwapCard.executeSwap branches on direction —
sell -> swapExactInput(minReceivedRaw), buy -> swapExactOutput(maxInRaw) — and
canSubmit/submitButtonText/buildSnapshot handle both. The confirmation dialog
switches wording by mode ("You pay at most" / "You receive exactly" for exact
output). The Buy field is now digitsOnly since its value is submitted as a raw
base-units integer.
With both directions priced and oriented by the module, the client no longer
needs any pool math. Remove the reserve-orientation chain (sellIsPoolA,
buy/sellReserveNum, poolReserveA/B, poolDefAHex) — resolvePool now reports only
existence and fee — and the now-dead helpers (formatBaseUnits/formatAmountValue,
DummySwapState.amountInFor/minReceived/priceImpactPercent/maxSent). The
impossible-swap guard moves from a client reserve compare to the module's
output_exceeds_liquidity error, surfaced as "Insufficient liquidity".
Wire the Buy direction of the swap card to the module's server-side
swapExactOutQuote, mirroring the exact-input path. AmmUiBackend gains a
swapExactOutQuote(tokenIn, tokenOut, amountOutDecimal, slippageBps) slot
returning { requiredInRaw, maxInRaw, priceImpactBps } (read-only).
Editing the Buy amount now debounces a swapExactOutQuote call and sources the
required input (shown in the Sell field), the price impact, and the slippage
ceiling from it — the exact figures come straight from the quote's raw integer
strings, so the preview matches execution and no reserve orientation happens
client-side. A retyped amount invalidates the quote up front.
SwapSummary's last row is generalised from a hardcoded "Min received" to a
direction-aware bound (boundLabel/boundText): "Min received" (min out) for
exact input, "Maximum sent" (max in) for exact output. SwapConfirmationSummary
is updated for the renamed property.
The Buy field remains preview-only — canSubmit is still sell-only, pending the
exact-output submit wiring. The now-orphaned DummySwapState pricing helpers are
left for a follow-up cleanup.
Wire the Sell direction of the swap card to the module's server-side quote
instead of the client-side DummySwapState estimate. AmmUiBackend gains a
swapExactInQuote(tokenIn, tokenOut, amountInDecimal, slippageBps) slot that
forwards to amm_module and returns { expectedOutRaw, minReceivedRaw,
priceImpactBps } (read-only, no wallet guard).
SwapCard debounces a swapExactInQuote call as the user types a Sell amount and
sources the expected output, min received, and price impact from it. The exact
figures shown (buy field, confirmation snapshot) and the submitted min_out come
straight from the quote's raw integer strings, so the preview can't drift from
execution and the client no longer orients reserves (fixes the #236 defAHex
bug) or recomputes min_out in double/BigInt. A retyped amount invalidates the
quote immediately and blocks submit until the re-quote lands.
The Buy direction is unchanged — still a local DummySwapState preview, pending
the exact-output wiring. resolvePool stays as the source of pool existence, the
fee row, and the Buy-side reserves.
The add-liquidity flow stamped a `new-position.v1` schema tag on every
request and response and validated it across all three layers — the QML
plugin, the amm_module core module, and the amm_client Rust crate. It was a
cross-version compatibility guard, but these artifacts always ship together,
so the contract is honored implicitly, and the swap view already works fine
without one. Dropping it makes the liquidity view consistent with swap and
removes a layer of ceremony.
- amm_client: remove PositionRequest.schema and the unsupported_schema check
in compute_quote; drop QuoteCommitment.schema (changes quoteHash, which is
internal-only) and every "schema" response stamp; delete the SCHEMA /
NEW_POSITION_SCHEMA constants and the public export.
- amm_module: remove the SCHEMA constant and its four response stamps.
- AmmUiBackend: remove its local NEW_POSITION_SCHEMA and the stamps in
loadingContext() / newPositionError().
- QML: drop the "schema" fields from the request/envelope builders and relax
the validity gates to check status / canSubmit instead (the sole check in
NewPositionFlow now guards on a missing `status`); strip the now-dead
schema fields from the liquidity QML test fixtures.
- Also removes the last stale comment references to the deleted *Runtime
classes.
Introduce modules/amm — the AMM business logic as a universal core Logos module,
consumed identically by the QML UI (via modules().amm_module) and headlessly
(logoscore call amm_module ...). The module is a thin transport adapter: the
domain math lives in the Rust amm_client crate (the transport-independent JSON
FFI), and the module sequences those pure ops with chain I/O delegated to the
logos_execution_zone wallet module. It reaches the same shared wallet instance
the UI opened (Basecamp loads core modules as singletons; standalone the
LogosAPI client cache dedups the connection), so it never opens a second wallet.
The module owns the full AMM surface — not just swaps:
- resolvePool / swapExactInput / tokenList (the swap path)
- newPositionContext / quoteNewPosition / submitNewPosition (add-liquidity)
apps/amm: delete the app-side orchestration (SwapRuntime, NewPositionRuntime,
AmmClient/BundledAmmClient) and the amm_client link. AmmUiBackend now owns only
wallet-session lifecycle and forwards every AMM slot to modules().amm_module.
The one wallet-keyset mutation add-liquidity needs — creating a fresh LP holding
— stays in the backend (via its wallet provider, keeping the account model and
on-disk storage coherent): the module returns "requires_fresh_lp" without
submitting, the backend creates the account and resubmits with its id.
flake.nix / CMakeLists / metadata: the module links the amm_client crate; the UI
links no external lib and depends on amm_module (injected into the UI builder's
flakeInputs so the dependency resolves).
- amounts/deadline declared nlohmann::json so the generated dispatch accepts a
JSON number (bare small ints on the CLI) or a string (exact u128 from the UI,
or a quote-wrapped big value on the CLI); JSON floats are rejected rather than
submit a silently-rounded amount.
- AMM_DEBUG-gated tracing for the swap path.
- Drop tests/cpp/NewPositionRuntimeTest.cpp with the class it covered
(module-level tests to follow).
- modules/amm/README.md: architecture, headless prerequisites, logoscore recipe.
Keep wallet assets as token choices without automatically selecting a pair. Keep pool-probe amounts internal so active-pool quotes do not populate the form before user input.
Add a "Create Pool" flow to the AMM app that lets a user open a new
liquidity position — seeding a pool's initial liquidity — from token
selection and amount entry, through a confirmation dialog, to on-chain
submission.
- client crate (apps/amm/client): pure, testable protocol logic — account
decoding, pair/position modelling, and quote/plan computation — exposed to
the app over a C ABI (config/networks.json drives network selection).
- C++ runtime + backend: AmmClient, ActiveNetwork, and NewPositionRuntime,
wired into AmmUiBackend (new resolve/quote/submit slots).
- QML flow: NewPositionForm, NewPositionFlow state, NewPositionConfirmation-
Dialog, TokenSelectorModal, and reusable Amm* presentational components
(theme, surfaces, buttons) + AmountMath.js.
- tests: C++ (NewPositionRuntimeTest, ActiveNetworkTest) and QML
(tst_NewPositionForm, tst_LiquidityPage, tst_TokenAmountInput, …).
Turns the dummy-data AMM UI into a real client of the on-chain LEZ wallet.
Adds a hand-written ui_qml C++ backend (src/AmmUi*) over the core
logos_execution_zone module: create/open a local wallet, create and list
public/private accounts, and a navbar Connect / Connected + account-selector
+ Disconnect flow. Onboarding is password-only (no path picking) with a
per-app wallet at ~/.lee/amm-wallet (override: AMM_WALLET_HOME_DIR);
standalone gets its own wallet, Basecamp shares accounts via adopt-on-start.
Requires Nix with flakes; macOS also needs `sandbox = false` (the default).
The logos_execution_zone input is pinned to a module rev whose LEZ (lssa)
already includes the macOS Metal-build fix, so no `--override-input` is
needed — plain `nix run .` works:
cd apps/amm
nix run .
- create_new now returns the new wallet's BIP39 mnemonic (not an int status);
the app currently discards it, so the wallet can't yet be recovered. Surfacing
it in onboarding (+ restore_storage) is a follow-up.
- The wallet password is currently a no-op upstream (storage.rs: "TODO: use
password for storage encryption"); storage.json is plaintext. So Disconnect
is a UI-level lock and reconnect does not (cannot yet) re-prompt for it.
- wallet-ffi requires explicit config/storage paths; a *_default() FFI would
let the app drop its path handling.
- Bundled network config: connects to whatever WalletConfig::default() points
at; real testnet endpoints still TBD.