Skip to content

SwapDeposit

Convert a single-token holding into a balanced LP position in one shot. SwapDeposit is a mutating dispatch primitive that internally swaps an optimal fraction of the input, then deposits the resulting two-sided balance. V2/V3 only — see the architectural reason below.

ProtocolRequired call shape
Uniswap V2SwapDeposit().apply(lp, token_in, user_nm, amount_in)
Uniswap V3SwapDeposit().apply(lp, token_in, user_nm, amount_in, lwr_tick, upr_tick)
Balancer❌ Not supported — see “Why no Balancer/Stableswap section” below
Stableswap❌ Not supported
ParameterTypeDescription
lpExchangeInitialized V2 or V3 pool with active liquidity.
token_inERC20The single token the user holds and wants to deposit on both sides.
user_nmstrAccount name receiving the resulting LP credit.
amount_infloatTotal quantity of token_in available to spend (swap + deposit combined).

The dispatcher solves a closed-form quadratic for the optimal swap fraction alpha, accounting for V2’s 0.3% fee, then runs:

  1. Swap alpha * amount_in of token_in for the other token.
  2. lp.quote(...) the matching amount of token_in against the post-swap reserves.
  3. lp.add_liquidity(user_nm, balance0, balance1, balance0, balance1) to mint LP shares.
ParameterTypeNotes
(no extras)Common parameters only.
from defipy import SwapDeposit
# Hold 1000 DAI; want a balanced ETH/DAI LP position
deposited = SwapDeposit().apply(lp, dai, "user", 1000)
# Returns the total `token_in`-denominated value deposited (swap leg + deposit leg)

The closed-form quadratic lives at SwapDeposit._calc_univ2_deposit_portion. It’s the same math the read-only OptimalDepositSplit primitive composes against — same alpha, no mutation.

V3 doesn’t admit a closed-form solution because the in-range liquidity-to-amount relationship varies with the sqrt-price across the range. The dispatcher uses scipy.optimize.minimize(method='Nelder-Mead', bounds=[(0.35, 0.65)]) to find the optimal swap fraction numerically, then:

  1. Swap that fraction of token_in.
  2. Compute the in-range liquidity L from the post-swap balance using UniV3Helper.calc_Lx / calc_Ly.
  3. lp.mint(user_nm, lwr_tick, upr_tick, L) to mint the position.
ParameterTypeNotes
lwr_tickintLower tick of the position. Must be tick_spacing-aligned.
upr_tickintUpper tick.
from defipy import SwapDeposit, UniV3Utils
tick_spacing = 60
lwr_tick = UniV3Utils.getMinTick(tick_spacing)
upr_tick = UniV3Utils.getMaxTick(tick_spacing)
deposited = SwapDeposit().apply(lp, dai, "user", 1000, lwr_tick, upr_tick)

SwapDeposit exists because V2/V3 deposits at the pool level need balanced two-sided amounts — providing a single token requires a manual swap step first. Balancer and Stableswap don’t have this constraint: AddLiquidity already accepts a single-token-in call shape and the pool math handles the imbalance against the invariant directly. There’s no swap-then-deposit pattern to abstract because the underlying primitive already does what you’d want.

The dispatcher reflects this — the defipy.process.deposit namespace re-exports only uniswappy.process.deposit.SwapDeposit, so calling SwapDeposit().apply(lp, ...) against a BalancerExchange or StableswapExchange will fail at the version check (no .version attribute on those exchanges). Use AddLiquidity for those protocols.

How SwapDeposit interacts with the rest of the pipeline

Section titled “How SwapDeposit interacts with the rest of the pipeline”
  1. JoinAddLiquidity — pool initialized.
  2. Pre-deposit projectionOptimalDepositSplit (V2 only) projects the same alpha SwapDeposit will use, plus the resulting balance, without mutating.
  3. SwapDeposit — execute the zap-in (this primitive).
  4. Post-deposit analyticsAnalyzePosition decomposes the resulting position over time.