ReputationFee implements 2 of the fourteen Uniswap v4 callbacks: afterInitialize, beforeSwap.
drag to orbit
Uniswap v4 hook · Agent-native
ReputationFee
Charges a counterparty according to how it has behaved, using an on-chain reputation registry, with no signature to present and nothing for a router to forward.
- Family
- Agent-native
- Callbacks
- 2 of 14
- Fee
- dynamic
- Admin keys
- none
- Licence
- Apache-2.0
How it works
Pools price everyone identically because they cannot tell anyone apart, and the cost of that falls on the people who are cheapest to trade with. A venue that could distinguish its flow would quote the retail order and the arbitrageur differently, which is what every venue that can identify its counterparties actually does. The obstacle in v4 is that a hook does not see the trader.
`beforeSwap` receives the router, not the person who called it, so any scheme keyed on the trader's address needs the trader to sign something and the router to forward it, which means most routers cannot trade the pool at all. This takes the other route. register(myRouter); Thereafter the hook resolves `sender` to that agent with one storage read, asks the registry for its score, and prices the swap between `maxFee` at a score of zero and `minFee` at a perfect score.
No signature, no `hookData`, nothing for a router to support. Flow through an unregistered router is simply unknown and pays `maxFee`, which is the correct default: an unidentified counterparty is priced as the worst one. Spoofing is not possible because the mapping runs from router to agent and only the agent can write its own entry.
Registering a router somebody else also uses means paying for their flow with your reputation, which is a mistake you can only make about yourself. The registry is fixed at deployment and the fee bounds are fixed before the pool exists. There is no admin and no way to re-point the pool at a friendlier scorer once liquidity has arrived.
Prior art
Identity-gated hooks are common (KYC, Civic, VioletID, World ID, PureFi) and they gate: pass or be refused. Loyalty and fidelity hooks discount by volume or tenure, which is a proxy for behaviour rather than a judgement of it. Pricing continuously off an external reputation score, and resolving the trader through a registered router so no signature or hookData is needed, is the contribution here.
Where it does not help
The pool trusts the registry absolutely: a registry that can be bought is a fee schedule that can be bought, and nothing here detects that. Registration is also per router, so an agent that trades through a router it has not registered pays the unknown rate until it registers, and an agent using a shared public router either cannot register it or is subsidising everyone else who uses it.
Using it
Uniswap v4 removed hookData from initialize, so per-pool parameters arrive out of band.
Fix them for a pool key whose pool does not exist yet, then initialize. Nobody can change them afterwards,
including you.
hook.configure(
key,
ReputationFeeHook.Config({
minFee: /* uint24 */ 0,
maxFee: /* uint24 */ 0
})
);
poolManager.initialize(key, startingSqrtPriceX96);
The pool's fee field must be LPFeeLibrary.DYNAMIC_FEE_FLAG. The hook rejects a pool initialized without it, which is the most common integration failure.
Parameters
| Parameter | Type | Units |
|---|---|---|
minFee | uint24 | hundredths of a bip (3000 = 0.30%) |
maxFee | uint24 | hundredths of a bip (3000 = 0.30%) |
From TypeScript
npm i @hookforge/sdk
import {getHook, hookAddress, poolKeyFor} from "@hookforge/sdk";
const hook = getHook("reputation-fee");
const key = poolKeyFor({
hook: hookAddress("reputation-fee", 8453), // Base
currencyA: USDC, currencyB: WETH,
tickSpacing: 60, dynamicFee: true,
});
What it reverts with
| Error | Meaning |
|---|---|
FeeTooLarge(uint24) | A fee was configured above the protocol maximum of 100%. |
InvalidRegistry() | A registry that reports a zero scale cannot be normalised against. |
MaxBelowMin() | maxFee must be at least minFee; a schedule that rewards a bad score is a configuration error. |
NotDynamicFee() | The hook was attempted to be initialized with a non-dynamic fee. |
PoolAlreadyInitialized() | The pool already exists, so its configuration is final. |
PoolNotConfigured() | The pool was initialized without a configuration for this hook. |
The callbacks it claims
Uniswap v4 reads a hook's permissions from the low fourteen bits of its own address, which is why deploying one
means mining a CREATE2 salt. This hook claims 2, so every deployment of it has an address ending
in 0x1080.
- beforeInitialize
- afterInitialize
- beforeAddLiquidity
- afterAddLiquidity
- beforeRemoveLiquidity
- afterRemoveLiquidity
- beforeSwap
- afterSwap
- beforeDonate
- afterDonate
- beforeSwapReturnsDelta
- afterSwapReturnsDelta
- afterAddLiquidityReturnsDelta
- afterRemoveLiquidityReturnsDelta
It says what it is, on-chain
Nothing about a hook's address tells an indexer, a wallet, a router or an agent what the pool does, which is why
hook discovery today is a curated list. This hook answers for itself, in one eth_call, with no
registry in the loop.
cast call $HOOK "hookName()(string)" # ReputationFee
cast call $HOOK "specURI()(string)" # https://reputation-fee.pages.dev/hook.json
cast call $HOOK "hookTags()(string[])" # agent, reputation, dynamic-fee, erc8004, no-admin
Build, test and deploy
git clone --recurse-submodules https://github.com/nirholas/reputation-fee
cd reputation-fee
forge build && forge test
# Dry run: mines the salt, prints the address, sends nothing.
forge script script/Deploy.s.sol --rpc-url $RPC_URL
# For real.
forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify
Status
Unaudited. Built to an audited shape, on OpenZeppelin's audited hook bases, and tested against
a real PoolManager. No third party has reviewed it. Read "where it does not help" above before
putting money behind it. Not affiliated with Uniswap Labs.
Try it
This is the hook running, not a picture of it. Connect a wallet on a chain it is deployed to, or bring the whole stack up locally in one command and use it with no funds and no wallet risk at all.
Loading the demo… if this does not change, JavaScript is blocked and the demo cannot run.
Run the whole thing locally
git clone --recurse-submodules https://github.com/nirholas/reputation-fee
cd reputation-fee
anvil &
forge script script/DeployLocal.s.sol --rpc-url http://127.0.0.1:8545 --broadcast \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
node web/build.mjs && npx serve web/dist
The deploy script writes web/local.json itself and the build merges it, so the page points at the
chain you just created without you editing anything. Point a wallet at
http://127.0.0.1:8545 and every button on this page works.
Anvil's first account is pre-funded and its key is public by design. Never use it anywhere real.