Commit Graph
78 Commits
Author SHA1 Message Date
r4bbit 5b6786ce08 test(apps/amm): add remove-liquidity e2e test
Drive the full remove-liquidity flow through the QML inspector: open the
wallet's seeded A/B position from the positions view, use the Manage
dropdown to open the remove sheet, withdraw 50% (the slider default),
submit, and verify the A/B pool reserves shrank on-chain.

Give the Manage trigger an objectName (poolDetailManageButton) so the
test can open the dropdown via its openMenu().
2026-08-25 10:35:25 +02:00
r4bbit 62d133e909 feat(amm): let LPs choose the LP-token destination account
Add-liquidity minted a fresh LP account every time, fragmenting a position
across holdings. The New position form now has an LP-destination selector (the
same Input-mode component as the token funding rows): add-liquidity preselects
the wallet's existing LP holding so deposits consolidate, while create-pool has
none and mints a fresh one.

- addLiquidityQuote returns lpDefinitionId (base58) so the form matches holdings
- createPool/addLiquidity submit into the chosen holding, else create-fresh
- e2e: add-liquidity waits for the preselect; create-pool asserts fresh-account
2026-08-21 15:35:27 +02:00
r4bbit 4cb7c7e51c refactor(amm): drop the Raw suffix from amount/value field names
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).
2026-08-21 08:11:58 +02:00
r4bbit 78edd23b5b feat(apps/amm): remove liquidity from the pool detail view
The module already had removeLiquidityQuote and removeLiquidity, but
AmmUiBackend never forwarded them, so QML had no way to reach them. Expose
both, mirroring the add pair: the quote is read-only and unguarded, the
submit is wallet-guarded and refreshes balances once the withdrawal lands.
Unlike createPool/addLiquidity nothing fresh is created -- the request names
the existing LP holding to burn and the two holdings that receive.

The pool detail view's secondary action reads "Add liquidity" until the
wallet holds LP for that pool, then becomes "Manage position" with a
dropdown offering both directions, opening on hover with the same deferred
close the nav bar's tab menu uses. Remove is disabled until the LP account
and both receiving holdings resolve.

RemoveLiquidityDialog is a modal sheet: 25/50/75/Max presets over a slider,
a debounced quote of both withdrawals, the post-slippage floors, submit. The
quote is generation-tagged because dragging the slider fires quotes faster
than they return and a late reply must not paint over a newer percentage.
The submit passes the quote's own minimumAmount*Raw as its floors, so it
enforces what the preview promised. 100% burns the balance exactly; every
other percentage floors, so rounding can't push the request past it.
2026-08-20 20:02:11 +02:00
r4bbit 99ea6805df feat(apps/amm): introduce positions view
This view allows the user to manage their positions.
2026-08-20 19:52:35 +02:00
r4bbit 06d7119a97 feat(apps/amm): add a pool detail view reached from the Pools list 2026-08-20 15:39:40 +02:00
r4bbit 4651f28a05 fix(apps/amm): one token list and one token picker for both views
The two views listed different tokens: swap rendered tokenList()
(TOKENS_CONFIG verbatim, no chain check) while liquidity rendered
resolveTokens(), which reads each id on-chain and silently drops any whose
definition isn't a readable fungible token owned by the configured
TokenProgram.
2026-08-20 15:21:47 +02:00
r4bbit 627fcfa4e2 fix(apps/amm): make the AMM UI load in Basecamp
Stage Logos.Wallet at the plugin root as pure QML (Basecamp rejects the
  prefer :/qt/qml/... resource), move NavBar into qml/chrome/ so it stops
  colliding in Basecamp's shared import path, and add a QML_IMPORT_PATH
  wrapper so standalone still resolves the root module. Adds amm-ui-lgx /
  amm-module-lgx aliases for addressable Basecamp installs.
2026-08-20 15:21:28 +02:00
Ricardo Guilherme Schmidt 470e06c6c3 feat(token-ui): integrate Basecamp token module
Add portable Basecamp packaging and an inspector-driven end-to-end flow.
2026-08-20 13:42:46 +02:00
Ricardo Guilherme Schmidt dc386dee17 feat(token-ui): connect Basecamp UI to token module 2026-08-20 13:42:46 +02:00
Ricardo Guilherme Schmidt 24b66f4c77 feat(token): add token definition app 2026-08-20 13:42:46 +02:00
Ricardo Guilherme Schmidt 741e72add9 feat(token): add Logos token API module 2026-08-20 12:12:57 +02:00
r4bbit 72a3e741a0 fix(apps/amm): match swap holdings on the configured id encoding
Moving the token list app-side (readTokensConfig) dropped the module
tokenList()'s base58->hex normalization, so a token's definitionId now
reaches the swap view in whatever encoding TOKENS_CONFIG uses — base58 in
practice. The swap account selector filtered holdings on the hex
definitionIdHex field using that value, so a base58 id matched nothing and
every token showed "No funds", blocking swaps.

Make the selector encoding-aware: a 64-char hex id filters definitionIdHex,
otherwise the base58 definitionId. tokenHoldings already emits both encodings
per holding, so this matches whichever the config uses, needs no app-side
base58 decoder, and mirrors how the liquidity view already filters. The swap
quote/submit path already normalizes base58->hex in the module, so nothing
else changes.
2026-08-19 15:52:06 +02:00
r4bbit 09f3d594a7 refactor(amm): move tokenList off the module; app reads TOKENS_CONFIG
Token discovery is an app concern, not module business — same rationale as
poolList reading AMM_POOLS_CONFIG. Drop tokenList() from amm_module and have the
app read the config itself.
2026-08-14 12:08:01 +02:00
r4bbit 7e45e44eac feat(amm): add oracle setup ops (createPriceObservations / createOraclePriceAccount)
Expose the two TWAP oracle-setup instructions as module ops so a pool's price feeds
can be seeded from the app. Both chain into the configured oracle, seeded from
validated pool state (initial tick read on-chain) — nothing is caller-priced, and
each window is a distinct feed account.
2026-08-13 22:43:48 +02:00
r4bbit ca8adfc4af feat(amm): add transferOwnership (UpdateConfig admin transfer)
Expose the authority-only UpdateConfig as a module op so the admin can transfer
AMM ownership. The guest change (UpdateConfig restricted to the current admin) is
already shipped; this is the module wrapper.
2026-08-13 21:32:42 +02:00
r4bbit 56c80ce29d feat(amm): add configAccount read (decode the singleton config)
Expose the AMM config account as a read op, so a future config/admin view can show
the authority and the token/twap program ids the AMM chains into.
2026-08-13 21:30:14 +02:00
r4bbit 92f55652a8 refactor(amm): enrich resolvePool into resolvePoolAccount
Return the pool's full derived state from one read instead of just existence +
reserves, so callers get the derived accounts (for future account views / oracle
setup) without re-deriving.

FFI resolve_pool: drop the `exists` boolean — the presence of data is the signal.
An existing pool returns { status:"ok", ..., poolId, defAHex, defBHex, vaultAId,
vaultBId, lpDefinitionId, reserveA, reserveB, liquiditySupply, feeBps }; a missing /
uninitialized pool is the { status:"error", error:"no_pool", poolId } error (still
carrying the derived poolId for address derivation).

Module: resolvePool -> resolvePoolAccount — { status:"error", error } envelope for
hard failures, and orient reserves + defs + vaults to the caller's requested order.

Backend + QML: rename the slot; SwapCard and NewPositionFlow switch the existence
check from pool.exists to pool.status === "ok" (reserve field names unchanged, so
no other consumer edits). no_pool still routes to create-pool; hard errors still
surface.
2026-08-13 16:55:45 +02:00
r4bbit cbb75c38fd 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.
2026-08-13 16:17:58 +02:00
r4bbit 10b52ea2f6 fix(apps/amm): repair addCustomToken — restore token resolution and closing brace
The Copilot Autofix squash reworked addCustomToken to persist the canonical
definitionId (and to fail when the store write fails), but it deleted the line
that resolves the token — `const QVariantList rows = resolveTokens(probe, …)` —
while still calling `rows.first()`, and it dropped the function's closing brace.
The result didn't compile: `rows` was undefined and customTokenStorePath parsed
as a nested definition ("function definition is not allowed here").

Restore the resolveTokens call and the `rows.isEmpty()` guard before `rows` is
used, and re-add the closing brace. The autofix's intent is preserved: resolve
the pasted id, keep the canonical definitionId, persist it, and surface a
backend_error if saveCustomTokenIds fails
2026-08-13 15:45:37 +02:00
r4bbit 7a7ebfdbaf feat(amm): source liquidity tokens app-side + add custom tokens by id
Move the liquidity token selector off the module's stateful newPositionContext
onto a lean, app-owned surface, and let users add unlisted tokens by id.

FFI: new stateless `resolve_tokens` op — the app passes an explicit id set and
gets uniform selector rows `{ definitionId (base58), name, totalSupply, holdingId,
balance }`, held tokens first, unresolvable/non-fungible ids omitted. Reuses the
per-token definition/holding logic from `context`, without the network/status
envelope. Unit-tested.

Module: `resolveTokens(request, wallet_open)` reads the definitions + wallet and
calls the op (ids wrapped in a map — the universal-module glue only marshals
map/scalar inputs, not bare lists).

Backend: the app owns the id set — configured tokens (TOKENS_CONFIG) plus the
user's persisted custom ids. Held-but-unlisted tokens are NOT auto-listed (the
list mirrors the swap side); a token you hold still shows its balance once listed.
`addCustomToken` validates a pasted id by resolving its on-chain definition, then
persists it to CUSTOM_TOKEN_CONFIG (defaulting to the per-user app-data store, with
a HOME fallback so persistence never silently no-ops on an empty path).

QML: NewPositionForm/LiquidityPage take tokens/walletReady/loadingTokens as inputs
and drive selection + custom-token resolution through the backend; dropped all
newPositionContext reads and the selectable/status/code row fields.

Tests: custom-token.mjs creates token D on-chain (left out of the token config)
and verifies pasting its id resolves, selects, and persists it across a reload.
The setup script mints token D and initializes/prints the isolated
CUSTOM_TOKEN_CONFIG store
2026-08-13 15:33:31 +02:00
r4bbit cb1b457ad3 feat(amm): expose supported fee tiers via feeTiers() op
The liquidity form's fee-tier selector was fed from the module's
newPositionContext, which hardcoded an empty list — leaving the selector
blank. Source the tiers from the program instead so the UI can never
drift from what the guest accepts.

Add amm_core::SUPPORTED_FEE_TIERS: the canonical ascending list of raw
bps ([1, 5, 30, 100]), built from the existing FEE_TIER_BPS_* constants.
is_supported_fee_tier's match is left unchanged and the new const is
unused on-chain, so the guest ImageID is unaffected; a drift-guard test
locks the list to the check (every entry accepted, neighbours rejected,
ascending/deduped).

Wire it through the stack:
- FFI: amm_fee_tiers op reading SUPPORTED_FEE_TIERS -> { feeTiers: [...] }
  (empty FeeTiersRequest, cbindgen header regenerated).
- Module: LogosList feeTiers() unwrapping the list, like tokenHoldings.
- Backend: QVariantList feeTiers() QtRO slot forwarding to the module.
- QML: LiquidityPage fetches backend.feeTiers() once (wallet-independent)
  and injects it into NewPositionForm, which wraps each int into a
  { feeBps } row for the existing delegate. Drop the now-dead feeTiers
  key from the flow's loadingContext().
2026-08-13 14:22:11 +02:00
Ricardo Guilherme Schmidt 4cfc03a815 feat(apps/amm): drive the Pools list from AMM_POOLS_CONFIG
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.
2026-08-13 14:13:31 +02:00
r4bbit c47f387ad4 refactor(amm): rename the create-pool quote surface for symmetry
Two naming cleanups on the create-pool quote, aligning it with the add / remove
counterparts (per modules/amm/INTERFACE.md). Pure renames — no behavior change.

- `liquidityQuote` → `createPoolQuote` across the stack: the FFI op
  (`liquidity_quote` → `create_pool_quote`, `LiquidityQuoteRequest` →
  `CreatePoolQuoteRequest`, `amm_liquidity_quote` → `amm_create_pool_quote`,
  cbindgen header regenerated), the module method, the AmmUiBackend slot, and the
  QML call site. It really is the create-pool quote — `addLiquidityQuote` /
  `removeLiquidityQuote` are the other branches — so the old name misled.
- `initialPriceRealRaw` → `priceRaw` (request field `initial_price_real_raw` →
  `price_raw`): drops the legacy "Real" and unifies the price key with the add /
  remove quotes, which already return `priceRaw`. Create, add, and remove quotes
  now all speak `priceRaw`; the create-vs-add routing in NewPositionFlow keys on
  `request.priceRaw`.
2026-08-13 13:25:20 +02:00
r4bbit 037e0a192c refactor(apps/amm): drop dead liquidity-form leftovers from the quote migration
Cleanup surfaced while auditing the lean-quote migration:
- Remove the `quote_changed` / `quote_not_submittable` error messages — relics of
  the dropped quoteHash/canSubmit commitment model, emitted by nothing now.
- Remove the `network_unknown` / `network_mismatch` messages — the `Network`
  model is gone, so they're never emitted.
- warningText: drop the dead `quotePayload.warnings` branch (lean quotes carry no
  warnings); keep the live token-sourcing-context warnings.
2026-08-13 13:25:20 +02:00
Ricardo Guilherme Schmidt 574d814f48 fix(amm): restore liquidity controls after refresh 2026-08-13 13:24:16 +02:00
r4bbit bf63070a9e fix(apps/amm): disable the already-selected token in the swap token picker
The token selector let you pick the same token on both sides of a swap.
That drove resolvePool into amm_client_pool_pda with def_a == def_b, which
hits `panic!("Definitions match")` in amm_core (a pool needs two distinct
tokens). Because that panic crosses the `#[no_mangle]` FFI boundary — which
can't unwind — it aborts, taking the whole UI process down.

Guard it at the source: the picker now disables (dims, no hover/click, tags
"Selected") whichever token is already chosen on the opposite side, so the
two sides can never match.

- TokenListItem: add a `disabled` state (opacity, inert MouseArea, tag)
- TokenSelectorModal: add `disabledDefinitionId`; gate both the popular-token
  pills and the list rows on it
- SwapPage: on open, set it to the opposite side's selected token
2026-08-13 13:03:58 +02:00
r4bbit 64e7614e74 refactor(amm): remove the dead newPosition quote path
Both liquidity branches now quote through the lean ops (liquidityQuote /
addLiquidityQuote), so quoteNewPosition and the heavy amm_quote machinery it
drove are unreachable. Remove them end to end.

FFI (modules/amm/ffi):
- Drop the amm_quote entry point and the whole quote-evaluation graph:
  api/{accounts,commitment,funding,position}.rs, the QuoteRequest /
  PositionRequest / PairSnapshot request types, quote_error::fatal_quote, and
  api/clock.rs (its decode_clock was quote-only). quote.rs keeps only the shared
  opening-deposit math (minimum_opening_pair + helpers) that liquidity_quote
  reuses.
- Trim the fields the quote path was the sole reader of: SelectedHolding.account
  and PairIds.{token_program,twap_program}.
- Drop the quote-path unit tests; keep the math / pair / context / holding /
  swap ones (37 pass, clippy clean).

Module (modules/amm/src):
- Remove AmmModuleImpl::quoteNewPosition and its buildQuoteInput snapshot helper.

App (apps/amm):
- Remove the AmmUiBackend quoteNewPosition slot (.rep/.h/.cpp) and the dead QML
  backend mock + obsolete fresh-quote test.
- finishSubmitFailure no longer keeps a submit-returned re-quote (the lean submit
  ops never return one); it always re-quotes on failure.
- submissionSnapshot drops the always-empty quoteHash and derives the confirm
  dialog's action from the resolved pool state instead of the dead
  quotePayload.instruction (restores the "Create pool" / "Add liquidity" label).
2026-08-11 17:51:51 +02:00
r4bbit eb98aac31f feat(amm): drive the create-pool liquidity preview from liquidityQuote
Both liquidity branches now quote through the lean composable ops. The create
path joins the add path (already on addLiquidityQuote) by reworking
liquidity_quote into a dual-mode create quote and wiring the form to it via
the resolvePool pool read. quoteNewPosition/amm_quote are no longer reached
from the UI.

FFI (modules/amm/ffi):
- liquidity_quote is dual-mode: price-only (initialPriceRealRaw, no amounts)
  returns the minimum opening deposit via minimum_opening_pair; supplied
  amounts return the actual deposit with the price derived from them. Emits
  actual/minimum amounts, expectedLp, lockedLp and the Q64.64 price.
- LiquidityQuoteRequest gains initial_price_real_raw (Option<String>, needed
  only in price-only mode).

Module (modules/amm/src):
- liquidityQuote forwards initialPriceRealRaw to the op.

UI (apps/amm/qml):
- Route create-vs-add on the pool read (resolvePool -> poolExists); create
  quotes via liquidityQuote, assembled into the missing-pool shape the form
  already consumes.
- poolStatus moves off the quote onto the flow's poolExists; the form derives
  activePool/missingPool from it. Trim the vestigial quote fields (canSubmit,
  requiresFreshLp, warnings, errors[], accountPreview, the "Pool" row) and drop
  the account-plan panel for parity with the swap view.
- Fix a real bug: a pair change now resets poolExists (resetPoolExistence) so
  resetPairDraft re-resolves the pool like a fresh selection. Otherwise an
  active pool kept stale (cleared) reserves with no re-quote, and the deposit
  ratio-fill silently no-op'd.

Tests (apps/amm/tests):
- Read activePool instead of the removed poolStatus. The add test waits for the
  reset's active-pool quote to settle (reserves reloaded) before the ratio-fill;
  the create test resets the draft to clear leftover cross-run amounts and the
  stale submitted transactionId.
2026-08-11 17:51:51 +02:00
r4bbit 60e38f4e5f feat(apps/amm): drive add-liquidity quoting from addLiquidityQuote in the UI
Wire the liquidity view's active-pool preview onto the lean addLiquidityQuote +
resolvePool, off the legacy quoteNewPosition. Create-pool quoting stays legacy
for now.

- Expose addLiquidityQuote as a QtRO slot + backend forwarding.
- NewPositionFlow.requestQuoteNow routes on resolvePool.exists (existence, like
  the swap card — no quote-derived poolStatus): active -> addLiquidityQuote,
  assembled into the shape the form consumes (reserves/fee from resolvePool,
  minimumLpRaw from the quote); missing -> legacy quoteNewPosition.
- Drop the obsolete quoteHash gate from canConfirm (the lean quotes are
  stateless).
2026-08-11 17:51:51 +02:00
r4bbit b1b6ec8517 refactor(amm): remove the dead submitNewPosition path
Now that add-liquidity and pool creation submit via addLiquidity / createPool,
the legacy submitNewPosition path is dead. Remove it end to end.

App backend (AmmUiBackend):
- Drop the submitNewPosition slot/method and its newPositionError helper.

Module (AmmModuleImpl):
- Drop submitNewPosition, the m_requestPending guard, and the now-orphaned
  nowMs/parseU64 helpers (+ the <chrono> include).

FFI (amm_ffi) — the plan op only submitNewPosition called:
- Delete plan.rs; remove amm_plan (extern + regenerated header), api::plan,
  and PlanRequest.
- Remove the plan-only machinery it fed: QuoteBranch, NewPositionPlan,
  EvaluatedQuote.plan/quote_hash, and AccountPlan's wallet_args /
  requires_fresh_lp / contains / validate_ready, plus the plan construction
  in quote.rs.
- Tests: drop the plan-only tests/helpers; keep quote coverage by trimming
  the mixed tests to their quote assertions.

QML tests (tst_LiquidityPage):
- Remove the two legacy-submit tests (base58-only success) + the
  submitNewPosition mock and its now-unused fixtures. Keep the
  finishSubmitFailure test (unchanged behaviour).

quoteNewPosition and its machinery (amm_quote, buildQuoteInput, PairSnapshot,
AccountPlan preview/sources, commitment) stay — they retire with the legacy
quoting in the quote-migration vertical.
2026-08-11 17:51:51 +02:00
r4bbit 71fb18c50f feat(apps/amm): wire the add-liquidity submit end-to-end
Migrate the active-pool branch of the liquidity form onto the new addLiquidity
op (quoting stays on legacy quoteNewPosition for now, as agreed).

- AmmUiBackend: add the addLiquidity QtRO slot; forwards to the module and
  refreshes balances (mirrors createPool).
- NewPositionFlow: route the active-pool confirm to addLiquidity, minting a
  fresh LP holding then submitting {tokenA/B, holdingA/B, lpHolding, maxAmountA/B,
  minLpRaw, deadline}. minLpRaw comes from the legacy quote's minimumLpRaw.
- NewPositionForm: show the per-side account selectors in add mode; gate the CTA
  on both holdings and on the deposit amounts being present (hasDepositAmounts) —
  the pair's probe quote otherwise reports canSubmit on simulated amounts and
  wrongly enables the button before any amount is entered.
- amm_ffi: fix add_liquidity_plan to orient the (max amount, holding) pair to the
  pool's STORED definition order, not is_canonical_pair — a pool created outside
  the FFI (the testnet setup's spel new-definition) can store a non-canonical
  order, which otherwise sent a holding into the wrong vault (Transfer
  sender/recipient definition mismatch). Test uses a non-canonical pool.

Adds add-liquidity.mjs: asserts the CTA stays disabled with no amounts, submits
an add to the seeded A/B pool, and verifies reserveA grew on-chain.
2026-08-11 17:51:51 +02:00
r4bbit b1b4631234 feat(apps/amm): pick the token account per side when creating a pool
Add per-side account selectors to the create-pool form, sourced from the
wallet's token holdings, so a user holding a token in several accounts chooses
which funds each deposit. A single holding auto-selects.

Mirrors the swap card: the selector lives inside the token input card, below
the token button. AmmTokenAmountSurface gains an optional footer slot (unchanged
when unset, so add-liquidity renders as before); TokenAmountInput fills it with a
ProgramAccountSelector filtered on the token's base58 definitionId.

- LiquidityPage fetches backend.tokenHoldings(), refetching when the wallet opens
- NewPositionForm routes the chosen holdings into submissionSnapshot's createPool
  call; canConfirm now requires both holdings when creating a pool (add-liquidity
  is untouched — it enumerates holdings server-side)
- create-pool.mjs selects the funding account for each side before submitting
2026-08-11 12:04:48 +02:00
r4bbit 86575a65aa test(apps/amm): select the funding account before submitting a swap
canSubmit now requires a chosen holding on both swap sides, so the swap e2e
test must select them first. The selector auto-selects a single holding, but
pick it explicitly (robust to multi-account wallets) via a new selectAccount
step that waits for the holdings to load and selects the first match.

Expose each side's selector via selectorObjectName (swapSell/BuyAccountSelector)
and surface sellHolding/buyHolding in the failure diagnostics.
2026-08-11 12:04:48 +02:00
r4bbit 968001066c feat(apps/amm): pick the token account per swap side
Add a per-slot account selector to the swap card's sell/buy inputs, sourced
from the wallet's token holdings, so a user holding a token in several accounts
chooses which funds each side. A single holding auto-selects.

- SwapPage fetches backend.tokenHoldings(), refetching when the wallet opens
- TokenInput embeds ProgramAccountSelector (half width, right-aligned) in a
  full-width row below the amount/token row; exposes it as selectedHoldingId
- SwapCard drives swapExactInput/Output with the selected holdings and blocks
  submit ("Select token accounts") until both are chosen
- ProgramAccountSelector gains showWhenSingle + textAlignment opt-ins
2026-08-11 12:04:48 +02:00
r4bbit 2a3be1278a feat(modules/amm): add tokenHoldings — list the wallet's token holdings
A thin source for the account selector: an amm_ffi op that decodes the wallet's
fungible TokenHoldings (owned by the configured token program) into
[{ accountId (hex), accountType:\"TokenHolding\", definitionId (base58),
definitionIdHex (hex), balanceRaw }] — one row per holding account, every token,
including zero-balance holdings; narrowing to a specific token is the selector's
job. Exposed via AmmModuleImpl::tokenHoldings and the AmmUiBackend tokenHoldings()
slot, both gated on wallet-open.
2026-08-11 12:04:48 +02:00
Ricardo Guilherme Schmidt e0ae3208a1 feat(wallet): add reusable ProgramAccountSelector component
A shared QML select box for choosing a program account (a token holding) that
matches given criteria (accountType + a state field such as the token
definitionId), listing matches with balances and emitting selectionChanged.
Registered in the wallet module's public QML.

Cherry-picked from feat/shared-wallet-token-holding-selector, reduced to the
reusable component only; the AMM swap/liquidity wiring and FFI account source
are reimplemented on the current (post swap + createPool) code.
2026-08-11 12:04:48 +02:00
r4bbit 02e77032b7 test(apps/amm): add create-pool UI e2e test + Token C setup
- setup-amm-testnet.sh: mint a third token (TKC), appended to ACCOUNT_LABELS so
  the deterministic a/b/lp ids don't shift and left unseeded (only A/B is created)
  for the test to create the A/C pair. Split wallet restore from account
  registration (ensure_accounts always runs) so adding a token needs no re-restore.
- create-pool.mjs: drives the Liquidity view to create the A/C pool, then verifies
  it on-chain via the swap card's resolvePool. evaluate() fallbacks for the
  submit/confirm buttons (synthetic clicks aren't reliable on QtQuick Buttons).
- Adds test-hook objectNames (newPositionSubmitButton, liquidityConfirmDialog).
2026-08-11 12:04:48 +02:00
r4bbit e1398ffcad feat(apps/amm): create pools via the new createPool op; drop the pool-watch poll
Route pool creation through the redesigned createPool op and retire the old
create-flow machinery. Add-liquidity stays on the legacy submitNewPosition.

Module (amm_module):
- createPool becomes a single-param envelope: it reads the caller-provided
  lpHoldingId from the request (a new pool has no pre-existing LP holding),
  dropping the requires-fresh-lp handshake — the module never creates wallet
  accounts. Returns { status, error, transactionId }; unlike the swaps, a
  submit failure carries a code so the UI can explain why.

Backend (AmmUiBackend):
- Expose liquidityQuote (read-only preview) and createPool slots. createPool
  guards on the app's wallet-open state, forwards the request, and refreshes
  balances on success. No account creation here.

UI (liquidity flow/form):
- NewPositionFlow.confirm() branches on the missing-pool signal: create ->
  createPool (mint a fresh public LP account via createAccountPublic, then
  submit); add -> unchanged submitNewPosition. Hex transactionId accepted.
- submissionSnapshot supplies canonical-order holdingAId/holdingBId.
- (liquidityQuote is wired to the backend but the create preview still rides
  the legacy quote for now.)

Cleanup: the pool-creation confirmation poll is orphaned now that create no
longer submits via submitNewPosition. It polled the pool account by re-quoting
until poolStatus flipped to active_pool — a stopgap for the missing
transactionStatus/poll_tx on the lez module. Removed pendingPoolProbes,
poolPoller, watchPoolCreation, pollPendingPool, finishPoolProbe,
rotate/removePendingPool, pairKey, matchesSelectedPair,
selectedPoolCreationPending, poolActivated, the poolCreationPending state,
acceptPoolActivation, and their now-obsolete tests.
2026-08-11 12:04:48 +02:00
r4bbit 4363f13912 feat(apps/amm): submit exact-output swaps and drop client-side swap math
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".
2026-08-06 23:15:02 +02:00
r4bbit d9876d08ca feat(apps/amm): drive the exact-output swap preview from swapExactOutQuote
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.
2026-08-06 23:15:02 +02:00
r4bbit 37c2f294ac feat(apps/amm): drive the exact-input swap preview from swapExactInQuote
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.
2026-08-06 23:15:01 +02:00
Andrea Franz 200f429ec6 test(amm-ui): feed setup password via stdin so bootstrap never blocks 2026-08-05 17:19:38 +02:00
Andrea Franz c5b9508e4d docs(amm-ui): clarify test wallet password (throwaway setup vs restore-keys) 2026-08-05 17:19:38 +02:00
Andrea Franz 0576b10dcb test(amm-ui): feed wallet setup password non-interactively 2026-08-05 17:19:38 +02:00
Andrea Franz 191942f2a8 docs(amm-ui): document isolated UI test flow in tests/README.md 2026-08-05 17:19:38 +02:00
Andrea Franz 2ed13506d1 test(amm-ui): isolate test token config, fix repo-root resolution 2026-08-05 17:19:38 +02:00
Andrea Franz 9a1f76ff6b test(amm-ui): bootstrap deterministic test wallet in testnet setup 2026-08-05 17:19:38 +02:00
Andrea Franz 380bbff308 test(amm-ui): add isolated AMM testnet setup script 2026-08-05 17:19:38 +02:00
Andrea Franz fc7b07174c test(amm-ui): add swap UI test 2026-08-05 17:19:38 +02:00