# Audit report

> # IMPEPE live Ethereum token audit
>
> Audit the deployed Ethereum mainnet system as it is. Do not change source, deploy contracts, submit transactions or mark it approved without findings. Website: https://www.impepe.fun
>
> The live ERC-20 is contracts/IMPEPE.sol, NOT the legacy contracts/ProjectToken.sol. IMPEPE uses OpenZeppelin ERC20, with constructor-only minting of 1 billion tokens to the deploying wallet. It has no owner, token transfer tax, blacklist, pause or router allowance shortcut. Verify these statements against the deployed code rather than taking them as conclusions.
>
> Live addresses (chain ID 1):
>
> | Component | Address |
> | --- | --- |
> | IMPEPE token | 0x5018f7d89e1433dbc0c64e29092e2fa16192905d |
> | IMD quote token | 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 |
> | Uniswap PoolManager | 0x000000000004444c5dc75cB358380D2e3dE08A90 |
> | IMPEPEFeeHook | 0x5108c5507dA225a4582E8629661b5D83d476aaCc |
> | IMDLaunchSwapRouter | 0x3dbee01da73f4ccb8b781424527c67c579c2f718 |
> | Liquidity bootstrap | 0x8f45658fc5ad35ce4fd0c85b541412097d28ba52 |
> | Fee router | 0x42a02182432b7de815085efdbed8a0173f813f28 |
> | CreationController | 0xde25d66569eb05b7c73f61310d028838b820b570 |
> | SwarmCollection | 0x537d5ce0f449faa79affbdbb971cf444277a375a |
> | RewardsDistributor | 0xea817217c31565a63c75c865eed8ba76a4ac72d5 |
> | Receipt verifier | 0x5a832701be1504094db39c0d01d65b293e0aba66 |
>
> Pool ID: 0xf468a137b19bc0822a46914599afad10af396ee876859f14ff46bbb5d0a1321c
> Pool fee 0, tick spacing 60. The hook charges 4% on the IMD side, split 3% creation / 1% protocol. After NFT #1000, the 3% routes to equal-share NFT rewards. These rewards belong to NFT holders, not ERC-20 holders. The public allocation is 98% locked pool liquidity and 2% deployer; confirm current pool state separately from the historical allocation.
>
> Prior audit files and owner approval records refer to older source versions and must not be treated as an audit of this deployed system. Legacy IMD standard-launch contracts also remain in the source tree; use the deployed addresses and bytecode matching to identify the actual scope.
>
> ## Required checks
>
> 1. Match source/build settings and constructor immutables against deployed bytecode; report discrepancies and any unverifiable component explicitly. Use the verified hook compiler input under artifacts/live-token-audit. Compiler Solidity 0.8.26, optimizer 200, Cancun; router deployment metadata settings are in its deployment record.
> 2. Assess sellability, honeypot-like behavior, arbitrary holder balance changes, mint/burn authority, unusual token functions and any actual scanner warnings. Do not assume GMGN/GoPlus issued warnings or that they are false positives; quote current evidence when available.
> 3. Trace router payer/recipient binding, approvals, manager-only callback, slippage, deadline, reentrancy and full-input checks in both swap directions. Test third-party routers and unsupported exact-output/partial-fill cases rather than extrapolating the reference project's findings.
> 4. Review hook return deltas, actual-versus-requested amount fee calculation, rounding, ERC-6909 claims, flush behavior during PoolManager unlock and fund solvency. Test flash-take/unlock accounting attacks and fee diversion with reproducible local or fork tests; never attack the live pool.
> 5. Verify locked liquidity cannot be withdrawn by admin, deployer, hook or another caller. State all remaining administrator powers and external IMD/Uniswap trust dependencies accurately.
> 6. Review creation budget custody, independent signer assumptions, holder selection, retries and delayed migration as they affect the 3% stream. Confirm migration cannot recover locked liquidity or already-accrued NFT rewards.
> 7. Exercise the irreversible NFT #1000 transition, per-NFT equal shares, proportional multiple ownership and preservation of seller-accrued rewards across transfers. Check no overdistribution, repeated claims or flash-NFT reward capture.
>
> Return a public report with exact repository commit, chain/block used for live observations, component source matches, findings by severity, source lines, concrete impact, proof tests and recommended fixes. Clearly separate proven findings, assumptions, scanner limitations and untested areas. No predetermined pass verdict.
>
>
> Pin repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Include the GMGN fee settlement behavior: transaction 0xc1942775a4d39b429b02f56e5d6df950cfb502acc1f641d35c6fb53e95eee096 accrued fees without flushing; website transaction 0xaf953bd7af307bef8717eb764c3a8aded0312998fa68dc6b4730a65d17184e3b accrued and flushed. Check claim backing and permissionless flushFees settlement outside PoolManager unlock. The new ETH adapter is not in this pinned commit and is outside this audit scope.

| | |
|---|---|
| Repository | https://github.com/impepedev/IMPEPE-contract-audit.git |
| Commit | `b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d` |
| Job | `38766dd2-8b90-495c-aa35-9cccc9d856a1` |
| Judged | 2026-10-10 03:11 UTC |
| Findings | 1 medium · 5 low · 7 info |

Four agents audited the code as it is at `b8fceec`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Medium: IMPEPEFeeHook accepts liquidity from any sender but forbids every withdrawal: third-party LP principal is permanently trapped

`contracts/IMPEPEFeeHook.sol:124`

```
        // Liquidity may be added through the launch factory or its position manager.
        sender;
        return this.beforeAddLiquidity.selector;
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). Merged from four specialist reports (economics, math, flow, permissions). beforeAddLiquidity (lines 117-127) runs only check(key) and discards `sender`; beforeRemoveLiquidity (line 136) rejects every modifyLiquidity with liquidityDelta != 0 for every position owner. beforeInitialize does restrict sender to the launch factory, so the intended model is a single bootstrap position, yet nothing stops a third party from minting a position in the official pool (fee 0, spacing 60, pool id 0xf468a137...) through Uniswap's PositionManager, any v4 LP router or the Uniswap web UI. Their deposit is accepted, can never be withdrawn by them, the admin, the hook or anyone (hook is immutable, no owner), and earns nothing because lpFee is 0. THREAT_MODEL.md line 9 claims "outside additions/removals rejected"; the only test for it (tests/contracts.test.mjs line 248) rejects for an unrelated allowance reason (separate finding). Live hook 0x5108c5507dA225a4582E8629661b5D83d476aaCc runtime code hash 0xb4b4513d53c8cb11498e6149243e51915babc9ff6f6ad056c4bf32c247c44b96 matches this source, and Sourcify reports exact_match, so the deployed hook has this behaviour; at block 26159214 pool liquidity still equals the bootstrap position, so nobody has fallen in yet. The deployer's own 980M position is correctly locked (removal of 1 wei reverts for every caller, also after warping). Impact: irrecoverable loss of a user's full deposit through an ordinary, non-malicious action presented by standard tooling; the public "98% locked pool liquidity" statement stops describing pool composition once anyone adds. Fix for any future deployment, preserving the single-locked-position design: in beforeAddLiquidity add `require(sender == launchFactory, "launch factory adds liquidity");` (the bootstrap seeds through its own unlockCallback so seeding still works; verified: all three specialist proofs and my harness pass with that one line). The live hook cannot be changed, so the live remediation is operational: document loudly that adding liquidity to pool 0xf468a137... is irreversible and ask interfaces to hide the add-liquidity action for this pool.

**Reproduction**

Local (test/scratch, mirrors mainnet seeding): outsider 0xA1 holding 1,000,000 IMPEPE approves PoolModifyLiquidityTest and calls modifyLiquidity(key, {tickLower:-138180, tickUpper:887220, liquidityDelta:+1e20, salt:0}). Expected: hook refuses (only the launch factory may add). Actual: succeeds, outsider's IMPEPE balance drops by 100089969853226811341908 wei (100,089.97 IMPEPE). Then modifyLiquidity with liquidityDelta -1e20 and -1 both revert with WrappedError(hook, 0x21d0ee70 beforeRemoveLiquidity, Error("liquidity permanently locked")). Same result for a narrow range {-138240,-138120,+1e18} and for a fresh pool at tick 0 {-600,600,+1e18}. All three specialist proofs (.imd/reads/proofs/Proof_6303684b4eac, Proof_0295d614ca48, Proof_4b54ae9ad438) fail on the pinned code for this reason and pass once beforeAddLiquidity requires sender == launchFactory. The attached proof is the economics specialist's test (two tests fail: "next call did not revert as expected" and "hook accepted outsider liquidity and then locked it permanently").

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
// Proof for: IMPEPEFeeHook.beforeAddLiquidity accepts liquidity from any caller while
// beforeRemoveLiquidity rejects every non-zero removal, so an outsider's deposit into the
// official pool is accepted and then permanently locked (zero LP fee, no exit).
// Fails on the current code (the outsider's add succeeds); passes once beforeAddLiquidity
// restricts `sender` to the launch factory (the bootstrap), which keeps the seeding path working.
import "forge-std/Test.sol";
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IMPEPE} from "contracts/IMPEPE.sol";
import {IMPEPEFeeHook} from "contracts/IMPEPEFeeHook.sol";
import {IMDFeeRouter} from "contracts/IMDFeeRouter.sol";
import {IMDCreationController, IIMDArtifactVerifier} from "contracts/IMDCreationController.sol";
import {IMDReceiptVerifier} from "contracts/IMDReceiptVerifier.sol";
import {SwarmCollection} from "contracts/SwarmCollection.sol";
import {RewardsDistributor, IRewardCollection} from "contracts/RewardsDistributor.sol";
import {IMPEPELiquidityBootstrap} from "contracts/IMPEPELiquidityBootstrap.sol";

contract TestIMD is ERC20 {
    constructor() ERC20("Test IMD", "IMD") { _mint(msg.sender, 1e30); }
}
contract Create2Deployer {
    function deploy(bytes32 salt, bytes memory code) external returns (address a) {
        assembly { a := create2(0, add(code, 32), mload(code), salt) }
        require(a != address(0), "create2");
    }
}

contract OutsiderLiquidityLockTest is Test {
    PoolManager pm;
    IMPEPE impepe;
    TestIMD imd;
    SwarmCollection collection;
    IMDReceiptVerifier verifier;
    IMDCreationController controller;
    RewardsDistributor rewards;
    IMDFeeRouter feeRouter;
    IMPEPELiquidityBootstrap bootstrap;
    IMPEPEFeeHook hook;
    PoolModifyLiquidityTest lpRouter; // stands in for Uniswap's PositionManager / any third-party LP router
    PoolKey key;
    address operator = address(0x0b);
    address attestor = address(0xA77);
    address protocolWallet = address(0xFEE);
    address outsider = address(0xA1);

    function setUp() public {
        vm.roll(100);
        vm.warp(1_800_000_000);
        pm = new PoolManager(address(this));
        impepe = new IMPEPE();
        imd = new TestIMD();
        for (uint256 i; i < 64 && address(imd) < address(impepe); i++) imd = new TestIMD();
        require(address(impepe) < address(imd), "mirror mainnet ordering: IMPEPE is currency0");
        collection = new SwarmCollection(address(this));
        verifier = new IMDReceiptVerifier(attestor);
        bytes memory baseArt = new bytes(300);
        controller = new IMDCreationController(
            address(this), IERC20(address(imd)), collection, IIMDArtifactVerifier(address(verifier)),
            operator, operator, 0.5 ether, 128, sha256(baseArt)
        );
        rewards = new RewardsDistributor(IERC20(address(imd)), IRewardCollection(address(collection)));
        collection.configure(address(controller), address(rewards));
        feeRouter = new IMDFeeRouter(address(this), IERC20(address(imd)), controller, rewards);
        controller.configureRouter(address(feeRouter));
        controller.configureToken(IERC20(address(impepe)), block.number);
        bootstrap = new IMPEPELiquidityBootstrap(address(this), pm, IERC20(address(impepe)), IERC20(address(imd)), controller);
        Create2Deployer dep = new Create2Deployer();
        bytes memory init = abi.encodePacked(
            type(IMPEPEFeeHook).creationCode,
            abi.encode(pm, address(impepe), address(bootstrap), address(imd), feeRouter, int24(60), protocolWallet)
        );
        bytes32 ih = keccak256(init);
        bytes32 salt;
        for (uint256 i; ; i++) {
            salt = bytes32(i);
            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(dep), salt, ih)))));
            if (uint160(p) & 0x3fff == 0x2acc) break;
        }
        hook = IMPEPEFeeHook(dep.deploy(salt, init));
        feeRouter.configureHook(address(hook));
        address[13] memory ex = [address(this), operator, attestor, address(impepe), address(collection), address(verifier), address(rewards), address(controller), address(feeRouter), address(pm), address(bootstrap), address(hook), address(dep)];
        for (uint256 i; i < ex.length; i++) controller.addExclusion(ex[i]);
        controller.sealEligibility();
        key = PoolKey(Currency.wrap(address(impepe)), Currency.wrap(address(imd)), 0, 60, IHooks(address(hook)));
        impepe.transfer(address(bootstrap), 980_000_000 ether);
        bootstrap.configure(key, -138180); // 1e-6 IMD per IMPEPE, as on mainnet
        bootstrap.seed();                   // the only intended liquidity provider
        lpRouter = new PoolModifyLiquidityTest(pm);
        impepe.transfer(outsider, 1_000_000 ether);
        imd.transfer(outsider, 1_000 ether);
        vm.startPrank(outsider);
        impepe.approve(address(lpRouter), type(uint256).max);
        imd.approve(address(lpRouter), type(uint256).max);
        vm.stopPrank();
    }

    function test_outsider_liquidity_is_rejected_instead_of_permanently_locked() public {
        int24 lo = bootstrap.lower();
        int24 up = bootstrap.upper();
        uint256 before = impepe.balanceOf(outsider);
        // Expected: the official pool only ever holds the launch factory's position, so an
        // outsider's add must be refused by the hook. Actual on current code: it succeeds...
        vm.prank(outsider);
        vm.expectRevert();
        lpRouter.modifyLiquidity(key, ModifyLiquidityParams(lo, up, 1e20, bytes32(0)), "");
        assertEq(impepe.balanceOf(outsider), before, "outsider must not have deposited into the locked pool");
    }

    function test_outsider_cannot_remove_what_the_hook_let_in() public {
        int24 lo = bootstrap.lower();
        int24 up = bootstrap.upper();
        uint256 before = impepe.balanceOf(outsider);
        vm.prank(outsider);
        try lpRouter.modifyLiquidity(key, ModifyLiquidityParams(lo, up, 1e20, bytes32(0)), "") {
            // the add was accepted (current code): the deposit is now unrecoverable
            uint256 deposited = before - impepe.balanceOf(outsider);
            assertGt(deposited, 0);
            vm.prank(outsider);
            vm.expectRevert();
            lpRouter.modifyLiquidity(key, ModifyLiquidityParams(lo, up, -1e20, bytes32(0)), "");
            vm.prank(outsider);
            vm.expectRevert();
            lpRouter.modifyLiquidity(key, ModifyLiquidityParams(lo, up, -1, bytes32(0)), "");
            assertEq(impepe.balanceOf(outsider), before - deposited);
            assertTrue(false, "hook accepted outsider liquidity and then locked it permanently");
        } catch {
            // fixed code: add refused, nothing to lock
            assertEq(impepe.balanceOf(outsider), before);
        }
    }
}
```

### 2. Low: Owner-controlled controller migration admits arbitrary replacement code: after 2 days the owner can mint every remaining NFT to chosen wallets and redirect the 3% stream

`contracts/IMDFeeRouter.sol:80`

```
        pendingCodeHash = address(replacement).codehash;
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). Merged from the permissions specialist (low) and the flow specialist (info). Reported as a material privileged power with preconditions, not as a permission bypass: migration is an intended owner role and THREAT_MODEL.md calls replacement-code approval "an explicit administrator trust boundary". scheduleControllerMigration validates the replacement only through its own view answers (owner, feeRouter, predecessor, paused, migrationImported) and pins pendingCodeHash to the hash of whatever was scheduled, not to a known IMDCreationController build. executeControllerMigration then calls next.importMigration(), SwarmCollection.replaceController(next) (which also only checks view answers) and forwards escrow to next.deposit(). After that the replacement is the sole caller allowed into commitAndMint and receives every creation deposit. Live preconditions at block 26159214: controller.owner() = feeRouter.owner() = 0x61aEFdAAa5FA9ac74AA0051238Fd4a37B1d24148 (EOA, also recoveryRecipient and protocolRecipient), collection totalSupply 0, pendingController 0, migrationEscrow 0. The single deployer key can therefore, with a public 2-day delay, hand all 1000 mints and the whole pre-#1000 3% stream to code it wrote (no attestor signature, ranking, operator or 0.5 IMD payment required) and later collect the post-#1000 reward share of those NFTs. What stays protected: pool liquidity, already-minted NFTs, RewardsDistributor balances, the hook and the token. Fix within the agreed design: in scheduleControllerMigration require address(replacement).codehash == address(creation).codehash (identical runtime code implies identical immutables: imd, collection, verifier, operator, paymentRecipient, jobBudget, settlementBlocks, baseHash, recoveryRecipient) and have SwarmCollection.replaceController check the same hash; if logic upgrades must remain possible, that is an explicit scope decision (longer delay plus a published approved hash). On the live, immutable router and collection this stays a documented trust assumption on the deployer key.

**Reproduction**

Local test test_owner_migration_to_arbitrary_code_mints_and_sweeps (passes on pinned code, i.e. the behaviour exists): deploy EvilController exposing owner()=admin, feeRouter()=router, predecessor()=controller, collection()=nft, paused()=true, migrationImported() set by importMigration(), activateMigration(), deposit() and mintTo()/sweep(). Owner sequence: controller.emergencyPause(); controller.scheduleWithdrawal("reason"); feeRouter.scheduleControllerMigration(evil); warp +2 days; controller.withdrawCreationFunds() (3 IMD of creation budget go to the owner); feeRouter.executeControllerMigration(). Expected per the documented ranking/attestor model: a replacement must still require attestor-signed selection and artifacts. Actual: collection.controller() == evil and feeRouter.creation() == evil; evil.mintTo(0xBEEF, 300-byte art) mints NFT #1 to 0xBEEF with no proof; a subsequent 100 IMD buy routes 3 IMD into evil and evil.sweep(0xCAFE) moves it out; bootstrap.positionLiquidity() unchanged.

### 3. Low: A contract account can be selected and minted an NFT it can never claim rewards for; its 1/1000 share is stranded forever

`contracts/SwarmCollection.sol:96`

```
        _mint(recipient, id);
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the flow specialist. selectRecipient (IMDCreationController.sol line 455) checks only minimum balance, sealed exclusions, one-per-wallet and a few protocol addresses; it has no code-length or ERC-721 receiver check, and neither does backend/selection.mjs. commitAndMint uses _mint without a receiver check (accepted design so a rejecting receiver cannot stall minting). Balance-time ranking favours long-lived large holders, so a Uniswap v2/v3 pair, vesting contract or custody contract holding >= 10,000 IMPEPE is a realistic top candidate. Such a contract cannot call RewardsDistributor.claim (line 43 requires collection.ownerOf(id) == msg.sender) and cannot transfer the NFT. After NFT #1000 every fund() adds amount*SCALE/1000 to accRewardPerNFT for that id too, so 1/1000 of all rewards accrues to it and can never be paid; the IMD stays in RewardsDistributor and no one else receives it. THREAT_MODEL.md item 4 acknowledges "may be unable to recover its NFT" but not the perpetual reward loss. Fix options: have the attestor rank only EOAs or contracts implementing IERC721Receiver and record that in the sealed rules; or allow claim by getApproved/isApprovedForAll operators so custodial contracts that can set approvals are not stranded; at minimum document the risk.

**Reproduction**

Local test test_contract_winner_cannot_claim_rewards (real EIP-712 flow with the attestor key): DumbHolder contract C holds 10,000 IMPEPE; a 100 IMD buy funds the controller with 3 IMD; openNextJob(); roll +129 blocks; confirmFinality with a signed Finalized(cutoff, blockhash, controller, expiry); selectRecipient(C, 10000e18, 777, signed Selection) succeeds (no code check); operator payJob, bindRequest("req1"), submit(baseArt, 0, 0, signed Artifact). Expected: every minted share eventually claimable. Actual: collection.ownerOf(1) == C; rewards.claim([1]) reverts "owner" for any other msg.sender (tested from 0xD00D and the admin), and C has no code path to call claim or transfer, so once fund() runs after #1000 the share credited to id 1 is unreachable.

### 4. Low: Deployment records mark every component "verified" but only the hook is source-verified on Sourcify/Etherscan; scanners report IMPEPE as not open source

`artifacts/live-token-audit/impepe-token-mainnet-deployment.json:2`

```
  "status": "verified",
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the economics specialist, confirmed independently. "status": "verified" in the nine deployment records means only that the runtime code hash was matched locally. On 2026-10-10 Sourcify /v2/contract/1/<addr> returns match null for IMPEPE 0x5018f7d8... and IMDLaunchSwapRouter 0x3dbee01d... (the specialist found the same for the bootstrap, fee router, controller, collection, rewards and verifier); only IMPEPEFeeHook 0x5108c550... is exact_match (matchId 55839067, verified 2026-10-09T19:52:41Z). GoPlus token_security/1 for IMPEPE at the same time: is_open_source "0", is_in_dex "0", buy_tax "0", sell_tax "0", cannot_buy "0", cannot_sell_all "0", honeypot_with_same_creator "0", owner_address "", holder_count "8", creator_percent "0.020288". The "not open source" flag is the only live scanner warning observed and it is accurate, not a false positive. GMGN could not be queried from this environment; no GMGN warning text is quoted. Runtime code hashes read live (token 0x303caa06..., hook 0xb4b4513d...) equal the records, so the token is the 7-line OpenZeppelin ERC20 in contracts/IMPEPE.sol (fixed 1e27 supply minted once to the deployer, no owner, tax, blacklist, pause, mint, burn or allowance shortcut). Impact: users, wallets and scanners cannot confirm this themselves and keep warning. Fix: submit the frozen standard JSON (artifacts/live-token-audit/hook-standard-input.json; router with metadata bytecodeHash none) for each address to Etherscan and Sourcify, and record the explorer status per component instead of the ambiguous "verified".

**Reproduction**

GET https://sourcify.dev/server/v2/contract/1/0x5018f7d89e1433dbc0c64e29092e2fa16192905d -> {"match":null,"creationMatch":null,"runtimeMatch":null}; same for 0x3dbee01da73f4ccb8b781424527c67c579c2f718; 0x5108c5507dA225a4582E8629661b5D83d476aaCc -> "match":"exact_match". GET https://api.gopluslabs.io/api/v1/token_security/1?contract_addresses=0x5018f7d89e1433dbc0c64e29092e2fa16192905d -> is_open_source "0". Expected for a token whose records say verified: exact match on the explorer and is_open_source "1".

### 5. Low: The "outside additions rejected" test only passes because the caller never approved the liquidity router, so it does not test the hook

`tests/contracts.test.mjs:248`

```
  await assert.rejects(f.vault.unlockCallback('0x'));const params={tickLower:await f.vault.lower(),tickUpper:await f.vault.upper(),liquidityDelta:1n,salt:ZeroHash};await assert.rejects(f.liquidity['modifyLiquidity((address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)'](f.key,params,'0x'));await assert.rejects(f.liquidity['modifyLiquidity((address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)'](f.key,{...params,liquidityDelta:-1n},'0x'));
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the economics specialist, confirmed. The test sends modifyLiquidity(liquidityDelta: 1n) from admin through PoolModifyLiquidityTest and asserts a rejection. seedPool (line 96) only approves f.swap and f.trade for publicWallet; admin never approves f.liquidity, so the add reverts inside the LP router's settle with ERC20InsufficientAllowance before the hook's decision matters. The assertion passes regardless of what beforeAddLiquidity does, and THREAT_MODEL.md line 9 ("outside additions/removals rejected") is derived from it while the hook actually accepts outside additions (medium finding). Fix: approve the liquidity router for the caller and assert the specific hook revert ("launch factory adds liquidity" after the fix), and additionally assert pool liquidity still equals bootstrap.positionLiquidity() after the attempt.

**Reproduction**

Local test test_fixture_without_allowance_reverts_before_hook: outsider without allowance calls modifyLiquidity(key,{-138180,887220,+1e20,0}) -> revert selector 0xfb8f41b2 ERC20InsufficientAllowance(PoolModifyLiquidityTest, 0, 100089969853226811341908), raised by IMPEPE.transferFrom, not by the hook. After approve(lpRouter, max) the same call succeeds and the outsider's IMPEPE balance drops. Expected: a rejection test must pass the allowance step and still observe a hook revert.

### 6. Low: Repository test suite cannot pass at the pinned commit: deployment-plan test reads artifacts/legacy-deployment-config-v1.2.json, which is not in the tree

`tests/contracts.test.mjs:300`

```
  const cfg={...JSON.parse(fs.readFileSync('artifacts/legacy-deployment-config-v1.2.json')),launchRoute:'legacy_custom',chainId:31337,deployer:await admin.getAddress(),operator:await(await provider.getSigner(4)).getAddress(),protocolRecipient:await admin.getAddress(),artifactAttestor:await(await provider.getSigner(5)).getAddress(),deployerNonce:await provider.getTransactionCount(await admin.getAddress(),'pending'),poolManager:manager.target,imdAddress:imd.target,artifactTrustApproved:true};
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the math specialist. The test "the generated ten-contract unsigned deployment plan executes locally and seeds the approved locked pool" unconditionally calls fs.readFileSync('artifacts/legacy-deployment-config-v1.2.json'); the file is absent from the committed tree (git ls-files has no match), so the test throws ENOENT and `npm test` reports a failure at the audited commit. The archived artifacts/audit-regression-tests.tap therefore does not reflect this tree. Coverage note: the suite exercises both currency orderings, 4% buy/sell accounting and a seeded #999 -> #1000 state, but never swaps amounts below 1e18, never tests an outside LP adding liquidity with allowance, and never runs the #1000 transition end to end. Fix: commit the fixture or drop the legacy-launch test together with the legacy route.

**Reproduction**

ls artifacts/legacy-deployment-config-v1.2.json -> No such file or directory; git ls-files | grep legacy -> nothing. tests/contracts.test.mjs line 300 reads the file unconditionally inside the test body, so node --test reports the test as failed with ENOENT (specialist ran npm ci && npm test on a copy: "not ok 29"). Expected: the suite passes at the pinned commit.

### 7. Info: Remaining administrator powers and external trust dependencies of the deployed system (stated for the report, not defects)

`contracts/IMDCreationController.sol:214`

```
    function withdrawCreationFunds() external onlyOwner nonReentrant {
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). Merged from the economics, flow and permissions specialists' inventories, cross-checked live. Locked liquidity: bootstrap 0x8f45658f... owns the only position (positionLiquidity 979119087993616449488660 = pool liquidity); no withdraw path exists (seed is one-shot, unlocking is only set inside seed(), hook rejects any liquidityDelta != 0 for everyone); nobody including the admin can remove it. Deployer EOA 0x61aEFdAAa5FA9ac74AA0051238Fd4a37B1d24148 (owner of controller, fee router, collection and bootstrap, all Ownable2Step with renounce disabled; protocolRecipient of the 1%; recoveryRecipient) can: (a) emergencyPause, scheduleWithdrawal, and after 2 days withdrawCreationFunds, taking every unpaid creation IMD (0.091492713707310532 IMD at block 26159214) to itself and retiring the controller; with no obligation to migrate, creation can stop forever while the 3% accrues in IMDFeeRouter.migrationEscrow and the 1% keeps flowing to the deployer; done at job 999 this also captures the surplus submit(#1000) would forward to NFT holders; (b) migrate to arbitrary replacement code (separate low finding); (c) transfer ownership of all four contracts. The admin cannot touch the pool, mint IMPEPE, change the 4% fee, pause swaps or move RewardsDistributor funds; after NFT #1000 (a)-(b) are disabled by totalSupply checks. Operator 0x9A6dA9E6... (= paymentRecipient) can release the 0.5 IMD budget of the current job to itself via payJob before delivering and can stall by never submitting; recovery needs an attestor-signed authorization plus an exact 0.5 IMD refund. Attestor 0xaBe1a47E... (single EIP-712 key, distinct from admin/operator) chooses every winner in practice (selectRecipient only checks minimum balance, exclusions and one-per-wallet), attests finality beyond 256 blocks without blockhash binding, and approves artifacts and recoveries. External: IMD 0xd34a99bc... is an owner-controlled LayerZero OFT (owner 0x047F606f..., setPeer/lzReceive present, no pause/blacklist/fee observed), so IMD supply on Ethereum can grow through bridge peers the owner configures; every website swap depends on IMD transfers from the PoolManager, hook and fee router succeeding because IMDLaunchSwapRouter.swapExactInput calls hook.flushFees() unconditionally (line 42), while third-party routers would keep trading and accruing claims if that path ever reverted. Uniswap's protocolFeeController 0x89A5D5bF... may set up to 0.1% protocol fee on this pool (live protocolFee 0, lpFee 0); the hook's full-input check tolerates it because v4 deducts it inside the input.

**Reproduction**

Live (cast, block 26159214): controller.owner() = feeRouter.owner() = 0x61aEFdAAa5FA9ac74AA0051238Fd4a37B1d24148; collection.totalSupply() = 0; bootstrap.positionLiquidity() = 979119087993616449488660. Local: owner sequence emergencyPause(); scheduleWithdrawal(reason); warp 2 days; withdrawCreationFunds() moves the full controller IMD balance to the owner and sets retired = true (test_owner_migration_to_arbitrary_code_mints_and_sweeps). Removing 1 wei of bootstrap liquidity from any caller reverts "liquidity permanently locked"; bootstrap.unlockCallback and seed() revert after seeding (test_donate_succeeds_and_cannot_be_collected). These are designed powers; expected and actual coincide and no code change is proposed beyond the separate migration finding.

### 8. Info: Third-party router swaps (GMGN) accrue fees as fully backed ERC-6909 claims without flushing; settlement waits for a permissionless flushFees

`contracts/IMPEPEFeeHook.sol:40`

```
    function flushFees() external nonReentrant {
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). Merged from the math and economics specialists; no defect. Mainnet tx 0xc1942775a4d39b429b02f56e5d6df950cfb502acc1f641d35c6fb53e95eee096 (block 26156476, GMGN router 0x4313c378...): WETH -> IMD on another pool, then 136671468656021417 wei IMD into the official pool; hook emitted FeesAccrued(136671468656021417, 4100144059680642, 1366714686560214) and the PoolManager minted 5466858746240856 wei of IMD claims to the hook; no burn, no IMD transfer out, no CreationFunded in that tx because flushFees cannot run inside a foreign unlock (manager.unlock would revert AlreadyUnlocked). Website tx 0xaf953bd7af307bef8717eb764c3a8aded0312998fa68dc6b4730a65d17184e3b (block 26156306, IMDLaunchSwapRouter): FeesAccrued(0.3e18, 0.009e18, 0.003e18), then in the same tx the claims were burned, 0.012 IMD taken to the hook, 0.003 IMD to the deployer, 0.009 IMD through IMDFeeRouter.routeAmount into the controller (CreationFunded 0.009). Claim backing at block 26159214: hook ERC-6909 IMD claim balance 21426032040690113 = pendingCreation 16069524030517585 + pendingProtocol 5356508010172528. flushFees opens its own unlock, burns exactly the pending total and takes the same amount; nobody else can burn, transfer or take the hook's claims or call its unlockCallback. Operational consequence only: fees from GMGN/aggregator volume do not create a Funding checkpoint (and cannot open or advance a job) until anyone calls flushFees(); recommend a keeper flush.

**Reproduction**

Local test test_third_party_router_accrues_without_flush_then_anyone_flushes: PoolSwapTest.swap(key, {zeroForOne:false, amountSpecified:-10e18}) -> pendingCreation 0.3e18, pendingProtocol 0.1e18, PoolManager.balanceOf(hook, uint160(IMD)) 0.4e18, controller IMD 0. UnlockProbe calling hook.flushFees() inside its own unlock reverts; pm.burn(hook, imdId, 1) from 0xBAD reverts; hook.unlockCallback from 0xBAD reverts. 0xBAD calls hook.flushFees() -> controller +0.3e18, protocol wallet +0.1e18, claims 0. Expected and actual match.

### 9. Info: Exact-output swaps and partial fills are rejected by design; sell capacity is bounded by IMD held in the pool, which scanners may read as "cannot sell"

`contracts/IMPEPEFeeHook.sol:153`

```
            params.amountSpecified < 0 && params.amountSpecified != type(int256).min,
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the math specialist; design constraint with user-visible failure modes. Any exact-output swap (amountSpecified > 0) reverts in beforeSwap with "exact input only", so Universal Router / aggregator routes that quote exact output cannot trade this pool. afterSwap (lines 175-178) reverts "partial fills unsupported" whenever the pool cannot consume the full input, which is what happens when a sell exceeds the IMD currently in the pool: the launch is single-sided (98% IMPEPE, 0 IMD), so at every moment total sellable value equals the IMD previously bought in. Not a honeypot: IMPEPE is unmodified OpenZeppelin ERC20 (runtime hash matches), the official router binds payer = recipient = msg.sender, enforces deadline and minimumOut, and consumes exactly amountIn; selling bought tokens back returns 92.16% of the IMD paid (4% each way, zero LP fee). Scanner sell-simulations that try to sell a large balance will revert and may be reported as "cannot sell"; GoPlus currently reports cannot_sell_all "0".

**Reproduction**

Local test test_exact_output_and_partial_fill_revert: PoolSwapTest.swap(key, {zeroForOne:false, amountSpecified:+1e18}) -> revert "exact input only". Buy with 100 IMD, sell everything back via IMDLaunchSwapRouter.swapExactInput -> 92.16e18 IMD returned exactly. Then swapExactInput(key, sell, 1_000_000e18, 1, deadline) -> revert "partial fills unsupported". swapExactInput with deadline in the past -> "swap configuration"; with minimumOut = max -> "amount or slippage".

### 10. Info: Hook fee truncation: buys below 25 wei IMD pay no fee and the 3%/1% split is not honoured below 50 wei (dust only)

`contracts/IMPEPEFeeHook.sol:158`

```
        uint256 fee = (gross * HOOK_FEE_BPS) / 10000;
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the math specialist. fee = floor(gross*400/10000), allocation = floor(gross*300/10000), protocol = fee - allocation. gross 24 -> fee 0 (swap fee-free, afterSwap expected == gross); gross 25..33 -> fee 1, allocation 0, protocol 1; gross 34..49 -> fee 1, allocation 1, protocol 0. Because 300 <= 400 the subtraction never underflows and minted claims always equal pendingCreation + pendingProtocol (confirmed live). Deviation is at most 1 wei of IMD per swap while a swap costs >150k gas, so not exploitable; recorded for completeness. No fix required.

**Reproduction**

Local test test_fee_rounding_boundaries via IMDLaunchSwapRouter.swapExactInput(key, buy, amountIn, 0, deadline): amountIn 24 -> controller +0, protocol +0; 25 -> controller +0, protocol +1; 34 -> controller +1, protocol +0. Expected nominal 3%/1% split; actual as listed, bounded by 1 wei.

### 11. Info: FeesRouted event always reports zero gross and zero protocol amounts

`contracts/IMDFeeRouter.sol:130`

```
        emit FeesRouted(0, allocation, 0, completed);
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the flow specialist. routeAmount only sees the 3% allocation, so FeesRouted(grossImd, creationOrRewards, protocol, rewardPhase) is emitted with grossImd = 0 and protocol = 0 on every call. Indexers or reports relying on this event under-report protocol revenue. Fix: drop the unused fields or pass the real gross/protocol amounts from the hook.

**Reproduction**

Mainnet tx 0xaf953bd7af307bef8717eb764c3a8aded0312998fa68dc6b4730a65d17184e3b: IMDFeeRouter 0x42a02182... emitted FeesRouted with data (0, 9000000000000000, 0, false) while the hook's FeesAccrued in the same tx reported protocol 3000000000000000 and 0.003 IMD was transferred to the deployer. Expected FeesRouted.protocol == 3000000000000000; actual 0.

### 12. Info: Pool donations and any fee growth on the locked position are unrecoverable: donate is not hooked and the bootstrap has no collect path

`contracts/IMPEPELiquidityBootstrap.sol:125`

```
        require(msg.sender == address(manager) && unlocking, "manager");
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). From the flow specialist. The hook comment at IMPEPEFeeHook.sol line 135 allows a zero-liquidity-delta modification so LP fees could be collected, but IMPEPELiquidityBootstrap only enters the PoolManager inside seed(), which is one-shot, and `unlocking` is never set again. Pool LP fee is 0 so no swap fees accrue, but donate() is not hooked (no beforeDonate permission) and succeeds for anyone; donated IMD/IMPEPE is credited to the in-range locked position and can never be collected. Harmless unless someone donates by mistake; stated so the report does not claim the collection path works.

**Reproduction**

Local test test_donate_succeeds_and_cannot_be_collected: PoolDonateTest.donate(key, 1e18, 1e18, "") succeeds; bootstrap.unlockCallback("") reverts "manager" and bootstrap.seed() reverts "seed" afterwards; no bootstrap function reaches modifyLiquidity(liquidityDelta = 0).

### 13. Info: Review coverage, assumptions, scanner limitations and untested areas

`LIVE_TOKEN_AUDIT.md:38`

```
Return a public report with exact repository commit, chain/block used for live observations, component source matches, findings by severity, source lines, concrete impact, proof tests and recommended fixes. Clearly separate proven findings, assumptions, scanner limitations and untested areas. No predetermined pass verdict.
```

Repository commit b8fceec323ca2ff2ce299e2fb0b827c99bed9e1d. Live observations: chain 1, block 26159214 via https://ethereum-rpc.publicnode.com. Local reproduction: Foundry 1.8.3, solc 0.8.26, optimizer 200, cancun, @openzeppelin/contracts 5.4.0, @uniswap/v4-core 1.0.2, real PoolManager, hook deployed at a 0x2acc-flag CREATE2 address, bootstrap seeded with 980,000,000 IMPEPE at tick -138180 (IMPEPE = currency0, as on mainnet; local position liquidity 979119087993616449488660 equals the live bootstrap.positionLiquidity()). Scope reviewed: IMPEPE.sol, IMPEPEFeeHook.sol, IMDLaunchSwapRouter.sol, IMPEPELiquidityBootstrap.sol, IMDFeeRouter.sol, IMDCreationController.sol, SwarmCollection.sol, RewardsDistributor.sol, IMDReceiptVerifier/ReceiptVerifier.sol, deployment records and live state. Legacy ProjectToken/LiquidityBootstrap/IMPEPEHook/IMPEPESwapRouter/FeeRouter/CreationController/HookFactory/IMDLaunchHook are not deployed and were only read for context; the ETH adapter is out of scope. Source match: live runtime code hashes of the token (0x303caa06...) and hook (0xb4b4513d...) read at block 26159214 equal the repository records and the hook is a Sourcify exact match; the other seven components were matched by the specialists against the records (hashes 0x485251dd router, 0x1a142b.. bootstrap, 0x7f34ae.. fee router, 0x6c74b1.. controller, 0x612ada.. collection, 0xdc0c05.. rewards, 0xb67c31.. verifier) and one specialist rebuilt all nine from hook-standard-input.json with byte-for-byte runtime matches (router body matches with bytecodeHash none metadata); I did not repeat the full rebuild. Proven locally (14 scratch tests, real v4 PoolManager): 100 IMD buy pays exactly 3 creation + 1 protocol and 96 enters the pool; claims always equal pendingCreation + pendingProtocol; official router binds payer = recipient = msg.sender, enforces deadline/minimumOut and consumes exactly amountIn; exact-output and partial fills revert; flush-inside-foreign-unlock, claim burning and direct unlockCallback calls revert; bootstrap liquidity cannot be removed by anyone; the real EIP-712 job flow mints #1. Taken from specialists without my own re-run (consistent with code reading): the 1000-job end-to-end run showing equal per-NFT shares with zero dust, double share for two NFTs, seller credit preserved across transfer, repeated claims return 0, a just-received NFT claims 0, and sum of claimable equals the distributor balance. Assumptions: IMD behaves as a standard ERC20 on Ethereum (inferred from selectors and live transfers, not verified source); attestor, operator and admin keys are honest within their documented powers; public RPC state is correct. Scanner limitations: GoPlus fields quoted; GMGN endpoints could not be queried; honeypot.is does not index v4 pools; Etherscan verification API needs a key. Not tested: no fork test against the live PoolManager (live pool never touched); Universal Router and V4Quoter not executed; backend ranking code not reviewed; the hardhat suite was not run here. No pass verdict is implied: one medium defect in the deployed, immutable hook and verification/transparency gaps remain.

**Reproduction**

Scratch suites Judge.t.sol and Judge2.t.sol (8 tests) and the three specialist proofs were run with Foundry 1.8.3 against the pinned contracts: 8 pass (behaviours as described), 4 proof tests fail on the pinned code and pass with the beforeAddLiquidity sender check. Live reads via cast at block 26159214 as quoted in the individual findings.

---

Judge's submission `1f6fe704fc56748dc2394fef43318bac264a90c02882351e40fc6f29993900fa`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
