# PRESSURE — contract review pack

**Commit `008f0fe`** (`008f0fec7bcb75b00bc7420daf8e759af27042e0`)
Solidity **0.8.26**, Foundry, optimizer on, **`viaIR` deliberately OFF** (see below).
Target chain: **Ethereum mainnet**, chainId **1**.
Venue: **Uniswap v4**, paired against **WETH** (never native ETH).

**Status: unaudited and not yet deployed.** 78 tests pass, including a 10k-run fuzz profile,
a fork suite against live chain state, and a full deploy-and-trade rehearsal on an anvil fork
of mainnet. No known outstanding defects — which is exactly why an outside reader is wanted.

---

## What it is

A Uniswap v4 hook token where **selling gets more expensive the more of the supply has already
been sold.** Buying is free and always will be.

| Parameter | Value |
| --- | --- |
| Buy tax | 0% |
| Sell tax | 1% → 5%, by tier |
| Tier ladder | pressure `<100` bps → 1%, `<250` → 2.5%, `<500` → 4.5%, `≥500` → 5% |
| Pressure meter | bps of **total supply**, hard-capped at **600** on write |
| Buy decay | 3x — buying 1% of supply removes 300 bps of pressure |
| Time decay | none |
| Fee split | 50% treasury **in WETH** / 50% holders **in PRESSURE** |
| Supply | 1,000,000,000, fixed, no mint |
| Liquidity | 100% of supply, seeded one-sided, locked in the deploy transaction |
| Ownership | **none.** No `owner()` on any contract. |

---

## Build and run

```bash
forge build
forge test                      # 78 pass, 1 fork suite skips without an RPC
FOUNDRY_PROFILE=ci forge test    # 10k-run fuzz
forge test --match-path 'test/MainnetPoolCreationFork.t.sol' --fork-url <archive RPC>
```

**Do not enable `viaIR`.** The hook's address is mined so its low 14 bits equal `0x10CC`,
which is how v4 encodes hook permissions. `viaIR` changes the creation code, which changes
the CREATE2 address a given salt produces, which invalidates the mined salt and makes the
PoolManager reject the pool. Three stack-too-deep errors during development were resolved by
extracting frames instead, and the reasons are commented at each site.

---

## Scope

**In scope — these four are deployed and immutable:**

| File | Lines | Role |
| --- | --- | --- |
| `src/DynamicSellTaxHook.sol` | 706 | The hook. Pressure accounting and fee capture. **The main event.** |
| `src/PressureToken.sol` | 229 | Fixed-supply **rebasing** ERC-20. Shares + index; reflections raise the index. |
| `src/PressureLauncher.sol` | 146 | The entire launch in one transaction. Mandatory where a mempool exists. |
| `src/PressureLiquidityDeployer.sol` | 234 | Opens the pool, seeds one-sided liquidity, locks it. One transaction. |

**Context, not deployed bytecode:**

| File | Lines | Role |
| --- | --- | --- |
| `script/Deploy.s.sol` | 193 | Deployment orchestration. Not deployed bytecode, but the ordering it enforces is load-bearing. |

**Explicitly out of scope:** `src/BurnOnBuyToken.sol` is a different, unrelated product that
shares the repo. It is not part of this launch. Ignore it.

---

## Invariants worth attacking

These are the properties the design depends on. Breaking any one of them is a finding.

1. **Buys are never taxed.** In either direction, exact-input or exact-output.
2. **A sell can never revert because of the hook.** A token whose sells can fail is
   honeypot-shaped. This previously *did* happen — see "Bugs already found" below.
3. **Fees are never lost and never over-collected.** Every sell accrues exactly half the tier
   rate in each currency; `distributeTax` moves the whole balance and keeps nothing.
4. **The treasury never receives PRESSURE**, and holders never receive WETH.
5. **The pressure meter cannot exceed 600**, by any route, including exact-output swaps and
   repeated small sells.
6. **The pool's liquidity can never be withdrawn**, by us or anyone, ever.
7. **No privileged caller exists.** `treasury` and `reflectionDistributor` cannot be changed
   by any selector.
8. **Reflections cannot be stolen or double-claimed**, and a transfer must not carry unclaimed
   reflections to the recipient.
9. **The pool and the treasury must never accrue reflections** — if the pool did, the holders'
   half would be paid to liquidity providers.
10. **A broken or reverting distributor must never freeze token transfers.**

## Specific things I would look at first

- **Delta sign conventions.** `_beforeSwap` returns a positive `specifiedDelta` and mints an
  ERC-6909 claim; the two must cancel exactly. Getting the sign backwards is precisely the bug
  that shipped once already.
- **The two-currency fee.** On a sell the token and WETH sit on opposite sides of v4's
  specified/unspecified divide, so `beforeSwapReturnDelta` takes one half and
  `afterSwapReturnDelta` the other, in one swap. Is that true in **all four**
  direction/exactness combinations? `_wethOut` clamps a negative delta to zero — is that the
  right call for a partially filled exact-output sell?
- **`redeemFees` solvency.** Fees accrue as claims and are converted later by
  `burn` + `take`. Can `take` ever fail, or take value that is not ours?
- **Rebasing accounting in `PressureToken`.** Shares plus a monotonically rising index,
  with a permanent exclusion set (`PoolManager`, treasury, hook) fixed at deploy and no
  setter. Rounding, the shares<->tokens conversion in both directions, and whether the sum
  of balances can ever exceed `totalSupply`. Integer division loses ~5e-19 of supply per
  reflection — confirm it can only ever round DOWN, never mint.
- **Rebasing against v4 settlement.** v4 settles on `balanceOf(poolManager)` deltas across
  `sync` -> transfer -> `settle`. Excluding the PoolManager is what makes that safe. Is the
  exclusion airtight, and can a rebase land mid-swap in any path that still matters?
- **The one-hour-window surcharge.** Window volume is recorded GROSS (grossed back up by the
  half already skimmed in `beforeSwap`), and the rate is decided once in `beforeSwap` and
  carried to `afterSwap` in transient storage so both halves price identically. Check the
  exact-output path, which cannot see its own token size and so escapes the surcharge on
  its own volume.
- **`PressureLiquidityDeployer.unlockCallback`.** The permanence of the liquidity lock rests
  on a single `if (lpLocked) revert` line.

---

## Known and accepted — please do not report these as findings

Each is deliberate, measured, and documented in the source.

1. **The effective sell rate is below nominal.** Charging half on each side compounds, so a
   seller nets `(1 - r/2)²` rather than `(1 - r)`. True rate is `r - r²/4`. The nominals are
   therefore chosen so the EFFECTIVE rate is the round number quoted (2112 -> 20%, 5858 ->
   50%), and the same maths caps the design at **75% effective** — nominal 100% yields 75%
   and nothing above it is expressible. **Every nominal is even, so both halves are exact —
   ladder says 5%**. Bounded, and in the seller's favour. Disclosed on the website.
2. **Pressure is measured on the settled delta**, which on an exact-input sell is net of the
   fee `beforeSwap` already took. So pressure understates the gross amount sold by 1–5%.
   Consistent and bounded.
3. **The tier ladder is very sensitive at launch.** At a ~2.5 ETH opening FDV, 1% of supply
   costs ~0.025 ETH, so tiers are crossed by tiny trades and the meter will mostly sit at 0 or
   at the 600 cap. This self-corrects as price rises (the ETH cost of crossing a tier scales
   linearly with FDV) and percent-of-supply is the intended denominator. Reviewed and kept.
4. **A wrong `treasury` address is unrecoverable.** It is immutable with no setter by design.
   The constructor rejects zero; beyond that it is an operational check.
5. **The deployer ends the launch holding ~4276 wei** of PRESSURE — the remainder the
   single-sided liquidity maths could not place. Returned rather than stranded.
6. **`distributeTax` and `redeemFees` are permissionless.** Intentional: every caller does the
   same single predetermined thing, and nobody has to be trusted to run them.

---

## Bugs already found and fixed

Included because they show the failure modes this codebase is prone to, and every one was an
**integration** bug that unit tests on a synthetic pool could not see.

1. **The sell tax never worked.** `take()` was called *and* a negative `specifiedDelta`
   returned. `take` puts the hook's delta negative, so the returned delta must be positive to
   offset it. Every sell reverted with `CurrencyNotSettled`. Nothing caught it because no test
   in the original suite performed a swap.
2. **`take()` in `beforeSwap` made sells revert.** It moves *physical* tokens, but the
   seller's tokens have not settled yet during the callbacks — so the fee came out of existing
   reserves, and once buyers had drained the token side every sell failed. Replaced with
   `mint()` claims redeemed later.
3. **Buy decay was denominated in the wrong currency.** `amountSpecified` on an exact-input
   buy is WETH, and it was being divided by the token's total supply. Fixing it properly
   required moving all pressure accounting to `afterSwap`, because only two of the four
   input/output combinations have a knowable token amount in `beforeSwap`.
4. **The reflection half did not exist.** `setShares` was `onlyOwner` and nothing ever called
   it. There was also no exclusion set, so the pool itself would have earned reflections.
5. **Ownership was not what it appeared to be.** `Ownable(msg.sender)` in a contract deployed
   through the CREATE2 factory makes *the factory* the owner — an address that can never call
   anything. The setters were unreachable and `renounceOwnership()` would have reverted
   forever, while `owner()` read a plausible-looking address. Ownership was deleted rather
   than renounced.
6. **The original test suite was largely hollow.** Three of nine tests computed a value in the
   test body and asserted it against itself; a fourth wrote to the wrong storage slot via a
   hardcoded index. Rewritten to drive real swaps, with `stdstore` resolving slots from
   getters.

---

## Test inventory

| Suite | Lines | Covers |
| --- | --- | --- |
| `test/AtomicLaunch.t.sol` | 307 |  |
| `test/BurnOnBuy.t.sol` | 214 | Out of scope (separate product). |
| `test/DeployerAsTreasury.t.sol` | 270 | The exact production config: treasury == deployer, 100% of supply in LP. |
| `test/DynamicSellTaxHook.t.sol` | 433 | Tier ladder, decay, caps, fee routing, exact-output paths. |
| `test/FeeOnTransferInV4.t.sol` | 179 | Pins the v4 settlement asymmetry that forced this design. |
| `test/HooklessBuyTax.t.sol` | 166 | Out of scope (separate product). |
| `test/LaunchIntegration.t.sol` | 579 | The real launch sequence end to end, then trades through it. Two-currency fee, ownerlessness, LP lock. |
| `test/MainnetPoolCreationFork.t.sol` | 248 | Live mainnet state: mine a hook, deploy via the real CREATE2 factory, open a pool. |
| `test/RebasePrototype.t.sol` | 146 |  |
| `test/WindowSurcharge.t.sol` | 45 | The one-hour-window dump surcharge: rate ladder, even halves, and the 75% ceiling. |

Plus `script/Rehearse.s.sol`, which broadcasts the real deploy script against an anvil fork of
mainnet and then buys, sells and distributes as a separate account, asserting on the resulting
balances. That is the only check that exercises deployed bytecode through real transactions.

---

## Deploy ordering is forced, not preferred

The token needs the distributor's address; the distributor needs the token's *and* the hook's;
the hook's address is not known until mined, and mining needs the token address as a
constructor argument. That cycle is broken with a one-shot `initialize`:

```
distributor -> token -> mine hook -> distributor.initialize(token, hook) -> syncAccount(deployer)
             -> liquidity deployer: initialize pool, seed one-sided, lock
```

The last sync matters: the initial mint happens in the token's constructor, before the
distributor knows the token, so that notification is swallowed and the deployer's shares would
otherwise start at zero.

**Mine the hook last.** Any change to hook code changes its creation code, which changes the
address a given salt produces.

---

# Source


## `src/DynamicSellTaxHook.sol`

The hook. Pressure accounting and fee capture. **The main event.**

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

import {BaseHook} from "v4-periphery/src/utils/BaseHook.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
// SwapParams left IPoolManager and became a standalone type in v4-core.
import {SwapParams} from "v4-core/src/types/PoolOperation.sol";
import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary, toBeforeSwapDelta} from "v4-core/src/types/BeforeSwapDelta.sol";
import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * DynamicSellTaxHook — Uniswap V4 hook with escalating sell tax
 *
 * Paired currency must be WETH (not native ETH). Validated at pool initialization.
 * Sell tax rises as cumulative sell pressure builds, resets aggressively on buys.
 * Buy tax is always 0%.
 *
 * Tax tiers (sell pressure as % of total supply):
 *   0% - 1%   supply sold -> 1%   tax
 *   1% - 2.5%             -> 2.5% tax
 *   2.5% - 5%             -> 4.5% tax
 *   5%+                   -> 5%   tax (rate cap)
 *
 * The pressure meter is itself capped at PRESSURE_CAP (600 bps = 6% of supply),
 * which bounds how long the rate can sit at the ceiling. See PRESSURE_CAP.
 *
 * Buy decay: each buy reduces sell pressure by 3x its relative size.
 * Fee split: 50% treasury, 50% reflected to holders.
 *
 * # No owner
 *
 * There is no owner and no privileged function. Every address is immutable and every
 * entry point is permissionless. See the note above `treasury`.
 */
/// @dev The rebasing hook on PressureToken: give up tokens and lift every holder at once.
interface IPressureReflect {
    function reflect(uint256 amount) external;
}

contract DynamicSellTaxHook is BaseHook, IUnlockCallback {
    using PoolIdLibrary for PoolKey;
    using CurrencyLibrary for Currency;
    using SafeERC20 for IERC20;

    // ─── Tax tiers (basis points, 100 bps = 1%) ───────────────────────────────

    // Raised for the Ethereum mainnet launch on 2026-08-20, from 100/250/450/500.
    //
    // The old ladder capped at 4.94% effective specifically to stay under the ~5% line
    // where aggregators, wallets and honeypot scanners start flagging tokens. That
    // constraint has been **deliberately abandoned**: a 20% ceiling will be flagged by
    // those tools, and the audience is expected to read the mechanism rather than a
    // scanner badge. It is a considered trade, not an oversight.
    //
    // It also fixes a ladder that barely laddered. The old top two tiers were 4.5% and
    // 5%, so above 2.5% supply exited the mechanism did almost nothing -- pushing the
    // meter from there to the 6% cap cost a seller 0.5pp more. The new spread is
    // 1 -> 4 -> 10 -> 20, roughly doubling per tier.
    //
    // All four are still EVEN in basis points, which is load-bearing: half of each rate
    // is charged in WETH and half in the token, so an odd value would lose a bps to
    // integer division and make the "50/50" claim approximate. Asserted by
    // test_everyTierRateHalvesExactly.
    // Nominal bps. Charged half in each currency, so a seller nets (1 - r/2)^2 and the
    // EFFECTIVE rate is r - r^2/4. These nominals are chosen so the effective rate is the
    // round number quoted, which is why they are not themselves round:
    //   100 -> 1%   404 -> 4%   1026 -> 10%   2112 -> 20%
    // Every one is even, so both halves are exact. A test asserts that.
    //
    // The old ladder was 100 / 250 / 450 / 500 (1 / 2.5 / 4.5 / 5%). Its top two tiers
    // were nearly identical, so above 2.5% the ladder did almost no work -- a seller who
    // pushed the meter from 2.5% to the 6% cap paid 0.5pp more for it.
    uint24 public constant TAX_TIER_0 = 100; // 1%  effective — baseline
    uint24 public constant TAX_TIER_1 = 404; // 4%  effective
    uint24 public constant TAX_TIER_2 = 1026; // 10% effective
    uint24 public constant TAX_TIER_3 = 2112; // 20% effective — cap

    // Cumulative sell pressure thresholds (bps of total supply: 100 = 1%)
    uint256 public constant THRESHOLD_1 = 100; // 1%
    uint256 public constant THRESHOLD_2 = 250; // 2.5%
    uint256 public constant THRESHOLD_3 = 500; // 5%

    // Each unit of buying cancels 3x the same relative sell pressure
    uint256 public constant BUY_DECAY_MULTIPLIER = 3;

    /// @notice Ceiling on the pressure meter itself, in bps of supply (600 = 6%).
    /// @dev A different knob from the tax cap. The top tier always bounded the
    ///      *rate*; nothing bounded the *meter*, so one large exit could pin the rate
    ///      at maximum for as long as it took to buy the pressure back down -- 6.3%
    ///      of supply in buys after a 20% dump. At 600 the worst case is 1.67%, which
    ///      is what makes "one large buyer can rescue the tier" true rather than
    ///      aspirational. Enforced on write, below.
    uint256 public constant PRESSURE_CAP = 600;

    // ─── Rolling-window sell surcharge ────────────────────────────────────────
    //
    // The cumulative meter above has no concept of time: dumping 2% of supply in five
    // minutes and bleeding 2% over three weeks produce an identical reading, because it
    // only decays on buys. That is the hole a determined seller walks through -- split a
    // dump into twenty small sells and each one is individually unremarkable.
    //
    // So this second mechanism measures sell volume inside a one-hour window. Twenty 0.1%
    // sells inside the window total the same 2% as one 2% sell and are charged the same,
    // which is what makes splitting pointless. It is deliberately POOL-WIDE rather than
    // per-wallet: a v4 hook receives `msg.sender`, which is the router, never the trader,
    // so per-wallet accounting is not merely hard here, it is unimplementable.
    //
    // It also encodes the actual intent better than a per-transaction check could.
    // Trimming a position gradually IS spreading sells over time; dumping IS concentrating
    // them. A window measures exactly that distinction. A per-transaction check only
    // measures how good someone's bot is.
    uint256 public constant SELL_WINDOW = 1 hours;

    // Window volume thresholds, bps of total supply.
    uint256 public constant WINDOW_T1 = 50; // 0.5%
    uint256 public constant WINDOW_T2 = 100; // 1.0%
    uint256 public constant WINDOW_T3 = 150; // 1.5% -- top band

    // Nominal rates. Charged half in each currency, so the seller nets (1 - r/2)^2 and the
    // EFFECTIVE rate is r - r^2/4. These nominals are chosen so the effective rate is the
    // round number quoted, and every one is even so both halves are exact:
    //   506 -> 5%   3268 -> 30%   5858 -> 50%
    // The architectural ceiling is nominal 10000 -> 75% effective; 90% is not expressible.
    //
    // The top band was 70% at 2% of supply. Lowered to 50% arriving at 1.5% because
    // deterrence saturates long before 70: anyone rational facing 50% waits for the window
    // instead of selling, so the extra 20 points bought no additional prevention. What it
    // did buy was severity for the people who pay it anyway -- panic sellers, forced
    // sellers, and BYSTANDERS. The window is pool-wide, so once a whale trips the top band
    // every seller in that window pays it, including someone exiting 0.01% who had nothing
    // to do with it. That cannot be designed away: exempting small sells would immediately
    // reopen the splitting loophole this window exists to close, since a dumper would just
    // use sub-threshold chunks. So the only available lever is severity.
    //
    // Moving the top band from 2% down to 1.5% keeps the bite on the actual dumper while
    // lowering the ceiling, and leaves real headroom under the 75% maximum.
    uint24 public constant WINDOW_RATE_1 = 506;
    uint24 public constant WINDOW_RATE_2 = 3268;
    uint24 public constant WINDOW_RATE_3 = 5858;

    /// @dev Transient slot carrying the rate from `_beforeSwap` to `_afterSwap`.
    ///
    ///      Both halves of a fee must be priced identically. `_beforeSwap` skims its half
    ///      off the input, so the token amount `_afterSwap` observes is SMALLER than the
    ///      one `_beforeSwap` saw. With a flat rate that was harmless. With a
    ///      size-dependent rate the two callbacks would read different amounts, compute
    ///      different rates, and charge mismatched halves. So the rate is decided once, in
    ///      `_beforeSwap`, and carried across rather than recomputed.
    bytes32 private constant RATE_SLOT = keccak256("pressure.rate.transient");

    // ─── The 50/50 split is structural, not arithmetic ────────────────────────
    //
    // Half the tier rate is charged in WETH and goes to the treasury; the other half is
    // charged in the token and goes to holders as reflections. Nothing is divided at
    // distribution time -- each currency has exactly one destination, so there is no
    // split arithmetic and no rounding remainder to argue about.
    //
    // Why the treasury half is WETH: it is the only team take, and paying it in the
    // token means realising it requires selling, which pushes the meter up for everyone
    // and prints the team's exit onto the chart. In WETH it is spendable on arrival.
    //
    // Why the holders' half stays in the token: paying holders the paired asset for
    // merely holding is a cash distribution, which is the shape of the thing this
    // deliberately is not. Reflections redistribute supply from the people leaving to
    // the people staying; they are not yield.
    //
    // This works because a v4 swap has a *specified* and an *unspecified* currency, and
    // the two return-delta callbacks each move exactly one of them. On a sell the token
    // and WETH are always on opposite sides of that divide, whichever direction the swap
    // is quoted in -- so one callback takes the token half and the other takes the WETH
    // half, in the same swap. See `_beforeSwap`.
    //
    // Every tier rate is even (100, 250, 450, 500), so the halves are exact.
    uint256 public constant SPLIT_DIVISOR = 2;

    // ─── State ─────────────────────────────────────────────────────────────────

    IERC20 public immutable token;
    address public immutable weth; // required paired currency — native ETH not accepted

    /// @notice Where the treasury half of each fee goes. Immutable.
    /// @dev These two were `address public` behind `onlyOwner` setters, and the plan was
    ///      to renounce ownership at the end of the deploy script. A live dry-run showed
    ///      that could never have worked, and that the ownership was not what it looked
    ///      like either.
    ///
    ///      The hook is deployed through the deterministic CREATE2 factory at
    ///      0x4e59b448..., so `msg.sender` in this constructor is the *factory*, not the
    ///      deployer. `Ownable(msg.sender)` therefore made the factory the owner. That
    ///      proxy's entire bytecode is a CREATE2 and a return — it can never be made to
    ///      call another contract — so the setters were already permanently unreachable
    ///      and `renounceOwnership()` would have reverted for everyone, forever.
    ///
    ///      The practical effect was worse than useless: `owner()` would have read
    ///      0x4e59b448... for the life of the token, which to any scanner or holder
    ///      checking is indistinguishable from a live admin key. Unreachable-but-visible
    ///      is the shape of the thing the product promises does not exist.
    ///
    ///      So ownership is gone rather than renounced. Immutable is strictly stronger:
    ///      there is no setter to reach, no owner to read, no window during deployment,
    ///      and the values are in the deployed bytecode where anyone can verify them.
    ///      The cost is the same one renouncing carried — a wrong treasury address can
    ///      never be corrected — so check it before deploying.
    address public immutable treasury;

    // Whether token is currency0 or currency1 in the pool (set at registration)
    mapping(PoolId => bool) public tokenIsCurrency0;

    // Cumulative sell pressure per pool (bps of total supply, can exceed thresholds)
    mapping(PoolId => uint256) public sellPressure;

    /// @notice Rolling-window sell volume. Packed into one slot: a sell writes it once.
    struct SellWindow {
        uint64 start; // unix seconds the current window opened
        uint192 volume; // tokens sold within it
    }

    mapping(PoolId => SellWindow) public sellWindow;

    // Deliberately absent: an `accumulatedTax` counter. One existed, was only ever
    // assigned zero, and would have read zero forever while real fees accrued -- worse
    // than no getter, because it looks authoritative. Use `pendingClaims()` for
    // unredeemed fees and the two balances of this contract for redeemed ones.

    // ─── Events ────────────────────────────────────────────────────────────────

    /// @notice One per half of a sell fee, so two per taxed sell: the token half for
    ///         holders and the WETH half for the treasury.
    ///
    /// @dev Nothing on chain could otherwise report a fee as it happened.
    ///      `TaxDistributed` only fires when someone flushes the accumulated balances,
    ///      and `SellPressureUpdated` carries the meter rather than the amount — so an
    ///      interface had no way to show "this sell paid this much", and an indexer would
    ///      have had to decode `PoolManager.Swap` and re-derive the fee from the tier.
    ///
    ///      `rate` is the full tier rate in force for the trade, not the half actually
    ///      charged in this currency, because the rate is the number a seller experiences
    ///      and the half is an implementation detail of how it is collected.
    event FeeAccrued(PoolId indexed poolId, address indexed currency, uint256 amount, uint24 rate);

    event SellPressureUpdated(PoolId indexed poolId, uint256 newPressure, uint24 newTax);
    event BuyDecay(PoolId indexed poolId, uint256 reduction, uint256 newPressure);
    event TaxDistributed(uint256 wethToTreasury, uint256 tokensToHolders);
    event PoolRegistered(PoolId indexed poolId, bool tokenIsCurrency0);

    error OnlyPoolManagerCallback();

    // ─── Constructor ───────────────────────────────────────────────────────────

    constructor(IPoolManager _manager, address _token, address _weth, address _treasury)
        BaseHook(_manager)
    {
        require(_weth != address(0), "zero weth");
        // Load-bearing, because it is immutable: a zero treasury would burn half of every
        // fee to address(0) forever, and that is not correctable after this line.
        require(_treasury != address(0), "zero treasury");
        token = IERC20(_token);
        weth = _weth;
        treasury = _treasury;
    }

    // ─── Hook permissions ──────────────────────────────────────────────────────

    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeInitialize: false,
            afterInitialize: true, // capture pool/token orientation
            beforeAddLiquidity: false,
            afterAddLiquidity: false,
            beforeRemoveLiquidity: false,
            afterRemoveLiquidity: false,
            beforeSwap: true,
            afterSwap: true, // pressure accounting, and the fee when the
            // token is the *unspecified* currency
            beforeDonate: false,
            afterDonate: false,
            beforeSwapReturnDelta: true, // fee when the token is the specified currency
            afterSwapReturnDelta: true, // fee when it is not
            afterAddLiquidityReturnDelta: false,
            afterRemoveLiquidityReturnDelta: false
        });
    }

    // ─── afterInitialize: record which currency is the token ──────────────────

    /// @dev BaseHook makes the external callbacks final and dispatches to these
    ///      internal ones, so `onlyPoolManager` lives in the base rather than here.
    function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
        address c0 = Currency.unwrap(key.currency0);
        address c1 = Currency.unwrap(key.currency1);

        // Exactly one currency must be the token, the other must be WETH.
        // Native ETH (address(0)) is not accepted.
        bool tokenIs0 = c0 == address(token);
        bool tokenIs1 = c1 == address(token);
        require(tokenIs0 || tokenIs1, "pool must include this token");
        require((tokenIs0 && c1 == weth) || (tokenIs1 && c0 == weth), "paired currency must be WETH");

        PoolId poolId = key.toId();
        tokenIsCurrency0[poolId] = tokenIs0;
        emit PoolRegistered(poolId, tokenIs0);
        return BaseHook.afterInitialize.selector;
    }

    // ─── Core: beforeSwap ──────────────────────────────────────────────────────

    /// @dev Takes whichever half of the fee is denominated in the *specified* currency.
    ///
    ///      A swap has a specified currency (the side the caller pinned an amount to) and
    ///      an unspecified one. `beforeSwapReturnDelta` can only move the specified
    ///      currency; `afterSwapReturnDelta` only the unspecified one. On a sell the token
    ///      and WETH are always on opposite sides of that divide, so the two callbacks
    ///      take one half each and the whole fee is collected in a single swap:
    ///
    ///        exact-input sell    specified = token   -> holders' half (token), here
    ///                            unspecified = WETH  -> treasury half (WETH), _afterSwap
    ///        exact-output sell   specified = WETH    -> treasury half (WETH), here
    ///                            unspecified = token -> holders' half (token), _afterSwap
    ///        buys                                    -> never, buys are untaxed
    ///
    ///      Pressure is deliberately NOT touched here. For two of the four input/output
    ///      combinations the token amount is simply not known yet, and an earlier version
    ///      papered over that by using `amountSpecified` regardless of which currency it
    ///      referred to — dividing a WETH amount by the token's total supply. `_afterSwap`
    ///      reads the real amounts from the settled delta instead.
    function _beforeSwap(address, PoolKey calldata key, SwapParams calldata params, bytes calldata)
        internal
        override
        returns (bytes4, BeforeSwapDelta, uint24)
    {
        PoolId poolId = key.toId();
        if (!_isSell(poolId, params.zeroForOne)) {
            return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
        }

        // exactInput == true when the caller pinned the amount they are paying in. On a
        // sell the caller pays the token, so the token is the specified currency exactly
        // when the swap is exact-input.
        bool exactInput = params.amountSpecified < 0;
        uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);

        // On exact-input the specified amount IS the token going in, so the surcharge can
        // see this sell's own size. On exact-output the specified amount is WETH out and
        // the token size is not known until the swap has run, so only the pre-trade window
        // counts. Known gap, documented in TAX-SPEC.md: an exact-output sell escapes the
        // surcharge on its own volume, though it still raises the window for whoever
        // trades next.
        uint24 rate = quoteTax(poolId, exactInput ? specified : 0);
        _stashRate(rate);
        uint256 taxAmount = (specified * (rate / SPLIT_DIVISOR)) / 10_000;
        if (taxAmount == 0) return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);

        // Minting a claim puts this hook's currency delta NEGATIVE — it owes the pool for
        // the claim. The returned specifiedDelta offsets that by charging the swapper, so
        // it must be POSITIVE. An earlier version returned a negative value, which
        // deepened the debt and made every sell revert with CurrencyNotSettled().
        //
        // On exact-input this skims the seller's input before the pool sees it. On
        // exact-output it raises the amount the pool is asked to produce, so the seller
        // still receives exactly what they asked for and pays the fee in extra tokens.
        _accrue(poolId, exactInput ? address(token) : weth, taxAmount, rate);
        return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(int128(int256(taxAmount)), 0), 0);
    }

    /// @dev Pressure accounting for every swap, and the half of the fee denominated in the
    ///      *unspecified* currency — the half `_beforeSwap` structurally could not take.
    ///
    ///      `delta` is the pool's swap result, so its token-side component is the real
    ///      token amount for all four input/output combinations. Negative means the
    ///      swapper paid tokens in — a sell.
    function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata)
        internal
        override
        returns (bytes4, int128)
    {
        PoolId poolId = key.toId();
        bool tokenIs0 = tokenIsCurrency0[poolId];

        int128 tokenDelta = tokenIs0 ? delta.amount0() : delta.amount1();
        if (tokenDelta == 0) return (BaseHook.afterSwap.selector, 0);

        // Each branch is its own frame. Held inline, the locals the two cases need
        // between them overflow the stack -- and viaIR is not an option here, because it
        // changes creation code and the hook's address is mined from that hash.
        if (tokenDelta < 0) {
            int128 fee = _onSell(poolId, tokenIs0, uint256(uint128(-tokenDelta)), delta, params.amountSpecified < 0);
            return (BaseHook.afterSwap.selector, fee);
        }

        _onBuy(poolId, uint256(uint128(tokenDelta)));
        return (BaseHook.afterSwap.selector, 0);
    }

    /// @dev Raise the meter, then take the unspecified-currency half of the fee.
    function _onSell(PoolId poolId, bool tokenIs0, uint256 tokenAmount, BalanceDelta delta, bool exactInput)
        private
        returns (int128)
    {
        uint256 supply = token.totalSupply();
        if (supply == 0) return 0;

        // Rate is decided once in `_beforeSwap` and carried here. Recomputing would read a
        // token amount already reduced by the half skimmed there, so a size-dependent rate
        // would differ between the two halves of the same fee.
        uint24 rateAtTrade = _readRate();

        // Record what the seller actually put in, not what survived the skim.
        //
        // `tokenAmount` is the pool's view, which on an exact-input sell is already net of
        // the half `_beforeSwap` took off the top. Recording that net figure would let the
        // surcharge partially un-count itself -- a higher rate skims more, so less lands
        // in the window, so the next sell is charged less. The feedback runs the wrong way
        // and weakens the mechanism exactly when it is meant to bite hardest. Measured on
        // a 20-slice split it lost ~10.5% of the volume.
        //
        // Exact-output sells skim WETH rather than the token, so their token side is
        // already gross and needs no adjustment.
        uint256 grossed = tokenAmount;
        if (exactInput) {
            uint256 half = rateAtTrade / SPLIT_DIVISOR;
            if (half < 10_000) grossed = (tokenAmount * 10_000) / (10_000 - half);
        }
        _recordWindow(poolId, grossed);

        uint256 next = sellPressure[poolId] + (tokenAmount * 10_000) / supply;
        sellPressure[poolId] = next > PRESSURE_CAP ? PRESSURE_CAP : next;
        emit SellPressureUpdated(poolId, sellPressure[poolId], getCurrentTax(poolId));

        return _chargeUnspecifiedHalf(poolId, exactInput, rateAtTrade, delta, tokenIs0, tokenAmount);
    }

    function _stashRate(uint24 rate) private {
        bytes32 slot = RATE_SLOT;
        assembly ("memory-safe") {
            tstore(slot, rate)
        }
    }

    function _readRate() private view returns (uint24 rate) {
        bytes32 slot = RATE_SLOT;
        assembly ("memory-safe") {
            rate := tload(slot)
        }
    }

    /// @dev Add a sell to the window, opening a fresh one if the last has expired.
    ///
    ///      This is a TUMBLING window, not a rolling one: the bucket resets wholesale once
    ///      SELL_WINDOW has passed since it opened, rather than always looking back exactly
    ///      an hour. Cheaper (one slot, one write) and not meaningfully more gameable,
    ///      because the window's start is set by trading activity rather than a wall clock,
    ///      so there is no predictable boundary to wait for.
    ///      Buys deliberately do NOT reduce it: the window measures how concentrated
    ///      selling is in time, and a buy does not make a dump that already happened any
    ///      less concentrated. Relief from buying is the cumulative meter's job.
    function _recordWindow(PoolId poolId, uint256 tokenAmount) private {
        SellWindow memory w = sellWindow[poolId];
        if (w.start == 0 || block.timestamp >= uint256(w.start) + SELL_WINDOW) {
            sellWindow[poolId] = SellWindow({start: uint64(block.timestamp), volume: uint192(tokenAmount)});
        } else {
            uint256 next = uint256(w.volume) + tokenAmount;
            sellWindow[poolId] = SellWindow({start: w.start, volume: uint192(next)});
        }
    }

    /// @dev Relieve pressure at BUY_DECAY_MULTIPLIER, measured in tokens received.
    ///      An earlier version used `amountSpecified` here, which on an exact-input buy is
    ///      the WETH paid — so decay scaled with the ETH price rather than with supply, and
    ///      contradicted the stated "buying 1% of supply reduces pressure by 3%".
    function _onBuy(PoolId poolId, uint256 tokenAmount) private {
        uint256 current = sellPressure[poolId];
        if (current == 0) return;

        uint256 supply = token.totalSupply();
        if (supply == 0) return;

        uint256 reduction = (tokenAmount * 10_000 * BUY_DECAY_MULTIPLIER) / supply;
        sellPressure[poolId] = reduction >= current ? 0 : current - reduction;
        emit BuyDecay(poolId, reduction, sellPressure[poolId]);
    }

    /// @dev The half of the fee denominated in the swap's *unspecified* currency, which is
    ///      the only half `afterSwapReturnDelta` can move.
    ///
    ///        exact-input sell  -> unspecified is WETH, the seller's proceeds. Skimming it
    ///                             is what pays the treasury in WETH, so its take never
    ///                             has to be sold to be spent.
    ///        exact-output sell -> unspecified is the token, so this is the holders' half,
    ///                             charged as extra token the seller pays in.
    ///
    ///      `rate` is the rate read before the meter moved, the same one `_beforeSwap`
    ///      charged its half at, so both halves of one swap are priced identically.
    ///
    ///      Its own frame purely for stack depth: inlined, this pushed `_afterSwap` over
    ///      the limit. viaIR would also fix it and is deliberately off, because it changes
    ///      creation code and the hook's address is mined from the creation code hash.
    function _chargeUnspecifiedHalf(
        PoolId poolId,
        bool exactInput,
        uint24 rate,
        BalanceDelta delta,
        bool tokenIs0,
        uint256 tokenAmount
    ) private returns (int128) {
        uint256 base = exactInput ? _wethOut(delta, tokenIs0) : tokenAmount;
        uint256 taxAmount = (base * (rate / SPLIT_DIVISOR)) / 10_000;
        if (taxAmount == 0) return 0;
        _accrue(poolId, exactInput ? weth : address(token), taxAmount, rate);
        return int128(int256(taxAmount));
    }

    /// @dev A sell is the token flowing into the pool. zeroForOne means currency0 is
    ///      being paid in, so it is a sell exactly when the token is currency0.
    function _isSell(PoolId poolId, bool zeroForOne) private view returns (bool) {
        return tokenIsCurrency0[poolId] ? zeroForOne : !zeroForOne;
    }

    /// @dev WETH the pool paid out on this swap, before our cut. Positive for a seller.
    ///      Clamped rather than cast blind: on an exact-output sell that could not be
    ///      filled the pool may have produced less than asked, and a negative here would
    ///      otherwise wrap into an enormous fee.
    function _wethOut(BalanceDelta delta, bool tokenIs0) private pure returns (uint256) {
        int128 wethDelta = tokenIs0 ? delta.amount1() : delta.amount0();
        return wethDelta > 0 ? uint256(uint128(wethDelta)) : 0;
    }

    // ─── Fee accrual and redemption ────────────────────────────────────────────

    /// @dev Credits the fee as an ERC-6909 claim rather than pulling real tokens.
    ///
    ///      `take()` was used here originally and it made sells revert in a case the
    ///      integration test found: `take` moves *physical* tokens out of the
    ///      PoolManager, but a seller's tokens have not settled yet when the swap
    ///      callbacks run. The fee therefore had to come out of the pool's existing
    ///      reserves, so once buyers had drained the token side, every sell failed with
    ///      ERC20InsufficientBalance. A token whose sells can revert is honeypot-shaped,
    ///      which is not a tradeoff worth making.
    ///
    ///      `mint` is pure accounting -- it credits this contract inside the
    ///      PoolManager's own ledger and needs no balance to exist yet. The claim is
    ///      converted to real tokens later, by `redeemFees`, when the pool is settled and
    ///      solvent.
    ///
    ///      Takes the currency explicitly because the fee now arrives in both: the token
    ///      for the holders' half and WETH for the treasury's.
    function _accrue(PoolId poolId, address currency, uint256 amount, uint24 rate) private {
        poolManager.mint(address(this), Currency.wrap(currency).toId(), amount);
        emit FeeAccrued(poolId, currency, amount, rate);
    }

    /// @notice Convert accrued claims of both currencies into real balances held here.
    /// @dev Permissionless. Called automatically by `distributeTax`, and separately
    ///      available so anyone can advance the process.
    function redeemFees() public returns (uint256 tokenRedeemed, uint256 wethRedeemed) {
        (tokenRedeemed, wethRedeemed) = pendingClaims();
        if (tokenRedeemed == 0 && wethRedeemed == 0) return (0, 0);
        poolManager.unlock(abi.encode(tokenRedeemed, wethRedeemed));
    }

    /// @inheritdoc IUnlockCallback
    function unlockCallback(bytes calldata data) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert OnlyPoolManagerCallback();
        (uint256 tokenAmount, uint256 wethAmount) = abi.decode(data, (uint256, uint256));
        if (tokenAmount != 0) _redeem(address(token), tokenAmount);
        if (wethAmount != 0) _redeem(weth, wethAmount);
        return "";
    }

    function _redeem(address currency, uint256 amount) private {
        Currency c = Currency.wrap(currency);
        poolManager.burn(address(this), c.toId(), amount);
        poolManager.take(c, address(this), amount);
    }

    /// @notice Claims accrued but not yet converted into real balances.
    function pendingClaims() public view returns (uint256 tokenClaims, uint256 wethClaims) {
        tokenClaims = poolManager.balanceOf(address(this), Currency.wrap(address(token)).toId());
        wethClaims = poolManager.balanceOf(address(this), Currency.wrap(weth).toId());
    }

    // ─── Tax distribution ──────────────────────────────────────────────────────

    /**
     * Anyone can call to flush accumulated tax to treasury + holders.
     * No harm in calling frequently; it's a no-op when there is nothing to move.
     *
     * No split arithmetic: the WETH was collected for the treasury and the token for
     * holders, so each balance has exactly one destination. Whatever is here goes there.
     * That also means a stray donation of either currency is forwarded rather than
     * stranded.
     */
    function distributeTax() external {
        // Convert any outstanding claims first, so a caller does not have to know that
        // fees arrive as claims before they arrive as real balances.
        redeemFees();

        uint256 wethAmount = IERC20(weth).balanceOf(address(this));
        uint256 tokenAmount = token.balanceOf(address(this));
        if (wethAmount == 0 && tokenAmount == 0) return;

        if (wethAmount != 0) IERC20(weth).safeTransfer(treasury, wethAmount);
        // The holders' half is REFLECTED, not transferred: `reflect` gives up these
        // tokens and raises the token's index, so every holder's balance rises at once.
        // There is no distributor to send to, nothing for anyone to claim, and no
        // per-holder gas — which is the entire reason the keeper is gone.
        if (tokenAmount != 0) IPressureReflect(address(token)).reflect(tokenAmount);

        emit TaxDistributed(wethAmount, tokenAmount);
    }

    // ─── Views ─────────────────────────────────────────────────────────────────

    /// @notice Sell volume inside the current window, in tokens. Zero once it has expired.
    /// @dev Tumbling, not rolling — see `_recordWindow`.
    function windowVolume(PoolId poolId) public view returns (uint256) {
        SellWindow memory w = sellWindow[poolId];
        if (w.start == 0 || block.timestamp >= uint256(w.start) + SELL_WINDOW) return 0;
        return w.volume;
    }

    /// @notice The surcharge for a given in-window volume, in nominal bps. 0 below T1.
    function surchargeFor(uint256 volumeTokens) public view returns (uint24) {
        uint256 supply = token.totalSupply();
        if (supply == 0 || volumeTokens == 0) return 0;
        uint256 bps = (volumeTokens * 10_000) / supply;
        if (bps >= WINDOW_T3) return WINDOW_RATE_3;
        if (bps >= WINDOW_T2) return WINDOW_RATE_2;
        if (bps >= WINDOW_T1) return WINDOW_RATE_1;
        return 0;
    }

    /// @notice What a sell of `tokenAmount` would actually be charged, in nominal bps.
    /// @dev `max`, never additive. Additive would stack past the nominal-10000 ceiling and
    ///      silently clamp, and would also make the two mechanisms impossible to reason
    ///      about independently.
    ///
    ///      The current trade's own size IS counted toward the window, unlike the
    ///      cumulative meter which is read before it moves. That asymmetry is deliberate:
    ///      the meter charges you for market conditions you did not create, so it would be
    ///      unfair to include your own sell -- but the surcharge exists precisely to charge
    ///      you for the size of the sell you are making right now, so excluding it would
    ///      let a single large dump through at zero.
    function quoteTax(PoolId poolId, uint256 tokenAmount) public view returns (uint24) {
        uint24 tier = getCurrentTax(poolId);
        uint24 sur = surchargeFor(windowVolume(poolId) + tokenAmount);
        return sur > tier ? sur : tier;
    }

    function getCurrentTax(PoolId poolId) public view returns (uint24) {
        uint256 pressure = sellPressure[poolId];
        if (pressure >= THRESHOLD_3) return TAX_TIER_3;
        if (pressure >= THRESHOLD_2) return TAX_TIER_2;
        if (pressure >= THRESHOLD_1) return TAX_TIER_1;
        return TAX_TIER_0;
    }

    /// @notice The two halves of the current rate, in bps. `wethHalf` is charged in WETH
    ///         and paid to the treasury; `tokenHalf` in the token, reflected to holders.
    /// @dev Every tier rate is even, so these are exact and always equal to each other.
    function currentTaxHalves(PoolId poolId) external view returns (uint24 wethHalf, uint24 tokenHalf) {
        uint24 half = uint24(getCurrentTax(poolId) / SPLIT_DIVISOR);
        return (half, half);
    }

    function getPoolState(PoolKey calldata key)
        external
        view
        returns (uint256 pressure, uint24 currentTax, uint256 pendingTax, uint256 supplySnapshot)
    {
        PoolId poolId = key.toId();
        pressure = sellPressure[poolId];
        currentTax = getCurrentTax(poolId);
        pendingTax = token.balanceOf(address(this));
        supplySnapshot = token.totalSupply();
    }

    // ─── Admin ─────────────────────────────────────────────────────────────────
    //
    // There is none. This section is kept as a heading because what is missing from it
    // is a design decision rather than an omission, and the three things that were once
    // here are each worth naming.
    //
    // No setTreasury. The address is immutable; see the note on `treasury` for why it is
    // not merely renounced. There is no distributor to point anywhere either — the token
    // rebases, so the holders' half is paid by raising an index rather than sent to an
    // accountant contract.
    //
    // No resetSellPressure. An owner-callable one existed. It let the owner return the
    // rate to baseline at will, which makes the whole mechanism discretionary and
    // contradicts what the product says out loud -- "no timer unwinds it", "the only
    // thing that brings the rate down is inbound flow", params_upgradeable: false.
    // A privileged escape hatch from the one number the token is named after is not a
    // safety feature, it is the thing holders would most want to be impossible.
    //
    // No owner() at all, so nothing reads back as an admin key and there is nothing to
    // renounce. `distributeTax` and `redeemFees` are callable by anyone, which is the
    // only access control this contract needs: every caller does the same, single,
    // predetermined thing.
}
```


## `src/PressureToken.sol`

Fixed-supply **rebasing** ERC-20. Shares + index; reflections raise the index.

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

/// @title PressureToken
/// @notice Fixed-supply, ownerless, **rebasing** ERC-20 for the PRESSURE mechanism.
///
/// # Why rebasing
///
/// Reflections used to be *credited* to a distributor and then either claimed by the
/// holder or pushed to them by a keeper. Both cost gas per holder, and the keeper is a
/// moving part that can stop — it silently did for five days on the previous deployment,
/// stranding 24.2M tokens that holders had already earned.
///
/// Here, balances are computed rather than stored: an account holds *shares*, and
/// `balanceOf` is `shares * index / PRECISION`. Paying reflections means raising the
/// index, which lifts every holder at once, in the same transaction as the sell that
/// funded it, for a fixed cost independent of holder count. There is nothing to claim,
/// nothing to push, no keeper, no gas wallet, and no floor below which small holders are
/// skipped because delivering their dust costs more than the dust.
///
/// # The exclusion set, and why it is not optional
///
/// **The v4 PoolManager holds essentially the entire supply.** If it rebased with
/// everyone else, the overwhelming majority of every reflection would flow straight back
/// into liquidity instead of to holders — the mechanism would mostly pay itself.
///
/// Worse, it would not merely be unfair, it would be *broken*: v4 settles by measuring
/// `balanceOf(poolManager)` across `sync` -> transfer -> `settle`. A balance that can move
/// for reasons unrelated to a transfer corrupts that measurement, and a rebase landing
/// mid-swap would desynchronise pool accounting from reality.
///
/// So excluded accounts store a raw balance and never rebase. Proven against a real v4
/// pool in `test/RebasePrototype.t.sol` before this was written.
///
/// Exclusion is also where reflect-style tokens have historically had their worst bugs —
/// an account crossing the boundary with a stale conversion silently mints or burns value
/// — so the set is **fixed at deploy and has no setter**. Nothing can ever cross it.
///
/// # Ownerless
///
/// No owner, no pause, no upgrade, no mint. The exclusion set is written once during
/// construction plus a single one-shot `initHook`, spent during the launch, and there is
/// no path to change it afterwards. `initHook` exists only because of a dependency cycle:
/// the hook needs the token's address in its constructor, so the token necessarily exists
/// first and cannot have been told the hook's address at construction.
contract PressureToken {
    string public name;
    string public symbol;
    uint8 public constant decimals = 18;

    /// @notice Supply at launch. It does not change: a reflection moves supply from the
    ///         seller to holders, it does not create any.
    uint256 public constant TOTAL_SUPPLY = 1_000_000_000e18;

    uint256 private constant PRECISION = 1e18;

    /// @dev Included accounts hold shares; excluded accounts hold tokens directly.
    mapping(address => uint256) private _shares;
    mapping(address => uint256) private _raw;
    mapping(address => bool) public isExcluded;
    mapping(address => mapping(address => uint256)) public allowance;

    uint256 public totalShares;
    uint256 public excludedTotal;

    /// @notice shares -> tokens multiplier. Only ever rises.
    uint256 public index = PRECISION;

    /// @notice The address allowed to spend the one-shot `initHook`. The launcher.
    address private immutable _initializer;
    /// @notice The hook. Set once, then permanently fixed.
    address public hook;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /// @notice Emitted when a sell's holder half is reflected across every holder.
    /// @param from the account that gave up the tokens (the hook)
    /// @param amount tokens redistributed
    /// @param newIndex the resulting multiplier
    event Reflected(address indexed from, uint256 amount, uint256 newIndex);

    error ZeroAddress();
    error AlreadyInitialized();
    error NotInitializer();
    error InsufficientBalance();
    error NothingToReflect();

    /// @param poolManager_ the v4 singleton. MUST be excluded; see above.
    /// @param treasury_ excluded so the team half never earns reflections on top.
    constructor(string memory name_, string memory symbol_, address poolManager_, address treasury_) {
        if (poolManager_ == address(0) || treasury_ == address(0)) revert ZeroAddress();
        name = name_;
        symbol = symbol_;
        _initializer = msg.sender;

        // The launcher holds the whole supply for the length of one transaction and then
        // puts it into liquidity. Excluded so a reflection can never be aimed at it.
        _excludeAtDeploy(msg.sender);
        _excludeAtDeploy(poolManager_);
        if (treasury_ != msg.sender) _excludeAtDeploy(treasury_);

        _raw[msg.sender] = TOTAL_SUPPLY;
        excludedTotal = TOTAL_SUPPLY;
        emit Transfer(address(0), msg.sender, TOTAL_SUPPLY);
    }

    /// @notice One-shot: record and exclude the hook. Spent during the launch.
    /// @dev The hook cannot be an argument to the constructor because its own constructor
    ///      takes the token's address — the cycle has to be broken somewhere, and this is
    ///      the same one-shot pattern the old distributor used.
    function initHook(address hook_) external {
        if (msg.sender != _initializer) revert NotInitializer();
        if (hook != address(0)) revert AlreadyInitialized();
        if (hook_ == address(0)) revert ZeroAddress();
        hook = hook_;
        _excludeAtDeploy(hook_);
    }

    function _excludeAtDeploy(address a) private {
        if (isExcluded[a]) return;
        isExcluded[a] = true;
    }

    // ─── ERC-20 ────────────────────────────────────────────────────────────────

    /// @notice Excluded raw balances plus the rebased share pool. Constant by design:
    ///         a reflection moves supply between holders, it never mints.
    function totalSupply() public view returns (uint256) {
        return excludedTotal + (totalShares * index) / PRECISION;
    }

    function balanceOf(address a) public view returns (uint256) {
        if (isExcluded[a]) return _raw[a];
        return (_shares[a] * index) / PRECISION;
    }

    /// @notice Shares held, for anyone who wants to reason about dilution directly.
    function sharesOf(address a) external view returns (uint256) {
        return _shares[a];
    }

    function transfer(address to, uint256 value) external returns (bool) {
        _move(msg.sender, to, value);
        return true;
    }

    function approve(address spender, uint256 value) external returns (bool) {
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value) external returns (bool) {
        uint256 a = allowance[from][msg.sender];
        if (a != type(uint256).max) {
            if (a < value) revert InsufficientBalance();
            allowance[from][msg.sender] = a - value;
        }
        _move(from, to, value);
        return true;
    }

    function _move(address from, address to, uint256 value) private {
        if (to == address(0)) revert ZeroAddress();
        if (balanceOf(from) < value) revert InsufficientBalance();

        if (isExcluded[from]) {
            _raw[from] -= value;
            excludedTotal -= value;
        } else {
            uint256 sh = (value * PRECISION) / index;
            _shares[from] -= sh;
            totalShares -= sh;
        }

        if (isExcluded[to]) {
            _raw[to] += value;
            excludedTotal += value;
        } else {
            uint256 sh = (value * PRECISION) / index;
            _shares[to] += sh;
            totalShares += sh;
        }

        emit Transfer(from, to, value);
    }

    // ─── Reflection ────────────────────────────────────────────────────────────

    /// @notice Give up `amount` of your own balance and distribute it across every
    ///         non-excluded holder, proportionally, by raising the index.
    ///
    /// @dev This is what the hook calls with the holders' half of a sell fee, in the same
    ///      transaction as the sell. Every holder's balance rises immediately; nobody
    ///      sends a transaction and nobody pays gas for their share.
    ///
    ///      Deliberately **permissionless**: it can only ever move the caller's OWN
    ///      tokens to everyone else, so the worst a stranger can do is donate. Restricting
    ///      it would add an access-control surface to buy nothing.
    ///
    ///      Total supply is unchanged — the caller's balance falls by exactly what the
    ///      share pool gains.
    function reflect(uint256 amount) external {
        if (amount == 0) revert NothingToReflect();
        if (balanceOf(msg.sender) < amount) revert InsufficientBalance();

        // Take it from the caller first, so a caller who is themselves a holder cannot
        // receive a slice of their own reflection.
        if (isExcluded[msg.sender]) {
            _raw[msg.sender] -= amount;
            excludedTotal -= amount;
        } else {
            uint256 sh = (amount * PRECISION) / index;
            _shares[msg.sender] -= sh;
            totalShares -= sh;
        }

        // With no included holders there is nobody to reflect to. Reverting rather than
        // silently burning keeps supply honest: the fee stays with the caller and can be
        // reflected later once there are holders.
        if (totalShares == 0) revert NothingToReflect();

        uint256 pool = (totalShares * index) / PRECISION;
        index = ((pool + amount) * PRECISION) / totalShares;

        emit Reflected(msg.sender, amount, index);
    }
}
```


## `src/PressureLauncher.sol`

The entire launch in one transaction. Mandatory where a mempool exists.

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

import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";

import {DynamicSellTaxHook} from "./DynamicSellTaxHook.sol";
import {PressureToken} from "./PressureToken.sol";
import {PressureLiquidityDeployer} from "./PressureLiquidityDeployer.sol";

/// @title PressureLauncher
/// @notice The entire launch, in one transaction: this contract's constructor deploys the
///         the token and the mined hook, wires them, opens the pool, seeds it
///         one-sided, and locks the liquidity. Nothing exists half-built at any point an
///         outside observer could act on.
///
/// # Why atomic
///
/// The previous deploy script did the same steps as separate transactions. Between the hook
/// landing and the pool being seeded, the hook was live and its pool was uninitialised —
/// so anyone could have called `PoolManager.initialize` on our `PoolKey` at a price of
/// their choosing. That could not steal anything (our `deploy()` would have reverted on
/// `PoolAlreadyInitialized`), but it could have blocked the launch and forced a re-mine.
///
/// The window was negligible on a chain with sub-second blocks and no public mempool — but
/// "probably too fast to exploit" is a weaker guarantee than "cannot happen". Here the pool
/// is created inside the same transaction that seeds and locks it, so there is no moment to
/// race.
///
/// # No owner, and nothing left behind
///
/// This contract has no owner, no functions, and no way to move anything after its
/// constructor returns. It is a throwaway whose only purpose is to make the launch
/// indivisible. The addresses it created are stored so the whole launch is auditable from
/// one place.
///
/// The launch dust — the remainder the single-sided liquidity maths cannot place, a few
/// thousand wei — stays here rather than being forwarded. That is deliberate: this contract
/// has no transfer function, so the dust is provably unrecoverable and the deploying wallet
/// ends the launch holding **exactly zero** rather than a rounding artefact.
///
/// # Address prediction
///
/// The hook's address must carry its permission bits in the low 14 bits, so it is CREATE2'd
/// with an off-chain mined salt. That salt is mined against *this contract's* address, and
/// the hook's constructor argument includes the token, which is created here.
/// So the script must predict, before anything is deployed:
///
///   launcher = CREATE(deployer EOA, its current nonce)
///   token    = CREATE(launcher, 1)   // a contract's nonce starts at 1 (EIP-161)
///
/// NOTE: the token was at nonce 2 while a separate ReflectionDistributor existed. Moving
/// to a rebasing token deleted that contract, so the token moved DOWN to nonce 1 and its
/// address changed. Anything that predicted the old address is wrong.
///
/// The creation order below is therefore load-bearing and must not be reordered.
///
/// If any prediction is wrong the salt is wrong, the hook lands at an address without the
/// required bits, and `BaseHook`'s own constructor reverts `HookAddressNotValid` — taking
/// the whole transaction with it. The launch fails atomically rather than half-completing,
/// which is the right way for this to break.
contract PressureLauncher {
    PressureToken public immutable token;
    DynamicSellTaxHook public immutable hook;
    PressureLiquidityDeployer public immutable liquidity;

    /// @notice The pool this launch created. A struct, so it cannot be immutable.
    PoolKey public poolKey;

    struct Params {
        address poolManager;
        address weth;
        /// @dev Receives the WETH half of every sell fee. Baked into the hook as an
        ///      immutable, so it can never be changed by anyone afterwards.
        address treasury;
        string name;
        string symbol;
        /// @dev Mined off-chain against this contract's predicted address.
        bytes32 hookSalt;
        uint256 lpSupply;
        int24 startTick;
        int24 farTick;
        int24 tickSpacing;
        uint24 fee;
    }

    error ZeroTreasury();
    error LPNotLocked();
    error SupplyNotSeeded();

    event Launched(address token, address hook, address liquidity, address treasury);

    constructor(Params memory p) {
        if (p.treasury == address(0)) revert ZeroTreasury();

        // 1. Token. Mints the whole supply to its deployer, which is this contract.
        //    The PoolManager and the treasury are excluded from reflections at
        //    construction: the pool holds nearly all the supply, so if it rebased with
        //    everyone else the holders' half would flow back into liquidity instead of to
        //    holders — and a balance that moves outside a transfer also corrupts v4's
        //    sync/settle accounting.
        token = new PressureToken(p.name, p.symbol, p.poolManager, p.treasury);

        // 2. Hook, at the mined address. Reverts the whole launch if the salt is wrong.
        hook = new DynamicSellTaxHook{salt: p.hookSalt}(
            IPoolManager(p.poolManager), address(token), p.weth, p.treasury
        );

        // 3. One-shot wiring: exclude the hook, which holds fees between collection and
        //    reflection. Can never be done again — the token has no setter.
        //    It could not be a constructor argument because the hook's own constructor
        //    takes the token's address; the cycle has to be broken somewhere.
        token.initHook(address(hook));

        // 4. Open the market: initialise the pool, seed it one-sided, lock the position.
        //    All three inside `deploy`, so there is no unseeded or unlocked moment.
        liquidity = new PressureLiquidityDeployer(IPoolManager(p.poolManager));
        token.approve(address(liquidity), p.lpSupply);
        poolKey = _seed(p);

        if (!liquidity.lpLocked()) revert LPNotLocked();
        // Belt and braces: the pool must actually hold the supply. Catches a silently
        // partial seed, which would otherwise only show up as a thin market later.
        if (token.balanceOf(p.poolManager) == 0) revert SupplyNotSeeded();

        emit Launched(address(token), address(hook), address(liquidity), p.treasury);
    }

    /// @dev Its own frame purely for stack depth. Inlined, the constructor's live locals
    ///      overflow — and `viaIR` is not an option, because it changes creation code and
    ///      the hook's address is mined from the creation code hash.
    function _seed(Params memory p) private returns (PoolKey memory) {
        return liquidity.deploy(
            PressureLiquidityDeployer.Params({
                hook: address(hook),
                token: address(token),
                weth: p.weth,
                startTick: p.startTick,
                farTick: p.farTick,
                tickSpacing: p.tickSpacing,
                fee: p.fee,
                tokenAmount: p.lpSupply
            })
        );
    }
}
```


## `src/PressureLiquidityDeployer.sol`

Opens the pool, seeds one-sided liquidity, locks it. One transaction.

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

import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {Currency} from "v4-core/src/types/Currency.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {ModifyLiquidityParams} from "v4-core/src/types/PoolOperation.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "v4-core/src/libraries/FixedPoint96.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title PressureLiquidityDeployer
/// @notice Opens the pool, seeds one-sided liquidity, and locks that liquidity
///         permanently — all in a single transaction.
///
/// # Single-sided seeding
///
/// The pool is initialised at the boundary of the position's range, so the entire
/// position is denominated in the token and no WETH is required to launch. The first
/// buyer walks the price into the range. That is what makes a launch possible with
/// nothing but supply.
///
/// Which boundary depends on token ordering, which is not known until the token is
/// deployed — v4 requires `currency0 < currency1` by address. If the token sorts
/// second, the position runs up to `startTick` and the pool opens there. If it sorts
/// first, the range is mirrored and the pool opens at the lower edge instead. Both give
/// an all-token position; the arithmetic is just reflected.
///
/// # The lock is not a second step
///
/// `unlockCallback` is the only route by which this contract can instruct the
/// PoolManager to touch the position, and it reverts once `lpLocked` is set. `deploy`
/// sets that flag before it returns, in the same transaction that seeded the position.
///
/// The stack this was ported from locked in a separate owner-only call, which left a
/// window between seeding and locking during which the deployer could have withdrawn
/// everything — and made the guarantee depend on someone remembering to call it. Here
/// there is no window, no owner, and nothing to remember: after `deploy` returns,
/// `lpLocked` is true forever and can be read by anyone.
///
/// This contract has no owner and no privileged functions at all.
contract PressureLiquidityDeployer is IUnlockCallback {
    IPoolManager public immutable poolManager;

    /// @notice True once liquidity has been seeded. Permanently disables `unlockCallback`,
    ///         which is the only path to modifying the position.
    bool public lpLocked;
    /// @notice One pool per deployer, so a second call cannot re-enter with new terms.
    bool public deployed;

    /// @notice The pool that was created. Readable for verification.
    PoolKey public poolKey;

    struct Params {
        address hook;
        address token;
        address weth;
        /// @dev Opening tick. Sets the launch price, and therefore the opening market cap.
        int24 startTick;
        /// @dev Far edge of the range. Bounds the price ceiling the position covers.
        int24 farTick;
        int24 tickSpacing;
        /// @dev Static LP fee. Our hook does not override the LP fee, so this must be a
        ///      real value rather than the dynamic-fee sentinel.
        uint24 fee;
        uint256 tokenAmount;
    }

    struct Seed {
        PoolKey key;
        int24 tickLower;
        int24 tickUpper;
        address token;
        uint256 tokenAmount;
        bool tokenIsToken0;
    }

    Seed private _pending;

    error NotPoolManager();
    error AlreadyDeployed();
    error LPLocked();
    error TransferFailed();
    error ZeroAmount();

    event PoolSeededAndLocked(
        address indexed token, address indexed hook, int24 tickLower, int24 tickUpper, uint128 liquidity
    );

    constructor(IPoolManager poolManager_) {
        poolManager = poolManager_;
    }

    /// @notice Create the pool, seed it one-sided, and lock the position forever.
    /// @dev Caller must have approved `tokenAmount` to this contract first.
    function deploy(Params calldata p) external returns (PoolKey memory key) {
        if (deployed) revert AlreadyDeployed();
        if (p.tokenAmount == 0) revert ZeroAmount();
        deployed = true;

        if (!IERC20(p.token).transferFrom(msg.sender, address(this), p.tokenAmount)) {
            revert TransferFailed();
        }
        // Read back rather than trusting the argument: the token may take a fee on
        // transfer, in which case less arrived than was asked for and seeding the
        // requested amount would fail later with a far less obvious error.
        uint256 held = IERC20(p.token).balanceOf(address(this));
        if (held == 0) revert ZeroAmount();

        bool tokenIsToken0 = p.token < p.weth;
        (address t0, address t1) = tokenIsToken0 ? (p.token, p.weth) : (p.weth, p.token);

        key = PoolKey({
            currency0: Currency.wrap(t0),
            currency1: Currency.wrap(t1),
            fee: p.fee,
            tickSpacing: p.tickSpacing,
            hooks: IHooks(p.hook)
        });
        poolKey = key;

        int24 lower;
        int24 upper;
        int24 initTick;
        if (tokenIsToken0) {
            // Token sorts first: mirror the range and open at its lower edge, so the
            // whole position sits in token0.
            lower = _align(-p.startTick, p.tickSpacing);
            upper = _align(-p.farTick, p.tickSpacing);
            initTick = lower;
        } else {
            // Token sorts second: open at the upper edge, whole position in token1.
            lower = _align(p.farTick, p.tickSpacing);
            upper = _align(p.startTick, p.tickSpacing);
            initTick = upper;
        }

        poolManager.initialize(key, TickMath.getSqrtPriceAtTick(initTick));

        _pending = Seed({
            key: key,
            tickLower: lower,
            tickUpper: upper,
            token: p.token,
            tokenAmount: held,
            tokenIsToken0: tokenIsToken0
        });

        poolManager.unlock("");

        // Locked in the same transaction that seeded it. No window, no second call.
        lpLocked = true;

        // Whatever the liquidity maths could not place is returned rather than stranded
        // here, where it would be unreachable forever.
        uint256 dust = IERC20(p.token).balanceOf(address(this));
        if (dust > 0) IERC20(p.token).transfer(msg.sender, dust);
    }

    /// @inheritdoc IUnlockCallback
    function unlockCallback(bytes calldata) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert NotPoolManager();
        // The permanence of the lock rests entirely on this line.
        if (lpLocked) revert LPLocked();

        Seed memory d = _pending;
        delete _pending;

        uint128 liquidity = _liquidityForAmount(d.tickLower, d.tickUpper, d.tokenAmount, d.tokenIsToken0);

        (BalanceDelta delta,) = poolManager.modifyLiquidity(
            d.key,
            ModifyLiquidityParams({
                tickLower: d.tickLower,
                tickUpper: d.tickUpper,
                liquidityDelta: int256(uint256(liquidity)),
                salt: bytes32(0)
            }),
            ""
        );

        // Only the token side is owed — the position is one-sided by construction, so
        // there is no WETH leg to settle.
        Currency owedCurrency = d.tokenIsToken0 ? d.key.currency0 : d.key.currency1;
        int128 owed = d.tokenIsToken0 ? -delta.amount0() : -delta.amount1();
        if (owed > 0) {
            poolManager.sync(owedCurrency);
            IERC20(d.token).transfer(address(poolManager), uint256(int256(owed)));
            poolManager.settle();
        }

        emit PoolSeededAndLocked(d.token, address(d.key.hooks), d.tickLower, d.tickUpper, liquidity);
        return "";
    }

    /// @dev Liquidity for a position entirely on one side, priced at that side's edge.
    function _liquidityForAmount(int24 tickLower, int24 tickUpper, uint256 amount, bool isToken0)
        internal
        pure
        returns (uint128)
    {
        uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
        uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);

        if (isToken0) {
            // L = amount0 * (sqrtLower * sqrtUpper / Q96) / (sqrtUpper - sqrtLower)
            //
            // This divided by Q96 TWICE and so returned a value 2^96 too small, which for
            // any realistic amount truncates to zero and reverts the seed with
            // CannotUpdateEmptyPosition. It never showed up because every test happened to
            // have the token sort ABOVE weth, taking the amount1 branch below.
            //
            // Mainnet makes that luck run out: WETH is 0xC02a..., which sorts above most
            // token addresses, so a real launch is likely to be token0 and would have
            // seeded zero liquidity. Found when the token's address moved and flipped the
            // ordering. Pinned by test_liquidityMathIsCorrectForBothOrderings.
            uint256 intermediate = FullMath.mulDiv(uint256(sqrtLower), uint256(sqrtUpper), FixedPoint96.Q96);
            return uint128(FullMath.mulDiv(amount, intermediate, uint256(sqrtUpper) - uint256(sqrtLower)));
        }
        // amount1 = L * (sqrtUpper - sqrtLower) / Q96
        return uint128(amount * FixedPoint96.Q96 / (uint256(sqrtUpper) - uint256(sqrtLower)));
    }

    /// @dev Round toward negative infinity, matching how v4 treats tick boundaries.
    function _align(int24 tick, int24 spacing) internal pure returns (int24) {
        int24 compressed = tick / spacing;
        if (tick < 0 && tick % spacing != 0) compressed--;
        return compressed * spacing;
    }
}
```


## `script/Deploy.s.sol`

Deployment orchestration. Not deployed bytecode, but the ordering it enforces is load-bearing.

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

import {Script, console2} from "forge-std/Script.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {HookMiner} from "v4-periphery/src/utils/HookMiner.sol";

import {DynamicSellTaxHook} from "../src/DynamicSellTaxHook.sol";
import {PressureLauncher} from "../src/PressureLauncher.sol";

/// @title Deploy — the entire PRESSURE launch in ONE transaction
///
/// Sends exactly one transaction: `new PressureLauncher(...)`. That constructor deploys the
/// distributor, the token and the mined hook, wires them, opens the pool, seeds it
/// one-sided and locks the liquidity — indivisibly. See `PressureLauncher` for why atomic.
///
/// # What this script does before broadcasting
///
/// The hook's address has to carry its permission bits in the low 14 bits, so it is CREATE2
/// deployed with a mined salt. The salt is mined against the *launcher's* address, and the
/// hook's constructor arguments include the token and distributor — which the launcher
/// creates. So all three addresses have to be predicted first:
///
///   launcher    = CREATE(msg.sender, current nonce)
///   distributor = CREATE(launcher, 1)   // a contract's nonce starts at 1 (EIP-161)
///   token       = CREATE(launcher, 2)
///
/// `new PressureLauncher(...)` is a plain CREATE from the EOA. Foundry only rewrites
/// `new X{salt: s}` into a call to the deterministic CREATE2 factory, so the launcher's
/// address genuinely depends on the sender's nonce — which is asserted immediately after.
///
/// **The launcher cannot be CREATE2'd instead.** Its address would then depend on its
/// initcode, its initcode contains the hook salt, and the salt is mined against its
/// address. That is circular. Nonce-based CREATE breaks the cycle, at the cost of the
/// launch depending on the sender's nonce — hence the assertion.
///
/// Nothing is at risk if a prediction is wrong: the salt would be wrong, the hook would
/// land at an address without the required bits, and `BaseHook`'s constructor would revert
/// the whole transaction. The launch fails whole rather than half.
///
/// Required env:
///   POOL_MANAGER   v4 PoolManager. Ethereum mainnet: 0x000000000004444c5dc75cB358380D2e3dE08A90
///   WETH           Ethereum mainnet: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
///   TOKEN_NAME, TOKEN_SYMBOL
/// Optional:
///   TREASURY       defaults to the broadcaster, which is the settled configuration.
///   LP_SUPPLY, START_TICK, FAR_TICK, TICK_SPACING, LP_FEE
contract DeployScript is Script {
    struct Cfg {
        address poolManager;
        address weth;
        address treasury;
        string name;
        string symbol;
        uint256 lpSupply;
        int24 startTick;
        int24 farTick;
        int24 tickSpacing;
        uint24 fee;
    }

    function run() external {
        Cfg memory c = _config();

        // Predict, in the exact order PressureLauncher creates them.
        address launcher = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender));
        // Nonce 1, not 2: the separate ReflectionDistributor is gone. The token rebases,
        // so the holders' half is distributed by raising an index rather than by
        // transferring to an accountant contract. That deleted a CREATE and moved the
        // token's address DOWN one slot -- any older prediction of the CA is stale.
        address predToken = vm.computeCreateAddress(launcher, 1);

        bytes32 salt = _mineHook(c, launcher, predToken);

        vm.startBroadcast();
        PressureLauncher launched = new PressureLauncher(
            PressureLauncher.Params({
                poolManager: c.poolManager,
                weth: c.weth,
                treasury: c.treasury,
                name: c.name,
                symbol: c.symbol,
                hookSalt: salt,
                lpSupply: c.lpSupply,
                startTick: c.startTick,
                farTick: c.farTick,
                tickSpacing: c.tickSpacing,
                fee: c.fee
            })
        );
        vm.stopBroadcast();

        // If any of these trip, the prediction model is wrong and the mined salt was
        // meaningless. They cannot trip after a successful launch -- the hook's own
        // constructor would have reverted first -- so they are here to fail loudly during
        // a dry run rather than to guard the broadcast.
        require(address(launched) == launcher, "launcher address mismatch -- was the sender's nonce bumped?");
        require(address(launched.token()) == predToken, "token address mismatch");

        _report(launched, c);
    }

    function _config() private view returns (Cfg memory c) {
        c.poolManager = vm.envAddress("POOL_MANAGER");
        c.weth = vm.envAddress("WETH");
        // Defaults to the broadcaster: the deployer is the treasury. Deliberately not a
        // required variable -- `treasury` is immutable in the hook with no setter, so
        // mistyping it is the one unrecoverable mistake available at launch. Defaulting to
        // the address already signing means there is no address to mistype.
        c.treasury = vm.envOr("TREASURY", msg.sender);
        c.name = vm.envString("TOKEN_NAME");
        c.symbol = vm.envString("TOKEN_SYMBOL");
        // The entire supply. Nothing is held back, so the only way to obtain the token is
        // to buy it from the pool -- which applies to us too, since supply is fixed and
        // there is no mint.
        c.lpSupply = vm.envOr("LP_SUPPLY", uint256(1_000_000_000e18));
        // 198_000 opens at ~2.52 ETH FDV on a 1e27 supply. 191_000 is the ~5.07 ETH
        // alternative. Both align to a 200 spacing.
        c.startTick = int24(vm.envOr("START_TICK", int256(198_000)));
        c.farTick = int24(vm.envOr("FAR_TICK", int256(46_000)));
        c.tickSpacing = int24(vm.envOr("TICK_SPACING", int256(200)));
        c.fee = uint24(vm.envOr("LP_FEE", uint256(10_000)));
        require(c.treasury != address(0), "TREASURY is the zero address");
    }

    /// @dev V4 reads a hook's permissions from the low 14 bits of its address. These five
    ///      callbacks give `0x10CC`:
    ///
    ///        afterInitialize        1 << 12 = 0x1000
    ///        beforeSwap             1 << 7  = 0x0080
    ///        afterSwap              1 << 6  = 0x0040
    ///        beforeSwapReturnDelta  1 << 3  = 0x0008
    ///        afterSwapReturnDelta   1 << 2  = 0x0004
    ///
    ///      Each earns its place. `beforeSwapReturnDelta` can only move the *specified*
    ///      currency and `afterSwapReturnDelta` only the *unspecified* one, and on a sell
    ///      the token and WETH are always on opposite sides of that divide -- so one
    ///      callback takes the holders' half in the token and the other takes the
    ///      treasury's half in WETH, in the same swap. Pressure is accounted in afterSwap
    ///      for every swap, because that is the only place the real token amount is known
    ///      for all four input/output combinations.
    ///
    ///      Mined against the LAUNCHER, not the CREATE2 factory: the launcher is what
    ///      executes the CREATE2. Mining against the wrong deployer is exactly the bug that
    ///      kept the fork test red for a while -- it produced an address whose bits were
    ///      wrong, and `BaseHook` rejected it 1725 gas in.
    function _mineHook(Cfg memory c, address launcher, address token) private view returns (bytes32 salt) {
        uint160 flags = uint160(
            Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG
                | Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG
        );
        bytes memory args = abi.encode(c.poolManager, token, c.weth, c.treasury);
        address predicted;
        (predicted, salt) = HookMiner.find(launcher, flags, type(DynamicSellTaxHook).creationCode, args);
        require(uint160(predicted) & 0x3FFF == 0x10CC, "mined address lacks the required bits");
    }

    function _report(PressureLauncher launched, Cfg memory c) private view {
        console2.log("");
        console2.log("=== PRESSURE deployment (one transaction) ===");
        console2.log("  LAUNCHER=%s", address(launched));
        console2.log("  TOKEN=%s", address(launched.token()));
        console2.log("  HOOK=%s", address(launched.hook()));
        console2.log("  LIQUIDITY_DEPLOYER=%s", address(launched.liquidity()));
        console2.log("  TREASURY=%s", c.treasury);
        console2.log("  WETH=%s", c.weth);
        console2.log("  POOL_MANAGER=%s", c.poolManager);
        console2.log("");
        // %x already prefixes with 0x.
        console2.log("hook address low 14 bits: %x (must be 0x10CC)", uint160(address(launched.hook())) & 0x3FFF);
        console2.log("LP locked      = %s", launched.liquidity().lpLocked());
        console2.log("deployer holds = %s PRESSURE", launched.token().balanceOf(msg.sender));
        console2.log("");
        if (c.treasury == msg.sender) {
            console2.log("TREASURY is the deployer -- it was not set, so it defaulted to the");
            console2.log("broadcasting address. This is the intended configuration.");
        } else {
            console2.log("TREASURY was set explicitly and is NOT the deployer. Check it against");
            console2.log("the address you meant, character by character: it is immutable.");
        }
        console2.log("Either way it is permanent. If it is wrong, nothing can fix it.");
        console2.log("");
        console2.log("The market is open and the system is ownerless. No contract here has an");
        console2.log("owner() to read or a privileged function to call, and the whole launch");
        console2.log("happened inside one transaction -- there was never a moment when the hook");
        console2.log("was live and its pool was unseeded.");
        console2.log("");
        console2.log("The launch dust stays in the launcher, which has no transfer function, so");
        console2.log("the deploying wallet ends holding exactly zero.");
        console2.log("");
        console2.log("Remaining, outside this script: audit. The contracts are unaudited.");
    }
}
```
