Commit Graph
222 Commits
Author SHA1 Message Date
r4bbitandCopilot Autofix powered by AI 66a8dfa988 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-21 15:36:14 +02:00
r4bbit 07b0d04b16 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-21 11:28:15 +02:00
r4bbit f3b44b61b5 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 09:58: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
Andrea Franz 092aa4ac98 fix(stablecoin): annotate initialize_program guest accounts 2026-08-20 14:21:58 +02:00
Andrea Franz a338de44c3 docs: note that Instruction variants need guest entries, run make clippy-guest 2026-08-20 14:21:58 +02:00
Andrea Franz ebc9b4b9fe feat(stablecoin): expose initialize_program guest entry + e2e test
closes #164
2026-08-20 14:21:58 +02:00
Andrea Franz 0410d83ae4 feat(stablecoin): implement initialize_program host function
closes #163
2026-08-20 14:21:58 +02:00
Andrea Franz 3204a37588 docs(stablecoin): reference issues instead of plan names 2026-08-20 14:21:58 +02:00
Andrea Franz 5b67d8d886 feat(stablecoin): add Instruction::InitializeProgram variant
closes #162
2026-08-20 14:21:58 +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
Andrea Franz f62444ffa2 refactor(stablecoin): address review on Position migration 2026-08-19 12:30:22 +02:00
Andrea Franz 2d3392393a refactor(stablecoin)!: migrate Position to spec §4.4 shape
closes #161
2026-08-19 12:30:22 +02:00
Andrea Franz ed6e20e11c refactor(stablecoin): simplify RedemptionPriceState PDA domain 2026-08-19 11:53:45 +02:00
Andrea Franz 4e087e6ff6 feat(stablecoin): add RedemptionPriceState account type
closes #160
2026-08-19 11:53:45 +02:00
Andrea Franz d9b7366990 refactor(stablecoin): simplify StabilityFeeAccumulator PDA domain 2026-08-19 11:49:57 +02:00
Andrea Franz 0a44380a70 feat(stablecoin): add StabilityFeeAccumulator account type
closes #159
2026-08-19 11:49:57 +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 62dc45177d feat(modules/amm): add sync-reserves module op
A permissionless keeper op that refreshes a pool's stored reserves to the live
vault balances (and its TWAP tick). Same lean pattern as the other plans, but
minimal: SyncReserves is a unit instruction — no quote, no user inputs (no
amounts/slippage/deadline/holdings), and nothing signs.

FFI (modules/amm/ffi):
- sync_reserves_plan: encodes SyncReserves over the fixed 6-account IDL order
  (config, pool, vault_a, vault_b, current_tick, clock), all non-signing.
  config/pool/current_tick/clock are order-independent PDAs from derive_pair;
  the vaults come from the pool's stored ids in pool_data (read-only, but the
  guest still asserts them — a non-canonically-stored pool would otherwise
  mismatch). Fails closed: same_token_pair, config_unavailable, no_pool.
- Wired through mod.rs / ffi.rs (cbindgen header regenerated). Tests cover the
  stored-vault + zero-signer + unit-instruction layout and the fail-closed
  paths. amm_ffi: 43 tests pass, clippy clean. (lib.rs is a cargo fmt re-wrap.)

C++ module (modules/amm/src):
- syncReserves reads config + pool server-side, calls the plan, and submits.
  request is just { tokenAId, tokenBId } — no holdings/amounts/deadline. Public
  method → auto-exposed via the universal-module dispatch.
2026-08-11 17:51:51 +02:00
r4bbit 44b70e4333 feat(modules/amm): add remove-liquidity module ops
The remove-liquidity counterpart of the add ops, following the same lean
pattern: pure Rust FFI pricing/plan + thin C++ orchestration, hex ids end to
end, the token pair oriented to the pool's stored order server-side. No UI yet.

FFI (modules/amm/ffi):
- remove_liquidity_quote: burning lpAmountRaw returns the proportional share of
  each reserve — withdraw = floor(reserve·lp/supply), the guest's own math —
  plus the slippage-floored minimumAmount{A,B}Raw the submit enforces and the
  pool's spot price, all in the caller's display order. Guards: same_token_pair,
  invalid_slippage, no_pool, insufficient_pool_liquidity (burn exceeds the
  supply unlocked above MINIMUM_LIQUIDITY), pair_mismatch, amount_too_low,
  minimum_amount_zero.
- remove_liquidity_plan: encodes RemoveLiquidity over the fixed 10-account IDL
  order, orienting (min_amount, holding) to the pool's stored order like the add
  plan — but only user_holding_lp signs (it is burned) and there is no fresh
  holding: the existing token a/b holdings receive the withdrawal.
- Wired through mod.rs / ffi.rs (cbindgen header regenerated). Unit tests cover
  the guest-formula pricing + display orientation, the guard set, the plan's
  account/signer layout, and fail-closed. amm_ffi: 41 tests pass, clippy clean.

C++ module (modules/amm/src):
- removeLiquidityQuote / removeLiquidity mirror addLiquidityQuote / addLiquidity:
  read the pool server-side, call the ops, submit. removeLiquidity takes no fresh
  account (the LP holding already exists) and threads the caller-provided
  deadlineMs like the other submits. Public methods → auto-exposed via the
  universal-module dispatch.
2026-08-11 17:51:51 +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 37f28fe663 feat(modules/amm): add_liquidity_quote takes slippage, returns minimumLpRaw
The add-liquidity quote now takes slippageBps and returns minimumLpRaw =
floor(delta_lp * (10000 - slippage) / 10000) — the LP floor the submit passes as
min_amount_liquidity, mirroring the swap quotes' minReceivedRaw. Computing it in
Rust keeps the u128 slippage math out of the UI. Adds invalid_slippage (>= 100%)
and minimum_lp_zero (slippage leaves no floor) errors; the module's
addLiquidityQuote forwards slippageBps.
2026-08-11 17:51:51 +02:00
r4bbit c95322c033 refactor(modules/amm): resolvePool accepts base58 ids and orients reserves
resolvePool now normalizes base58-or-hex ids (the liquidity view passes base58,
the swap card hex) and orients the returned reserves to the caller's requested
token order — reserveA is the requested tokenA's reserve — instead of the pool's
stored order, which needn't be canonical (e.g. a pool created outside the FFI,
like the testnet setup's). Callers read reserveA/reserveB directly; the swap card
is unaffected (it reads the reserves for display only and never oriented them
itself).
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 0eaf51476b feat(modules/amm): add the add-liquidity API and quoting
Introduce the add-liquidity vertical mirroring the swap + createPool patterns,
for depositing into an existing pool via the AddLiquidity instruction.

FFI (amm_ffi):
- add_liquidity_quote — decode poolData, orient the caller's max amounts to the
  pool's canonical order, run the guest's exact ideal->actual->delta_lp math, and
  return { amountARaw, amountBRaw, expectedLpRaw, priceRaw } in display order.
  Slippage-free like create's quote; the min-LP floor is applied at submit.
- add_liquidity_plan — canonicalize (token, max-amount, holding) as one unit,
  take vaults + LP definition from poolData, emit the 10-account AddLiquidity
  order (only the user holdings a/b/LP sign). Takes minLpRaw directly, like
  swap_exact_in_plan takes min_out.

Shared with createPool: extract canonical_triples (the pair/amount/holding
canonical swap) and plan_response (the tx-submission envelope); both the create
and add plans now use them.
2026-08-11 12:56:37 +02:00