A Uniswap v4 hook that redirects LP trading fees to a beneficiary. LPs provide liquidity through the standard Uniswap interface — no custom frontend, no approvals, no extra transactions. Every swap that passes through a Patron Pool automatically accrues fees for the designated cause.
Each Patron Pool is a standard Uniswap v4 liquidity pool with a hook that intercepts the fee mechanism:
beforeSwapoverrides the LP fee to zero — no fees accrue to LP positions.afterSwapcomputes a hook fee on the swap output, extracts the tokens viapoolManager.take(), and records them in an internal ledger.- The beneficiary (or anyone) calls
collectFees()to withdraw accumulated fees at any time.
From the trader's perspective, the pool behaves like any other Uniswap v4 pool. From the LP's perspective, they are providing liquidity knowing their fee revenue goes to a cause instead of to themselves.
Each hook contract is deployed with an immutable beneficiary address and immutable fee percentage. A hook address represents a specific (beneficiary, fee tier) combination.
PatronPoolFactory
│
├── deploy(beneficiary, fee, salt) → PatronPoolHook @ 0xabc...
│ beneficiary = 0xBEEF (immutable)
│ fee = 500 (0.05%, immutable)
│
└── deploy(beneficiary, fee, salt) → PatronPoolHook @ 0xdef...
beneficiary = 0xBEEF (immutable)
fee = 3000 (0.30%, immutable)
The factory deploys hooks via CREATE2 with a mined salt that produces an address with the correct v4 permission flag bits.
Uniswap v4's PoolManager.initialize() does not accept a hookData parameter — there is no way to pass configuration to a hook during pool creation. By baking the beneficiary and fee into the contract as immutables, pool creation on the Uniswap UI requires zero extra steps: just paste the hook address and go.
Fees are not transferred on every swap. They accumulate in the hook contract and the beneficiary calls collectFees(currency) to withdraw. This eliminates reentrancy risk during swap execution and reduces per-swap gas.
| Contract | Purpose |
|---|---|
PatronPoolHook |
Core hook — overrides LP fees to zero, charges hook fee, accrues for beneficiary |
PatronPoolFactory |
Deploys hooks via CREATE2, maintains on-chain registry |
Constructor:
constructor(IPoolManager poolManager, address beneficiary, uint24 fee)beneficiary— address that receives accrued fees (immutable, cannot be zero)fee— fee in hundredths of a bip (3000 = 0.30%, max 10000 = 1.00%)
Hook Permissions:
| Permission | Purpose |
|---|---|
beforeInitialize |
Validates pool uses dynamic fee flag (0x800000) |
beforeSwap |
Overrides LP fee to zero |
afterSwap |
Computes fee, calls take(), records accrual |
afterSwapReturnDelta |
Returns delta to charge fee to swapper |
External Functions:
collectFees(Currency currency)— transfers all accrued fees in the given currency to the beneficiary. Permissionless (anyone can call it).
Events:
FeesRedirected(PoolId poolId, Currency currency, uint256 amount)— emitted on every swapFeesCollected(Currency currency, uint256 amount)— emitted on fee collection
Functions:
deploy(address beneficiary, uint24 fee, bytes32 salt)— deploys a new hook via CREATE2computeAddress(address beneficiary, uint24 fee, bytes32 salt)— predicts deployment addressisPatronHook(address hook)— checks if an address was deployed by this factoryhooks(address)— returns(beneficiary, fee)for a deployed hook
- Call
factory.deploy(beneficiaryAddress, fee, salt)— via a frontend, Etherscan, or script. - Receive the hook address. Share it publicly.
- Go to the Uniswap web app.
- Select Pool → New, choose a token pair.
- Select "Add a hook" and paste the Patron Pool hook address.
- Create the pool (it will be a dynamic fee pool).
- Find the pool on Uniswap.
- Add liquidity at any tick range.
- That's it — standard Uniswap v4 position, standard NFT.
Call hook.collectFees(currency) on the hook contract — via Etherscan, a frontend, or a script. Anyone can trigger collection; fees always go to the beneficiary.
The fee is set at hook deployment and cannot be changed. Recommended tiers:
| Pair Type | Recommended Fee | Value |
|---|---|---|
| Stablecoin / stablecoin | 0.01% | 100 |
| Blue-chip majors (ETH/USDC) | 0.05% | 500 |
| Mid-cap altcoins | 0.30% | 3000 |
| Exotic / long-tail | 1.00% | 10000 |
Uniswap's smart order router splits volume across pools proportional to liquidity. A Patron Pool doesn't need to undercut other pools — it just needs to match the standard fee tier for its pair type.
- Foundry
- Node.js (for dependency management)
git clone <repo-url>
cd patron-hook
npm install
forge buildforge testforge script script/DeployPatronPool.s.sol \
--rpc-url <RPC_URL> \
--broadcast \
--sig "run(address,address,uint24)" \
<POOL_MANAGER_ADDRESS> <BENEFICIARY_ADDRESS> <FEE>- Immutable — no admin functions, no upgrades, no mutable configuration.
- No
beforeSwapReturnDelta— the critical NoOp rug pull attack vector is not enabled. - No
beforeRemoveLiquidity— LP funds cannot be trapped. - Pull-based collection —
afterSwapmakes no external calls to unknown addresses. - Reentrancy guard on
collectFees(). - Fee cap — constructor enforces
fee <= 10000(1%).
- Fee-on-transfer tokens: If a pool's tokens charge transfer fees,
accruedFeesmay overstate the actual hook balance.collectFees()handles this gracefully by transferringmin(owed, actualBalance). - Exact-output swaps: Supported — fee is charged on the unspecified (input) side. Both swap directions are handled for both exact-input and exact-output.
This code has not been audited. Use at your own risk.
MIT