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 page.
Chains
The factory is deployed on Base only (address on the Contract Addresses 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; 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) isonlyFactoryOwner. On a self-deployed pool these default to0, 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.Amplification ramping (
startAmpRamp,stopAmpRamp) isonlyFactoryOwner. Your initialbaseAmpis fixed at deploy; only the factory owner can later ramp it, and only gradually (see limits below).
Parameters
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:
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():
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.
script/hookMiner.demo.js does the same in standalone JavaScript (mask 0x3fff, deployer = the factory address) if you are not using Foundry.
4. Deploy
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).
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:
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:
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
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.
Last updated