Settings

All seventeen NFT Marketplace settings keys with their defaults, the five tabs they live under, what actually reads each one, and the three that change nothing.

8 min readUpdated 6 August 2026settings, auctions, royalties, kyc, escrow

The addon's settings screen is Admin → NFT → System → Settings (/admin/nft/settings). Opening it needs access.nft.settings; saving it calls the platform-wide PUT /api/admin/system/settings, which needs edit.settings. Those are two different keys and an admin can easily hold one without the other — the screen loads, the values edit, and Save returns 403.

Every value lands in the platform settings table as a string, so the backend parses each one on read and falls back when the parse fails. Saves take effect immediately: the endpoint upserts only the keys whose value actually changed and then clears the settings cache.

Earlier versions of this page listed nftMinBidIncrement as the auction bid increment. No seeder, no migration and no admin screen has ever written it. Two routes used to read it, always got nothing, and silently fell back to a hardcoded 0.01, which is how an auction created with an increment of 10 could accept a bid one cent above the standing one.

Both readers now use the per-listing nft_listing.minBidIncrement column. The setting that governs that column is nftBidIncrementPercentage. If you have a nftMinBidIncrement row in your settings table from a hand edit, it is inert — deleting it changes nothing.

The five tabs

Tab What it groups Keys
Trading Which sale types exist, auction mechanics, the offer confirmation window 9
Fees Platform fee, royalty ceiling, listing fee 3
Verification KYC gates 3
Content Metadata validation 1
Integrations Cross-chain 1

Trading

Which sale types exist

Allow straightforward buy-now listings. Off makes POST /api/nft/listing return 403 for FIXED_PRICE.
Allow timed auctions. Off makes POST /api/nft/listing return 403 for AUCTION.
Allow buyers to make escrowed offers. Off makes POST /api/nft/offer return 403.

Turning one off does not cancel anything already in flight. Existing auctions still settle, outstanding offers can still be accepted and confirmed, and live fixed-price listings can still be bought. The switch only closes the door on new ones.

All three are checked with a string-aware helper, because a raw settings.get("nftEnableAuctions") returns the string "false" when the switch is off and "false" is truthy. If yours were saved before that fix and appear to do nothing, save the tab once to rewrite them.

Auction window

Shortest auction a seller may create, in SECONDS. Enforced at listing creation.
Longest auction a seller may create, in SECONDS. Enforced at listing creation.

Both are entered in seconds — the screen steps the minimum in 60s increments (floor 60) and the maximum in 3600s increments (floor 3600). The defaults are one hour and seven days.

POST /api/nft/listing computes endTime − startTime and refuses anything outside the pair, quoting the bound back in hours or days. Changing either value does not touch auctions already running.

Bid increment

Minimum bid step as a PERCENT of the starting price. Validated at listing creation against the seller-supplied per-listing increment.

This is a percentage, not an amount, and it is a validator rather than the increment itself. When a seller creates an auction, the listing route computes price × nftBidIncrementPercentage / 100 and rejects the listing if the minBidIncrement the seller submitted is below it — the error names the figure they need. The accepted value is then stored on the row as nft_listing.minBidIncrement.

Everything downstream reads that column, never this setting:

  • POST /api/nft/bid requires a new bid to clear the current highest ACTIVE bid by at least the column's value.
  • GET /api/nft/auction/{id} publishes minimumNextBid from it.
  • POST /api/nft/auction/deploy passes it into the auction contract, which then enforces it on chain for the rest of that auction's life.

The screen exposes it as a slider from 1 to 25 in whole percents, so you cannot set it to zero from the interface. Raising it does not change any auction already created — those rows keep the increment they were validated against.

Anti-snipe

Extends an auction when a bid lands in its final moments, so a last-second snipe cannot win uncontested.
Seconds to extend by, and also the window that triggers the extension.

One number does two jobs: a bid arriving with less than nftAntiSnipeExtension seconds left pushes endTime out to now plus that same extension.

With it on, an auction's advertised end is a floor rather than a deadline. Say so on your terms page — bidders who expect a hard close will complain otherwise. The bid response carries auctionExtended: true so the interface can announce it.

Offer settlement

Hours an ACCEPTED offer may sit unconfirmed before the sweep unwinds it and releases the buyer's escrow.

Accepting an offer moves no money. The sale settles only when either party calls POST /api/nft/offer/{id}/confirm with a verifiable on-chain transfer hash. Until then the buyer's funds sit on hold in their own SPOT wallet and the token is off the market.

The expireOffers cron — every five minutes — sweeps ACCEPTED offers older than this many hours: it releases the hold, marks the offer EXPIRED, and puts the token back on sale. A non-numeric or non-positive value falls back to 24.

The seller is usually the party holding the transaction hash, because they are the one who executes transferFrom. Shorten this and you unwind real sales whose on-chain transfer was merely slow. Lengthen it and a buyer's money stays locked for longer when the transfer never happens.

If the release itself fails, the offer is flagged instead of unwound — see When money or an NFT is stuck.

Fees

Platform fee percentage. Sizes offer escrow, and is the rate recorded on a sale unless the marketplace contract's own rate took over.
The ceiling every royalty in the product is clamped to.
Flat fee quoted when a listing is created.

This slider governs the custodial path: it sizes the buyer's escrow when an offer is made (amount + fee), and it is the rate applied at offer settlement and at auction settlement.

A fixed-price purchase reads it too, but does not always use it. POST /api/nft/listing/{id}/buy computes the fee from this slider up front, then tries the marketplace contract. If one is deployed for the collection's chain and the token is genuinely listed on it, buyItem splits the payment using the feePercentage baked into that contract at deployment, in basis points, and those are the figures written to the nft_sale row. If any part of that is untrue — no contract for the chain, the token was never listed on it, or the call throws — the route falls back to a direct safeTransferFrom and the sale row records this slider's figure instead.

Nothing reconciles the two. Deploy at 2.5%, move this slider to 5%, and the same item is recorded two different ways depending on which path the purchase took. The purchase response quotes this slider's figure either way, so it can disagree with the sale row it has just created.

On the fallback path the fee is a record, not a collection: the direct transfer moves the token only, and the buyer paid the seller with their own transaction, so nothing debits a wallet and nothing accrues to the platform.

PUT /api/nft/marketplace/config is the only thing that changes the contract, and it costs gas. See Marketplace contracts.

Two more places read this key that are easy to miss:

  • POST /api/nft/auction/deploy seeds each new auction contract with it. That contract then keeps that rate for its whole life, so auctions deployed before a change keep the old fee.
  • POST /api/nft/listing quotes it back in the listing response as marketplaceFee, alongside a clamped royaltyFee and an estimatedTotal.

The screen offers 0–10 in half-percent steps for the fee and 0–25 for the royalty ceiling. The backend accepts anything numeric and non-negative and only falls back (2.5 and 10) when the stored string cannot be parsed.

nftListingFee is a flat amount, not a percentage. The listing route reads it and returns it to the seller — it debits no wallet, and nothing collects it anywhere. The shipped NFTMarketplace contract has no listing fee at all: its constructor takes only a fee recipient and a fee percentage, and listItem is not payable. The separate listingFee that the marketplace deploy call accepts is written to the nft_marketplace row and echoed back by GET /api/nft/marketplace/info — it never reaches a contract either. Set this to a non-zero value and you are quoting sellers a charge nobody takes; no redeploy changes that.

Where the money lands, and the worked example, are in Fees and royalties.

Verification

Included in the launch-readiness checklist. It does NOT gate collection creation on its own.
Require KYC above the threshold before a fixed-price purchase completes.
The USD figure that counts as high value.

The high-value pair

POST /api/nft/listing/{id}/buy converts the sale price to USD and, when nftRequireKycForHighValue is on and nftHighValueThreshold is above zero, requires the buyer to clear the buy_nft KYC feature before the purchase completes. Below the threshold the purchase runs with no extra check.

Two traps:

  • The threshold is USD, converted from the listing currency at request time. A conversion that cannot be resolved leaves the purchase ungated.
  • The backend's fallback for the switch is off, while the screen's initial value is on. On an install where the key was never saved the gate is not running. Save the Verification tab once so the row exists.

nftRequireKycForCreators is weaker than it reads

The only thing that reads this key is GET /api/nft/onboarding/status, the launch-readiness endpoint behind /admin/nft/onboarding. It marks configure verification complete once the key exists at all — whether it is on or off.

Collection creation is gated by the shared create_nft KYC feature, not by this switch. If you need creators verified, set that feature's level in the core KYC configuration; flipping this switch alone changes nothing about who may create a collection.

The six NFT actions the KYC system can gate are view_nft, create_nft, buy_nft, sell_nft, transfer_nft and deploy_nft_contract.

Content

Validate metadata before a token is minted and before it is listed.

One key, two very different enforcements:

Route What it checks when the switch is on
POST /api/nft/token/mint-web3 The NFT has a non-empty name of at most 200 characters. Nothing else.
POST /api/nft/listing Fetches the token's metadataUri and validates the real JSON

The listing-time check is the strict one. It runs only when the token already has both a metadataUri and an image, and it requires both to match one of seven accepted IPFS URL shapes:

  • ipfs://<hash>
  • https://ipfs.io/ipfs/<hash>
  • https://gateway.ipfs.io/ipfs/<hash>
  • https://<hash>.ipfs.<host>/ — the dweb.link style
  • https://gateway.pinata.cloud/ipfs/<hash>
  • https://<hash>.ipfs.nftstorage.link
  • https://<hash>.ipfs.w3s.link

It then fetches the metadata JSON over a gateway with a 10-second timeout and requires a name, an image, and for that image to itself be a valid IPFS URL. Anything else — a self-hosted image, an Arweave URL, a private gateway, a slow pin — fails the listing with "NFT metadata validation failed".

When the key is absent, the mint route treats it as on and the listing route treats it as off. On a fresh install that has never saved this tab you get name validation at mint and no IPFS validation at listing. Save the Content tab once to make both paths agree with what you see on screen.

If your creators host media anywhere other than those seven gateway shapes, leave this off — otherwise nothing they mint can ever be listed.

Integrations

Labelled cross-chain bridging. Nothing in this build reads it.

This switch is written by the settings screen and read by no backend route, no cron and no frontend component. There is no bridge in this build. Leave it alone; it changes nothing either way.

Chain availability is not controlled here — a chain becomes selectable only once a marketplace contract is deployed on it, which is the nft_marketplace table. See Marketplace contracts.

Which key is read where

Key Read by
nftEnableFixedPriceSales POST /api/nft/listing (FIXED_PRICE), readiness checklist
nftEnableAuctions POST /api/nft/listing (AUCTION), readiness checklist
nftEnableOffers POST /api/nft/offer, readiness checklist
nftMinAuctionDuration POST /api/nft/listing (AUCTION)
nftMaxAuctionDuration POST /api/nft/listing (AUCTION)
nftBidIncrementPercentage POST /api/nft/listing (AUCTION) — validates minBidIncrement
nftEnableAntiSnipe POST /api/nft/bid
nftAntiSnipeExtension POST /api/nft/bid
nftTransferConfirmGraceHours The expireOffers cron's stale-accepted-offer sweep
nftMarketplaceFeePercentage Offer escrow sizing, offer settlement, auction settlement, auction contract deployment, listing quote, POST /api/nft/listing/{id}/buy — its quoted fee always, and the fee written to nft_sale whenever the marketplace-contract path is not taken
nftMaxRoyaltyPercentage Every path that quotes or pays a royalty
nftListingFee POST /api/nft/listing — response only, no debit
nftRequireKycForCreators Readiness checklist only
nftRequireKycForHighValue POST /api/nft/listing/{id}/buy
nftHighValueThreshold POST /api/nft/listing/{id}/buy
nftRequireMetadataValidation POST /api/nft/token/mint-web3, POST /api/nft/listing
nftEnableCrossChain Nothing

Keys that are not on this screen

Three more NFT settings rows exist that this screen never shows and never writes. All of them are per-chain, and all of them are lower-cased snake case with the chain appended — nft_marketplace_address_eth, ..._bsc and so on:

Key Written by Read by
nft_marketplace_address_<chain> Nothing Pause, unpause, config, whitelist, withdraw, readiness checklist
nft_marketplace_paused_<chain> Pause and unpause Pause and unpause
nft_marketplace_pause_reason_<chain> Pause and unpause Unpause, which reports it back

The first one is the reason the emergency controls answer "Marketplace contract not found" on a fresh install. The recovery is in When money or an NFT is stuck.