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).
| Call | What it does |
|---|---|
Listings.relist | Caller buys the listing and simultaneously creates a new listing on the same tokenId. Atomic. |
Listings.modifyListings | Update one or more (duration, floorMultiple) pairs, paying any tax delta. |
Listings.cancelListings | Cancel and refund. Tax for already-elapsed time stays captured. |
Listings.extendListings (via modifyListings) | Extend duration. Implemented as a modifyListings with the same floorMultiple. |
Listings.transferOwnership | Hand 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
ownerof 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 sufficientTokenEscrow.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
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
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
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
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)
| Error | When |
|---|---|
CallerIsNotOwner(expected) | msg.sender isn’t the listing owner |
NewOwnerIsZero | transferOwnership to address(0) |
CallerIsAlreadyOwner | transferOwnership to current owner |
CannotCancelListingType | Trying to cancel a NONE (floor) row |
ListingNotAvailable | Listing has expired or has been filled |
RelistAcceptsSingleTokenOnly | relist 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
}
}