Updated on 2026-08-14
This commit is contained in:
parent
53a84a6aa8
commit
d9f3a8e456
9 changed files with 376 additions and 45 deletions
|
|
@ -40,6 +40,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.swap.models)
|
||||
implementation(projects.domain.notifications)
|
||||
implementation(projects.domain.dynamicAddresses)
|
||||
|
||||
// region Project - Libs
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
|
||||
import com.tangem.domain.managetokens.ValidateDerivationPathUseCase
|
||||
import com.tangem.features.managetokens.utils.CardanoDerivationPathValidator
|
||||
import com.tangem.domain.managetokens.model.exceptoin.DerivationPathValidationException
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.features.managetokens.component.CustomTokenDerivationInputComponent
|
||||
|
|
@ -16,9 +17,12 @@ import com.tangem.features.managetokens.entity.customtoken.CustomDerivationInput
|
|||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.dialog.CustomDerivationInputDialog
|
||||
import com.tangem.features.managetokens.utils.CardanoDerivationPathValidator
|
||||
import com.tangem.features.managetokens.utils.DynamicAddressesDerivationValidator
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
|
|
@ -26,9 +30,15 @@ internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constr
|
|||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: CustomTokenDerivationInputComponent.Params,
|
||||
private val validateDerivationPathUseCase: ValidateDerivationPathUseCase,
|
||||
private val dynamicAddressesRepository: DynamicAddressesRepository,
|
||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
||||
) : CustomTokenDerivationInputComponent, AppComponentContext by context {
|
||||
|
||||
private val cardanoDerivationPathValidator = CardanoDerivationPathValidator()
|
||||
private val dynamicAddressesDerivationValidator = DynamicAddressesDerivationValidator(
|
||||
dynamicAddressesRepository = dynamicAddressesRepository,
|
||||
dynamicAddressesFeatureToggles = dynamicAddressesFeatureToggles,
|
||||
)
|
||||
|
||||
private val state: MutableStateFlow<CustomDerivationInputUM> = MutableStateFlow(
|
||||
value = getInitialState(),
|
||||
|
|
@ -52,13 +62,15 @@ internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constr
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||
private fun observeValueUpdates() {
|
||||
state
|
||||
.map { it.value.text }
|
||||
.distinctUntilChanged()
|
||||
.sample(periodMillis = 1_000)
|
||||
.onEach(::validateValue)
|
||||
.flatMapLatest { value ->
|
||||
flow { emit(validateValue(value)) }
|
||||
}
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +82,7 @@ internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constr
|
|||
onConfirm = ::confirm,
|
||||
)
|
||||
|
||||
private fun validateValue(value: String) {
|
||||
private suspend fun validateValue(value: String) {
|
||||
validateDerivationPathUseCase(value).getOrElse { e ->
|
||||
updateWithValidationError(e)
|
||||
return
|
||||
|
|
@ -90,6 +102,21 @@ internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constr
|
|||
return
|
||||
}
|
||||
|
||||
val isInvalidForDA = dynamicAddressesDerivationValidator.isInvalidForDynamicAddresses(
|
||||
userWalletId = params.mode.userWalletId,
|
||||
networkId = params.selectedNetwork.id,
|
||||
path = value,
|
||||
)
|
||||
if (isInvalidForDA) {
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
error = resourceReference(R.string.dynamic_addresses_custom_token_error_on_addition),
|
||||
isConfirmEnabled = false,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
error = null,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.features.managetokens.utils
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesDerivationChecker
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
|
||||
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.firstOrNull
|
||||
|
||||
/**
|
||||
* Validates that custom derivation paths don't conflict with Dynamic Addresses.
|
||||
*
|
||||
* When DA is enabled for an account, custom derivation paths with non-zero
|
||||
* change (node 3) or address_index (node 4) are forbidden, because DA
|
||||
* manages those nodes automatically.
|
||||
*/
|
||||
internal class DynamicAddressesDerivationValidator(
|
||||
private val dynamicAddressesRepository: DynamicAddressesRepository,
|
||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @return true if the path is invalid (DA is enabled for the same account and change/index ≠ 0)
|
||||
*/
|
||||
suspend fun isInvalidForDynamicAddresses(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
path: String?,
|
||||
): Boolean {
|
||||
if (!dynamicAddressesFeatureToggles.isDynamicAddressesEnabled) return false
|
||||
if (path == null) return false
|
||||
if (!isSupportedBlockchain(networkId)) return false
|
||||
|
||||
val basePath = networkId.derivationPath.value ?: return false
|
||||
val isEnabled = dynamicAddressesRepository
|
||||
.isDynamicAddressesEnabledForNetwork(userWalletId, networkId)
|
||||
.firstOrNull() == true
|
||||
if (!isEnabled) return false
|
||||
|
||||
return DynamicAddressesDerivationChecker.hasSameAccountWithNonZeroChangeOrIndex(
|
||||
customPath = path,
|
||||
basePath = basePath,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isSupportedBlockchain(networkId: Network.ID): Boolean {
|
||||
return DynamicAddressesSupportedBlockchains.isSupported(networkId.toBlockchain())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue