Fees and royalties
Where every percentage in the NFT marketplace is read, where it is clamped, and the two entirely separate places your revenue accumulates — the on-chain marketplace contract and the Super Admin wallet.
Three numbers decide who gets paid: the marketplace fee, the creator royalty, and the listing fee. Each one is read in more than one place, and two of them exist in two copies that do not synchronise. Getting this wrong does not throw an error — it quietly pays the wrong people.
The three numbers
The settings screen exposes the fee as a 0–10 slider in half-percent steps and the royalty ceiling as a 0–25 slider. The backend accepts anything numeric and non-negative; it falls back to 2.5 and 10 only when the stored value cannot be parsed.
The fee exists in two copies
This is the single most important thing on this page.
| Copy | Set by | Applies to |
|---|---|---|
The contract's feePercentage |
The marketplace deploy call, in basis points | Every on-chain sale — fixed price and auction |
nftMarketplaceFeePercentage |
The admin settings screen | Every custodial settlement — accepted offers |
They are written at different times, by different people, into different systems, and nothing reconciles them. Deploy at 2.5% and later move the slider to 5% and you have a marketplace that charges 2.5% on a direct purchase and 5% on an accepted offer for the identical item.
PUT /api/nft/marketplace/config is the only thing that changes the deployed
contract's fee. It sends a transaction, so it costs gas, and it needs the chain
and the contract address. Changing the settings slider alone leaves the contract
untouched.
Change both, and record which value you deployed at — the contract is the authority for on-chain sales and there is no screen that compares the two.
Where the money actually lands
On-chain sales
A fixed-price purchase or an auction settlement pays out inside the contract. The buyer's transaction sends the full price; the contract keeps its fee, pays the royalty recipient, and forwards the remainder to the seller. No Bicrypto wallet balance changes.
Your fee therefore accumulates as a native-token balance held by the marketplace contract. It is not income until you withdraw it:
The withdrawal requires chain, contractAddress and a reason (recorded, up
to 500 characters). Amount and destination are optional — omit the amount to
withdraw everything, omit the address to send to the configured fee recipient.
There is no alert, no dashboard tile and no scheduled sweep. The balance shows on the Marketplace admin screen only when you open it. Put a calendar reminder against it.
Custodial offer settlements
An accepted offer settles entirely inside your platform, in the offer's currency, against SPOT wallets. Three legs run in one database transaction:
-
The buyer's escrow is consumed.
executeFromHoldtakes exactly what the hold row says was reserved — the offer amount plus the fee that applied when the offer was made. -
The seller is credited the offer amount less the creator royalty. Note that the seller does not pay the marketplace fee out of their proceeds; the buyer paid it on top.
-
The creator is credited the royalty, resolved through
nft_collection.creatorId→nft_creator.userIdto reach a real account.
The fee then goes to the platform through collectPlatformFee, which credits
the Super Admin's SPOT wallet in that currency and writes an adminProfit
row of type NFT_SALE. It never throws — a failure is logged and the sale still
completes.
collectPlatformFee resolves the oldest user holding the "Super Admin" role. If
that role or that user does not exist, it logs a critical error and returns —
the sale succeeds and the fee vanishes. Check that the role exists and has
exactly one obvious owner before you take real volume.
Every leg carries a stable idempotency key derived from the offer id
(nft_offer_execute_<id>, nft_offer_credit_seller_<id>,
nft_offer_royalty_<id>), so a retried confirmation cannot double-pay.
Why the hold is read, not recomputed
The escrow was sized when the offer was made. Settlement reads that figure back from the hold row rather than recalculating it from the current fee, because recomputing diverges the moment an admin moves the slider between the offer and its acceptance. Over-releasing drains other operations' held funds; under-releasing strands the residue in the buyer's wallet forever.
Creator royalties
A royalty percentage lives on the collection (nft_collection.royaltyPercentage,
default 2.5) and optionally on individual tokens. Settlement uses the
collection's value, clamped to nftMaxRoyaltyPercentage:
effectiveRoyalty = min(collection.royaltyPercentage, nftMaxRoyaltyPercentage)That clamp is applied in every path that quotes or pays a royalty — collection creation, minting, batch preparation, listing creation, direct purchase, offer acceptance, manual auction settlement and the settlement cron — so lowering the ceiling immediately reduces payouts on existing collections. It does not rewrite the stored percentage; it just caps what is paid.
A creator who launched at 10% and later finds the ceiling at 5% starts receiving half as much, with no notification and no change visible on their collection. If you intend to lower it, tell your creators first.
On-chain royalties are set once, at deployment
The collection contract is deployed with the royalty in basis points and a royalty recipient address baked in. Those cannot be changed afterwards, and they are what an external marketplace would honour. The platform's clamp only governs what this marketplace pays.
If the creator had no linked wallet address when the contract was deployed, the recipient is your master wallet — permanently. See Collections.
The royalty ledger is dormant
nft_royalty has a full PENDING / PAID / FAILED lifecycle and nothing
writes a row to it. The single insert that existed was removed because it was
being handed an offer id where a sale id belonged and an nft_creator id where
a user id belonged; both foreign keys failed and the error was swallowed.
Royalties are paid in cash at settlement, so there is no debt to track. Do not build reporting on that table — a "royalties owed" figure read from it can only ever be zero.
The listing fee
nftListingFee is a flat amount, not a percentage, and it defaults to 0. The
listing route reads it and returns it in the response so the seller sees it, but
the platform does not debit any wallet for it — a non-zero listing fee is
charged by the marketplace contract, which takes its own listingFee
parameter at deployment.
If you set the settings value without redeploying or reconfiguring the contract, sellers are shown a fee that is never collected.
A worked example
A 100 USDT offer on a collection with a 7.5% royalty, on a platform with the fee at 2.5% and the ceiling at 5%:
| Step | Amount |
|---|---|
| Buyer's escrow when the offer is made | 102.50 USDT |
| Royalty percentage after the clamp | 5% — not 7.5% |
| Creator receives | 5.00 USDT |
| Seller receives | 95.00 USDT |
Platform receives (Super Admin wallet, adminProfit type NFT_SALE) |
2.50 USDT |
The same item bought at a fixed price for 100 USDT would instead pay the contract's deployed fee, pay the royalty to the address baked into the collection contract, and never touch a Bicrypto wallet at all.
Where to see the numbers
- Admin → NFT → Trading → Sales (
/admin/nft/sale) lists everynft_salerow withmarketplaceFee,royaltyFee,totalFeeandnetAmount. - Admin → NFT → System → Analytics (
/admin/nft/analytics) aggregates volume and revenue. - The core revenue reporting reads
adminProfit, so custodial NFT fees show up alongside every other platform revenue stream as typeNFT_SALE. On-chain fees do not appear there — they are in the contract until you withdraw them.
The analytics endpoint fabricates its fourteen-point sparkline trends from the
current total multiplied by a random factor, and falls back to a hardcoded
Art / Gaming / Music / Sports category split when the real group-by is empty.
The totals are real; those two decorations are not. The moderation dashboard at
/admin/nft deliberately does not use them.