From 95b6933a147b0cb082aeb5d8beff199ed1906fdc Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Apr 2026 18:44:35 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tokens/DefaultTokensFeatureToggles.kt | 8 ++--- domain/dynamic-addresses/build.gradle.kts | 20 +++++++++++ .../dynamic-addresses/models/build.gradle.kts | 7 ++++ .../model/ConsolidationInfo.kt | 9 +++++ .../model/DynamicAddressesStatus.kt | 18 ++++++++++ .../DisableDynamicAddressesUseCase.kt | 33 +++++++++++++++++++ .../DynamicAddressesFeatureToggles.kt | 6 ++++ .../EnableDynamicAddressesUseCase.kt | 16 +++++++++ .../GetConsolidationInfoUseCase.kt | 16 +++++++++ .../GetDynamicAddressesStatusUseCase.kt | 16 +++++++++ .../GetDynamicReceiveAddressUseCase.kt | 16 +++++++++ .../repository/ConsolidationRepository.kt | 21 ++++++++++++ .../repository/DynamicAddressesRepository.kt | 22 +++++++++++++ .../domain/tokens/TokensFeatureToggles.kt | 4 +-- settings.gradle.kts | 2 ++ 15 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 domain/dynamic-addresses/build.gradle.kts create mode 100644 domain/dynamic-addresses/models/build.gradle.kts create mode 100644 domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/ConsolidationInfo.kt create mode 100644 domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/DynamicAddressesStatus.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesFeatureToggles.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/EnableDynamicAddressesUseCase.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetConsolidationInfoUseCase.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicAddressesStatusUseCase.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicReceiveAddressUseCase.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/ConsolidationRepository.kt create mode 100644 domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/DynamicAddressesRepository.kt diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt index a223acdcac..0029654a9b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt @@ -1,13 +1,9 @@ package com.tangem.tap.domain.tokens -import com.tangem.core.configtoggle.FeatureToggles import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.domain.tokens.TokensFeatureToggles +@Suppress("UnusedPrivateProperty") internal class DefaultTokensFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, -) : TokensFeatureToggles { - - override val isDynamicAddressesEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.DYNAMIC_ADDRESSES_ENABLED) -} \ No newline at end of file +) : TokensFeatureToggles \ No newline at end of file diff --git a/domain/dynamic-addresses/build.gradle.kts b/domain/dynamic-addresses/build.gradle.kts new file mode 100644 index 0000000000..6190ff052f --- /dev/null +++ b/domain/dynamic-addresses/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.domain.dynamicaddresses" +} + +dependencies { + api(projects.domain.core) + api(projects.domain.dynamicAddresses.models) + + implementation(projects.domain.models) + + implementation(tangemDeps.blockchain) { + exclude(module = "joda-time") + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/models/build.gradle.kts b/domain/dynamic-addresses/models/build.gradle.kts new file mode 100644 index 0000000000..64e9c3fdfe --- /dev/null +++ b/domain/dynamic-addresses/models/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + id("configuration") +} + +dependencies { +} \ No newline at end of file diff --git a/domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/ConsolidationInfo.kt b/domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/ConsolidationInfo.kt new file mode 100644 index 0000000000..47c36bd8f9 --- /dev/null +++ b/domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/ConsolidationInfo.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.dynamicaddresses.model + +import java.math.BigDecimal + +data class ConsolidationInfo( + val fee: BigDecimal, + val inputCount: Int, + val canCoverFee: Boolean, +) \ No newline at end of file diff --git a/domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/DynamicAddressesStatus.kt b/domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/DynamicAddressesStatus.kt new file mode 100644 index 0000000000..a64a721595 --- /dev/null +++ b/domain/dynamic-addresses/models/src/main/kotlin/com/tangem/domain/dynamicaddresses/model/DynamicAddressesStatus.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.dynamicaddresses.model + +import java.math.BigDecimal + +enum class DynamicAddressesStatus { + ENABLED, + + DISABLED, + + /** Enabled on backend, but local XPUB setup required (cross-device sync) */ + ENABLED_REQUIRES_SETUP, +} + +data class UsedAddress( + val address: String, + val path: String, + val balance: BigDecimal, +) \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt new file mode 100644 index 0000000000..ff9e869754 --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DisableDynamicAddressesUseCase.kt @@ -0,0 +1,33 @@ +package com.tangem.domain.dynamicaddresses + +import arrow.core.Either +import arrow.core.getOrElse +import arrow.core.raise.either +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.dynamicaddresses.model.ConsolidationInfo +import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository +import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository + +class DisableDynamicAddressesUseCase( + private val dynamicAddressesRepository: DynamicAddressesRepository, + private val consolidationRepository: ConsolidationRepository, +) { + + /** + * Returns [ConsolidationInfo] when consolidation is required before disabling, + * or null when DA can be disabled immediately (no non-base balances). + */ + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = + either { + val hasNonBaseBalances = dynamicAddressesRepository.hasNonBaseBalances(userWalletId, network) + + if (!hasNonBaseBalances) { + dynamicAddressesRepository.disable(userWalletId, network) + return@either null + } + + consolidationRepository.getConsolidationInfo(userWalletId, network) + .getOrElse { raise(it) } + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesFeatureToggles.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesFeatureToggles.kt new file mode 100644 index 0000000000..0d665d090e --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/DynamicAddressesFeatureToggles.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.dynamicaddresses + +interface DynamicAddressesFeatureToggles { + + val isDynamicAddressesEnabled: Boolean +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/EnableDynamicAddressesUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/EnableDynamicAddressesUseCase.kt new file mode 100644 index 0000000000..8b888a54f4 --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/EnableDynamicAddressesUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.dynamicaddresses + +import arrow.core.Either +import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId + +class EnableDynamicAddressesUseCase( + private val dynamicAddressesRepository: DynamicAddressesRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network, xpub: String): Either = + Either.catch { + dynamicAddressesRepository.enable(userWalletId, network, xpub) + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetConsolidationInfoUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetConsolidationInfoUseCase.kt new file mode 100644 index 0000000000..a498b28cc7 --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetConsolidationInfoUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.dynamicaddresses + +import arrow.core.Either +import com.tangem.domain.dynamicaddresses.model.ConsolidationInfo +import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId + +class GetConsolidationInfoUseCase( + private val consolidationRepository: ConsolidationRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either { + return consolidationRepository.getConsolidationInfo(userWalletId, network) + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicAddressesStatusUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicAddressesStatusUseCase.kt new file mode 100644 index 0000000000..3ea5c97bdc --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicAddressesStatusUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.dynamicaddresses + +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 kotlinx.coroutines.flow.Flow + +class GetDynamicAddressesStatusUseCase( + private val dynamicAddressesRepository: DynamicAddressesRepository, +) { + + operator fun invoke(userWalletId: UserWalletId, network: Network): Flow { + return dynamicAddressesRepository.getStatus(userWalletId, network) + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicReceiveAddressUseCase.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicReceiveAddressUseCase.kt new file mode 100644 index 0000000000..591d915366 --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/GetDynamicReceiveAddressUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.dynamicaddresses + +import arrow.core.Either +import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId + +class GetDynamicReceiveAddressUseCase( + private val dynamicAddressesRepository: DynamicAddressesRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = + Either.catch { + dynamicAddressesRepository.getReceiveAddress(userWalletId, network) + } +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/ConsolidationRepository.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/ConsolidationRepository.kt new file mode 100644 index 0000000000..4eaa27eb38 --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/ConsolidationRepository.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.dynamicaddresses.repository + +import arrow.core.Either +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.domain.dynamicaddresses.model.ConsolidationInfo +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId + +interface ConsolidationRepository { + + suspend fun getConsolidationInfo( + userWalletId: UserWalletId, + network: Network, + ): Either + + suspend fun sendConsolidationTransaction( + userWalletId: UserWalletId, + network: Network, + signer: TransactionSigner, + ): Either +} \ No newline at end of file diff --git a/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/DynamicAddressesRepository.kt b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/DynamicAddressesRepository.kt new file mode 100644 index 0000000000..c30355fa48 --- /dev/null +++ b/domain/dynamic-addresses/src/main/kotlin/com/tangem/domain/dynamicaddresses/repository/DynamicAddressesRepository.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.dynamicaddresses.repository + +import com.tangem.domain.dynamicaddresses.model.DynamicAddressesStatus +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface DynamicAddressesRepository { + + fun getStatus(userWalletId: UserWalletId, network: Network): Flow + + suspend fun enable(userWalletId: UserWalletId, network: Network, xpub: String) + + suspend fun disable(userWalletId: UserWalletId, network: Network) + + suspend fun getReceiveAddress(userWalletId: UserWalletId, network: Network): String + + // for explorer url + suspend fun getLastUsedReceiveAddress(userWalletId: UserWalletId, network: Network): String? + + suspend fun hasNonBaseBalances(userWalletId: UserWalletId, network: Network): Boolean +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt index ea588e781c..a2eeb1e0a6 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt @@ -5,6 +5,4 @@ package com.tangem.domain.tokens * [REDACTED_AUTHOR] */ -interface TokensFeatureToggles { - val isDynamicAddressesEnabled: Boolean -} \ No newline at end of file +interface TokensFeatureToggles \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 46f42fb5bc..6131532e14 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -335,6 +335,8 @@ include(":domain:common") include(":domain:core") include(":domain:demo") include(":domain:demo:models") +include(":domain:dynamic-addresses") +include(":domain:dynamic-addresses:models") include(":domain:settings") include(":domain:tokens") include(":domain:tokens:models")