The Rehyp Hook
A Uniswap v4 hook that is the $REHYP ERC-20. One contract, three asset pools, Aave-fed buybacks.
01Executive Summary
One Solidity contract is both the $REHYP ERC-20 and a Uniswap v4 hook. There are no LPs — the hook holds all inventory and quotes prices off a virtual constant-product curve. Idle asset reserves earn interest on Aave v3, and that interest is used to buy REHYP back along the curve and burn it.
$REHYP ships as a Uniswap v4 hook with hook-owned inventory. The same address is the ERC-20 token, the hook contract, and the counterparty to every swap. There are three asset pools on Base — WETH/REHYP, USDC/REHYP, GHO/REHYP — each routed through the same hook, each backed by its own Aave v3 aToken.
Because the design starts with no LP reserves, the hook uses Uniswap v4's custom-accounting path: beforeSwap returns a BeforeSwapDelta and settles input/output directly against the PoolManager rather than against an LP-provided curve. The asset side of each pool keeps a small raw buffer (default 20% of xReserve); everything above that target is supplied to Aave to earn interest. When a sell needs more asset than the raw buffer holds, the hook withdraws from Aave inside the same swap call.
Buy REHYP → asset flows in → excess auto-supplied to Aave. Sell REHYP → asset withdrawn from Aave on demand. Interest accrues on the asset side; harvestYield() buys REHYP with that interest and burns it. Supply only ever goes down.
02Background & Primitives
2.1 Uniswap v4 Custom Accounting
Uniswap v4 lets a hook return a BeforeSwapDelta from beforeSwap, which credits or debits both sides of the swap directly against the PoolManager. The pool's own concentrated-liquidity reserves are bypassed entirely. This is the only way to launch with zero LP liquidity: the hook itself supplies the quote, takes the input, and settles the output.
Because the hook takes input via PoolManager.take() inside beforeSwap, swaps must be routed through a router that prepays the swap input into the PoolManager during its unlock callback — a normal v3-style router that settles input only after the swap will revert against a zero-reserve pool. On Base this works out of the box with the official v4 Universal Router via Permit2.
2.2 The Hook Address Is the Token
The contract implements both IHooks and the ERC-20 interface for $REHYP. currency1 in every pool key is the contract's own address. When the hook needs to release REHYP to a buyer it calls PoolManager.burn on an internal ERC-6909 claim balance, which credits the swapper's side of the swap delta with REHYP. There is never any ERC-20 transfer on the swap path.
2.3 Aave v3 aTokens as the Yield Source
The hook supplies excess asset balance into Aave v3 via IPool.supply and receives the corresponding aToken (aWETH, aUSDC, aGHO on Base). aTokens accrue interest in-place — the balance grows over time without any user action. On a sell that needs more asset than the raw buffer holds, the hook calls aave.withdraw directly into the PoolManager's expected target.
There is no Morpho integration. There is no Euler integration. There is one yield source per asset, and it is Aave v3.
03Architecture Overview
3.1 Component Map
| Component | Role |
|---|---|
RehypHook.sol | One contract: $REHYP ERC-20 + Uniswap v4 hook + Aave integration |
assetPools[asset] | Per-asset state: xReserve, xVirtual, yReserve, aToken, rawTargetBps |
| Aave v3 Pool | External lending source — single integration, no ERC-4626 wrapper used |
| aToken (per asset) | aWETH / aUSDC / aGHO — interest-bearing receipt held by the hook |
v4 PoolManager | Holds REHYP claims (ERC-6909) seeded once at launch; settles all swap deltas |
| Universal Router + Permit2 | The required prepaying entry point for end-user swaps |
3.2 The Three Pools
At deploy time the constructor accepts three PoolConfigInput entries. Each registers an asset with its aToken, its initial REHYP reserve (yReserve), its virtual asset reserve (xVirtual), and the raw-buffer target in basis points. The total of the three yReserve values is the entire $REHYP supply — 50,000,000 by default — minted once to the hook itself and never inflated.
| Pool | Asset (Base) | aToken (Base) | Default seed (REHYP) |
|---|---|---|---|
| WETH / REHYP | 0x4200…0006 | aWETH · 0xD4a0…8bb7 | 20,000,000 |
| USDC / REHYP | 0x8335…2913 | aUSDC · 0x4e65…c0AB | 15,000,000 |
| GHO / REHYP | 0x6Bb7…10Ee | aGHO · 0x067a…cBd1 | 15,000,000 |
3.3 Pool Key Constraints
Every pool initialised against this hook must use currency1 = address(this), fee = 0 (the LP fee path is unused), and tickSpacing = 1. Attempts to addLiquidity or donate revert with NoLP / NoDonate. Only buys and sells go through.
04Hook Logic in Detail
4.1 beforeInitialize — Register the Asset Pool
On PoolManager.initialize, the hook validates the pool key and records the resulting PoolId against the asset. Once initialised, a pool cannot be re-initialised. This is the only place where pool/asset identity is bound.
4.2 seedRehypClaims — One-Shot Inventory Seeding
After deployment the launch script calls seedRehypClaims() once. The hook unlocks the PoolManager, transfers its entire ERC-20 balance to the PoolManager, settles, and mints itself an equivalent ERC-6909 claim balance against its own currency. From that point on, releasing REHYP to a swap is a PoolManager.burn against the hook's claim, and receiving REHYP back from a sell is a PoolManager.mint.
4.3 beforeAddLiquidity / beforeRemoveLiquidity — Reject
Both revert with NoLP. There is no external liquidity provision. All inventory is hook-owned.
4.4 beforeSwap — Buys
A buy is zeroForOne = true: asset in, REHYP out. The hook quotes the buy against the virtual constant-product curve, takes the input asset out of the PoolManager via take(), deposits any amount above the raw-buffer target into Aave, and returns a BeforeSwapDelta that credits the swapper with REHYP.
if (params.zeroForOne) { fee = amountIn * feeBps / BPS; amountOut = yReserve * (amountIn - fee) / (xReserve + xVirtual + amountIn - fee); poolManager.take(asset, address(this), amountIn); _depositExcessRaw(asset, pool); // supply to Aave above target poolManager.burn(address(this), rehypId, amountOut); // release REHYP to swapper }
4.5 beforeSwap — Sells
A sell is zeroForOne = false: REHYP in, asset out. The hook claims REHYP back from the PoolManager, then settles the asset side. If the raw balance is enough it transfers directly; otherwise it withdraws the shortfall from Aave straight to the PoolManager.
if (rawBalance >= amountOut) { IERC20(asset).safeTransfer(address(poolManager), amountOut); poolManager.settle(); } else { // transfer what we have, then top up from Aave uint256 shortfall = amountOut - rawBalance; aave.withdraw(asset, shortfall, address(poolManager)); poolManager.settle(); }
Aave occasionally returns one or two wei less than requested due to internal rounding. The hook tolerates a shortfall up to AAVE_ROUNDING_TOLERANCE by absorbing the difference into xReserve and emitting AaveRoundingShortfallAbsorbed. Anything larger reverts the swap.
4.6 rebalanceRaw — Push Excess Into Aave
Permissionless. Anyone can call rebalanceRaw(asset) to sweep any raw balance above the pool's rawTargetBps into Aave. The same routine runs automatically inside every buy and sell, so explicit calls are only useful when raw balance has accumulated outside the swap path.
05Yield Harvest & Burn
The single deflationary mechanism: Aave interest accrued on the asset side is treated as a virtual buyback input, used to consume REHYP from the curve, and then permanently burned.
5.1 Measuring Realised Yield
For each asset, the hook tracks xReserve as the principal it actually owes back to swappers. The real managed total is rawBalance + aTokenBalance. The delta between managed total and xReserve is realised yield:
managed = IERC20(asset).balanceOf(this) + IERC20(aToken).balanceOf(this);
yieldAssets = managed - pool.xReserve; // must be > 0
5.2 Buying REHYP With Yield
harvestYield(asset) is permissionless. It quotes the yield amount as a buy on the curve — but at zero fee, since the buyer is the protocol itself — and burns the REHYP that would have been minted to a normal buyer:
rehypBurned = yReserve * yieldAssets / (xReserve + xVirtual + yieldAssets); xReserve += yieldAssets; // principal grows yReserve -= rehypBurned; // circulating supply drops poolManager.unlock(UNLOCK_BURN_REHYP, rehypBurned); totalRehypBurnedFromYield[asset] += rehypBurned;
The unlock path mirrors the seed: burn the claim against the PoolManager, clear the delta, and decrement totalSupply. The supply only ever moves in one direction.
5.3 Why It's Safe
Because the principal (xReserve) is reconciled against managed balance before any burn, a harvest can never reduce the asset-side cushion that swappers depend on. RehypReserveTooLow reverts before the burn if it would push yReserve below MIN_REHYP_RESERVE. Aave insolvency would manifest as aTokenBalance < xReserve, which makes managed - xReserve underflow-check revert — harvest fails, but no funds are at additional risk.
06Pricing Model
6.1 Virtual Constant Product
Each pool runs a standard x · y = k curve, but with a virtual offset on the asset side:
k = (xReserve + xVirtual) * yReserve
xVirtual is set at construction. It exists only as a number — no asset actually backs it — and serves to set the initial price without requiring a seed deposit. xReserve starts at zero and grows as buys arrive; xVirtual never changes.
6.2 Initial Price
With xReserve = 0, the spot price of one REHYP in asset units is xVirtual / yReserve. The launch script derives xVirtual from a target initial market cap in USD and a USD WAD for the asset:
xVirtual = (targetMarketCapUSD * 1e18 * 10**assetDecimals)
/ (totalSupply * assetUsdWad);
The default configuration targets a $10,000 initial market cap across 50M REHYP — an initial price of $0.0002 per REHYP, encoded separately into each pool's xVirtual based on that asset's USD price.
6.3 Fee
A single fee parameter applies to all three pools, default 30 bps (0.30%). Buy-side fees are subtracted from the asset input before quoting; sell-side fees are grossed up so that the swapper receives the requested net amount. The fee is retained inside the pool — there is no external fee recipient — so it grows xReserve and therefore counts toward future harvestable yield once supplied to Aave.
6.4 Quoting Helpers
For UIs and routers, four read-only quote helpers are exposed:
| Function | Returns |
|---|---|
quoteBuy(asset, amountIn) | amountOut, fee |
quoteBuyExactOut(asset, amountOut) | amountIn, fee |
quoteSell(asset, amountIn) | amountOut, fee |
quoteSellExactOut(asset, amountOut) | amountIn, fee |
sellCap(asset) returns the maximum REHYP that can be sold given the current raw + Aave-withdrawable balance — useful for routers to detect a partial-fill scenario before submitting.
07Solidity Surface
Excerpts from src/RehypHook.sol. Refer to the repository for the full source.
7.1 State
struct AssetPool { bool enabled; bool initialized; address aToken; // e.g. aWETH PoolId poolId; uint256 xVirtual; // constant; sets initial price uint256 xReserve; // principal asset owed to swappers uint256 yReserve; // circulating REHYP in this pool uint16 rawTargetBps; // e.g. 2000 = keep 20% raw, lend the rest } mapping(address asset => AssetPool) public assetPools; mapping(address asset => uint256) public totalYieldHarvested; mapping(address asset => uint256) public totalRehypBurnedFromYield;
7.2 Hook Permission Bits
function getHookPermissions() public pure returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, beforeAddLiquidity: true, // reverts: NoLP beforeRemoveLiquidity: true, // reverts: NoLP beforeSwap: true, beforeDonate: true, // reverts: NoDonate beforeSwapReturnDelta: true, // everything else: false }); }
7.3 Public Read API
function poolView(address asset) external view returns ( bool initialized, PoolId poolId, address aToken, uint256 xReserve, uint256 xVirtual, uint256 yReserve, uint256 rawAssets, uint256 aaveAssets, uint256 withdrawableAssets, uint256 harvestableYield ); function sellCap(address asset) external view returns (uint256 rehypInMax);
7.4 Public Write API
function harvestYield(address asset) external returns (uint256 yieldAssets, uint256 rehypBurned); function rebalanceRaw(address asset) external returns (uint256 deposited); // One-shot at launch; permissionless function seedRehypClaims() external returns (uint256 amount);
08The $REHYP Token
8.1 Supply
Fixed supply of 50,000,000 REHYP, minted once in the constructor — equal to the sum of the three pool seeds. The constructor is the only mint site. There is no owner-mint function, no governance mint, no upgradeability. Supply moves only in one direction, via harvestYield burns.
8.2 Custody Model
At launch the hook holds the entire supply as its own ERC-20 balance. seedRehypClaims() transfers all of it into the v4 PoolManager as an ERC-6909 claim balance against currency address(this). From that point, REHYP circulates only via swaps: a buy mints a swap-side credit that the router forwards to the swapper, and a sell takes it back.
8.3 Why REHYP Is Held by the Hook, Not Distributed
Because pricing is virtual-CPMM and there are no LPs, the hook itself is the counterparty. Distributing REHYP outside the swap path before launch would imbalance yReserve against the curve and break price discovery. Every REHYP in circulation entered through a buy at the curve's then-current price.
Fee bps, raw-target bps, xVirtual, aToken bindings and pool registrations are all set in the constructor and are immutable. There is no owner, no multisig, no pausing, no upgrade path. The only ongoing privileged action is harvestYield, and that is permissionless.
09Risk Analysis
| Risk | Mitigation |
|---|---|
| Aave v3 insolvency or paused withdrawals on Base | Single, well-known integration; sellCap exposes withdrawable headroom so routers can size accordingly; sells revert before partial fill |
| Custom-accounting correctness | Full v4 PoolManager integration test plus mainnet- and Base-fork tests against the official deployment |
| Router uses non-prepaying flow | Hook reverts on a normal v3-style router; documented requirement to use the v4 Universal Router (or any router that settles input during unlock) |
| Aave rounding produces under-withdrawal | Up to AAVE_ROUNDING_TOLERANCE wei absorbed into xReserve; larger shortfalls revert the swap |
| Reentrancy via aToken / asset transfer | All external token transfers settle through the v4 PoolManager lock; asset and aToken contracts on Base are non-reentrant ERC-20s |
| Hook permission-bit mismatch on deployment | CREATE2 salt-mining in DeployRehypHook.s.sol forces the required permission bits into the address; validateHookAddress() checks them post-deploy |
| Price manipulation via large in-block buy then sell | Swapper pays fee both ways and round-trips the curve; sandwiches are an MEV cost for the user but cannot extract beyond standard CPMM bounds |
10Deployment & Configuration
10.1 Launch Script
- CREATE2 mine an address with the correct hook permission flags (
0x2aa8). - Deploy
RehypHookwith the v4PoolManager, Aave v3Pool, fee bps, and threePoolConfigInputentries. - Call
seedRehypClaims()once to move the entire supply into thePoolManager. - Call
PoolManager.initializethree times — once each for WETH/REHYP, USDC/REHYP, GHO/REHYP — withfee = 0,tickSpacing = 1, and the initialsqrtPriceX96.
10.2 Default Parameters
| Parameter | Default |
|---|---|
feeBps | 30 (0.30% on every swap) |
rawTargetBps | 2000 (keep 20% of xReserve raw; supply 80% to Aave) |
totalSupply | 50_000_000e18 |
targetMarketCapUSD | 10_000 ($0.0002 / REHYP initial) |
| Pool seed (WETH / USDC / GHO) | 20M / 15M / 15M REHYP respectively |
| Network | Base mainnet |
10.3 Base Mainnet Addresses
| Contract | Address |
|---|---|
v4 PoolManager | 0x498581fF718922c3f8e6A244956aF099B2652b2b |
Aave v3 Pool | 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5 |
| WETH | 0x4200000000000000000000000000000000000006 |
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| GHO | 0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee |
| Rehyp Hook · $REHYP | 0xa6BFdD21489C576B48585DC28D74AB79d0Af6aa8 |
11References & Further Reading
- Uniswap v4 Core — github.com/Uniswap/v4-core
- Uniswap v4 Hooks Documentation — docs.uniswap.org/contracts/v4/concepts/hooks
- v4 Custom Accounting (BeforeSwapDelta) — docs.uniswap.org/contracts/v4/concepts/custom-accounting
- Uniswap Universal Router — github.com/Uniswap/universal-router
- Permit2 — github.com/Uniswap/permit2
- Aave v3 — aave.com
- Aave v3 Base Deployment — app.aave.com/markets · Base
This document is a technical design specification for research and development purposes. It does not constitute financial or investment advice.