In-app reader
When a marketplace order is settled (MarketBuy / BuyItNow, and auction Claim), the buyer's
payment is split three ways — referral, royalties, and the **seller (market-order owner)
remainder**:
marketOwnerAmount = CurrentBid − referralAmount − royaltiesAmount
Referral and royalties are paid out unconditionally, but the seller remainder is only paid
when positive (computeMarketOwnerAmount returns Ok and pays nothing when the amount is
The combined ceilingroyalty% + referral%
referral % is snapshotted into the order at Sell (MarketOrderData.ReferralPercentage);
royalty % is never snapshotted — it is read live from the asset at buy time
(asset.Royalties.MarketPercentage).
So the listing-time invariant is a time-of-check/time-of-use guarantee only. After a valid
listing, the asset owner raises the royalty MarketPercentage via AssetTrigger → UpdateRoyalties;
at the next buy the live royalty plus the snapshotted referral exceed 100%, and the settlement mints
the overflow. The minted funds land in attacker-controlled referral / royalty addresses.
This was actively exploited on mainnet (see Evidence), minting tens of millions of KLV before
the emergency guard was deployed.
Repository: klever-io/klever-go (node).
Settlement / mint site: core/kapp/market/market.go — executeBuyMarket (L575+),
computeReferralAmount (L361+), computeRoyaltiesAmount (L490+),
computeRoyaltiesFixedDeposit (L443+), computeMarketOwnerAmount (L540+).
Sell combined check (market.go:908), order snapshot of referral but notroyalty (market.go:997), live royalty mutation via
core/kapp/kda/trigger.go — handleUpdateRoyaltiesNFTandSFT (L613+, sets
asset.Royalties.MarketPercentage at L670).
Buy (BuyItNow, market.go:204+) and auction Claim(market.go:705, market.go:731).
FixMarketBuyOverflow activation-epoch flag.
core/kapp/market/market.go — executeBuyMarket (L575+):
referralAmount, _ := tools.ComputePercentageI64(marketOrder.CurrentBid,
int64(marketOrder.ReferralPercentage), ...) // L583: SNAPSHOT referral %
royaltiesAmount, _ := tools.ComputePercentageI64(marketOrder.CurrentBid,
int64(asset.Royalties.MarketPercentage), ...) // L587: LIVE royalty %
marketOwnerAmount := marketOrder.CurrentBid - referralAmount - royaltiesAmount // L591: can go negative
// ---- FIX (FixMarketBuyOverflow), added by the patch ----
if m.forkController.FixMarketBuyOverflow() && marketOwnerAmount 0 { // L593-596
ctx.Receipts().AddError(ctx.ContractID(), common.ErrFieldInvalidRoyalties, common.ErrInvalidValue.Error())
return transaction.Transaction_AmountInvalid, common.ErrInvalidValue
}
m.computeReferralAmount(ctx, marketOrder, referralAmount, currencyID) // pays referral in full
m.computeRoyaltiesFixedDeposit(ctx, marketOrder, asset) // pays fixed royalty (KLV)
m.computeRoyaltiesAmount(ctx, marketOrder, asset, currencyID, royaltiesAmount) // pays % royalty in full
m.computeMarketOwnerAmount(ctx, marketOrder, currencyID, marketOwnerAmount) //
Discussion
Sign in to join the discussion.
Keep reading
Optional: create a free account to save items, track programs, and sync across web + app. Reading stays free.