Initialize a collection
Bootstraps the Uniswap V4 pool for a CollectionToken paired against the
hook’s nativeToken(). After this transaction the collection is fully
tradable — listings can be created, deposits/redeems work, and swaps route
through the V4 PoolManager.
Prerequisites
- The collection has been registered.
- It has not already been initialized (
Locker.collectionInitialized(_collection) == false). - The caller holds at least
_tokensNFTs from_collectionand has approved theLockerfor transfer (setApprovalForAll). - The caller holds at least
_ethofNFTXV4Hook.nativeToken()(typically flETH) and has approved theLockerfor that amount. - The
_recipientof the LP NFT is non-zero. The LP NFT is minted by the canonical V4 PositionManager; whoever holds it controls the seeded liquidity. - The Locker is not paused.
Inputs
| Param | Type | Meaning |
|---|---|---|
_collection | address | ERC-721 contract |
_eth | uint256 | Amount of nativeToken to seed |
_tokenIds | uint256[] | NFT tokenIds to deposit (1 NFT mints 1e18 CollectionToken) |
_tokenSlippage | uint256 | Max slippage on the underlying token side |
_sqrtPriceX96 | uint160 | Initial pool price (V4 sqrt-price encoding) |
_recipient (hook side) | address | LP NFT recipient |
Locker.initializeCollection accepts the first 5 params; the LP recipient
is set by the Locker itself when it calls through to
NFTXV4Hook.initializeCollection. The recipient defaults to msg.sender
in normal flows; if you need a different recipient, mint via the hook
directly (admin-only path).
Contract calls
| # | Caller | Call | Notes |
|---|---|---|---|
| 1 | NFT owner | <ERC721>.setApprovalForAll(locker, true) | One-shot per collection |
| 2 | NFT owner | <nativeToken>.approve(locker, _eth) | Or use Permit2 flow |
| 3 | NFT owner | Locker.initializeCollection(_collection, _eth, _tokenIds, _tokenSlippage, _sqrtPriceX96) | Locker forwards the seed liquidity to NFTXV4Hook.initializeCollection |
Events emitted (in order):
Locker.TokenDeposit(collection, tokenIds, sender, locker)(deposit leg).NFTXV4Hook.PoolStateUpdated(collection, sqrtPriceX96, tick, protocolFee, swapFee, liquidity).Locker.CollectionInitialized(collection, poolKey, tokenIds, sqrtPriceX96, sender).
Code
initializeCollection.ts
import { lockerAbi } from "@nftx/contracts";
import { NFTX_CONTRACTS } from "@nftx/config";
import { encodeFunctionData, parseEther } from "viem";
const collection = "0xCollection";
const tokenIds = [1n, 2n, 3n].map((id) => id);
const ethSeed = parseEther("3"); // 3 flETH for 3 NFTs (1.0 floor)
const tokenSlippage = parseEther("0.05"); // 5% on the token leg
const sqrtPriceX96 = 79228162514264337593543950336n; // 1.0
await walletClient.writeContract({
address: NFTX_CONTRACTS[base.id].locker,
abi: lockerAbi,
functionName: "initializeCollection",
args: [collection, ethSeed, tokenIds, tokenSlippage, sqrtPriceX96],
});Failure modes
| Error | Source | Cause |
|---|---|---|
CollectionDoesNotExist | Locker | Collection wasn’t created first |
CollectionAlreadyInitialized | Locker | Already initialized |
NoTokenIds | Locker | Empty _tokenIds array |
LPRecipientIsZeroAddress | INFTXV4Hook | Hook saw a zero recipient |
CannotBeInitializedDirectly | INFTXV4Hook | The V4 PoolManager.initialize was called outside this flow |
Paused() | Locker | Protocol paused |
Subgraph follow-through
The existing Collection row is patched in place:
poolKeybecomes the encoded V4PoolKeybytes.sqrtPriceX96/tick/liquidity/swapFee/protocolFeepopulate.- A new
ListingActivityrow is written for each deposited tokenId withactivityType: DEPOSITED.
query InitializedCollection($chainId: Int!, $collection: String!) {
Collection(
where: { chainId: { _eq: $chainId }, collection: { _eq: $collection } }
limit: 1
) {
id
poolKey
sqrtPriceX96
tick
liquidity
swapFee
publicListings
updatedAt
}
}