> For the complete documentation index, see [llms.txt](https://docs.revert.finance/revert/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.revert.finance/revert/technical-docs/stable-hooks/deploying-a-pool.md).

# Deploying a pool

Stable Hooks pools are permissionless. Anyone can deploy one by calling the factory on-chain: there is no allowlist and no approval step. `StableSwapHooksFactory.deploy(...)` is `whenNotPaused` with no owner gate, so any address can call it while the factory is unpaused.

There is no UI for this today, so deploying a pool is a contract-level operation aimed at developers and issuers. This page documents the full path. The factory address is on the [Contract Addresses](/revert/resources/contract-addresses.md) page.

{% hint style="info" %}
The reference implementation of everything below is `script/DeployStableSwapPool.s.sol` (a Foundry script) and `script/hookMiner.demo.js` (a standalone salt miner) in [github.com/revert-finance/stableswap-hooks](https://github.com/revert-finance/stableswap-hooks).
{% endhint %}

## Chains

The factory is deployed on **Base** only (address on the [Contract Addresses](/revert/resources/contract-addresses.md) page). Deploying a pool requires the factory, so pools can currently be created on Base only. To bring Stable Hooks to another chain, reach out on [Discord](https://discord.gg/HXfxKHrRmf); we deploy and configure the factory per chain.

## What you control, and what you don't

You set these at deploy time, and they are **immutable** for the life of the pool:

* the **currency set** (2 to 4 correlated assets),
* the **LP fee** charged to traders,
* the **amplification** coefficient (A),
* the per-currency **rate oracles**.

Two things are administered by the factory owner, not the deployer:

* **The hook/protocol fee split** (`setProtocolFeePercentage`, `setHookFeePercentage`) is `onlyFactoryOwner`. On a self-deployed pool these default to `0`, so the entire net LP fee accrues to LPs. The trader-facing LP fee you set at deploy is immutable and is never changed by this split. This mirrors the behavior described under [Integrating swaps → Stability guarantees](/revert/technical-docs/stable-hooks/integrating-swaps.md).
* **Amplification ramping** (`startAmpRamp`, `stopAmpRamp`) is `onlyFactoryOwner`. Your initial `baseAmp` is fixed at deploy; only the factory owner can later ramp it, and only gradually (see limits below).

## Parameters

| Parameter         | Type                 | Rules                                                                                                                                                                                                                               |
| ----------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `currencies`      | `Currency[]`         | 2 to 4 entries (`MIN_CURRENCIES`..`MAX_CURRENCIES`), sorted strictly ascending by address. Native ETH is `address(0)`.                                                                                                              |
| `rateOracles`     | `RateOracleConfig[]` | One per currency (length must match). `{oracle: address(0), selector: 0x00000000}` for a static 1:1 asset. For a yield-bearing asset, point at its rate function (e.g. wstETH `stEthPerToken()`) so it is priced at its true ratio. |
| `lpFeePercentage` | `uint256`            | Trader-facing LP fee, scaled by `FEE_PRECISION = 1e6`. Must be `0 < fee < 1e6`. Example: `500` = 0.05%. Stored in `PoolKey.fee`.                                                                                                    |
| `baseAmp`         | `uint256`            | Initial amplification, unscaled. Must be `< MAX_AMP` (1,000,000); use a non-zero value. Higher A prices tighter around the reference ratio.                                                                                         |

## Deploy flow

### 1. Verify the creation bytecode

The factory validates that the creation code you pass hashes to a stored value, so you must use the exact `StableSwapHooks` creation bytecode:

```solidity
bytes memory creationCode = type(StableSwapHooks).creationCode;
require(keccak256(creationCode) == factory.creationCodeHash(), "hash mismatch");
```

### 2. Build the constructor args

The factory appends `abi.encode(poolManager, currencies, rateOracles, lpFeePercentage, baseAmp)` to the creation code. Use the factory's own `poolManager()`:

```solidity
bytes memory constructorArgs = abi.encode(
    factory.poolManager(), currencies, rateOracles, lpFeePercentage, baseAmp
);
```

### 3. Mine a CREATE2 salt

A Uniswap v4 hook address must encode its permission flags in its low 14 bits. Stable Hooks uses `factory.HOOK_FLAGS()` (`0x2AA8` = 10920: `BEFORE_INITIALIZE`, `BEFORE_ADD_LIQUIDITY`, `BEFORE_REMOVE_LIQUIDITY`, `BEFORE_SWAP`, `BEFORE_SWAP_RETURNS_DELTA`, `BEFORE_DONATE`). Mine a salt whose predicted CREATE2 address matches. This is pure off-chain computation and costs no gas.

```solidity
// Foundry: @uniswap/v4-periphery/src/utils/HookMiner.sol
(address predicted, bytes32 salt) =
    HookMiner.find(address(factory), factory.HOOK_FLAGS(), creationCode, constructorArgs);
```

`script/hookMiner.demo.js` does the same in standalone JavaScript (mask `0x3fff`, deployer = the factory address) if you are not using Foundry.

### 4. Deploy

```solidity
address hook = factory.deploy(
    currencies, rateOracles, lpFeePercentage, baseAmp, salt, creationCode
);
```

This deploys the hook via CREATE2 and initializes **every pairwise pool** for the currency set (`n * (n - 1) / 2` pools), all sharing one set of reserves. It emits `StableSwapHooksDeployed(msg.sender, hook)`, which integrators watch for discovery (see [Integrating swaps → Pool discovery](/revert/technical-docs/stable-hooks/integrating-swaps.md)).

### 5. Seed initial liquidity

The pool is live but empty. Add the first liquidity so swaps can execute. Approve the tokens to the hook, preview with `quoteAddLiquidity`, then deposit:

```solidity
// amounts are proportional across all pool currencies
(uint256 shares, uint256[] memory used) = hook.quoteAddLiquidity(amounts);
hook.addLiquidity(amounts, minAmounts, minShares);
```

The first deposit permanently locks `MINIMUM_LIQUIDITY` (1000) shares to a dead address to prevent share-price manipulation. Net LP fees stay in reserves, so fees compound into every LP share automatically; there is no claim step. Withdraw with `removeLiquidity(shares, minAmounts)`.

## Foundry one-liner

The reference script mines the salt, deploys, and (as factory owner) configures the fee split:

```bash
forge script script/DeployStableSwapPool.s.sol:DeployStableSwapPool \
  --rpc-url base --broadcast --verify -vvvv \
  --sig "run(address)" <FACTORY_ADDRESS>
```

Edit the currency addresses, `LP_FEE_PERCENTAGE`, and `BASE_AMP` constants in the script for your pool. The `setProtocolFeePercentage`/`setHookFeePercentage` calls at the end only succeed if the sender is the factory owner; drop them if you are deploying a third-party pool (the split then stays at 0).

## Common reverts

| Revert                     | Cause                                                                                    |
| -------------------------- | ---------------------------------------------------------------------------------------- |
| `CurrenciesNotSorted`      | `currencies` not strictly ascending by address                                           |
| `InvalidCurrenciesLength`  | fewer than 2 or more than 4 currencies                                                   |
| `InvalidFeePercentage`     | `lpFeePercentage` is 0 or `>= 1e6`                                                       |
| `InvalidRateOraclesLength` | `rateOracles.length != currencies.length`                                                |
| `InvalidCreationCode`      | `keccak256(creationCode) != factory.creationCodeHash()`                                  |
| `InvalidAmp`               | `baseAmp >= MAX_AMP` (1,000,000)                                                         |
| `EnforcedPause`            | the factory is paused                                                                    |
| hook address mismatch      | the mined salt does not encode `HOOK_FLAGS`; re-mine against the exact `constructorArgs` |

## Amplification ramping (factory owner)

`baseAmp` is fixed at deploy. Changing it later is a factory-owner action and is rate-limited for pool safety: at most a 10x change (`MAX_AMP_MULTIPLIER`) per ramp, at least `MIN_AMP_RAMP_TIME` (1 day) between ramps, and the ramp end must be at least 1 day out. `getCurrentAmp()` returns the interpolated value during a ramp.
