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

LedgerFilled byHeld asExit
protocolFees[quote]30 bps of every buy and sellERC-6909 claims on the PoolManagersweepProtocolFees(quote) by anyone, or claimProtocolFees(quote, to) by the hook owner
creatorFees[poolId]50 bps of every buy and sell, plus the Anti-Snipe taxERC-6909 claimsclaimCreatorFees(tokenId, to) by the current NFT owner
pot[poolId] then potOwed[poolId][winner]the pot share of every buyERC-6909 claimsclaimPot(poolId, to) by the winner
factory.creationFees1 USDC per launch, paid as native valuenative USDC in the factorywithdrawCreationFees(to) by the factory owner
Fee ledgers and exitsFees never sit as loose balances: they are ERC-6909 claims on the PoolManager until a claim or sweep redeems them.
USDCcirBTCbuy feebeforeSwap · 30 + 50sell feeafterSwap · 30 + 50AntiSnipe taxwindow onlypot shareNthBuyPotcreation fee1 USDC / launchprotocolFees[quote]ERC-6909 claimscreatorFees[poolId]ERC-6909 claimspot → potOwed[winner]ERC-6909 claimsfactory.creationFeesnative USDC balanceclaimProtocolFees(q, to)hook ownersweepProtocolFees(q)anyone · to the sinkclaimCreatorFees(id, to)current NFT ownerclaimPot(id, to)the winnerwithdrawCreationFees(to)factory ownerFlywheelBurner≤ cap · once per blockTreasurycirBTC sinkUSDC / $HARC poolexact-input swap$HARC → 0xdead

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 (TREASURY env, 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 AMM98.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.

read the code
  • contracts/src/HookarcHook.sol claimProtocolFees, claimCreatorFees, claimPot, sweepProtocolFees, _redeem, unlockCallback
  • contracts/src/FlywheelBurner.sol configure, buybackAndBurn, harc(), totalQuoteSpent, unlockCallback
  • contracts/src/CreatorFeeNFT.sol mint and the one-shot minter
  • contracts/test/invariant/Solvency.t.sol claims cover ledgers; no loose balances
  • contracts/test/FlywheelBurner.t.sol caps, once-per-block, slippage floor