Updated on 2026-08-14
This commit is contained in:
parent
182aa24095
commit
94a9b132a2
23 changed files with 458 additions and 60 deletions
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.data.dynamicaddresses
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.DynamicAddressesManager
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultConsolidationRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ConsolidationRepository {
|
||||
|
||||
override suspend fun createConsolidationTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Either<Throwable, TransactionData> = Either.catch {
|
||||
withContext(dispatchers.io) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
|
||||
?: error("WalletManager not found")
|
||||
|
||||
val dynamicAddressesManager = walletManager as? DynamicAddressesManager
|
||||
?: error("WalletManager does not support dynamic addresses")
|
||||
|
||||
val fee = getNormalFee(walletManager)
|
||||
|
||||
when (val result = dynamicAddressesManager.createConsolidationTransaction(fee)) {
|
||||
is Result.Success -> result.data
|
||||
is Result.Failure -> error("Failed to create consolidation tx: ${result.error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedParameter")
|
||||
private fun getNormalFee(walletManager: WalletManager): Fee {
|
||||
TODO("Fee calculation for consolidation from multiple addresses is not yet implemented")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.data.dynamicaddresses
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
|
||||
internal class DefaultDynamicAddressesFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : DynamicAddressesFeatureToggles {
|
||||
|
||||
override val isDynamicAddressesEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.DYNAMIC_ADDRESSES_ENABLED)
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.data.dynamicaddresses
|
||||
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultDynamicAddressesRepository(
|
||||
private val walletAccountsFetcher: WalletAccountsFetcher,
|
||||
private val walletAccountsSaver: WalletAccountsSaver,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : DynamicAddressesRepository {
|
||||
|
||||
override fun getStatus(userWalletId: UserWalletId, network: Network): Flow<DynamicAddressesStatus> {
|
||||
return walletAccountsFetcher.get(userWalletId)
|
||||
.map { response ->
|
||||
val token = response.findToken(network)
|
||||
when {
|
||||
token?.dynamicAddressesEnabled == true -> DynamicAddressesStatus.ENABLED
|
||||
else -> DynamicAddressesStatus.DISABLED
|
||||
}
|
||||
// TODO handle ENABLED_REQUIRES_SETUP when XPUB is not derived locally
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
override suspend fun enable(userWalletId: UserWalletId, network: Network, xpub: String) {
|
||||
withContext(dispatchers.io) {
|
||||
walletManagersFacade.enableXpubMode(userWalletId, network, xpub)
|
||||
updateTokenDynamicAddressesFlag(userWalletId, network, enabled = true)
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Failed to sync tokens after DA enable for $userWalletId", it) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun disable(userWalletId: UserWalletId, network: Network) {
|
||||
withContext(dispatchers.io) {
|
||||
walletManagersFacade.disableXpubMode(userWalletId, network)
|
||||
updateTokenDynamicAddressesFlag(userWalletId, network, enabled = false)
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { TangemLogger.e("Failed to sync tokens after DA disable for $userWalletId", it) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getReceiveAddress(userWalletId: UserWalletId, network: Network): String {
|
||||
return walletManagersFacade.getDynamicAddressesReceiveAddress(userWalletId, network)
|
||||
?: error("Dynamic receive address not available for $userWalletId / ${network.id}")
|
||||
}
|
||||
|
||||
override suspend fun getLastUsedReceiveAddress(userWalletId: UserWalletId, network: Network): String? {
|
||||
return walletManagersFacade.getDynamicAddressesLastUsedReceiveAddress(userWalletId, network)
|
||||
}
|
||||
|
||||
override suspend fun hasNonBaseBalances(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
return walletManagersFacade.hasDynamicAddressesNonBaseBalances(userWalletId, network)
|
||||
}
|
||||
|
||||
private suspend fun updateTokenDynamicAddressesFlag(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
enabled: Boolean,
|
||||
) {
|
||||
walletAccountsSaver.update(userWalletId) { response ->
|
||||
response?.copy(
|
||||
accounts = response.accounts.map { account ->
|
||||
account.copy(
|
||||
tokens = account.tokens?.map { token ->
|
||||
if (token.matchesNetwork(network)) {
|
||||
token.copy(dynamicAddressesEnabled = enabled)
|
||||
} else {
|
||||
token
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun GetWalletAccountsResponse.findToken(network: Network): UserTokensResponse.Token? {
|
||||
return accounts
|
||||
.flatMap { it.tokens.orEmpty() }
|
||||
.find { it.matchesNetwork(network) }
|
||||
}
|
||||
|
||||
private fun UserTokensResponse.Token.matchesNetwork(network: Network): Boolean {
|
||||
return networkId == network.backendId &&
|
||||
derivationPath == network.derivationPath.value &&
|
||||
contractAddress == null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.data.dynamicaddresses.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.data.dynamicaddresses.DefaultConsolidationRepository
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.data.dynamicaddresses.DefaultDynamicAddressesFeatureToggles
|
||||
import com.tangem.data.dynamicaddresses.DefaultDynamicAddressesRepository
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object DynamicAddressesDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDynamicAddressesRepository(
|
||||
walletAccountsFetcher: WalletAccountsFetcher,
|
||||
walletAccountsSaver: WalletAccountsSaver,
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DynamicAddressesRepository {
|
||||
return DefaultDynamicAddressesRepository(
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
walletAccountsSaver = walletAccountsSaver,
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDynamicAddressesFeatureToggles(
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): DynamicAddressesFeatureToggles {
|
||||
return DefaultDynamicAddressesFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideConsolidationRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ConsolidationRepository {
|
||||
return DefaultConsolidationRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue