Skip to main content

Deposit & Withdraw

LT.vy is ERC-4626-like but not strictly compliant. It emits ERC-4626 Deposit / Withdraw events, but its deposit takes an explicit debt parameter (not computed from assets) and it does not implement convertToAssets, convertToShares, mint, redeem, previewMint, or previewRedeem. It implements IERC20 only. Transfers to/from the staker address are rejected via explicit revert. For strict ERC-4626 semantics, use the LiquidityGauge (staker) instead, which wraps LT shares.

This page covers the integration-side call pattern. For the on-chain sequence (what LT does with your deposit), see Action Flow: Deposit and Action Flow: Withdraw.

Deposit

Signature

def deposit(
assets: uint256,
debt: uint256,
min_shares: uint256,
receiver: address = msg.sender
) -> uint256
  • assets — amount of the volatile asset (e.g. WBTC) you are depositing.
  • debt — crvUSD amount LEVAMM will borrow to maintain L=2L = 2. For an LT deposit, this is typically the oracle value of the volatile leg: assets × oracle_price / 10**asset_decimals, denominated in crvUSD wei.
  • min_shares — sandwich/slippage protection.
  • receiver — destination for minted yb-LP. Must not equal LT.staker(); use StakeZap for atomic deposit + stake.

Call pattern

# 1. Compute expected debt at current oracle price
p_o = PriceOracle(oracle).price()
debt_estimate = assets * p_o // 10**asset_decimals

# 2. Preview to get expected shares (reverts if Debt too high)
shares_estimate = lt.preview_deposit(assets, debt_estimate)
# Soft quote alternative: returns 0 instead of reverting on overflow.
# Used by LTMigrator and HybridVault.
# shares_estimate = lt.preview_deposit(assets, debt_estimate, False)

# 3. Approve + deposit
asset.approve(lt, assets)
shares = lt.deposit(assets, debt_estimate, min_shares, receiver)

Worked example: if the asset uses 18 decimals, assets = 1e18, and the oracle price is 100_000e18 crvUSD per asset, then debt_estimate = 100_000e18 crvUSD. The AMM's L = 2 target is applied to the resulting Cryptoswap LP collateral value; because the deposit adds both the volatile leg and the borrowed stable leg, the borrowed leg is roughly equal to the volatile leg's oracle value.

Error modes

ErrorCause
"Deposit to staker"receiver == LT.staker(). Either pass a different receiver or use StakeZap.
"Debt too high"debt exceeds AMM.max_debt() / 2. Reduce debt or wait for allocation.
"Slippage"shares < min_shares. Re-quote via preview_deposit and retry.
"Remainder too small"Final supply after mint would be below MIN_SHARE_REMAINDER (1e6). Increase deposit or combine with another.

Gas

~ 300–500k on mainnet. Dominated by Cryptoswap add_liquidity and LEVAMM invariant solve.

Withdraw

Signature

def withdraw(
shares: uint256,
min_assets: uint256,
receiver: address = msg.sender
) -> uint256

Returns the amount of underlying asset received.

Call pattern

# If staked, unstake first
if gauge.balanceOf(user) > 0:
gauge.withdraw(gauge.balanceOf(user)) # returns yb-LP to user

# Quote redemption (includes TRD)
expected_assets = lt.preview_withdraw(shares)

# Withdraw with slippage bound
assets = lt.withdraw(shares, min_assets, receiver)

Error modes

ErrorCause
"Withdrawing nothing"shares == 0.
"Withdraw to/from staker"Either msg.sender or receiver equals LT.staker(). Unstake from gauge first.
"We're dead. Use emergency_withdraw"AMM.is_killed() == True. Use emergency_withdraw instead; repay debt from wallet.
"Slippage"crypto_received < min_assets. Re-quote and retry.
"Remainder too small"Post-burn, supply would fall below MIN_SHARE_REMAINDER. Withdraw less, or (if full exit) withdraw all shares atomically — the last-withdrawer path is special-cased.

preview_withdraw vs pricePerShare

Both speak to per-share value but via different reference prices:

  • pricePerShare() — returns v.total × 1e18 / v.supply_tokens from _calculate_values at oracle price. The fundamental per-share value; sandwich-resistant; does not include TRD. Multiply by shares / 1e18 for per-position PPS.
  • preview_withdraw(shares) — realistic redemption: runs calc_withdraw_fixed_out against the current Cryptoswap state, factoring the fixed-debt-output unwind. Includes TRD.

TRD = preview_withdraw(1e18) / pricePerShare() − 1 for one share, or generally preview_withdraw(shares) × 1e18 / (pricePerShare() × shares) − 1. Negative during a discount (the common case); positive in the rare premium case. See Temporary Redemption Discount for resolution dynamics.

Note: LT does not expose convertToAssets — it implements IERC20 only. The ERC-4626 convertToAssets/previewRedeem shape lives on LiquidityGauge instead, where it maps gauge-shares to underlying LT-shares.

Gas

~ 300–450k. Dominated by Cryptoswap remove_liquidity_fixed_out and LEVAMM accounting.

Staked positions

If the user has yb-LP staked in LiquidityGauge:

  1. Call gauge.withdraw(amount) — transfers yb-LP back to user, forfeiting future emissions. Accrued emissions are claimable separately.
  2. Then call LT.withdraw as above.

There is no combined "unstake + withdraw" helper. Use multicall if atomicity matters.

Preview reliability

Preview functions are view calls that read current state. In the same block, they are accurate. Across blocks, state may change (oracle updates, rebalance trades, other LPs' deposits/withdrawals).

For MEV-sensitive flows:

  • Quote in the simulation, then execute with tight min_* bounds.
  • preview_withdraw varies with Cryptoswap state — re-quote if you observe large trades in the mempool.
  • preview_deposit varies with LEVAMM invariant state — same advice.

Events

EventWhen
Deposit(sender, owner, assets, shares)Every deposit
Withdraw(sender, receiver, owner, assets, shares)Every withdraw
Transfer(from, to, value)ERC-20 transfers of yb-LP, plus bucket-reallocation transfers on stake/unstake
DistributeBorrowerFees(sender, amount, min_amount, discount)When LT routes borrower interest to the donation path