Updated on 2026-08-14
This commit is contained in:
parent
f49a2bc054
commit
d630852c29
28 changed files with 406 additions and 10 deletions
|
|
@ -185,6 +185,7 @@ internal object TokensDomainModule {
|
|||
@ViewModelScoped
|
||||
fun provideGetCryptoCurrencyActionsUseCase(
|
||||
rampStateManager: RampStateManager,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
|
|
@ -193,6 +194,7 @@ internal object TokensDomainModule {
|
|||
): GetCryptoCurrencyActionsUseCase {
|
||||
return GetCryptoCurrencyActionsUseCase(
|
||||
rampManager = rampStateManager,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
|
|
@ -40,6 +42,22 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideAssociateAssetUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
): AssociateAssetUseCase {
|
||||
return AssociateAssetUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
/** Tangem libraries */
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.blockchain.common.address.EstimationFeeAddressFactory
|
|||
import com.tangem.blockchain.common.pagination.Page
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
|
|
@ -26,6 +27,7 @@ import com.tangem.domain.demo.DemoConfig
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryState
|
||||
|
|
@ -57,6 +59,8 @@ class DefaultWalletManagersFacade(
|
|||
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
|
||||
private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) }
|
||||
private val sdkPageConverter by lazy { SdkPageConverter() }
|
||||
private val cryptoCurrencyTypeConverter by lazy { CryptoCurrencyTypeConverter() }
|
||||
private val requirementsConditionConverter by lazy { SdkRequirementsConditionConverter() }
|
||||
private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() }
|
||||
|
||||
override suspend fun update(
|
||||
|
|
@ -575,6 +579,34 @@ class DefaultWalletManagersFacade(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun getAssetRequirements(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): AssetRequirementsCondition? {
|
||||
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
|
||||
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
|
||||
if (walletManager !is AssetRequirementsManager || !walletManager.hasRequirements(currencyType)) return null
|
||||
|
||||
val condition = walletManager.requirementsCondition(currencyType) ?: return null
|
||||
return requirementsConditionConverter.convert(condition)
|
||||
}
|
||||
|
||||
override suspend fun associateAsset(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
signer: CommonSigner,
|
||||
): SimpleResult {
|
||||
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
|
||||
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
|
||||
|
||||
if (walletManager !is AssetRequirementsManager) {
|
||||
return SimpleResult.Failure(
|
||||
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
|
||||
)
|
||||
}
|
||||
return walletManager.fulfillRequirements(currencyType, signer)
|
||||
}
|
||||
|
||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||
if (tokens.isEmpty()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryState
|
||||
|
|
@ -240,4 +241,16 @@ interface WalletManagersFacade {
|
|||
decimals: Int,
|
||||
id: String? = null,
|
||||
): BigDecimal
|
||||
|
||||
/**
|
||||
* Get requirements for asset(currency)
|
||||
* @return null if there's no requirement, otherwise [AssetRequirementsCondition].
|
||||
*/
|
||||
suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition?
|
||||
|
||||
suspend fun associateAsset(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
signer: CommonSigner,
|
||||
): SimpleResult
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.CryptoCurrencyType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class CryptoCurrencyTypeConverter : Converter<CryptoCurrency, CryptoCurrencyType> {
|
||||
override fun convert(value: CryptoCurrency): CryptoCurrencyType {
|
||||
return when (value) {
|
||||
is CryptoCurrency.Coin -> CryptoCurrencyType.Coin
|
||||
is CryptoCurrency.Token -> CryptoCurrencyType.Token(
|
||||
info = Token(
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
contractAddress = value.contractAddress,
|
||||
decimals = value.decimals,
|
||||
id = value.id.rawCurrencyId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as SdkRequirementsCondition
|
||||
|
||||
internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCondition, AssetRequirementsCondition> {
|
||||
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
|
||||
return when (value) {
|
||||
SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
|
||||
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
|
||||
feeAmount = requireNotNull(value.feeAmount.value),
|
||||
feeCurrencySymbol = value.feeAmount.currencySymbol,
|
||||
decimals = value.feeAmount.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.tokens.model.warnings
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class HederaWarnings : CryptoCurrencyWarning() {
|
||||
|
||||
abstract val currency: CryptoCurrency
|
||||
|
||||
data class AssociateWarning(override val currency: CryptoCurrency) : HederaWarnings()
|
||||
|
||||
data class AssociateWarningWithFee(
|
||||
override val currency: CryptoCurrency,
|
||||
val fee: BigDecimal,
|
||||
val feeCurrencySymbol: String,
|
||||
val feeCurrencyDecimals: Int,
|
||||
) : HederaWarnings()
|
||||
}
|
||||
|
|
@ -72,4 +72,9 @@ sealed class TokenScreenAnalyticsEvent(
|
|||
event = "Token Bought",
|
||||
params = mapOf("Token" to token),
|
||||
)
|
||||
|
||||
class Associate(tokenSymbol: String, blockchain: String) : TokenScreenAnalyticsEvent(
|
||||
event = "Button - Token Trustline",
|
||||
params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain),
|
||||
)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
|
|
@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.*
|
|||
@Suppress("LongParameterList")
|
||||
class GetCryptoCurrencyActionsUseCase(
|
||||
private val rampManager: RampStateManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
|
|
@ -30,7 +32,10 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus): Flow<TokenActionsState> {
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Flow<TokenActionsState> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
|
|
@ -38,7 +43,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWalletId = userWallet.walletId,
|
||||
)
|
||||
val networkId = cryptoCurrencyStatus.currency.network.id
|
||||
|
||||
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency)
|
||||
return flow {
|
||||
val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
|
||||
|
|
@ -53,6 +58,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWallet = userWallet,
|
||||
coinStatus = maybeCoinStatus.getOrNull(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
needAssociateAsset = requirements != null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +70,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWallet: UserWallet,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
needAssociateAsset: Boolean,
|
||||
): TokenActionsState {
|
||||
return TokenActionsState(
|
||||
walletId = userWallet.walletId,
|
||||
|
|
@ -72,6 +79,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWallet = userWallet,
|
||||
coinStatus = coinStatus,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
needAssociateAsset = needAssociateAsset,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -85,13 +93,14 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
userWallet: UserWallet,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
needAssociateAsset: Boolean,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) {
|
||||
return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
}
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) {
|
||||
return getActionsForUnreachableCurrency(cryptoCurrencyStatus)
|
||||
return getActionsForUnreachableCurrency(cryptoCurrencyStatus, needAssociateAsset)
|
||||
}
|
||||
|
||||
val activeList = mutableListOf<TokenActionsState.ActionState>()
|
||||
|
|
@ -104,7 +113,12 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
|
||||
// receive
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
activeList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None))
|
||||
val scenario = if (needAssociateAsset) {
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset
|
||||
} else {
|
||||
ScenarioUnavailabilityReason.None
|
||||
}
|
||||
activeList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
|
||||
// send
|
||||
|
|
@ -190,6 +204,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
|
||||
private fun getActionsForUnreachableCurrency(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
needAssociateAsset: Boolean,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
val actionsList = mutableListOf<TokenActionsState.ActionState>()
|
||||
|
||||
|
|
@ -212,7 +227,12 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.Unreachable))
|
||||
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
actionsList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None))
|
||||
val scenario = if (needAssociateAsset) {
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset
|
||||
} else {
|
||||
ScenarioUnavailabilityReason.None
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
return actionsList
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.model.warnings.HederaWarnings
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
|
|
@ -76,6 +78,7 @@ class GetCurrencyWarningsUseCase(
|
|||
getNetworkUnavailableWarning(currencyStatus),
|
||||
getNetworkNoAccountWarning(currencyStatus),
|
||||
getBeaconChainShutdownWarning(currency.network.id),
|
||||
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
|
@ -267,6 +270,22 @@ class GetCurrencyWarningsUseCase(
|
|||
return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null
|
||||
}
|
||||
|
||||
private suspend fun getAssetRequirementsWarning(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): CryptoCurrencyWarning? {
|
||||
return when (val requirements = walletManagersFacade.getAssetRequirements(userWalletId, currency)) {
|
||||
is AssetRequirementsCondition.PaidTransaction -> HederaWarnings.AssociateWarning(currency = currency)
|
||||
is AssetRequirementsCondition.PaidTransactionWithFee -> HederaWarnings.AssociateWarningWithFee(
|
||||
currency = currency,
|
||||
fee = requirements.feeAmount,
|
||||
feeCurrencySymbol = requirements.feeCurrencySymbol,
|
||||
feeCurrencyDecimals = requirements.decimals,
|
||||
)
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal?.isZero(): Boolean {
|
||||
return this?.signum() == 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ sealed class ScenarioUnavailabilityReason {
|
|||
|
||||
data object Unreachable : ScenarioUnavailabilityReason()
|
||||
|
||||
data object UnassociatedAsset : ScenarioUnavailabilityReason()
|
||||
|
||||
enum class WithdrawalScenario {
|
||||
SELL, SEND
|
||||
}
|
||||
|
|
|
|||
1
domain/transaction/models/.gitignore
vendored
Normal file
1
domain/transaction/models/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
4
domain/transaction/models/build.gradle.kts
Normal file
4
domain/transaction/models/build.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.transaction.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class AssetRequirementsCondition {
|
||||
|
||||
/**
|
||||
* The exact value of the fee for this type of condition is unknown.
|
||||
*/
|
||||
data object PaidTransaction : AssetRequirementsCondition()
|
||||
|
||||
/**
|
||||
* The exact value of the fee for this type of condition is stored in `feeAmount`.
|
||||
*/
|
||||
data class PaidTransactionWithFee(
|
||||
val feeAmount: BigDecimal,
|
||||
val feeCurrencySymbol: String,
|
||||
val decimals: Int,
|
||||
) : AssetRequirementsCondition()
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.transaction.error
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
sealed class AssociateAssetError {
|
||||
data class NotEnoughBalance(val feeCurrency: CryptoCurrency) : AssociateAssetError()
|
||||
|
||||
data class DataError(val message: String?) : AssociateAssetError()
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.isNullOrZero
|
||||
|
||||
class AssociateAssetUseCase(
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Either<AssociateAssetError, Unit> {
|
||||
return either {
|
||||
val networkCoin = currenciesRepository.getNetworkCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = currency.network.id,
|
||||
derivationPath = currency.network.derivationPath,
|
||||
)
|
||||
if (isBalanceZero(userWalletId, networkCoin)) {
|
||||
raise(AssociateAssetError.NotEnoughBalance(networkCoin))
|
||||
}
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(cardId = null)
|
||||
|
||||
catch(
|
||||
block = {
|
||||
when (val result = walletManagersFacade.associateAsset(userWalletId, currency, signer)) {
|
||||
is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.message))
|
||||
SimpleResult.Success -> Unit
|
||||
}
|
||||
},
|
||||
catch = { error -> AssociateAssetError.DataError(error.message) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean {
|
||||
val networkStatus = networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(currency.network),
|
||||
).find { it.network == currency.network }
|
||||
val networkCoinAmountStatus = (networkStatus?.value as? NetworkStatus.Verified)
|
||||
?.amounts
|
||||
?.get(currency.id)
|
||||
return networkCoinAmountStatus is CryptoCurrencyAmountStatus.Loaded &&
|
||||
networkCoinAmountStatus.value.isNullOrZero()
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +74,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.transaction)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
|
|||
is TokenDetailsNotification.RentInfo,
|
||||
is TokenDetailsNotification.SwapPromo,
|
||||
is TokenDetailsNotification.NetworkShutdown,
|
||||
is TokenDetailsNotification.HederaAssociateWarning,
|
||||
-> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,5 +94,22 @@ internal data class TokenDetailsDialogConfig(
|
|||
onClick = onConfirmClick,
|
||||
)
|
||||
}
|
||||
|
||||
data class ErrorDialogConfig(
|
||||
val text: TextReference,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : DialogContentConfig() {
|
||||
|
||||
override val title = null
|
||||
|
||||
override val message: TextReference = text
|
||||
|
||||
override val cancelButtonConfig = null
|
||||
|
||||
override val confirmButtonConfig: ButtonConfig = ButtonConfig(
|
||||
text = TextReference.Res(R.string.common_ok),
|
||||
onClick = onConfirmClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -117,8 +117,7 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
),
|
||||
),
|
||||
iconResId = currency.networkIconResId,
|
||||
buttonsState =
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(
|
||||
id = R.string.common_buy_currency,
|
||||
formatArgs = wrappedList(
|
||||
|
|
@ -176,4 +175,26 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
title = title,
|
||||
subtitle = subtitle,
|
||||
)
|
||||
|
||||
data class HederaAssociateWarning(
|
||||
private val currency: CryptoCurrency,
|
||||
private val fee: String?,
|
||||
private val feeCurrencySymbol: String?,
|
||||
private val onAssociateClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(R.string.warning_hedera_missing_token_association_title),
|
||||
subtitle = if (fee != null && feeCurrencySymbol != null) {
|
||||
resourceReference(
|
||||
id = R.string.warning_hedera_missing_token_association_message,
|
||||
formatArgs = wrappedList(fee, feeCurrencySymbol),
|
||||
)
|
||||
} else {
|
||||
resourceReference(R.string.warning_hedera_missing_token_association_message_brief)
|
||||
},
|
||||
iconResId = currency.networkIconResId,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_hedera_missing_token_association_button_title),
|
||||
onClick = onAssociateClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.model.warnings.HederaWarnings
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.*
|
||||
|
|
@ -30,6 +31,13 @@ internal class TokenDetailsNotificationConverter(
|
|||
return newNotifications.toImmutableList()
|
||||
}
|
||||
|
||||
fun removeHederaAssociateWarning(currentState: TokenDetailsState): ImmutableList<TokenDetailsNotification> {
|
||||
val newNotifications = currentState.notifications.toMutableList()
|
||||
newNotifications.removeBy { it is HederaAssociateWarning }
|
||||
return newNotifications.toImmutableList()
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification {
|
||||
return when (warning) {
|
||||
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> NetworkFeeWithBuyButton(
|
||||
|
|
@ -86,6 +94,23 @@ internal class TokenDetailsNotificationConverter(
|
|||
title = resourceReference(R.string.warning_beacon_chain_retirement_title),
|
||||
subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content),
|
||||
)
|
||||
|
||||
is HederaWarnings.AssociateWarning -> HederaAssociateWarning(
|
||||
currency = warning.currency,
|
||||
fee = null,
|
||||
feeCurrencySymbol = null,
|
||||
onAssociateClick = clickIntents::onAssociateClick,
|
||||
)
|
||||
is HederaWarnings.AssociateWarningWithFee -> HederaAssociateWarning(
|
||||
currency = warning.currency,
|
||||
fee = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = warning.fee,
|
||||
cryptoCurrency = "",
|
||||
decimals = warning.feeCurrencyDecimals,
|
||||
),
|
||||
feeCurrencySymbol = warning.feeCurrencySymbol,
|
||||
onAssociateClick = clickIntents::onAssociateClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import com.tangem.core.ui.components.transactions.state.TransactionState
|
|||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
|
|
@ -35,7 +37,6 @@ import com.tangem.utils.Provider
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
internal class TokenDetailsStateFactory(
|
||||
|
|
@ -187,6 +188,19 @@ internal class TokenDetailsStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getStateWithErrorDialog(text: TextReference): TokenDetailsState {
|
||||
return currentStateProvider().copy(
|
||||
dialogConfig = TokenDetailsDialogConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = clickIntents::onDismissDialog,
|
||||
content = TokenDetailsDialogConfig.DialogContentConfig.ErrorDialogConfig(
|
||||
text = text,
|
||||
onConfirmClick = clickIntents::onDismissDialog,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getRefreshingState(): TokenDetailsState {
|
||||
return refreshStateConverter.convert(true)
|
||||
}
|
||||
|
|
@ -256,6 +270,11 @@ internal class TokenDetailsStateFactory(
|
|||
return state.copy(notifications = notificationConverter.removeRentInfo(state))
|
||||
}
|
||||
|
||||
fun getStateWithRemovedHederaAssociateNotification(): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(notifications = notificationConverter.removeHederaAssociateWarning(state))
|
||||
}
|
||||
|
||||
fun getStateWithExchangeStatusBottomSheet(swapTxState: SwapTransactionsState): TokenDetailsState {
|
||||
return currentStateProvider().copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
|
|
@ -379,6 +398,9 @@ internal class TokenDetailsStateFactory(
|
|||
id = R.string.token_button_unavailability_generic_description,
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,4 +53,6 @@ interface TokenDetailsClickIntents {
|
|||
fun onGenerateExtendedKey()
|
||||
|
||||
fun onCopyAddress(): TextReference?
|
||||
|
||||
fun onAssociateClick()
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -38,6 +39,8 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
|
|||
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.tokens.models.analytics.TokenSwapPromoAnalyticsEvent
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.transaction.usecase.AssociateAssetUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
|
|
@ -99,6 +102,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val quotesRepository: QuotesRepository,
|
||||
private val swapTransactionStatusStore: SwapTransactionStatusStore,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val associateAssetUseCase: AssociateAssetUseCase,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val hapticManager: HapticManager,
|
||||
|
|
@ -216,7 +220,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun updateButtons(currencyStatus: CryptoCurrencyStatus) {
|
||||
private suspend fun updateButtons(currencyStatus: CryptoCurrencyStatus) {
|
||||
getCryptoCurrencyActionsUseCase(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencyStatus = currencyStatus,
|
||||
|
|
@ -709,6 +713,36 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
return resourceReference(R.string.wallet_notification_address_copied)
|
||||
}
|
||||
|
||||
override fun onAssociateClick() {
|
||||
analyticsEventsHandler.send(
|
||||
TokenScreenAnalyticsEvent.Associate(
|
||||
tokenSymbol = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
associateAssetUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
).fold(
|
||||
ifLeft = { e ->
|
||||
when (e) {
|
||||
is AssociateAssetError.NotEnoughBalance -> {
|
||||
uiState = stateFactory.getStateWithErrorDialog(
|
||||
resourceReference(
|
||||
id = R.string.warning_hedera_token_association_not_enough_hbar_message,
|
||||
formatArgs = wrappedList(e.feeCurrency.symbol),
|
||||
),
|
||||
)
|
||||
}
|
||||
is AssociateAssetError.DataError -> Timber.e(e.message)
|
||||
}
|
||||
},
|
||||
ifRight = { uiState = stateFactory.getStateWithRemovedHederaAssociateNotification() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean {
|
||||
if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false
|
||||
|
||||
|
|
|
|||
|
|
@ -492,6 +492,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
id = R.string.token_button_unavailability_generic_description,
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ include(":domain:app-theme:models")
|
|||
include(":domain:balance-hiding")
|
||||
include(":domain:balance-hiding:models")
|
||||
include(":domain:transaction")
|
||||
include(":domain:transaction:models")
|
||||
include(":domain:analytics")
|
||||
include(":domain:visa")
|
||||
include(":domain:onboarding")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue