Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-10 15:21:55 +05:00
parent 4570bc334c
commit 602e20a1d2
53 changed files with 489 additions and 368 deletions

View file

@ -0,0 +1,22 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.domain.assetsdiscovery"
}
dependencies {
api(projects.domain.core)
implementation(projects.domain.models)
implementation(projects.domain.account.status)
implementation(projects.core.utils)
implementation(projects.libs.blockchainSdk)
implementation(tangemDeps.blockchain)
implementation(deps.kotlin.coroutines)
implementation(deps.arrow.core)
}

View file

@ -0,0 +1,15 @@
package com.tangem.domain.assetsdiscovery
import com.tangem.blockchain.assetsdiscovery.AssetsDiscoveryService
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
interface AssetsDiscoveryFacade {
suspend fun getAssetsDiscoveryService(userWalletId: UserWalletId, network: Network): AssetsDiscoveryServiceInfo?
data class AssetsDiscoveryServiceInfo(
val address: String,
val service: AssetsDiscoveryService,
)
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.assetsdiscovery.model
sealed class AssetsDiscoveryProgress {
data object Idle : AssetsDiscoveryProgress()
data class InProgress(
val completedNetworks: Int,
val totalNetworks: Int,
) : AssetsDiscoveryProgress() {
val progressPercent: Int
get() = if (totalNetworks > 0) {
completedNetworks * 100 / totalNetworks
} else {
0
}
}
data object Completed : AssetsDiscoveryProgress()
}

View file

@ -0,0 +1,25 @@
package com.tangem.domain.assetsdiscovery.repository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
import kotlinx.coroutines.flow.Flow
interface AssetsDiscoveryRepository {
suspend fun runDiscovery(userWalletId: UserWalletId)
suspend fun completeDiscovery(userWalletId: UserWalletId)
suspend fun getPendingDiscoveryWalletIds(): List<UserWalletId>
fun observeDiscoveryProgress(userWalletId: UserWalletId): Flow<AssetsDiscoveryProgress>
fun acknowledgeCompletion(userWalletId: UserWalletId)
suspend fun clearPendingFlag(userWalletId: UserWalletId)
suspend fun getDiscoveredCurrencies(userWalletId: UserWalletId): List<CryptoCurrency>
suspend fun clearDiscoveredTokens(userWalletId: UserWalletId)
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.assetsdiscovery.usecase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
class AcknowledgeAssetsDiscoveryCompletionUseCase(
private val assetsDiscoveryRepository: AssetsDiscoveryRepository,
) {
operator fun invoke(userWalletId: UserWalletId) {
assetsDiscoveryRepository.acknowledgeCompletion(userWalletId)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.domain.assetsdiscovery.usecase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
import kotlinx.coroutines.flow.Flow
class ObserveAssetsDiscoveryUseCase(
private val assetsDiscoveryRepository: AssetsDiscoveryRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<AssetsDiscoveryProgress> {
return assetsDiscoveryRepository.observeDiscoveryProgress(userWalletId)
}
}

View file

@ -0,0 +1,79 @@
package com.tangem.domain.assetsdiscovery.usecase
import arrow.core.Either
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap
class StartAssetsDiscoveryUseCase(
private val assetsDiscoveryRepository: AssetsDiscoveryRepository,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val appCoroutineScope: AppCoroutineScope,
) {
private val activeSyncJobs = ConcurrentHashMap<UserWalletId, Job>()
operator fun invoke(userWalletId: UserWalletId) {
activeSyncJobs[userWalletId]?.cancel()
activeSyncJobs[userWalletId] = appCoroutineScope.launch {
try {
assetsDiscoveryRepository.runDiscovery(userWalletId)
applyDiscoveredTokens(userWalletId)
assetsDiscoveryRepository.completeDiscovery(userWalletId)
} catch (e: Exception) {
TangemLogger.e("Token sync failed for wallet: $userWalletId", e)
} finally {
activeSyncJobs.remove(userWalletId)
}
}
}
suspend fun cancel(userWalletId: UserWalletId): Either<Throwable, Unit> = Either.catch {
activeSyncJobs.remove(userWalletId)?.cancel()
assetsDiscoveryRepository.clearPendingFlag(userWalletId)
assetsDiscoveryRepository.clearDiscoveredTokens(userWalletId)
}
fun applyPendingAssetsDiscovery() {
appCoroutineScope.launch {
try {
val pendingIds = assetsDiscoveryRepository.getPendingDiscoveryWalletIds()
for (walletId in pendingIds) {
val isApplied = applyDiscoveredTokens(walletId)
if (isApplied) {
assetsDiscoveryRepository.clearPendingFlag(walletId)
}
}
} catch (e: Exception) {
TangemLogger.e("Failed to apply pending syncs", e)
}
}
}
private suspend fun applyDiscoveredTokens(userWalletId: UserWalletId): Boolean {
val currencies = assetsDiscoveryRepository.getDiscoveredCurrencies(userWalletId)
if (currencies.isEmpty()) return true
val accountId = AccountId.forMainCryptoPortfolio(userWalletId)
return manageCryptoCurrenciesUseCase.invokeAndAwait(
accountId = accountId,
add = currencies,
).fold(
ifRight = {
assetsDiscoveryRepository.clearDiscoveredTokens(userWalletId)
true
},
ifLeft = { error ->
TangemLogger.e("Failed to apply discovered tokens for wallet: $userWalletId, error: $error")
false
},
)
}
}