feat: one-sided liquidity add and withdraw (auto zap, including wrap) #533
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#533
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Retail
/poolstill 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:
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.
ProvideLiquidity { assets: [Asset; 2], slippage_tolerance?, receiver?, deadline? }always takes both legs. Later deposits mintmin(amount_a × share / reserve_a, amount_b × share / reserve_b). Off-ratio excess is donated to the pool. First deposit (both reserves0) issqrt(a×b) − MINIMUM_LIQUIDITY(1000 LP units; LP CW20 decimals = 18). Withdraw is CW20send→WithdrawLiquidity { min_assets? }and returns both underlyings. Pause blocks provide/withdraw.ExecuteSwapOperations+ optionalunwrap_output. No provide/withdraw / zap message. Native input wrap is never on the router (would double-tax); frontend multi-msgtreasury.wrap_depositthen CW20 send./pooladd UI/poolwithdraw UIgetTokenBalanceof that CW20 (entire wallet balance, not the withdrawn amount). That is a custody bug and must not survive this work.executeTerraContractMultiofwrap_deposit×1–2 +increase_allowance×2 +provide_liquidity(#213). Wrap fee isfee_wrap_bpsonly (W8);wrap_depositis not burn-taxed. Withdraw unwrap uses mapperfee_unwrap_bpsthen treasury InstantWithdraw (burn-taxed, W9).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”.min_returnon a swap-half, and native-wrap provide currently passesslippage_tolerance: null.PoolPage.tsxand portfolio useLP_DECIMALS = 6while the LP CW20 is 18. Do not copy that into the new form.Why this is needed
/wrapare easy to miss; the new flow must treat native as just another from / as token.Constraints / guardrails
uluna/uusd↔ cLUNC / cUSTC via existing mapper + treasury. Do not send native into the pair or the router.provide_liquidityoff-ratio without an Advanced override.sqrt+MINIMUM_LIQUIDITY).poolOnlyHybridParams.max_spread/min_return. Provide:slippage_tolerance(stop passingnull). Withdraw:min_assets. Output swap:min_return. Unwrap: quote post-fee post-tax (W9); do not promise “2% flat”.ulunamust 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.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.Zapexecute unless LocalTerra rehearsal proves multi-msg gas/atomicity cannot work. Default: frontend orchestration on existing messages.6leftover.Relevant files
frontend-dapp/src/pages/PoolPage.tsxfrontend-dapp/src/services/terraclassic/pair.tsprovideLiquidity(3 txs) /withdrawLiquidityfrontend-dapp/src/services/terraclassic/transactions.tsfrontend-dapp/src/services/terraclassic/router.tsunwrap_output;netCw20AfterNativeWrap/netNativeAfterUnwrapfrontend-dapp/src/services/terraclassic/wrapMapper.tsfrontend-dapp/src/utils/poolProvideCounterpart.tsfrontend-dapp/src/utils/provideLiquidityEstimate.tsfrontend-dapp/src/utils/rawAmountMath.tsfrontend-dapp/src/utils/provideLiquidityNativeGasBalanceGate.tsfrontend-dapp/src/utils/maxSpendableAmount.tsfrontend-dapp/src/components/ui/TokenSelect.tsx/TokenSearchSelect.tsxfrontend-dapp/src/hooks/usePortfolioLpBalances.tsfrontend-dapp/src/components/pool/PoolPreSubmitSummary.tsxfrontend-dapp/src/utils/poolLpHowtoCopy.tsfrontend-dapp/src/types/index.tsgetNativeEquivalent/getWrappedEquivalentsmartcontracts/packages/dex-common/src/pair.rsProvideLiquidity/ withdraw hooksmartcontracts/contracts/pair/src/contract.rssmartcontracts/contracts/router/src/msg.rsdocs/frontend.md§ Pool provide / how-todocs/user-lunc-liquidity.mdNATIVE_TOKEN_WRAPPING.mdfrontend-dapp/e2e/wrap-pool.spec.ts/pool.spec.tsRecommended 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).
Token(balances> 0from indexer catalog +uluna/uusdwhen 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.LP(portfolio rows withbalanceRaw > 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.<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
sof the input leg so that(in − s)andswapOut(s)match post-swap reserves, using the pair’sfee_bps(and fee-discount if the wallet is registered — same as a pool-only swap). Integer floor; unit-test against LCDhybrid_simulation+estimateProvideLiquidityUserLp.GET /route/solveinto one pair leg (prefer the direct/shortest path), then zap. No route → one-sentence disable.Execution (new builder, e.g.
oneSidedLiquidityTx.ts)Prefer one
executeTerraContractMultiwhen gas fits:treasury.wrap_deposit(native in).min_return/max_spreadfrom the snapshot).increase_allowance×2 for the quoted provide amounts.provide_liquiditywithslippage_toleranceand those amounts.Withdraw sequence: LP
sendwithdraw (withmin_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*UlunaFeesTotalhelpers — 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
> 0), pair (factory), amount. No wrap checkbox, no second amount, no on-card slippage chips.> 0), token to receive, amount. No “receive wrapped” checkbox.uluna/uusdas add-from or withdraw-as wraps/unwraps automatically when wrap env is set; pools still hold CW20 only.min_return/slippage_tolerance/min_assets). Native-wrap provide no longer sendsslippage_tolerance: null.docs/frontend.md+docs/user-lunc-liquidity.mddescribe one-sided as default; H531-3 updated; no incentive chrome.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
minimum_receiveon the swap stays post-fee pre-tax if router unwrap is used (R3).unavailable(no divide-by-zero, no fake price).route/solvethen zap; empty route → disable reason.6.>wrap-only provide (#213)>CW20 three-tx (#147); Max subtracts the matching envelope.decrease_allowancein one multi-msg.UI / integration
#lp-howtono longer says both tokens are required.Playwright (5 workers smoke; tx project 1 worker)
/pooladd card: token + pair + amount only (no auto-wrap checkbox, no second asset input).uluna, receives LP, holds no forced leftover of the other native.uluna; unwrap amount ≤ withdrawn/zapped output; pre-existing cLUNC balance is unchanged.wrap-pool/pool-lp-howto-531specs updated to the new controls (no leftover checkbox assertions).Test plan (attack, hack, and abuse)
min_return+slippage_tolerancerevert the tx; user keeps input (minus gas). No off-ratio donate. Document MEV the same way as Swap (no fake “protected” toggle).MINIMUM_LIQUIDITY. No “zap into 0 reserves”.lp_tokenmatches a factory pair. Sending a non-LP CW20 to a pair must fail on-chain; UI must not offer it.wrap_depositwould misroute.min_returnomitted,slippage_tolerance: null).route/solve).simulationpreflight (same spirit as Swap hop preflight). Bad route disables submit./pool(C1–C10, #138).Verification criteria
Close when all of the following are true:
make verify-issue-<iid>is green (unit + scoped frontend + Playwright smoke).#531how-to /user-lunc-liquidity.md/docs/frontend.mdPool section match the shipped UI (H531-3 rewritten).mentioned in commit
54365e4e86mentioned in merge request !1068
mentioned in commit
a5639d8ba9mentioned in commit
52c8693c5a!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)pool-one-sided-533.spec.ts, e2e-smoke) — 3/3 passoneSidedLiquidityTx.test.ts)useFeeDiscountRegistryStatus(merge fix with !1066)Still blocking close:
fee_bps(scripts/deploy-dex-local.sh); newer wasm expectsfee_wrap_bps/fee_unwrap_bps(#516).A focused follow-up issue will track the deploy instantiate + P4–P8 recording.
mentioned in issue #539
marked as related to #539
Post-merge tracker for remaining close criteria: #539 (LocalTerra wrap-mapper split-fee instantiate + record e2e-tx P4–P8).
mentioned in commit 60e407771591c7045cbf1904262a1a7a8720064b
mentioned in commit
a99c91d6a5#539 e2e-tx P4–P8 recorded locally:
pool-one-sided-533-tx.spec.ts4 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 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.mentioned in issue #547
marked as related to #547
mentioned in issue #549
marked as related to #549
marked as related to #559
mentioned in issue #559
mentioned in issue #592
mentioned in issue #595
mentioned in issue #660
marked as related to #660
mentioned in issue #661