OptimalDepositSplit
OptimalDepositSplit is the read-only, stateless projection of the optimal swap fraction for a single-sided V2 zap-in. It tells you what SwapDeposit would do against the current pool state without executing the swap or the deposit.
V2 only — V3 raises ValueError (the V3 extension is blocked on the upstream UniV3Helper.quote hard-coded fee, tracked on the cleanup backlog).
Signature at a glance
Section titled “Signature at a glance”| Protocol | Required call shape |
|---|---|
| Uniswap V2 | OptimalDepositSplit().apply(lp, token_in, amount_in) |
| Uniswap V3 | ❌ Raises ValueError (deferred, pending UniV3Helper fix) |
| Balancer | ❌ Not applicable — AddLiquidity already accepts single-asset deposits |
| Stableswap | ❌ Not applicable — same architectural reason as Balancer |
Common parameters
Section titled “Common parameters”| Parameter | Type | Description |
|---|---|---|
lp | UniswapExchange | V2 LP exchange at current state. V3 raises. |
token_in | ERC20 | The token being provided (the “single side”). Must be one of the pool’s two tokens. |
amount_in | float | Total human-units quantity of token_in to zap. Must be > 0. |
The V2 zap-in problem: starting with dx of token_in, swap fraction α of it for token_out, then deposit the remaining (1-α)·dx paired with the swap output. The optimum minimizes leftover dust.
Closed-form quadratic (for V2, with fee multiplier f = 0.997):
where r is the input-side reserve. Solving via the quadratic formula gives the unique α ∈ (0, 1) consistent with the V2 add-liquidity invariant.
Limit at zero size. As dx → 0,
The naive answer is 50/50; the slight upward bias comes from fee asymmetry — the swap leg eats a 0.3% fee that the deposit leg doesn’t, so a touch more goes to the swap side at infinitesimal size.
Direction of change (the counterintuitive result). Implicit differentiation of the quadratic gives
So α decreases monotonically with deposit size. The naive intuition “the fee eats my swap so I should swap more to compensate” is wrong: the fee is already baked into get_amount_out, and what dominates at size is the AMM curve’s nonlinearity. A larger swap moves the spot price more → each unit swapped buys less of the opposing token → you swap less.
This direction was wrong-signed in an earlier test iteration; failing tests forced the derivation. Naming this in the docs prevents future readers (and LLMs composing primitives) from making the same mistake.
Slippage and expected LP tokens. Reported in token_out units:
where p_spot = reserve_out / reserve_in. Expected LP tokens come from V2’s add_liquidity mint formula L_new = min(b₀·L/res₀, b₁·L/res₁) against post-swap reserves — the primitive computes the same thing the executor would compute.
Example
Section titled “Example”from defipy import OptimalDepositSplitfrom defipy.twin import MockProvider, StateTwinBuilder
provider = MockProvider()builder = StateTwinBuilder()lp_v2 = builder.build(provider.snapshot("eth_dai_v2"))v2_tokens = lp_v2.factory.token_from_exchange[lp_v2.name]
result = OptimalDepositSplit().apply( lp_v2, token_in = v2_tokens["ETH"], amount_in = 100.0,)
print(f"token_in: {result.token_in_name}")print(f"amount_in: {result.amount_in}")print(f"optimal_fraction: {result.optimal_fraction:.6f}")print(f"swap_amount_in: {result.swap_amount_in:.4f}")print(f"swap_amount_out: {result.swap_amount_out:.4f}")print(f"deposit_amount_in: {result.deposit_amount_in:.4f}")print(f"deposit_amount_out: {result.deposit_amount_out:.4f}")print(f"expected_lp_tokens: {result.expected_lp_tokens:.4f}")print(f"slippage_cost: {result.slippage_cost:.4f}")print(f"slippage_pct: {result.slippage_pct:.6f}")optimal_fraction = 0.4888 — below the 0.5008 zero-size limit because this 100 ETH zap is meaningful relative to the 1000 ETH reserve, so the curve’s nonlinearity has bitten. The closed-form does not reject deposits that are large fractions of reserves — slippage_pct (~4.9% here) surfaces the friction; the caller decides whether to proceed.
How this composes
Section titled “How this composes”- Delegates to
SwapDeposit._calc_univ2_deposit_portion(inuniswappy.process.deposit) for the closed-formα. That same helper is the math heart of the executingSwapDepositprimitive — the projection and the executor share the solve. - Composed into by
EvaluateRebalancewhen projecting the redeposit leg of a withdraw → swap → re-zap cycle.
See also
Section titled “See also”SwapDeposit— the executing counterpart; sameα, mutatingEvaluateRebalance— composes this primitive for the redeposit legCalculateSlippage— slippage on the swap leg in isolation- The Primitive Contract — cross-cutting invariants
- MCP tool exposure: Not in the curated 10. Optimization questions are typically composed LLM-side after a position-level analysis.