# Uniswap V4 Hooks

> |

- **Type:** Skill
- **Install:** `agentstack add skill-igoryuzo-uniswapv4-hooks-skill-uniswapv4-hooks-skill`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [igoryuzo](https://agentstack.voostack.com/s/igoryuzo)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [igoryuzo](https://github.com/igoryuzo)
- **Source:** https://github.com/igoryuzo/uniswapV4-hooks-skill
- **Website:** https://uniswap-v4-hooks-skill.vercel.app

## Install

```sh
agentstack add skill-igoryuzo-uniswapv4-hooks-skill-uniswapv4-hooks-skill
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

This skill guides secure development of Uniswap v4 hooks. Hooks are external smart contracts that intercept pool operations at specific lifecycle points. Security is paramount—hook vulnerabilities can drain user funds.

## Security Thinking

Before writing ANY hook code, assess the threat model:

**Who calls your hook?**
- Only `PoolManager` should call hook functions
- `msg.sender` in a hook is ALWAYS `PoolManager`, never the user
- The `sender` parameter is the router, not the end user

**What state is exposed?**
- Hooks execute mid-transaction—state can be manipulated between callbacks
- Reentrancy is possible through external calls
- Shared storage across pools using the same hook can break unexpectedly

**What can go wrong with deltas?**
- Every token in MUST equal tokens out (delta accounting)
- Rounding errors accumulate in iterative operations
- BeforeSwapDelta can bypass normal swap logic entirely

## CRITICAL: The NoOp Rug Pull Vector

Hooks with `BEFORE_SWAP_RETURNS_DELTA_FLAG` can **steal user funds**. This is the most dangerous hook permission.

```solidity
// MALICIOUS EXAMPLE - DO NOT USE
// This hook takes user tokens and returns nothing
function beforeSwap(...) external returns (bytes4, BeforeSwapDelta, uint24) {
    Currency input = params.zeroForOne ? key.currency0 : key.currency1;

    // Take all user funds
    input.take(poolManager, address(this), uint256(-params.amountSpecified), false);

    // Return delta saying we took everything, returning zero
    return (
        BaseHook.beforeSwap.selector,
        toBeforeSwapDelta(int128(-params.amountSpecified), 0), // THEFT
        0
    );
}
```

**When `beforeSwapReturnDelta: true`:**
- The hook can completely replace swap logic
- Pool math is SKIPPED if delta equals amountSpecified
- User funds flow to the hook, not through the AMM curve

**Defense:** NEVER enable `beforeSwapReturnDelta` unless implementing a legitimate custom AMM. Users should verify hook permissions before swapping.

## Permission Flags (Address Encoding)

Hook permissions are encoded in the contract address. The address must have specific bits set:

| Bit | Permission | Risk Level |
|-----|------------|------------|
| 0 | beforeInitialize | Low |
| 1 | afterInitialize | Low |
| 2 | beforeAddLiquidity | Medium |
| 3 | afterAddLiquidity | Medium |
| 4 | beforeRemoveLiquidity | Medium |
| 5 | afterRemoveLiquidity | Medium |
| 6 | beforeSwap | High |
| 7 | afterSwap | Medium |
| 8 | beforeDonate | Low |
| 9 | afterDonate | Low |
| 10 | beforeSwapReturnDelta | **CRITICAL** |
| 11 | afterSwapReturnDelta | High |
| 12 | afterAddLiquidityReturnDelta | Medium |
| 13 | afterRemoveLiquidityReturnDelta | Medium |

**Address Mining:** Use CREATE2 with salt grinding to deploy at an address with correct permission bits. Tools: `hook-mine-and-sinker`, `v4-hook-address-miner`.

## Access Control Pattern

```solidity
// REQUIRED: Verify caller is PoolManager
modifier onlyPoolManager() {
    require(msg.sender == address(poolManager), "Not PoolManager");
    _;
}

function beforeSwap(
    address sender,      // This is the ROUTER, not the user
    PoolKey calldata key,
    IPoolManager.SwapParams calldata params,
    bytes calldata hookData
) external onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
    // ...
}
```

## Identifying the Actual User (msg.sender Problem)

The `sender` parameter is the router contract, NOT the end user. To get the actual user:

```solidity
// 1. Define interface for trusted routers
interface IMsgSender {
    function msgSender() external view returns (address);
}

// 2. Maintain allowlist of trusted routers
mapping(address => bool) public trustedRouters;

// 3. Query router safely in hook
function afterSwap(
    address sender,  // This is the router
    PoolKey calldata key,
    IPoolManager.SwapParams calldata params,
    BalanceDelta delta,
    bytes calldata hookData
) external override returns (bytes4, int128) {
    // CRITICAL: Only trust verified routers
    if (!trustedRouters[sender]) {
        revert UntrustedRouter(sender);
    }

    // Safe to query actual user
    try IMsgSender(sender).msgSender() returns (address user) {
        // Use `user` for rewards, tracking, etc.
    } catch {
        revert RouterDoesNotImplementMsgSender();
    }

    return (this.afterSwap.selector, 0);
}
```

**NEVER trust `tx.origin`** for authentication (only acceptable for anti-gaming checks like self-referral prevention).

## Delta Accounting Rules

Deltas track what the hook owes or is owed. They MUST net to zero.

```solidity
// Taking tokens FROM PoolManager (hook receives tokens)
currency.take(poolManager, address(this), amount, false);

// Settling tokens TO PoolManager (hook sends tokens)
currency.settle(poolManager, address(this), amount, false);

// INVARIANT: All deltas must balance before unlock completes
```

**Common Delta Bugs:**
- Forgetting to settle after taking
- Rounding in wrong direction (always round against the user/hook)
- Not handling both swap directions (zeroForOne true AND false)

## Hook Implementation Checklist

Before ANY hook implementation:

- [ ] **Access Control**: Only PoolManager can call hook functions
- [ ] **Delta Balance**: Every take has a corresponding settle
- [ ] **Router Verification**: Never trust sender without allowlist check
- [ ] **Overflow Protection**: Use `mulDiv` for price math, never raw multiplication
- [ ] **Reentrancy Guards**: Add if making external calls
- [ ] **Token Type Handling**: Document unsupported tokens (rebasing, fee-on-transfer)
- [ ] **Permission Flags**: Minimal permissions needed for functionality

## Base Hook Template

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {BalanceDelta} from "v4-core/types/BalanceDelta.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";

contract MyHook is BaseHook {
    constructor(IPoolManager _manager) BaseHook(_manager) {}

    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeInitialize: false,
            afterInitialize: false,
            beforeAddLiquidity: false,
            afterAddLiquidity: false,
            beforeRemoveLiquidity: false,
            afterRemoveLiquidity: false,
            beforeSwap: false,
            afterSwap: true,  // Enable only what you need
            beforeDonate: false,
            afterDonate: false,
            beforeSwapReturnDelta: false,  // DANGEROUS - enable with caution
            afterSwapReturnDelta: false,
            afterAddLiquidityReturnDelta: false,
            afterRemoveLiquidityReturnDelta: false
        });
    }

    function _afterSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        BalanceDelta delta,
        bytes calldata hookData
    ) internal override returns (bytes4, int128) {
        // Your logic here
        return (this.afterSwap.selector, 0);
    }
}
```

## Common Vulnerability Patterns

### 1. Overflow in Price Calculations
```solidity
// BAD: Can overflow
uint256 price = uint256(sqrtPriceX96) ** 2;

// GOOD: Use mulDiv
uint256 price = FullMath.mulDiv(uint256(sqrtPriceX96), uint256(sqrtPriceX96), 1  block.timestamp, "Expiration must be in future");
    require(expiration % interval == 0, "Must align to interval");
    orders[msg.sender] = Order(expiration, ...);
}
```

### 4. Trusting Sender Without Verification
```solidity
// BAD: sender could be malicious contract
address user = sender;

// GOOD: Verify router and query actual user
require(trustedRouters[sender], "Untrusted router");
address user = IMsgSender(sender).msgSender();
```

## Token Handling Hazards

**Unsupported token types** (document explicitly if your hook doesn't handle):
- **Fee-on-transfer**: Actual received amount differs from transfer amount
- **Rebasing**: Balance changes without transfers
- **ERC-777**: Reentrancy via transfer hooks
- **Pausable**: Transfers can revert unexpectedly
- **Blocklist**: Some addresses may be blocked

```solidity
// If handling non-standard tokens, validate actual balances
uint256 balanceBefore = token.balanceOf(address(this));
token.transferFrom(user, address(this), amount);
uint256 actualReceived = token.balanceOf(address(this)) - balanceBefore;
```

## Testing Requirements

Use Foundry for comprehensive testing:

```solidity
// Invariant test: Deltas always balance
function invariant_deltasBalance() public {
    assertEq(poolManager.currencyDelta(address(hook), currency0), 0);
    assertEq(poolManager.currencyDelta(address(hook), currency1), 0);
}

// Fuzz test: No overflow in price calculations
function testFuzz_priceCalculation(uint160 sqrtPriceX96) public {
    vm.assume(sqrtPriceX96 >= TickMath.MIN_SQRT_PRICE);
    vm.assume(sqrtPriceX96 <= TickMath.MAX_SQRT_PRICE);
    // Should not revert
    hook.calculatePrice(sqrtPriceX96);
}

// Test both swap directions
function test_swapZeroForOne() public { ... }
function test_swapOneForZero() public { ... }
```

## Risk Assessment (Self-Score)

Before deployment, score your hook (0-33 scale):

| Dimension | Score Range | Your Hook |
|-----------|-------------|-----------|
| Code Complexity | 0-5 | |
| Custom Math | 0-5 | |
| External Dependencies | 0-3 | |
| External Liquidity | 0-3 | |
| TVL Potential | 0-5 | |
| Team Maturity | 0-3 | |
| Upgradeability | 0-3 | |
| Autonomous Updates | 0-3 | |
| Price-Impacting | 0-3 | |

**Risk Tiers:**
- **Low (0-6)**: 1 audit + AI static analysis
- **Medium (7-17)**: 1-2 audits + bug bounty recommended
- **High (18-33)**: 2 audits (1 math specialist) + mandatory bug bounty + monitoring

## Production Hooks Reference

Allowlisted hooks on Uniswap (as of Jan 2025):
- Flaunch (meme coin launchpad, 100% fees to creators)
- EulerSwap (lending-powered DEX)
- Zaha Studios TWAMM (time-weighted orders)
- Coinbase Verified Pools
- Panoptic Oracle Hook
- AEGIS Dynamic Fee Mechanism

Study these for patterns: https://raw.githubusercontent.com/fewwwww/awesome-uniswap-hooks/refs/heads/main/README.md

## NEVER Do This

- NEVER enable `beforeSwapReturnDelta` without understanding the rug pull risk
- NEVER trust `sender` parameter without router verification
- NEVER use `unchecked` blocks for price/amount math
- NEVER assume ERC20 standard behavior for all tokens
- NEVER skip delta settlement
- NEVER deploy without address permission verification
- NEVER store sensitive state readable mid-swap
- NEVER use `tx.origin` for authentication

## Resources

- **v4-core**: https://github.com/Uniswap/v4-core
- **v4-periphery**: https://github.com/Uniswap/v4-periphery
- **Security Framework**: https://docs.uniswap.org/contracts/v4/security
- **Hook Examples**: https://github.com/fewwwww/awesome-uniswap-hooks
- **Address Mining**: https://github.com/hensha256/hook-mine-and-sinker
- **v4 by Example**: https://v4-by-example.org

## Audit Checklist (Pre-Deployment)

- [ ] All hook functions check `msg.sender == poolManager`
- [ ] Deltas verified to net zero in all paths
- [ ] No overflow possible in math operations
- [ ] Router allowlist implemented if identifying users
- [ ] Token types explicitly documented
- [ ] Reentrancy guards on external calls
- [ ] Timestamp validations in place
- [ ] Permission flags minimal and correct
- [ ] Fuzz tests pass for edge cases
- [ ] Invariant tests for delta accounting
- [ ] Both swap directions tested
- [ ] Fee calculations match Uniswap's formula

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [igoryuzo](https://github.com/igoryuzo)
- **Source:** [igoryuzo/uniswapV4-hooks-skill](https://github.com/igoryuzo/uniswapV4-hooks-skill)
- **License:** MIT
- **Homepage:** https://uniswap-v4-hooks-skill.vercel.app

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-igoryuzo-uniswapv4-hooks-skill-uniswapv4-hooks-skill
- Seller: https://agentstack.voostack.com/s/igoryuzo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
