User journeysShutdowns

Shutdowns

A shutdown wraps the lifecycle for retiring a collection. Holders vote their CollectionToken balance into a quorum gate; once quorum is reached an admin executes by liquidating all remaining locker NFTs into a SudoSwap pool, after which voters claim their proportional share of the ETH yielded.

The full lifecycle has six FE-relevant entry-points; admin-only flows are flagged.

start

Anyone holding CollectionToken can start a shutdown if the collection hasn’t been opted out via preventShutdown.

Prerequisites

  • _collection is registered.
  • CollectionShutdown.shutdownPrevented(_collection) == false.
  • No active shutdown in progress (ShutdownProcessAlreadyStarted otherwise).
  • Caller holds at least 1 wei of the relevant CollectionToken (UserHoldsNoTokens otherwise).
  • Shutdown contract is not paused.

Code

startShutdown.ts
import { collectionShutdownAbi } from "@nftx/contracts";
import { NFTX_CONTRACTS } from "@nftx/config";
 
await walletClient.writeContract({
  address: NFTX_CONTRACTS[base.id].collectionShutdown,
  abi: collectionShutdownAbi,
  functionName: "start",
  args: [collection],
});

Subgraph

CollectionShutdownStarted(collection)CollectionShutdown { startedAt, startedBy, quorum, claimAvailable: 0 }.

vote / voteAndClaim

Locks the caller’s CollectionToken balance into the shutdown’s vote tally. voteAndClaim is a gas-saving combo that votes and (if execute-ready) also claims in one tx.

Prerequisites

  • Shutdown is started but not executed.
  • Caller has approved the shutdown contract for the entire balance they want to vote, or has approved type(uint256).max once.

Code

vote.ts
import { collectionShutdownAbi, collectionTokenAbi } from "@nftx/contracts";
 
await walletClient.writeContract({
  address: collectionToken,
  abi: collectionTokenAbi,
  functionName: "approve",
  args: [shutdown, balance],
});
 
await walletClient.writeContract({
  address: shutdown,
  abi: collectionShutdownAbi,
  functionName: "vote",
  args: [collection],
});

Subgraph

CollectionShutdownVote(collection, voter, vote)CollectionShutdownVote { votes: + }. If quorum is reached: CollectionShutdownQuorumReached(collection)CollectionShutdown.quorumReachedAt = now.

reclaimVote

Withdraws an outstanding shutdown vote and returns the locked CollectionToken balance.

Prerequisites

  • Caller has previously voted (shutdownVoters > 0).
  • Quorum hasn’t been passed in a way that disallows reclaiming — ShutdownQuorumHasPassed reverts when reclaim would push the tally below quorum but the shutdown has already been executed.

Code

reclaimVote.ts
await walletClient.writeContract({
  address: shutdown,
  abi: collectionShutdownAbi,
  functionName: "reclaimVote",
  args: [collection],
});

Subgraph

CollectionShutdownVoteReclaim(collection, voter, vote)CollectionShutdownVote { votes: - }. If reclaim drops the tally below quorum, quorumReachedAt is unset.

execute (admin)

Liquidates every remaining locker NFT for the collection into a SudoSwap linear-curve pair and starts the claim window. Admin-only, gated by ShutdownNotReachedQuorum.

Prerequisites

  • quorumReachedAt != 0.
  • All listing rows for the collection have been resolved (ListingsExist otherwise).
  • tokenIds is the complete locker inventory; the contract verifies it via Locker.collectionToken balance bookkeeping.

Code

execute.ts
await walletClient.writeContract({
  address: shutdown,
  abi: collectionShutdownAbi,
  functionName: "execute",
  args: [collection, allLockerTokenIds],
});

Subgraph

CollectionShutdownExecuted(collection, sweeperPool, tokenIds)CollectionShutdown { executedAt, executedBy, liquidationPool, liquidationPoolTokens }. Subsequent CollectionShutdownTokenLiquidated events bump claimAvailable as the SudoSwap pair drains.

claim

Once claimAvailable > 0, voters claim a proportional share of the realised ETH. Either the holder calls it themselves, or anyone calls it on behalf of _claimant (e.g. a keeper).

Prerequisites

  • Shutdown is executed.
  • _claimant has shutdown votes recorded.
  • The caller can pay the gas; the eth itself is paid to _claimant.

Code

claim.ts
await walletClient.writeContract({
  address: shutdown,
  abi: collectionShutdownAbi,
  functionName: "claim",
  args: [collection, claimant],
});

Subgraph

CollectionShutdownClaim(collection, claimant, tokenAmount, ethAmount) → new CollectionShutdownClaim row.

cancel

Cancels an in-progress shutdown when the collection’s CollectionToken supply has grown back above the quorum threshold (e.g. new mints arrived post-vote).

Prerequisites

  • Shutdown started but not executed.
  • CollectionToken.totalSupply >= quorum * SHUTDOWN_QUORUM_PERCENT / 100 (InsufficientTotalSupplyToCancel otherwise).

Code

cancel.ts
await walletClient.writeContract({
  address: shutdown,
  abi: collectionShutdownAbi,
  functionName: "cancel",
  args: [collection],
});

Subgraph

CollectionShutdownCancelled(collection)CollectionShutdown row’s tracker fields reset; voters keep their balances (reclaim individually).

preventShutdown (admin)

Locks a collection out of the shutdown lifecycle entirely (used for strategic collections that should never be retired).

Code

preventShutdown.ts
await walletClient.writeContract({
  address: shutdown,
  abi: collectionShutdownAbi,
  functionName: "preventShutdown",
  args: [collection, true],
});

Subgraph

CollectionShutdownPrevention(collection, prevented)Collection.shutdownPrevented updates.

Combined view: a holder’s shutdown dashboard

query MyShutdownDashboard($chainId: Int!, $voter: String!) {
  CollectionShutdown(
    where: {
      chainId: { _eq: $chainId }
      votes: { voter: { _eq: $voter } }
    }
    order_by: { startedAt: desc }
  ) {
    id
    collection { collection name }
    quorum
    quorumReachedAt
    executedAt
    claimAvailable
    votes(where: { voter: { _eq: $voter } }) { votes createdAt }
    claims(where: { claimant: { _eq: $voter } }) {
      tokensBurnt
      ethReceived
      claimedAt
    }
  }
}

Failure modes (across the lifecycle)

ErrorWhen
ShutdownPreventedstart while preventShutdown(_, true)
ShutdownProcessAlreadyStarted / ShutdownProccessNotStartedmismatched lifecycle
ShutdownNotReachedQuorumexecute before quorum
ShutdownQuorumHasPassedreclaimVote after execute
ShutdownNotExecutedclaim before execute
NoTokensAvailableToClaimclaimant has no votes / already claimed
NotAllTokensSoldclaim before SudoSwap pair has drained
ListingsExistexecute while liquid/dutch listings remain
UserHoldsNoTokensstart from a wallet with zero CT
InsufficientTotalSupplyToCancelcancel when supply is still below the cancel threshold
CollectionNotShutdownCompatible(_collection)start for an opted-out collection (e.g. very large supply)