Collections and contracts

How an NFT collection is created, approved and deployed — the PENDING gate, the symbol uniqueness rule, the royalty clamp, what deploying an ERC-721 contract actually costs, and why an undeployed collection blocks nearly everything.

6 min readUpdated 3 August 2026collections, erc721, erc1155, deployment, royalties

A collection is two things at once: a database row that groups tokens for browsing, and — after a separate deployment step — a real ERC-721 or ERC-1155 contract on a chain. Almost every complaint an operator receives about this product comes from the gap between those two states.

The lifecycle

DRAFT ──▶ PENDING ──▶ ACTIVE ──▶ (deployed) ──▶ mintable
              │           │
              │           └──▶ SUSPENDED   (admin disabled it)
              └──▶ INACTIVE

nft_collection.status is an enum of DRAFT, PENDING, ACTIVE, INACTIVE and SUSPENDED. A user-created collection is always written PENDING — that is hardcoded in the create route, not a setting — and only an admin can move it on.

Creating a collection

Users go to /nft/collection/create. The form is gated by the create_nft KYC feature, so if you have bound that feature to a KYC level an unverified user sees the gate rather than the form.

Creates a collection in PENDING status

The required fields are name, symbol, categoryId, chain, network and standard. Everything else — description, logo, banner, max supply, mint price, currency, royalty, public flag — is optional.

The validation rules that surprise people

Names only have to be unique per creator — two different creators can both have a collection called "Genesis". Symbols are checked across the entire marketplace and a clash returns 409 with the message "Token symbols must be unique across the marketplace".

The format is also strict: 1–10 characters, uppercase alphanumerics plus $. Lowercase input is upper-cased for you; anything else is rejected.

Creation looks up nft_marketplace for an ACTIVE row matching both chain and network. No row, no collection — the error names the chain and network it could not find.

This is why deploying a marketplace contract is the first real step of an install. It is also why a chain silently disappearing from the create form usually means somebody redeployed with force: true and the old row went DEPRECATED.

The request schema advertises royaltyPercentage up to 50. The handler ignores that and clamps to nftMaxRoyaltyPercentage (default 10), rejecting anything above it.

That is deliberate. When the two disagreed, a collection created above the platform maximum showed one royalty to buyers and was silently clamped to a different one at settlement, so the creator was quoted earnings they would never receive.

Letters, digits, spaces, hyphens, underscores, apostrophes and full stops, up to 255 characters. Emoji and most punctuation are rejected outright. The value is then HTML-escaped before it is stored, so &, < and / come back as entities in the database.

logoImage and bannerImage must match https://… or /uploads/…. A bare filename fails validation.

The creator profile is created for you

If the user has no nft_creator row, one is created during collection creation using their real name from the user table, or creator_<first 8 chars of id> when they have no name set. It starts isVerified: false and profilePublic: true.

Note that nft_collection.creatorId points at nft_creator.id, not at user.id. Anything that needs to pay the creator — the royalty leg of a settlement, for instance — has to resolve through nft_creator.userId to reach a real account.

Approving a collection

Nobody can mint into a PENDING collection. The mint route checks status !== "ACTIVE" and refuses with the current status in the message.

Go to Admin → NFT → Content → Collections (/admin/nft/collection).

Enable or disable a collection

The endpoint takes a boolean, not a status string: true sets ACTIVE, false sets SUSPENDED. There is no way from this control to reach DRAFT, PENDING or INACTIVE again.

The admin Collections table is deliberately read-mostly — create and delete are both switched off in the UI, because collections belong to their creators. Editing metadata and flipping status are the two things an admin does here.

Edit collection metadata as an admin

Verification is separate from approval. nft_collection.isVerified is what the onboarding checklist counts as "featured content", and it is what puts the badge on a collection in the marketplace.

Deploying the contract

Approval makes a collection browsable. It does not put anything on a chain. A separate deployment writes contractAddress onto the row and flips its status to ACTIVE.

Deploys an ERC-721 or ERC-1155 contract for a collection

This is gated by the deploy_nft_contract KYC feature.

What it does, in order:

  1. Resolves the caller's creator profile and confirms the collection belongs to it. A collection you do not own returns 404, not 403.

  2. Refuses a second deployment. If contractAddress is already set you get 400 — there is no redeploy path for a collection.

  3. Loads the Ecosystem master wallet for the chain, with status enabled. No wallet, no deployment.

  4. Signs and broadcasts with the master wallet's decrypted private key, using the ERC721NFT or ERC1155NFT artifact.

  5. Writes the result back: contractAddress, chain, standard, maxSupply, royaltyPercentage, mintPrice, deployedAt, and status: "ACTIVE".

  6. Records an activity row of type COLLECTION_CREATED with a CONTRACT_DEPLOYMENT action in its metadata, carrying the gas used and the deployment cost.

Who owns the deployed contract

The contract's owner and its royalty recipient are both set to the creator's linked wallet address. If the creator has no walletAddress on their user row, both fall back to the master wallet — which means the platform, not the creator, owns the contract and collects its on-chain royalties, permanently, with no way to change it afterwards.

There is no check for this. A creator with no linked address gets a successful deployment whose royalties are hard-wired to your master wallet. The only fix is a new collection with a new contract.

Linking is also harder than it looks — see the wallet-address entry in Troubleshooting.

Gas comes out of your master wallet, per collection

Every collection contract is a separate deployment paid by the platform's master wallet. On a busy marketplace this is a real recurring cost that scales with the number of creators, and nothing in the product bills it back to them. Watch the balance on the Marketplace screen; a drained master wallet fails deployments with an RPC error rather than a clear message.

Recording an externally deployed contract

If a contract was deployed outside the platform there is a route to attach it:

Attaches an already-deployed contract to a collection

What an undeployed collection cannot do

This is the list worth pinning up, because each of these is a support ticket:

Action Behaviour without contractAddress
Mint a token 400 — "Collection contract not deployed"
List a token at a fixed price Allowed, but the purchase will fail later
List a token as an auction 400 — an auction could never be settled, so it is refused up front
Deploy an auction contract 400
Buy a listed token 400 — "Collection contract must be deployed to execute blockchain transfer"

The auction guard is the important one. Auction escrow lives inside a per-listing NFTAuction contract, and that contract can only be deployed against a real collection contract and a real on-chain token id. Before the guard existed, an auction on an undeployed collection was accepted, ran to completion, took bids, and then sat unsettleable forever. Fixed-price listings still allow this shape, which is why the failure surfaces at purchase time instead.

Collection statistics

Public statistics for one collection

Note that volumeTraded, totalSales and floorPrice are not columns on nft_collection. They are computed from nft_sale and nft_listing rows when asked for. A collection with no sales reports zeroes rather than nulls, and there is no cached figure to go stale.

Next

  • Minting — what happens after the contract exists.
  • Trading — listings, auctions and offers.
  • Fees and royalties — where the collection's royalty percentage is actually applied.