diff --git a/service/src/main/kotlin/io/provenance/explorer/domain/extensions/NavEventExtensions.kt b/service/src/main/kotlin/io/provenance/explorer/domain/extensions/NavEventExtensions.kt new file mode 100644 index 00000000..62ac09bd --- /dev/null +++ b/service/src/main/kotlin/io/provenance/explorer/domain/extensions/NavEventExtensions.kt @@ -0,0 +1,29 @@ +package io.provenance.explorer.domain.extensions + +import io.provenance.explorer.model.base.USD_LOWER +import io.provlabs.flow.api.NavEvent +import java.math.BigDecimal +import java.math.RoundingMode + +/** + * Calculates the USD price per unit for a NAV event. + * + * The `priceAmount` is in dollar millis (e.g., 1234 = $1.234) and is divided by the volume to get the price per unit. + * If the `priceDenom` is not "usd" or the volume is 0, it returns `BigDecimal.ZERO`. + * + * @return The USD price per unit or `BigDecimal.ZERO` if not a USD event or volume is 0. + */ +fun NavEvent.calculateUsdPricePerUnit(): BigDecimal { + if (this.priceDenom != USD_LOWER) { + return BigDecimal.ZERO + } + + if (this.volume == 0L) { + return BigDecimal.ZERO + } + + return BigDecimal(this.priceAmount) + .setScale(3, RoundingMode.DOWN) + .divide(BigDecimal(1000), RoundingMode.DOWN) + .divide(BigDecimal(this.volume), 3, RoundingMode.DOWN) +} diff --git a/service/src/main/kotlin/io/provenance/explorer/service/PricingService.kt b/service/src/main/kotlin/io/provenance/explorer/service/PricingService.kt index 651de33c..f029ea3a 100644 --- a/service/src/main/kotlin/io/provenance/explorer/service/PricingService.kt +++ b/service/src/main/kotlin/io/provenance/explorer/service/PricingService.kt @@ -10,6 +10,7 @@ import io.provenance.explorer.domain.core.logger import io.provenance.explorer.domain.entities.AssetPricingRecord import io.provenance.explorer.domain.entities.MarkerCacheRecord import io.provenance.explorer.domain.entities.MarkerCacheTable +import io.provenance.explorer.domain.extensions.calculateUsdPricePerUnit import io.provenance.explorer.domain.extensions.toDateTime import io.provenance.explorer.domain.models.explorer.AssetPricing import io.provenance.explorer.grpc.flow.FlowApiGrpcClient @@ -55,7 +56,7 @@ class PricingService( marker = marker, markerDenom = price.denom, pricingDenom = price.priceDenom, - pricingAmount = BigDecimal(price.priceAmount).setScale(3).divide(BigDecimal(1000)), + pricingAmount = price.calculateUsdPricePerUnit(), timestamp = DateTime(price.blockTime * 1000) ) } else { diff --git a/service/src/test/kotlin/io/provenance/explorer/domain/extensions/NavEventExtensionsKtTest.kt b/service/src/test/kotlin/io/provenance/explorer/domain/extensions/NavEventExtensionsKtTest.kt new file mode 100644 index 00000000..62611a62 --- /dev/null +++ b/service/src/test/kotlin/io/provenance/explorer/domain/extensions/NavEventExtensionsKtTest.kt @@ -0,0 +1,58 @@ +package io.provenance.explorer.domain.extensions + +import io.provlabs.flow.api.NavEvent +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class NavEventExtensionsKtTest { + + @Test + fun `test calculateUsdPricePerUnit for flow nav events`() { + val usdDenom = "usd" + val nonUsdDenom = "non-usd" + + val navEventUsdVolume1Price1 = NavEvent.newBuilder() + .setPriceAmount(1) + .setPriceDenom(usdDenom) + .setVolume(1L) + .build() + + val navEventUsdVolume1Price12 = NavEvent.newBuilder() + .setPriceAmount(12) + .setPriceDenom(usdDenom) + .setVolume(1L) + .build() + + val navEventUsdVolume1Price123 = NavEvent.newBuilder() + .setPriceAmount(123) + .setPriceDenom(usdDenom) + .setVolume(1L) + .build() + + val navEventUsdVolume1Price1234 = NavEvent.newBuilder() + .setPriceAmount(1234) + .setPriceDenom(usdDenom) + .setVolume(1L) + .build() + + val navEventNonUsd = NavEvent.newBuilder() + .setPriceAmount(1234) + .setPriceDenom(nonUsdDenom) + .setVolume(1L) + .build() + + val navEventZeroVolume = NavEvent.newBuilder() + .setPriceAmount(1234) + .setPriceDenom(usdDenom) + .setVolume(0L) + .build() + + assertEquals(BigDecimal("0.001"), navEventUsdVolume1Price1.calculateUsdPricePerUnit(), "Price amount 1 should be converted to 0.001") + assertEquals(BigDecimal("0.012"), navEventUsdVolume1Price12.calculateUsdPricePerUnit(), "Price amount 12 should be converted to 0.012") + assertEquals(BigDecimal("0.123"), navEventUsdVolume1Price123.calculateUsdPricePerUnit(), "Price amount 123 should be converted to 0.123") + assertEquals(BigDecimal("1.234"), navEventUsdVolume1Price1234.calculateUsdPricePerUnit(), "Price amount 1234 should be converted to 1.234") + assertEquals(BigDecimal.ZERO, navEventNonUsd.calculateUsdPricePerUnit(), "Non-USD denomination should return 0") + assertEquals(BigDecimal.ZERO, navEventZeroVolume.calculateUsdPricePerUnit(), "Zero volume should return 0") + } +}