From b4cbe7ccb030f5bf49cf888438e9b15655ca2fb9 Mon Sep 17 00:00:00 2001 From: marc0olo Date: Sun, 23 Aug 2026 01:27:48 +0200 Subject: [PATCH] fix: an unknown exchange rate is unknown, not zero buildPriceInfo fell back to multiplying by zero when the token was not in the exchange rate cache: priceUSD = roundTo(priceUSD != null ? priceUSD : price * (cache.containsKey(token) ? cache.get(token) : 0)); so a price in a token we have no rate for was published as a confident $0.00, indistinguishable from a genuinely free item. _PriceInfo consumers already render a null usd as "n/a" - see BaseTransformer - so null is the value this should produce. Measured in soonmarket_realtime_event: 49 activity rows carry a non zero price and a zero usd in a token with no exchange rate at all, across FOOBAR, EASY, CYPHR, RDM and PIXEL. The other 9737 zero usd rows are not this bug and must not be "repaired" away. XPR, XUSDC and LOAN all have rates, so the fallback never fired for them. Their zeros come from roundTo defaulting to 2 decimals with amounts genuinely below half a cent: every one of the 9724 XPR rows is between 0.0001 and 5 XPR, and XPR is $0.00286. A bad cached rate would not respect that ceiling, which is what rules it out. roundTo unboxes its argument, so the derived values cannot simply be computed and rounded once priceUSD may be null. royaltyUSD, marketFeeUSD and sellerReceivedPriceUSD are each guarded instead - the last one dereferenced priceUSD unguarded and would otherwise have turned this fix into a new NPE. No repair is possible or needed for the 49 rows: no rate exists for those tokens, so null is the correct value and this produces it going forward. Refs #7 Co-Authored-By: Claude Opus 5 --- .../com/kryptokrauts/shared/BaseMapper.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/kryptokrauts/shared/BaseMapper.java b/src/main/java/com/kryptokrauts/shared/BaseMapper.java index 2cb56ed..5edaa29 100644 --- a/src/main/java/com/kryptokrauts/shared/BaseMapper.java +++ b/src/main/java/com/kryptokrauts/shared/BaseMapper.java @@ -73,26 +73,33 @@ public static _PriceInfo buildPriceInfo( Double royaltyPrice = royalty != null ? royalty * price : null; Double marketFeePrice = marketFee != null ? marketFee * price : null; - priceUSD = - roundTo( - priceUSD != null - ? priceUSD - : price - * (BaseCache.getExchangeRateCache().containsKey(token) - ? BaseCache.getExchangeRateCache().get(token) - : 0)); - Double royaltyUSD = royalty != null ? roundTo(priceUSD * royalty) : null; - Double marketFeeUSD = marketFee != null ? roundTo(priceUSD * marketFee) : null; + // an unknown exchange rate is unknown, not zero. Falling back to zero published a confident + // $0.00 for every price in a token we have no rate for, which is indistinguishable from a + // genuinely free item. _PriceInfo consumers already render a null usd as "n/a", see + // BaseTransformer, so null is the value this should produce + if (priceUSD == null) { + Double rate = BaseCache.getExchangeRateCache().get(token); + priceUSD = rate != null ? price * rate : null; + } + // roundTo unboxes its argument, so every derived value below has to be guarded rather than + // computed blindly once priceUSD can be null + priceUSD = priceUSD != null ? roundTo(priceUSD) : null; + + Double royaltyUSD = priceUSD != null && royalty != null ? roundTo(priceUSD * royalty) : null; + Double marketFeeUSD = + priceUSD != null && marketFee != null ? roundTo(priceUSD * marketFee) : null; // seller receives info Double sellerReceivedPrice = roundTo( price * (1 - (royalty != null ? royalty : 0) - (marketFee != null ? marketFee : 0))); Double sellerReceivedPriceUSD = - roundTo( - priceUSD - - (royaltyUSD != null ? royaltyUSD : 0) - - (marketFeeUSD != null ? marketFeeUSD : 0)); + priceUSD != null + ? roundTo( + priceUSD + - (royaltyUSD != null ? royaltyUSD : 0) + - (marketFeeUSD != null ? marketFeeUSD : 0)) + : null; return _PriceInfo.builder() .paymentAsset(token)