Skip to main content

LT (Leveraged Token)

The source code of the LT.vy contract can be found on GitHub. The contract is written with Vyper version 0.4.3.

Overview

The LT contract is the market’s leveraged-liquidity share token (ERC-20, 18 decimals; symbol yb-asset). Users deposit the asset token (e.g., BTC) while the contract borrows a matching amount of stablecoin liquidity from the market (via its allocation) to mint Curve LP and hand the LP into the LEVAMM. In return, users receive LT shares that represent a pro-rata claim on the AMM position’s value. Withdrawals burn shares and unwind through the AMM and Curve pool back to asset (and any required stablecoin settlement). The contract exposes standard deposit/withdraw flows with slippage guards and a fair, oracle-anchored price-per-share.

LT also manages the yb markets: it owns the LEVAMM instance, forwards interest-rate and fee updates to the AMM, manages the stablecoin allocation to the AMM (allocate_stablecoins), and periodically sweeps borrower interest collected in the AMM into the Curve pool as LP tokens (distribute_borrower_fees).

An optional staker address can be set once to integrate with gauges/rewards, and admin fees are tracked and withdrawable to the protocol’s fee receiver. Safety is enforced with access controls (admin/factory admins), non-reentrancy on state-changing entry points, kill/unkill passthrough to the AMM, and oracle-based checks to keep the position within safe bounds.

Deployed by the Factory: LT instances are cloned from the configured implementation using Vyper’s create_from_blueprint(asset_token, stablecoin, cryptopool, admin_or_factory), then connected to the market’s LEVAMM and pricing oracle during market creation.


Function Documentation

Public Variables

The LT contract exposes several public variables for reading contract state and configuration.

Constants

  • CRYPTOPOOL_N_COINS (uint256) - Number of coins in the crypto pool (2)
  • FEE_CLAIM_DISCOUNT (uint256) - Fee claim discount (10^16 = 1%)
  • MIN_SHARE_REMAINDER (uint256) - Minimum shares to leave if > 0 (10^6)
  • SQRT_MIN_UNSTAKED_FRACTION (int256) - Minimum unstaked fraction (10^14 = 1e-4)
  • MIN_STAKED_FOR_FEES (int256) - Minimum staked amount for fees (10^16)

Immutable Variables

  • CRYPTOPOOL (CurveCryptoPool) - Curve crypto pool contract (immutable)
  • STABLECOIN (IERC20) - Stablecoin contract (immutable)
  • ASSET_TOKEN (IERC20) - Asset token contract (immutable)

State Variables

  • admin (address) - Admin address
  • amm (LevAMM) - AMM contract address
  • agg (PriceOracle) - Price aggregator contract
  • staker (address) - Staker contract address
  • liquidity (LiquidityValues) - Liquidity state struct
  • allowance (HashMap[address, HashMap[address, uint256]]) - ERC20 allowances
  • balanceOf (HashMap[address, uint256]) - ERC20 balances
  • totalSupply (uint256) - Total token supply
  • decimals (uint8) - Token decimals (18)
  • stablecoin_allocation (uint256) - Stablecoin allocation limit
  • stablecoin_allocated (uint256) - Currently allocated stablecoins

Data Structs

AMMState

  • collateral (uint256) - LP token amount held as collateral
  • debt (uint256) - Interest-accrued stablecoin debt
  • x0 (uint256) - Virtual balance for maintaining 2x leverage

Pair

  • collateral (uint256) - LP token amount
  • debt (uint256) - Stablecoin debt amount

OraclizedValue

  • p_o (uint256) - Oracle price used for calculation
  • value (uint256) - Calculated value in stablecoin terms

LiquidityValues

  • admin (int256) - Admin fees (can be negative)
  • total (uint256) - Total position value
  • ideal_staked (uint256) - Ideal staked amount
  • staked (uint256) - Current staked amount

LiquidityValuesOut

  • admin (int256) - Admin fees (can be negative)
  • total (uint256) - Total position value
  • ideal_staked (uint256) - Ideal staked amount
  • staked (uint256) - Current staked amount
  • staked_tokens (uint256) - Staked token balance after reductions
  • supply_tokens (uint256) - Total supply after reductions
  • token_reduction (int256) - Token reduction amount

Events

SetStaker (Emitted when a new staker contract is set)

  • staker (address) - New staker contract address

WithdrawAdminFees (Emitted when admin fees are withdrawn)

  • admin (address) - Admin address
  • amount (uint256) - Amount of admin fees withdrawn

AllocateStablecoins (Emitted when stablecoins are allocated to the AMM)

  • allocator (address) - Address that allocated stablecoins
  • stablecoin_allocation (uint256) - Total allocation limit
  • stablecoin_allocated (uint256) - Currently allocated amount

DistributeBorrowerFees (Emitted when borrower fees are distributed to the Curve pool)

  • amount (uint256) - Amount of fees distributed
  • discount (uint256) - Discount applied to the distribution

Deposit (Emitted when assets are deposited and shares are minted)

  • sender (address) - Address that initiated the deposit
  • owner (address) - Address that receives the shares
  • assets (uint256) - Amount of assets deposited
  • shares (uint256) - Amount of shares minted

Withdraw (Emitted when shares are burned and assets are withdrawn)

  • sender (address) - Address that initiated the withdrawal
  • receiver (address) - Address that receives the assets
  • owner (address) - Address that owns the shares
  • assets (uint256) - Amount of assets withdrawn
  • shares (uint256) - Amount of shares burned

SetAdmin (Emitted when the admin address is changed)

  • admin (address) - New admin address

_calculate_values()

Description:
Internal function that calculates the current liquidity values, including admin fees, token reductions, and position metrics. This is the core function for determining LT token values and managing fee distributions.

Returns:
LiquidityValuesOut - Struct containing detailed position metrics including:

FieldTypeDescription
adminint256Admin fees (can be negative)
totaluint256Total position value
ideal_stakeduint256Ideal staked amount
stakeduint256Current staked amount
staked_tokensuint256Staked token balance after reductions
supply_tokensuint256Total supply after reductions
token_reductionint256Token reduction amount
InputTypeDescription
p_ouint256Oracle price for calculations
📄 View Source Code
@view
def _calculate_values(p_o: uint256) -> LiquidityValuesOut:
prev: LiquidityValues = self.liquidity
staker: address = self.staker
staked: int256 = 0
if staker != empty(address):
staked = convert(self.balanceOf[self.staker], int256)
supply: int256 = convert(self.totalSupply, int256)
# staked is guaranteed to be <= supply

f_a: int256 = convert(
10**18 - (10**18 - self._min_admin_fee()) * isqrt(convert(10**36 - staked * 10**36 // supply, uint256)) // 10**18,
int256)

cur_value: int256 = convert((staticcall self.amm.value_oracle()).value * 10**18 // p_o, int256)
prev_value: int256 = convert(prev.total, int256)
value_change: int256 = cur_value - (prev_value + prev.admin)

v_st: int256 = convert(prev.staked, int256)
v_st_ideal: int256 = convert(prev.ideal_staked, int256)
# ideal_staked is set when some tokens are transferred to staker address

# Admin fees are earned only when all losses are paid off
dv_use_36: int256 = 0
v_st_loss: int256 = max(v_st_ideal - v_st, 0)
if staked >= MIN_STAKED_FOR_FEES:
if value_change > 0:
# Admin fee is only charged once the loss if fully paid off
v_loss: int256 = min(value_change, v_st_loss * supply // staked)
dv_use_36 = v_loss * 10**18 + (value_change - v_loss) * (10**18 - f_a)
else:
# Admin doesn't pay for value loss
dv_use_36 = value_change * 10**18
else:
# If staked part is small - positive admin fees are charged on profits and negative on losses
dv_use_36 = value_change * (10**18 - f_a)

prev.admin += (value_change - dv_use_36 // 10**18)

# dv_s is guaranteed to be <= dv_use
# if staked < supply (not exactly 100.0% staked) - dv_s is strictly < dv_use
dv_s_36: int256 = self.mul_div_signed(dv_use_36, staked, supply)
if dv_use_36 > 0:
dv_s_36 = min(dv_s_36, v_st_loss * 10**18)

# new_staked_value is guaranteed to be <= new_total_value
new_total_value_36: int256 = max(prev_value * 10**18 + dv_use_36, 0)
new_staked_value_36: int256 = max(v_st * 10**18 + dv_s_36, 0)

# Solution of:
# staked - token_reduction new_staked_value
# ------------------------- = -------------------
# supply - token_reduction new_total_value
#
# the result:
# new_total_value * staked - new_staked_value * supply
# token_reduction = ------------------------------------------------------
# new_total_value - new_staked_value

token_reduction: int256 = new_total_value_36 - new_staked_value_36 # Denominator
token_reduction = self.mul_div_signed(new_total_value_36, staked, token_reduction) - self.mul_div_signed(new_staked_value_36, supply, token_reduction)

max_token_reduction: int256 = abs(value_change * supply // (prev_value + value_change + 1) * (10**18 - f_a) // SQRT_MIN_UNSTAKED_FRACTION)

# let's leave at least 1 LP token for staked and for total
if staked > 0:
token_reduction = min(token_reduction, staked - 1)
if supply > 0:
token_reduction = min(token_reduction, supply - 1)
# But most likely it's this condition to apply
if token_reduction >= 0:
token_reduction = min(token_reduction, max_token_reduction)
else:
token_reduction = max(token_reduction, -max_token_reduction)
# And don't allow negatives if denominator was too small
if new_total_value_36 - new_staked_value_36 < 10**4 * 10**18:
token_reduction = max(token_reduction, 0)

return LiquidityValuesOut(
admin=prev.admin,
total=convert(new_total_value_36 // 10**18, uint256),
ideal_staked=prev.ideal_staked,
staked=convert(new_staked_value_36 // 10**18, uint256),
staked_tokens=convert(staked - token_reduction, uint256),
supply_tokens=convert(supply - token_reduction, uint256),
token_reduction=token_reduction
)

deposit()

Description:
Method to deposit assets (e.g., BTC) to receive shares (e.g., yield-bearing BTC). This function implements the ERC4626 deposit standard and handles the complex liquidity calculations.

Returns:
uint256 - Amount of shares minted to the receiver.

Emits:
Deposit - Deposit event with sender, owner, assets, and shares amounts.

InputTypeDescription
assetsuint256Amount of assets to deposit
debtuint256Amount of debt for AMM to take (approximately BTC * btc_price)
min_sharesuint256Minimal amount of shares to receive (slippage protection)
receiveraddressReceiver of the shares (defaults to msg.sender)
📄 View Source Code
@external
@nonreentrant
def deposit(assets: uint256, debt: uint256, min_shares: uint256, receiver: address = msg.sender) -> uint256:
staker: address = self.staker
assert receiver != staker, "Deposit to staker"

amm: LevAMM = self.amm
assert extcall STABLECOIN.transferFrom(amm.address, self, debt, default_return_value=True)
assert extcall ASSET_TOKEN.transferFrom(msg.sender, self, assets, default_return_value=True)
lp_tokens: uint256 = extcall CRYPTOPOOL.add_liquidity([debt, assets], 0, amm.address, False)
p_o: uint256 = self._price_oracle_w()

supply: uint256 = self.totalSupply
shares: uint256 = 0

liquidity_values: LiquidityValuesOut = empty(LiquidityValuesOut)
if supply > 0:
liquidity_values = self._calculate_values(p_o)

v: OraclizedValue = extcall amm._deposit(lp_tokens, debt)
value_after: uint256 = v.value * 10**18 // p_o

assert staticcall amm.max_debt() // 2 >= v.value, "Debt too high"

if supply > 0 and liquidity_values.total > 0:
supply = liquidity_values.supply_tokens
self.liquidity.admin = liquidity_values.admin
value_before: uint256 = liquidity_values.total
value_after = convert(convert(value_after, int256) - liquidity_values.admin, uint256)
self.liquidity.total = value_after
self.liquidity.staked = liquidity_values.staked
self.totalSupply = liquidity_values.supply_tokens
if staker != empty(address):
self.balanceOf[staker] = liquidity_values.staked_tokens
self._log_token_reduction(staker, liquidity_values.token_reduction)
shares = supply * value_after // value_before - supply
else:
shares = value_after
self.liquidity.ideal_staked = 0
self.liquidity.staked = 0
self.liquidity.total = shares + supply
self.liquidity.admin = 0
if self.balanceOf[staker] > 0:
log IERC20.Transfer(sender=staker, receiver=empty(address), value=self.balanceOf[staker])
self.balanceOf[staker] = 0

assert shares + supply >= MIN_SHARE_REMAINDER, "Remainder too small"
assert shares >= min_shares, "Slippage"

self._mint(receiver, shares)
self._checkpoint_gauge()
log Deposit(sender=msg.sender, owner=receiver, assets=assets, shares=shares)
self._distribute_borrower_fees(FEE_CLAIM_DISCOUNT)
return shares

withdraw()

Description:
Method to withdraw assets (e.g., BTC) by spending shares (e.g., yield-bearing BTC). This function implements the ERC4626 withdraw standard.

Returns:
uint256 - Amount of assets received by the receiver.

Emits:
Withdraw - Withdrawal event with sender, receiver, owner, assets, and shares amounts.

InputTypeDescription
sharesuint256Shares to withdraw
min_assetsuint256Minimal amount of assets to receive (slippage protection)
receiveraddressReceiver of the assets (defaults to msg.sender)
📄 View Source Code
@external
@nonreentrant
def withdraw(shares: uint256, min_assets: uint256, receiver: address = msg.sender) -> uint256:
assert shares > 0, "Withdrawing nothing"

staker: address = self.staker
assert staker not in [msg.sender, receiver], "Withdraw to/from staker"

assert not (staticcall self.amm.is_killed()), "We're dead. Use emergency_withdraw"

amm: LevAMM = self.amm
liquidity_values: LiquidityValuesOut = self._calculate_values(self._price_oracle_w())
supply: uint256 = liquidity_values.supply_tokens
self.liquidity.admin = liquidity_values.admin
self.liquidity.staked = liquidity_values.staked
self.totalSupply = supply

assert supply >= MIN_SHARE_REMAINDER + shares or supply == shares, "Remainder too small"

if staker != empty(address):
self.balanceOf[staker] = liquidity_values.staked_tokens
self._log_token_reduction(staker, liquidity_values.token_reduction)

admin_balance: uint256 = convert(max(liquidity_values.admin, 0), uint256)

withdrawn: Pair = extcall amm._withdraw(10**18 * liquidity_values.total // (liquidity_values.total + admin_balance) * shares // supply)
assert extcall CRYPTOPOOL.transferFrom(amm.address, self, withdrawn.collateral, default_return_value=True)
crypto_received: uint256 = extcall CRYPTOPOOL.remove_liquidity_fixed_out(withdrawn.collateral, 0, withdrawn.debt, 0)

self._burn(msg.sender, shares)
self.liquidity.total = liquidity_values.total * (supply - shares) // supply
if liquidity_values.admin < 0:
self.liquidity.admin = liquidity_values.admin * convert(supply - shares, int256) // convert(supply, int256)
assert crypto_received >= min_assets, "Slippage"
assert extcall STABLECOIN.transfer(amm.address, withdrawn.debt, default_return_value=True)
assert extcall ASSET_TOKEN.transfer(receiver, crypto_received, default_return_value=True)

self._checkpoint_gauge()
log Withdraw(sender=msg.sender, receiver=receiver, owner=msg.sender, assets=crypto_received, shares=shares)
self._distribute_borrower_fees(FEE_CLAIM_DISCOUNT)
return crypto_received

preview_deposit()

Description:
Returns the amount of shares which can be obtained upon depositing assets, including slippage. This is a view function that simulates the deposit without executing it.

Returns:
uint256 - Amount of shares that would be minted for the given assets and debt.

InputTypeDescription
assetsuint256Amount of crypto to deposit
debtuint256Amount of stables to borrow for MMing (approx same value as crypto)
📄 View Source Code
@external
@view
@nonreentrant
def preview_deposit(assets: uint256, debt: uint256) -> uint256:
lp_tokens: uint256 = staticcall CRYPTOPOOL.calc_token_amount([debt, assets], True)
supply: uint256 = self.totalSupply
p_o: uint256 = self._price_oracle()
amm: LevAMM = self.amm
amm_max_debt: uint256 = staticcall amm.max_debt() // 2

if supply > 0:
liquidity: LiquidityValuesOut = self._calculate_values(p_o)
if liquidity.total > 0:
v: OraclizedValue = staticcall amm.value_change(lp_tokens, debt, True)
if amm_max_debt < v.value:
raise "Debt too high"
value_after: uint256 = convert(convert(v.value * 10**18 // p_o, int256) - liquidity.admin, uint256)
return liquidity.supply_tokens * value_after // liquidity.total - liquidity.supply_tokens

v: OraclizedValue = staticcall amm.value_oracle_for(lp_tokens, debt)
if amm_max_debt < v.value:
raise "Debt too high"
return v.value * 10**18 // p_o

preview_withdraw()

Description:
Returns the amount of assets which can be obtained upon withdrawing from tokens. This is a view function that simulates the withdrawal without executing it.

Returns:
uint256 - Amount of assets that would be received for the given shares.

InputTypeDescription
tokensuint256Amount of shares to withdraw
📄 View Source Code
@external
@view
@nonreentrant
def preview_withdraw(tokens: uint256) -> uint256:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
state: AMMState = staticcall self.amm.get_state()
admin_balance: uint256 = convert(max(v.admin, 0), uint256)
frac: uint256 = 10**18 * v.total // (v.total + admin_balance) * tokens // v.supply_tokens
withdrawn_lp: uint256 = state.collateral * frac // 10**18
withdrawn_debt: uint256 = state.debt * frac // 10**18
return staticcall CRYPTOPOOL.calc_withdraw_fixed_out(withdrawn_lp, 0, withdrawn_debt)

set_amm()

Description:
Sets the AMM contract address. This function can only be called once and only by the admin. It also sets the price aggregator from the AMM's price oracle. AMM can only be changed by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

InputTypeDescription
ammLevAMMAMM contract address
📄 View Source Code
@external
@nonreentrant
def set_amm(amm: LevAMM):
self._check_admin()
assert self.amm == empty(LevAMM), "Already set"
assert staticcall amm.STABLECOIN() == STABLECOIN.address
assert staticcall amm.COLLATERAL() == CRYPTOPOOL.address
assert staticcall amm.LT_CONTRACT() == self
self.amm = amm
self.agg = PriceOracle(staticcall (staticcall amm.PRICE_ORACLE_CONTRACT()).AGG())

set_admin()

Description:
Sets a new admin address. This function can only be called by the current admin. It performs sanity checks if the new admin is a contract. New admin can only be changed by the current admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

Emits:
SetAdmin - Admin change event with new admin address.

InputTypeDescription
new_adminaddressNew admin address
📄 View Source Code
@external
@nonreentrant
def set_admin(new_admin: address):
self._check_admin()

if new_admin.is_contract:
check_address: address = staticcall Factory(new_admin).admin()
check_address = staticcall Factory(new_admin).emergency_admin()
check_address = staticcall Factory(new_admin).fee_receiver()
check_uint: uint256 = staticcall Factory(new_admin).min_admin_fee()

self.admin = new_admin
log SetAdmin(admin=new_admin)

set_rate()

Description:
Sets the interest rate in the AMM. This function can only be called by the admin and forwards the call to the AMM contract. Interest rate can only be changed by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

InputTypeDescription
rateuint256New interest rate
📄 View Source Code
@external
@nonreentrant
def set_rate(rate: uint256):
self._check_admin()
extcall self.amm.set_rate(rate)

set_amm_fee()

Description:
Sets the trading fee in the AMM. This function can only be called by the admin and forwards the call to the AMM contract. AMM fee can only be changed by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

InputTypeDescription
feeuint256New trading fee
📄 View Source Code
@external
@nonreentrant
def set_amm_fee(fee: uint256):
self._check_admin()
extcall self.amm.set_fee(fee)

allocate_stablecoins()

Description:
Allocates stablecoins to the AMM for this pool. This method must be used once the contract has received allocation of stablecoins. Allocation can only be handled by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

Emits:
AllocateStablecoins - Allocation event with allocator address, allocation amount, and allocated amount.

InputTypeDescription
limituint256Limit to allocate for this pool (max uint256 = do not change)
📄 View Source Code
@external
@nonreentrant
def allocate_stablecoins(limit: uint256 = max_value(uint256)):
allocator: address = self.admin
allocation: uint256 = limit
allocated: uint256 = self.stablecoin_allocated
if limit == max_value(uint256):
allocation = self.stablecoin_allocation
else:
self._check_admin()
self.stablecoin_allocation = limit

extcall self.amm.check_nonreentrant()

if allocation > allocated:
assert extcall STABLECOIN.transferFrom(allocator, self.amm.address, allocation - allocated, default_return_value=True)
self.stablecoin_allocated = allocation
elif allocation < allocated:
lp_price: uint256 = extcall (staticcall self.amm.PRICE_ORACLE_CONTRACT()).price_w()
assert allocation >= lp_price * (staticcall self.amm.collateral_amount()) // 10**18, "Not enough stables"
to_transfer: uint256 = min(allocated - allocation, staticcall STABLECOIN.balanceOf(self.amm.address))
allocated -= to_transfer
assert extcall STABLECOIN.transferFrom(self.amm.address, allocator, to_transfer, default_return_value=True)
self.stablecoin_allocated = allocated

log AllocateStablecoins(allocator=allocator, stablecoin_allocation=allocation, stablecoin_allocated=allocated)

pricePerShare()

Description:
Non-manipulatable "fair price per share" oracle of the yb-asset token.

Returns:
uint256 - Price per share in 1e18 units.

📄 View Source Code
@external
@view
def pricePerShare() -> uint256:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
if v.supply_tokens == 0:
return 10**18
else:
return v.total * 10**18 // v.supply_tokens

symbol()

Description:
Returns the token symbol in the format 'yb-asset'. This implements the ERC20 symbol standard.

Returns:
String[32] - Token symbol (e.g., "yb-BTC").

📄 View Source Code
@external
@view
def symbol() -> String[32]:
return concat('yb-', staticcall IERC20Slice(ASSET_TOKEN.address).symbol())

name()

Description:
Returns the token name in the format 'Yield Basis liquidity for asset'. This implements the ERC20 name standard.

Returns:
String[58] - Token name (e.g., "Yield Basis liquidity for BTC").

📄 View Source Code
@external
@view
def name() -> String[58]:
return concat('Yield Basis liquidity for ', staticcall IERC20Slice(ASSET_TOKEN.address).symbol())