Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-01 18:44:35 +03:00
parent 8c8e00e7b4
commit 95b6933a14
15 changed files with 205 additions and 9 deletions

View file

@ -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)
}
) : TokensFeatureToggles

View file

@ -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")
}
}

View file

@ -0,0 +1,7 @@
plugins {
alias(deps.plugins.kotlin.jvm)
id("configuration")
}
dependencies {
}

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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<Throwable, ConsolidationInfo?> =
either {
val hasNonBaseBalances = dynamicAddressesRepository.hasNonBaseBalances(userWalletId, network)
if (!hasNonBaseBalances) {
dynamicAddressesRepository.disable(userWalletId, network)
return@either null
}
consolidationRepository.getConsolidationInfo(userWalletId, network)
.getOrElse { raise(it) }
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.dynamicaddresses
interface DynamicAddressesFeatureToggles {
val isDynamicAddressesEnabled: Boolean
}

View file

@ -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<Throwable, Unit> =
Either.catch {
dynamicAddressesRepository.enable(userWalletId, network, xpub)
}
}

View file

@ -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<Throwable, ConsolidationInfo> {
return consolidationRepository.getConsolidationInfo(userWalletId, network)
}
}

View file

@ -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<DynamicAddressesStatus> {
return dynamicAddressesRepository.getStatus(userWalletId, network)
}
}

View file

@ -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<Throwable, String> =
Either.catch {
dynamicAddressesRepository.getReceiveAddress(userWalletId, network)
}
}

View file

@ -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<Throwable, ConsolidationInfo>
suspend fun sendConsolidationTransaction(
userWalletId: UserWalletId,
network: Network,
signer: TransactionSigner,
): Either<Throwable, String>
}

View file

@ -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<DynamicAddressesStatus>
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
}

View file

@ -5,6 +5,4 @@ package com.tangem.domain.tokens
*
[REDACTED_AUTHOR]
*/
interface TokensFeatureToggles {
val isDynamicAddressesEnabled: Boolean
}
interface TokensFeatureToggles

View file

@ -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")