Skip to main content

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 debt with the asset's decimals: assets * oracle_price / 10**asset_decimals, where oracle_price is 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.

ContractAddressUse
Factory0x370a449FeBb9411c95bf897021377fe0B7D100c0Enumerate markets, resolve per-market contracts
GaugeController0x1Be14811A3a06F6aF4fA64310a636e1Df04c1c21lt → gauge lookup; gauge voting
YB0x01791F726B4103694969820be083196cC7c045fFYB token
VotingEscrow (veYB)0x8235c179E9e84688FBd8B12295EfC26834dAC211veYB balance / voting power
FeeDistributor0xD11b416573EbC59b6B2387DA0D2c0D1b3b1F7A90veYB admin-fee claims
StakeZap0xE862bC39B8D5F12D8c4117d3e2D493Dc20051EC6Atomic deposit + stake
LTMigrator0xc51C8e4CFB7FB969DaE4b10052A1BB6d15fcd96BLegacy LT migration
HybridVaultFactory0xBdC32268851C324c6185809271dfe6d8dab8dC5bHybridVault deploy/registry
crvUSD0xf939E0A03FB07F59A73314E73794Be0E57ac1b4EDebt asset
DAO0x42F2A41A0D0e65A440813190880c8a65124895FaGovernance 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:

  • receiver cannot be the LT.staker address on deposit. Use StakeZap if you want atomic deposit + stake.
  • msg.sender and receiver cannot be the staker address on withdraw. Unstake from gauge first.
  • preview_withdraw(shares) includes TRD. For the fundamental reference, compare against pricePerShare() × 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 LT
  • Withdraw(sender, receiver, owner, assets, shares) on LT
  • Transfer(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 VirtualPool
  • TokenExchange(buyer, sold_id, tokens_sold, bought_id, tokens_bought, fee, price_oracle) on AMM (LEVAMM)

Full list: Event Catalog.