User journeysManage a listing

Manage a listing

Five owner-only mutations on an existing listing. All five share the same auth model — msg.sender must equal the listing’s owner — and most also take a _payTaxWithEscrow flag that decides whether the additional tax is pulled from the caller’s wallet (false) or their TokenEscrow balance (true).

CallWhat it does
Listings.relistCaller buys the listing and simultaneously creates a new listing on the same tokenId. Atomic.
Listings.modifyListingsUpdate one or more (duration, floorMultiple) pairs, paying any tax delta.
Listings.cancelListingsCancel and refund. Tax for already-elapsed time stays captured.
Listings.extendListings (via modifyListings)Extend duration. Implemented as a modifyListings with the same floorMultiple.
Listings.transferOwnershipHand the listing to a new owner. Doesn’t move tax.

There is no separate extendListings entry-point on-chain — it’s just modifyListings keeping floorMultiple fixed. The subgraph distinguishes the two by emitting an EXTENDED activity when only duration changed and REPRICED when only floorMultiple changed.

Common prerequisites

  • Caller is the current owner of every targeted listing.
  • For relist: the new owner (the caller) must satisfy all the preconditions in Create a listing for the replacement listing.
  • For paths that increase tax: caller has either an approval against Listings (when _payTaxWithEscrow == false) or sufficient TokenEscrow.balances(caller, nativeToken).
  • Listings is not paused.

relist

Atomic “buy and re-list at a new price”. relist accepts a single CreateListing payload (use tokenIds[0]); RelistAcceptsSingleTokenOnly reverts on multi-id payloads.

Inputs

function relist(CreateListing calldata _listing, bool _payTaxWithEscrow) external;

Code

relist.ts
import { listingsAbi } from "@nftx/contracts";
 
await walletClient.writeContract({
  address: listings,
  abi: listingsAbi,
  functionName: "relist",
  args: [
    {
      collection,
      tokenIds: [42n],
      listing: {
        owner: account,
        created: 0,
        duration: 60 * 60 * 24 * 30, // 30 day liquid listing
        floorMultiple: 175,
      },
    },
    /* payTaxWithEscrow */ false,
  ],
});

Subgraph

relist emits ListingRelisted. The subgraph keeps the Listing row in place, updating owner, floorMultiple, duration, createdAt. A new ListingActivity { activityType: RELISTED } row is written.

modifyListings (and extend)

struct ModifyListing {
  uint tokenId;
  uint32 duration;
  uint16 floorMultiple;
}
 
function modifyListings(
  address _collection,
  ModifyListing[] calldata _modifyListings,
  bool _payTaxWithEscrow
) external returns (uint taxRequired_, uint refund_);

taxRequired_ is the additional tax pulled in this call; refund_ is the amount returned (in nativeToken) to the caller — non-zero when tax was prepaid against a longer original duration that the modify reduces. Both legs net through the caller’s wallet or escrow depending on _payTaxWithEscrow.

Code

modifyListings.ts
import { listingsAbi } from "@nftx/contracts";
 
await walletClient.writeContract({
  address: listings,
  abi: listingsAbi,
  functionName: "modifyListings",
  args: [
    collection,
    [
      { tokenId: 11n, duration: 60 * 60 * 24 * 30, floorMultiple: 200 },
      { tokenId: 12n, duration: 60 * 60 * 24 * 30, floorMultiple: 175 },
    ],
    /* payTaxWithEscrow */ true,
  ],
});

Subgraph

For each modified tokenId one of:

  • ListingExtended(collection, tokenId, oldDuration, newDuration) — duration only.
  • ListingFloorMultipleUpdated(collection, tokenId, oldFM, newFM) — multiple only.

Maps to ListingActivity { activityType: EXTENDED } or REPRICED respectively. If both change, both activity rows are written.

cancelListings

Cancels listings owned by the caller. Tax already accrued (i.e. tax for elapsed time) stays captured; the caller is refunded the unaccrued remainder either to their wallet or escrow.

Inputs

function cancelListings(
  address _collection,
  uint[] calldata _tokenIds,
  bool _payTaxWithEscrow
) external;

Code

cancelListings.ts
await walletClient.writeContract({
  address: listings,
  abi: listingsAbi,
  functionName: "cancelListings",
  args: [collection, [11n, 12n], false],
});

Subgraph

Listings.ListingsCancelled(collection, tokenIds)Listing row updated (drops out of the listings counters; reverts to floor format: NONE if the NFT remains in the Locker, otherwise removed entirely on burn). A CANCELLED ListingActivity row is written per tokenId.

transferOwnership

Hands the listing to a new owner without changing economics.

Inputs

function transferOwnership(
  address _collection,
  uint _tokenId,
  address payable _newOwner
) external;

Code

transferOwnership.ts
await walletClient.writeContract({
  address: listings,
  abi: listingsAbi,
  functionName: "transferOwnership",
  args: [collection, 42n, "0xNewOwner"],
});

Subgraph

Listings.ListingTransferred(collection, tokenId, owner, newOwner)Listing.owner updates; ListingActivity { activityType: TRANSFERRED, from, to } is written.

Failure modes (across all five)

ErrorWhen
CallerIsNotOwner(expected)msg.sender isn’t the listing owner
NewOwnerIsZerotransferOwnership to address(0)
CallerIsAlreadyOwnertransferOwnership to current owner
CannotCancelListingTypeTrying to cancel a NONE (floor) row
ListingNotAvailableListing has expired or has been filled
RelistAcceptsSingleTokenOnlyrelist with more than one tokenId
InsufficientTax(paid, required)Approval too small or escrow too low
Paused()Listings paused

Combined subgraph query — full activity log for a listing

query ListingHistory(
  $chainId: Int!
  $collection: String!
  $tokenId: numeric!
) {
  ListingActivity(
    where: {
      chainId: { _eq: $chainId }
      collection: { collection: { _eq: $collection } }
      tokenId: { _eq: $tokenId }
    }
    order_by: { created: asc }
  ) {
    activityType
    from
    to
    floorMultiple
    ethPrice
    txHash
    created
  }
}