diff --git a/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepository.kt b/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepository.kt index b6d20e8a..579f853d 100644 --- a/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepository.kt +++ b/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepository.kt @@ -68,6 +68,11 @@ interface CardanoRepository { assetName: String ): Long + suspend fun isNewmToken( + policyId: String, + assetName: String + ): Boolean + suspend fun withLock(block: suspend () -> T): T suspend fun verifySignData( diff --git a/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepositoryImpl.kt b/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepositoryImpl.kt index bea42cb9..8270893c 100644 --- a/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepositoryImpl.kt +++ b/newm-server/src/main/kotlin/io/newm/server/features/cardano/repo/CardanoRepositoryImpl.kt @@ -89,12 +89,6 @@ import io.newm.txbuilder.ktx.fingerprint import io.newm.txbuilder.ktx.mergeAmounts import io.newm.txbuilder.ktx.toCborObject import io.newm.txbuilder.ktx.toNativeAssetMap -import java.time.Duration -import java.util.UUID -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import kotlin.time.Duration.Companion.minutes import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -105,6 +99,12 @@ import software.amazon.awssdk.core.SdkBytes import software.amazon.awssdk.services.kms.KmsAsyncClient import software.amazon.awssdk.services.kms.model.DecryptRequest import software.amazon.awssdk.services.kms.model.EncryptRequest +import java.time.Duration +import java.util.UUID +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine +import kotlin.time.Duration.Companion.minutes internal class CardanoRepositoryImpl( private val client: NewmChainCoroutineStub, @@ -425,14 +425,22 @@ internal class CardanoRepositoryImpl( policyId: String, assetName: String ): Long { - if ((isMainnet() && policyId == NEWM_TOKEN_POLICY && assetName == NEWM_TOKEN_NAME) || - (!isMainnet() && policyId == NEWM_TOKEN_POLICY_TEST && assetName == NEWM_TOKEN_NAME_TEST) - ) { + if (isNewmToken(policyId, assetName)) { return queryNEWMUSDPrice() } throw IllegalArgumentException("Unsupported token for price API - policyId: $policyId, assetName: $assetName") } + override suspend fun isNewmToken( + policyId: String, + assetName: String + ): Boolean = + if (isMainnet()) { + policyId == NEWM_TOKEN_POLICY && assetName == NEWM_TOKEN_NAME + } else { + policyId == NEWM_TOKEN_POLICY_TEST && assetName == NEWM_TOKEN_NAME_TEST + } + override suspend fun snapshotToken( policyId: String, name: String, diff --git a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/database/MarketplaceSaleEntity.kt b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/database/MarketplaceSaleEntity.kt index bfc77cac..3d2982f1 100644 --- a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/database/MarketplaceSaleEntity.kt +++ b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/database/MarketplaceSaleEntity.kt @@ -3,6 +3,7 @@ package io.newm.server.features.marketplace.database import io.newm.chain.util.assetFingerprintOf import io.newm.chain.util.assetUrlOf import io.newm.server.features.collaboration.database.CollaborationEntity +import io.newm.server.features.marketplace.model.CostAmountConversions import io.newm.server.features.marketplace.model.Sale import io.newm.server.features.marketplace.model.SaleFilters import io.newm.server.features.marketplace.model.SaleStatus @@ -65,7 +66,7 @@ class MarketplaceSaleEntity( fun toModel( isMainnet: Boolean, isNftCdnEnabled: Boolean, - costAmountUsd: String + costAmountConvertions: CostAmountConversions ): Sale { val song = SongEntity[songId] val artist = UserEntity[song.ownerId] @@ -100,7 +101,8 @@ class MarketplaceSaleEntity( costPolicyId = costPolicyId, costAssetName = costAssetName, costAmount = costAmount, - costAmountUsd = costAmountUsd, + costAmountUsd = costAmountConvertions.usd, + costAmountNewm = costAmountConvertions.newm, maxBundleSize = maxBundleSize, totalBundleQuantity = totalBundleQuantity, bundleAmount = bundleAmount, diff --git a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/CostAmountConversions.kt b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/CostAmountConversions.kt new file mode 100644 index 00000000..c349eea3 --- /dev/null +++ b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/CostAmountConversions.kt @@ -0,0 +1,6 @@ +package io.newm.server.features.marketplace.model + +data class CostAmountConversions( + val usd: String, + val newm: String +) diff --git a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/Sale.kt b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/Sale.kt index 33d53db0..37b77946 100644 --- a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/Sale.kt +++ b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/model/Sale.kt @@ -20,6 +20,7 @@ data class Sale( val costAssetName: String, val costAmount: Long, val costAmountUsd: String, + val costAmountNewm: String, val maxBundleSize: Long, val totalBundleQuantity: Long, val availableBundleQuantity: Long, diff --git a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/repo/MarketplaceRepositoryImpl.kt b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/repo/MarketplaceRepositoryImpl.kt index e139eb80..fa6754a8 100644 --- a/newm-server/src/main/kotlin/io/newm/server/features/marketplace/repo/MarketplaceRepositoryImpl.kt +++ b/newm-server/src/main/kotlin/io/newm/server/features/marketplace/repo/MarketplaceRepositoryImpl.kt @@ -44,6 +44,7 @@ import io.newm.server.features.marketplace.database.MarketplacePurchaseEntity import io.newm.server.features.marketplace.database.MarketplaceSaleEntity import io.newm.server.features.marketplace.model.Artist import io.newm.server.features.marketplace.model.ArtistFilters +import io.newm.server.features.marketplace.model.CostAmountConversions import io.newm.server.features.marketplace.model.OrderAmountRequest import io.newm.server.features.marketplace.model.OrderAmountResponse import io.newm.server.features.marketplace.model.OrderTransactionRequest @@ -96,8 +97,8 @@ internal class MarketplaceRepositoryImpl( val sale = transaction { MarketplaceSaleEntity[saleId] } val isMainnet = cardanoRepository.isMainnet() val isNftCdnEnabled = configRepository.getBoolean(CONFIG_KEY_NFTCDN_ENABLED) - val costAmountUsd = sale.getCostAmountUsd() - return transaction { sale.toModel(isMainnet, isNftCdnEnabled, costAmountUsd) } + val costAmountConversions = sale.computeCostAmountConversions() + return transaction { sale.toModel(isMainnet, isNftCdnEnabled, costAmountConversions) } } override suspend fun getSales( @@ -111,9 +112,9 @@ internal class MarketplaceRepositoryImpl( val sales = transaction { MarketplaceSaleEntity.all(filters).limit(n = limit, offset = offset.toLong()).toList() } - val costAmountsUsd: Map = sales.associate { it.id.value to it.getCostAmountUsd() } + val costAmountConversions = sales.associate { it.id.value to it.computeCostAmountConversions() } return transaction { - sales.map { it.toModel(isMainnet, isNftCdnEnabled, costAmountsUsd[it.id.value]!!) } + sales.map { it.toModel(isMainnet, isNftCdnEnabled, costAmountConversions[it.id.value]!!) } } } @@ -748,12 +749,20 @@ internal class MarketplaceRepositoryImpl( .firstOrNull() ?.get(SongTable.id) - private suspend fun MarketplaceSaleEntity.getCostAmountUsd(): String { - val unitPrice = if (costPolicyId == configRepository.getString(CONFIG_KEY_MARKETPLACE_USD_POLICY_ID)) { - 1L - } else { - cardanoRepository.queryNativeTokenUSDPrice(costPolicyId, costAssetName) + private suspend fun MarketplaceSaleEntity.computeCostAmountConversions(): CostAmountConversions { + val newmUsdPrice = cardanoRepository.queryNEWMUSDPrice() + val costTokenUsdPrice = when { + // for USD, 1 rather that 1000000 to account for costAmount being in 12 instead of 6 decimal places + costPolicyId == configRepository.getString(CONFIG_KEY_MARKETPLACE_USD_POLICY_ID) -> 1L + + cardanoRepository.isNewmToken(costPolicyId, costAssetName) -> newmUsdPrice + + else -> cardanoRepository.queryNativeTokenUSDPrice(costPolicyId, costAssetName) } - return (costAmount.toBigInteger() * unitPrice.toBigInteger()).toBigDecimal(12).toPlainString() + val costAmountUsd = costAmount.toBigInteger() * costTokenUsdPrice.toBigInteger() + return CostAmountConversions( + usd = costAmountUsd.toBigDecimal(12).toPlainString(), + newm = (costAmountUsd.toBigDecimal(6) / newmUsdPrice.toBigDecimal()).toPlainString() + ) } } diff --git a/newm-server/src/test/kotlin/io/newm/server/features/marketplace/MarketplaceRoutesTests.kt b/newm-server/src/test/kotlin/io/newm/server/features/marketplace/MarketplaceRoutesTests.kt index 8c5a224c..011cbaa1 100644 --- a/newm-server/src/test/kotlin/io/newm/server/features/marketplace/MarketplaceRoutesTests.kt +++ b/newm-server/src/test/kotlin/io/newm/server/features/marketplace/MarketplaceRoutesTests.kt @@ -19,6 +19,7 @@ import io.newm.server.features.marketplace.database.MarketplaceArtistEntity import io.newm.server.features.marketplace.database.MarketplaceSaleEntity import io.newm.server.features.marketplace.database.MarketplaceSaleTable import io.newm.server.features.marketplace.model.Artist +import io.newm.server.features.marketplace.model.CostAmountConversions import io.newm.server.features.marketplace.model.Sale import io.newm.server.features.marketplace.model.SaleStatus import io.newm.server.features.model.CountResponse @@ -45,6 +46,7 @@ import java.time.LocalDateTime import java.time.temporal.ChronoUnit private const val COST_TOKEN_USD_PRICE = 5000L +private const val NEWM_USD_PRICE = 2500L class MarketplaceRoutesTests : BaseApplicationTests() { @BeforeAll @@ -55,6 +57,8 @@ class MarketplaceRoutesTests : BaseApplicationTests() { mockk(relaxed = true) { coEvery { isMainnet() } returns false coEvery { queryNativeTokenUSDPrice(any(), any()) } returns COST_TOKEN_USD_PRICE + coEvery { queryNEWMUSDPrice() } returns NEWM_USD_PRICE + coEvery { isNewmToken(any(), any()) } returns false } } single { @@ -1009,6 +1013,8 @@ class MarketplaceRoutesTests : BaseApplicationTests() { } } + val costAmount = (offset + 1L) * 1234567L + val costAmountUsd = costAmount.toBigInteger() * COST_TOKEN_USD_PRICE.toBigInteger() return transaction { MarketplaceSaleEntity .new { @@ -1023,16 +1029,17 @@ class MarketplaceRoutesTests : BaseApplicationTests() { bundleAmount = offset + 1L costPolicyId = "costPolicyId$offset" costAssetName = "costAssetName$offset" - costAmount = offset.toLong() + this.costAmount = costAmount maxBundleSize = offset + 100L totalBundleQuantity = offset + 1000L availableBundleQuantity = offset + 1000L }.toModel( isMainnet = false, isNftCdnEnabled = false, - costAmountUsd = (offset.toBigInteger() * COST_TOKEN_USD_PRICE.toBigInteger()) - .toBigDecimal(12) - .toPlainString() + costAmountConvertions = CostAmountConversions( + usd = costAmountUsd.toBigDecimal(12).toPlainString(), + newm = (costAmountUsd.toBigDecimal(6) / NEWM_USD_PRICE.toBigDecimal()).toPlainString() + ) ) } }