Buyback auction
Summary
Section titled “Summary”fact RevenueRouter is the protocol feeSink. On notifyFee(USDG, amount) it splits buybackBps (9,000 in DeployToken.s.sol) into the buyback reserve and forwards the remainder (1,000) to the Treasury (FeesRouted). Anyone may call sellNoteForQuote(noteAmount, minQuoteOut) and be paid at the current auction price from the buyback reserve. Purchased NOTE is transferred to sNOTE and notifyReward is called in the same transaction.
| Parameter | Deploy-script value | Bounds |
|---|---|---|
buybackBps | 9,000 (90%) | ≤ 10,000 |
halfLifeSeconds | 6 hours | 5 minutes to 30 days |
bumpBps | 2,000 (20%) | ≤ 10,000 |
maxFillBps | 2,500 (25% of reserve) | ≤ 10,000 |
2^(−x) is computed by ExpMath.halfLifeDecayWad, a fixed-point exponential with bounded error tested against reference values.
Worked example
Section titled “Worked example”illustrative Floor 0.50, startPriceWad 1.20 after the last fill.
| Hours since last fill | Decay factor 2^(−t/6h) | P(t) = 0.50 + 0.70 × factor |
|---|---|---|
| 0 | 1.0000 | 1.2000 |
| 3 | 0.7071 | 0.9950 |
| 6 | 0.5000 | 0.8500 |
| 12 | 0.2500 | 0.6750 |
| 24 | 0.0625 | 0.5438 |
| 48 | 0.0039 | 0.5027 |
| ∞ | 0 | 0.5000 (floor) |
A seller fills at hour 6 for 0.85. The start price bumps to 0.85 × 1.20 = 1.02 and decay restarts from there. If the buyback reserve holds 200,000 USDG, one fill pays at most 50,000 USDG (25%), buying 58,823.5 NOTE at 0.85.
Properties
Section titled “Properties”- No DEX dependency. Buybacks do not route through an AMM, so there is no pool to sandwich and no price impact fee paid to LPs.
- Floor-aware. Because
floor(t)is read live from the Treasury, the auction adjusts automatically as reserves grow. - Rate-limited. The fill cap and bump limit how fast a single seller can drain the reserve in a volatile market; the exponential decay ensures the reserve is still spent within hours or days rather than sitting idle.
- Reward routing. Every fill immediately becomes an sNOTE reward stream; there is no manual distribution step.
Governance surface
Section titled “Governance surface”| Function | Effect |
|---|---|
setSplit(buybackBps) | Fee split |
setAuctionParams(halfLife, bumpBps, maxFillBps) | Auction shape |
resetAuction(newStartPriceWad) | Reset start price (AuctionReset), for example after a long idle period |
sweep(token, to, amount) | Recover a non-quote token sent by mistake (Swept); the quote token cannot be swept |
sync() | Permissionless: splits any quote balance not yet routed (FeesRouted), used after a paused period |
Treasury and sNOTE addresses are immutable constructor arguments; there is no setter for either destination.
Failure modes
Section titled “Failure modes”- If the buyback reserve is empty,
sellNoteForQuotereverts withFillExceedsCap(requested, 0). Nothing is lost; the next fee sweep refills it. - If the Treasury’s
circulatingSupplyis zero,floorPriceWadreturns zero and the auction decays towards zero. This happens only before any NOTE circulates. IfstartPriceWadis at or below the floor,currentPriceWadreturns the floor. notifyFeeemitsFeeNotifiedeven while paused, but the split is deferred untilsync()after unpausing, sosweepFeesonNoteCoreis never blocked by a router pause.- If the start price has decayed to the floor and NOTE trades above the floor, no seller appears and the reserve accumulates until governance resets the start price or the market falls.