Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-12 18:28:46 +03:00
commit 1510b5e454
10 changed files with 189 additions and 32 deletions

View file

@ -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,
)
}
}