Skip to main content
One Xcollection is deployed per collection. It handles the full drop lifecycle: three-phase minting, allowlist enforcement, the Xholder reserve, reveal, burning, royalty routing, holder enumeration, and XNS-1 compatible token ownership. The current version is v4.0.0, formally verified before deployment. See the changelog for what changed and when.

Minting phases

Phases are time-gated by epoch ranges and resolved by get_current_phase(). Up to three sequential phases per collection. Durations are measured in epochs, the chain’s native time unit. MIN_PHASE_EPOCHS = 240, about 40 minutes. That floor is set from the fastest epoch rate the chain has actually held rather than a nominal ten seconds, so a phase usually runs 42 to 49 minutes. GTD and FCFS are bounded above by 259200 epochs (roughly a month). Set windows in epochs against the live chain rate rather than assuming a fixed seconds-per-epoch, since epoch wall-clock can drift. GTD and public both require a valid window; FCFS is the optional one. Phase ordering is enforced on-chain: fcfs_start >= gtd_end and pub_start >= fcfs_end.
Required here means the phase needs a valid time window. You can still make a phase mint nothing by setting its cap to 0 (closed). So a “no public sale” collection schedules a public window but leaves its cap at 0.
A phase cap of 0 means the phase is closed, not unlimited. To leave a phase open to the full supply, set its cap to the max supply. The global total_minted <= max_supply guard bounds everything regardless.
Force a specific phase via set_phase_override(); return to time-based resolution via clear_phase_override().

Caps share one budget

The three phase caps are not independent ceilings. The contract sums them with the Xholder reserve and anything already airdropped, and rejects a configuration whose total exceeds supply:
A cap below the phase’s already-minted count is also rejected.

The Xholder reserve

Every Xcollection reserves an allocation for Xpectra holders, computed at deploy time as:
Xpectra holders mint through xholder_mint(xpectra_id) during GTD. The contract verifies the caller currently owns that Xpectra token (owner_of(xpectra_id) == caller) and marks the claim before the external call. Xpectra holders pay the GTD price, with no discount. The reserve is its own pool, additive on top of the creator’s GTD allowlist allocation. It is decoupled from the GTD allocation: an xholder mint does not increment gtd_minted, does not consume gtd_cap, and is not bound by gtd_wallet_cap, so the reserve can never erode a creator’s own allowlist. It is still bound by the collection’s global max_per_wallet.
One claim per wallet, not one per Xpectra held. xholder_claimed[caller] is checked and set per wallet, so a wallet holding ten Xpectra gets one reserved mint on a given collection, the same as a wallet holding one. Read it with has_claimed_xholder(wallet).Separately, used_xpectra_ids records which Xpectra token IDs have been spent on a collection, readable with is_xpectra_id_used(id), so the same token cannot be passed between wallets to claim twice.
Read the pool state with get_xholder_status(), which returns xholder_minted_count | injection_cap | xpectra_contract | injection_swept. After GTD ends, unclaimed reserve slots return to the public pool through trigger_sweep(). The sweep is permissionless once GTD has ended, so no party can withhold it.

Allowlist

Xcollection calls its bound Xlist contract to verify GTD and FCFS access at mint time. See Xlist.

Payment splitting

Every mint splits payment at the contract level: Both collect via claim_proceeds(); balances are read with get_pending_proceeds(addr). Proceeds are never pushed alongside cross-contract calls. The pull model keeps funds isolated from AML atomicity risk.

Reveal

Collections deploy unrevealed. token_uri() returns the unrevealed_uri until reveal. The creator commits a provenance hash before mint opens, then calls reveal(base_uri) to publish the real metadata. Tokens then resolve as base_uri + token_id + .json. On Xpectrum the base URI is the collection’s Circle (oct://<circle_id>/), so revealed metadata and art are on-chain. Reveal is irreversible. See Provenance for how the held-back metadata is verified against the pre-committed hash.

Airdrop

The owner can airdrop up to 20 tokens per transaction via airdrop(). Airdrops bypass phase, price, and allowlist checks and count toward total_minted and max_supply.
Airdropped tokens consume max_supply but do not reduce phase caps. Plan your airdrop allocation before configuring phase caps to avoid overselling.

Burning

burn(token_id) is callable only by the token’s current holder. Not the collection owner, not an admin, not an approved spender, not the trusted factory.
  • max_supply is written once at init and has no setter, so burning never lowers the advertised maximum.
  • total_minted is monotonic and is the id source, so a burn never reopens a mint slot and ids are never reused.
  • Live supply is derived as total_minted - burned and is never stored.
  • The tombstone is tokens[id].owner == 0. Every token view rejects a burned id.
There is no batch burn: one token, one transaction. Read burn state with burned() and is_burned(token_id).
total_supply() returns live supply and total_minted() returns the id count. If you previously read total_supply expecting the number ever minted, you now get a different number. This is the breaking change in v4.0.0.

Holder enumeration

Xcollection maintains a holder index, so the platform can page through current holders on-chain via get_holders_page(offset, limit) for distribution analytics, without scanning every token.