ZKDepth has one use for its protocol revenue: buying $ZKD and burning it. A fee collector swaps fees to ETH, and BuybackV2 spends the ETH on $ZKD and sends it to 0x…dEaD.
The path of one fee
A ZKDepth pool swap pays its protocol share straight to the fee collector. Every other fee is booked in the FeeRouter until someone calls flush(token), see Fees and referrals. The collector turns what it holds into ETH for BuybackV2, and buyback() spends that ETH on $ZKD in the ETH/ZKD Uniswap v4 pool, which pays the tokens directly to 0x…dEaD.
Converting fees to ETH
The owner gives each fee token a route: Router hops ending in native ETH, a cap per call, and an optional oracle floor. Then anyone can call convert. With the floor on, the output must be at most 3 % below the AssetRegistry feed's price, and a stale feed reverts. WETH needs no route: unwrapWeth() unwraps it 1:1, and forwardEth() passes ETH fees on. A token without a route waits in the collector.
FeeCollectorV2 has the same conversion logic plus burnZKDepth(), which sends its whole $ZKD balance to 0x…dEaD. It can never sell or sweep $ZKD. The pools pay it once the owner calls ProtocolConfig.setFeeCollector. The FeeRouter's treasury stays the first FeeCollector, and both send ETH to the same BuybackV2.
Buying and burning
buyback() takes BuybackV2's ETH balance, including any ETH sent with the call, up to 0.05 ETH. It keeps 0.5 % for the caller and spends the rest. Unspent ETH waits for the next call.
$ZKD has no Chainlink feed, so BuybackV2 checks the pool against a reference price that follows it slowly.
- The reference moves at most 30 % per hour since its last update, and at most 10 % per update. With no time elapsed it cannot move, so a pump inside one block does not shift it.
buyback()catches the reference up, then revertsPriceAboveBandif $ZKD is above the band.- The swap stops at the tighter of the band edge and 10 % above its own starting price.
A reverted buyback also undoes its catch-up, so poke() moves the reference on its own and pays nothing. After PriceAboveBand: poke, wait, retry.
BuybackV2 needs one owner step: configure names the $ZKD pool, once. Until then buyback() and poke() revert NotConfigured, and converted ETH waits in BuybackV2, which has no other way to send it.
Burned and circulating supply
A burn is a transfer to 0x…dEaD, so circulating supply is totalSupply() less balanceOf(0x…dEaD), checkable by anyone. BuybackV2 also counts totalEthSpent and totalBurned. ZKDepth marks any other token called ZKD as not the official one.
What the owner controls
| Contract | The owner can | Limit |
|---|---|---|
ProtocolConfig | set the protocol share, name another collector | share at most 50 % |
| Collectors | set routes and maxSlippageBps, name another buyback | slippage at most 20 % |
| Collectors | sweep a token | only a token with no route, never ETH |
BuybackV2 | set the per-call cap, the reward, the guard | reward at most 5 %, band 1 to 2,000 bps, drift at most 10,000 bps per hour |
So the owner can redirect revenue that has not reached BuybackV2. It cannot take ETH from BuybackV2, and none of these contracts reaches a user's position or deposit. Every other key is in Who can do what.
contracts/src/venue/periphery/FeeCollectorV2.solroutes, the oracle floor, convert, burnZKDepthcontracts/src/venue/periphery/BuybackV2.solbuyback, poke, the reference and the bandcontracts/script/venue/VenueWiring.soldeploy values and ownership