Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-25 12:41:13 +05:00
parent 4649abbb60
commit db55e6eb1d
8 changed files with 101 additions and 64 deletions

View file

@ -19,23 +19,20 @@ class AccountPortfolioItemUMConverter(
private val isBalanceHidden: Boolean = false,
private val isEnabled: Boolean = true,
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
) : Converter<Account, UserWalletItemUM> {
) : Converter<Account.CryptoPortfolio, UserWalletItemUM> {
override fun convert(value: Account): UserWalletItemUM {
return when (value) {
is Account.CryptoPortfolio -> with(value) {
UserWalletItemUM(
id = accountId.value,
name = accountName.toUM().value,
information = getInfo(account = this),
balance = getBalanceInfo(),
isEnabled = isEnabled,
endIcon = endIcon,
onClick = onClick,
imageState = getImageState(account = this),
)
}
is Account.Payment -> TODO("[REDACTED_JIRA]")
override fun convert(value: Account.CryptoPortfolio): UserWalletItemUM {
return with(value) {
UserWalletItemUM(
id = accountId.value,
name = accountName.toUM().value,
information = getInfo(account = this),
balance = getBalanceInfo(),
isEnabled = isEnabled,
endIcon = endIcon,
onClick = onClick,
imageState = getImageState(account = this),
)
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.error.UniversalError
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.pay.util.TangemPayErrorConverter
import com.tangem.domain.card.common.visa.VisaUtilities
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
@ -57,4 +58,25 @@ internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor(
errorConverter.convert(exception)
}
}
override fun create(userWallet: UserWallet): Either<UniversalError, CryptoCurrency.Token> {
return catch {
val network = networkFactory.create(
blockchain = VisaUtilities.visaBlockchain,
extraDerivationPath = null,
userWallet = userWallet,
)
cryptoCurrencyFactory.createToken(
network = requireNotNull(network),
rawId = CryptoCurrency.RawID(TOKEN_ID),
name = TOKEN_NAME,
symbol = TOKEN_NAME,
contractAddress = TOKEN_CONTRACT_ADDRESS,
decimals = TOKEN_DECIMALS,
)
}.mapLeft { exception ->
TangemLogger.withTag(TAG).e("Error", exception)
errorConverter.convert(exception)
}
}
}

View file

@ -8,4 +8,5 @@ import com.tangem.domain.models.wallet.UserWallet
interface TangemPayCryptoCurrencyFactory {
fun create(userWallet: UserWallet, chainId: Int): Either<UniversalError, CryptoCurrency>
fun create(userWallet: UserWallet): Either<UniversalError, CryptoCurrency.Token>
}

View file

@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.account.PortfolioFetcher
@ -179,13 +180,10 @@ internal class PortfolioSelectorModel @Inject constructor(
}
}
portfolio.accountsBalance.accountStatuses.forEach { accountStatus ->
portfolio.accountsBalance.accountStatuses.filterCryptoPortfolio().forEach { accountStatus ->
val isEnabledByFeature = isEnabled(wallet, accountStatus)
val account = accountStatus.account
val accountBalance = when (accountStatus) {
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
is AccountStatus.Payment -> accountStatus.value.totalFiatBalance
}
val accountBalance = accountStatus.tokenList.totalFiatBalance
val accountItemUM = AccountPortfolioItemUMConverter(
onClick = { selectorController.selectAccount(account.accountId) },
appCurrency = appCurrency,

View file

@ -8,6 +8,7 @@ import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
@ -28,7 +29,7 @@ internal class AvailableToAddDataConverter @Inject constructor(
availableNetworks: Set<TokenMarketInfo.Network>,
marketParams: TokenMarketParams,
): AvailableToAddData {
suspend fun AccountStatus.getAvailableToAddAccount(wallet: UserWallet): AvailableToAddAccount? {
suspend fun AccountStatus.CryptoPortfolio.getAvailableToAddAccount(wallet: UserWallet): AvailableToAddAccount? {
val currencies = availableNetworks
.mapNotNull { network ->
createCryptoCurrency(
@ -64,7 +65,7 @@ internal class AvailableToAddDataConverter @Inject constructor(
val (_, balance) = entry
val wallet = balance.userWallet
val filteredNetworks = wallet.filteredAvailableNetworks(availableNetworks)
val accounts = balance.accountsBalance.accountStatuses
val accounts = balance.accountsBalance.accountStatuses.filterCryptoPortfolio()
val availableToAddAccounts: Map<AccountId, AvailableToAddAccount> = accounts
.mapNotNull { accountStatus ->
val availableToAddAccount = accountStatus.getAvailableToAddAccount(wallet) ?: return@mapNotNull null
@ -103,17 +104,13 @@ internal class AvailableToAddDataConverter @Inject constructor(
userWallet: UserWallet,
network: TokenMarketInfo.Network,
marketParams: TokenMarketParams,
account: Account,
account: Account.CryptoPortfolio,
): CryptoCurrency? {
val derivationIndex = when (account) {
is Account.CryptoPortfolio -> account.derivationIndex
is Account.Payment -> TODO("[REDACTED_JIRA]")
}
return getTokenMarketCryptoCurrency(
userWalletId = userWallet.walletId,
tokenMarketParams = marketParams,
network = network,
accountIndex = derivationIndex,
accountIndex = account.derivationIndex,
)
}
}

View file

@ -18,6 +18,7 @@ import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -192,15 +193,12 @@ internal class MarketsPortfolioDelegate @AssistedInject constructor(
}.distinctUntilChanged()
private fun AccountStatusList.filterByRawID(): List<AccountWithAdded> {
fun AccountStatus.filterByRawID(): List<CryptoCurrencyStatus> = when (this) {
is AccountStatus.CryptoPortfolio -> this.tokenList.flattenCurrencies()
.filter { status ->
val currencyId = status.currency.id.rawCurrencyId ?: return@filter false
getTokenIdIfL2Network(currencyId.value) == currencyRawId.value
}
is AccountStatus.Payment -> TODO("[REDACTED_JIRA]")
}
return accountStatuses.map { accountStatus ->
fun AccountStatus.CryptoPortfolio.filterByRawID(): List<CryptoCurrencyStatus> = tokenList.flattenCurrencies()
.filter { status ->
val currencyId = status.currency.id.rawCurrencyId ?: return@filter false
getTokenIdIfL2Network(currencyId.value) == currencyRawId.value
}
return accountStatuses.filterCryptoPortfolio().map { accountStatus ->
AccountWithAdded(
accountStatus = accountStatus,
addedCurrency = accountStatus.filterByRawID(),

View file

@ -13,9 +13,13 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.CryptoCurrencyAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
@ -65,6 +69,7 @@ internal class SendDestinationModel @Inject constructor(
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val parseQrCodeUseCase: ParseQrCodeUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val tangemPayCryptoCurrencyFactory: TangemPayCryptoCurrencyFactory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
) : Model(), SendDestinationClickIntents {
@ -217,8 +222,6 @@ internal class SendDestinationModel @Inject constructor(
flow = getWalletsUseCase().conflate(),
flow2 = multiAccountStatusListSupplier().conflate(),
) { wallets, accountStatusLists ->
val cryptoCurrencyNetwork = cryptoCurrency.network
coroutineScope {
accountStatusLists.mapNotNull { accountStatusList ->
val wallet = wallets
@ -227,35 +230,55 @@ internal class SendDestinationModel @Inject constructor(
?: return@mapNotNull null
async {
accountStatusList.flattenCurrencies()
.filter { it.currency.network.rawId == cryptoCurrencyNetwork.rawId }
.mapNotNull { cryptoCurrencyStatus ->
val address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: return@mapNotNull null
// Find the corresponding account from accountStatuses
val account = accountStatusList.accountStatuses
.filterCryptoPortfolio()
.firstOrNull { accountStatus ->
accountStatus.tokenList
.flattenCurrencies()
.any { it.currency.id == cryptoCurrencyStatus.currency.id }
}?.account
DestinationWalletUM(
name = wallet.name,
address = address,
cryptoCurrency = cryptoCurrencyStatus.currency,
userWalletId = wallet.walletId,
account = account,
)
accountStatusList.accountStatuses.flatMap { accountStatus ->
when (accountStatus) {
is AccountStatus.CryptoPortfolio -> accountStatus.getDestinationWalletUM(wallet)
is AccountStatus.Payment -> listOfNotNull(accountStatus.getDestinationWalletUM(wallet))
}
}
}
}.awaitAll().flatten()
}
}.flowOn(dispatchers.default)
}
private fun AccountStatus.CryptoPortfolio.getDestinationWalletUM(wallet: UserWallet): List<DestinationWalletUM> {
return this.flattenCurrencies()
.filter { it.currency.network.rawId == cryptoCurrency.network.rawId }
.mapNotNull { cryptoCurrencyStatus ->
val address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: return@mapNotNull null
DestinationWalletUM(
name = wallet.name,
address = address,
cryptoCurrency = cryptoCurrencyStatus.currency,
userWalletId = wallet.walletId,
account = account,
)
}
}
private fun AccountStatus.Payment.getDestinationWalletUM(wallet: UserWallet): DestinationWalletUM? {
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: return null
val address = when (val status = this.value) {
is PaymentAccountStatusValue.Loaded -> status.cryptoBalance.depositAddress
is PaymentAccountStatusValue.Locked -> status.cryptoBalance.depositAddress
else -> return null
}
val currency = tangemPayCryptoCurrencyFactory.create(wallet).getOrNull() ?: return null
return if (contractAddress.equals(currency.contractAddress, true)) {
DestinationWalletUM(
name = wallet.name,
address = address,
cryptoCurrency = currency,
userWalletId = wallet.walletId,
account = account,
)
} else {
null
}
}
private fun validate(address: String, memo: String?, type: EnterAddressSource? = null) {
modelScope.launch {
_uiState.update(SendDestinationValidationStartedTransformer)

View file

@ -35,10 +35,11 @@ internal class SendRecipientWalletListConverter(
return this.filterNotNull()
.filter { destinationWallet ->
val isCoin = destinationWallet.cryptoCurrency is CryptoCurrency.Coin
val isPaymentAccount = destinationWallet.account is Account.Payment
val isNotSameAddress = destinationWallet.address != senderAddress
val isNotBlankAddress = destinationWallet.address.isNotBlank()
isNotBlankAddress && isCoin && (isNotSameAddress || isSelfSendAvailable)
isNotBlankAddress && (isCoin || isPaymentAccount) && (isNotSameAddress || isSelfSendAvailable)
}
.groupBy { item -> item.name }
.values.map { wallets ->