Create a collection
Registers a new ERC-721 collection on the Locker, deploying a paired
CollectionToken ERC-20 clone in the same call. The collection is not yet
tradable — pool initialization is a separate transaction
(Initialize a collection).
Prerequisites
- The collection address is a real ERC-721. The Locker calls
supportsInterface(0x80ac58cd)and rejects anything that doesn’t claim it. - The collection has never been sunset on this Locker. Sunsetted
addresses are permanently rejected (
CollectionPreviouslySunset). - If
Locker.permissionlessCreateCollection() == false, the caller is the Locker owner. msg.value == Locker.collectionCreationFee()exactly. Both0and non-zero values are valid; non-matching values revert withCollectionCreationFeeDoesNotMatch.- The Locker is not paused (
Locker.paused() == false).
Inputs
| Param | Type | Meaning |
|---|---|---|
_collection | address | ERC-721 contract address |
_name | string | Display name for the new CollectionToken |
_symbol | string | Symbol for the new CollectionToken |
Contract calls
| # | Caller | Call | Notes |
|---|---|---|---|
| 1 | Anyone (or owner) | Locker.createCollection{value: fee}(collection, name, symbol) | Returns the deployed CollectionToken address |
Events emitted in order:
Locker.CollectionCreated(collection, collectionToken, name, symbol, creator)- The Locker forwards
(collection, collectionToken)toNFTXV4Hook.registerCollection(collection, collectionToken)(no event).
Code
createCollection.ts
import { lockerAbi } from "@nftx/contracts";
import { NFTX_CONTRACTS } from "@nftx/config";
import {
createPublicClient,
createWalletClient,
custom,
http,
parseEther,
} from "viem";
import { base } from "viem/chains";
const publicClient = createPublicClient({ chain: base, transport: http() });
const walletClient = createWalletClient({
chain: base,
transport: custom(window.ethereum!),
});
const [account] = await walletClient.getAddresses();
const locker = NFTX_CONTRACTS[base.id].locker;
const fee = await publicClient.readContract({
address: locker,
abi: lockerAbi,
functionName: "collectionCreationFee",
});
const { request } = await publicClient.simulateContract({
account,
address: locker,
abi: lockerAbi,
functionName: "createCollection",
args: ["0xCollection", "Cool Cats Floor", "fCATS"],
value: fee,
});
const txHash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });Failure modes
| Error | Cause | Resolution |
|---|---|---|
InvalidERC721 | Address doesn’t return true for supportsInterface(0x80ac58cd) | Confirm you’re passing the NFT contract, not the OpenSea proxy |
CollectionAlreadyExists | _collection already registered | Look up the existing CollectionToken via Locker.collectionToken(_collection) |
CollectionPreviouslySunset | Address was sunset previously | Migrate to a new ERC-721 address |
CreateCollectionRestrictedToOwner | permissionlessCreateCollection == false and caller isn’t owner | Wait for the toggle, or ask the owner |
CollectionCreationFeeDoesNotMatch(received, required) | msg.value differs from collectionCreationFee | Re-read the fee just before sending |
Paused() | Locker is paused | Retry once unpaused |
⚠️
createCollection is not idempotent — the receipt’s deployed
CollectionToken address changes per-call. Index Locker.CollectionCreated
rather than re-deriving the clone address.
Subgraph follow-through
A Collection row appears in the subgraph keyed by
${chainId}-${collection}. Until Initialize a
collection succeeds:
poolKey,sqrtPriceX96,tick,liquidity,swapFee,protocolFeeremain null/zero.defaultFeeis theConfig.defaultFeefor the chain.publicListings == 0.
query NewCollection($chainId: Int!, $collection: String!) {
Collection(
where: {
chainId: { _eq: $chainId }
collection: { _eq: $collection }
}
limit: 1
) {
id
collectionToken
name
symbol
creator
createdAt
poolKey # null until initializeCollection
}
}