Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-25 15:09:26 +03:00
parent 041ff02106
commit 307b311afd
8 changed files with 77 additions and 17 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.account.supplier.MultiAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository
import com.tangem.domain.qrscanning.usecases.EmitQrScannedEventUseCase
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
@ -41,11 +42,13 @@ internal object QrScanningDomainModule {
multiAccountListSupplier: MultiAccountListSupplier,
qrScanningEventsRepository: QrScanningEventsRepository,
userWalletsListRepository: UserWalletsListRepository,
networksRepository: NetworksRepository,
): ResolveQrSendTargetsUseCase {
return ResolveQrSendTargetsUseCase(
multiAccountListSupplier = multiAccountListSupplier,
qrScanningEventsRepository = qrScanningEventsRepository,
userWalletsListRepository = userWalletsListRepository,
networksRepository = networksRepository,
)
}
}

View file

@ -57,7 +57,7 @@ internal class DefaultNetworksRepository(
.map { currency ->
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = getDefaultAddress(userWalletId, network),
address = getDefaultAddress(userWalletId, network).orEmpty(),
)
}
}
@ -74,11 +74,17 @@ internal class DefaultNetworksRepository(
.map { currency ->
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = getDefaultAddress(userWalletId, currency.network),
address = getDefaultAddress(userWalletId, currency.network).orEmpty(),
)
}
}
override suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String? {
return withContext(dispatchers.io) {
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = network)
}
}
override suspend fun hasCachedStatuses(userWalletId: UserWalletId): Boolean {
return networksStatusesStore.contains(userWalletId)
}
@ -100,10 +106,4 @@ internal class DefaultNetworksRepository(
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = networkStatus)
}
private suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String {
return withContext(dispatchers.io) {
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = network).orEmpty()
}
}
}

View file

@ -30,6 +30,11 @@ interface NetworksRepository {
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network.RawID): List<CryptoCurrencyAddress>
/**
* Returns the default address for the given [network] in the selected [userWalletId]
*/
suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String?
/** Checks if there are cached statuses for given [userWalletId] */
suspend fun hasCachedStatuses(userWalletId: UserWalletId): Boolean
}

View file

@ -14,6 +14,7 @@ dependencies {
api(projects.domain.models)
implementation(projects.domain.account)
implementation(projects.domain.common)
implementation(projects.domain.networks)
implementation(projects.domain.qrScanning.models)
implementation(projects.domain.tokens.models)

View file

@ -39,6 +39,8 @@ sealed class QrSendTarget {
)
}
data object AddressSameAsWallet : QrSendTarget()
data class WalletConnect(val uri: String) : QrSendTarget()
data class Error(val error: ClassifiedQrContent.Error) : QrSendTarget()

View file

@ -5,17 +5,23 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountName
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.math.BigDecimal
import com.tangem.domain.qrscanning.models.QrSendTarget
import kotlinx.coroutines.awaitAll
class ResolveQrSendTargetsUseCase(
private val multiAccountListSupplier: MultiAccountListSupplier,
private val qrScanningEventsRepository: QrScanningEventsRepository,
private val userWalletsListRepository: UserWalletsListRepository,
private val networksRepository: NetworksRepository,
) {
suspend operator fun invoke(qrCode: String): QrSendTarget {
@ -49,7 +55,7 @@ class ResolveQrSendTargetsUseCase(
return resolve(classified, portfolioIndex)
}
private fun resolve(classified: ClassifiedQrContent, portfolioIndex: PortfolioIndex): QrSendTarget {
private suspend fun resolve(classified: ClassifiedQrContent, portfolioIndex: PortfolioIndex): QrSendTarget {
return when (classified) {
is ClassifiedQrContent.WalletConnect -> QrSendTarget.WalletConnect(classified.uri)
is ClassifiedQrContent.Error -> QrSendTarget.Error(classified)
@ -70,14 +76,19 @@ class ResolveQrSendTargetsUseCase(
}
}
private fun resolveAddressTarget(
private suspend fun resolveAddressTarget(
address: String,
amount: BigDecimal?,
memo: String?,
matchingCurrencies: List<CryptoCurrency>,
portfolioIndex: PortfolioIndex,
): QrSendTarget {
val walletGroups = buildWalletGroups(matchingCurrencies, portfolioIndex)
val ownAddressNetworks = findOwnAddressNetworks(address, matchingCurrencies, portfolioIndex)
val walletGroups = buildWalletGroups(matchingCurrencies, portfolioIndex, ownAddressNetworks)
if (walletGroups.isEmpty()) {
return QrSendTarget.AddressSameAsWallet
}
val singleGroup = walletGroups.singleOrNull()
val singleCurrency = singleGroup?.accounts?.singleOrNull()?.currencies?.singleOrNull()
@ -100,9 +111,33 @@ class ResolveQrSendTargetsUseCase(
}
}
private suspend fun findOwnAddressNetworks(
address: String,
matchingCurrencies: List<CryptoCurrency>,
portfolioIndex: PortfolioIndex,
): Map<UserWalletId, List<Network.ID>> = coroutineScope {
matchingCurrencies.distinctBy { it.id }
.flatMap { currency ->
portfolioIndex.currencyLocations[currency.id].orEmpty().map {
it.accountId.userWalletId to currency.network
}
}
.distinct()
.map { (walletId, network) ->
async {
val ownAddress = networksRepository.getDefaultAddress(walletId, network)
if (ownAddress == address) walletId to network else null
}
}
.awaitAll()
.filterNotNull()
.groupBy(keySelector = { it.first }, valueTransform = { it.second.id })
}
private fun buildWalletGroups(
matchingCurrencies: List<CryptoCurrency>,
portfolioIndex: PortfolioIndex,
ownAddressNetworks: Map<UserWalletId, List<Network.ID>>,
): List<QrSendTarget.Multiple.WalletGroup> {
val walletMap = linkedMapOf<UserWalletId, WalletInfo>()
val uniqueCurrencies = matchingCurrencies.distinctBy { it.id }
@ -110,13 +145,18 @@ class ResolveQrSendTargetsUseCase(
for (currency in uniqueCurrencies) {
val locations = portfolioIndex.currencyLocations[currency.id].orEmpty()
for (location in locations) {
val walletInfo = walletMap.getOrPut(location.accountId.userWalletId) {
WalletInfo(location.walletName, linkedMapOf())
val walletId = location.accountId.userWalletId
val isOwnAddress = ownAddressNetworks[walletId]?.contains(currency.network.id) == true
if (!isOwnAddress) {
val walletInfo = walletMap.getOrPut(walletId) {
WalletInfo(location.walletName, linkedMapOf())
}
val accountInfo = walletInfo.accounts.getOrPut(location.accountId) {
AccountInfo(location.accountName, mutableListOf())
}
accountInfo.currencies.add(currency)
}
val accountInfo = walletInfo.accounts.getOrPut(location.accountId) {
AccountInfo(location.accountName, mutableListOf())
}
accountInfo.currencies.add(currency)
}
}

View file

@ -785,6 +785,9 @@ internal class WalletModel @Inject constructor(
is QrSendTarget.Multiple -> {
innerWalletRouter.openNetworkSelectionBottomSheet(target)
}
is QrSendTarget.AddressSameAsWallet -> {
uiMessageSender.send(WalletAlertUM.qrCodeAddressSameAsWallet())
}
is QrSendTarget.Error -> handleQrError(target.error)
}
}

View file

@ -94,6 +94,12 @@ internal object WalletAlertUM {
)
}
fun qrCodeAddressSameAsWallet(): DialogMessage {
return DialogMessage(
message = resourceReference(R.string.send_error_address_same_as_wallet),
)
}
fun confirmExpressStatusHide(onConfirmClick: () -> Unit): DialogMessage {
return DialogMessage(
title = resourceReference(R.string.express_status_hide_dialog_title),