Listings
Contracts/src/contracts/Listings.sol
Owns the liquid + dutch + floor listing lifecycle. Floor listings (format = NONE) are created implicitly by the Locker on deposit/swap; liquid and
dutch listings are first-class objects created via createListings.
Listings inherits TokenEscrow, so listing refunds, sale proceeds, and
unaccrued tax all share the same escrow surface as NFTXV4Hook AMM fees.
State views
| Function | Returns |
|---|---|
locker() | The owning ILocker |
listings(_collection, _tokenId) | (owner, created, duration, floorMultiple) |
listingCount(_collection) | Active liquid + dutch listings count |
defaultTaxCalculator() | Default ITaxCalculator |
collectionTaxCalculator(_collection) | Per-collection override (zero = use default) |
taxCalculator(_collection) | Effective tax calc after override-or-default fallback |
defaultListingConfig() | (maxFloorMultiple, minLiquid, maxLiquid, minDutch, maxDutch, liquidDutchDuration) defaults |
collectionListingConfig(_collection) | Raw per-collection override |
listingConfig(_collection) | Effective config after override-or-default fallback |
getListingType(_collection, _listing) | Resolves Enums.ListingType (NONE, LIQUID, DUTCH) |
getListingPrice(_collection, _tokenId) | (isAvailable_, price_) — current fillable price |
getListingTaxRequired(_listing, _collection) | Tax owed up-front for a hypothetical listing |
Public surface
Create / fill
| Function | Caller | Description |
|---|---|---|
createListings(_createListings) | Owner of NFTs | Mint one or many listings; all share their CreateListing.listing config |
fillListings(params) | Buyer | Atomic group fill across owners |
relist(_listing, _payTaxWithEscrow) | Buyer | Buy + re-list in one tx |
Modify / cancel
| Function | Caller | Description |
|---|---|---|
modifyListings(_collection, _modifyListings, _payTaxWithEscrow) | Listing owner | Update (duration, floorMultiple) per tokenId; returns (taxRequired_, refund_) |
cancelListings(_collection, _tokenIds, _payTaxWithEscrow) | Listing owner | Cancel — refund the unaccrued tax |
transferOwnership(_collection, _tokenId, _newOwner) | Listing owner | Hand over without changing economics |
Tax / config (admin)
| Function | Description |
|---|---|
setDefaultTaxCalculator(_taxCalculator) | Default tax calc |
setCollectionTaxCalculator(_collection, _taxCalculator) | Per-collection override (zero clears) |
setDefaultListingConfig(_config) | Default ListingConfig |
setCollectionListingConfig(_collection, _config) | Per-collection override |
Structs
struct Listing {
address payable owner;
uint40 created;
uint32 duration;
uint16 floorMultiple;
}
struct CreateListing {
address collection;
uint[] tokenIds;
Listing listing;
}
struct ModifyListing {
uint tokenId;
uint32 duration;
uint16 floorMultiple;
}
struct ListingConfig {
uint16 maxFloorMultiple; // 100 = 1.00x
uint32 minLiquidDuration;
uint32 maxLiquidDuration;
uint32 minDutchDuration;
uint32 maxDutchDuration;
uint32 liquidDutchDuration; // tail decay applied to expired liquid
}
struct FillListingsParams {
address collection;
uint[][] tokenIdsOut;
address recipient;
uint maxSpend;
}ListingConfig.maxFloorMultiple == 0 is the sentinel value the contract
uses to mean “no per-collection override” — any valid config has
maxFloorMultiple > 100.
Errors
| Error | Where |
|---|---|
LockerIsZeroAddress | constructor |
TaxCalculatorIsZeroAddress | constructor |
InvalidListingConfig | setDefaultListingConfig / setCollectionListingConfig cross-field violations |
InsufficientTax(taxPaid, taxRequired) | create / modify |
InvalidFloorMultiple(_floorMultiple, _expectedFloorMultiple) | mismatch on relist preflight |
LockerIsNotTokenHolder | NFT not in the Locker |
CollectionNotInitialized | pool not bootstrapped |
ListingOwnerIsZero, NewOwnerIsZero | invariants |
FloorMultipleMustBeAbove100(_floorMultiple) | floor not allowed as a listing |
FloorMultipleExceedsMax(_floorMultiple, _maxFloorMultiple) | over per-collection cap |
ListingDurationBelowMin(...) / ListingDurationExceedsMax(...) | outside config bands |
InvalidListingType | duration falls between dutch and liquid bands |
CallerIsNotOwner(_expectedCaller) | mutation attempted by non-owner |
CannotCancelListingType | tried to cancel NONE |
ListingNotAvailable | expired beyond dutch tail / cancelled / filled |
InvalidCollection, InvalidOwner, CallerIsAlreadyOwner | argument invariants |
Paused() | listings paused |
FillCostExceedsMaxSpend(totalCost, maxSpend) | sandwich guard tripped on fill |
RelistAcceptsSingleTokenOnly | passed >1 tokenId to relist |
Events
| Event | Trigger |
|---|---|
ListingsCreated(_collection, _tokenIds, _listing, _listingType, _tokensRequired, _taxRequired, _sender) | createListings |
ListingRelisted(_collection, _tokenId, _listing) | relist |
ListingsFilled(_collection, _tokenIds, _recipient) | fillListings |
ListingTransferred(_collection, _tokenId, _owner, _newOwner) | transferOwnership |
ListingsCancelled(_collection, _tokenIds) | cancelListings |
ListingExtended(_collection, _tokenId, _oldDuration, _newDuration) | modifyListings (duration changed) |
ListingFloorMultipleUpdated(_collection, _tokenId, _oldFM, _newFM) | modifyListings (multiple changed) |
ListingFeeCaptured(_collection, _tokenId, _amount) | tax accrual |
DefaultTaxCalculatorUpdated(_taxCalculator) | admin |
CollectionTaxCalculatorUpdated(_collection, _taxCalculator) | admin |
DefaultListingConfigUpdated(_config) | admin |
CollectionListingConfigUpdated(_collection, _config) | admin |
Inherited surface
TokenEscrow—withdraw(_recipient, _token, _amount),balances(_recipient, _token),Deposit/Withdrawalevents.Pausable,Ownable,ReentrancyGuard.