Developer Quickstart
Minimal integration path. The snippets show the call sequence; wire them to your ABI/client setup before using them in production.
Setup assumptions
The examples below assume:
- Ethereum mainnet.
- A Web3/ethers/viem client connected to an RPC endpoint.
- Contract instances created from the ABIs for
Factory,GaugeController,LT,VirtualPool,LiquidityGauge, and the relevant ERC-20 tokens. - Token amounts are in raw units. For LT deposits, compute
debtwith the asset's decimals:assets * oracle_price / 10**asset_decimals, whereoracle_priceis crvUSD per whole asset with 18 decimals.
Minimal Python-style binding shape:
from web3 import Web3
w3 = Web3(Web3.HTTPProvider(RPC_URL))
factory = w3.eth.contract(address=FACTORY, abi=FACTORY_ABI)
gc = w3.eth.contract(address=GAUGE_CONTROLLER, abi=GAUGE_CONTROLLER_ABI)
# Per market, bind these from Factory.markets(i)
lt = w3.eth.contract(address=market["lt"], abi=LT_ABI)
vp = w3.eth.contract(address=market["virtual_pool"], abi=VIRTUAL_POOL_ABI)
asset = w3.eth.contract(address=market["asset_token"], abi=ERC20_ABI)
1. Discover markets
The addresses below are the entrypoints most integrators hardcode. The full per-market list (LEVAMM / VirtualPool / LP Oracle / Gauge per asset), deprecated markets, and DAO/vesting contracts live on the canonical Contract Addresses page.
| Contract | Address | Use |
|---|---|---|
Factory | 0x370a449FeBb9411c95bf897021377fe0B7D100c0 | Enumerate markets, resolve per-market contracts |
GaugeController | 0x1Be14811A3a06F6aF4fA64310a636e1Df04c1c21 | lt → gauge lookup; gauge voting |
YB | 0x01791F726B4103694969820be083196cC7c045fF | YB token |
VotingEscrow (veYB) | 0x8235c179E9e84688FBd8B12295EfC26834dAC211 | veYB balance / voting power |
FeeDistributor | 0xD11b416573EbC59b6B2387DA0D2c0D1b3b1F7A90 | veYB admin-fee claims |
StakeZap | 0xE862bC39B8D5F12D8c4117d3e2D493Dc20051EC6 | Atomic deposit + stake |
LTMigrator | 0xc51C8e4CFB7FB969DaE4b10052A1BB6d15fcd96B | Legacy LT migration |
HybridVaultFactory | 0xBdC32268851C324c6185809271dfe6d8dab8dC5b | HybridVault deploy/registry |
crvUSD | 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E | Debt asset |
DAO | 0x42F2A41A0D0e65A440813190880c8a65124895Fa | Governance execution |
Enumerate per-market contracts
Prefer on-chain enumeration over hardcoding per-market addresses — new markets are added over time and enumeration picks them up automatically. Factory.markets(i) returns a Market struct; the gauge is the struct's staker field (also exposed as LT.staker()):
FACTORY = "0x370a449FeBb9411c95bf897021377fe0B7D100c0"
factory = IFactory(FACTORY)
count = factory.market_count()
for i in range(count):
m = factory.markets(i)
# Market struct fields (Factory.vy:32, 7 fields, in this order):
# (asset_token, cryptopool, amm, lt, price_oracle, virtual_pool, staker)
gauge = m.staker # LiquidityGauge; equivalent to lt.staker()
print(i, m.lt, m.amm, m.virtual_pool, m.price_oracle, gauge)
GaugeController.gauges is an enumerated address[1_000_000_000] indexed by integer, not a mapping by LT address; resolve gauges via the market struct or LT.staker().
To distinguish active vs. deprecated LTs, cross-check against the lists on the Contract Addresses page — the on-chain state is not annotated. LTMigrator handles legacy → current migration.
HybridVault discovery
Per-user HybridVaults are deployed as minimal proxies pointing to the HybridVault impl. Look up a user's vault via the factory:
hvf = IHybridVaultFactory("0xBdC32268851C324c6185809271dfe6d8dab8dC5b")
user_vault = hvf.vaults(user) # zero address if no vault
2. Read market state
# PPS — fundamental per-share value (oracle-priced, sandwich-resistant)
pps = lt.pricePerShare()
# Realistic redemption for one share (live Cryptoswap state, includes TRD)
redemption = lt.preview_withdraw(10**18)
# TRD for one share (signed)
trd = redemption / pps - 1 # negative during a discount; rare positive = premium
# Staked bucket state (for staked-position logic)
(admin, total, ideal_staked, staked) = lt.liquidity()
# Oracle price
p_o = PriceOracle(oracle).price()
The gap between pps and redemption is TRD — a timing mismatch that resolves via arbitrage. For latency-sensitive integrations (e.g. same-block quotes), preview_withdraw is the number to rely on; pricePerShare() is an upper bound. Note: LT does not implement convertToAssets — that function lives on LiquidityGauge (the staker), where it maps gauge-shares to LT-shares.
3. Quote a swap via VirtualPool
# crvUSD → asset
dy = VirtualPool(vp).get_dy(0, 1, amount_in)
# asset → crvUSD
dy = VirtualPool(vp).get_dy(1, 0, amount_in)
get_dy prices in the full flash-loan round-trip through LEVAMM + Cryptoswap. Result reflects fees and internal slippage — no further adjustment needed to check arb profitability.
Note: for i=0 (crvUSD-in direction), a ROUNDING_DISCOUNT = 1e18 / 1e8 is applied to the input inside the quote (see VirtualPool.vy line 131). Quote and execution may differ by ~1e-8 of input.
4. Execute a swap
# asset → crvUSD
asset.approve(vp, amount_in)
out = VirtualPool(vp).exchange(1, 0, amount_in, min_out, recipient)
# crvUSD → asset
crvusd.approve(vp, amount_in)
out = VirtualPool(vp).exchange(0, 1, amount_in, min_out, recipient)
The flash loan is taken internally; your account only needs to hold the input token and approve the VirtualPool. Expect 400–600k gas.
5. Deposit / withdraw against LT
# Deposit — mints yb-LP
asset.approve(lt, amount)
debt = amount * oracle_price // 10**asset_decimals
shares_estimate = lt.preview_deposit(amount, debt) # raise_overflow=True default; pass False for soft quote
shares = lt.deposit(amount, debt, min_shares, receiver)
# Withdraw — burns yb-LP, returns asset
amount = lt.withdraw(shares, min_assets, receiver)
Gotchas:
receivercannot be theLT.stakeraddress on deposit. UseStakeZapif you want atomic deposit + stake.msg.senderandreceivercannot be the staker address on withdraw. Unstake from gauge first.preview_withdraw(shares)includes TRD. For the fundamental reference, compare againstpricePerShare() × shares / 1e18.- Slippage protection: pass a realistic
min_shares/min_assets.
See Deposit & Withdraw and Action Flow: Deposit / Withdraw for the call-level detail.
6. Watch events
Key events you'll care about:
Deposit(sender, owner, assets, shares)on LTWithdraw(sender, receiver, owner, assets, shares)on LTTransfer(from, to, value)on LT (ERC-20 side; transfers to/from staker trigger bucket rebalancing)TokenExchange(buyer, sold_id, tokens_sold, bought_id, tokens_bought)on VirtualPoolTokenExchange(buyer, sold_id, tokens_sold, bought_id, tokens_bought, fee, price_oracle)on AMM (LEVAMM)
Full list: Event Catalog.