From 0662e31ed0e61a05d076755af91a7a712b68217b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Dec 2023 15:08:07 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/data/RuntimeUserWalletsStore.kt | 4 + .../tangem/tap/di/domain/CardDomainModule.kt | 14 ++- .../card/DefaultDerivationsRepository.kt | 75 +++++++++++ .../card/DefaultDerivePublicKeysUseCase.kt | 14 ++- .../domain/card/MissedDerivationsFinder.kt | 118 ++++++++++++++++++ .../domain/DefaultCustomTokenInteractor.kt | 2 + .../tokens/legacy/redux/TokensMiddleware.kt | 2 + .../local/userwallet/UserWalletsStore.kt | 3 + domain/card/build.gradle.kts | 3 +- .../domain/card/DerivePublicKeysUseCase.kt | 6 + .../card/repository/DerivationsRepository.kt | 9 ++ .../wallet/viewmodels/WalletViewModel.kt | 3 +- .../intents/WalletWarningsClickIntents.kt | 3 +- .../tangem/lib/crypto/DerivationManager.kt | 2 + 14 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index a84ddc77c6..884fa65323 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -21,4 +21,8 @@ internal class RuntimeUserWalletsStore( ?.firstOrNull() ?.singleOrNull { it.walletId == key } } + + override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) { + walletsStateHolder.userWalletsListManager?.update(userWalletId, update) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 918b41ff04..3444333c34 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -1,5 +1,6 @@ package com.tangem.tap.di.domain +import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.* import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository @@ -8,7 +9,9 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.card.DefaultDerivationsRepository import com.tangem.tap.domain.card.DefaultDerivePublicKeysUseCase +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -61,8 +64,15 @@ internal object CardDomainModule { @Provides @ViewModelScoped - fun provideDerivePublicKeysUseCase(tangemSdkManager: TangemSdkManager): DerivePublicKeysUseCase { - return DefaultDerivePublicKeysUseCase(tangemSdkManager = tangemSdkManager) + fun provideDerivePublicKeysUseCase( + tangemSdkManager: TangemSdkManager, + userWalletsStore: UserWalletsStore, + dispatchers: CoroutineDispatcherProvider, + ): DerivePublicKeysUseCase { + return DefaultDerivePublicKeysUseCase( + tangemSdkManager = tangemSdkManager, + derivationsRepository = DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, dispatchers), + ) } @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt new file mode 100644 index 0000000000..bd0b2d6054 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -0,0 +1,75 @@ +package com.tangem.tap.domain.card + +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import timber.log.Timber + +internal typealias Derivations = Map> +private typealias DerivedKeys = Map + +internal class DefaultDerivationsRepository( + private val tangemSdkManager: TangemSdkManager, + private val userWalletsStore: UserWalletsStore, + private val dispatchers: CoroutineDispatcherProvider, +) : DerivationsRepository { + + override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) { + val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") + + val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse) + .find(currencies) + .ifEmpty { + Timber.d("Nothing to derive") + return + } + + tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations) + .doOnSuccess { response -> + updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold( + onSuccess = { return }, + onFailure = { throw it }, + ) + } + .doOnFailure { throw it } + + error("This code should never be reached") + } + + private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result { + return runCatching(dispatchers.io) { + userWalletsStore.update( + userWalletId = userWalletId, + update = { userWallet -> userWallet.updateDerivedKeys(keys) }, + ) + } + } + + private fun UserWallet.updateDerivedKeys(keys: DerivedKeys): UserWallet { + return copy( + scanResponse = scanResponse.copy( + derivedKeys = getUpdatedDerivedKeys(oldKeys = scanResponse.derivedKeys, newKeys = keys), + ), + ) + } + + private fun getUpdatedDerivedKeys(oldKeys: DerivedKeys, newKeys: DerivedKeys): DerivedKeys { + return (oldKeys.keys + newKeys.keys).toSet() + .associateWith { walletKey -> + val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey] ?: emptyMap()) + val newDerivations = newKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) + + ExtendedPublicKeysMap(oldDerivations + newDerivations) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt index d402c97a7c..a09df2969a 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivePublicKeysUseCase.kt @@ -8,12 +8,15 @@ import com.tangem.common.doOnSuccess import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.card.DerivePublicKeysUseCase +import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.tap.domain.TangemSdkManager -// TODO: [REDACTED_JIRA] internal class DefaultDerivePublicKeysUseCase( private val tangemSdkManager: TangemSdkManager, + private val derivationsRepository: DerivationsRepository, ) : DerivePublicKeysUseCase { override suspend fun invoke( @@ -26,4 +29,13 @@ internal class DefaultDerivePublicKeysUseCase( return Unit.left() } + + override suspend fun invoke( + userWalletId: UserWalletId, + currencies: List, + ): Either { + return Either.catch { + derivationsRepository.derivePublicKeys(userWalletId, currencies) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt new file mode 100644 index 0000000000..97c0a7bf8f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/card/MissedDerivationsFinder.kt @@ -0,0 +1,118 @@ +package com.tangem.tap.domain.card + +import com.tangem.blockchain.blockchains.cardano.CardanoUtils +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.card.EllipticCurve +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.extensions.toMapKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.common.configs.CardConfig +import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.models.scan.KeyWalletPublicKey +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.operations.derivation.ExtendedPublicKeysMap + +private typealias DerivationData = Pair> + +/** + * Finder of missed derivations + * + * @property scanResponse scanning response + * +[REDACTED_AUTHOR] + */ +internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) { + + /** Find missed derivations for given currencies [currencies] */ + fun find(currencies: List): Derivations { + return buildMap> { + currencies + .mapToNewDerivations() + .forEach { data -> + val current = this[data.first] + if (current != null) { + current.addAll(data.second) + current.distinct() + } else { + this[data.first] = data.second.toMutableList() + } + } + } + } + + private fun List.mapToNewDerivations(): List { + val config = CardConfig.createConfig(scanResponse.card) + return mapNotNull { currency -> + val blockchain = Blockchain.fromId(id = currency.network.id.value) + val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null + + findNewDerivations(curve = curve, scanResponse = scanResponse, currency = currency) + } + } + + private fun findNewDerivations( + curve: EllipticCurve, + scanResponse: ScanResponse, + currency: CryptoCurrency, + ): DerivationData? { + val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null + val publicKey = wallet.publicKey.toMapKey() + + val derivationCandidates = currency + .getDerivationCandidates(curve) + .ifEmpty { return null } + .filterAlreadyDerivedKeys(publicKey) + .ifEmpty { return null } + + return publicKey to derivationCandidates + } + + private fun CryptoCurrency.getDerivationCandidates(curve: EllipticCurve): List { + val blockchain = Blockchain.fromId(id = network.id.value) + + return buildList { + add(blockchain.getDerivationPath(curve = curve)) + add(blockchain.getCustomDerivationPath(curve = curve, currency = this@getDerivationCandidates)) + add(blockchain.getCardanoDerivationPathIfNeeded(currency = this@getDerivationCandidates)) + } + .filterNotNull() + .distinct() + } + + private fun Blockchain.getDerivationPath(curve: EllipticCurve): DerivationPath? { + return if (getSupportedCurves().contains(curve)) { + derivationPath(style = scanResponse.derivationStyleProvider.getDerivationStyle()) + } else { + null + } + } + + private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, currency: CryptoCurrency): DerivationPath? { + return if (getSupportedCurves().contains(curve)) { + currency.network.derivationPath.value?.let(::DerivationPath) + } else { + null + } + } + + private fun Blockchain.getCardanoDerivationPathIfNeeded(currency: CryptoCurrency): DerivationPath? { + return if (currency is CryptoCurrency.Coin && this == Blockchain.Cardano) { + currency.network.derivationPath.value?.let { + CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it)) + } + } else { + null + } + } + + private fun List.filterAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List { + val alreadyDerivedPaths = getAlreadyDerivedKeys(publicKey) + return filterNot(alreadyDerivedPaths::contains) + } + + private fun getAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List { + val extendedPublicKeysMap = scanResponse.derivedKeys[publicKey] ?: ExtendedPublicKeysMap(emptyMap()) + return extendedPublicKeysMap.keys.toList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt index 4541939006..2eb39173ce 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt @@ -83,6 +83,8 @@ class DefaultCustomTokenInteractor( return currency.derivationPath?.let { !scanResponse.hasDerivation(currency.blockchain, it) } ?: false } + @Deprecated("Use DerivePublicKeysUseCase instead") + // FIXME: Migration [REDACTED_JIRA] private suspend fun deriveMissingBlockchains( userWallet: UserWallet, currencyList: List, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt index 7f8d65c2d5..e2c41c8e88 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt @@ -96,6 +96,8 @@ object TokensMiddleware { } } + @Deprecated(message = "Use DerivePublicKeysUseCase instead") + // FIXME: Migration [REDACTED_JIRA] private fun deriveMissingCoins( scanResponse: ScanResponse, currencyList: List, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt index c905063cc3..f3b1c56267 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/userwallet/UserWalletsStore.kt @@ -8,4 +8,7 @@ interface UserWalletsStore { val selectedUserWalletOrNull: UserWallet? suspend fun getSyncOrNull(key: UserWalletId): UserWallet? + + @Throws + suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) } \ No newline at end of file diff --git a/domain/card/build.gradle.kts b/domain/card/build.gradle.kts index 342a410f5a..4ec562467f 100644 --- a/domain/card/build.gradle.kts +++ b/domain/card/build.gradle.kts @@ -16,7 +16,8 @@ dependencies { implementation(projects.domain.legacy) // TODO: Remove after new card scan result was implemented implementation(projects.domain.models) - + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) { diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt index 8a82c6b79d..d810031075 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/DerivePublicKeysUseCase.kt @@ -3,12 +3,18 @@ package com.tangem.domain.card import arrow.core.Either import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse +// FIXME: Cover with tests [REDACTED_JIRA] interface DerivePublicKeysUseCase { + @Deprecated(message = "Use invoke(cardId: String?, derivations: Map>) instead") suspend operator fun invoke( cardId: String? = null, derivations: Map>, ): Either + + suspend operator fun invoke(userWalletId: UserWalletId, currencies: List): Either } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt new file mode 100644 index 0000000000..eb058255a9 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.card.repository + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +interface DerivationsRepository { + + suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 1d8fd54f88..4306e8b79a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -315,7 +315,8 @@ internal class WalletViewModel @Inject constructor( } } - // TODO: [REDACTED_JIRA] + @Deprecated("Use DerivePublicKeysUseCase instead") + // FIXME: Migration: [REDACTED_JIRA] private fun deriveMissingCurrencies( scanResponse: ScanResponse, currencyList: List, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index b433801e19..4643ee1da6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -107,6 +107,8 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( } } + @Deprecated("Use DerivePublicKeysUseCase instead") + // FIXME: Migration: [REDACTED_JIRA] override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) { val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return @@ -127,7 +129,6 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor( } } - // TODO: [REDACTED_JIRA] private fun deriveMissingCurrencies( scanResponse: ScanResponse, currencyList: List, diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt index bf9803cbb2..b62cb56576 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt @@ -2,6 +2,8 @@ package com.tangem.lib.crypto import com.tangem.lib.crypto.models.Currency +// FIXME: Migration [REDACTED_JIRA] +@Deprecated(message = "Use DerivePublicKeysUseCase instead") interface DerivationManager { /**