Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Multi-collection · Boson Protocol
Skip to content

Multi-collection

By default a seller has exactly one voucher (rNFT) contract — every offer they publish mints into that one ERC-721. Marketplaces and AMMs treat everything in the same contract as one collection, which conflates brands, themes, and product lines.

Multi-collection lets a seller create additional collections, each its own ERC-721 contract instance. Every offer picks which collection it belongs to via a collectionIndex field. This is purely an organising layer — there's still one seller, one set of approved royalty recipients, one treasury — but external observers see distinct collections.

When to use

  • Distinct brands under one entity. Sell limited drops and everyday goods without mixing them on OpenSea.
  • Season releases. Each season as its own collection makes browsing and floor-tracking sensible.
  • Marketplaces with collection-level royalty enforcement. A separate collection gets its own ERC-2981 royalty profile, contract URI, and OpenSea collection page. Pairs well with per-offer royalties.

When not to use

  • A single brand with many products — one collection is fine.
  • You actually want separate sellers (different keys, different treasuries) → just create another seller.

Mechanics

Collections are minimal Beacon proxies pointing at the same voucher implementation. Creating one is cheap (a clone deploy, no upgradeable proxy machinery to manage). The seller has one shared mapping sellerId → cloneAddress[] and every offer carries a collectionIndex into that array.

Index 0 is always the seller's original collection — created automatically when the seller is created. New collections are indices 1, 2, 3, ….

Create a collection

SDK
// Assistant-only. Creates a new clone with its own contract URI.
await (await sdk.createNewCollection({
  collectionId: "spring-2026",         // free-form tag; used to derive collectionSalt by default
  contractUri: "ipfs://Qm…",            // OpenSea-style collection metadata JSON
  // Optional: pass an explicit collectionSalt and/or sellerId
  // collectionSalt: ethers.utils.formatBytes32String("spring-2026"),
  // sellerId: sellerId,
})).wait()
 
// Discover collections later (subgraph query)
const collections = await sdk.getOfferCollections({
  offerCollectionsFilter: { seller: sellerId },
})

The collection's address is deterministic via CREATE2 + collectionSalt, so you can compute it before sending the transaction. The CollectionCreated event carries the resolved address.

Publish into a specific collection

Pass collectionIndex on the offer:

await sdk.createOffer({
  // …offer fields
  collectionIndex: 1, // 0 = default, 1 = first additional collection, etc.
})

If collectionIndex is out of bounds for the seller, createOffer reverts.

What's shared across collections

A single seller has:

  • One assistant / admin / clerk / treasury.
  • One protocol treasury balance per token.
  • One set of approved royalty recipients.
  • One reputation / dispute history.

What's per-collection:

  • The ERC-721 contract address (and therefore the OpenSea collection page).
  • The contractURI (collection-level metadata).
  • The collectionSalt (used at clone deploy).

Royalties are now per-offer, so two offers in the same collection can pay different recipients. Marketplaces that treat all tokens in a contract as one collection still see one OpenSea page but the registry honours the per-offer split.

Common gotchas

  • Index 0 is implicit. Don't try to createNewCollection for it — it was created when the seller was registered.
  • Salts are unique per seller, not global. Two sellers can use the same collectionSalt and get different deterministic addresses.
  • Don't conflate with multi-seller. If you want different keys / treasuries / dispute histories, create distinct sellers — not collections.
  • OpenSea metadata. Set contractURI to a JSON document at the metadata standard OpenSea expects (name, description, image, external_link). Without it the OpenSea page is blank.

Related