HookForge

Hooks / Launch

AntiSnipeRamp

Opens a pool at a punitive fee that decays to normal over a fixed window, and pays every cent of the difference to liquidity providers rather than to the deployer.

AntiSnipeRamp implements 2 of the fourteen Uniswap v4 callbacks: afterInitialize, beforeSwap.

How it works

The first block of a new pool is the most valuable block it will ever have. A bot that buys in it and sells an hour later takes the entire launch premium, and everyone who arrives through the front door pays for it. The usual answers are a whitelist, which is a promise rather than a mechanism, or a bonding curve that hands the premium to the deployer, which moves the extraction rather than removing it.

This hook makes the first block expensive to trade in and lets that expense decay: fee(t) = startFee - (startFee - endFee) * min(t, rampSeconds) / rampSeconds A sniper in the first second pays `startFee`, which can be set high enough that the trade is not worth making. A buyer twenty minutes later pays close to `endFee`. Because the fee is an LP fee, the premium the early trader surrenders is paid to the people who put the liquidity up, not to whoever deployed the token.

There is no address in this contract that can receive anything. A second lever handles the case where the fee alone is not enough. While the ramp is running, a single swap may not exceed `maxSwapDuringRamp` units of the specified currency.

This is a size cap, not an identity check, and it is deliberately not per-address: a hook sees the router that called the `PoolManager`, not the person behind it, so any per-address limit is a limit on routers and is defeated by a fresh key. Capping size is enforceable against everyone equally, including the deployer. Set it to zero to disable it.

Prior art: liquidity bootstrapping pools ramp the *price* down and were built for price discovery; several launchpad hooks charge a launch fee and route it to a creator or a protocol treasury. Ramping the *fee* down while directing the proceeds to liquidity is a different mechanism with a different beneficiary, and it composes with any curve rather than replacing it.

Prior art

Liquidity bootstrapping pools ramp the price down and were built for price discovery; several launchpad hooks charge a launch fee and route it to a creator or a treasury. Ramping the fee down while directing the proceeds to liquidity is a different mechanism with a different beneficiary, and it composes with any curve rather than replacing it.

Where it does not help

The size cap is per swap, not per address: a hook sees the router that called the PoolManager, not the person behind it, so a determined buyer can split across transactions. The cap raises the cost of sniping rather than preventing it, and the fee ramp is what does the real work.

Configuring a pool

Uniswap v4 removed hookData from initialize, so a hook that needs per-pool parameters has to receive them out of band. AntiSnipeRamp takes them through configure, which anyone may call for a pool key whose pool does not exist yet, and which nobody may call afterwards. The parameters are part of what the pool is, so they are fixed for its lifetime.

// 1. Fix the terms, before the pool exists.
hook.configure(
    key,
    AntiSnipeRampHook.Config({
        startFee: /* uint24 */ 0,
        endFee: /* uint24 */ 0,
        rampSeconds: /* uint32 */ 0,
        maxSwapDuringRamp: /* uint128 */ 0
    })
);

// 2. Initialize the pool. The hook rejects a pool it was never configured for.
poolManager.initialize(key, startingSqrtPriceX96);
ParameterTypeUnits
startFeeuint24hundredths of a bip (3000 = 0.30%)
endFeeuint24hundredths of a bip (3000 = 0.30%)
rampSecondsuint32seconds
maxSwapDuringRampuint128seconds

From TypeScript

The SDK ships the catalogue, the address book and the pool-key helpers, so a client never hardcodes an address or recomputes a pool id by hand.

npm i @hookforge/sdk

import {getHook, hookAddress, poolKeyFor, poolId} from "@hookforge/sdk";

const hook = getHook("anti-snipe-ramp");
const address = hookAddress("anti-snipe-ramp", 1);
const key = poolKeyFor({hook: address, currencyA: USDC, currencyB: WETH, tickSpacing: 60, dynamicFee: true});
console.log(poolId(key));

What it reverts with

ErrorMeaning
FeeTooLarge(uint24)A fee was configured above the protocol maximum of 100%.
InvalidConfig()`rampSeconds` was zero, or `endFee` was above `startFee`, which would ramp the fee upward.
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.
SwapTooLargeDuringRamp(uint256,uint128)The swap is larger than the pool allows while its launch ramp is running.

Addresses

ChainChain IDAddress
unichain1300x53D7d95607a608369cEF06018C0D7aE2505Dd080
robinhood46630x20A9Ef8C1916c549eBa01Fe4e486be2adc0Bd080
base84530x6A35Ea8E3bCbCB45Ca764CD097F9495FB1709080
arbitrum421610xdD3d11653d22120D31D3d0FbfA7c1A763BA71080

These addresses are deterministic, not live. They are the CREATE2 addresses the deploy script mines so that the low fourteen bits encode this hook's permissions. Until the deploy runs on a chain, there is no code at them. Check before you send anything anywhere.

Source and verification

The contract is contracts/src/hooks/AntiSnipeRampHook.sol, and everything on this page is generated from it: the prose is its NatSpec, the parameters are its configure ABI, the callbacks above are the flags it declares, and the tags are the strings its own hookTags() returns. A hook cannot be documented here as something it is not.

Ask the deployed contract what it is and it answers directly, with no registry in the loop:

cast call $HOOK "hookName()(string)"    # AntiSnipeRamp
cast call $HOOK "specURI()(string)"     # https://hookforge.pages.dev/schema/hooks/anti-snipe-ramp.json
cast call $HOOK "hookTags()(string[])"  # launch, anti-snipe, dynamic-fee, no-admin