Summary
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
<= 0). When referral% + royalty% exceeds 100% of the bid, marketOwnerAmount goes negative
and is silently skipped — so the marketplace pays out more KLV / sale currency than the buyer
paid in, minting the difference out of thin air.
The combined ceiling royalty% + referral% <= 100% is checked once, at listing time (Sell).
But the two percentages are sourced asymmetrically at settlement:
- 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.
Affected component
- Repository:
klever-io/klever-go (node).
- Settlement / mint site:
core/kapp/market/market.go — executeBuyMarket (L575+),
computeReferralAmount (L361+), computeRoyaltiesAmount (L490+),
computeRoyaltiesFixedDeposit (L443+), computeMarketOwnerAmount (L540+).
- TOCTOU sources:
Sell combined check (market.go:908), order snapshot of referral but not
royalty (market.go:997), live royalty mutation via
core/kapp/kda/trigger.go — handleUpdateRoyaltiesNFTandSFT (L613+, sets
asset.Royalties.MarketPercentage at L670).
- Reachable from both
Buy (BuyItNow, market.go:204+) and auction Claim
(market.go:705, market.go:731).
- Pre-fix: not gated by any fork flag — exploitable on mainnet. The fix is gated behind the new
FixMarketBuyOverflow activation-epoch flag.
Root cause
1. Settlement pays referral + royalty unconditionally, seller remainder only if positive
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) // <-- skips when <= 0
computeMarketOwnerAmount (L540-542) — the silent skip:
func (m *marketKapp) computeMarketOwnerAmount(... marketOwnerAmount int64) (... , error) {
if marketOwnerAmount <= 0 {
return transaction.Transaction_Ok, nil // negative seller share dropped, NO error
}
// ... AddToBalance(marketOwnerAmount) ...
}
Meanwhile computeReferralAmount (L376) and computeRoyaltiesAmount (L515) each AddToBalance(...)
the full computed amount with no matching debit from the buyer beyond the single
bidderAcc.SubFromBalance(amount) taken in Buy (market.go:301).
Conservation breaks: buyer is debited bid once; recipients are credited
referralAmount + royaltiesAmount. When that sum > bid, the surplus
(referralAmount + royaltiesAmount − bid) is minted.
2. The combined ≤100% invariant is enforced only at listing time
Sell (market.go:908) correctly rejects a listing whose combined cut exceeds 100%:
if asset.Royalties.MarketPercentage + marketplace.ReferralPercentage > core.HundredPercent {
return transaction.Transaction_ParameterInvalid, common.ErrInvalidValue
}
…and snapshots referral into the order, but not royalty (market.go:997-998):
marketOrder := &kapps.MarketOrderData{
// ...
ReferralPercentage: marketplace.ReferralPercentage, // snapshotted
RoyaltiesFixedDeposit: asset.Royalties.MarketFixed, // snapshotted
// NOTE: asset.Royalties.MarketPercentage is NOT snapshotted -> read live at buy
}
MarketOrderData has no field for the royalty percentage (kapps/market.pb.go), so settlement
always re-reads it live from the (mutable) asset.
3. Royalty % is mutable after listing
core/kapp/kda/trigger.go — handleUpdateRoyaltiesNFTandSFT (L613+) lets the asset owner overwrite
asset.Royalties.MarketPercentage (L670) with only a per-field <= 100% check (CheckValid100Params,
L651) — it has no knowledge of any outstanding marketplace listing's snapshotted referral. So the
owner can list at, e.g., referral 100% / royalty 0% (sum 100%, passes Sell), then raise royalty to
100%, making the buy-time sum 200%.
The shipped emergency-guard source documents this exact vector:
"The royalty percentage is read live at buy time, so a listing made now can be weaponised later
via UpdateRoyalties." (common/emergencyGuard.go)
Net effect: referralAmount + royaltiesAmount = bid + bid = 2·bid; marketOwnerAmount = −bid
(skipped); bid KLV minted per settlement, paid to attacker-controlled addresses.
Proof of Concept
A. Committed regression test (deterministic, runnable today)
core/kapp/market/market_test.go — TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation.
It builds an order with ReferralPercentage = 100% and an asset with MarketPercentage = 100%
(the attacker is both the referral and the royalty address), then settles a bid of
25,600,000 KLV (25600000000000 base units):
go test ./core/kapp/market/ -run TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation -v
FixDisabled_MintsKLVFromThinAir: settlement returns Ok; the attacker address ends with
2·bid credited while only bid was paid in — i.e. bid KLV minted.
FixEnabled_RejectsInflation: with FixMarketBuyOverflow on, settlement returns
Transaction_AmountInvalid and the attacker balance stays 0 — no payout runs.
B. End-to-end on a local node (the real attack path)
A single-node local network is sufficient. The exploit is four transactions from one ordinary
funded account; nothing privileged is required.
- Create an NFT collection you own, with
royalties.marketPercentage = 0 and a royalties
address you control.
- Create a marketplace with
referralPercentage = 10000 (100%) and a referral address you
control (CreateMarketplace).
- List one NFT for sale (
Sell) on that marketplace. The Sell check passes because
0 (royalty) + 10000 (referral) = 10000 = HundredPercent. The order snapshots
ReferralPercentage = 10000.
- Raise the royalty on the asset to 100% (
AssetTrigger / UpdateRoyalties,
marketPercentage = 10000). Allowed: the per-field check passes and the live combined invariant
is never re-evaluated against the open listing.
- Buy the listing (
MarketBuy) from a second account (or settle the auction via Claim).
referralAmount = bid, royaltiesAmount = bid, marketOwnerAmount = −bid (skipped). Your
referral + royalty addresses receive 2·bid; the buyer paid bid; bid KLV is minted.
Because the attacker controls buyer, seller, referral and royalty addresses, the only real cost is
transaction fees; the cycle is repeatable until supply targets are met.
Evidence
Regression test (local, verbatim)
=== RUN TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation
=== RUN TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixDisabled_MintsKLVFromThinAir
=== RUN TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixEnabled_RejectsInflation
--- PASS: TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation (0.00s)
--- PASS: TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixDisabled_MintsKLVFromThinAir (0.00s)
--- PASS: TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation/FixEnabled_RejectsInflation (0.00s)
PASS
ok github.com/klever-io/klever-go/core/kapp/market 0.279s
FixDisabled asserts the attacker balance equals 2·bid = 51,200,000 KLV for a single settlement
(bid = 25,600,000 KLV), with bid of that minted. FixEnabled asserts rejection and a 0
balance.
Mainnet exploitation (observed)
The bug was exploited in production, and was detected and characterised externally by the
community monitoring project KleverPuls / kpulse.tech before the root
cause was known internally. Over a ~24h window kpulse isolated a single wallet (opened
2026-06-04, ~204 transactions in ~24h, funded only by a ~242K KLV KuCoin withdrawal, no
treasury/foundation funding) that:
- self-issued 3 NFT collections named "InflationPOC" and wash-traded one (
NFLATION-ESGO/1)
29 times through self-created marketplaces — ~$450K of artificial, economically empty NFT
volume;
- swapped the proceeds KLV → USDC / USDT / WBTC / WETH on KleverSwap and bridged ~$72K of value
to Ethereum via wrapped-asset burns over 24h (USDC −12,487 ≈ $12.5K; USDT −26,362 ≈ $26.4K; WBTC
−0.31 ≈ $19.6K; WETH −7.55 ≈ $14K);
- surfaced a spurious "1.86B KLV outflow" headline that kpulse correctly identified as a
wash-trade receipt-doubling artifact with small real net KLV flow.
That "doubling of marketplace receipts" is precisely the on-chain signature of this bug: each
abusive settlement pays out a referral cut (bid) plus a royalty cut (bid) while the buyer paid
only bid once — the market contract emits ~2× the value it took in, which is the mint. The
attacker's self-issued collection and self-created marketplaces are exactly the self-dealing setup the
regression test reproduces (the test reuses the real on-chain identifiers: collectionID = "NFLATION-ESGO", asset 1, market name "Inflation Market").
kpulse could not determine the cause from on-chain data alone and flagged the activity for
confirmation; the Klever core team then traced it to the referral+royalty settlement defect described
above and shipped the emergency guard + protocol fix.
Each abusive settlement minted one bid of KLV; the observed bid was 25,600,000 KLV, repeated and
funnelled through a short hop chain before being swapped and bridged. The emergency guard
(common/emergencyGuard.go) blocks the following observed sender public keys (hex):
| Public key (hex) |
Address |
Role (observed) |
54ea28e527d4136508be955374afa54a8c25c19a48c674f412f7ce02db0f4e1b |
klv12n4z3ef86sfk2z97j4fhfta9f2xztsv6frr8faqj7l8q9kc0fcdsfjfqez |
root / minter |
bb687dbba23e1844fec674a32cb8809f0d3207506c53fc3d637e40dc56708d63 |
klv1hd58mwaz8cvyflkxwj3jewyqnuxnyp6sd3flc0tr0eqdc4ns343skngdjq |
collector hop (~125M KLV) |
77388d3dfe6cd88e8da723254c11abf3d9cccb6fb77b000e5038fc3ff92b964d |
klv1wuug6007dnvgard8yvj5cydt70vuejm0kaasqrjs8r7rl7ftjexsglalf6 |
direct recipient (25.6M, idle) |
a196789b026f996867f08317cc6c5a4eb9ad3a59b1be3716420bc8692d4c3048 |
klv15xt83xczd7vkselssvtucmz6f6u66wjekxlrw9jzp0yxjt2vxpyq2nawrw |
hop-2 recipient (25M) |
The single-bid per-settlement size (25.6M KLV) matches the "direct recipient, 25.6M" entry, and the
~125M at the collector hop is consistent with roughly five abusive settlements.
Impact
- Unbounded inflation of KLV (and of any sale currency used for the listing), repeatable for only
transaction fees, by any account that creates its own collection + marketplace.
- The minted KLV is created by direct
AddToBalance to attacker addresses (no tracked Mint), so
the asset's booked supply does not change — the inflation is off the books and only detectable
by summing balances / auditing receipts (it surfaces on-chain as doubled marketplace receipts).
- Realized impact (observed): the attacker minted KLV via ~29 self-dealt settlements, swapped to
stable/wrapped assets on KleverSwap, and off-ramped ~$72K to Ethereum via the bridge (USDC,
USDT, WBTC, WETH) before the emergency guard halted the activity, alongside ~$450K of artificial
NFT wash-trade volume.
- Total loss of economic integrity for all KLV / token holders.
Who can exploit it / prerequisites
- Any account that can pay the one-time collection-create + marketplace-create fees and tx
fees. No roles, admin, or allowlist.
- Any client. The settlement is triggered by standard
MarketBuy / Claim contracts POSTed to
the public, unauthenticated /transaction/send RPC (network/api/transaction/routes.go,
SendTX / BroadcastTX). The operator CLI, the SDKs, or a hand-signed curl all work.
- Deterministic; the abusive state is reached with one extra
UpdateRoyalties after a normal listing.
Remediation
Shipped as a layered response (embargoed):
Layer 0 — emergency guard (deployed first, fork-proof) — GHSA-p7gw rc1
common/emergencyGuard.go + data/transaction/emergencyGuard.go: matching transactions are kept
out of blocks this node proposes (core/process/block/preprocess/transactions.go) and refused at
the node API (node.go SendTransaction / SendBulkTransactions). It never changes block
validity, so a partial-fleet rollout cannot fork the chain. It blocks the known attacker senders
(all contract types) plus all MarketBuy, Sell, and CreateMarketplace / ConfigMarketplace
operations while the protocol fix rolls out. Enforcement is by proposer cooperation, not protocol —
coverage equals the share of block producers running the guard.
Layer 1 — protocol fix (consensus, epoch-gated) — GHSA-p7gw rc2
core/kapp/market/market.go:593 rejects the settlement when marketOwnerAmount < 0, before any
payout runs, returning Transaction_AmountInvalid. Gated behind the new FixMarketBuyOverflow
activation-epoch flag (config/enableEpochs.*, core/fork/forks.go, core/interface.go) so
historical blocks reprocess identically. Covered by
TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation.
Recommended hardening (defense in depth)
- Snapshot the royalty % into
MarketOrderData at Sell (as referral already is) and pay from
the snapshot, eliminating the TOCTOU entirely; or re-evaluate the combined
referral% + royalty% <= 100% invariant at settlement.
- Treat a negative seller remainder as a hard error everywhere, and only treat exactly
0 as a
no-op skip (computeMarketOwnerAmount), so a future regression aborts the tx instead of minting.
- After splitting a payment pool, assert conservation (
referral + royalties + ownerShare == bid)
so any drift aborts the transaction.
Notes
- Triggered by both BuyItNow (
Buy) and auction settlement (Claim).
- The same family of "pay full cut, silently drop the negative remainder" minting also exists in the
royalty-split paths and is tracked separately under GHSA-cgc5-v3f2-8m2v (split-royalty uint32
overflow). This advisory covers the top-level referral+royalty > bid case; the
FixMarketBuyOverflow guard here only checks marketOwnerAmount, not intra-split over-payments.
Acknowledgments
- KleverPuls / kpulse.tech — community monitoring project that first
detected and characterised the exploitation in the wild. kpulse isolated the attacker wallet and
its "InflationPOC" collections, identified the marketplace wash-trading of NFLATION-ESGO/1 and the
KleverSwap → bridge off-ramp (~$72K to Ethereum), and flagged the anomalous doubling of
marketplace receipts — the exact on-chain signature of this bug — prompting the incident response
that led to this fix. The root cause was then identified and remediated by the Klever core team.
Source
- Vulnerable / fixed code:
core/kapp/market/market.go:540-545,575-596,908,997-998,
core/kapp/kda/trigger.go:613-676, core/process/kda/assetHelper.go:101,
tools/converters.go:102, core/constants.go:18.
- Emergency guard:
common/emergencyGuard.go, data/transaction/emergencyGuard.go,
core/process/block/preprocess/transactions.go, node/node.go.
- Fork flag:
config/enableEpochs.go, config/node/enableEpochs.yaml, core/fork/forks.go,
core/interface.go.
- Regression test:
core/kapp/market/market_test.go
(TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation).
References
Summary
When a marketplace order is settled (
MarketBuy/BuyItNow, and auctionClaim), the buyer'spayment is split three ways — referral, royalties, and the seller (market-order owner)
remainder:
Referral and royalties are paid out unconditionally, but the seller remainder is only paid
when positive (
computeMarketOwnerAmountreturnsOkand pays nothing when the amount is<= 0). Whenreferral% + royalty%exceeds 100% of the bid,marketOwnerAmountgoes negativeand is silently skipped — so the marketplace pays out more KLV / sale currency than the buyer
paid in, minting the difference out of thin air.
The combined ceiling
royalty% + referral% <= 100%is checked once, at listing time (Sell).But the two percentages are sourced asymmetrically at settlement:
Sell(MarketOrderData.ReferralPercentage);(
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
MarketPercentageviaAssetTrigger → 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.
Affected component
klever-io/klever-go(node).core/kapp/market/market.go—executeBuyMarket(L575+),computeReferralAmount(L361+),computeRoyaltiesAmount(L490+),computeRoyaltiesFixedDeposit(L443+),computeMarketOwnerAmount(L540+).Sellcombined check (market.go:908), order snapshot of referral but notroyalty (
market.go:997), live royalty mutation viacore/kapp/kda/trigger.go—handleUpdateRoyaltiesNFTandSFT(L613+, setsasset.Royalties.MarketPercentageat L670).Buy(BuyItNow,market.go:204+) and auctionClaim(
market.go:705,market.go:731).FixMarketBuyOverflowactivation-epoch flag.Root cause
1. Settlement pays referral + royalty unconditionally, seller remainder only if positive
core/kapp/market/market.go—executeBuyMarket(L575+):computeMarketOwnerAmount(L540-542) — the silent skip:Meanwhile
computeReferralAmount(L376) andcomputeRoyaltiesAmount(L515) eachAddToBalance(...)the full computed amount with no matching debit from the buyer beyond the single
bidderAcc.SubFromBalance(amount)taken inBuy(market.go:301).Conservation breaks: buyer is debited
bidonce; recipients are creditedreferralAmount + royaltiesAmount. When that sum> bid, the surplus(referralAmount + royaltiesAmount − bid)is minted.2. The combined ≤100% invariant is enforced only at listing time
Sell(market.go:908) correctly rejects a listing whose combined cut exceeds 100%:…and snapshots referral into the order, but not royalty (
market.go:997-998):MarketOrderDatahas no field for the royalty percentage (kapps/market.pb.go), so settlementalways re-reads it live from the (mutable) asset.
3. Royalty % is mutable after listing
core/kapp/kda/trigger.go—handleUpdateRoyaltiesNFTandSFT(L613+) lets the asset owner overwriteasset.Royalties.MarketPercentage(L670) with only a per-field<= 100%check (CheckValid100Params,L651) — it has no knowledge of any outstanding marketplace listing's snapshotted referral. So the
owner can list at, e.g., referral 100% / royalty 0% (sum 100%, passes
Sell), then raise royalty to100%, making the buy-time sum 200%.
Net effect:
referralAmount + royaltiesAmount = bid + bid = 2·bid;marketOwnerAmount = −bid(skipped);
bidKLV minted per settlement, paid to attacker-controlled addresses.Proof of Concept
A. Committed regression test (deterministic, runnable today)
core/kapp/market/market_test.go—TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation.It builds an order with
ReferralPercentage = 100%and an asset withMarketPercentage = 100%(the attacker is both the referral and the royalty address), then settles a
bidof25,600,000 KLV(25600000000000base units):go test ./core/kapp/market/ -run TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation -vFixDisabled_MintsKLVFromThinAir: settlement returnsOk; the attacker address ends with2·bidcredited while onlybidwas paid in — i.e.bidKLV minted.FixEnabled_RejectsInflation: withFixMarketBuyOverflowon, settlement returnsTransaction_AmountInvalidand the attacker balance stays0— no payout runs.B. End-to-end on a local node (the real attack path)
A single-node local network is sufficient. The exploit is four transactions from one ordinary
funded account; nothing privileged is required.
royalties.marketPercentage = 0and a royaltiesaddress you control.
referralPercentage = 10000(100%) and a referral address youcontrol (
CreateMarketplace).Sell) on that marketplace. TheSellcheck passes because0 (royalty) + 10000 (referral) = 10000 = HundredPercent. The order snapshotsReferralPercentage = 10000.AssetTrigger / UpdateRoyalties,marketPercentage = 10000). Allowed: the per-field check passes and the live combined invariantis never re-evaluated against the open listing.
MarketBuy) from a second account (or settle the auction viaClaim).referralAmount = bid,royaltiesAmount = bid,marketOwnerAmount = −bid(skipped). Yourreferral + royalty addresses receive
2·bid; the buyer paidbid;bidKLV is minted.Because the attacker controls buyer, seller, referral and royalty addresses, the only real cost is
transaction fees; the cycle is repeatable until supply targets are met.
Evidence
Regression test (local, verbatim)
FixDisabledasserts the attacker balance equals2·bid = 51,200,000 KLVfor a single settlement(
bid = 25,600,000 KLV), withbidof that minted.FixEnabledasserts rejection and a0balance.
Mainnet exploitation (observed)
The bug was exploited in production, and was detected and characterised externally by the
community monitoring project KleverPuls / kpulse.tech before the root
cause was known internally. Over a ~24h window kpulse isolated a single wallet (opened
2026-06-04, ~204 transactions in ~24h, funded only by a ~242K KLV KuCoin withdrawal, no
treasury/foundation funding) that:
NFLATION-ESGO/1)29 times through self-created marketplaces — ~$450K of artificial, economically empty NFT
volume;
to Ethereum via wrapped-asset burns over 24h (USDC −12,487 ≈ $12.5K; USDT −26,362 ≈ $26.4K; WBTC
−0.31 ≈ $19.6K; WETH −7.55 ≈ $14K);
wash-trade receipt-doubling artifact with small real net KLV flow.
That "doubling of marketplace receipts" is precisely the on-chain signature of this bug: each
abusive settlement pays out a referral cut (
bid) plus a royalty cut (bid) while the buyer paidonly
bidonce — the market contract emits ~2× the value it took in, which is the mint. Theattacker's self-issued collection and self-created marketplaces are exactly the self-dealing setup the
regression test reproduces (the test reuses the real on-chain identifiers:
collectionID = "NFLATION-ESGO", asset1, market name "Inflation Market").kpulse could not determine the cause from on-chain data alone and flagged the activity for
confirmation; the Klever core team then traced it to the referral+royalty settlement defect described
above and shipped the emergency guard + protocol fix.
Each abusive settlement minted one
bidof KLV; the observedbidwas25,600,000 KLV, repeated andfunnelled through a short hop chain before being swapped and bridged. The emergency guard
(
common/emergencyGuard.go) blocks the following observed sender public keys (hex):54ea28e527d4136508be955374afa54a8c25c19a48c674f412f7ce02db0f4e1bklv12n4z3ef86sfk2z97j4fhfta9f2xztsv6frr8faqj7l8q9kc0fcdsfjfqezbb687dbba23e1844fec674a32cb8809f0d3207506c53fc3d637e40dc56708d63klv1hd58mwaz8cvyflkxwj3jewyqnuxnyp6sd3flc0tr0eqdc4ns343skngdjq77388d3dfe6cd88e8da723254c11abf3d9cccb6fb77b000e5038fc3ff92b964dklv1wuug6007dnvgard8yvj5cydt70vuejm0kaasqrjs8r7rl7ftjexsglalf6a196789b026f996867f08317cc6c5a4eb9ad3a59b1be3716420bc8692d4c3048klv15xt83xczd7vkselssvtucmz6f6u66wjekxlrw9jzp0yxjt2vxpyq2nawrwThe single-
bidper-settlement size (25.6M KLV) matches the "direct recipient, 25.6M" entry, and the~125M at the collector hop is consistent with roughly five abusive settlements.
Impact
transaction fees, by any account that creates its own collection + marketplace.
AddToBalanceto attacker addresses (no trackedMint), sothe asset's booked supply does not change — the inflation is off the books and only detectable
by summing balances / auditing receipts (it surfaces on-chain as doubled marketplace receipts).
stable/wrapped assets on KleverSwap, and off-ramped ~$72K to Ethereum via the bridge (USDC,
USDT, WBTC, WETH) before the emergency guard halted the activity, alongside ~$450K of artificial
NFT wash-trade volume.
Who can exploit it / prerequisites
fees. No roles, admin, or allowlist.
MarketBuy/Claimcontracts POSTed tothe public, unauthenticated
/transaction/sendRPC (network/api/transaction/routes.go,SendTX/BroadcastTX). TheoperatorCLI, the SDKs, or a hand-signedcurlall work.UpdateRoyaltiesafter a normal listing.Remediation
Shipped as a layered response (embargoed):
Layer 0 — emergency guard (deployed first, fork-proof) —
GHSA-p7gwrc1common/emergencyGuard.go+data/transaction/emergencyGuard.go: matching transactions are keptout of blocks this node proposes (
core/process/block/preprocess/transactions.go) and refused atthe node API (
node.goSendTransaction/SendBulkTransactions). It never changes blockvalidity, so a partial-fleet rollout cannot fork the chain. It blocks the known attacker senders
(all contract types) plus all
MarketBuy,Sell, andCreateMarketplace/ConfigMarketplaceoperations while the protocol fix rolls out. Enforcement is by proposer cooperation, not protocol —
coverage equals the share of block producers running the guard.
Layer 1 — protocol fix (consensus, epoch-gated) —
GHSA-p7gwrc2core/kapp/market/market.go:593rejects the settlement whenmarketOwnerAmount < 0, before anypayout runs, returning
Transaction_AmountInvalid. Gated behind the newFixMarketBuyOverflowactivation-epoch flag (
config/enableEpochs.*,core/fork/forks.go,core/interface.go) sohistorical blocks reprocess identically. Covered by
TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation.Recommended hardening (defense in depth)
MarketOrderDataatSell(as referral already is) and pay fromthe snapshot, eliminating the TOCTOU entirely; or re-evaluate the combined
referral% + royalty% <= 100%invariant at settlement.0as ano-op skip (
computeMarketOwnerAmount), so a future regression aborts the tx instead of minting.referral + royalties + ownerShare == bid)so any drift aborts the transaction.
Notes
Buy) and auction settlement (Claim).royalty-split paths and is tracked separately under GHSA-cgc5-v3f2-8m2v (split-royalty
uint32overflow). This advisory covers the top-level referral+royalty > bid case; the
FixMarketBuyOverflowguard here only checksmarketOwnerAmount, not intra-split over-payments.Acknowledgments
detected and characterised the exploitation in the wild. kpulse isolated the attacker wallet and
its "InflationPOC" collections, identified the marketplace wash-trading of
NFLATION-ESGO/1and theKleverSwap → bridge off-ramp (~$72K to Ethereum), and flagged the anomalous doubling of
marketplace receipts — the exact on-chain signature of this bug — prompting the incident response
that led to this fix. The root cause was then identified and remediated by the Klever core team.
Source
core/kapp/market/market.go:540-545,575-596,908,997-998,core/kapp/kda/trigger.go:613-676,core/process/kda/assetHelper.go:101,tools/converters.go:102,core/constants.go:18.common/emergencyGuard.go,data/transaction/emergencyGuard.go,core/process/block/preprocess/transactions.go,node/node.go.config/enableEpochs.go,config/node/enableEpochs.yaml,core/fork/forks.go,core/interface.go.core/kapp/market/market_test.go(
TestMarketKApp_ExecuteBuyMarket_RoyaltyReferralInflation).References