User journeysInitialize a collection

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 _tokens NFTs from _collection and has approved the Locker for transfer (setApprovalForAll).
  • The caller holds at least _eth of NFTXV4Hook.nativeToken() (typically flETH) and has approved the Locker for that amount.
  • The _recipient of 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

ParamTypeMeaning
_collectionaddressERC-721 contract
_ethuint256Amount of nativeToken to seed
_tokenIdsuint256[]NFT tokenIds to deposit (1 NFT mints 1e18 CollectionToken)
_tokenSlippageuint256Max slippage on the underlying token side
_sqrtPriceX96uint160Initial pool price (V4 sqrt-price encoding)
_recipient (hook side)addressLP 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

#CallerCallNotes
1NFT owner<ERC721>.setApprovalForAll(locker, true)One-shot per collection
2NFT owner<nativeToken>.approve(locker, _eth)Or use Permit2 flow
3NFT ownerLocker.initializeCollection(_collection, _eth, _tokenIds, _tokenSlippage, _sqrtPriceX96)Locker forwards the seed liquidity to NFTXV4Hook.initializeCollection

Events emitted (in order):

  1. Locker.TokenDeposit(collection, tokenIds, sender, locker) (deposit leg).
  2. NFTXV4Hook.PoolStateUpdated(collection, sqrtPriceX96, tick, protocolFee, swapFee, liquidity).
  3. 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

ErrorSourceCause
CollectionDoesNotExistLockerCollection wasn’t created first
CollectionAlreadyInitializedLockerAlready initialized
NoTokenIdsLockerEmpty _tokenIds array
LPRecipientIsZeroAddressINFTXV4HookHook saw a zero recipient
CannotBeInitializedDirectlyINFTXV4HookThe V4 PoolManager.initialize was called outside this flow
Paused()LockerProtocol paused

Subgraph follow-through

The existing Collection row is patched in place:

  • poolKey becomes the encoded V4 PoolKey bytes.
  • sqrtPriceX96 / tick / liquidity / swapFee / protocolFee populate.
  • A new ListingActivity row is written for each deposited tokenId with activityType: 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
  }
}