Skip to main content

Gauges and emissions

A gauge is the staking contract of one YieldBasis market. You stake yb-LP, the market's share token, in the gauge, and the gauge pays you YB emissions. Every market has its own gauge. Emissions follow one chain: lock YB → hold veYB → vote on gauges → stake yb-LP in a gauge → earn YB emissions. veYB votes set each gauge's share of emissions. The staking ratio adjusts that share: a gauge with a low staking ratio gets a smaller share. The staking ratio is the fraction of a market's yb-LP that is staked in its gauge.

What stakers earn​

Stakers earn YB emissions, plus any extra reward token that the gauge owner adds. A gauge holds at most 7 extra reward tokens. The gauge owner is the YB DAO, a HybridFactoryOwner contract, or a MigrationFactoryOwner contract, and it can differ between gauges.

Fundamental yield, the price-per-share (PPS) growth of yb-LP from trading fees, goes to unstaked holders. One exception applies after losses. While the staked side is below its watermark, the value it must regain, it also receives its share of gains, as Watermark and recovery explains.

To compare the two positions, use one named rate for each. A staked position earns Emissions APR (shown as Token APR in the app): its YB emissions valued at the YB/USD price. An unstaked position earns Fundamental yield (shown as FT APY in the app). Never add the two rates for one staked position. For the comparison and a breakeven example, read Staked and unstaked economics.

How emissions are routed​

Emissions reach stakers in three steps:

  1. veYB votes set raw weights. Holders allocate vote weight across gauges. The vote weight of a gauge is its raw weight.
  2. The staking ratio adjusts each gauge's weight. The GaugeController, the contract that tallies gauge votes, multiplies the raw weight by the staking-ratio adjustment. The staking-ratio adjustment is the square root of the gauge's staking ratio (sqrt(staked / total_supply)). The result is the adjusted weight.
  3. Stakers in each gauge split its emissions pro rata.

Worked example​

The example assumes two gauges, A and B, with equal veYB votes (50% each) and different staking ratios:

  • A: 80% staking ratio. sqrt(0.8) ≈ 0.894. Adjusted weight ≈ 0.5 × 0.894 = 0.447.
  • B: 10% staking ratio. sqrt(0.1) ≈ 0.316. Adjusted weight ≈ 0.5 × 0.316 = 0.158.

The total adjusted weight is 0.447 + 0.158 = 0.605. Emissions split in proportion to adjusted weight: A gets 0.447 / 0.605 ≈ 74%, and B gets 0.158 / 0.605 ≈ 26%. A gets more because market A has the higher staking ratio.

The rate factor is the sum of adjusted weights divided by the sum of raw weights. In this example, the voted gauges are partly staked, so the rate factor is 0.605 / 1.0 = 0.605, and the overall YB mint rate slows. With 100% staking on all gauges, rate_factor = 1.0 and emissions run at the maximum. The rate factor uses the weight sums stored before the current checkpoint.

If one gauge has 0% staking, its adjusted weight is 0. With gauge A at 100% staking and gauge B at 0%, A gets 100% of emissions and B gets nothing, whatever their votes.

Adjustment at each staking ratio​

The adjustment table gives the staking-ratio adjustment at five staking ratios.

Staking ratioAdjustment
100%1.00×
50%0.71×
25%0.50×
10%0.32×
0%0.00×

The square root is gentle in the mid-range, so a gauge at 50% staking keeps 71% of its vote weight. At the low end, the curve is steep, so votes on gauges that nobody stakes in route little emission.

Vote and emission timing​

Gauge votes follow two timing rules:

  • Votes apply immediately. Gauge weights update in the same transaction when you call vote_for_gauge_weights, with no wait for an epoch boundary.
  • Same-gauge re-vote cooldown. After you vote for a gauge, you must wait 10 days (WEIGHT_VOTE_DELAY = 864000 seconds) to change that vote. During the wait, you can still change your votes for other gauges.

Emissions accrue every second. Each gauge checkpoint makes the GaugeController call YB.emit, which mints the emissions. Deposits, withdrawals, transfers, claims, votes, and checkpoint calls all checkpoint the gauge. Week boundaries (Thursday 00:00 UTC) apply only to scheduled vote-weight changes at lock ends.

Gauge lifecycle​

Factory.vy deploys a gauge with each market. A new gauge receives votes and emissions only after a YB DAO vote calls GaugeController.add_gauge.

The YB DAO can kill a gauge with GaugeController.set_killed. A killed gauge cannot receive new vote weight above zero. Existing votes keep their weight until voters remove them. The gauge still receives emissions: the GaugeController reads is_killed only in the vote check. The GaugeController has no function to remove a gauge.

Vote aggregation lives in GaugeController.vy. Staking, reward distribution, and the staking-ratio adjustment live in LiquidityGauge.vy.

Routing formula and getters​

To check the routing onchain, use the following pseudocode and getters. The pseudocode, not contract source, states the same steps as How emissions are routed, plus the mint:

# Stage 1: vote aggregation (GaugeController.vy)
gauge.raw_weight = sum(weight_bps / 10000 × veYB(t)) # over all voters
# (each vote decays with the voter's veYB and ends at the voter's unlock time;
# a permalock vote stays constant)

# Stage 2: staking ratio adjustment (from LiquidityGauge.get_adjustment)
adjustment = sqrt(staked_in_gauge / lt_total_supply)
# (capped at 1.0: the adjustment can only lower a gauge's weight)
gauge.adjusted_weight = raw_weight × adjustment

# rate_factor (passed to YB.emit) = sum(adjusted) / sum(raw)
total_rate_factor = Σ adjusted_weight / Σ raw_weight

# Emissions since the last checkpoint
# (Δt = time since the previous checkpoint of any gauge)
emitted = reserve × (1 − exp(−Δt × max_mint_rate × rate_factor / 1e18))

# Per-gauge share
emissions_per_gauge = emitted × adjusted_weight / Σ adjusted_weight

The variable table gives the getter for each live input. get_adjustment() and gauge_relative_weight() return 1e18 for 1.0.

TermGetter
raw_weight, Σ raw_weightGaugeController.get_gauge_weight(gauge), gauge_weight_sum()
staked_in_gauge, lt_total_supplyLT.balanceOf(gauge), LT.totalSupply()
adjustmentLiquidityGauge.get_adjustment()
adjusted_weight, Σ adjusted_weightGaugeController.adjusted_gauge_weight(gauge), adjusted_gauge_weight_sum()
Gauge share of emissionsGaugeController.gauge_relative_weight(gauge)
reserve, max_mint_rateYB.reserve(), YB.max_mint_rate()