Install
$ agentstack add skill-uniswap-uniswap-ai-v4-security-foundations ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
v4 Hook Security Foundations
Security-first guide for building Uniswap v4 hooks. Hook vulnerabilities can drain user funds—understand these concepts before writing any hook code.
Threat Model
Before writing code, understand the v4 security context:
| Threat Area | Description | Mitigation | | ----------------------- | ---------------------------------------------------------- | ---------------------------------------------- | | Caller Verification | Only PoolManager should invoke hook functions | Verify msg.sender == address(poolManager) | | Sender Identity | msg.sender always equals PoolManager, never the end user | Use sender parameter for user identity | | Router Context | The sender parameter identifies the router, not the user | Implement router allowlisting | | State Exposure | Hook state is readable during mid-transaction execution | Avoid storing sensitive data on-chain | | Reentrancy Surface | External calls from hooks can enable reentrancy | Use reentrancy guards; minimize external calls |
Permission Flags Risk Matrix
All 14 hook permissions with associated risk levels:
| Permission Flag | Risk Level | Description | Security Notes | | --------------------------------- | ---------- | --------------------------- | ----------------------------- | | beforeInitialize | LOW | Called before pool creation | Validate pool parameters | | afterInitialize | LOW | Called after pool creation | Safe for state initialization | | beforeAddLiquidity | MEDIUM | Before LP deposits | Can block legitimate LPs | | afterAddLiquidity | LOW | After LP deposits | Safe for tracking/rewards | | beforeRemoveLiquidity | HIGH | Before LP withdrawals | Can trap user funds | | afterRemoveLiquidity | LOW | After LP withdrawals | Safe for tracking | | beforeSwap | HIGH | Before swap execution | Can manipulate prices | | afterSwap | MEDIUM | After swap execution | Can observe final state | | beforeDonate | LOW | Before donations | Access control only | | afterDonate | LOW | After donations | Safe for tracking | | beforeSwapReturnDelta | CRITICAL | Returns custom swap amounts | NoOp attack vector | | afterSwapReturnDelta | HIGH | Modifies post-swap amounts | Can extract value | | afterAddLiquidityReturnDelta | HIGH | Modifies LP token amounts | Can shortchange LPs | | afterRemoveLiquidityReturnDelta | HIGH | Modifies withdrawal amounts | Can steal funds |
Risk Thresholds
- LOW: Unlikely to cause fund loss
- MEDIUM: Requires careful implementation
- HIGH: Can cause fund loss if misimplemented
- CRITICAL: Can enable complete fund theft
CRITICAL: NoOp Rug Pull Attack
The BEFORE_SWAP_RETURNS_DELTA permission (bit 10) is the most dangerous hook permission. A malicious hook can:
- Return a delta claiming it handled the entire swap
- PoolManager accepts this and settles the trade
- Hook keeps all input tokens without providing output
- User loses entire swap amount
Attack Pattern
// MALICIOUS - DO NOT USE
function beforeSwap(
address,
PoolKey calldata,
IPoolManager.SwapParams calldata params,
bytes calldata
) external override returns (bytes4, BeforeSwapDelta, uint24) {
// Claim to handle the swap but steal tokens
int128 amountSpecified = int128(params.amountSpecified);
BeforeSwapDelta delta = toBeforeSwapDelta(amountSpecified, 0);
return (BaseHook.beforeSwap.selector, delta, 0);
}
Detection
Before interacting with ANY hook that has beforeSwapReturnDelta: true:
- Audit the hook code - Verify legitimate use case
- Check ownership - Is it upgradeable? By whom?
- Verify track record - Has it been audited by reputable firms?
- Start small - Test with minimal amounts first
Legitimate Uses
NoOp patterns are valid for:
- Just-in-time liquidity (JIT)
- Custom AMM curves
- Intent-based trading systems
- RFQ/PMM integrations
But each requires careful implementation and audit.
Delta Accounting Fundamentals
v4 uses a credit/debit system through the PoolManager:
Core Invariant
For every transaction: sum(deltas) == 0
The PoolManager tracks what each address owes or is owed. At transaction end, all debts must be settled.
Key Functions
| Function | Purpose | Direction | | ---------------------------- | ----------------------------------- | ---------------------- | | take(currency, to, amount) | Withdraw tokens from PoolManager | You receive tokens | | settle(currency) | Pay tokens to PoolManager | You send tokens | | sync(currency) | Update PoolManager balance tracking | Preparation for settle |
Settlement Pattern
// Correct pattern: sync before settle
poolManager.sync(currency);
currency.transfer(address(poolManager), amount);
poolManager.settle(currency);
Common Mistakes
- Forgetting sync: Settlement fails without sync
- Wrong order: Must sync → transfer → settle
- Partial settlement: Leaves transaction in invalid state
- Double settlement: Causes accounting errors
Access Control Patterns
PoolManager Verification
Every hook callback MUST verify the caller:
modifier onlyPoolManager() {
require(msg.sender == address(poolManager), "Not PoolManager");
_;
}
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
// Safe to proceed
}
Why This Matters
Without this check:
- Anyone can call hook functions directly
- Attackers can manipulate hook state
- Funds can be drained through fake callbacks
Router Verification Patterns
The sender parameter is the router, not the end user. For hooks that need user identity:
Allowlisting Pattern
mapping(address => bool) public allowedRouters;
function beforeSwap(
address sender, // This is the router
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
require(allowedRouters[sender], "Router not allowed");
// Proceed with swap
}
User Identity via hookData
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
// Decode user address from hookData (router must include it)
address user = abi.decode(hookData, (address));
// CAUTION: Router must be trusted to provide accurate user
}
msg.sender Trap
// WRONG - msg.sender is always PoolManager in hooks
function beforeSwap(...) external {
require(msg.sender == someUser); // Always fails or wrong
}
// CORRECT - Use sender parameter
function beforeSwap(address sender, ...) external {
require(allowedRouters[sender], "Invalid router");
}
Token Handling Hazards
Not all tokens behave like standard ERC-20s:
| Token Type | Hazard | Mitigation | | ------------------- | ------------------------------------ | ----------------------------------- | | Fee-on-transfer | Received amount = 0.8.24 with EVM target set to cancun or later.
- External calls: Each cross-contract call adds ~2,600 gas base cost plus the callee's execution. Batch calls where possible.
- String operations: Avoid
stringmanipulation in callbacks; usebytes32for identifiers. - Redundant reads: Cache
poolManagercalls — repeatedgetSlot0()orgetLiquidity()reads cost gas each time.
Measuring Gas
# Profile a specific hook callback with Foundry
forge test --match-test test_beforeSwapGas --gas-report
# Snapshot gas usage across all tests
forge snapshot --match-contract MyHookTest
Risk Scoring System
Calculate your hook's risk score (0-33):
| Category | Points | Criteria | | --------------------- | ------ | ---------------------------------------- | | Permissions | 0-14 | Sum of enabled permission risk levels | | External Calls | 0-5 | Number and type of external interactions | | State Complexity | 0-5 | Amount of mutable state | | Upgrade Mechanism | 0-5 | Proxy, admin functions, etc. | | Token Handling | 0-4 | Non-standard token support |
Audit Tier Recommendations
| Score | Risk Level | Recommendation | | ----- | ---------- | ------------------------------ | | 0-5 | Low | Self-audit + peer review | | 6-12 | Medium | Professional audit recommended | | 13-20 | High | Professional audit required | | 21-33 | Critical | Multiple audits required |
Absolute Prohibitions
Never do these things in a hook:
- Never trust
msg.senderfor user identity - It's always PoolManager - Never enable
beforeSwapReturnDeltawithout understanding NoOp attacks - Never store passwords, keys, or PII on-chain
- Never use
transfer()for ETH - Usecall{value:}("") - Never assume token decimals - Always query the token
- Never use
block.timestampfor randomness - Never hardcode gas limits in calls
- Never ignore return values from external calls
- Never use
tx.originfor authorization - It's a phishing vector; malicious contracts can relay calls with the original user'stx.origin
Pre-Deployment Audit Checklist
| # | Item | Required For | | --- | ----------------------------------------- | ------------------------ | | 1 | Code review by security-focused developer | All hooks | | 2 | Unit tests for all callbacks | All hooks | | 3 | Fuzz testing with Foundry | All hooks | | 4 | Invariant testing | Hooks with delta returns | | 5 | Fork testing on mainnet | All hooks | | 6 | Gas profiling | All hooks | | 7 | Formal verification | Critical hooks | | 8 | Slither/Mythril analysis | All hooks | | 9 | External audit | Medium+ risk hooks | | 10 | Bug bounty program | High+ risk hooks | | 11 | Monitoring/alerting setup | All production hooks |
See [references/audit-checklist.md](references/audit-checklist.md) for detailed audit requirements.
Production Hook References
Learn from audited, production hooks:
| Project | Description | Notable Security Features | | -------------- | --------------------- | ----------------------------- | | Flaunch | Token launch platform | Multi-sig admin, timelocks | | EulerSwap | Lending integration | Isolated risk per market | | Zaha TWAMM | Time-weighted AMM | Gradual execution reduces MEV | | Bunni | LP management | Concentrated liquidity guards |
External Resources
Official Documentation
Security Resources
Community
- v4-hooks-skill by @igoryuzo - Community skill that inspired this guide
- v4hooks.dev - Community hook resources
Additional References
- [Base Hook Template](references/base-hook-template.md) - Complete implementation starter
- [Vulnerabilities Catalog](references/vulnerabilities-catalog.md) - Common patterns and mitigations
- [Audit Checklist](references/audit-checklist.md) - Detailed pre-deployment checklist
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Uniswap
- Source: Uniswap/uniswap-ai
- License: MIT
- Homepage: https://developers.uniswap.org
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.