Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-19 10:12:01 +02:00
parent c68cf7165b
commit 39a190f056
22 changed files with 378 additions and 2 deletions

View file

@ -238,6 +238,8 @@ dependencies {
implementation(projects.features.home.impl)
implementation(projects.features.account.api)
implementation(projects.features.account.impl)
implementation(projects.features.tokenRecieve.api)
implementation(projects.features.tokenRecieve.impl)
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)

View file

@ -24,6 +24,7 @@ import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
@ -506,4 +507,20 @@ internal object TokensDomainModule {
fun provideGetAssetRequirementsUseCase(walletManagersFacade: WalletManagersFacade): GetAssetRequirementsUseCase {
return GetAssetRequirementsUseCase(walletManagersFacade)
}
@Provides
@Singleton
fun provideGetViewedTokenReceiveWarningUseCase(
tokenReceiveWarningsViewedRepository: TokenReceiveWarningsViewedRepository,
): GetViewedTokenReceiveWarningUseCase {
return GetViewedTokenReceiveWarningUseCase(tokenReceiveWarningsViewedRepository)
}
@Provides
@Singleton
fun provideSaveViewedTokenReceiveWarningUseCase(
tokenReceiveWarningsViewedRepository: TokenReceiveWarningsViewedRepository,
): SaveViewedTokenReceiveWarningUseCase {
return SaveViewedTokenReceiveWarningUseCase(tokenReceiveWarningsViewedRepository)
}
}

View file

@ -10,6 +10,7 @@ import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module
@ -18,6 +19,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Module
@InstallIn(SingletonComponent::class)
internal object TransactionDomainModule {
@ -210,4 +212,24 @@ internal object TransactionDomainModule {
): CreateNFTTransferTransactionUseCase {
return CreateNFTTransferTransactionUseCase(transactionRepository)
}
@Provides
@Singleton
fun provideGetEnsNameUseCase(
walletManagersFacade: WalletManagersFacade,
walletAddressServiceRepository: WalletAddressServiceRepository,
): GetEnsNameUseCase {
return GetEnsNameUseCase(
walletManagersFacade = walletManagersFacade,
walletAddressServiceRepository = walletAddressServiceRepository,
)
}
@Provides
@Singleton
fun provideGetReverseResolvedEnsAddressUseCase(
walletAddressServiceRepository: WalletAddressServiceRepository,
): GetReverseResolvedEnsAddressUseCase {
return GetReverseResolvedEnsAddressUseCase(walletAddressServiceRepository)
}
}

View file

@ -0,0 +1,44 @@
package com.tangem.datasource.di
import android.content.Context
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.datasource.local.token.DefaultTokenReceiveWarningActionStore
import com.tangem.datasource.local.token.TokenReceiveWarningActionStore
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.setTypes
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object TokenReceiveWarningModule {
@Provides
@Singleton
fun provideTokenReceiveWarningStore(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
dispatchers: CoroutineDispatcherProvider,
): TokenReceiveWarningActionStore {
return DefaultTokenReceiveWarningActionStore(
persistenceStore = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = setTypes<String>(),
defaultValue = emptySet(),
),
produceFile = { context.dataStoreFile(fileName = "token_receive_warnings_viewed") },
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
),
)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.datasource.local.token
import androidx.datastore.core.DataStore
import kotlinx.coroutines.flow.firstOrNull
internal class DefaultTokenReceiveWarningActionStore(
private val persistenceStore: DataStore<Set<String>>,
) : TokenReceiveWarningActionStore {
override suspend fun getSync(): Set<String> {
return persistenceStore.data.firstOrNull() ?: emptySet()
}
override suspend fun store(symbol: String) {
persistenceStore.updateData { data -> data.plus(symbol) }
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.datasource.local.token
interface TokenReceiveWarningActionStore {
suspend fun getSync(): Set<String>
suspend fun store(symbol: String)
}

View file

@ -8,14 +8,17 @@ import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository
import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository
import com.tangem.data.tokens.repository.DefaultTokenReceiveWarningsViewedRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.TokenReceiveWarningActionStore
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -83,4 +86,14 @@ internal object TokensDataModule {
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideTokenReceiveWarningsViewedRepository(
tokenReceiveWarningActionStore: TokenReceiveWarningActionStore,
): TokenReceiveWarningsViewedRepository {
return DefaultTokenReceiveWarningsViewedRepository(
tokenReceiveWarningActionStore = tokenReceiveWarningActionStore,
)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.data.tokens.repository
import com.tangem.datasource.local.token.TokenReceiveWarningActionStore
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
internal class DefaultTokenReceiveWarningsViewedRepository(
private val tokenReceiveWarningActionStore: TokenReceiveWarningActionStore,
) : TokenReceiveWarningsViewedRepository {
override suspend fun getViewedWarnings(): Set<String> {
return tokenReceiveWarningActionStore.getSync()
}
override suspend fun view(symbol: String) {
tokenReceiveWarningActionStore.store(symbol)
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.blockchain.blockchains.near.NearWalletManager
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.NameResolver
import com.tangem.blockchain.common.ResolveAddressResult
import com.tangem.blockchain.common.ReverseResolveAddressResult
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
@ -22,6 +23,40 @@ class DefaultWalletAddressServiceRepository(
private val dispatchers: CoroutineDispatcherProvider,
) : WalletAddressServiceRepository {
override suspend fun getEns(userWalletId: UserWalletId, network: Network, address: String): String? {
return withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
walletManager?.wallet?.ens
}
}
override suspend fun reverseResolveAddress(
userWalletId: UserWalletId,
network: Network,
address: String,
): ReverseResolveAddressResult {
return withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
if (walletManager is NameResolver) {
walletManager.reverseResolve(address.toByteArray())
} else {
ReverseResolveAddressResult.NotSupported
}
}
}
override suspend fun resolveAddress(
userWalletId: UserWalletId,
network: Network,

View file

@ -0,0 +1,7 @@
package com.tangem.domain.models.ens
sealed interface EnsAddress {
data class Address(val name: String) : EnsAddress
data class Error(val error: Exception) : EnsAddress
data object NotSupported : EnsAddress
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.tokens
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
class GetViewedTokenReceiveWarningUseCase(
private val tokenReceiveWarningsViewedRepository: TokenReceiveWarningsViewedRepository,
) {
suspend operator fun invoke(): Set<String> {
return tokenReceiveWarningsViewedRepository.getViewedWarnings()
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.tokens
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
class SaveViewedTokenReceiveWarningUseCase(
private val tokenReceiveWarningsViewedRepository: TokenReceiveWarningsViewedRepository,
) {
suspend operator fun invoke(symbol: String) {
tokenReceiveWarningsViewedRepository.view(symbol)
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.tokens.repository
interface TokenReceiveWarningsViewedRepository {
suspend fun getViewedWarnings(): Set<String>
suspend fun view(symbol: String)
}

View file

@ -1,15 +1,24 @@
package com.tangem.domain.transaction
import com.tangem.blockchain.common.ResolveAddressResult
import com.tangem.blockchain.common.ReverseResolveAddressResult
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.ParsedQrCode
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.models.ParsedQrCode
/**
* Wallet address service repository.
*/
interface WalletAddressServiceRepository {
suspend fun getEns(userWalletId: UserWalletId, network: Network, address: String): String?
suspend fun reverseResolveAddress(
userWalletId: UserWalletId,
network: Network,
address: String,
): ReverseResolveAddressResult
suspend fun resolveAddress(userWalletId: UserWalletId, network: Network, address: String): ResolveAddressResult
suspend fun validateAddress(userWalletId: UserWalletId, network: Network, address: String): Boolean

View file

@ -0,0 +1,31 @@
package com.tangem.domain.transaction.usecase
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
class GetEnsNameUseCase(
private val walletManagersFacade: WalletManagersFacade,
private val walletAddressServiceRepository: WalletAddressServiceRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId, network: Network, address: String): String? {
if (network.nameResolvingType != Network.NameResolvingType.ENS) {
return null
}
val addresses = walletManagersFacade.getAddresses(userWalletId, network)
val isOwnAddress = addresses.any { it.value == address }
if (!isOwnAddress) {
return null
}
return walletAddressServiceRepository.getEns(
userWalletId = userWalletId,
network = network,
address = address,
)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.domain.transaction.usecase
import com.tangem.blockchain.common.ReverseResolveAddressResult
import com.tangem.domain.models.ens.EnsAddress
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.WalletAddressServiceRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope
class GetReverseResolvedEnsAddressUseCase(private val walletAddressServiceRepository: WalletAddressServiceRepository) {
suspend operator fun invoke(
userWalletId: UserWalletId,
network: Network,
addresses: List<String>,
): List<EnsAddress> {
return supervisorScope {
val addressesDeferred = addresses.map { address ->
async {
val result = walletAddressServiceRepository.reverseResolveAddress(
userWalletId = userWalletId,
network = network,
address = address,
)
mapReverseResolveResult(result)
}
}
addressesDeferred.awaitAll()
}
}
private fun mapReverseResolveResult(reverseResolveAddressResult: ReverseResolveAddressResult): EnsAddress {
return when (reverseResolveAddressResult) {
is ReverseResolveAddressResult.Error -> EnsAddress.Error(reverseResolveAddressResult.error)
ReverseResolveAddressResult.NotSupported -> EnsAddress.NotSupported
is ReverseResolveAddressResult.Resolved -> EnsAddress.Address(reverseResolveAddressResult.name)
}
}
}

1
features/token-recieve/api/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,23 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("kotlin-parcelize")
id("configuration")
}
android {
namespace = "com.tangem.features.tokenrecieve.api"
}
dependencies {
/** Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
/** Common */
implementation(projects.common.ui)
/** Domain */
implementation(projects.domain.models)
}

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,55 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.kotlin.serialization)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.features.tokenreceive.impl"
}
dependencies {
/** Compose */
implementation(deps.compose.foundation)
implementation(deps.compose.material3)
implementation(deps.compose.navigation)
implementation(deps.compose.navigation.hilt)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
implementation(deps.compose.ui.utils)
implementation(deps.kotlin.immutable.collections)
implementation(deps.timber)
implementation(deps.lifecycle.compose)
implementation(deps.kotlin.serialization)
implementation(deps.decompose.ext.compose)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
/** Common */
implementation(projects.common.ui)
/** Core modules */
implementation(projects.common.routing)
implementation(projects.core.navigation)
implementation(projects.core.ui)
implementation(projects.core.utils)
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.decompose)
implementation(projects.core.res)
/** Domain modules */
implementation(projects.domain.models)
implementation(projects.domain.transaction)
implementation(projects.domain.transaction.models)
/** Feature Apis */
implementation(projects.features.tokenRecieve.api)
}

View file

@ -272,6 +272,9 @@ include(":features:welcome:impl")
include(":features:account:api")
include(":features:account:impl")
include(":features:token-recieve:api")
include(":features:token-recieve:impl")
// endregion Feature modules
// region Domain modules

@ -1 +1 @@
Subproject commit 794a8187e6d248ca3c21661df199a34ffeb0037a
Subproject commit 428b83bb378b615209e23afa05c88c454d06a9f1