diff --git a/CHANGELOG.md b/CHANGELOG.md index 644c0419..52adc93e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,11 @@ Types of changes (Stanzas): Ref: https://keepachangelog.com/en/1.0.0/ --> -## Unreleased +## [v7.0.3](https://github.com/provenance-io/explorer-service/releases/tag/v7.0.3) - 2025-03-10 + +Version 7.0.3 adds new cards for total NAV values based on metadata module NAV +events like creating/adding NAV to a scope and reduction in NAV balance via +payment/sales. ## [v7.0.2](https://github.com/provenance-io/explorer-service/releases/tag/v7.0.2) - 2025-02-27 diff --git a/service/src/main/kotlin/io/provenance/explorer/domain/entities/NavEvents.kt b/service/src/main/kotlin/io/provenance/explorer/domain/entities/NavEvents.kt index 554d8235..689093c5 100644 --- a/service/src/main/kotlin/io/provenance/explorer/domain/entities/NavEvents.kt +++ b/service/src/main/kotlin/io/provenance/explorer/domain/entities/NavEvents.kt @@ -11,6 +11,7 @@ import org.jetbrains.exposed.sql.insertIgnore import org.jetbrains.exposed.sql.javatime.JavaLocalDateTimeColumnType import org.jetbrains.exposed.sql.javatime.datetime import org.jetbrains.exposed.sql.transactions.transaction +import java.math.BigDecimal import java.sql.ResultSet import java.time.LocalDateTime @@ -180,6 +181,55 @@ class NavEventsRecord(id: EntityID) : IntEntity(id) { ) } } + + fun navPricesBetweenDays( + startDateTime: LocalDateTime, + endDateTime: LocalDateTime + ) = transaction { + val query = """ + select c.denom,c.source, c.scope_id, + c.price_amount as current_amount, + c.volume as current_volume, + max(c.block_time) as current_block_time, + p.price_amount as previous_amount, + p.volume as previous_volume, + p.block_time as previous_block_time + from nav_events c, + (select p.scope_id, p.price_amount, p.volume, max(p.block_time) as block_time + from nav_events p + where p.source = 'metadata' + and date_trunc('DAYS', block_time) = ? + group by p.scope_id, p.price_amount, p.volume) as p + where c.source = 'metadata' + and date_trunc('DAYS', c.block_time) = ? + and c.scope_id = p.scope_id + group by c.denom, c.source, c.scope_id, c.price_amount, c.volume, p.price_amount, p.volume, p.block_time + """.trimIndent() + + val args = mutableListOf>( + Pair(JavaLocalDateTimeColumnType(), startDateTime), + Pair(JavaLocalDateTimeColumnType(), endDateTime) + ) + query.execAndMap(args) { + val map = mutableMapOf() + (1..it.metaData.columnCount).forEach { index -> + map[it.metaData.getColumnName(index)] = it.getObject(index) + } + map // return a list of map of column name/value + } + } + + fun totalMetadataNavs() = transaction { + val query = """ + select sum(price_amount) + from (select scope_id, price_amount, row_number() over (partition by scope_id order by block_height desc) as r + from nav_events where source = 'metadata' and price_amount > 0) s + where r = 1 + """.trimIndent() + query.execAndMap { + BigDecimal(it.getString(1)) + }.firstOrNull() ?: BigDecimal.ZERO + } } var blockHeight by NavEventsTable.blockHeight diff --git a/service/src/main/kotlin/io/provenance/explorer/domain/models/explorer/pulse/Enums.kt b/service/src/main/kotlin/io/provenance/explorer/domain/models/explorer/pulse/Enums.kt index 0d594bfa..0661f214 100644 --- a/service/src/main/kotlin/io/provenance/explorer/domain/models/explorer/pulse/Enums.kt +++ b/service/src/main/kotlin/io/provenance/explorer/domain/models/explorer/pulse/Enums.kt @@ -18,7 +18,9 @@ enum class PulseCacheType { PULSE_MARKET_CAP_METRIC, PULSE_TRANSACTION_VOLUME_METRIC, PULSE_FEES_AUCTIONS_METRIC, - PULSE_RECEIVABLES_METRIC, + PULSE_TODAYS_NAV_METRIC, + PULSE_NAV_DECREASE_METRIC, + PULSE_TOTAL_NAV_METRIC, PULSE_TRADE_SETTLEMENT_METRIC, PULSE_TRADE_VALUE_SETTLED_METRIC, PULSE_PARTICIPANTS_METRIC, diff --git a/service/src/main/kotlin/io/provenance/explorer/service/PulseMetricService.kt b/service/src/main/kotlin/io/provenance/explorer/service/PulseMetricService.kt index 17bba033..ff098631 100644 --- a/service/src/main/kotlin/io/provenance/explorer/service/PulseMetricService.kt +++ b/service/src/main/kotlin/io/provenance/explorer/service/PulseMetricService.kt @@ -48,7 +48,7 @@ class PulseMetricService( private val validatorService: ValidatorService, private val pricingService: PricingService, private val assetService: AssetService, - private val exchangeGrpcClient: ExchangeGrpcClient + private val exchangeGrpcClient: ExchangeGrpcClient, ) { protected val logger = logger(PulseMetricService::class) @@ -69,6 +69,11 @@ class PulseMetricService( maximumSize(100) }.build() + /* so it turns out that the `usd` in metadata nav events + use 3 decimal places - :| + */ + private val scopeNAVDecimal = inversePowerOfTen(3) + val base = UTILITY_TOKEN val quote = USD_UPPER @@ -264,9 +269,9 @@ class PulseMetricService( * Uses metadata module reported values to calculate receivables since * all data in metadata today is loan receivables */ - private fun pulseReceivableValue(): PulseMetric = + private fun pulseTodaysNavs(): PulseMetric = fetchOrBuildCacheFromDataSource( - type = PulseCacheType.PULSE_RECEIVABLES_METRIC + type = PulseCacheType.PULSE_TODAYS_NAV_METRIC ) { // TODO technically correct assuming only metadata nav events are receivables NavEventsRecord.getNavEvents( fromDate = LocalDateTime.now().startOfDay(), @@ -278,16 +283,48 @@ class PulseMetricService( it.scopeId } .sumOf { it.priceAmount!! }.toBigDecimal().let { - /* so it turns out that the `usd` in metadata nav events - use 3 decimal places - :| - */ PulseMetric.build( base = USD_UPPER, - amount = it.times(inversePowerOfTen(3)) + amount = it.times(scopeNAVDecimal) + ) + } + } + + private fun dailyNavDecrease(): PulseMetric = + fetchOrBuildCacheFromDataSource( + type = PulseCacheType.PULSE_NAV_DECREASE_METRIC + ) { + val today = LocalDateTime.now().startOfDay() + NavEventsRecord.navPricesBetweenDays( + startDateTime = today.minusDays(1), + endDateTime = today + ).map { + val currentAmount = BigDecimal(it["current_amount"].toString()) + val previousAmount = BigDecimal(it["previous_amount"].toString()) + val changeAmount = previousAmount.minus(currentAmount) + + Triple(currentAmount, previousAmount, changeAmount) + }.filter { it.third >= BigDecimal.ZERO } + .sumOf { it.third }.let { + PulseMetric.build( + base = USD_UPPER, + amount = it.times(scopeNAVDecimal) ) } } + private fun totalMetadataNavs(): PulseMetric = + fetchOrBuildCacheFromDataSource( + type = PulseCacheType.PULSE_TOTAL_NAV_METRIC + ) { + NavEventsRecord.totalMetadataNavs().let { + PulseMetric.build( + base = USD_UPPER, + amount = it.times(scopeNAVDecimal) + ) + } + } + /** * Retrieves the transaction volume for the last 30 days to build * metric chart data @@ -510,7 +547,11 @@ class PulseMetricService( PulseCacheType.HASH_SUPPLY_METRIC -> hashMetric(type) PulseCacheType.PULSE_MARKET_CAP_METRIC -> pulseMarketCap() PulseCacheType.PULSE_TRANSACTION_VOLUME_METRIC -> transactionVolume() - PulseCacheType.PULSE_RECEIVABLES_METRIC -> pulseReceivableValue() + + PulseCacheType.PULSE_TODAYS_NAV_METRIC -> pulseTodaysNavs() + PulseCacheType.PULSE_NAV_DECREASE_METRIC -> dailyNavDecrease() + PulseCacheType.PULSE_TOTAL_NAV_METRIC -> totalMetadataNavs() + PulseCacheType.PULSE_TRADE_SETTLEMENT_METRIC -> pulseTradesSettled() PulseCacheType.PULSE_TRADE_VALUE_SETTLED_METRIC -> pulseTradeValueSettled() PulseCacheType.PULSE_PARTICIPANTS_METRIC -> totalParticipants()