Chapter 07 · Fees and the flywheel
Fees and the flywheel
Where every basis point goes: creator NFT, protocol sinks, the $HARC buyback and burn.
Every fee in Hookarc is taken on the quote side and lands in one of four ledgers. Each ledger has exactly one exit. Protocol fees on USDC-quoted pools feed the $HARC buyback and burn; protocol fees on cirBTC-quoted pools go to the treasury; creator fees go to whoever holds the pool's NFT; pot winnings go to the winner.
The ledgers
| Ledger | Filled by | Held as | Exit |
|---|---|---|---|
protocolFees[quote] | 30 bps of every buy and sell | ERC-6909 claims on the PoolManager | sweepProtocolFees(quote) by anyone, or claimProtocolFees(quote, to) by the hook owner |
creatorFees[poolId] | 50 bps of every buy and sell, plus the Anti-Snipe tax | ERC-6909 claims | claimCreatorFees(tokenId, to) by the current NFT owner |
pot[poolId] then potOwed[poolId][winner] | the pot share of every buy | ERC-6909 claims | claimPot(poolId, to) by the winner |
factory.creationFees | 1 USDC per launch, paid as native value | native USDC in the factory | withdrawCreationFees(to) by the factory owner |
Why ERC-6909 claims
When the hook takes a fee it does not move tokens; it calls poolManager.mint(hook, quote.toId(), amount), which credits the hook with a claim on the PoolManager's balance. The PoolManager already holds every asset in every pool, so no fee ever sits as a loose balance in the hook. A claim is redeemed inside unlock: the hook burns the claim and takes the underlying to the recipient. The invariant tests check that the sum of the ledgers never exceeds the hook's claims and that the hook holds no loose USDC, cirBTC or tokens.
Creator fees and the NFT
The CreatorFeeNFT is minted to the launcher with tokenId = uint256(poolId). claimCreatorFees pays ownerOf(tokenId), so selling or transferring the NFT transfers the fee stream, including whatever has accrued and not been claimed. The creator address recorded in the hook is informational only; it does not gate anything.
Protocol fees and the sinks
The hook owner configures a sink per quote with setFeeSink. On Arc:
- USDC sink: the
FlywheelBurner. - cirBTC sink: the treasury address from the deploy (
TREASURYenv, or the deployer).
sweepProtocolFees(quote) is permissionless: anyone can move the accrued fees for a quote to its sink at any time, which removes the need to trust the owner to do it. claimProtocolFees remains as the owner's direct path, for example for a quote that has no sink yet.
The flywheel
The burner holds the USDC protocol fees (the 6-decimal ERC-20 view). Its owner calls buybackAndBurn(quoteIn, minOut), which swaps through the configured USDC / $HARC Uniswap v4 pool (settling the USDC through sync / transfer / settle) and sends the $HARC straight from the PoolManager to 0x000…dEaD. Three bounds apply: quoteIn ≤ maxQuotePerCall, at most one call per block, and minOut as a reviewed slippage floor. totalQuoteSpent and totalBurned are public counters and every call emits Burned(quoteIn, harcBurned), which the indexer lists at /burns. The burn goes to the dead address, never to address(0), because Arc rejects transfers to the zero address.
Worked example
A trader buys with 100 USDC in a pool with the Nth-buy pot at 0.5 % and no other blocks:
| USDC | |
|---|---|
| Protocol fee (30 bps, rounded up) | 0.30 |
| Creator fee (50 bps, rounded up) | 0.50 |
| Pot share (50 bps) | 0.50 |
| Reaches the AMM | 98.70 |
The pool's HookSwap event reports quoteAmount = 100 USDC (98.70 consumed + 1.30 fee), feeQuote = 1.30 USDC, all in raw units of 10⁻⁶ USDC. If the same trader sells the tokens back for 90 USDC gross, the sell pays 0.27 + 0.45 USDC and the trader receives 89.28 USDC. Gas for the two trades comes to a few cents, paid in native USDC.
contracts/src/HookarcHook.solclaimProtocolFees, claimCreatorFees, claimPot, sweepProtocolFees, _redeem, unlockCallbackcontracts/src/FlywheelBurner.solconfigure, buybackAndBurn, harc(), totalQuoteSpent, unlockCallbackcontracts/src/CreatorFeeNFT.solmint and the one-shot mintercontracts/test/invariant/Solvency.t.solclaims cover ledgers; no loose balancescontracts/test/FlywheelBurner.t.solcaps, once-per-block, slippage floor