veTOWN for Lending & Borrowing

TownSquare is introducing a vote-escrow (ve) incentives system that directs emissions to two activities in each lending market:

  • Lenders (deposits) — incentivize TVL on the supply side.

  • Borrowers (debt outstanding / activity) — incentivize healthy demand.

The design mirrors ve(3,3) stacks (Velodrome/Aerodrome) and classic veCRV (Curve) so that engineering can fork and retrofit minimal code: keep VotingEscrow, Voter, Minter, RewardsDistributor, Bribe, and replace LP pool gauges with SupplyGauge and BorrowGauge per asset.


High-Level Concepts

  • veToken (NFT lock): Users lock the protocol token for a duration to receive vote power that decays to zero at unlock.

  • Epochs: Fixed windows (default 7 days) where votes are snapshotted and emissions are streamed to gauges.

  • Gauges: Reward contracts that stream emissions to participants according to balances:

    • SupplyGauge[asset] → pays lenders by scaled supply balance (aToken-like).

    • BorrowGauge[asset] → pays borrowers by scaled debt balance (variableDebt-like).

  • Voter: Aggregates epoch votes from ve holders and assigns each gauge a % of the weekly emission.

  • Bribes / Voting Rewards (optional): Per-gauge reward channels paying ve voters who supported that gauge in the epoch.

  • Rewards Distributor (ve rebase): Optional “locker rebase” stream to ve lockers (separate from gauge emissions).

  • Market Registry: Whitelists which markets have gauges and sets risk params (caps, utilization floors).

  • Emission Policy: Config for weekly budget, decay, and optional side multipliers (e.g., borrow 1.2×).

Roles

  • User: Locks tokens for ve; supplies/borrows assets; votes each epoch; claims rewards.

  • Protocol Gov/Multisig: Lists markets; sets caps/allowlists; updates emission policy.

  • Partners: Can add bribes to steer votes (with allowlisted tokens).

  • Keepers (optional): Trigger epoch rollovers / batch distributions if not auto-pulled by claims.


User Flows

Lock → Vote → Earn

  1. Lock: User locks token → receives veNFT(id, power).

  2. Vote: User allocates vote weights across any combination of SupplyGauge[asset] and BorrowGauge[asset].

  3. Epoch Start: Votes snapshot → Voter computes per-gauge weight share.

  4. Stream: Minter mints E_epoch → routes to Voter → each gauge receives E_g and streams linearly over the epoch.

  5. Accrual:

    • Lenders accrue in SupplyGauge by scaled aToken balance.

    • Borrowers accrue in BorrowGauge by scaled debt balance.

  6. Claim: Users call claim() on gauges (and getReward() on bribe contracts if they voted).

Supply/Borrow Balance Change

When a user supplies/withdraws or borrows/repays, the respective scaled balance changes. The gauge updates the user’s accrual using a cumulative reward index.


Economics & Formulas

Epoch Allocation

  • Let E_epoch be emissions for the epoch.

  • Let w_g be the vote weight for gauge g.

  • Gauge share:

Eg=EepochwgiwiE_g = E_{\text{epoch}} \cdot \frac{w_g}{\sum_{i} w_i}
  • Optional side multiplier (policy overlay):

    Eg={Egmborrow,if g is a BorrowGauge,Egmsupply,if g is a SupplyGauge.E'_g = \begin{cases} E_g \cdot m_{\text{borrow}}, & \text{if } g \text{ is a BorrowGauge},\\[4pt] E_g \cdot m_{\text{supply}}, & \text{if } g \text{ is a SupplyGauge}. \end{cases}

Then renormalize to keep the epoch budget constant:

E~g  =  EgiEiEepochso thatgE~g  =  Eepoch.\tilde{E}_g \;=\; \frac{E'_g}{\sum_{i} E'_i} \cdot E_{\text{epoch}} \quad\text{so that}\quad \sum_{g} \tilde{E}_g \;=\; E_{\text{epoch}}.

Streaming Rate

  • rate_g = E_g / epochSeconds

  • Gauges stream linearly over the epoch (no compounding inside the epoch).

Per-User Accrual (Index Model)

For each gauge:

  • Maintain index and userIndex[user].

  • On time progress dt:

    index+=rategΔtmax{1,TotalScaledBalance}\text{index} \mathrel{+}= \frac{\text{rate}_g \cdot \Delta t}{\max\{1,\, \text{TotalScaledBalance}\}}
  • On user interaction:

    accrued[u]+=scaledBalance[u](indexuserIndex[u])userIndex[u]=index\begin{aligned} \text{accrued}[u] &\mathrel{+}= \text{scaledBalance}[u]\cdot\bigl(\text{index}-\text{userIndex}[u]\bigr) \\ \text{userIndex}[u] &= \text{index} \end{aligned}
  • Scaled balances come from your interest-bearing tokens:

    • Supply side: aToken.scaledBalanceOf(user)

    • Borrow side: variableDebtToken.scaledBalanceOf(user)

Optional “Boost” for Lockers Who Use the Market

  • Effective balance cap (Curve-style idea, optional):

    effectiveBalance  =  min ⁣(rawBalance(1+kveUserveOnGauge), cap)\text{effectiveBalance} \;=\; \min\!\left( \text{rawBalance}\cdot\Bigl(1 + k \cdot \frac{\text{veUser}}{\text{veOnGauge}}\Bigr),\ \text{cap} \right)
  • Keep cap modest (e.g., 2.5×) to avoid runaway concentration.


Contracts & Responsibilities

VotingEscrow (veNFT)

  • Core: createLock(amount, unlockTime), increaseAmount, increaseUnlockTime, balanceOfNFT(tokenId, t), merge/split (optional).

  • Voting Slope/Decay: Time-weighted decay to enforce long-term alignment.

  • Events: LockCreated, LockIncreased, LockExtended, Withdrawn.

Voter

  • Register Gauges: addGauge(gauge, type) where type ∈ {SUPPLY, BORROW}.

  • Vote: vote(tokenId, gauges[], weights[]) (can be re-cast each epoch).

  • Distribute: On epoch start, computes each gauge’s E_g (with policy multipliers) and calls notifyRewardAmount on gauges.

  • Voting Rewards: Tracks who voted for which gauges for bribe/fee claims.

  • Guards: Max % per gauge/asset; gauge kill/suspend; per-epoch vote limits.

Minter

  • Emission Budget: Holds the epoch schedule and decay.

  • Mint & Route: At epoch rollover, mints E_epoch, sends to Voter, optional % to RewardsDistributor (ve rebase).

RewardsDistributor (optional)

  • ve Rebase: Streams a portion of emissions (or protocol fees) to ve lockers proportionally to ve balances.

Bribe / FeesVotingReward (optional but recommended)

  • Per-Gauge Vaults: Accept allowlisted reward tokens.

  • Eligibility: Only ve voters who voted for this gauge in the epoch can claim.

  • Use: Partners can fund these to steer votes without protocol code changes.

SupplyGauge[asset] / BorrowGauge[asset]

  • Stake Model: No explicit staking; the “stake” is the user’s scaled balance read from the market tokens.

  • Hooks:

    • Option A (hook-based): Market tokens call onBalanceChange(user, delta) on mint/burn/transfer.

    • Option B (lazy): Settle on user actions + periodic keeper tick.

  • Core Methods:

    • notifyRewardAmount(amount) — set epoch stream.

    • claim(user) — pull user’s accrued rewards.

    • earned(user) — view function.

  • Admin: setRewardToken, setBribeAddress, setMarketOracle (if needed), pause.

MarketRegistry

  • Lists markets eligible for gauges with risk flags.

  • Params per market: utilizationFloor, maxEmissionPct, sideCaps, isActive.

EmissionPolicy

  • Global knobs: epochLength, baseWeeklyEmission, decayBps, supplyMultiplier, borrowMultiplier, sinkGauge (if zero-vote fallback).


Security & Risk Controls

  • Gauge Whitelist: Only protocol-approved markets get gauges.

  • Bribe Token Allowlist: Prevent malicious token griefing.

  • Caps:

    • Per-gauge max share of weekly emissions.

    • Per-asset aggregate cap across its two gauges.

  • Utilization Floor: Optionally require U ≥ U_min to accept votes for a gauge (avoid wasting emissions on dead markets).

  • Kill-Switch: Ability to pause individual gauges and bribe vaults.

  • Reentrancy & Accounting: Guard claim() and notifyRewardAmount(); use pull-based transfers.


Parameters (suggested defaults, all configurable)

  • epochLength: 7 days

  • decayBps: 100 bps per epoch (example)

  • supplyMultiplier: 1.0 (neutral)

  • borrowMultiplier: 1.0–1.2 (policy lever)

  • maxEmissionPctPerGauge: 15%

  • maxEmissionPctPerAsset: 25% (supply+borrow combined)

  • utilizationFloor: 10–20% (if enabled)

  • bribeTokensAllowlist: curated list (stablecoins + blue-chips)


Last updated