feat: one-sided liquidity add and withdraw (auto zap, including wrap) #533

Closed
opened 2026-08-16 09:55:47 +00:00 by PlasticDigits · 23 comments
PlasticDigits commented 2026-08-16 09:55:47 +00:00 (Migrated from gitlab.com)

Summary

Retail /pool still requires two tokens to add liquidity and returns two tokens on withdraw. Native wrap is a pair of checkboxes. That is why LUNC holders bounce: they have one asset and cannot complete the form.

Ship one-sided add and withdraw as the default Pool flow, including wrap/unwrap. It must be automatic and almost option-less:

Action User chooses Everything else
Add (1) a token they already hold, (2) which pair to join, (3) amount Wrap if native, swap the optimal slice into the other side, provide at the pool ratio
Withdraw (1) an LP token they already hold, (2) which single token to receive, (3) amount Burn LP, swap the unwanted side, unwrap if they asked for native

Do not split wrap, zap-in, zap-out, or the retail form into separate issues. Two-sided provide/withdraw stays only as Advanced (empty-pool bootstrap / power users).

Related: #147 (CW20 add-LP three fees), #213 (native wrap + provide), #366 (IL notice), #462 (pre-sign summary), #480 (counterpart auto-fill / withdraw preview), #489 (no always-on essays), #497 (default 5% slippage), #507 (/wrap), #512 / #516 (wrap/unwrap fee + burn tax), #531 (retail how-to — H531-3 must be rewritten).

Current codebase

The mechanics are two-sided. Wrap is bolted on as checkboxes. There is no zap solver and no “pick one token” form.

Layer Behavior today
On-chain pair ProvideLiquidity { assets: [Asset; 2], slippage_tolerance?, receiver?, deadline? } always takes both legs. Later deposits mint min(amount_a × share / reserve_a, amount_b × share / reserve_b). Off-ratio excess is donated to the pool. First deposit (both reserves 0) is sqrt(a×b) − MINIMUM_LIQUIDITY (1000 LP units; LP CW20 decimals = 18). Withdraw is CW20 send → WithdrawLiquidity { min_assets? } and returns both underlyings. Pause blocks provide/withdraw.
Router ExecuteSwapOperations + optional unwrap_output. No provide/withdraw / zap message. Native input wrap is never on the router (would double-tax); frontend multi-msg treasury.wrap_deposit then CW20 send.
/pool add UI Per-card Provide Liquidity: two amount fields, Max/50%, counterpart auto-fill (#480), ratio warning, Use native (auto-wrap) checkboxes per wrap-able leg, IL notice, gas gate (#147), pre-sign summary (#462). CTA disabled until both amounts are set.
/pool withdraw UI LP amount + slippage chips on the card + Receive as wrapped tokens checkbox. Preview shows both underlyings. If unwrap is checked, a follow-up tx per wrap-able CW20 runs after withdraw — and today’s loop unwraps getTokenBalance of that CW20 (entire wallet balance, not the withdrawn amount). That is a custody bug and must not survive this work.
Wrap path Add: one executeTerraContractMulti of wrap_deposit ×1–2 + increase_allowance ×2 + provide_liquidity (#213). Wrap fee is fee_wrap_bps only (W8); wrap_deposit is not burn-taxed. Withdraw unwrap uses mapper fee_unwrap_bps then treasury InstantWithdraw (burn-taxed, W9).
Token / pair pickers Pair list is indexer GET /pairs + factory badge set. Token pickers (TokenSelect / TokenSearchSelect) exist on Swap/Mint, not on Pool add (assets are implied by the card). Portfolio already scans LP balances (usePortfolioLpBalances) but only links “Manage on Pool”.
Quote / submit Swap has submit-aligned sim (#356 / #501). Pool provide does not swap, so there is no zap quote, no min_return on a swap-half, and native-wrap provide currently passes slippage_tolerance: null.
How-to (#531) Invariant H531-3 still says both assets are required and a LUNC-only deposit is not a v2 action. That becomes false once one-sided ships.
LP decimals in UI PoolPage.tsx and portfolio use LP_DECIMALS = 6 while the LP CW20 is 18. Do not copy that into the new form.

Why this is needed

  1. Users cannot finish the supported action with one asset. Support already tells people they can LP. The form still demands the other side. Auto-fill (#480) only helps if they already hold both.
  2. Wrap is a hidden second product. Native LUNC/USTC are not pool CW20s. Checkboxes + /wrap are easy to miss; the new flow must treat native as just another from / as token.
  3. Two-sided + donation is a foot-gun. Off-ratio provide silently donates. One-sided must swap to ratio so the user is not donating by default.
  4. Withdraw returning two tokens is the same problem in reverse. Retail wants “give me LUNC” (or UST1, or USTC), not a leftover bag of the other leg plus a wrap checkbox.
  5. #531 how-to cannot stay honest if the product still requires two assets. This issue is the product change; the how-to update is in scope.

Constraints / guardrails

  1. Retail surface is option-less. Add: token (wallet holdings), pair, amount. Withdraw: LP (wallet holdings), token to receive, amount. No wrap checkboxes, no second amount field, no on-card slippage chips, no book-leg / hybrid controls. Slippage stays in existing Settings (default 5%, #497).
  2. Wrap/unwrap is implied by the token, not a toggle. uluna / uusd ↔ cLUNC / cUSTC via existing mapper + treasury. Do not send native into the pair or the router.
  3. Never donate by default. The zap split must target the post-swap pool ratio. If residual dust would donate, fail the quote or leave dust in the wallet — do not call provide_liquidity off-ratio without an Advanced override.
  4. Empty pool cannot one-side. No price → disable add with one short sentence. First deposit stays two-sided Advanced (sqrt + MINIMUM_LIQUIDITY).
  5. Factory pairs only for the retail pair picker (same provenance as Swap router graph / pool “In router (factory)” badge). Indexer-only / foreign pairs are not zap targets.
  6. Pool-only zap swap (no limit-book / hybrid book leg) so the split is deterministic. Reuse poolOnlyHybridParams.
  7. Same quote snapshot on submit as Swap (#356): pay raw, split, min LP / min out, wrap nets, route ops. Stale quote disables CTA.
  8. Slippage on every leg. Zap swap: max_spread / min_return. Provide: slippage_tolerance (stop passing null). Withdraw: min_assets. Output swap: min_return. Unwrap: quote post-fee post-tax (W9); do not promise “2% flat”.
  9. Unwrap only the zap-out amount. Never unwrap the user’s full CW20 balance (fix the current withdraw loop).
  10. Keep safety gates. IL notice (#366), NFA (#138), clickwrap (#517), pause, blacklist, wrap-mapper pause / treasury mismatch (W2), gas envelopes, Expert Mode for extreme impact, pre-sign summary (#462).
  11. Gas / Max. Bank uluna must cover the full sequence (wrap + swap + allowances + provide, or withdraw + swap + unwrap) before the first broadcast. Max leaves fee reserve (maxSpendableAmount / new zap envelope). Do not spend the last LUNC needed for gas.
  12. #489 copy. Labels ≤ ~5 words (Token, Pair, Amount, Add, Withdraw as). No architecture essays, no “you pay burn tax” footers unless that line is a live blocking unwrap quote. Blocking errors ≤ 1 sentence.
  13. Do not change pair mint/burn math, fee treasury, wrap-mapper fee bps, or factory whitelist in this issue.
  14. Do not add a pair/router Zap execute unless LocalTerra rehearsal proves multi-msg gas/atomicity cannot work. Default: frontend orchestration on existing messages.
  15. Do not invent incentives. No APR / farm chrome.
  16. LP amount scale must use the LP CW20’s real decimals (18), not the UI 6 leftover.
  17. LocalTerra for on-chain paths. Do not skip with a false “no LocalTerra”.
  18. Update #531 how-to (H531-3 and copy) in the same change so the in-app guide matches the new form.

Relevant files

File Role
frontend-dapp/src/pages/PoolPage.tsx Two-sided provide/withdraw, wrap checkboxes, IL, gas gate, pre-sign
frontend-dapp/src/services/terraclassic/pair.ts provideLiquidity (3 txs) / withdrawLiquidity
frontend-dapp/src/services/terraclassic/transactions.ts Multi-msg + wrap+provide fee envelopes (#213)
frontend-dapp/src/services/terraclassic/router.ts Wrap-in + swap + unwrap_output; netCw20AfterNativeWrap / netNativeAfterUnwrap
frontend-dapp/src/services/terraclassic/wrapMapper.ts Mapper config, fee bps, treasury match
frontend-dapp/src/utils/poolProvideCounterpart.ts Two-sided auto-fill (not a zap solver)
frontend-dapp/src/utils/provideLiquidityEstimate.ts LP mint estimate / proportional check
frontend-dapp/src/utils/rawAmountMath.ts Withdraw pro-rata + min assets
frontend-dapp/src/utils/provideLiquidityNativeGasBalanceGate.ts CW20/CW20 three-fee gate
frontend-dapp/src/utils/maxSpendableAmount.ts Max vs gas reserve
frontend-dapp/src/components/ui/TokenSelect.tsx / TokenSearchSelect.tsx Reuse for from / withdraw-as
frontend-dapp/src/hooks/usePortfolioLpBalances.ts Wallet LP scan for withdraw picker
frontend-dapp/src/components/pool/PoolPreSubmitSummary.tsx SEC-I05 pre-sign card
frontend-dapp/src/utils/poolLpHowtoCopy.ts #531 copy (H531-3)
frontend-dapp/src/types/index.ts getNativeEquivalent / getWrappedEquivalent
smartcontracts/packages/dex-common/src/pair.rs ProvideLiquidity / withdraw hook
smartcontracts/contracts/pair/src/contract.rs Mint/burn + pause
smartcontracts/contracts/router/src/msg.rs Swap ops + unwrap only
docs/frontend.md § Pool provide / how-to Engineering invariants to extend
docs/user-lunc-liquidity.md Retail backup (update with one-sided)
NATIVE_TOKEN_WRAPPING.md Wrap multi-msg architecture
frontend-dapp/e2e/wrap-pool.spec.ts / pool.spec.ts Existing Pool / wrap E2E (will break)

Frontend zap on existing messages. Do not wait on a contract upgrade.

Retail UI (/pool)

Replace the per-card two-field expand with one Add card and one Withdraw card (pair list can stay as discovery; selecting a row pre-fills Pair).

  • Add: Token (balances > 0 from indexer catalog + uluna/uusd when wrap env is set) → Pair (factory set) → Amount + Max/50% → IL notice → compact quote (you add X, pool receives ~Y/Z, estimated LP) → pre-sign → CTA.
  • Withdraw: LP (portfolio rows with balanceRaw > 0) → Withdraw as (pair legs + native equivalents; plus routed tokens only if a route exists) → Amount + Max → estimated single-token receive (post-swap, post-unwrap tax if native) → pre-sign → CTA.
  • Advanced <details>: current two-sided provide/withdraw (empty-pool bootstrap, manual ratio). Default closed.

Quote math (new module, e.g. oneSidedLiquidity.ts)

Classic constant-product zap-in: solve swap amount s of the input leg so that (in − s) and swapOut(s) match post-swap reserves, using the pair’s fee_bps (and fee-discount if the wallet is registered — same as a pool-only swap). Integer floor; unit-test against LCD hybrid_simulation + estimateProvideLiquidityUserLp.

  • Input is native wrap of a pair leg → net CW20 after mapper wrap fee (W8), then zap.
  • Input is a pair leg → zap only.
  • Input is not a pair leg → GET /route/solve into one pair leg (prefer the direct/shortest path), then zap. No route → one-sentence disable.
  • Withdraw: pro-rata both legs → simulate selling the non-output leg (and any leftover of the other leg if output is off-pair) → optional unwrap quote (W9).

Execution (new builder, e.g. oneSidedLiquidityTx.ts)

Prefer one executeTerraContractMulti when gas fits:

  1. Optional treasury.wrap_deposit (native in).
  2. Pair or router swap of the solved slice (min_return / max_spread from the snapshot).
  3. increase_allowance ×2 for the quoted provide amounts.
  4. provide_liquidity with slippage_tolerance and those amounts.

Withdraw sequence: LP send withdraw (with min_assets) → swap unwanted side → optional mapper unwrap of that output only.

If the combined tx exceeds a measured gas envelope, split into the fewest txs and rollback allowances in one multi-msg on provide failure (#147). New estimate*UlunaFeesTotal helpers — no magic LUNC constants.

Docs / how-to

Rewrite H531-3 and user-lunc-liquidity.md: one token in, one token out; wrap is automatic when the selected token is native; two-sided is Advanced / empty pool only.

Acceptance criteria

  • AC1 — Add retail controls are only: token (wallet holdings > 0), pair (factory), amount. No wrap checkbox, no second amount, no on-card slippage chips.
  • AC2 — Withdraw retail controls are only: LP (wallet LP > 0), token to receive, amount. No “receive wrapped” checkbox.
  • AC3 — Selecting native uluna/uusd as add-from or withdraw-as wraps/unwraps automatically when wrap env is set; pools still hold CW20 only.
  • AC4 — Zap-in split is ratio-correct after the swap (unit-tested). Provide is not submitted off-ratio in the retail path.
  • AC5 — Empty pool: one-sided add disabled with one short error; two-sided Advanced still bootstraps.
  • AC6 — Submit uses the same settled quote snapshot as the pre-sign card (input, implied swap, min LP / min out, wrap nets, pair address, chain name).
  • AC7 — Every hop has a slippage floor (min_return / slippage_tolerance / min_assets). Native-wrap provide no longer sends slippage_tolerance: null.
  • AC8 — Unwrap spends only the zap-out / withdrawn amount — never the rest of the wallet’s cLUNC/cUSTC.
  • AC9 — Gas gate + Max reserve cover the full zap sequence. CTA disabled on pause, blacklist, wrap-mapper pause, treasury mismatch, no route, stale quote, insufficient balance, or LCD/indexer outage (existing banners).
  • AC10 — Token not in the pair and not a native wrap of a pair leg: route-in then zap, or a one-sentence “No route” disable. Foreign/indexer-only pairs are not selectable.
  • AC11 — #531 how-to + docs/frontend.md + docs/user-lunc-liquidity.md describe one-sided as default; H531-3 updated; no incentive chrome.
  • AC12 — Two-sided path remains reachable under Advanced and still used for empty-pool first deposit.
  • AC13 — make verify-issue-<iid> covers unit + scoped frontend tests + Playwright smoke (5 workers) and documents the LocalTerra tx project.

Test plan (all paths)

Unit / math

ID Path
T1 Zap-in split vs fixture reserves (equal pool, skewed pool, 6- vs 18-decimal legs, fee 30 bps, fee-discount > 0).
T2 Zap-in with wrap-fee-only net (W8): 10 000 LUNC @ 200 bps → 9 800 CW20 into the solver, not ~9 751.
T3 Zap-out: pro-rata + sell other side; min out after 0.5 / 1 / 5% slippage.
T4 Zap-out to native: post-unwrap-fee then burn tax (W9); minimum_receive on the swap stays post-fee pre-tax if router unwrap is used (R3).
T5 Empty reserves → solver returns unavailable (no divide-by-zero, no fake price).
T6 Dust / rounding: leftover after provide < 1 raw unit or quote rejected — never a donating provide in retail.
T7 Off-pair input: mocked route/solve then zap; empty route → disable reason.
T8 LP decimals 18: Max LP uses chain decimals, not 6.
T9 Fee envelope: wrap+swap+provide total > wrap-only provide (#213) > CW20 three-tx (#147); Max subtracts the matching envelope.
T10 Allowance rollback helper still batches two decrease_allowance in one multi-msg.

UI / integration

ID Path
U1 Disconnected: pickers + IL visible; CTA is Connect Wallet (same pattern as #494).
U2 Connected, zero holdings: add token list empty / short empty state; withdraw LP list empty.
U3 Holding only LUNC: LUNC appears; choosing a cLUNC pair wraps + zaps; no second amount field.
U4 Holding only a pair CW20: zap without wrap.
U5 Holding a non-leg token with a route: route-in then zap.
U6 Holding a non-leg token with no route: CTA disabled, one sentence.
U7 Pair paused / wallet or token blacklisted / wrap mapper paused / treasury mismatch: CTA disabled, existing short errors.
U8 Empty pool card: one-sided disabled; Advanced two-sided enabled.
U9 Pre-sign shows action, pair, input (or LP), estimated single-token out or estimated LP, min floor, chain.
U10 Stale quote (amount edited mid-fetch) disables submit.
U11 How-to #lp-howto no longer says both tokens are required.
U12 Light + dark; no overlay on wallet / clickwrap / CTA.

Playwright (5 workers smoke; tx project 1 worker)

ID Path
P1 /pool add card: token + pair + amount only (no auto-wrap checkbox, no second asset input).
P2 Withdraw card: LP + withdraw-as + amount only (no receive-wrapped checkbox).
P3 Native LUNC selected as add-from on a wrap-able pair: quote shows wrap, submit not blocked by missing counterpart field.
P4 LocalTerra e2e-tx: one-sided add with CW20 pair-leg; LP balance increases; pool reserves move on both sides.
P5 LocalTerra e2e-tx: one-sided add with native LUNC (wrap+zap); user spends uluna, receives LP, holds no forced leftover of the other native.
P6 LocalTerra e2e-tx: withdraw as the input CW20; user receives one CW20; other-leg wallet delta is ~0 (swap consumed it).
P7 LocalTerra e2e-tx: withdraw as uluna; unwrap amount ≤ withdrawn/zapped output; pre-existing cLUNC balance is unchanged.
P8 Empty pool: one-sided CTA disabled; Advanced two-sided first deposit still works.
P9 Pause / blacklist fixtures still block add/withdraw.
P10 Existing wrap-pool / pool-lp-howto-531 specs updated to the new controls (no leftover checkbox assertions).

Test plan (attack, hack, and abuse)

ID Vector Expect
A1 Sandwich / front-run the zap swap-half (mempool sees swap then provide). min_return + slippage_tolerance revert the tx; user keeps input (minus gas). No off-ratio donate. Document MEV the same way as Swap (no fake “protected” toggle).
A2 Stale quote / UI lie — reserves move after quote, submit uses live typed amounts. Submit blocked while fetching / key mismatch (#356). Chain floors still protect if broadcast.
A3 Forced donation — solver rounds so provide is off-ratio. Retail path refuses or trims to the minting side and returns dust to the wallet. Never silently donate a material excess.
A4 Empty-pool grief / first-depositor inflation (P3). One-sided disabled. Advanced first deposit still burns MINIMUM_LIQUIDITY. No “zap into 0 reserves”.
A5 Foreign / indexer-only pair as zap target (fake LP, unaudited pair). Not in factory picker. Providing to a non-factory address is not a retail path.
A6 Fake LP token in the withdraw picker (random CW20). Picker is portfolio rows whose lp_token matches a factory pair. Sending a non-LP CW20 to a pair must fail on-chain; UI must not offer it.
A7 Unwrap-all drain — withdraw 1 LP with unwrap, wallet holds a large cLUNC bag. Only the zap-out amount is unwrapped. Regression test is mandatory (today’s loop is the bug).
A8 Wrap-mapper pause / wrong treasury (W2) — wrap_deposit would misroute. CTA disabled; no broadcast.
A9 Pair pause / blacklist mid-flow. Provide/withdraw/swap blocked; no partial “helpful” unwrap of unrelated balances.
A10 Allowance leftover after failed provide. Rollback both allowances in one multi-msg. Do not leave the pair as spender for the quoted amounts.
A11 Gas grief / fee rewrite — wallet extension lowers gas below envelope. Existing LocalTerra post-sign guards; zap uses a documented envelope. Max cannot spend gas LUNC.
A12 Unlimited unwrap / swap slippage (min_return omitted, slippage_tolerance: null). Forbidden on retail zap. Treat missing floors as a test failure.
A13 Hybrid book fill in the zap (unpredictable split). Pool-only ops only. A book fill that desyncs the provide amounts must not be the default path.
A14 Route-in to a non-leg token that then cannot provide (spoofed route/solve). Factory-resolve + pair simulation preflight (same spirit as Swap hop preflight). Bad route disables submit.
A15 Phishing / wrong network — pre-sign omitted or amounts ≠ form. SEC-I05 card required; testids on action / pair / amount / chain.
A16 Social-engineering copy — extra options, APR, “wrap first on /wrap”. Retail card stays option-less; wrap is automatic; no farm chrome; how-to remains opt-in.
A17 Expert-mode bypass — zap price impact > 30% / ≥ 99%. Same Expert Mode / extreme-slippage gates as Swap.
A18 Re-entrancy / multi-msg ordering — swap credits pair before allowances, or unwrap before withdraw settles. Message order specified and covered by builder unit tests; CosmWasm tx is atomic per broadcast.
A19 Dust spam / min-amount — zap of 1 raw unit. Quote unavailable or CTA disabled below a dust threshold; no revert storm.
A20 Clickwrap / NFA bypass via new Pool entry. Existing gates still wrap /pool (C1–C10, #138).

Verification criteria

Close when all of the following are true:

  1. AC1–AC13 checked on the MR.
  2. make verify-issue-<iid> is green (unit + scoped frontend + Playwright smoke).
  3. LocalTerra e2e-tx P4–P8 recorded (add CW20, add native, withdraw as CW20, withdraw as native without draining leftover wrap, empty-pool Advanced).
  4. A7 unwrap-all regression is in CI, not a manual note.
  5. #531 how-to / user-lunc-liquidity.md / docs/frontend.md Pool section match the shipped UI (H531-3 rewritten).
  6. No new pair/router execute message or an explicit follow-up issue if rehearsal proved one is required (do not silently ship a contract upgrade in this issue).
make verify-issue-<iid>
make test-frontend
# scoped (adjust paths to the new modules):
#   oneSidedLiquidity + oneSidedLiquidityTx + PoolPage + poolLpHowtoCopy
# Playwright smoke (5 workers):
#   frontend-dapp/e2e/pool-one-sided-<iid>.spec.ts
# LocalTerra tx (1 worker):
#   sg docker -c 'CI=1 make test-e2e'  # or the dedicated e2e-tx file
## Summary Retail `/pool` still requires **two tokens** to add liquidity and returns **two tokens** on withdraw. Native wrap is a pair of checkboxes. That is why LUNC holders bounce: they have one asset and cannot complete the form. Ship **one-sided** add and withdraw as the default Pool flow, **including wrap/unwrap**. It must be automatic and almost option-less: | Action | User chooses | Everything else | |--------|----------------|-----------------| | **Add** | (1) a token they already hold, (2) which pair to join, (3) amount | Wrap if native, swap the optimal slice into the other side, provide at the pool ratio | | **Withdraw** | (1) an LP token they already hold, (2) which single token to receive, (3) amount | Burn LP, swap the unwanted side, unwrap if they asked for native | Do **not** split wrap, zap-in, zap-out, or the retail form into separate issues. Two-sided provide/withdraw stays only as **Advanced** (empty-pool bootstrap / power users). Related: [#147](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/147) (CW20 add-LP three fees), [#213](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/213) (native wrap + provide), [#366](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/366) (IL notice), [#462](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/462) (pre-sign summary), [#480](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/480) (counterpart auto-fill / withdraw preview), [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) (no always-on essays), [#497](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/497) (default 5% slippage), [#507](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/507) (`/wrap`), [#512](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/512) / [#516](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/516) (wrap/unwrap fee + burn tax), [#531](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/531) (retail how-to — **H531-3 must be rewritten**). ## Current codebase The **mechanics are two-sided**. Wrap is bolted on as checkboxes. There is no zap solver and no “pick one token” form. | Layer | Behavior today | |-------|----------------| | **On-chain pair** | `ProvideLiquidity { assets: [Asset; 2], slippage_tolerance?, receiver?, deadline? }` always takes **both** legs. Later deposits mint `min(amount_a × share / reserve_a, amount_b × share / reserve_b)`. Off-ratio excess is **donated** to the pool. First deposit (both reserves `0`) is `sqrt(a×b) − MINIMUM_LIQUIDITY` (1000 LP units; LP CW20 decimals = **18**). Withdraw is CW20 `send` → `WithdrawLiquidity { min_assets? }` and returns **both** underlyings. Pause blocks provide/withdraw. | | **Router** | `ExecuteSwapOperations` + optional `unwrap_output`. **No** provide/withdraw / zap message. Native **input** wrap is never on the router (would double-tax); frontend multi-msg `treasury.wrap_deposit` then CW20 send. | | **`/pool` add UI** | Per-card **Provide Liquidity**: two amount fields, Max/50%, counterpart auto-fill (#480), ratio warning, **Use native (auto-wrap)** checkboxes per wrap-able leg, IL notice, gas gate (#147), pre-sign summary (#462). CTA disabled until **both** amounts are set. | | **`/pool` withdraw UI** | LP amount + slippage chips on the card + **Receive as wrapped tokens** checkbox. Preview shows **both** underlyings. If unwrap is checked, a **follow-up tx per wrap-able CW20** runs after withdraw — and today’s loop unwraps `getTokenBalance` of that CW20 (**entire wallet balance**, not the withdrawn amount). That is a custody bug and must not survive this work. | | **Wrap path** | Add: one `executeTerraContractMulti` of `wrap_deposit` ×1–2 + `increase_allowance` ×2 + `provide_liquidity` (#213). Wrap fee is `fee_wrap_bps` only (W8); `wrap_deposit` is not burn-taxed. Withdraw unwrap uses mapper `fee_unwrap_bps` then treasury InstantWithdraw (burn-taxed, W9). | | **Token / pair pickers** | Pair list is indexer `GET /pairs` + factory badge set. Token pickers (`TokenSelect` / `TokenSearchSelect`) exist on Swap/Mint, **not** on Pool add (assets are implied by the card). Portfolio already scans LP balances (`usePortfolioLpBalances`) but only links “Manage on Pool”. | | **Quote / submit** | Swap has submit-aligned sim (#356 / #501). Pool provide does **not** swap, so there is no zap quote, no `min_return` on a swap-half, and native-wrap provide currently passes `slippage_tolerance: null`. | | **How-to (#531)** | Invariant **H531-3** still says both assets are required and a LUNC-only deposit is not a v2 action. That becomes false once one-sided ships. | | **LP decimals in UI** | `PoolPage.tsx` and portfolio use `LP_DECIMALS = 6` while the LP CW20 is **18**. Do not copy that into the new form. | ## Why this is needed 1. **Users cannot finish the supported action with one asset.** Support already tells people they can LP. The form still demands the other side. Auto-fill (#480) only helps if they already hold both. 2. **Wrap is a hidden second product.** Native LUNC/USTC are not pool CW20s. Checkboxes + `/wrap` are easy to miss; the new flow must treat native as just another **from** / **as** token. 3. **Two-sided + donation is a foot-gun.** Off-ratio provide silently donates. One-sided must **swap to ratio** so the user is not donating by default. 4. **Withdraw returning two tokens is the same problem in reverse.** Retail wants “give me LUNC” (or UST1, or USTC), not a leftover bag of the other leg plus a wrap checkbox. 5. **#531 how-to cannot stay honest** if the product still requires two assets. This issue is the product change; the how-to update is in scope. ## Constraints / guardrails 1. **Retail surface is option-less.** Add: token (wallet holdings), pair, amount. Withdraw: LP (wallet holdings), token to receive, amount. No wrap checkboxes, no second amount field, no on-card slippage chips, no book-leg / hybrid controls. Slippage stays in existing Settings (default **5%**, #497). 2. **Wrap/unwrap is implied by the token, not a toggle.** `uluna` / `uusd` ↔ cLUNC / cUSTC via existing mapper + treasury. Do not send native into the pair or the router. 3. **Never donate by default.** The zap split must target the post-swap pool ratio. If residual dust would donate, fail the quote or leave dust in the wallet — do not call `provide_liquidity` off-ratio without an Advanced override. 4. **Empty pool cannot one-side.** No price → disable add with one short sentence. First deposit stays **two-sided Advanced** (`sqrt` + `MINIMUM_LIQUIDITY`). 5. **Factory pairs only** for the retail pair picker (same provenance as Swap router graph / pool “In router (factory)” badge). Indexer-only / foreign pairs are not zap targets. 6. **Pool-only zap swap** (no limit-book / hybrid book leg) so the split is deterministic. Reuse `poolOnlyHybridParams`. 7. **Same quote snapshot on submit** as Swap (#356): pay raw, split, min LP / min out, wrap nets, route ops. Stale quote disables CTA. 8. **Slippage on every leg.** Zap swap: `max_spread` / `min_return`. Provide: `slippage_tolerance` (stop passing `null`). Withdraw: `min_assets`. Output swap: `min_return`. Unwrap: quote post-fee post-tax (W9); do not promise “2% flat”. 9. **Unwrap only the zap-out amount.** Never unwrap the user’s full CW20 balance (fix the current withdraw loop). 10. **Keep safety gates.** IL notice (#366), NFA (#138), clickwrap (#517), pause, blacklist, wrap-mapper pause / treasury mismatch (W2), gas envelopes, Expert Mode for extreme impact, pre-sign summary (#462). 11. **Gas / Max.** Bank `uluna` must cover the **full** sequence (wrap + swap + allowances + provide, or withdraw + swap + unwrap) before the first broadcast. Max leaves fee reserve (`maxSpendableAmount` / new zap envelope). Do not spend the last LUNC needed for gas. 12. **#489 copy.** Labels ≤ ~5 words (`Token`, `Pair`, `Amount`, `Add`, `Withdraw as`). No architecture essays, no “you pay burn tax” footers unless that line is a **live blocking** unwrap quote. Blocking errors ≤ 1 sentence. 13. **Do not change** pair mint/burn math, fee treasury, wrap-mapper fee bps, or factory whitelist in this issue. 14. **Do not add a pair/router `Zap` execute** unless LocalTerra rehearsal proves multi-msg gas/atomicity cannot work. Default: frontend orchestration on existing messages. 15. **Do not invent incentives.** No APR / farm chrome. 16. **LP amount scale** must use the LP CW20’s real decimals (18), not the UI `6` leftover. 17. **LocalTerra** for on-chain paths. Do not skip with a false “no LocalTerra”. 18. **Update #531 how-to** (H531-3 and copy) in the same change so the in-app guide matches the new form. ## Relevant files | File | Role | |------|------| | [`frontend-dapp/src/pages/PoolPage.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/PoolPage.tsx) | Two-sided provide/withdraw, wrap checkboxes, IL, gas gate, pre-sign | | [`frontend-dapp/src/services/terraclassic/pair.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/pair.ts) | `provideLiquidity` (3 txs) / `withdrawLiquidity` | | [`frontend-dapp/src/services/terraclassic/transactions.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/transactions.ts) | Multi-msg + wrap+provide fee envelopes (#213) | | [`frontend-dapp/src/services/terraclassic/router.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/router.ts) | Wrap-in + swap + `unwrap_output`; `netCw20AfterNativeWrap` / `netNativeAfterUnwrap` | | [`frontend-dapp/src/services/terraclassic/wrapMapper.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/wrapMapper.ts) | Mapper config, fee bps, treasury match | | [`frontend-dapp/src/utils/poolProvideCounterpart.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/poolProvideCounterpart.ts) | Two-sided auto-fill (not a zap solver) | | [`frontend-dapp/src/utils/provideLiquidityEstimate.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/provideLiquidityEstimate.ts) | LP mint estimate / proportional check | | [`frontend-dapp/src/utils/rawAmountMath.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/rawAmountMath.ts) | Withdraw pro-rata + min assets | | [`frontend-dapp/src/utils/provideLiquidityNativeGasBalanceGate.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/provideLiquidityNativeGasBalanceGate.ts) | CW20/CW20 three-fee gate | | [`frontend-dapp/src/utils/maxSpendableAmount.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/maxSpendableAmount.ts) | Max vs gas reserve | | [`frontend-dapp/src/components/ui/TokenSelect.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/TokenSelect.tsx) / [`TokenSearchSelect.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/TokenSearchSelect.tsx) | Reuse for from / withdraw-as | | [`frontend-dapp/src/hooks/usePortfolioLpBalances.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/hooks/usePortfolioLpBalances.ts) | Wallet LP scan for withdraw picker | | [`frontend-dapp/src/components/pool/PoolPreSubmitSummary.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/pool/PoolPreSubmitSummary.tsx) | SEC-I05 pre-sign card | | [`frontend-dapp/src/utils/poolLpHowtoCopy.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/poolLpHowtoCopy.ts) | #531 copy (H531-3) | | [`frontend-dapp/src/types/index.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/types/index.ts) | `getNativeEquivalent` / `getWrappedEquivalent` | | [`smartcontracts/packages/dex-common/src/pair.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/packages/dex-common/src/pair.rs) | `ProvideLiquidity` / withdraw hook | | [`smartcontracts/contracts/pair/src/contract.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/contracts/pair/src/contract.rs) | Mint/burn + pause | | [`smartcontracts/contracts/router/src/msg.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/contracts/router/src/msg.rs) | Swap ops + unwrap only | | [`docs/frontend.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md) § Pool provide / how-to | Engineering invariants to extend | | [`docs/user-lunc-liquidity.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/user-lunc-liquidity.md) | Retail backup (update with one-sided) | | [`NATIVE_TOKEN_WRAPPING.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/NATIVE_TOKEN_WRAPPING.md) | Wrap multi-msg architecture | | [`frontend-dapp/e2e/wrap-pool.spec.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/e2e/wrap-pool.spec.ts) / [`pool.spec.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/e2e/pool.spec.ts) | Existing Pool / wrap E2E (will break) | ## Recommended direction **Frontend zap on existing messages.** Do not wait on a contract upgrade. ### Retail UI (`/pool`) Replace the per-card two-field expand with one Add card and one Withdraw card (pair list can stay as discovery; selecting a row pre-fills **Pair**). - **Add:** `Token` (balances `> 0` from indexer catalog + `uluna`/`uusd` when wrap env is set) → `Pair` (factory set) → `Amount` + Max/50% → IL notice → compact quote (you add X, pool receives ~Y/Z, estimated LP) → pre-sign → CTA. - **Withdraw:** `LP` (portfolio rows with `balanceRaw > 0`) → `Withdraw as` (pair legs + native equivalents; plus routed tokens only if a route exists) → `Amount` + Max → estimated single-token receive (post-swap, post-unwrap tax if native) → pre-sign → CTA. - **Advanced `<details>`:** current two-sided provide/withdraw (empty-pool bootstrap, manual ratio). Default closed. ### Quote math (new module, e.g. `oneSidedLiquidity.ts`) Classic constant-product zap-in: solve swap amount `s` of the input leg so that `(in − s)` and `swapOut(s)` match **post-swap** reserves, using the pair’s `fee_bps` (and fee-discount if the wallet is registered — same as a pool-only swap). Integer floor; unit-test against LCD `hybrid_simulation` + `estimateProvideLiquidityUserLp`. - Input is native wrap of a pair leg → net CW20 after mapper wrap fee (W8), then zap. - Input **is** a pair leg → zap only. - Input is **not** a pair leg → `GET /route/solve` into one pair leg (prefer the direct/shortest path), then zap. No route → one-sentence disable. - Withdraw: pro-rata both legs → simulate selling the non-output leg (and any leftover of the other leg if output is off-pair) → optional unwrap quote (W9). ### Execution (new builder, e.g. `oneSidedLiquidityTx.ts`) Prefer **one** `executeTerraContractMulti` when gas fits: 1. Optional `treasury.wrap_deposit` (native in). 2. Pair or router swap of the solved slice (`min_return` / `max_spread` from the snapshot). 3. `increase_allowance` ×2 for the **quoted** provide amounts. 4. `provide_liquidity` with `slippage_tolerance` and those amounts. Withdraw sequence: LP `send` withdraw (with `min_assets`) → swap unwanted side → optional mapper unwrap of **that output only**. If the combined tx exceeds a measured gas envelope, split into the fewest txs and **rollback allowances** in one multi-msg on provide failure (#147). New `estimate*UlunaFeesTotal` helpers — no magic LUNC constants. ### Docs / how-to Rewrite H531-3 and `user-lunc-liquidity.md`: one token in, one token out; wrap is automatic when the selected token is native; two-sided is Advanced / empty pool only. ## Acceptance criteria - [ ] **AC1** — Add retail controls are only: token (wallet holdings `> 0`), pair (factory), amount. No wrap checkbox, no second amount, no on-card slippage chips. - [ ] **AC2** — Withdraw retail controls are only: LP (wallet LP `> 0`), token to receive, amount. No “receive wrapped” checkbox. - [ ] **AC3** — Selecting native `uluna`/`uusd` as add-from or withdraw-as wraps/unwraps automatically when wrap env is set; pools still hold CW20 only. - [ ] **AC4** — Zap-in split is ratio-correct after the swap (unit-tested). Provide is not submitted off-ratio in the retail path. - [ ] **AC5** — Empty pool: one-sided add disabled with one short error; two-sided Advanced still bootstraps. - [ ] **AC6** — Submit uses the same settled quote snapshot as the pre-sign card (input, implied swap, min LP / min out, wrap nets, pair address, chain name). - [ ] **AC7** — Every hop has a slippage floor (`min_return` / `slippage_tolerance` / `min_assets`). Native-wrap provide no longer sends `slippage_tolerance: null`. - [ ] **AC8** — Unwrap spends only the zap-out / withdrawn amount — never the rest of the wallet’s cLUNC/cUSTC. - [ ] **AC9** — Gas gate + Max reserve cover the full zap sequence. CTA disabled on pause, blacklist, wrap-mapper pause, treasury mismatch, no route, stale quote, insufficient balance, or LCD/indexer outage (existing banners). - [ ] **AC10** — Token not in the pair and not a native wrap of a pair leg: route-in then zap, or a one-sentence “No route” disable. Foreign/indexer-only pairs are not selectable. - [ ] **AC11** — #531 how-to + `docs/frontend.md` + `docs/user-lunc-liquidity.md` describe one-sided as default; H531-3 updated; no incentive chrome. - [ ] **AC12** — Two-sided path remains reachable under Advanced and still used for empty-pool first deposit. - [ ] **AC13** — `make verify-issue-<iid>` covers unit + scoped frontend tests + Playwright smoke (5 workers) and documents the LocalTerra tx project. ## Test plan (all paths) ### Unit / math | ID | Path | |----|------| | **T1** | Zap-in split vs fixture reserves (equal pool, skewed pool, 6- vs 18-decimal legs, fee 30 bps, fee-discount > 0). | | **T2** | Zap-in with wrap-fee-only net (W8): 10 000 LUNC @ 200 bps → 9 800 CW20 into the solver, not ~9 751. | | **T3** | Zap-out: pro-rata + sell other side; min out after 0.5 / 1 / 5% slippage. | | **T4** | Zap-out to native: post-unwrap-fee then burn tax (W9); `minimum_receive` on the swap stays post-fee pre-tax if router unwrap is used (R3). | | **T5** | Empty reserves → solver returns `unavailable` (no divide-by-zero, no fake price). | | **T6** | Dust / rounding: leftover after provide < 1 raw unit or quote rejected — never a donating provide in retail. | | **T7** | Off-pair input: mocked `route/solve` then zap; empty route → disable reason. | | **T8** | LP decimals 18: Max LP uses chain decimals, not `6`. | | **T9** | Fee envelope: wrap+swap+provide total `> ` wrap-only provide (#213) `>` CW20 three-tx (#147); Max subtracts the matching envelope. | | **T10** | Allowance rollback helper still batches two `decrease_allowance` in one multi-msg. | ### UI / integration | ID | Path | |----|------| | **U1** | Disconnected: pickers + IL visible; CTA is Connect Wallet (same pattern as #494). | | **U2** | Connected, zero holdings: add token list empty / short empty state; withdraw LP list empty. | | **U3** | Holding only LUNC: LUNC appears; choosing a cLUNC pair wraps + zaps; no second amount field. | | **U4** | Holding only a pair CW20: zap without wrap. | | **U5** | Holding a non-leg token with a route: route-in then zap. | | **U6** | Holding a non-leg token with no route: CTA disabled, one sentence. | | **U7** | Pair paused / wallet or token blacklisted / wrap mapper paused / treasury mismatch: CTA disabled, existing short errors. | | **U8** | Empty pool card: one-sided disabled; Advanced two-sided enabled. | | **U9** | Pre-sign shows action, pair, input (or LP), estimated single-token out or estimated LP, min floor, chain. | | **U10** | Stale quote (amount edited mid-fetch) disables submit. | | **U11** | How-to `#lp-howto` no longer says both tokens are required. | | **U12** | Light + dark; no overlay on wallet / clickwrap / CTA. | ### Playwright (5 workers smoke; tx project 1 worker) | ID | Path | |----|------| | **P1** | `/pool` add card: token + pair + amount only (no auto-wrap checkbox, no second asset input). | | **P2** | Withdraw card: LP + withdraw-as + amount only (no receive-wrapped checkbox). | | **P3** | Native LUNC selected as add-from on a wrap-able pair: quote shows wrap, submit not blocked by missing counterpart field. | | **P4** | LocalTerra **e2e-tx**: one-sided add with CW20 pair-leg; LP balance increases; pool reserves move on both sides. | | **P5** | LocalTerra **e2e-tx**: one-sided add with native LUNC (wrap+zap); user spends `uluna`, receives LP, holds no forced leftover of the other native. | | **P6** | LocalTerra **e2e-tx**: withdraw as the input CW20; user receives one CW20; other-leg wallet delta is ~0 (swap consumed it). | | **P7** | LocalTerra **e2e-tx**: withdraw as `uluna`; unwrap amount ≤ withdrawn/zapped output; **pre-existing cLUNC balance is unchanged**. | | **P8** | Empty pool: one-sided CTA disabled; Advanced two-sided first deposit still works. | | **P9** | Pause / blacklist fixtures still block add/withdraw. | | **P10** | Existing `wrap-pool` / `pool-lp-howto-531` specs updated to the new controls (no leftover checkbox assertions). | ## Test plan (attack, hack, and abuse) | ID | Vector | Expect | |----|--------|--------| | **A1** | **Sandwich / front-run** the zap swap-half (mempool sees swap then provide). | `min_return` + `slippage_tolerance` revert the tx; user keeps input (minus gas). No off-ratio donate. Document MEV the same way as Swap (no fake “protected” toggle). | | **A2** | **Stale quote / UI lie** — reserves move after quote, submit uses live typed amounts. | Submit blocked while fetching / key mismatch (#356). Chain floors still protect if broadcast. | | **A3** | **Forced donation** — solver rounds so provide is off-ratio. | Retail path refuses or trims to the minting side and returns dust to the wallet. Never silently donate a material excess. | | **A4** | **Empty-pool grief / first-depositor inflation (P3).** | One-sided disabled. Advanced first deposit still burns `MINIMUM_LIQUIDITY`. No “zap into 0 reserves”. | | **A5** | **Foreign / indexer-only pair** as zap target (fake LP, unaudited pair). | Not in factory picker. Providing to a non-factory address is not a retail path. | | **A6** | **Fake LP token** in the withdraw picker (random CW20). | Picker is portfolio rows whose `lp_token` matches a factory pair. Sending a non-LP CW20 to a pair must fail on-chain; UI must not offer it. | | **A7** | **Unwrap-all drain** — withdraw 1 LP with unwrap, wallet holds a large cLUNC bag. | Only the zap-out amount is unwrapped. Regression test is mandatory (today’s loop is the bug). | | **A8** | **Wrap-mapper pause / wrong treasury (W2)** — `wrap_deposit` would misroute. | CTA disabled; no broadcast. | | **A9** | **Pair pause / blacklist** mid-flow. | Provide/withdraw/swap blocked; no partial “helpful” unwrap of unrelated balances. | | **A10** | **Allowance leftover** after failed provide. | Rollback both allowances in one multi-msg. Do not leave the pair as spender for the quoted amounts. | | **A11** | **Gas grief / fee rewrite** — wallet extension lowers gas below envelope. | Existing LocalTerra post-sign guards; zap uses a documented envelope. Max cannot spend gas LUNC. | | **A12** | **Unlimited unwrap / swap slippage** (`min_return` omitted, `slippage_tolerance: null`). | Forbidden on retail zap. Treat missing floors as a test failure. | | **A13** | **Hybrid book fill in the zap** (unpredictable split). | Pool-only ops only. A book fill that desyncs the provide amounts must not be the default path. | | **A14** | **Route-in to a non-leg token** that then cannot provide (spoofed `route/solve`). | Factory-resolve + pair `simulation` preflight (same spirit as Swap hop preflight). Bad route disables submit. | | **A15** | **Phishing / wrong network** — pre-sign omitted or amounts ≠ form. | SEC-I05 card required; testids on action / pair / amount / chain. | | **A16** | **Social-engineering copy** — extra options, APR, “wrap first on /wrap”. | Retail card stays option-less; wrap is automatic; no farm chrome; how-to remains opt-in. | | **A17** | **Expert-mode bypass** — zap price impact > 30% / ≥ 99%. | Same Expert Mode / extreme-slippage gates as Swap. | | **A18** | **Re-entrancy / multi-msg ordering** — swap credits pair before allowances, or unwrap before withdraw settles. | Message order specified and covered by builder unit tests; CosmWasm tx is atomic per broadcast. | | **A19** | **Dust spam / min-amount** — zap of 1 raw unit. | Quote unavailable or CTA disabled below a dust threshold; no revert storm. | | **A20** | **Clickwrap / NFA bypass** via new Pool entry. | Existing gates still wrap `/pool` (C1–C10, #138). | ## Verification criteria Close when **all** of the following are true: 1. AC1–AC13 checked on the MR. 2. `make verify-issue-<iid>` is green (unit + scoped frontend + Playwright smoke). 3. LocalTerra **e2e-tx** P4–P8 recorded (add CW20, add native, withdraw as CW20, withdraw as native **without** draining leftover wrap, empty-pool Advanced). 4. A7 unwrap-all regression is in CI, not a manual note. 5. `#531` how-to / `user-lunc-liquidity.md` / `docs/frontend.md` Pool section match the shipped UI (H531-3 rewritten). 6. No new pair/router execute message **or** an explicit follow-up issue if rehearsal proved one is required (do not silently ship a contract upgrade in this issue). ```bash make verify-issue-<iid> make test-frontend # scoped (adjust paths to the new modules): # oneSidedLiquidity + oneSidedLiquidityTx + PoolPage + poolLpHowtoCopy # Playwright smoke (5 workers): # frontend-dapp/e2e/pool-one-sided-<iid>.spec.ts # LocalTerra tx (1 worker): # sg docker -c 'CI=1 make test-e2e' # or the dedicated e2e-tx file ```
PlasticDigits commented 2026-08-16 10:40:23 +00:00 (Migrated from gitlab.com)

mentioned in commit 54365e4e86

mentioned in commit 54365e4e86f9f504a94103c772068d767cb7afe8
PlasticDigits commented 2026-08-17 01:33:50 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1068

mentioned in merge request !1068
PlasticDigits commented 2026-08-17 03:39:50 +00:00 (Migrated from gitlab.com)

mentioned in commit a5639d8ba9

mentioned in commit a5639d8ba9795acc2e4a8f6f143685924fd2528d
PlasticDigits commented 2026-08-17 03:41:26 +00:00 (Migrated from gitlab.com)

mentioned in commit 52c8693c5a

mentioned in commit 52c8693c5a60eba57037c6dd9f5cdcae037d783f
PlasticDigits commented 2026-08-17 03:42:14 +00:00 (Migrated from gitlab.com)

!1068 merged to main (3eceb216). MR description was changed to Related (not Closes) so this issue stays open until close criteria 3 is met.

Sanity after merge:

  • VERIFY_ISSUE_533_SKIP_E2E=1 make verify-issue-533 — 6/6 (133 Vitest)
  • Playwright P1–P3 (pool-one-sided-533.spec.ts, e2e-smoke) — 3/3 pass
  • A7 unwrap-all is in CI (oneSidedLiquidityTx.test.ts)
  • I14: one-sided zap quotes now pass the selected pair into useFeeDiscountRegistryStatus (merge fix with !1066)

Still blocking close:

  • LocalTerra e2e-tx P4–P8 not recorded. Deploy still instantiates wrap-mapper with legacy fee_bps (scripts/deploy-dex-local.sh); newer wasm expects fee_wrap_bps / fee_unwrap_bps (#516).
  • Manual QA unchecked (disconnected, empty holdings, pause/blacklist/mapper, light/dark)

A focused follow-up issue will track the deploy instantiate + P4–P8 recording.

!1068 merged to `main` (`3eceb216`). MR description was changed to **Related** (not Closes) so this issue stays open until close criteria 3 is met. Sanity after merge: - `VERIFY_ISSUE_533_SKIP_E2E=1 make verify-issue-533` — 6/6 (133 Vitest) - Playwright P1–P3 (`pool-one-sided-533.spec.ts`, e2e-smoke) — 3/3 pass - A7 unwrap-all is in CI (`oneSidedLiquidityTx.test.ts`) - I14: one-sided zap quotes now pass the selected pair into `useFeeDiscountRegistryStatus` (merge fix with !1066) Still blocking close: - LocalTerra e2e-tx **P4–P8** not recorded. Deploy still instantiates wrap-mapper with legacy `fee_bps` (`scripts/deploy-dex-local.sh`); newer wasm expects `fee_wrap_bps` / `fee_unwrap_bps` (#516). - Manual QA unchecked (disconnected, empty holdings, pause/blacklist/mapper, light/dark) A focused follow-up issue will track the deploy instantiate + P4–P8 recording.
PlasticDigits commented 2026-08-17 03:42:25 +00:00 (Migrated from gitlab.com)

mentioned in issue #539

mentioned in issue #539
PlasticDigits commented 2026-08-17 03:42:25 +00:00 (Migrated from gitlab.com)

marked as related to #539

marked as related to #539
PlasticDigits commented 2026-08-17 03:42:36 +00:00 (Migrated from gitlab.com)

Post-merge tracker for remaining close criteria: #539 (LocalTerra wrap-mapper split-fee instantiate + record e2e-tx P4–P8).

Post-merge tracker for remaining close criteria: #539 (LocalTerra wrap-mapper split-fee instantiate + record e2e-tx P4–P8).
PlasticDigits commented 2026-08-17 09:23:43 +00:00 (Migrated from gitlab.com)

mentioned in commit 60e407771591c7045cbf1904262a1a7a8720064b

mentioned in commit 60e407771591c7045cbf1904262a1a7a8720064b
PlasticDigits commented 2026-08-17 09:26:58 +00:00 (Migrated from gitlab.com)

mentioned in commit a99c91d6a5

mentioned in commit a99c91d6a54e4a367a0abd9dceaeab9260c5ff00
PlasticDigits commented 2026-08-17 09:31:30 +00:00 (Migrated from gitlab.com)

#539 e2e-tx P4–P8 recorded locally: pool-one-sided-533-tx.spec.ts 4 passed (P4, P5 wrap+zap, P6/P7, P8).

Instantiate + verify live on !1072. Keep this issue open until that MR merges (parent gate from #539).

#539 e2e-tx P4–P8 recorded locally: `pool-one-sided-533-tx.spec.ts` **4 passed** (P4, P5 wrap+zap, P6/P7, P8). Instantiate + verify live on !1072. Keep this issue open until that MR merges (parent gate from #539).
PlasticDigits commented 2026-08-17 09:59:12 +00:00 (Migrated from gitlab.com)

#539 is closed. LocalTerra e2e-tx P4–P8 recorded 4 passed (pool-one-sided-533-tx.spec.ts: CW20 add, native LUNC wrap+zap, withdraw, empty-pool). Closing the parent.

#539 is closed. LocalTerra e2e-tx P4–P8 recorded **4 passed** (`pool-one-sided-533-tx.spec.ts`: CW20 add, native LUNC wrap+zap, withdraw, empty-pool). Closing the parent.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-17 09:59:23 +00:00
PlasticDigits commented 2026-08-17 10:26:07 +00:00 (Migrated from gitlab.com)

mentioned in issue #547

mentioned in issue #547
PlasticDigits commented 2026-08-17 10:26:11 +00:00 (Migrated from gitlab.com)

marked as related to #547

marked as related to #547
PlasticDigits commented 2026-08-17 10:32:47 +00:00 (Migrated from gitlab.com)

mentioned in issue #549

mentioned in issue #549
PlasticDigits commented 2026-08-17 10:32:49 +00:00 (Migrated from gitlab.com)

marked as related to #549

marked as related to #549
PlasticDigits commented 2026-08-18 00:43:30 +00:00 (Migrated from gitlab.com)

marked as related to #559

marked as related to #559
PlasticDigits commented 2026-08-18 00:43:30 +00:00 (Migrated from gitlab.com)

mentioned in issue #559

mentioned in issue #559
PlasticDigits commented 2026-08-22 10:59:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #592

mentioned in issue #592
PlasticDigits commented 2026-08-22 11:02:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #595

mentioned in issue #595
PlasticDigits commented 2026-08-26 04:11:19 +00:00 (Migrated from gitlab.com)

mentioned in issue #660

mentioned in issue #660
PlasticDigits commented 2026-08-26 04:11:20 +00:00 (Migrated from gitlab.com)

marked as related to #660

marked as related to #660
PlasticDigits commented 2026-08-26 04:12:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #661

mentioned in issue #661
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#533
No description provided.