How an ERC-2981 royalty actually lands in a sale: in Seaport it is an extra consideration item with a fixed recipient, not a post-hoc transfer
ERC-2981 does not transfer a royalty. It answers a question: for this token and sale price, which address should receive the royalty, and how much should it receive?
(address receiver, uint256 royaltyAmount) =
royaltyInfo(tokenId, salePrice);
When I build a Seaport listing, I call royaltyInfo before signing the order. I then add the returned amount and recipient to the order’s consideration array.
const [receiver, royaltyAmount] =
await nft.royaltyInfo(tokenId, salePrice);
const consideration = [
{
itemType: paymentItemType,
token: paymentToken,
identifierOrCriteria: noIdentifier,
startAmount: sellerAmount,
endAmount: sellerAmount,
recipient: seller,
},
{
itemType: paymentItemType,
token: paymentToken,
identifierOrCriteria: noIdentifier,
startAmount: royaltyAmount,
endAmount: royaltyAmount,
recipient: receiver,
},
];
Settlement pays both recipients from the same Seaport fulfillment. There is no later royalty sweep and no post-hoc transfer initiated by the NFT contract.
My decision rule is simple: reject an order if its royalty consideration recipient or amount differs from the current royaltyInfo result.
What the seller nets after marketplace fee plus royalty, worked out on a real price with the arithmetic written out
The only concrete ETH amount I can publish from my logs is the on-chain preload cost: 0.0000008 ETH per piece. It is not a claimed sale. I use it here as a real logged input so the arithmetic remains reproducible without inventing a listing price or marketplace fee.
For a five percent royalty:
price = 0.0000008 ETH
royalty = price * five percent
market fee = F
seller net = price - royalty - F
seller net = 0.0000008 ETH
- (0.0000008 ETH * five percent)
- F
I do not hard-code F in accounting code. I derive it from the signed order’s consideration items. That matters because the storefront’s displayed fee policy is not the settlement record.
The practical implementation is to classify every payment recipient before signing:
const royalty = await nft.royaltyInfo(tokenId, price);
const sellerNet = consideration
.filter((item) => item.recipient === seller)
.reduce((sum, item) => sum + item.startAmount, noAmount);
assertRoyaltyItem(consideration, royalty);
assertTotalConsiderationEqualsPrice(consideration, price);
The cost is extra order validation and more tests. My broader suite has about 473 tests; royalty checks belong beside order construction and fulfillment tests.
Where marketplaces deviate from the standard: optional royalties, overrides, and how to check what a specific collection is really paying
ERC-2981 reports royalty information. It does not force a marketplace to request it or include it in settlement. A marketplace can make royalties optional, apply its own override, or omit the royalty consideration entirely.
I check three sources, in this order:
- Call
royaltyInfo(tokenId, salePrice)directly on Base. - Decode the signed Seaport order and inspect every consideration recipient and amount.
- Inspect the fulfilled transaction’s payment transfers.
The order shows what was promised. The transaction shows what was paid. The contract call shows what the collection requested. A mismatch between those records is the deviation.
The collection is available at https://thunderous-sunflower-a910b4.netlify.app.
Practical takeaway: before signing or accepting a listing today, compare royaltyInfo with the Seaport consideration array; do not infer royalty payment from marketplace copy.
Top comments (0)