Updated on 2026-08-14
This commit is contained in:
parent
ad8d026e40
commit
4b4b140182
9 changed files with 181 additions and 32 deletions
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.nft.*
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -83,4 +85,22 @@ internal object NFTDomainModule {
|
|||
GetNFTExploreUrlUseCase(
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNFTPriceUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
): GetNFTPriceUseCase {
|
||||
return GetNFTPriceUseCase(nftRepository, singleQuoteSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchNFTPriceUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
singleQuoteFetcher: SingleQuoteFetcher,
|
||||
): FetchNFTPriceUseCase {
|
||||
return FetchNFTPriceUseCase(nftRepository, singleQuoteFetcher)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ internal class DefaultNFTPersistenceStore(
|
|||
|
||||
override suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice) {
|
||||
pricesPersistenceStore.updateData {
|
||||
it.toMutableList().plus(NFTPriceId(assetId = assetId, price = salePrice))
|
||||
it.toMutableList() + NFTPriceId(assetId = assetId, price = salePrice)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@ import com.tangem.domain.nft.models.NFTSalePrice
|
|||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset.SalePrice as SDKSalePrice
|
||||
|
||||
internal class NFTSdkAssetSalePriceConverter(
|
||||
class NFTSdkAssetSalePriceConverter(
|
||||
private val assetId: NFTAsset.Identifier,
|
||||
) : TwoWayConverter<SDKSalePrice, NFTSalePrice.Value> {
|
||||
override fun convert(value: SDKSalePrice): NFTSalePrice.Value {
|
||||
return NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
fiatValue = null,
|
||||
value = value.value,
|
||||
symbol = value.symbol,
|
||||
symbol = value.symbol.orEmpty(),
|
||||
decimals = value.decimals ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -20,6 +22,7 @@ internal class NFTSdkAssetSalePriceConverter(
|
|||
return SDKSalePrice(
|
||||
symbol = value.symbol,
|
||||
value = value.value,
|
||||
decimals = value.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import arrow.core.Either
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStore
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStore
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetSalePriceConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
|
|
@ -19,6 +21,7 @@ import com.tangem.domain.nft.models.NFTCollection
|
|||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -30,7 +33,7 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.UnsupportedOperationException
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
|
|
@ -49,13 +52,62 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
|
||||
private val networkJobs = ConcurrentHashMap<Network, JobHolder>()
|
||||
private val collectionJobs = ConcurrentHashMap<NFTCollection.Identifier, JobHolder>()
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
|
||||
private val nftRuntimeStores = ConcurrentHashMap<String, NFTRuntimeStore>()
|
||||
private val nftPersistenceStores = ConcurrentHashMap<String, NFTPersistenceStore>()
|
||||
|
||||
private val collectionIdConverter = NFTSdkCollectionIdentifierConverter
|
||||
private val assetIdConverter = NFTSdkAssetIdentifierConverter
|
||||
|
||||
override fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>> =
|
||||
flow { emitAll(observeCollectionsInternal(userWalletId, networks)) }
|
||||
|
||||
override fun getNFTCurrency(network: Network): CryptoCurrency {
|
||||
return cryptoCurrencyFactory.createCoin(network)
|
||||
}
|
||||
|
||||
override suspend fun getNFTSalePrice(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionId: NFTCollection.Identifier,
|
||||
assetId: NFTAsset.Identifier,
|
||||
): NFTSalePrice = withContext(dispatchers.io) {
|
||||
val salePriceConverter = NFTSdkAssetSalePriceConverter(assetId)
|
||||
|
||||
runCatching {
|
||||
saveSalePriceInRuntime(userWalletId, network, NFTSalePrice.Loading(assetId))
|
||||
|
||||
val sdkPrice = walletManagersFacade.getNFTSalePrice(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collectionIdentifier = collectionIdConverter.convertBack(collectionId),
|
||||
assetIdentifier = assetIdConverter.convertBack(assetId),
|
||||
)
|
||||
val nftCurrency = getNFTCurrency(network)
|
||||
val salePrice = sdkPrice?.let {
|
||||
val convertedPrice = salePriceConverter.convert(sdkPrice)
|
||||
convertedPrice.copy(
|
||||
value = convertedPrice.value.movePointLeft(nftCurrency.decimals),
|
||||
decimals = nftCurrency.decimals,
|
||||
symbol = nftCurrency.symbol,
|
||||
)
|
||||
} ?: NFTSalePrice.Empty(assetId)
|
||||
|
||||
saveSalePriceInRuntime(userWalletId, network, salePrice)
|
||||
|
||||
sdkPrice?.let {
|
||||
val sdkAssetId = assetIdConverter.convertBack(assetId)
|
||||
saveSalePriceInPersistence(userWalletId, network, sdkAssetId, it)
|
||||
}
|
||||
|
||||
salePrice
|
||||
}.getOrElse {
|
||||
saveSalePriceInRuntime(userWalletId, network, NFTSalePrice.Error(assetId))
|
||||
NFTSalePrice.Error(assetId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun observeCollectionsInternal(
|
||||
userWalletId: UserWalletId,
|
||||
networks: List<Network>,
|
||||
|
|
@ -86,7 +138,7 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
) = coroutineScope {
|
||||
launch(dispatchers.io) {
|
||||
Either.catch {
|
||||
val sdkCollectionId = NFTSdkCollectionIdentifierConverter.convertBack(collectionId)
|
||||
val sdkCollectionId = collectionIdConverter.convertBack(collectionId)
|
||||
|
||||
val assets = walletManagersFacade.getNFTAssets(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -97,7 +149,7 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
expireAssets(userWalletId, network, collectionId)
|
||||
|
||||
assets.forEach {
|
||||
val assetId = NFTSdkAssetIdentifierConverter.convert(it.identifier)
|
||||
val assetId = assetIdConverter.convert(it.identifier)
|
||||
val price = getNFTRuntimeStore(userWalletId, network).getSalePriceSync(assetId)
|
||||
if (price is NFTSalePrice.Empty || price is NFTSalePrice.Error) {
|
||||
refreshSalePrice(userWalletId, network, sdkCollectionId, it.identifier)
|
||||
|
|
@ -157,7 +209,7 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
override suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? =
|
||||
walletManagersFacade.getNFTExploreUrl(
|
||||
network = network,
|
||||
assetIdentifier = NFTSdkAssetIdentifierConverter.convertBack(assetIdentifier),
|
||||
assetIdentifier = assetIdConverter.convertBack(assetIdentifier),
|
||||
)
|
||||
|
||||
private suspend fun refreshCollectionsInternal(
|
||||
|
|
@ -190,7 +242,7 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
refreshAssets(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collectionId = NFTSdkCollectionIdentifierConverter.convert(collection.identifier),
|
||||
collectionId = collectionIdConverter.convert(collection.identifier),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -215,29 +267,16 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
sdkAssetId: SdkNFTAsset.Identifier,
|
||||
) = coroutineScope {
|
||||
launch(dispatchers.io) {
|
||||
val assetId = NFTSdkAssetIdentifierConverter.convert(sdkAssetId)
|
||||
val assetId = assetIdConverter.convert(sdkAssetId)
|
||||
val collectionId = collectionIdConverter.convert(sdkCollectionId)
|
||||
|
||||
Either.catch {
|
||||
saveSalePriceInRuntime(userWalletId, network, NFTSalePrice.Loading(assetId))
|
||||
|
||||
val sdkSalePrice =
|
||||
walletManagersFacade.getNFTSalePrice(userWalletId, network, sdkCollectionId, sdkAssetId)
|
||||
|
||||
val salePrice = if (sdkSalePrice == null) {
|
||||
NFTSalePrice.Empty(assetId)
|
||||
} else {
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = sdkSalePrice.value,
|
||||
symbol = sdkSalePrice.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
saveSalePriceInRuntime(userWalletId, network, salePrice)
|
||||
|
||||
sdkSalePrice?.let {
|
||||
saveSalePriceInPersistence(userWalletId, network, sdkAssetId, it)
|
||||
}
|
||||
getNFTSalePrice(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collectionId = collectionId,
|
||||
assetId = assetId,
|
||||
)
|
||||
}.onLeft {
|
||||
saveSalePriceInRuntime(userWalletId, network, NFTSalePrice.Error(assetId))
|
||||
}
|
||||
|
|
@ -396,14 +435,17 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
prices
|
||||
.mapKeys {
|
||||
val (assetId, _) = it
|
||||
NFTSdkAssetIdentifierConverter.convert(assetId)
|
||||
assetIdConverter.convert(assetId)
|
||||
}
|
||||
.mapValues {
|
||||
val (assetId, price) = it
|
||||
val nftCurrency = getNFTCurrency(network)
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = price.value,
|
||||
symbol = price.symbol,
|
||||
value = price.value.movePointLeft(nftCurrency.decimals),
|
||||
fiatValue = null,
|
||||
symbol = nftCurrency.symbol,
|
||||
decimals = nftCurrency.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ dependencies {
|
|||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.quotes)
|
||||
}
|
||||
|
|
@ -26,6 +26,8 @@ sealed class NFTSalePrice {
|
|||
data class Value(
|
||||
override val assetId: NFTAsset.Identifier,
|
||||
val value: SerializedBigDecimal,
|
||||
val fiatValue: SerializedBigDecimal?,
|
||||
val symbol: String,
|
||||
val decimals: Int,
|
||||
) : NFTSalePrice()
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
class FetchNFTPriceUseCase(
|
||||
private val nftRepository: NFTRepository,
|
||||
private val singleQuoteFetcher: SingleQuoteFetcher,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(network: Network, appCurrencyId: String?): Either<Throwable, Unit> {
|
||||
return Either.catch {
|
||||
val nftCurrency = nftRepository.getNFTCurrency(network)
|
||||
val rawId = nftCurrency.id.rawCurrencyId ?: error("Invalid nft currency id")
|
||||
|
||||
singleQuoteFetcher(
|
||||
params = SingleQuoteFetcher.Params(
|
||||
rawCurrencyId = rawId,
|
||||
appCurrencyId = appCurrencyId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.quotes.single.SingleQuoteProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetNFTPriceUseCase(
|
||||
private val nftRepository: NFTRepository,
|
||||
private val singleQuoteSupplier: SingleQuoteSupplier,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, nftAsset: NFTAsset): Either<Throwable, Flow<NFTSalePrice>> {
|
||||
return Either.catch {
|
||||
val nftCurrency = nftRepository.getNFTCurrency(nftAsset.network)
|
||||
val rawId = nftCurrency.id.rawCurrencyId ?: error("Invalid nft currency id")
|
||||
|
||||
singleQuoteSupplier(
|
||||
params = SingleQuoteProducer.Params(rawCurrencyId = rawId),
|
||||
).map { quote ->
|
||||
val nftPrice = nftRepository.getNFTSalePrice(
|
||||
userWalletId = userWalletId,
|
||||
network = nftAsset.network,
|
||||
collectionId = nftAsset.collectionId,
|
||||
assetId = nftAsset.id,
|
||||
)
|
||||
|
||||
val quoteValue = quote as? Quote.Value
|
||||
|
||||
if (nftPrice !is NFTSalePrice.Value) {
|
||||
nftPrice
|
||||
} else {
|
||||
nftPrice.copy(fiatValue = quoteValue?.fiatRate?.multiply(nftPrice.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ package com.tangem.domain.nft.repository
|
|||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -10,6 +12,15 @@ import kotlinx.coroutines.flow.Flow
|
|||
interface NFTRepository {
|
||||
fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>>
|
||||
|
||||
fun getNFTCurrency(network: Network): CryptoCurrency
|
||||
|
||||
suspend fun getNFTSalePrice(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionId: NFTCollection.Identifier,
|
||||
assetId: NFTAsset.Identifier,
|
||||
): NFTSalePrice
|
||||
|
||||
suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>)
|
||||
|
||||
suspend fun refreshAssets(userWalletId: UserWalletId, network: Network, collectionId: NFTCollection.Identifier)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue