User journeysCreate a listing

Create a listing

Creates one or more above-floor listings owned by the caller. Liquid and dutch share the same struct; the discriminator is the duration field checked against the active ListingConfig for the collection.

Prerequisites

  • Collection created and initialized.
  • Caller owns each listed NFT and has approved the Locker (setApprovalForAll(locker, true)).
  • Caller holds enough nativeToken to pay tax up-front, and has approved Listings for taxRequired_. Use Listings.getListingTaxRequired(listing, collection) to size the approval.
  • Listings is not paused (Listings.paused() mirrors Locker.paused()).
  • For each listing, floorMultiple and duration must satisfy the collection’s ListingConfig:
    • floorMultiple > 100 (above floor).
    • floorMultiple <= maxFloorMultiple.
    • duration falls in either the dutch range [minDutchDuration, maxDutchDuration] or the liquid range [minLiquidDuration, maxLiquidDuration].

Format is inferred from the duration window — Listings.getListingType returns the active enum. If a duration sits outside both ranges the call reverts with InvalidListingType.

Inputs

struct Listing {
  address payable owner;
  uint40 created;
  uint32 duration;
  uint16 floorMultiple;   // 100 = floor, 200 = 2x floor (2dp)
}
 
struct CreateListing {
  address collection;
  uint[] tokenIds;
  Listing listing;        // applied to every tokenId in the array
}
 
function createListings(CreateListing[] calldata _createListings) external;
FieldTypeNotes
collectionaddressERC-721
tokenIdsuint[]All take the same Listing parameters
listing.owneraddress payableReceives sale proceeds; usually msg.sender
listing.createduint40Set by the contract — pass 0
listing.durationuint32Seconds; format derives from this
listing.floorMultipleuint162dp price (e.g. 150 = 1.5x floor)

Contract calls

#CallerCallPurpose
1Owner<ERC721>.setApprovalForAll(locker, true)One-shot
2Owner<nativeToken>.approve(listings, taxRequired)Per call (or use Permit2)
3OwnerListings.createListings([{collection, tokenIds, listing}])Pulls tax + NFTs in one tx

Events: Listings.ListingsCreated(collection, tokenIds, listing, listingType, tokensRequired, taxRequired, sender).

Code

createListings.ts
import { listingsAbi } from "@nftx/contracts";
import { NFTX_CONTRACTS } from "@nftx/config";
 
const listings = NFTX_CONTRACTS[base.id].listings;
const collection = "0xCollection";
const tokenIds = [11n, 12n];
 
const listing = {
  owner: account,
  created: 0,
  duration: 60 * 60 * 24 * 14, // 14 days = liquid
  floorMultiple: 150,           // 1.50x floor
} as const;
 
const taxRequired = await publicClient.readContract({
  address: listings,
  abi: listingsAbi,
  functionName: "getListingTaxRequired",
  args: [listing, collection],
});
 
await walletClient.writeContract({
  address: nativeToken,
  abi: erc20Abi,
  functionName: "approve",
  args: [listings, taxRequired],
});
 
await walletClient.writeContract({
  address: listings,
  abi: listingsAbi,
  functionName: "createListings",
  args: [[{ collection, tokenIds, listing }]],
});

Failure modes

ErrorCause
CollectionNotInitializedCollection’s pool isn’t bootstrapped yet
FloorMultipleMustBeAbove100(_floorMultiple)Tried to list at or below floor
FloorMultipleExceedsMax(...)Above the per-collection cap
ListingDurationBelowMin(...) / ListingDurationExceedsMax(...)Outside the active config window
InvalidListingTypeDuration falls between dutch and liquid bands
InsufficientTax(taxPaid, taxRequired)Approval too small / tax balance too low
LockerIsNotTokenHolderThe Locker doesn’t think this NFT was approved/transferred
Paused()Listings paused

Subgraph follow-through

For each new listing:

  • A Listing row is created/updated in place (format = LIQUID or DUTCH, taxCaptured = 0).
  • A ListingActivity { activityType: CREATED } row is written.
  • Collection.publicListings increments by 1.
query MyNewListings(
  $chainId: Int!
  $collection: String!
  $owner: String!
) {
  Listing(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
      owner: { _eq: $owner }
      format: { _in: [LIQUID, DUTCH] }
    }
    order_by: { createdAt: desc }
    limit: 50
  ) {
    tokenId
    floorMultiple
    duration
    format
    createdAt
  }
}