Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate, simulate, audit, and deploy a Uniswap v4 hook + test pool from a brief, on any Uniswap v4 chain (every testnet and mainnet) - pre-audited templates or a from-scratch freeform hook (flags auto-derived; static audit + dangerous-pattern scan + a behavioral forge test + fork sim gate the deploy). Dry-run by default; explicit arm: to broadcast; testnet default, mainnet behind a double opt-in; records the deploy to main.
.claude/skills/aeonfun-deploy-uni-hook/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 275% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 257% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 150% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 223% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 91% | 0% |
> ${var} — the hook brief. Grammar: [arm:][template:<name>] [chain:<name>] <brief> > - (empty) → print help and exit DEPLOY_HOOK_EMPTY. > - <brief> → dry-run: generate, compile, mine, and simulate. Never broadcasts. [default — no prefix] > - arm:<brief> → broadcast: do the full dry-run first, then deploy for real if the simulation passes. > - template:<name> → force a mode: dynamic | noop | skim (pre-audited templates) or freeform (build a whole hook from the prompt). Omit to auto-pick: a brief that matches a template uses it; anything else → freeform. > - chain:<name> → any Uniswap v4 chain in chains.tsv (run ./hook-deploy.sh chains to list). Default base-sepolia. Testnets: base-sepolia, unichain-sepolia, arbitrum-sepolia. Mainnets (testnet: false, e.g. base, ethereum, unichain, arbitrum, optimism, polygon, bnb, avalanche, ...) require BOTH arm: and an explicit chain: — the skill never targets mainnet by default. base-mainnet is accepted as an alias for base.
Today is ${today}. This skill turns a one-line brief into a live Uniswap v4 hook. It is built to be safe: it simulates every deploy before it broadcasts, it defaults to a dry-run on testnet, and it needs an explicit arm: to move on-chain.
A hook binding is immutable and a bad hook can brick a pool or steal funds. So the gates sit BEFORE the deploy: two of them (dry-run then arm:), a mandatory simulation, and idempotent state. Everything after the broadcast is just recording what already happened — appended to memory/state/hook-deploys.json on main, no PR (there is nothing left to review). The Foundry flow is the proven one — mine a CREATE2 salt so the address carries the right hook-flag bits, deploy, initialize the pool, add liquidity, run one swap.
testnet: false chain unless ${var} has BOTH arm: AND an explicit chain:<mainnet-name> — AND the instance has HOOK_MAINNET_OK=1 set (a third, operator-level lock enforced inside hook-deploy.sh, exit 7). An instance that never authorized mainnet cannot broadcast there even if an armed message asks it to. This skill must only run on an instance whose inbound path is owner-gated (TELEGRAM_ALLOWED_USER_ID / the multi-channel allowlist) — a mainnet deploy spends real gas, so an untrusted sender must never be able to dispatch it. On a mainnet chain, first read the deployer balance with cast balance and abort (DEPLOY_HOOK_UNDERFUNDED) if it cannot cover the simulation's Estimated amount required; hook-deploy.sh independently enforces a funding floor (exit 8), an optional MAX_GAS_GWEI gas-price ceiling (exit 9), and warns if the deployer holds more than HOOK_MAX_FLOAT_ETH (default 0.25) — a deploy key must hold gas float only, never LP or treasury capital. Log a clear MAINNET warning in the output.DEPLOY_HOOK_SIM_FAILED.${var} starts with arm:../hook-deploy.sh, which reads it from the env inside the script.memory/state/hook-deploys.json. If an identical brief already deployed within the last hour, do not re-deploy. The deploy script is also idempotent at the address level: it deploys to the canonical address (the first flag-matching CREATE2 salt for this exact (creationCode, flags, PoolManager)). If that address already holds code, an identical hook is already live, so the script logs ALREADY_DEPLOYED <addr> and does nothing — the runner reports the existing address instead of deploying a duplicate. (HookMiner itself skips occupied addresses, so without this check a re-run would silently deploy another copy at a new address.)skills/deploy-uni-hook/templates/ — DynamicFeeHook.sol, NoOpHook.sol, HookFeeHook.sol (pre-audited), Hook.sol + Hook.t.sol + hook.env.example (freeform scaffold, behavioral-test gate, manifest), plus DeployHook.s.sol, MockERC20.sol, foundry.toml, chains.tsv.skills/deploy-uni-hook/templates/chains.tsv is the single source of truth — TAB-separated name chainId testnet poolManager stateView rpc explorer alchemy, one row per Uniswap v4 chain (staged next to hook-deploy.sh, which reads it). memory/uni-deployments.md mirrors it for humans. To add a chain, append a row to chains.tsv.rpc column is a public endpoint. When ALCHEMY_API_KEY is set and the row has an alchemy slug, hook-deploy.sh uses https://<slug>.g.alchemy.com/v2/$ALCHEMY_API_KEY instead — a trusted RPC matters for the mainnet sim + broadcast (a lying public RPC can fake a clean sim). Precedence: RPC_URL (override, for testing) > Alchemy key + slug > public rpc. The RPC path (where the key lives) is never printed — logs show host only.skills/deploy-uni-hook/hook-deploy.sh — the only sanctioned broadcast path (hides the key).memory/state/hook-deploys.json — idempotency + the deploy ledger.template: is not given)| Brief mentions | Mode | |---|---| | fee, volatility, dynamic, surge | dynamic | | skim, hook fee, take a cut, revenue | skim | | "minimal" / "starter" / "empty" | noop | | anything else (novel logic the templates don't cover) | freeform |
${var}. Extract the arm: flag, the optional template:, the optional chain:, and the free-text brief. Empty brief → exit DEPLOY_HOOK_EMPTY with the grammar.chains.tsv (default base-sepolia); hook-deploy.sh maps it to the official PoolManager + RPC, so you pass the NAME, not the address. Run ./hook-deploy.sh chains to see the list, or read chains.tsv. If the name is not in the registry, exit DEPLOY_HOOK_BAD_CHAIN. Look up the row's testnet column: if it is false (mainnet), enforce the double opt-in — require BOTH arm: and an explicit chain: in ${var}, else exit DEPLOY_HOOK_BAD_CHAIN. Every Uniswap v4 chain is supported (Base, Ethereum, Unichain, Arbitrum, Optimism, Polygon, BNB, Avalanche, Robinhood, Worldchain, Ink, Soneium, Celo, X Layer + their testnets).scripts/stage-deploy-uni-hook.sh): Foundry on $PATH, a pre-built v4 project at $HOOKBUILD_DIR (default $HOME/hookbuild) holding all three templates + MockERC20.sol + DeployHook.s.sol + the v4 libraries, and ./hook-deploy.sh copied to the repo root. Do not install Foundry or clone the libs in-run — the sandbox blocks that. Check command -v forge and that $HOOKBUILD_DIR exists; if either is missing, degrade to DEPLOY_HOOK_NO_TOOLCHAIN (emit the generated source + plan).dynamic / noop / skim): in $HOOKBUILD_DIR/src/<Hook>.sol, edit ONLY the region between // --- AEON:LOGIC START --- and // --- AEON:LOGIC END ---. Keep the callback signatures and flag set unchanged. If the default already fits the brief, leave it.$HOOKBUILD_DIR/src/Hook.sol — replace the // --- AEON:BODY ... --- region. Rules: keep the contract name Hook and constructor(IPoolManager); implement whichever v4 callbacks the prompt needs, each with the EXACT IHooks signature, onlyPoolManager, and the right selector return. Do NOT hand-set flags — they are auto-derived from which callbacks you implement. If a callback returns a non-zero delta, set HOOK_RETURNS_DELTA in $HOOKBUILD_DIR/hook.env; for a fee-override hook set HOOK_POOL_FEE=dynamic there.$HOOKBUILD_DIR/test/Hook.t.sol, replace the // --- AEON:ASSERT ... --- region with test_* functions that assert the hook's SPECIFIC intended behavior — not just "does not revert". For every rule in the brief write at least one positive and one negative case: a swap the hook must REJECT as _expectSwapRevert(zeroForOne, amount, Hook.SomeError.selector) (this helper unwraps v4's WrappedError for you — do NOT use bare vm.expectRevert, it won't match the wrapper); a swap it must ALLOW as a plain _swap(...); any getter/accounting as assertEq(hook.someGetter(...), expected). Do NOT edit setUp() or the helpers — only the AEON:ASSERT region. If the brief has no rejectable behavior, still assert the observable state the hook changes.base-sepolia):bash ./hook-deploy.sh simulate <kind> <chain> For freeform this runs, in order, three gates before any deploy:
Hook, has ≥1 callback, every callback carries onlyPoolManager, test/Hook.t.sol has ≥1 test_ function, and scans for dangerous patterns (selfdestruct/delegatecall are hard fails; tx.origin/raw value-call/inline assembly print a warning to review). A failure exits DEPLOY_HOOK_AUDIT_FAILED (never deploy).forge test --fork-url <chain> --match-contract HookBehaviorTest runs the agent-written assertions on a fork. A failing OR non-compiling test exits DEPLOY_HOOK_TEST_FAILED (never deploy). This proves the hook does what the prompt asked.forge script compiles, mines the salt, deploys in-memory, initializes the pool, adds liquidity, and runs one swap against a fork of the target chain.On a compile error, fix and retry (max 3). On a sim revert, exit DEPLOY_HOOK_SIM_FAILED. Capture the mined hook address, the derived flags, and the Estimated amount required. On mainnet, compare that estimate to the deployer balance (cast balance <addr> --rpc-url <rpc>) and exit DEPLOY_HOOK_UNDERFUNDED if it will not cover it. For a freeform hook, also read the generated Hook.sol and reason about safety before arming: does any callback let a caller steal funds, brick the pool (unconditional revert), or reenter? If unsure, stop at the dry-run and report the concern.
${var} did NOT start with arm:, STOP here. Report: template, mined address (with its flag bits), the pool key, and the simulation result. Exit DEPLOY_HOOK_DRY_RUN.arm:).HOOK_DEPLOYER_PRIVATE_KEY is set (it is injected via requires:). If not, degrade to the dry-run report and exit DEPLOY_HOOK_NO_KEY.memory/state/hook-deploys.json. If the same (chain, template, brief) deployed in the last hour, exit DEPLOY_HOOK_IDEMPOTENT with the prior address.bash ./hook-deploy.sh broadcast <kind> <chain> The runner prints a deploy receipt (hook address, decoded flag names, explorer deep-link, tx hashes) and, when ETHERSCAN_API_KEY is set on an Etherscan-family chain, auto-verifies the hook source on the explorer (best-effort — a failed verify never fails a completed deploy). If it printed ALREADY_DEPLOYED, treat the reported address as the result (no new deploy). Read the hook address and the transaction hashes from the receipt or $HOOKBUILD_DIR/broadcast/DeployHook.s.sol/<chainId>/run-latest.json.
cast, read the pool back through StateView.getSlot0(poolId) on the RPC. Confirm the pool exists and the hook address low bits equal the template's flags. Confirm the swap emitted the hook event.main; the workflow's post-run commit lands it. Write:memory/state/hook-deploys.json — append this deploy (chain, template, brief, hook address, flags, tx hashes, timestamp, poolId, poolKey).output/hooks/<hook-address>.sol — copy the deployed source from $HOOKBUILD_DIR/src/<Hook>.sol.output/hooks/<hook-address>.t.sol — copy $HOOKBUILD_DIR/test/Hook.t.sol (the behavioral test that gated the deploy).Do NOT stage the root ./hook-deploy.sh or ./chains.tsv (runtime copies; both gitignored).
DEPLOY_HOOK_OK (or DEPLOY_HOOK_DRY_RUN).DEPLOY_HOOK_NO_KEY. Never fail hard.command -v forge fails or $HOOKBUILD_DIR absent) → emit the generated source + plan, DEPLOY_HOOK_NO_TOOLCHAIN. Do not try to install in-run (the sandbox blocks it).DEPLOY_HOOK_BAD_CHAIN.HOOK_MAINNET_OK=1 (hook-deploy.sh exit 7) → DEPLOY_HOOK_MAINNET_NOT_AUTHORIZED (never broadcast).hook-deploy.sh exit 8) → DEPLOY_HOOK_UNDERFUNDED (never broadcast).MAX_GAS_GWEI (hook-deploy.sh exit 9) → DEPLOY_HOOK_GAS_TOO_HIGH (never broadcast; retry when fees drop).onlyPoolManager / no test_ / selfdestruct / delegatecall) → DEPLOY_HOOK_AUDIT_FAILED (never deploy).DEPLOY_HOOK_TEST_FAILED (never deploy).DEPLOY_HOOK_SIM_FAILED (never broadcast after a failed sim).dynamic = 0x10C0 flags, noop = 0x80, skim = 0x44).src/Hook.sol and its behavioral test into test/Hook.t.sol. Flags are auto-derived from the callbacks (never hand-set). Three gates run before any deploy: a static audit (name/callbacks/onlyPoolManager/test-present/dangerous-pattern scan), the agent-written forge test behavioral assertions on a fork, then the fork simulation. The agent also reads the generated source for steal/brick/reentrancy risk before arming. Prefer a matching template when one fits (they are audited); use freeform for novel logic.chains.tsv carries every official v4 deployment (Base, Ethereum, Unichain, Arbitrum, Optimism, Polygon, BNB, Avalanche, Robinhood, Worldchain, Ink, Soneium, Celo, X Layer + the Sepolia testnets), each verified to hold the PoolManager. The same flow runs on all of them — only the PoolManager/RPC differ, resolved by name. The CREATE2 deployer (0x4e59…4956C) is required for the mined address; if a chain lacks it the fork simulation fails closed before any broadcast.MockERC20 tokens to itself (free) and seeds the demo pool with those mock tokens — a mainnet broadcast risks GAS ONLY, never real capital. The deployed pool is a MockA/MockB demo; the reusable hook contract is the real deliverable. The deployer key must be a funded burner holding gas float only (the runner warns above HOOK_MAX_FLOAT_ETH); mainnet also needs the HOOK_MAINNET_OK=1 operator lock. A future version can add the keyless Base MCP send_calls rail so no key sits in the runner.ALCHEMY_API_KEY + the chain's alchemy slug) over the public RPC, so a lying public node can't fake a clean sim. After a broadcast it prints a receipt (address, decoded flags, explorer link, tx hashes) and, with ETHERSCAN_API_KEY on an Etherscan-family chain, auto-verifies the source (best-effort). All of this is opt-in: with no keys set the skill still runs on public RPCs, unverified.Other measured skills in the registry, with their headline benchmark lift.