Skip to content

WithdrawSwap

Exit a V2/V3 LP position into a single token. WithdrawSwap is a mutating dispatch primitive that removes liquidity and also swaps the unwanted side back into the requested token, so the user ends up holding only token_out. V2/V3 only — see the architectural reason below.

ProtocolRequired call shape
Uniswap V2WithdrawSwap().apply(lp, token_out, user_nm, amount_out)
Uniswap V3WithdrawSwap().apply(lp, token_out, user_nm, amount_out, lwr_tick, upr_tick)
Balancer❌ Not supported — see “Why no Balancer/Stableswap section” below
Stableswap❌ Not supported
ParameterTypeDescription
lpExchangeInitialized V2 or V3 pool with the user’s LP shares to burn.
token_outERC20The single token the user wants to receive.
user_nmstrAccount name whose LP shares are burned and who receives token_out.
amount_outfloatTotal quantity of token_out the user expects to walk away with (combined burn + swap).

The dispatcher computes the optimal split between “burn proceeds” and “swap proceeds” via _calc_univ2_withdraw_portion (closed-form using the pool reserves and the V2 0.997 fee multiplier), then executes:

  1. RemoveLiquidity for p_out * amount_out of token_out — returns both tokens.
  2. Swap the other token’s proceeds back into token_out.
  3. Combined output is (swap result) + (RemoveLiquidity's token_out side).
ParameterTypeNotes
(no extras)Common parameters only.
from defipy import WithdrawSwap
# Burn ETH/DAI LP shares; walk away with only DAI
withdrawn = WithdrawSwap().apply(lp, dai, "user", 1000)
# Returns the total DAI received (burn leg + swap leg combined)

V3 uses the same two-step pattern, but the burn calculation goes through SettlementLPToken to compute the in-range L from the requested amount_out and the active sqrt-price.

ParameterTypeNotes
lwr_tickintLower tick of the position to withdraw from. Must match the original mint.
upr_tickintUpper tick.
from defipy import WithdrawSwap
WithdrawSwap().apply(lp, eth, "user", 1, lwr_tick, upr_tick)
# Burns position liquidity inside [lwr_tick, upr_tick] and swaps the
# non-eth side back into ETH; returns total ETH received.

WithdrawSwap exists because V2/V3’s RemoveLiquidity returns both sides of the pair — getting back to a single asset requires an extra swap. Balancer and Stableswap don’t have this problem: their RemoveLiquidity already supports specifying token_out and returning only that asset (exit_swap_extern_amount_out for Balancer, lp.remove_liquidity(amount, token_out, user) for Stableswap). The pool math handles the imbalance against the invariant directly.

Calling WithdrawSwap().apply(lp, ...) against a BalancerExchange or StableswapExchange will fail at the V2/V3 version check (the dispatcher is V2/V3-specific). Use RemoveLiquidity on those protocols.

How WithdrawSwap interacts with the rest of the pipeline

Section titled “How WithdrawSwap interacts with the rest of the pipeline”
  1. JoinAddLiquidity — pool initialized and LP positions held.
  2. Pre-exit analyticsAnalyzePosition decomposes IL vs accumulated fees so the user can decide whether to exit; CalculateSlippage projects the price impact of the internal swap leg.
  3. WithdrawSwap — burn + swap to single asset (this primitive).