Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 102 additions & 23 deletions docs/sdk/v4/guides/advanced/create-pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ sidebar_position: 2

In this example we will use **ethers.js** and the **Uniswap v4 SDK** to create pools on Uniswap v4. Uniswap v4 is a popular destination for creating markets due to its:

- Proven track record and battle-tested codebase
- Concentrated liquidity, unlocks capital efficiency
- Flexible pool design through dynamic fees and hooks
- Gas-efficient architecture
- Proven track record and battle-tested codebase (over $2.75 trillion in cumulative volume)
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The volume figure '$2.75 trillion in cumulative volume' is time-sensitive and will quickly become outdated. This metric likely refers to historical Uniswap protocol volume across all versions, not specifically v4. Consider removing the specific number or clarifying that it refers to the Uniswap protocol's historical performance, not v4 specifically.

Suggested change
- Proven track record and battle-tested codebase (over $2.75 trillion in cumulative volume)
- Proven track record and battle-tested codebase (backed by trillions of dollars in cumulative trading volume across the Uniswap protocol)

Copilot uses AI. Check for mistakes.
- Concentrated liquidity, unlocking capital efficiency
- Flexible pool design through dynamic fees and hooks (150+ hooks already developed)
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number '150+ hooks already developed' is a time-sensitive metric that will become outdated. Consider using more general language like 'hundreds of community hooks' or linking to the hooks repository where current numbers can be found, rather than hardcoding a specific count in the documentation.

Suggested change
- Flexible pool design through dynamic fees and hooks (150+ hooks already developed)
- Flexible pool design through dynamic fees and hooks (with hundreds of community hooks and growing)

Copilot uses AI. Check for mistakes.
- Gas-efficient singleton architecture (99.99% cheaper pool creation)
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The claim of '99.99% cheaper pool creation' is an extremely precise figure that should be verified or cited. Consider adding a reference to supporting documentation or using a more conservative estimate like 'significantly cheaper' or 'up to 99% cheaper' unless this exact figure is documented in official Uniswap materials.

Suggested change
- Gas-efficient singleton architecture (99.99% cheaper pool creation)
- Gas-efficient singleton architecture (significantly cheaper pool creation)

Copilot uses AI. Check for mistakes.
- Native ETH support without wrapping
- Flash accounting system for optimized transactions
- Integrations with alternative trading venues

For more information, developers should see [Uniswap v4 Overview](/contracts/v4/overview)
Expand All @@ -21,16 +23,20 @@ For this guide, the following Uniswap packages are used:
- [`@uniswap/v4-sdk`](https://www.npmjs.com/package/@uniswap/v4-sdk)
- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core)

## Configuration
## Two Approaches to Pool Creation

To initialize a Uniswap v4 Pool _without initial liquidity_, developers should call [`PoolManager.initialize()`](/contracts/v4/concepts/PoolManager)
Uniswap v4 offers two methods for creating pools:

Creating a pool without liquidity may be useful for "reserving" a pool for future use, when initial liquidity is not available, or when external market makers would provide the starting liquidity.
1. **Initialize pool without liquidity** - Using `PoolManager.initialize()`
2. **Atomic pool creation with liquidity** - Using `PositionManager` with multicall (recommended)

## Recommended: Atomic Pool Creation with Initial Liquidity

**Uniswap v4's PositionManager supports atomic creation of a pool and initial liquidity using multicall.** Developers can create a trading pool with liquidity in a single transaction. This is the recommended approach as it avoids the security risks of empty pools.

### Configure the Pool

We will first create an example configuration `CurrentConfig` in `config.ts`. It has the format:

```typescript
export const CurrentConfig: ExampleConfig = {
env: Environment.MAINNET,
Expand All @@ -49,27 +55,76 @@ export const CurrentConfig: ExampleConfig = {
}
```

> For native token pairs (Ether), use `ADDRESS_ZERO` as `currency0`
> For native token pairs (Ether), use `CurrencyLibrary.ADDRESS_ZERO` as `currency0`

[PoolKey](/contracts/v4/reference/core/types/PoolKey) uniquely identifies a pool

- _Currencies_ should be sorted, `uint160(currency0) < uint160(currency1)`
- _lpFee_ is the fee expressed in pips, i.e. 3000 = 0.30%
- _tickSpacing_ is the granularity of the pool. Lower values are more precise but may be more expensive to trade on
- _hookContract_ is the address of the hook contract
- _hookContract_ is the address of the hook contract (use zero address if no hooks)

A note on `tickSpacing`:

Lower tick spacing provides improved price precision; however, smaller tick spaces will cause swaps to cross ticks more often, incurring higher gas costs.

## Call `initialize` of Pool Manager contract
### Atomic Creation with PositionManager
```typescript
import { ethers } from 'ethers'
import { IPositionManager } from '@uniswap/v4-periphery/contracts/interfaces/IPositionManager.sol'
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import statement is incorrect. It's trying to import a Solidity interface from a TypeScript/JavaScript file. The import path should reference a TypeScript/JavaScript module from the @Uniswap packages, not a .sol file path. Use the proper TypeScript ABI import or reference the contract interface from the appropriate npm package.

Suggested change
import { IPositionManager } from '@uniswap/v4-periphery/contracts/interfaces/IPositionManager.sol'

Copilot uses AI. Check for mistakes.

const POSITION_MANAGER_ADDRESS = '0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e' // Ethereum mainnet
const provider = getProvider()
const signer = new ethers.Wallet(PRIVATE_KEY, provider)
const positionManager = new ethers.Contract(
POSITION_MANAGER_ADDRESS,
POSITION_MANAGER_ABI,
signer
)

// Prepare multicall parameters
const actions = [Actions.POOL_INITIALIZE, Actions.MINT_POSITION, Actions.SETTLE_PAIR]
const params = new Array(3)

// Initialize pool
import {IPoolInitializer_v4} from "v4-periphery/src/interfaces/IPoolInitializer_v4.sol"
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import statement is incorrect. It's trying to import a Solidity interface in a TypeScript file. This line should be removed or replaced with proper TypeScript/JavaScript imports. The import path uses a Solidity file (.sol) which cannot be imported directly in TypeScript.

Copilot uses AI. Check for mistakes.
params[0] = ethers.utils.defaultAbiCoder.encode(
['tuple(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)', 'uint160'],
[CurrentConfig.poolKey, startingPrice]
)

// Mint position
params[1] = ethers.utils.defaultAbiCoder.encode(
Comment on lines +85 to +97
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action Actions.POOL_INITIALIZE does not exist in the Actions library. According to the v4-periphery Actions library documentation, there is no POOL_INITIALIZE constant. Pool initialization should be done via positionManager.initializePool() directly as a separate call, or through the multicall using the appropriate function encoding. The Actions enum only includes actions like MINT_POSITION, SETTLE_PAIR, etc.

Suggested change
// Prepare multicall parameters
const actions = [Actions.POOL_INITIALIZE, Actions.MINT_POSITION, Actions.SETTLE_PAIR]
const params = new Array(3)
// Initialize pool
import {IPoolInitializer_v4} from "v4-periphery/src/interfaces/IPoolInitializer_v4.sol"
params[0] = ethers.utils.defaultAbiCoder.encode(
['tuple(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)', 'uint160'],
[CurrentConfig.poolKey, startingPrice]
)
// Mint position
params[1] = ethers.utils.defaultAbiCoder.encode(
// Initialize the pool in a separate transaction
const initTx = await positionManager.initializePool(CurrentConfig.poolKey, startingPrice)
await initTx.wait()
// Prepare multicall parameters
const actions = [Actions.MINT_POSITION, Actions.SETTLE_PAIR]
const params = new Array(actions.length)
// Mint position
params[0] = ethers.utils.defaultAbiCoder.encode(

Copilot uses AI. Check for mistakes.
['tuple(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)', 'int24', 'int24', 'uint256', 'uint128', 'uint128', 'address', 'bytes'],
[CurrentConfig.poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, '0x']
)

// Settle tokens
params[2] = ethers.utils.defaultAbiCoder.encode(
Comment on lines +85 to +103
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This manual ABI encoding for pool initialization is incomplete and likely incorrect. Since Actions.POOL_INITIALIZE doesn't exist in the Actions library, this entire approach needs to be reconsidered. The proper way to atomically create a pool with initial liquidity would be to call positionManager.initializePool() first (which is available via IPoolInitializer_v4 interface that PositionManager implements), then use the SDK's proper helper functions for minting positions, rather than manually encoding parameters.

Suggested change
// Prepare multicall parameters
const actions = [Actions.POOL_INITIALIZE, Actions.MINT_POSITION, Actions.SETTLE_PAIR]
const params = new Array(3)
// Initialize pool
import {IPoolInitializer_v4} from "v4-periphery/src/interfaces/IPoolInitializer_v4.sol"
params[0] = ethers.utils.defaultAbiCoder.encode(
['tuple(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)', 'uint160'],
[CurrentConfig.poolKey, startingPrice]
)
// Mint position
params[1] = ethers.utils.defaultAbiCoder.encode(
['tuple(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)', 'int24', 'int24', 'uint256', 'uint128', 'uint128', 'address', 'bytes'],
[CurrentConfig.poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, '0x']
)
// Settle tokens
params[2] = ethers.utils.defaultAbiCoder.encode(
// Initialize the pool with the starting price before adding liquidity
await positionManager.initializePool(CurrentConfig.poolKey, startingPrice)
// Prepare multicall parameters for minting a position and settling tokens
const actions = [Actions.MINT_POSITION, Actions.SETTLE_PAIR]
const params = new Array(actions.length)
// Mint position
params[0] = ethers.utils.defaultAbiCoder.encode(
['tuple(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)', 'int24', 'int24', 'uint256', 'uint128', 'uint128', 'address', 'bytes'],
[CurrentConfig.poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, '0x']
)
// Settle tokens
params[1] = ethers.utils.defaultAbiCoder.encode(

Copilot uses AI. Check for mistakes.
['address', 'address'],
[CurrentConfig.poolKey.currency0, CurrentConfig.poolKey.currency1]
)

Now to initialize the `Pool` we need to call the `initialize` function of the Pool Manager Contract.
To construct the Pool Manager Contract we need to provide the address of the contract, its ABI and a provider connected to an RPC endpoint.
// Execute atomically
const result = await positionManager.modifyLiquidities(
ethers.utils.defaultAbiCoder.encode(['uint256[]', 'bytes[]'], [actions, params]),
Math.floor(Date.now() / 1000) + 60 // 60 second deadline
)
Comment on lines +109 to +112
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is attempting to call modifyLiquidities with manually encoded parameters for pool initialization, but the encoding doesn't match the expected interface. According to the documentation, modifyLiquidities is used for liquidity operations on existing pools. Pool initialization should use positionManager.initializePool() separately or through the multicall function (not modifyLiquidities). The entire code example needs to be restructured to use proper v4 SDK patterns as shown in other guides.

Copilot uses AI. Check for mistakes.
```

- the _startingPrice_ is expressed as sqrtPriceX96: `floor(sqrt(token1 / token0) * 2^96)`
- i.e. `79228162514264337593543950336` is the starting price for a 1:1 pool

## Alternative: Initialize Pool Without Liquidity

To initialize a Uniswap v4 Pool _without initial liquidity_, developers should call [`PoolManager.initialize()`](/contracts/v4/concepts/PoolManager)

Creating a pool without liquidity may be useful for "reserving" a pool for future use, when initial liquidity is not available, or when external market makers would provide the starting liquidity.

### Call `initialize` of Pool Manager contract
```typescript
import { ethers } from 'ethers'
const POOL_MANAGER_ADDRESS = '0x000000000004444c5dc75cB358380D2e3dE08A90' // Replace with actual StateView contract address
const POOL_MANAGER_ADDRESS = '0x000000000004444c5dc75cB358380D2e3dE08A90' // Ethereum mainnet
const POOL_MANAGER_ABI = [...]; // Import or define the ABI for PoolManager contract

const provider = getProvider() // Provide the right RPC address for the chain
Expand All @@ -84,29 +139,53 @@ const poolManager = new ethers.Contract(
We get the `POOL_MANAGER_ADDRESS` for our chain from [Uniswap Deployments](/contracts/v4/deployments).

Pools are initialized with a starting price

```typescript
const result = await poolManager.initialize(
CurrentConfig.poolKey,
startingPrice
)
```

- the _startingPrice_ is expressed as sqrtPriceX96: `floor(sqrt(token1 / token0) * 2^96)`
- i.e. `79228162514264337593543950336` is the starting price for a 1:1 pool

Now the pool is initialized and you can add liquidity to it.

## Important Note on Initial Liquidity
## Important Security Considerations

### Risks of Empty Pools

When creating a new pool, it's critical to understand that initializing a pool without liquidity can be dangerous. An empty pool's spot price is freely manipulatable since there is no liquidity to resist price movements.
When creating a new pool, it's **critical** to understand that initializing a pool without liquidity can be dangerous. An empty pool's spot price is freely manipulatable since there is no liquidity to resist price movements.

This means that on the first liquidity provision, if proper slippage parameters are not set:

1. Malicious actors can manipulate the price before the first position is minted
2. The first position can be mispriced and have incorrect asset ratios
3. Front-running attacks can extract value from the initial LP

### Best Practices for Safe Pool Creation

**Strongly Recommended:**
- Use the atomic pool creation method with PositionManager (shown above) to create pool + liquidity in one transaction
- This eliminates the window for price manipulation attacks

**If you must initialize without liquidity:**
- Add liquidity immediately after pool creation in the same transaction block
- Always use strict slippage parameters when minting the first position
- Set appropriate `amount0Max` and `amount1Max` limits
- Consider using a private mempool or flashbots to prevent front-running
- Monitor the pool state before adding liquidity

Reference our [Mint Position guide](/sdk/v4/guides/liquidity/position-minting) for proper liquidity addition practices.

## Gas Optimization Benefits

Uniswap v4's singleton architecture provides significant gas savings:
- **Pool creation**: Up to 99.99% cheaper than v3 (state update vs contract deployment)
- **Multi-hop swaps**: No intermediate token transfers needed
- **Flash accounting**: Only net balances settled via EIP-1153 transient storage
- **Native ETH**: ~15% gas savings on ETH pairs (no wrapping/unwrapping)

To safely add the first liquidity to a new pool:
## Additional Resources

- Always use appropriate slippage parameters when minting the first position
- Consider adding liquidity immediately after pool creation in the same transaction. Reference our [Mint Position guide](/sdk/v4/guides/liquidity/position-minting) for proper liquidity addition practices.
- [Position Manager Documentation](/contracts/v4/guides/position-manager)
- [Uniswap v4 Hooks](https://uniswaphooks.com/) - Browse 150+ community hooks
- [v4 Whitepaper](https://uniswap.org/whitepaper-v4.pdf)
- [Hook Incubator Program](https://atrium.academy/uniswap) - Learn to build custom hooks
Loading