Updated on 2026-08-14
This commit is contained in:
parent
89b3e67f64
commit
296df28f0a
31 changed files with 958 additions and 0 deletions
|
|
@ -102,6 +102,8 @@ dependencies {
|
|||
implementation(projects.domain.walletConnect)
|
||||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.manageTokens)
|
||||
implementation(projects.domain.nft)
|
||||
implementation(projects.domain.nft.models)
|
||||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
|
|
@ -144,6 +146,7 @@ dependencies {
|
|||
implementation(projects.data.walletConnect)
|
||||
implementation(projects.data.markets)
|
||||
implementation(projects.data.manageTokens)
|
||||
implementation(projects.data.nft)
|
||||
implementation(projects.data.onramp)
|
||||
|
||||
/** Features */
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ dependencies {
|
|||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.nft.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.datasource.di
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
|
||||
import com.tangem.datasource.api.common.adapter.DateTimeAdapter
|
||||
|
|
@ -45,6 +47,18 @@ class MoshiModule {
|
|||
.withSubtype(NetworkStatusDM.Verified::class.java, "amounts")
|
||||
.withSubtype(NetworkStatusDM.NoAccount::class.java, "amount_to_create_account"),
|
||||
)
|
||||
.add(
|
||||
PolymorphicJsonAdapterFactory.of(NFTCollection.Identifier::class.java, "bc")
|
||||
.withSubtype(NFTCollection.Identifier.EVM::class.java, "evm")
|
||||
.withSubtype(NFTCollection.Identifier.TON::class.java, "ton")
|
||||
.withDefaultValue(NFTCollection.Identifier.Unknown),
|
||||
)
|
||||
.add(
|
||||
PolymorphicJsonAdapterFactory.of(NFTAsset.Identifier::class.java, "bc")
|
||||
.withSubtype(NFTAsset.Identifier.EVM::class.java, "evm")
|
||||
.withSubtype(NFTAsset.Identifier.TON::class.java, "ton")
|
||||
.withDefaultValue(NFTAsset.Identifier.Unknown),
|
||||
)
|
||||
.addLast(KotlinJsonAdapterFactory())
|
||||
.addStakeKitEnumFallbackAdapters()
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ class EnvironmentConfigModel(
|
|||
@Json(name = "bittensorOnfinalityKey") val bittensorOnfinalityKey: String?,
|
||||
@Json(name = "koinosProApiKey") val koinosProApiKey: String?,
|
||||
@Json(name = "alephiumTangemApiKey") val alephiumTangemApiKey: String?,
|
||||
@Json(name = "moralisApiKey") val moralisApiKey: String?,
|
||||
@Json(name = "nftScanApiKey") val nftScanApiKey: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultNFTPersistenceStore(
|
||||
private val collectionsPersistenceStore: DataStore<List<NFTCollection>>,
|
||||
private val pricesPersistenceStore: DataStore<Map<NFTAsset.Identifier, NFTAsset.SalePrice>>,
|
||||
) : NFTPersistenceStore {
|
||||
|
||||
override fun getCollections(): Flow<List<NFTCollection>> = collectionsPersistenceStore.data
|
||||
|
||||
override suspend fun getCollectionsSync(): List<NFTCollection>? = collectionsPersistenceStore
|
||||
.data
|
||||
.firstOrNull()
|
||||
|
||||
override fun getAsset(collectionId: NFTCollection.Identifier, assetId: NFTAsset.Identifier): Flow<NFTAsset?> =
|
||||
collectionsPersistenceStore.data
|
||||
.map { collection ->
|
||||
collection
|
||||
.firstOrNull { it.identifier == collectionId }
|
||||
?.getAsset(assetId)
|
||||
}
|
||||
|
||||
override fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTAsset.SalePrice?> = pricesPersistenceStore.data
|
||||
.map { it[assetId] }
|
||||
|
||||
override suspend fun getSalePricesSync(): Map<NFTAsset.Identifier, NFTAsset.SalePrice>? = pricesPersistenceStore
|
||||
.data
|
||||
.firstOrNull()
|
||||
|
||||
override suspend fun saveCollections(collections: List<NFTCollection>) {
|
||||
collectionsPersistenceStore.updateData {
|
||||
collections
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice) {
|
||||
pricesPersistenceStore.updateData {
|
||||
it.toMutableMap().apply { this[assetId] = salePrice }
|
||||
}
|
||||
}
|
||||
|
||||
private fun NFTCollection.getAsset(assetId: NFTAsset.Identifier) = assets.firstOrNull { it.identifier == assetId }
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
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.Network
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultNFTRuntimeStore(
|
||||
private val network: Network,
|
||||
private val collectionsRuntimeStore: RuntimeSharedStore<NFTCollections>,
|
||||
private val pricesRuntimeStore: RuntimeSharedStore<Map<NFTAsset.Identifier, NFTSalePrice>>,
|
||||
) : NFTRuntimeStore {
|
||||
|
||||
override suspend fun initialize(collections: NFTCollections, prices: Map<NFTAsset.Identifier, NFTSalePrice>) {
|
||||
collectionsRuntimeStore.store(collections)
|
||||
pricesRuntimeStore.store(prices)
|
||||
}
|
||||
|
||||
override fun getCollections(): Flow<NFTCollections> = collectionsRuntimeStore
|
||||
.get()
|
||||
.combine(pricesRuntimeStore.get(), ::Pair)
|
||||
.map {
|
||||
val (collectionsData, prices) = it
|
||||
collectionsData.mergeWithPrices(prices)
|
||||
}
|
||||
|
||||
override suspend fun getCollectionsSync(): NFTCollections {
|
||||
val collectionsData = collectionsRuntimeStore.getSyncOrNull()
|
||||
val prices = pricesRuntimeStore.getSyncOrNull()
|
||||
return collectionsData
|
||||
?.mergeWithPrices(prices.orEmpty())
|
||||
?: NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = null,
|
||||
source = StatusSource.CACHE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAsset(collectionId: NFTCollection.Identifier, assetId: NFTAsset.Identifier): Flow<NFTAsset> =
|
||||
collectionsRuntimeStore
|
||||
.get()
|
||||
.combine(getSalePrice(assetId), ::Pair)
|
||||
.map {
|
||||
val (collectionsData, price) = it
|
||||
collectionsData
|
||||
.getCollection(collectionId)
|
||||
?.getAsset(assetId)
|
||||
?.mergeWithPrice(price)
|
||||
?: NFTAsset.Error(assetId)
|
||||
}
|
||||
|
||||
override fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTSalePrice> = pricesRuntimeStore
|
||||
.get()
|
||||
.map { it[assetId] ?: NFTSalePrice.Empty(assetId) }
|
||||
|
||||
override suspend fun saveCollections(collections: NFTCollections) {
|
||||
collectionsRuntimeStore.store(collections)
|
||||
}
|
||||
|
||||
override suspend fun saveSalePrice(salePrice: NFTSalePrice) {
|
||||
pricesRuntimeStore.update(emptyMap()) {
|
||||
it.plus(salePrice.assetId to salePrice)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NFTCollections.getCollection(collectionId: NFTCollection.Identifier): NFTCollection? =
|
||||
(content as? NFTCollections.Content.Collections)
|
||||
?.collections
|
||||
?.firstOrNull { it.id == collectionId }
|
||||
|
||||
private fun NFTCollection.getAsset(assetId: NFTAsset.Identifier): NFTAsset? =
|
||||
assets.firstOrNull { it.id == assetId }
|
||||
|
||||
private fun NFTCollections.mergeWithPrices(prices: Map<NFTAsset.Identifier, NFTSalePrice>): NFTCollections =
|
||||
when (val content = this.content) {
|
||||
is NFTCollections.Content.Collections -> {
|
||||
this.copy(
|
||||
content = content.mergeWithPrices(prices),
|
||||
)
|
||||
}
|
||||
is NFTCollections.Content.Error -> this
|
||||
}
|
||||
|
||||
private fun NFTCollections.Content.Collections.mergeWithPrices(prices: Map<NFTAsset.Identifier, NFTSalePrice>) =
|
||||
copy(
|
||||
collections = this.collections?.map { data ->
|
||||
data.copy(
|
||||
assets = data.assets.map { asset ->
|
||||
asset.mergeWithPrice(prices[asset.id] ?: NFTSalePrice.Empty(asset.id))
|
||||
},
|
||||
)
|
||||
},
|
||||
source = this.source,
|
||||
)
|
||||
|
||||
private fun NFTAsset.mergeWithPrice(price: NFTSalePrice): NFTAsset = when (this) {
|
||||
is NFTAsset.Error -> this
|
||||
is NFTAsset.Value -> copy(
|
||||
salePrice = price,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NFTPersistenceStore {
|
||||
fun getCollections(): Flow<List<NFTCollection>>
|
||||
|
||||
suspend fun getCollectionsSync(): List<NFTCollection>?
|
||||
|
||||
fun getAsset(collectionId: NFTCollection.Identifier, assetId: NFTAsset.Identifier): Flow<NFTAsset?>
|
||||
|
||||
fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTAsset.SalePrice?>
|
||||
|
||||
suspend fun getSalePricesSync(): Map<NFTAsset.Identifier, NFTAsset.SalePrice>?
|
||||
|
||||
suspend fun saveCollections(collections: List<NFTCollection>)
|
||||
|
||||
suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice)
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.listTypes
|
||||
import com.tangem.datasource.utils.mapWithCustomKeyTypes
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NFTPersistenceStoreFactory @Inject constructor(
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
fun provide(network: Network): NFTPersistenceStore {
|
||||
return DefaultNFTPersistenceStore(
|
||||
collectionsPersistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = listTypes<NFTCollection>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
produceFile = {
|
||||
context.dataStoreFile(fileName = "nft_${network.name}_${network.derivationPath}_collections")
|
||||
},
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
pricesPersistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = {
|
||||
context.dataStoreFile(fileName = "nft_${network.name}_${network.derivationPath}_prices")
|
||||
},
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NFTRuntimeStore {
|
||||
|
||||
suspend fun initialize(collections: NFTCollections, prices: Map<NFTAsset.Identifier, NFTSalePrice>)
|
||||
|
||||
fun getCollections(): Flow<NFTCollections>
|
||||
|
||||
suspend fun getCollectionsSync(): NFTCollections
|
||||
|
||||
fun getAsset(collectionId: NFTCollection.Identifier, assetId: NFTAsset.Identifier): Flow<NFTAsset?>
|
||||
|
||||
fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTSalePrice?>
|
||||
|
||||
suspend fun saveCollections(collections: NFTCollections)
|
||||
|
||||
suspend fun saveSalePrice(salePrice: NFTSalePrice)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NFTRuntimeStoreFactory @Inject constructor() {
|
||||
|
||||
fun provide(network: Network): NFTRuntimeStore = DefaultNFTRuntimeStore(
|
||||
network = network,
|
||||
collectionsRuntimeStore = RuntimeSharedStore(),
|
||||
pricesRuntimeStore = RuntimeSharedStore(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.datasource.local.nft.converter
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
|
||||
class NFTSdkAssetConverter(
|
||||
private val nftSdkAssetIdentifierConverter: NFTSdkAssetIdentifierConverter,
|
||||
private val nftSdkCollectionIdentifierConverter: NFTSdkCollectionIdentifierConverter,
|
||||
) : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
override fun convert(value: Pair<Network, SdkNFTAsset>): NFTAsset {
|
||||
val (network, asset) = value
|
||||
val assetId = nftSdkAssetIdentifierConverter.convert(asset.identifier)
|
||||
val collectionId = nftSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier)
|
||||
return NFTAsset.Value(
|
||||
id = assetId,
|
||||
collectionId = collectionId,
|
||||
network = network,
|
||||
contractType = asset.contractType,
|
||||
owner = asset.owner,
|
||||
name = asset.name,
|
||||
description = asset.description,
|
||||
salePrice = asset.salePrice?.let {
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = it.value,
|
||||
symbol = it.symbol,
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
} ?: NFTSalePrice.Empty(assetId = assetId),
|
||||
rarity = asset.rarity?.let {
|
||||
NFTAsset.Value.Rarity(
|
||||
rank = it.rank,
|
||||
label = it.label,
|
||||
)
|
||||
},
|
||||
media = asset.media?.let {
|
||||
NFTAsset.Value.Media(
|
||||
url = it.url,
|
||||
mimetype = it.mimetype,
|
||||
)
|
||||
},
|
||||
traits = asset.traits.map {
|
||||
NFTAsset.Value.Trait(
|
||||
name = it.name,
|
||||
value = it.value,
|
||||
)
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.datasource.local.nft.converter
|
||||
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
|
||||
class NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier, NFTAsset.Identifier> {
|
||||
override fun convert(value: SdkNFTAsset.Identifier): NFTAsset.Identifier = when (value) {
|
||||
is SdkNFTAsset.Identifier.EVM -> NFTAsset.Identifier.EVM(
|
||||
tokenId = value.tokenId,
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is SdkNFTAsset.Identifier.TON -> NFTAsset.Identifier.TON(
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is SdkNFTAsset.Identifier.Unknown -> error("unknown asset id")
|
||||
}
|
||||
|
||||
override fun convertBack(value: NFTAsset.Identifier): SdkNFTAsset.Identifier = when (value) {
|
||||
is NFTAsset.Identifier.EVM -> SdkNFTAsset.Identifier.EVM(
|
||||
tokenId = value.tokenId,
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is NFTAsset.Identifier.TON -> SdkNFTAsset.Identifier.TON(
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.datasource.local.nft.converter
|
||||
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
class NFTSdkCollectionConverter(
|
||||
private val nftSdkCollectionIdentifierConverter: NFTSdkCollectionIdentifierConverter,
|
||||
private val nftSdkAssetConverter: NFTSdkAssetConverter,
|
||||
) : Converter<Pair<Network, SdkNFTCollection>, NFTCollection> {
|
||||
override fun convert(value: Pair<Network, SdkNFTCollection>): NFTCollection {
|
||||
val (network, collection) = value
|
||||
val collectionId = nftSdkCollectionIdentifierConverter.convert(collection.identifier)
|
||||
return NFTCollection(
|
||||
id = collectionId,
|
||||
network = network,
|
||||
name = collection.name,
|
||||
description = collection.description,
|
||||
logoUrl = collection.logoUrl,
|
||||
count = collection.count,
|
||||
assets = collection.assets.map { asset ->
|
||||
nftSdkAssetConverter.convert(network to asset)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.datasource.local.nft.converter
|
||||
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
class NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Identifier, NFTCollection.Identifier> {
|
||||
override fun convert(value: SdkNFTCollection.Identifier): NFTCollection.Identifier = when (value) {
|
||||
is SdkNFTCollection.Identifier.EVM -> NFTCollection.Identifier.EVM(
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is SdkNFTCollection.Identifier.TON -> NFTCollection.Identifier.TON(
|
||||
contractAddress = value.contractAddress,
|
||||
)
|
||||
is SdkNFTCollection.Identifier.Unknown -> error("unknown collection id")
|
||||
}
|
||||
|
||||
override fun convertBack(value: NFTCollection.Identifier): SdkNFTCollection.Identifier = when (value) {
|
||||
is NFTCollection.Identifier.EVM -> SdkNFTCollection.Identifier.EVM(
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is NFTCollection.Identifier.TON -> SdkNFTCollection.Identifier.TON(
|
||||
contractAddress = value.contractAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,10 @@ inline fun <reified T> mapWithStringKeyTypes(): ParameterizedType {
|
|||
return Types.newParameterizedType(Map::class.java, String::class.java, T::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified K, reified T> mapWithCustomKeyTypes(): ParameterizedType {
|
||||
return Types.newParameterizedType(Map::class.java, K::class.java, T::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified T> listTypes(): ParameterizedType {
|
||||
return Types.newParameterizedType(List::class.java, T::class.java)
|
||||
}
|
||||
|
|
|
|||
1
data/nft/.gitignore
vendored
Normal file
1
data/nft/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
51
data/nft/build.gradle.kts
Normal file
51
data/nft/build.gradle.kts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.ksp)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.nft"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Project - Data */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.nft)
|
||||
implementation(projects.domain.nft.models)
|
||||
|
||||
/** Project - Utils */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
/** Libs - Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.arrow.fx)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
implementation(deps.moshi.kotlin)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
|
||||
/** Libs - Tangem */
|
||||
implementation(tangemDeps.blockchain)
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
package com.tangem.data.nft
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
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.NFTSdkAssetConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
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.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
internal class DefaultNFTRepository @Inject constructor(
|
||||
private val nftPersistenceStoreFactory: NFTPersistenceStoreFactory,
|
||||
private val nftRuntimeStoreFactory: NFTRuntimeStoreFactory,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : NFTRepository {
|
||||
|
||||
private val scope = CoroutineScope(dispatchers.io + SupervisorJob())
|
||||
|
||||
private val jobs = mutableMapOf<Network, JobHolder>()
|
||||
|
||||
private val nftRuntimeStores = mutableMapOf<Network, NFTRuntimeStore>()
|
||||
private val nftPersistenceStores = mutableMapOf<Network, NFTPersistenceStore>()
|
||||
|
||||
private val nftSdkAssetIdentifierConverter = NFTSdkAssetIdentifierConverter()
|
||||
private val nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter()
|
||||
private val nftSdkAssetConverter = NFTSdkAssetConverter(
|
||||
nftSdkAssetIdentifierConverter = nftSdkAssetIdentifierConverter,
|
||||
nftSdkCollectionIdentifierConverter = nftSdkCollectionIdentifierConverter,
|
||||
)
|
||||
private val collectionConverter = NFTSdkCollectionConverter(
|
||||
nftSdkCollectionIdentifierConverter = nftSdkCollectionIdentifierConverter,
|
||||
nftSdkAssetConverter = nftSdkAssetConverter,
|
||||
)
|
||||
|
||||
override fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>> =
|
||||
flow {
|
||||
emitAll(
|
||||
combine(
|
||||
networks.map { getNFTRuntimeStore(it).getCollections() },
|
||||
) { it.asList() },
|
||||
)
|
||||
}.onStart {
|
||||
refreshCollections(userWalletId, networks)
|
||||
}
|
||||
|
||||
override suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>) {
|
||||
networks.map { network ->
|
||||
scope.launch {
|
||||
expireCollections(network)
|
||||
|
||||
Either.catch {
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
}.onLeft {
|
||||
saveFailedStateInRuntime(
|
||||
network = network,
|
||||
error = it,
|
||||
)
|
||||
}.onRight {
|
||||
val mergedCollections = it.mergeWithStoredAssets(network)
|
||||
|
||||
saveCollectionsInRuntime(
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
saveCollectionsInPersistence(
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
}
|
||||
}.saveIn(getJobHolder(network))
|
||||
}.joinAll()
|
||||
}
|
||||
|
||||
private suspend fun expireCollections(network: Network) {
|
||||
val runtimeStore = getNFTRuntimeStore(network)
|
||||
|
||||
val expiredCollections = runtimeStore
|
||||
.getCollectionsSync()
|
||||
.let { collections ->
|
||||
collections.copy(
|
||||
content = when (val content = collections.content) {
|
||||
is NFTCollections.Content.Collections -> content.copy(
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
is NFTCollections.Content.Error -> content
|
||||
},
|
||||
)
|
||||
}
|
||||
runtimeStore.saveCollections(expiredCollections)
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInRuntime(network: Network, collections: List<SdkNFTCollection>) {
|
||||
getNFTRuntimeStore(network).saveCollections(
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = collections.map { collection ->
|
||||
collectionConverter.convert(network to collection)
|
||||
},
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun saveFailedStateInRuntime(network: Network, error: Throwable) {
|
||||
getNFTRuntimeStore(network).saveCollections(
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Error(
|
||||
error = error,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInPersistence(network: Network, collections: List<SdkNFTCollection>) {
|
||||
getNFTPersistenceStore(network).saveCollections(collections)
|
||||
}
|
||||
|
||||
private fun getJobHolder(network: Network): JobHolder = jobs[network] ?: run {
|
||||
JobHolder().also {
|
||||
jobs[network] = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNFTPersistenceStore(network: Network): NFTPersistenceStore = nftPersistenceStores[network] ?: run {
|
||||
nftPersistenceStoreFactory.provide(network).also {
|
||||
nftPersistenceStores[network] = it
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNFTRuntimeStore(network: Network): NFTRuntimeStore = nftRuntimeStores[network] ?: run {
|
||||
nftRuntimeStoreFactory.provide(network).also {
|
||||
nftRuntimeStores[network] = it
|
||||
val storedCollections = getStoredCollections(network)
|
||||
val storedPrices = getStoredPrices(network)
|
||||
it.initialize(
|
||||
collections = storedCollections,
|
||||
prices = storedPrices,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getStoredCollections(network: Network) = getNFTPersistenceStore(network)
|
||||
.getCollectionsSync()
|
||||
.let {
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = it?.map { collection ->
|
||||
collectionConverter.convert(network to collection)
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getStoredPrices(network: Network) = getNFTPersistenceStore(network)
|
||||
.getSalePricesSync()
|
||||
.orEmpty()
|
||||
.let { prices ->
|
||||
prices
|
||||
.mapKeys {
|
||||
val (assetId, _) = it
|
||||
nftSdkAssetIdentifierConverter.convert(assetId)
|
||||
}
|
||||
.mapValues {
|
||||
val (assetId, price) = it
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = price.value,
|
||||
symbol = price.symbol,
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<SdkNFTCollection>.mergeWithStoredAssets(network: Network): List<SdkNFTCollection> {
|
||||
val storedCollections =
|
||||
getNFTPersistenceStore(network)
|
||||
.getCollectionsSync()
|
||||
.orEmpty()
|
||||
.associateBy(NFTCollection::identifier)
|
||||
|
||||
return this.map { updatedCollection ->
|
||||
if (!storedCollections.containsKey(updatedCollection.identifier)) {
|
||||
// if there is no collection in cache, then write a new one
|
||||
updatedCollection
|
||||
} else if (updatedCollection.assets.isNotEmpty()) {
|
||||
// if there are assets in a new collection, then write this
|
||||
updatedCollection
|
||||
} else {
|
||||
// otherwise, just update a stored collection fields
|
||||
storedCollections[updatedCollection.identifier]
|
||||
?.copy(
|
||||
name = updatedCollection.name,
|
||||
count = updatedCollection.count,
|
||||
description = updatedCollection.description,
|
||||
logoUrl = updatedCollection.logoUrl,
|
||||
)
|
||||
?: updatedCollection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.data.nft.di
|
||||
|
||||
import com.tangem.data.nft.DefaultNFTRepository
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface NFTDataModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNFTRepository(repository: DefaultNFTRepository): NFTRepository
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ import com.tangem.blockchain.common.transaction.TransactionFee
|
|||
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
|
|
@ -639,6 +641,47 @@ class DefaultWalletManagersFacade(
|
|||
return (walletManager as? UtxoBlockchainManager)?.allowConsolidation == true
|
||||
}
|
||||
|
||||
override suspend fun getNFTCollections(userWalletId: UserWalletId, network: Network): List<NFTCollection> {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return emptyList()
|
||||
val address = walletManager.wallet.address
|
||||
return walletManager.getCollections(address)
|
||||
}
|
||||
|
||||
override suspend fun getNFTAssets(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionIdentifier: NFTCollection.Identifier,
|
||||
): List<NFTAsset> {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return emptyList()
|
||||
val address = walletManager.wallet.address
|
||||
return walletManager.getAssets(address, collectionIdentifier)
|
||||
}
|
||||
|
||||
override suspend fun getAsset(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionIdentifier: NFTCollection.Identifier,
|
||||
assetIdentifier: NFTAsset.Identifier,
|
||||
): NFTAsset? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return null
|
||||
return walletManager.getAsset(collectionIdentifier, assetIdentifier)
|
||||
}
|
||||
|
||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||
if (tokens.isEmpty()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
|
|
@ -248,4 +250,19 @@ interface WalletManagersFacade {
|
|||
* @param network availability for network
|
||||
*/
|
||||
suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean
|
||||
|
||||
suspend fun getNFTCollections(userWalletId: UserWalletId, network: Network): List<NFTCollection>
|
||||
|
||||
suspend fun getNFTAssets(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionIdentifier: NFTCollection.Identifier,
|
||||
): List<NFTAsset>
|
||||
|
||||
suspend fun getAsset(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collectionIdentifier: NFTCollection.Identifier,
|
||||
assetIdentifier: NFTAsset.Identifier,
|
||||
): NFTAsset?
|
||||
}
|
||||
1
domain/nft/.gitignore
vendored
Normal file
1
domain/nft/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
22
domain/nft/build.gradle.kts
Normal file
22
domain/nft/build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.nft"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.nft.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
}
|
||||
1
domain/nft/models/.gitignore
vendored
Normal file
1
domain/nft/models/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
14
domain/nft/models/build.gradle.kts
Normal file
14
domain/nft/models/build.gradle.kts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
||||
implementation(projects.domain.models)
|
||||
|
||||
implementation(projects.domain.tokens.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.domain.nft.models
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
sealed class NFTAsset {
|
||||
abstract val id: Identifier
|
||||
|
||||
data class Value(
|
||||
override val id: Identifier,
|
||||
val collectionId: NFTCollection.Identifier,
|
||||
val network: Network,
|
||||
val contractType: String,
|
||||
val owner: String?,
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val salePrice: NFTSalePrice,
|
||||
val rarity: Rarity?,
|
||||
val media: Media?,
|
||||
val traits: List<Trait>,
|
||||
val source: StatusSource,
|
||||
) : NFTAsset() {
|
||||
|
||||
data class Media(
|
||||
val mimetype: String,
|
||||
val url: String,
|
||||
)
|
||||
|
||||
data class Rarity(
|
||||
val rank: String,
|
||||
val label: String,
|
||||
)
|
||||
|
||||
data class Trait(
|
||||
val name: String,
|
||||
val value: String,
|
||||
)
|
||||
}
|
||||
|
||||
data class Error(
|
||||
override val id: Identifier,
|
||||
) : NFTAsset()
|
||||
|
||||
sealed class Identifier {
|
||||
data class EVM(
|
||||
val tokenId: String,
|
||||
val tokenAddress: String,
|
||||
) : Identifier()
|
||||
|
||||
data class TON(val tokenAddress: String) : Identifier()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.nft.models
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
data class NFTCollection(
|
||||
val id: Identifier,
|
||||
val network: Network,
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val logoUrl: String?,
|
||||
val count: Int,
|
||||
val assets: List<NFTAsset> = emptyList(),
|
||||
) {
|
||||
sealed class Identifier {
|
||||
data class EVM(val tokenAddress: String) : Identifier()
|
||||
data class TON(val contractAddress: String?) : Identifier()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.nft.models
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
data class NFTCollections(
|
||||
val network: Network,
|
||||
val content: Content,
|
||||
) {
|
||||
sealed class Content {
|
||||
data class Collections(
|
||||
val collections: List<NFTCollection>?,
|
||||
val source: StatusSource,
|
||||
) : Content()
|
||||
|
||||
data class Error(
|
||||
val error: Throwable,
|
||||
) : Content()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.nft.models
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class NFTSalePrice {
|
||||
abstract val assetId: NFTAsset.Identifier
|
||||
|
||||
data class Empty(
|
||||
override val assetId: NFTAsset.Identifier,
|
||||
) : NFTSalePrice()
|
||||
|
||||
data class Error(
|
||||
override val assetId: NFTAsset.Identifier,
|
||||
) : NFTSalePrice()
|
||||
|
||||
data class Value(
|
||||
override val assetId: NFTAsset.Identifier,
|
||||
val value: BigDecimal,
|
||||
val symbol: String,
|
||||
val source: StatusSource,
|
||||
) : NFTSalePrice()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.nft.repository
|
||||
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NFTRepository {
|
||||
fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>>
|
||||
|
||||
suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>)
|
||||
}
|
||||
|
|
@ -275,6 +275,8 @@ include(":domain:manage-tokens:models")
|
|||
include(":domain:onramp")
|
||||
include(":domain:onramp:models")
|
||||
include(":domain:promo")
|
||||
include(":domain:nft")
|
||||
include(":domain:nft:models")
|
||||
// endregion Domain modules
|
||||
|
||||
// region Data modules
|
||||
|
|
@ -298,6 +300,7 @@ include(":data:staking")
|
|||
include(":data:wallet-connect")
|
||||
include(":data:markets")
|
||||
include(":data:manage-tokens")
|
||||
include(":data:nft")
|
||||
include(":data:onramp")
|
||||
// endregion Data modules
|
||||
include(":domain:promo:models")
|
||||
Loading…
Add table
Add a link
Reference in a new issue