Home / Documentation
Built on Uniswap v4How UniGuard works
UniGuard is a programmable Uniswap v4 hook that runs inside a token's own pool — enforcing progressive wallet limits, adaptive execution fees, and automatic sell-pressure decay directly in the swap path, block by block, with no off-chain component.
One hook slot. Three defense systems, running live.
UniGuard is a single Uniswap v4 hook contract (PinkShieldHook.sol) that a project creator configures and attaches to a new pool. Uniswap v4 allows exactly one hook per pool, so UniGuard composes three independent protection modules into that one slot: Launch Guard, Adaptive MEV Defense, and Dynamic Anti-Dump. Each is independently configurable, and each can be disabled entirely if a creator doesn't want it.
Every numeric parameter — every cap, every fee, every duration, every threshold — is set once, before deployment, validated on-chain by ConfigurationValidation.sol, and becomes permanently immutable the moment the pool initializes. There is no admin function anywhere in the deployed contracts that can change a locked parameter afterward. The pool itself is a real Uniswap v4 pool — standard liquidity, standard settlement, fully compatible with any v4 router once the Launch Guard window ends.
Launch directly from UniGuard
UniGuard protects a token's market — the pool it trades in — not the token contract itself. If you already have a token, you use its address directly. If you don't, PinkShieldTokenFactory.sol can create one for you as the first step of the same launch flow, so the whole thing happens in one place without needing a separate token deployer.
The token this creates is deliberately minimal: a standard, OpenZeppelin-based ERC20 with a fixed supply, minted once, in full, to the address you specify (typically your own wallet) — and almost nothing else. No owner, no mint function, no pause, no blacklist. Once deployed, there is no function anywhere that can change its supply or restrict who can hold or transfer it. This is intentional: a token with a hidden mint or pause function would undermine every guarantee the pool-level protection makes, no matter how well the hook itself is built.
The one exception: an optional description and image URI, written once into the token's own storage in the same deployment transaction. Same rule as everything else here — no setter, permanent the instant the token deploys. image is a link (an https:// or ipfs:// URI), not raw image bytes — storing an actual picture on-chain would cost gas proportional to its file size.
What this does not do is seed liquidity or handle tokenomics beyond the fixed mint — you decide how much of your own supply to add as pool liquidity (via Uniswap's own position manager, after your pool initializes) versus keep. UniGuard does not take a cut of the token supply, hold any of it in escrow, or have any privileged claim on it after deployment.
The three modules
Launch Guard
Up to three progressive stages, each with its own per-transaction cap, per-address cumulative cap, max-quote-spend cap, wallet-allocation percentage cap, and cooldown between acquisitions. Caps are enforced in afterSwap, against the swap's realized settled amounts — not an estimate — because an exact-input swap's output amount isn't known until the AMM curve actually executes. ConfigurationValidation.sol rejects any configuration where a later stage is stricter than an earlier one, so the progression can only ever loosen. Sells are never subject to any Launch Guard restriction, at any time.
Adaptive MEV Defense
A dynamic execution fee composed of a base rate plus a flat launch-window surcharge, plus a surcharge that scales with the swap's estimated size-to-liquidity ratio and same-block swap clustering (computed in beforeSwap, before the fee must be returned), reconciled in afterSwap against the swap's realized price impact — which, if a maxImpactBps cap is configured and exceeded during the guard window, reverts the whole transaction. This is the one place in the fee model that blocks a swap outright rather than pricing it — and it is deliberately evaluated for buys only. Its purpose is stopping a sniper's buy from moving price too far too fast; it can never be the reason a sell reverts, at any time, no matter how large the realized impact.
Dynamic Anti-Dump
Sell-side only. Rolling buy/sell volume is tracked as an exponentially-decaying accumulator (piecewise approximation, precise to within one configured half-life); when net sell pressure relative to current liquidity crosses a configured threshold, a fee band applies — plus a flat surcharge for any single unusually large sell. Both decay back to baseline automatically as pressure normalizes, with no admin action required.
The fee, computed live, every block
Every fee value uses Uniswap v4's own LP-fee unit — hundredths of a basis point, denominator 1,000,000. For a given swap, four components are summed, then clamped to the lesser of the pool's own configured maximum and the protocol-wide hard cap of 5.00%:
effectiveFee = min(baseFee + launchComponent + impactComponent + sellPressureComponent,
min(poolMaxFee, HARD_MAX_FEE_BPS))This is FeeMath.composeAndCap — the single choke point every effective fee passes through. No configuration and no code path can produce a fee above the hard cap, regardless of how the four components are configured (proven for arbitrary inputs by the contracts' own property-based fuzz tests).
Buy during Launch Guard
0.50%
Base 0.30% + launch surcharge 0.20%
Sell during Launch Guard
0.50%
Base 0.30% + launch surcharge 0.20%
Sell after Launch Guard ends
0.30%
Only the base fee applies once the guard window has fully elapsed.
Sell-pressure band at 32% rolling sell ratio
4.00%
Highest configured band — this is a fee, not a block; selling always remains possible.
Composed worst case shown for illustration only (base + launch-sell surcharge + severe sell-pressure band, all from the documented default policy): 4.00%, still under the pool's configured maximum and the 5.00% protocol hard cap.
Each component, in full
Base & launch
baseFee = configured constant, always applied launchFee = launchActive ? (isBuy ? launchBuyFeeBps : launchSellFeeBps) : 0
Impact — trade size vs. liquidity, plus same-block clustering
sizeRatio = tradeSize / currentLiquidity (bps) excess = max(0, sizeRatio − impactThresholdBps) sizeSurcharge = excess × impactMultiplierBps / 1,000,000 clusterSurchg = swapsAlreadyInThisBlock × impactMultiplierBps / 10 impactFee = sizeSurcharge + clusterSurchg
Estimated in beforeSwap from trade size (the exact post-trade price isn't known yet); reconciled against the swap's realized impact in afterSwap, computed directly from the pool's sqrtPriceX96 before and after:
priceRatio = (sqrtPriceAfter / sqrtPriceBefore)² realizedImpact = |priceRatio − 1| → expressed in bps (× 1,000,000)
Sell pressure — threshold bands over a decaying accumulator
sellRatio = decayedSellVolume / currentLiquidity
fee(r) = sellPressureFee3Bps if r ≥ threshold3
| sellPressureFee2Bps if r ≥ threshold2
| sellPressureFee1Bps if r ≥ threshold1
| 0 otherwiseDecay: a half-life, not a sliding window
Both rolling accumulators (Adaptive MEV Defense's velocity window and Dynamic Anti-Dump's sell-pressure window) use the same primitive — RollingFlowState.sol — rather than an explicit sliding-window buffer, which would need unbounded storage or an unbounded loop to prune. Decay is instead applied lazily, in O(1), the next time the accumulator is touched:
halvings = floor(secondsElapsed / halfLifeSeconds) value(t) = value₀ >> halvings (a right bit-shift — an exact halving each step)
This is a piecewise approximation of continuous exponential decay — precise to within one half-life, not a smooth curve — which is why the chart below steps rather than curves. Plotted for this policy's actual configured half-life of 5 minutes:
The full specification — including the same-block clustering term, the realized-impact formula, and the decayed-accumulator math — is written out in the repository's docs/fee-model.md, kept bit-for-bit equivalent between the Solidity implementation and the SDK's TypeScript mirror used on this page.
Why some buys require PinkShieldRouter
Uniswap v4's flash-accounting design means the hook only ever sees sender = the router contract that called PoolManager.swap() — never a raw wallet address (a wallet has no code to run PoolManager's required unlock callback). If per-wallet caps relied on sender directly, every trader sharing a router like Universal Router would collapse into a single identity.
PinkShieldRouter is the one contract the hook trusts to honestly report the true trader: it sets hookData to the trader's own address as seen by the router's own code, and the hook only accepts that claim when the immediate caller is exactly this router. Any other router is rejected — but only on the buy side, and only during the Launch Guard's active window. Sells are never blocked by this check, at any time, by design — this constraint can never trap a holder's exit liquidity. Outside the guard window, per-wallet identity isn't needed at all (Adaptive MEV Defense and Dynamic Anti-Dump both operate on pool-wide rolling aggregates), so any router works normally for both buys and sells once the guard window ends.
This authenticates which address initiated a swap — not that the address is controlled by a distinct human. See Known limitations below for what this does and doesn't protect against.
For other routers & aggregators
- Sells: always work through any router, at any time. No identity check ever applies to a sell.
- Buys outside the Launch Guard window: work through any router normally — per-wallet caps are not enforced once the guard window has elapsed.
- Buys during the Launch Guard window: must route through
PinkShieldRouterto be attributed to the trader's real address (and therefore subject to, and benefit from, per-wallet caps rather than a shared router identity). A buy through any other router during this window reverts withUnverifiedRouterunless the address is on the pool's allowlist. - The pool itself is a completely standard Uniswap v4 pool — its address, pool key, and dynamic-fee flag are all readable the normal way. Nothing about UniGuard requires a proprietary front end to trade against a protected pool outside the guard window.
No indexer — and that's a scoped decision
/pools and every pool dashboard on this site read on-chain state directly: readContract calls for current configuration and live fee quotes, and getLogs scans (chunked, with an explicit cap) for HookDeployed and ProtectedSwap event history. There is no backend database and no separate indexing service for this MVP — that's a deliberate scoping decision for a launch with a modest number of pools and no deep historical-analytics requirement yet, not an oversight. Where a scan hits its range cap, the affected page says so explicitly rather than silently presenting a partial result as complete.
A dedicated indexer is a natural next step once pool count and history depth justify it — the event schema in IPinkShieldHook.sol was written with that in mind.
Known limitations
UniGuard constrains abusive execution patterns, raises their economic cost, and limits concentration — it does not, and does not claim to, eliminate bots, guarantee the absence of sandwich attacks, or identify real humans. Every limitation we're aware of, stated as precisely as we can:
Multi-wallet circumvention
Every Launch Guard control is enforced per address, not per person. Nothing available to any Ethereum smart contract can distinguish one actor controlling many wallets from many independent holders. Progressive stage loosening is designed so this cost is highest in the first, most valuable minutes.
Router identity is authenticated, not verified-as-human
PinkShieldRouter authenticates which address initiated a swap. It cannot verify that address is controlled by a distinct human, or that it isn't a thin proxy for another address. Buys during the Launch Guard window must route through PinkShieldRouter or revert; sells are never subject to this restriction, at any time.
No visibility into private order flow
UniGuard has no visibility into private mempools or builder auctions. Its on-chain signals still apply to whatever swap eventually lands on-chain, but it cannot see or price how priority access was obtained.
Imperfect bot classification
UniGuard does not classify a transaction as "bot" or "human." Every signal it uses is a statistical proxy for automated or toxic flow, priced as additional cost — not a certain determination. A large, urgent retail buy can trigger the same surcharge as an automated one.
Limits of on-chain sandwich inference
A sandwich attack spans three transactions and, from inside any one of them, is indistinguishable from unrelated trades landing in the same block. UniGuard raises the cost of the pattern (same-block clustering fee, realized-impact cap) — it cannot definitively identify or reverse a specific sandwich after the fact.
Denylist / allowlist are admin-trust surfaces, but only for a short, fixed window
While the Launch Guard window is active, the creator-admin can deny or allow addresses, or pause new buys. This is real, bounded admin power — it cannot block sells, seize funds, or raise fees past the hard cap — and a malicious or compromised creator-admin could deny legitimate buyers or allowlist a colluding wallet to bypass caps during that window. The window itself is short and immutably fixed at deployment (30 minutes per stage, 1 hour total, protocol-enforced maximum): the moment it ends, calling any of these three functions reverts unconditionally for every address, including the creator's — not merely 'has no effect,' but a reverting transaction, provable by trying it. Anyone can also permissionlessly call finalizeLaunchProtection() once the window ends to zero out the creator-admin role on-chain, without depending on the creator's cooperation.
No oracle, no off-chain price reference
Every signal is derived from the pool's own on-chain state. A pool seeded with very little initial liquidity will show larger percentage price impacts and sell-pressure swings from ordinarily-sized trades — creators should size limits relative to their actual seeded liquidity.
Creator-minted supply is a standard, disclosed trust point
When a token is created through PinkShieldTokenFactory, its entire fixed supply is minted once to the address the creator specifies — same as any fixed-supply token launch anywhere. PinkShieldTokenFactory has no admin function and no ongoing relationship to a token after deploying it.
What creator-admin can and cannot do
The three temporary levers below are only ever callable while the Launch Guard window is still active — the same short, immutably-fixed window (30 minutes per stage, 1 hour total, protocol-enforced maximum) that gates the protection itself. The instant that window ends, calling any of them reverts unconditionally, for every address including the creator's — not merely "has no effect," a reverting transaction, provable by anyone simply by trying it.
Can, only during the Launch Guard window
- Pause new protected buys (never sells, never trading generally)
- Manage the allow/denylist (denylist blocks new buys only, never sells or holding)
Can, at any time
- Irrevocably renounce its own role early (always holder-favorable)
Anyone, once the window ends
- Permissionlessly call
finalizeLaunchProtection()to zero out the creator-admin role on-chain, with or without the creator's cooperation
Cannot, ever
- Change any locked numeric parameter after pool initialization
- Raise any fee above the contract-wide 5% hard cap
- Block or disable sells, at any time, for any reason
- Call pause / denylist / allowlist once the Launch Guard window has ended — the functions themselves revert, for anyone
- Withdraw or redirect user or pool funds
- Upgrade or replace the hook's logic — bytecode is immutable, no proxy
Verified addresses
Every Uniswap v4 address below was independently verified by reading deployed bytecode via eth_getCode against https://rpc.mainnet.chain.robinhood.com before being hard-coded into the frontend, and re-verified live by a mainnet-fork test that deploys a real hook and initializes a real pool against this exact PoolManager. Robinhood Chain mainnet, chain ID 4663.
| Contract | Address |
|---|---|
| pool Manager | 0x8366…0951 |
| position Descriptor | 0x9639…Dc06 |
| position Manager | 0x58da…4fA7 |
| quoter | 0x8DC1…8F94 |
| state View | 0xF333…673B |
| universal Router | 0x8876…0904 |
| permit2 | 0x0000…8BA3 |
| PinkShieldFactory | 0x90af…8191 |
| PinkShieldRouter | 0xde3d…cB3a |
| PinkShieldTokenFactory | 0x7430…c44a |