Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-07 11:32:19 +04:00
parent b3138bf560
commit 3088258c74
14 changed files with 339 additions and 97 deletions

View file

@ -38,6 +38,7 @@ dependencies {
implementation(projects.domain.notifications)
// region Project - Libs
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
// endregion

View file

@ -1,41 +1,46 @@
package com.tangem.features.managetokens.utils.list
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import arrow.core.right
import com.tangem.domain.account.producer.SingleAccountProducer
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import timber.log.Timber
@Suppress("LongParameterList")
internal class CustomTokenFormUseCasesFacade @AssistedInject constructor(
@Assisted private val mode: AddCustomTokenMode,
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val singleAccountSupplier: SingleAccountSupplier,
@Assisted private val mode: AddCustomTokenMode,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
) {
suspend fun addCryptoCurrenciesUseCase(currency: CryptoCurrency): Either<Throwable, Unit> = when (mode) {
is AddCustomTokenMode.Account -> {
manageCryptoCurrenciesUseCase(
accountId = AccountId.forCryptoPortfolio(
userWalletId = mode.userWalletId,
derivationIndex = DerivationIndex.Main,
),
add = currency,
)
is AddCustomTokenMode.Account -> either {
val accountId = getAccountId(currency)
manageCryptoCurrenciesUseCase(accountId = accountId, add = currency).bind()
}
is AddCustomTokenMode.Wallet -> {
addCryptoCurrenciesUseCase.invoke(
@ -59,16 +64,13 @@ internal class CustomTokenFormUseCasesFacade @AssistedInject constructor(
contractAddress: String?,
): Either<Throwable, Boolean> = when (mode) {
is AddCustomTokenMode.Account -> {
val account = singleAccountSupplier.getSyncOrNull(
params = SingleAccountProducer.Params(accountId = mode.accountId),
getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = mode.accountId.userWalletId,
networkId = networkId,
derivationPath = derivationPath,
contractAddress = contractAddress,
)
?: return IllegalStateException("Account not found").left()
account.cryptoCurrencies.none { currency ->
networkId == currency.network.id &&
derivationPath == currency.network.derivationPath &&
contractAddress.equals(currency.id.contractAddress, ignoreCase = true)
}
.fold(ifEmpty = { true }, ifSome = { false })
.right()
}
is AddCustomTokenMode.Wallet -> checkIsCurrencyNotAddedUseCase.invoke(
@ -79,6 +81,56 @@ internal class CustomTokenFormUseCasesFacade @AssistedInject constructor(
)
}
private suspend fun Raise<Throwable>.getAccountId(currency: CryptoCurrency): AccountId {
val accountList = singleAccountListSupplier.getSyncOrNull(
params = SingleAccountListProducer.Params(userWalletId = mode.userWalletId),
)
ensureNotNull(accountList) {
IllegalStateException("Account list not found: ${mode.userWalletId}")
}
if (accountList.activeAccounts == 1) {
return AccountId.forMainCryptoPortfolio(userWalletId = mode.userWalletId)
}
val currencyAccountIndex = currency.getAccountIndex().bind()
val account = accountList.accounts.firstOrNull { account ->
val cryptoPortfolioAccount = account as? Account.CryptoPortfolio
cryptoPortfolioAccount?.derivationIndex?.value == currencyAccountIndex
}
return account?.accountId ?: AccountId.forMainCryptoPortfolio(userWalletId = mode.userWalletId)
}
private fun CryptoCurrency.getAccountIndex(): Either<Throwable, Int> = either {
val currency = this@getAccountIndex
val blockchain = Blockchain.fromNetworkId(networkId = currency.network.backendId)
if (blockchain == null) {
val exception = IllegalStateException("Token has unknown networkId: ${currency.id}")
Timber.e(exception)
raise(exception)
}
val derivationPathValue = currency.network.derivationPath.value
if (derivationPathValue == null) {
val exception = IllegalStateException("Token has no derivation path: ${currency.id}")
Timber.e(exception)
raise(exception)
}
val accountNodeRecognizer = AccountNodeRecognizer(blockchain)
val index = accountNodeRecognizer.recognize(derivationPathValue)?.toInt()
ensureNotNull(index) {
val exception = IllegalStateException("Token has unrecognized derivation path: ${currency.id}")
Timber.e(exception)
exception
}
}
@AssistedFactory
interface Factory {
fun create(mode: AddCustomTokenMode): CustomTokenFormUseCasesFacade