██╗███╗ ██╗████████╗███████╗███╗ ██╗████████╗ ██╗ ██████╗
██║████╗ ██║╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝ ██║ ██╔══██╗
██║██╔██╗ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║ ██████╔╝
██║██║╚██╗██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔═══╝
██║██║ ╚████║ ██║ ███████╗██║ ╚████║ ██║ ███████╗██║
╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝
Intent-Based Liquidity Provision · Uniswap v4 Hook
Like a limit order, but for liquidity provision.
Auto-playing slides — opens in Google Slides
IntentLP is a Uniswap v4 hook that gives liquidity providers conditional control over when their capital is actively routed through a pool.
Instead of passively sitting in a pool and absorbing every swap regardless of conditions, LPs register an intent — a set of on-chain rules that must be satisfied before a swap is allowed to flow through their position at standard fees.
When conditions are violated, a proportional penalty fee is applied, disincentivising unfavourable routing without ever hard-blocking the pool.
Passive LPs in Uniswap v3/v4 have no say in which swaps hit their positions. A 0.01 ETH dust swap, a 500 ETH whale dump, a swap at 3am during zero-liquidity hours — all treated identically. This is the primary source of impermanent loss that the market hasn't solved at the hook layer.
LP sets intent: "Only route through my position when:
• Swap size is between 0.5 ETH and 50 ETH
• Current time is between 09:00–17:00 UTC
• Max slippage is under 1%"
The hook enforces this automatically on every beforeSwap(). No keeper. No rebalancing bot. No external protocol dependency. Pure on-chain, composable, and gas-efficient.
intent-lp-hook/
│
├── src/ # Smart contracts
│ ├── IntentLPHook.sol # Core hook — intent logic, volume tracking, fee moderation
│ └── IntentLPHookFactory.sol # CREATE2 factory for valid hook address mining
│
├── test/
│ ├── IntentLPHook.t.sol # 39 unit tests + fuzz suite
│ └── IntentLPHookIntegration.t.sol # 23 integration tests (forked Arbitrum Sepolia)
│
├── script/
│ └── Deploy.s.sol # Deploy scripts: local Anvil + Unichain/Base/Arbitrum
│
├── subgraph/ # Graph Protocol subgraph for event indexing
│
├── frontend/ # Vite + React UI
│ ├── src/
│ │ ├── App.tsx # Root with WagmiProvider + QueryClientProvider
│ │ ├── pages/
│ │ │ ├── HomePage.tsx # Landing page
│ │ │ ├── AnalyticsPage.tsx # Pool analytics with TradingView charts
│ │ │ ├── DashboardPage.tsx # LP dashboard
│ │ │ ├── SwapPage.tsx # Swap interface
│ │ │ ├── RegisterPage.tsx # Intent registration
│ │ │ ├── BridgePage.tsx # Cross-chain bridge
│ │ │ ├── RewardsPage.tsx # USDC rewards
│ │ │ └── DocsPage.tsx # Documentation
│ │ ├── components/
│ │ │ ├── Navbar.tsx # Sticky nav, wallet connect, chain switcher
│ │ │ ├── Hero.tsx # Landing hero with animated headline
│ │ │ ├── HowItWorks.tsx # 4-step explainer
│ │ │ ├── Features.tsx # Feature grid + Solidity code preview
│ │ │ ├── Dashboard.tsx # Pool stats + my intent status + activity feed
│ │ │ ├── SwapSimulator.tsx # Live preview: will this swap violate my intent?
│ │ │ ├── IntentForm.tsx # Register/update LP intent (wallet-connected)
│ │ │ ├── RegisterSection.tsx # Full register CTA section
│ │ │ ├── BridgeIntentSection.tsx # Cross-chain bridge form with Across
│ │ │ ├── Docs.tsx # FAQ accordion + resource links
│ │ │ └── Footer.tsx # Full footer with links
│ │ └── lib/
│ │ ├── contracts.ts # ABI + deployed hook addresses per chain
│ │ └── wagmi.ts # Wagmi config (Unichain, Base, Arbitrum, Mainnet)
│ ├── index.html
│ ├── package.json
│ ├── vite.config.ts
│ ├── tailwind.config.js
│ └── tsconfig.json
│
├── foundry.toml # Foundry config + remappings
├── Makefile # Convenience commands
└── README.md
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash && foundryup
# Install Node.js 18+ (for frontend)
node --version # should be >= 18# Install Solidity dependencies
make install
# Build
make build
# Run full test suite
make test-v
# Run fuzz tests (10k iterations)
make test-fuzz
# Gas report
make test-gascd frontend
# Install JS dependencies
npm install
# Start dev server at http://localhost:5173
npm run dev
# Production build
npm run build
# Preview production build
npm run preview# Terminal 1
anvil
# Terminal 2
make deploy-localcp .env.example .env
# Fill in your RPC URLs and private key
make deploy-unichain-sepolia # Unichain Sepolia
make deploy-base-sepolia # Base Sepolia
make deploy-arbitrum-sepolia # Arbitrum Sepolia# Requires ARBISCAN_API_KEY in .env
forge verify-contract <DEPLOYED_ADDRESS> src/IntentLPHook.sol:IntentLPHook \
--chain 421614 \
--constructor-args $(cast abi-encode "constructor(address,address)" \
<POOL_MANAGER_ADDR> <USDC_ADDR>)After deploying, update HOOK_ADDRESSES in frontend/src/lib/contracts.ts with your deployed address.
Current Arbitrum Sepolia deployment: 0x8BFBAA48cB579600FBDe29aAAf04965C3FDBDac0 (verified)
Base Sepolia deployment: 0xbB76a401b1381C501703008c34ef58C6e5701Ac0 (verified)
The frontend is deployed on Vercel:
https://intent-lp-hook.vercel.app
Connect your wallet on Arbitrum Sepolia to interact with the live hook contract. Supported chains: Arbitrum Sepolia, Base Sepolia, Unichain Sepolia, Ethereum Sepolia.
| Permission | Purpose |
|---|---|
afterInitialize |
Sets up rolling volume tracker for new pool |
beforeAddLiquidity |
Accepts inline intent registration via hookData |
beforeRemoveLiquidity |
Deactivates intent if LP removes all liquidity |
beforeSwap |
Core enforcement — evaluates all active intents, applies penalty fee |
afterSwap |
Finalises volume tracking, emits indexer events |
penaltyFee = (violatedIntents / totalActiveIntents) × 3000 bps
Returned with the 0x400000 flag to signal Uniswap v4's PoolManager to apply the override.
if elapsed >= 1 hour:
rollingVolume = (previousVolume × (24 - hoursElapsed) / 24) + newSwapVolume
else:
rollingVolume += newSwapVolume
A 24-hour exponential decay approximation — no oracle dependency, all on-chain.
| Field | Type | Description |
|---|---|---|
minSwapVolume |
uint256 |
Minimum swap size (in token0 units, e.g. wei for ETH) |
maxSwapVolume |
uint256 |
Maximum swap size |
maxSlippageBps |
uint256 |
Max slippage tolerance in basis points (reserved for oracle integration) |
activeHourStart |
uint8 |
UTC hour when intent activates (0–23) |
activeHourEnd |
uint8 |
UTC hour when intent deactivates (0–23) |
active |
bool |
Pause/resume toggle |
owner |
address |
Set automatically to msg.sender |
Note: Set both activeHourStart and activeHourEnd to 0 to allow all hours.
// Register intent directly
IntentLPHook.LPIntent memory intent = IntentLPHook.LPIntent({
minSwapVolume: 0.5 ether,
maxSwapVolume: 50 ether,
maxSlippageBps: 100, // 1%
activeHourStart: 9,
activeHourEnd: 17,
active: true,
owner: address(0) // set by hook
});
hook.registerIntent(poolKey, intent);
// Deactivate without removing liquidity
hook.deactivateIntent(poolKey);
// Preview: would a 3 ETH swap violate my intent?
(bool violated, uint256 count) = hook.previewSwap(poolKey, 3 ether);import { useWriteContract } from 'wagmi'
import { parseEther } from 'viem'
import { INTENT_LP_HOOK_ABI, HOOK_ADDRESSES } from './lib/contracts'
const { writeContract } = useWriteContract()
writeContract({
address: HOOK_ADDRESSES[chainId],
abi: INTENT_LP_HOOK_ABI,
functionName: 'registerIntent',
args: [poolKey, intent],
})Why penalty fees instead of hard blocks? Hard-blocking swaps can strand liquidity if intent conditions are too strict. Penalty fees keep the pool always accessible while making unfavourable routing economically unattractive.
Why no external oracle for volume?
Volume tracking is fully on-chain — no oracle required. maxSlippageBps is enforced via a configurable Chainlink price feed per pool for oracle-backed slippage protection.
Why rolling volume vs. per-block tracking? LPs care about aggregate market conditions, not individual swap context. The 24h decay approximation is a single SLOAD + arithmetic — minimal gas, meaningful signal.
| Operation | Estimated Gas |
|---|---|---|
| registerIntent (new) | ~85,000 |
| registerIntent (update) | ~40,000 |
| deactivateIntent | ~25,000 |
| beforeSwap (0 intents) | ~5,000 |
| beforeSwap (5 intents) | ~10,000 |
| beforeSwap (10 intents) | ~37,000 |
| _updateRollingVolume | moved to afterSwap — hot path no longer pays this cost |
- Chainlink integration — enforce
maxSlippageBpswith real-time price feeds - USDC streaming rewards — Circle reward pool for active LPs
- Emergency pause — owner-controlled enforcement bypass
- Gas optimization — ~25K gas saved in hot path
- SwapSimulator — live on-chain preview connected to deployed contract
- Subgraph — Graph Protocol indexer for all contract events
- Across Protocol — cross-chain intent sync via ERC-7683
- EigenLayer AVS — shared intent state & LVR reduction auction
- Intent Marketplace — publish and subscribe to community intent templates
- Formal Audit — engage a security firm before TVL exceeds safe thresholds
- Intent NFTs — make intents transferable ERC-721 tokens (sellable strategies)
IntentLP has not been audited. It was built for the Atrium Academy UHI9 Hookathon.
- The test suite covers 63 tests (41 unit + 22 integration) including fuzz tests
- The hook does not hold user funds; it only adjusts fees in real time
- Intents are stored per-LP per-pool and cannot be modified by anyone other than the intent owner
- No upgradeable proxy — deterministic CREATE2 deployment
Do not deploy significant capital without a professional security audit.
We welcome contributions from the community! Whether you're fixing a bug, adding a feature, or improving documentation, please read our Contributing Guide first.
git clone https://github.com/Mosss-OS/IntentLP.git
cd IntentLP
make install
make build
make test- Contributing Guide — detailed workflow and standards
- Bug Reports — report issues
- Feature Requests — suggest ideas
- Discussions — ask questions and share ideas
- Code of Conduct
Please run forge fmt before submitting PRs.
MIT — see LICENSE
Built with ♥ for the Atrium Academy UHI9 Hookathon