Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-19 15:15:33 +05:00
parent e0c101f15f
commit e5e2f4a1e7
13 changed files with 143 additions and 0 deletions

View file

@ -189,6 +189,7 @@ dependencies {
implementation(projects.data.common)
implementation(projects.data.settings)
implementation(projects.data.tokens)
implementation(projects.data.tokensync)
implementation(projects.data.txhistory)
implementation(projects.data.wallets)
implementation(projects.data.analytics)

View file

@ -71,5 +71,9 @@
{
"name": "VIRTUAL_ACCOUNTS_ENABLED",
"version": "undefined"
},
{
"name": "TOKEN_SYNC_ENABLED",
"version": "undefined"
}
]

View file

@ -28,6 +28,7 @@ interface TangemTechApi {
suspend fun getCoins(
@Header("Cache-Control") cacheControl: String = "max-age=600",
@Query("contractAddress") contractAddress: String? = null,
@Query("contractAddresses") contractAddresses: String? = null,
@Query("exchangeable") exchangeable: Boolean? = null,
@Query("networkIds") networkIds: String? = null,
@Query("networkId") networkId: String? = null,

View file

@ -0,0 +1,24 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.data.tokensync"
}
dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.domain.models)
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(deps.androidx.datastore)
implementation(deps.kotlin.coroutines)
implementation(deps.moshi)
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.tokensync.store
import androidx.datastore.core.DataStore
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import kotlinx.coroutines.flow.firstOrNull
internal class DefaultTokenSyncStore(
private val persistenceStore: DataStore<List<UserTokensResponse.Token>>,
) : TokenSyncStore {
override suspend fun get(): List<UserTokensResponse.Token> {
return persistenceStore.data.firstOrNull().orEmpty()
}
override suspend fun append(tokens: List<UserTokensResponse.Token>) {
persistenceStore.updateData { existing ->
(existing + tokens).distinct()
}
}
override suspend fun clear() {
persistenceStore.updateData { emptyList() }
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.data.tokensync.store
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
interface TokenSyncStore {
suspend fun get(): List<UserTokensResponse.Token>
suspend fun append(tokens: List<UserTokensResponse.Token>)
suspend fun clear()
}

View file

@ -0,0 +1,56 @@
package com.tangem.data.tokensync.store
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.listTypes
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.AppCoroutineScope
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class TokenSyncStoreFactory @Inject constructor(
@NetworkMoshi private val moshi: Moshi,
@ApplicationContext private val context: Context,
private val appScope: AppCoroutineScope,
) {
private val stores = ConcurrentHashMap<String, TokenSyncStore>()
fun provide(userWalletId: UserWalletId): TokenSyncStore {
val userWalletStringId = userWalletId.formatted()
return stores.computeIfAbsent(userWalletStringId) {
DefaultTokenSyncStore(
persistenceStore = createPersistenceStore(
fileName = "token_sync_$userWalletStringId",
types = listTypes<UserTokensResponse.Token>(),
defaultValue = emptyList(),
),
)
}
}
private fun <T> createPersistenceStore(
fileName: String,
types: java.lang.reflect.ParameterizedType,
defaultValue: T,
): DataStore<T> = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = types,
defaultValue = defaultValue,
),
produceFile = { context.dataStoreFile(fileName = fileName) },
scope = appScope,
)
private fun UserWalletId.formatted(): String = stringValue.lowercase()
}

View file

@ -18,6 +18,7 @@ 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.tokenbalance.models.TokenBalance
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest
import com.tangem.blockchain.yieldsupply.YieldSupplyContractCallDataProviderFactory
import com.tangem.blockchainsdk.BlockchainSDKFactory
@ -769,6 +770,17 @@ internal class DefaultWalletManagersFacade @Inject constructor(
return blockchain.getNFTExploreUrl(assetIdentifier)
}
override suspend fun getTokenBalances(userWalletId: UserWalletId, network: Network): List<TokenBalance> {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return emptyList()
val address = walletManager.wallet.address
return walletManager.getTokenBalances(address)
}
override suspend fun isAccountInitialized(userWalletId: UserWalletId, network: Network): Boolean {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = network)
val initializableAccountWalletManger = walletManager as? InitializableAccount ?: return true

View file

@ -12,6 +12,7 @@ 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.tokenbalance.models.TokenBalance
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
@ -281,6 +282,8 @@ interface WalletManagersFacade {
suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String?
suspend fun getTokenBalances(userWalletId: UserWalletId, network: Network): List<TokenBalance>
/**
* If wallet manager implements [InitializableAccount] then returns [InitializableAccount.isAccountInitialized]
* value. Otherwise always return true

View file

@ -22,6 +22,7 @@ internal class PreviewDetailsComponent : DetailsComponent {
router = DummyRouter(),
hotWalletFeatureToggles = object : HotWalletFeatureToggles {
override val isWalletCreationRestrictionEnabled: Boolean = true
override val isTokenSyncEnabled: Boolean = true
},
).buildAll(
isWalletConnectAvailable = true,

View file

@ -2,4 +2,5 @@ package com.tangem.features.hotwallet
interface HotWalletFeatureToggles {
val isWalletCreationRestrictionEnabled: Boolean
val isTokenSyncEnabled: Boolean
}

View file

@ -8,4 +8,7 @@ internal class DefaultHotWalletFeatureToggles(
override val isWalletCreationRestrictionEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_CREATION_RESTRICTION_ENABLED")
override val isTokenSyncEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "TOKEN_SYNC_ENABLED")
}

View file

@ -401,6 +401,7 @@ include(":data:balance-hiding")
include(":data:common")
include(":data:card")
include(":data:tokens")
include(":data:tokensync")
include(":data:settings")
include(":data:txhistory")
include(":data:wallets")