Updated on 2026-08-14
This commit is contained in:
parent
040b5f0cf6
commit
505df4db90
13 changed files with 326 additions and 352 deletions
|
|
@ -17,7 +17,6 @@ import com.tangem.core.navigation.share.ShareManager
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||||
import com.tangem.data.card.TransactionSignerFactory
|
import com.tangem.data.card.TransactionSignerFactory
|
||||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
|
||||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
|
|
@ -49,7 +48,6 @@ import com.tangem.tap.common.analytics.handlers.appsflyer.AppsFlyerClient
|
||||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import dagger.hilt.EntryPoint
|
import dagger.hilt.EntryPoint
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
@ -121,8 +119,6 @@ interface ApplicationEntryPoint {
|
||||||
|
|
||||||
fun getOnboardingRepository(): OnboardingRepository
|
fun getOnboardingRepository(): OnboardingRepository
|
||||||
|
|
||||||
fun getCoroutineDispatcherProvider(): CoroutineDispatcherProvider
|
|
||||||
|
|
||||||
fun getExcludedBlockchains(): ExcludedBlockchains
|
fun getExcludedBlockchains(): ExcludedBlockchains
|
||||||
|
|
||||||
fun getAppLogsStore(): AppLogsStore
|
fun getAppLogsStore(): AppLogsStore
|
||||||
|
|
@ -155,6 +151,4 @@ interface ApplicationEntryPoint {
|
||||||
fun getABTestsManager(): ABTestsManager
|
fun getABTestsManager(): ABTestsManager
|
||||||
|
|
||||||
fun getAppsFlyerClientFactory(): AppsFlyerClient.Factory
|
fun getAppsFlyerClientFactory(): AppsFlyerClient.Factory
|
||||||
|
|
||||||
fun getWalletAccountsFetcher(): WalletAccountsFetcher
|
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.navigation.settings.SettingsManager
|
import com.tangem.core.navigation.settings.SettingsManager
|
||||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||||
import com.tangem.data.card.TransactionSignerFactory
|
import com.tangem.data.card.TransactionSignerFactory
|
||||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||||
|
|
@ -74,10 +73,8 @@ import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.common.redux.appReducer
|
import com.tangem.tap.common.redux.appReducer
|
||||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||||
import com.tangem.tap.domain.tasks.product.DerivationsFinder
|
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import com.tangem.wallet.BuildConfig
|
import com.tangem.wallet.BuildConfig
|
||||||
import dagger.hilt.EntryPoints
|
import dagger.hilt.EntryPoints
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -90,7 +87,6 @@ import timber.log.Timber
|
||||||
lateinit var store: Store<AppState>
|
lateinit var store: Store<AppState>
|
||||||
|
|
||||||
val foregroundActivityObserver = ForegroundActivityObserver
|
val foregroundActivityObserver = ForegroundActivityObserver
|
||||||
internal lateinit var derivationsFinder: DerivationsFinder
|
|
||||||
|
|
||||||
open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
|
open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
|
||||||
|
|
||||||
|
|
@ -191,9 +187,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
private val onboardingRepository: OnboardingRepository
|
private val onboardingRepository: OnboardingRepository
|
||||||
get() = entryPoint.getOnboardingRepository()
|
get() = entryPoint.getOnboardingRepository()
|
||||||
|
|
||||||
private val dispatchers: CoroutineDispatcherProvider
|
|
||||||
get() = entryPoint.getCoroutineDispatcherProvider()
|
|
||||||
|
|
||||||
private val excludedBlockchains: ExcludedBlockchains
|
private val excludedBlockchains: ExcludedBlockchains
|
||||||
get() = entryPoint.getExcludedBlockchains()
|
get() = entryPoint.getExcludedBlockchains()
|
||||||
|
|
||||||
|
|
@ -247,9 +240,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
private val appsFlyerClientFactory: AppsFlyerClient.Factory
|
private val appsFlyerClientFactory: AppsFlyerClient.Factory
|
||||||
get() = entryPoint.getAppsFlyerClientFactory()
|
get() = entryPoint.getAppsFlyerClientFactory()
|
||||||
|
|
||||||
private val walletAccountsFetcher: WalletAccountsFetcher
|
|
||||||
get() = entryPoint.getWalletAccountsFetcher()
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private val appScope = MainScope()
|
private val appScope = MainScope()
|
||||||
|
|
@ -352,11 +342,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
derivationsFinder = DerivationsFinder(
|
|
||||||
walletAccountsFetcher = walletAccountsFetcher,
|
|
||||||
dispatchers = dispatchers,
|
|
||||||
)
|
|
||||||
|
|
||||||
appStateHolder.mainStore = store
|
appStateHolder.mainStore = store
|
||||||
|
|
||||||
wcInitializeUseCase.init(
|
wcInitializeUseCase.init(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||||
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
||||||
|
import com.tangem.tap.domain.tasks.product.BlockchainToDeriveFinder
|
||||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||||
|
|
@ -40,6 +41,7 @@ internal class TangemSdkManagerModule {
|
||||||
appFinisher: AppFinisher,
|
appFinisher: AppFinisher,
|
||||||
sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||||
analyticsExceptionHandler: AnalyticsExceptionHandler,
|
analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||||
|
blockchainToDeriveFinder: BlockchainToDeriveFinder,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): TangemSdkManager {
|
): TangemSdkManager {
|
||||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||||
|
|
@ -56,6 +58,7 @@ internal class TangemSdkManagerModule {
|
||||||
appFinisher = appFinisher,
|
appFinisher = appFinisher,
|
||||||
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
||||||
analyticsExceptionHandler = analyticsExceptionHandler,
|
analyticsExceptionHandler = analyticsExceptionHandler,
|
||||||
|
blockchainToDeriveFinder = blockchainToDeriveFinder,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,7 @@ import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||||
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
|
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
|
||||||
import com.tangem.tap.common.analytics.events.TangemSdkErrorEvent
|
import com.tangem.tap.common.analytics.events.TangemSdkErrorEvent
|
||||||
import com.tangem.tap.derivationsFinder
|
import com.tangem.tap.domain.tasks.product.*
|
||||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
|
||||||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
|
||||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
|
||||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
|
||||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||||
import com.tangem.tap.domain.tasks.visa.TangemPaySignWithdrawalHashTask
|
import com.tangem.tap.domain.tasks.visa.TangemPaySignWithdrawalHashTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
|
|
@ -85,6 +81,7 @@ internal class DefaultTangemSdkManager(
|
||||||
private val appFinisher: AppFinisher,
|
private val appFinisher: AppFinisher,
|
||||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||||
|
private val blockchainToDeriveFinder: BlockchainToDeriveFinder,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
) : TangemSdkManager {
|
) : TangemSdkManager {
|
||||||
|
|
||||||
|
|
@ -162,7 +159,7 @@ internal class DefaultTangemSdkManager(
|
||||||
runTaskAsyncReturnOnMain(
|
runTaskAsyncReturnOnMain(
|
||||||
runnable = ScanProductTask(
|
runnable = ScanProductTask(
|
||||||
card = null,
|
card = null,
|
||||||
derivationsFinder = derivationsFinder,
|
blockchainToDeriveFinder = blockchainToDeriveFinder,
|
||||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||||
visaCardScanHandler = visaCardScanHandler,
|
visaCardScanHandler = visaCardScanHandler,
|
||||||
visaCoroutineScope = this,
|
visaCoroutineScope = this,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.tangem.tap.domain.tasks.product
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||||
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
|
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||||
|
import com.tangem.data.wallets.derivations.BlockchainToDerive
|
||||||
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
|
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||||
|
import com.tangem.tap.features.demo.DemoHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finder of blockchains to derive.
|
||||||
|
* Returns only saved, default or demo blockchains without any additional logic
|
||||||
|
* (no cardano/ethereum additions or unnecessary blockchain removals).
|
||||||
|
*/
|
||||||
|
class BlockchainToDeriveFinder @Inject constructor(
|
||||||
|
private val walletAccountsFetcher: WalletAccountsFetcher,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun find(card: CardDTO): Set<BlockchainToDerive> {
|
||||||
|
if (!card.settings.isHDWalletAllowed || card.wallets.isEmpty()) return emptySet()
|
||||||
|
val userWalletId = UserWalletIdBuilder.card(card).build() ?: return emptySet()
|
||||||
|
|
||||||
|
val derivationStyle = card.derivationStyleProvider.getDerivationStyle()
|
||||||
|
|
||||||
|
val blockchains = getBlockchains(userWalletId).ifEmpty {
|
||||||
|
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||||
|
getDemoBlockchains(derivationStyle, card.cardId)
|
||||||
|
} else {
|
||||||
|
getDefaultBlockchains(derivationStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockchains
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getBlockchains(userWalletId: UserWalletId): Set<BlockchainToDerive> {
|
||||||
|
return walletAccountsFetcher.getSaved(userWalletId)?.accounts.orEmpty()
|
||||||
|
.flatMap { accountDTO ->
|
||||||
|
accountDTO.tokens.orEmpty()
|
||||||
|
.filter { it.contractAddress == null }
|
||||||
|
}
|
||||||
|
.mapNotNull { coin ->
|
||||||
|
val blockchain = Blockchain.fromNetworkId(coin.networkId) ?: return@mapNotNull null
|
||||||
|
val derivationPath = coin.derivationPath?.let(::DerivationPath) ?: return@mapNotNull null
|
||||||
|
|
||||||
|
BlockchainToDerive(blockchain, derivationPath)
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDemoBlockchains(derivationStyle: DerivationStyle?, cardId: String): Set<BlockchainToDerive> {
|
||||||
|
return DemoHelper.config.getDemoBlockchains(cardId).mapToBlockchainsWithDerivations(derivationStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDefaultBlockchains(derivationStyle: DerivationStyle?): Set<BlockchainToDerive> {
|
||||||
|
val defaultBlockchains = setOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||||
|
return defaultBlockchains.mapToBlockchainsWithDerivations(derivationStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Set<Blockchain>.mapToBlockchainsWithDerivations(
|
||||||
|
derivationStyle: DerivationStyle?,
|
||||||
|
): Set<BlockchainToDerive> {
|
||||||
|
return mapNotNullTo(hashSetOf()) { blockchain ->
|
||||||
|
val derivationPath = blockchain.derivationPath(derivationStyle) ?: return@mapNotNullTo null
|
||||||
|
BlockchainToDerive(blockchain, derivationPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
package com.tangem.tap.domain.tasks.product
|
|
||||||
|
|
||||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
|
||||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.hasOldStyleDerivation
|
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
|
||||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
|
||||||
import com.tangem.tap.features.demo.DemoHelper
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
internal data class BlockchainToDerive(
|
|
||||||
val blockchain: Blockchain,
|
|
||||||
val derivationPath: DerivationPath?,
|
|
||||||
)
|
|
||||||
|
|
||||||
// FIXME: May be move to DI, currently unnecessary
|
|
||||||
internal class DerivationsFinder(
|
|
||||||
private val walletAccountsFetcher: WalletAccountsFetcher,
|
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun findBlockchainsToDerive(
|
|
||||||
card: CardDTO,
|
|
||||||
derivationStyleProvider: DerivationStyleProvider,
|
|
||||||
): Set<BlockchainToDerive> {
|
|
||||||
if (!card.settings.isHDWalletAllowed || card.wallets.isEmpty()) return emptySet()
|
|
||||||
val userWalletId = UserWalletIdBuilder.card(card).build() ?: return emptySet()
|
|
||||||
val derivationStyle = derivationStyleProvider.getDerivationStyle()
|
|
||||||
|
|
||||||
val blockchains = withContext(dispatchers.io) {
|
|
||||||
getBlockchains(userWalletId)
|
|
||||||
}.ifEmpty {
|
|
||||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
|
||||||
getDemoBlockchains(derivationStyle, card.cardId)
|
|
||||||
} else {
|
|
||||||
getDefaultBlockchains(derivationStyle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we should generate second key for cardano
|
|
||||||
// because cardano address generation for wallet2 requires keys from 2 derivations
|
|
||||||
// https://developers.cardano.org/docs/get-started/cardano-serialization-lib/generating-keys/
|
|
||||||
blockchains.addSecondCardanoDerivationIfPresent()
|
|
||||||
|
|
||||||
if (card.settings.isHDWalletAllowed) {
|
|
||||||
blockchains.addEthereumBlockchains(derivationStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pay attention to this
|
|
||||||
return if (!card.hasOldStyleDerivation) {
|
|
||||||
blockchains.removeUnnecessaryBlockchains(derivationStyle)
|
|
||||||
} else {
|
|
||||||
blockchains
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getBlockchains(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
|
|
||||||
return walletAccountsFetcher.getSaved(userWalletId)?.accounts.orEmpty()
|
|
||||||
.flatMap { accountDTO ->
|
|
||||||
accountDTO.tokens.orEmpty()
|
|
||||||
.filter { it.contractAddress == null }
|
|
||||||
}
|
|
||||||
.mapNotNull { coin ->
|
|
||||||
val blockchain = Blockchain.fromNetworkId(coin.networkId) ?: return@mapNotNull null
|
|
||||||
val derivationPath = coin.derivationPath?.let(::DerivationPath)
|
|
||||||
|
|
||||||
BlockchainToDerive(blockchain, derivationPath)
|
|
||||||
}
|
|
||||||
.toMutableSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Move to user wallet config
|
|
||||||
private fun getDemoBlockchains(derivationStyle: DerivationStyle?, cardId: String): MutableSet<BlockchainToDerive> {
|
|
||||||
return DemoHelper.config.getDemoBlockchains(cardId).mapToBlockchainsWithDerivations(derivationStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Move to user wallet config
|
|
||||||
private fun getDefaultBlockchains(derivationStyle: DerivationStyle?): MutableSet<BlockchainToDerive> {
|
|
||||||
val defaultBlockchains = setOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
|
||||||
|
|
||||||
return defaultBlockchains.mapToBlockchainsWithDerivations(derivationStyle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableSet<BlockchainToDerive>.addEthereumBlockchains(derivationStyle: DerivationStyle?) {
|
|
||||||
val ethereumBlockchains = setOf(Blockchain.Ethereum)
|
|
||||||
.mapToBlockchainsWithDerivations(derivationStyle)
|
|
||||||
|
|
||||||
addAll(ethereumBlockchains)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Set<BlockchainToDerive>.removeUnnecessaryBlockchains(
|
|
||||||
derivationStyle: DerivationStyle?,
|
|
||||||
): Set<BlockchainToDerive> {
|
|
||||||
val defaultEthereum = BlockchainToDerive(
|
|
||||||
blockchain = Blockchain.Ethereum,
|
|
||||||
derivationPath = Blockchain.Ethereum.derivationPath(derivationStyle),
|
|
||||||
)
|
|
||||||
|
|
||||||
val addedEthereum = this.firstOrNull { it == defaultEthereum }
|
|
||||||
|
|
||||||
return if (addedEthereum != null) {
|
|
||||||
filterNot { it.derivationPath == defaultEthereum.derivationPath && it.blockchain != Blockchain.Ethereum }
|
|
||||||
} else {
|
|
||||||
// Impossible case because Ethereum was added at the last stage
|
|
||||||
distinctBy(BlockchainToDerive::derivationPath)
|
|
||||||
}
|
|
||||||
.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableSet<BlockchainToDerive>.addSecondCardanoDerivationIfPresent() {
|
|
||||||
val cardanoDerivation = this
|
|
||||||
.firstOrNull { it.blockchain == Blockchain.Cardano }
|
|
||||||
?.derivationPath
|
|
||||||
?: return
|
|
||||||
|
|
||||||
val secondCardanoBlockchain = BlockchainToDerive(
|
|
||||||
blockchain = Blockchain.Cardano,
|
|
||||||
derivationPath = CardanoUtils.extendedDerivationPath(cardanoDerivation),
|
|
||||||
)
|
|
||||||
|
|
||||||
add(secondCardanoBlockchain)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Set<Blockchain>.mapToBlockchainsWithDerivations(
|
|
||||||
derivationStyle: DerivationStyle?,
|
|
||||||
): MutableSet<BlockchainToDerive> {
|
|
||||||
return mapTo(hashSetOf()) { blockchain ->
|
|
||||||
BlockchainToDerive(blockchain, blockchain.derivationPath(derivationStyle))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,16 +13,14 @@ import com.tangem.common.tlv.Tlv
|
||||||
import com.tangem.common.tlv.TlvDecoder
|
import com.tangem.common.tlv.TlvDecoder
|
||||||
import com.tangem.crypto.CryptoUtils
|
import com.tangem.crypto.CryptoUtils
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
import com.tangem.data.wallets.derivations.MissedDerivationsFinder
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.isExcluded
|
import com.tangem.domain.card.common.TapWorkarounds.isExcluded
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.isNotSupportedInThatRelease
|
import com.tangem.domain.card.common.TapWorkarounds.isNotSupportedInThatRelease
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin
|
import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
|
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.isVisa
|
import com.tangem.domain.card.common.TapWorkarounds.isVisa
|
||||||
import com.tangem.domain.card.common.TwinsHelper
|
import com.tangem.domain.card.common.TwinsHelper
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
|
||||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||||
import com.tangem.domain.card.configs.CardConfig
|
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
||||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
||||||
|
|
@ -44,11 +42,10 @@ import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
internal class ScanProductTask(
|
internal class ScanProductTask(
|
||||||
private val card: Card?,
|
private val card: Card?,
|
||||||
private val derivationsFinder: DerivationsFinder?,
|
private val blockchainToDeriveFinder: BlockchainToDeriveFinder?,
|
||||||
private val visaCardScanHandler: VisaCardScanHandler?,
|
private val visaCardScanHandler: VisaCardScanHandler?,
|
||||||
private val visaCoroutineScope: CoroutineScope?,
|
private val visaCoroutineScope: CoroutineScope?,
|
||||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles?,
|
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles?,
|
||||||
|
|
@ -79,7 +76,7 @@ internal class ScanProductTask(
|
||||||
readVisaCard(
|
readVisaCard(
|
||||||
session = session,
|
session = session,
|
||||||
cardDto = cardDto,
|
cardDto = cardDto,
|
||||||
scanWalletProcessor = ScanWalletProcessor(derivationsFinder),
|
scanWalletProcessor = ScanWalletProcessor(blockchainToDeriveFinder),
|
||||||
callback = callback,
|
callback = callback,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
@ -87,7 +84,7 @@ internal class ScanProductTask(
|
||||||
|
|
||||||
val commandProcessor = when {
|
val commandProcessor = when {
|
||||||
cardDto.isTangemTwins -> ScanTwinProcessor()
|
cardDto.isTangemTwins -> ScanTwinProcessor()
|
||||||
else -> ScanWalletProcessor(derivationsFinder)
|
else -> ScanWalletProcessor(blockchainToDeriveFinder)
|
||||||
}
|
}
|
||||||
commandProcessor.proceed(cardDto, session) { processorResult ->
|
commandProcessor.proceed(cardDto, session) { processorResult ->
|
||||||
when (processorResult) {
|
when (processorResult) {
|
||||||
|
|
@ -160,7 +157,7 @@ internal class ScanProductTask(
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScanWalletProcessor(
|
private class ScanWalletProcessor(
|
||||||
private val derivationsFinder: DerivationsFinder?,
|
private val blockchainToDeriveFinder: BlockchainToDeriveFinder?,
|
||||||
) : ProductCommandProcessor<ScanResponse> {
|
) : ProductCommandProcessor<ScanResponse> {
|
||||||
|
|
||||||
var primaryCard: PrimaryCard? = null
|
var primaryCard: PrimaryCard? = null
|
||||||
|
|
@ -283,7 +280,6 @@ private class ScanWalletProcessor(
|
||||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val productType = getWalletProductType(card)
|
val productType = getWalletProductType(card)
|
||||||
val config = CardConfig.createConfig(card)
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val scanResponse = ScanResponse(
|
val scanResponse = ScanResponse(
|
||||||
card = card,
|
card = card,
|
||||||
|
|
@ -291,8 +287,7 @@ private class ScanWalletProcessor(
|
||||||
walletData = session.environment.walletData,
|
walletData = session.environment.walletData,
|
||||||
primaryCard = primaryCard,
|
primaryCard = primaryCard,
|
||||||
)
|
)
|
||||||
val derivations =
|
val derivations = collectDerivations(card, scanResponse)
|
||||||
collectDerivations(card, config, scanResponse.derivationStyleProvider)
|
|
||||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||||
callback(CompletionResult.Success(scanResponse))
|
callback(CompletionResult.Success(scanResponse))
|
||||||
return@launch
|
return@launch
|
||||||
|
|
@ -322,32 +317,13 @@ private class ScanWalletProcessor(
|
||||||
|
|
||||||
private suspend fun collectDerivations(
|
private suspend fun collectDerivations(
|
||||||
card: CardDTO,
|
card: CardDTO,
|
||||||
config: CardConfig,
|
scanResponse: ScanResponse,
|
||||||
derivationStyleProvider: DerivationStyleProvider,
|
|
||||||
): Map<ByteArrayKey, List<DerivationPath>> {
|
): Map<ByteArrayKey, List<DerivationPath>> {
|
||||||
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
val blockchains = blockchainToDeriveFinder
|
||||||
val blockchains = derivationsFinder
|
?.find(card)
|
||||||
?.findBlockchainsToDerive(card, derivationStyleProvider)
|
?: return emptyMap()
|
||||||
?: return derivations
|
|
||||||
|
|
||||||
blockchains.forEach { blockchain ->
|
return MissedDerivationsFinder(scanResponse).findByBlockchainsToDerive(blockchains)
|
||||||
val curve = config.primaryCurve(blockchain.blockchain)
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach
|
|
||||||
if (wallet.chainCode == null) return@forEach
|
|
||||||
|
|
||||||
val key = wallet.publicKey.toMapKey()
|
|
||||||
val path = blockchain.derivationPath
|
|
||||||
if (path != null) {
|
|
||||||
val addedDerivations = derivations[key]
|
|
||||||
if (addedDerivations != null) {
|
|
||||||
derivations[key] = addedDerivations + path
|
|
||||||
} else {
|
|
||||||
derivations[key] = listOf(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return derivations
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class FinalizeTwinTask(
|
||||||
is CompletionResult.Success ->
|
is CompletionResult.Success ->
|
||||||
ScanProductTask(
|
ScanProductTask(
|
||||||
card = readResult.data,
|
card = readResult.data,
|
||||||
derivationsFinder = null,
|
blockchainToDeriveFinder = null,
|
||||||
visaCardScanHandler = null,
|
visaCardScanHandler = null,
|
||||||
visaCoroutineScope = null,
|
visaCoroutineScope = null,
|
||||||
onboardingV2FeatureToggles = null,
|
onboardingV2FeatureToggles = null,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
package com.tangem.tap.domain.tasks.product
|
package com.tangem.tap.domain.tasks.product
|
||||||
|
|
||||||
import com.google.common.truth.Truth
|
import com.google.common.truth.Truth
|
||||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||||
|
import com.tangem.data.wallets.derivations.BlockchainToDerive
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
|
||||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
|
@ -24,19 +22,16 @@ import org.junit.jupiter.api.TestInstance
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class DerivationsFinderTest {
|
class BlockchainToDeriveFinderTest {
|
||||||
|
|
||||||
private val walletAccountsFetcher = mockk<WalletAccountsFetcher>()
|
private val walletAccountsFetcher = mockk<WalletAccountsFetcher>()
|
||||||
private val finder = DerivationsFinder(
|
private val finder = BlockchainToDeriveFinder(
|
||||||
walletAccountsFetcher = walletAccountsFetcher,
|
walletAccountsFetcher = walletAccountsFetcher,
|
||||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val derivationStyleProvider = mockk<DerivationStyleProvider>()
|
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
clearMocks(walletAccountsFetcher, derivationStyleProvider)
|
clearMocks(walletAccountsFetcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -47,7 +42,7 @@ class DerivationsFinderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = mockk())
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Truth.assertThat(actual).isEmpty()
|
Truth.assertThat(actual).isEmpty()
|
||||||
|
|
@ -62,29 +57,26 @@ class DerivationsFinderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = mockk())
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Truth.assertThat(actual).isEmpty()
|
Truth.assertThat(actual).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN saved bitcoin THEN return bitcoin and ethereum`() = runTest {
|
fun `GIVEN saved bitcoin THEN return only bitcoin`() = runTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
val card = createCardDTO()
|
val card = createCardDTO()
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V3
|
|
||||||
|
|
||||||
val response = createResponse(Blockchain.Bitcoin)
|
val response = createResponse(Blockchain.Bitcoin)
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val expected = setOf(
|
val expected = setOf(
|
||||||
createExpected(Blockchain.Bitcoin),
|
createExpected(Blockchain.Bitcoin),
|
||||||
createExpected(Blockchain.Ethereum),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||||
|
|
@ -98,12 +90,10 @@ class DerivationsFinderTest {
|
||||||
val demoCardId = "AC01000000045754"
|
val demoCardId = "AC01000000045754"
|
||||||
val card = createCardDTO(cardId = demoCardId)
|
val card = createCardDTO(cardId = demoCardId)
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V3
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val expected = setOf(
|
val expected = setOf(
|
||||||
|
|
@ -124,12 +114,10 @@ class DerivationsFinderTest {
|
||||||
val demoCardId = "DE00"
|
val demoCardId = "DE00"
|
||||||
val card = createCardDTO(cardId = demoCardId)
|
val card = createCardDTO(cardId = demoCardId)
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V3
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val expected = setOf(
|
val expected = setOf(
|
||||||
|
|
@ -148,12 +136,10 @@ class DerivationsFinderTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
val card = createCardDTO()
|
val card = createCardDTO()
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V3
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val expected = setOf(
|
val expected = setOf(
|
||||||
|
|
@ -167,26 +153,19 @@ class DerivationsFinderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN saved cardano THEN return cardano and ethereum`() = runTest {
|
fun `GIVEN saved cardano THEN return only cardano`() = runTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
val card = createCardDTO()
|
val card = createCardDTO()
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V3
|
|
||||||
|
|
||||||
val response = createResponse(Blockchain.Cardano)
|
val response = createResponse(Blockchain.Cardano)
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val expected = setOf(
|
val expected = setOf(
|
||||||
createExpected(Blockchain.Cardano),
|
createExpected(Blockchain.Cardano),
|
||||||
createExpected(
|
|
||||||
blockchain = Blockchain.Cardano,
|
|
||||||
derivationPath = CardanoUtils.extendedDerivationPath(Blockchain.Cardano.getDerivationPath())
|
|
||||||
),
|
|
||||||
createExpected(Blockchain.Ethereum),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
||||||
|
|
@ -195,53 +174,18 @@ class DerivationsFinderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN saved eth-like blockchains for v3 config wallet THEN return only unique evm derivations`() = runTest {
|
fun `GIVEN saved eth-like blockchains THEN return all saved blockchains without filtering`() = runTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
val card = createCardDTO()
|
val card = createCardDTO()
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V3
|
val blockchains = listOf(Blockchain.Ethereum, Blockchain.BSC, Blockchain.Polygon)
|
||||||
|
|
||||||
val blockchains = Blockchain.entries
|
val response = createResponse(*blockchains.toTypedArray())
|
||||||
.filter { it.isEvm() && !it.isTestnet() }
|
|
||||||
.toTypedArray()
|
|
||||||
|
|
||||||
val response = createResponse(*blockchains)
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
val actual = finder.find(card)
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = setOf(
|
|
||||||
createExpected(Blockchain.Ethereum),
|
|
||||||
createExpected(Blockchain.EthereumClassic),
|
|
||||||
createExpected(Blockchain.Quai),
|
|
||||||
createExpected(Blockchain.XDC),
|
|
||||||
)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN card has old style derivation (v1 config) THEN return all eth-like blockchains`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val oldBatchId = "AC01"
|
|
||||||
val card = createCardDTO(batchId = oldBatchId)
|
|
||||||
|
|
||||||
every { derivationStyleProvider.getDerivationStyle() } returns DerivationStyle.V1
|
|
||||||
|
|
||||||
val blockchains = Blockchain.entries
|
|
||||||
.filter { it.isEvm() && !it.isTestnet() }
|
|
||||||
.toTypedArray()
|
|
||||||
|
|
||||||
val response = createResponse(*blockchains)
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.findBlockchainsToDerive(card = card, derivationStyleProvider = derivationStyleProvider)
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val expected = blockchains.mapTo(hashSetOf(), ::createExpected)
|
val expected = blockchains.mapTo(hashSetOf(), ::createExpected)
|
||||||
|
|
@ -260,6 +204,13 @@ class DerivationsFinderTest {
|
||||||
every { this@mockk.cardId } returns cardId
|
every { this@mockk.cardId } returns cardId
|
||||||
every { this@mockk.batchId } returns batchId
|
every { this@mockk.batchId } returns batchId
|
||||||
every { this@mockk.settings.isHDWalletAllowed } returns true
|
every { this@mockk.settings.isHDWalletAllowed } returns true
|
||||||
|
every { this@mockk.settings.isKeysImportAllowed } returns true
|
||||||
|
every { this@mockk.firmwareVersion } returns CardDTO.FirmwareVersion(
|
||||||
|
major = 6,
|
||||||
|
minor = 33,
|
||||||
|
patch = 0,
|
||||||
|
type = com.tangem.common.card.FirmwareVersion.FirmwareType.Release,
|
||||||
|
)
|
||||||
every { this@mockk.wallets } returns listOf(wallet)
|
every { this@mockk.wallets } returns listOf(wallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +241,7 @@ class DerivationsFinderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Blockchain.getDerivationPath(): DerivationPath {
|
private fun Blockchain.getDerivationPath(): DerivationPath {
|
||||||
return derivationPath(derivationStyleProvider.getDerivationStyle())!!
|
return derivationPath(DerivationStyle.V3)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
@ -298,4 +249,4 @@ class DerivationsFinderTest {
|
||||||
// for byteArrayOf(0)
|
// for byteArrayOf(0)
|
||||||
val userWalletId = UserWalletId("41448576B8DA24C7D8F5F0F79863D20D7D8312A7F9E50D3248304136DDB7AAD7")
|
val userWalletId = UserWalletId("41448576B8DA24C7D8F5F0F79863D20D7D8312A7F9E50D3248304136DDB7AAD7")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +73,7 @@ object MockScanResponseFactory {
|
||||||
CardDTO.Wallet(
|
CardDTO.Wallet(
|
||||||
CardWallet(
|
CardWallet(
|
||||||
publicKey = curve.name.toByteArray(), // IMPORTANT: public key must equal to curve name
|
publicKey = curve.name.toByteArray(), // IMPORTANT: public key must equal to curve name
|
||||||
chainCode = null,
|
chainCode = ByteArray(32), // chainCode must not be null for HD wallets
|
||||||
curve = curve,
|
curve = curve,
|
||||||
settings = createSettings(),
|
settings = createSettings(),
|
||||||
totalSignedHashes = null,
|
totalSignedHashes = null,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.tangem.data.wallets.derivations
|
||||||
|
|
||||||
|
import com.tangem.common.card.EllipticCurve
|
||||||
|
import com.tangem.domain.card.common.TapWorkarounds.hasOldStyleDerivation
|
||||||
|
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.wallets.config.ColdCurvesConfig
|
||||||
|
import com.tangem.domain.wallets.config.CurvesConfig
|
||||||
|
import com.tangem.domain.wallets.config.curvesConfig
|
||||||
|
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
||||||
|
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||||
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of derivations data
|
||||||
|
*/
|
||||||
|
internal sealed interface DerivationsSource {
|
||||||
|
|
||||||
|
val isHDWalletAllowed: Boolean
|
||||||
|
val hasOldStyleDerivation: Boolean
|
||||||
|
val curvesConfig: CurvesConfig
|
||||||
|
val derivationStyleProvider: DerivationStyleProvider
|
||||||
|
|
||||||
|
fun getWalletPublicKey(curve: EllipticCurve): ByteArray?
|
||||||
|
fun getDerivedKeys(publicKey: KeyWalletPublicKey): ExtendedPublicKeysMap
|
||||||
|
|
||||||
|
data class FromUserWallet(val userWallet: UserWallet) : DerivationsSource {
|
||||||
|
override val isHDWalletAllowed: Boolean
|
||||||
|
get() = when (userWallet) {
|
||||||
|
is UserWallet.Cold -> userWallet.scanResponse.card.settings.isHDWalletAllowed
|
||||||
|
is UserWallet.Hot -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
override val hasOldStyleDerivation: Boolean
|
||||||
|
get() = when (userWallet) {
|
||||||
|
is UserWallet.Cold -> userWallet.scanResponse.card.hasOldStyleDerivation
|
||||||
|
is UserWallet.Hot -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
override val curvesConfig: CurvesConfig
|
||||||
|
get() = userWallet.curvesConfig
|
||||||
|
|
||||||
|
override val derivationStyleProvider: DerivationStyleProvider
|
||||||
|
get() = userWallet.derivationStyleProvider
|
||||||
|
|
||||||
|
override fun getWalletPublicKey(curve: EllipticCurve): ByteArray? {
|
||||||
|
return when (userWallet) {
|
||||||
|
is UserWallet.Cold -> userWallet.scanResponse.getWalletPublicKey(curve)
|
||||||
|
is UserWallet.Hot -> userWallet.wallets
|
||||||
|
?.firstOrNull { it.curve == curve && it.chainCode != null }
|
||||||
|
?.publicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDerivedKeys(publicKey: KeyWalletPublicKey): ExtendedPublicKeysMap {
|
||||||
|
return when (userWallet) {
|
||||||
|
is UserWallet.Cold -> userWallet.scanResponse.getDerivedKeys(publicKey)
|
||||||
|
is UserWallet.Hot -> {
|
||||||
|
val derivedKeys = userWallet.wallets
|
||||||
|
?.firstOrNull { it.publicKey.contentEquals(publicKey.bytes) }
|
||||||
|
?.derivedKeys
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
|
ExtendedPublicKeysMap(derivedKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FromScanResponse(val scanResponse: ScanResponse) : DerivationsSource {
|
||||||
|
override val isHDWalletAllowed: Boolean
|
||||||
|
get() = scanResponse.card.settings.isHDWalletAllowed
|
||||||
|
|
||||||
|
override val hasOldStyleDerivation: Boolean
|
||||||
|
get() = scanResponse.card.hasOldStyleDerivation
|
||||||
|
|
||||||
|
override val curvesConfig: CurvesConfig
|
||||||
|
get() = ColdCurvesConfig(scanResponse.card)
|
||||||
|
|
||||||
|
override val derivationStyleProvider: DerivationStyleProvider
|
||||||
|
get() = scanResponse.derivationStyleProvider
|
||||||
|
|
||||||
|
override fun getWalletPublicKey(curve: EllipticCurve): ByteArray? {
|
||||||
|
return scanResponse.getWalletPublicKey(curve)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDerivedKeys(publicKey: KeyWalletPublicKey): ExtendedPublicKeysMap {
|
||||||
|
return scanResponse.getDerivedKeys(publicKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ScanResponse.getWalletPublicKey(curve: EllipticCurve): ByteArray? {
|
||||||
|
return card.wallets.firstOrNull { it.curve == curve && it.chainCode != null }
|
||||||
|
?.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ScanResponse.getDerivedKeys(publicKey: KeyWalletPublicKey): ExtendedPublicKeysMap {
|
||||||
|
return derivedKeys[publicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||||
|
}
|
||||||
|
|
@ -3,45 +3,80 @@ package com.tangem.data.wallets.derivations
|
||||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||||
import com.tangem.common.card.EllipticCurve
|
|
||||||
import com.tangem.common.extensions.ByteArrayKey
|
import com.tangem.common.extensions.ByteArrayKey
|
||||||
import com.tangem.common.extensions.toMapKey
|
import com.tangem.common.extensions.toMapKey
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.wallets.config.curvesConfig
|
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
|
|
||||||
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
||||||
internal typealias Derivations = Map<ByteArrayKey, List<DerivationPath>>
|
internal typealias Derivations = Map<ByteArrayKey, List<DerivationPath>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class representing a blockchain with its derivation path
|
||||||
|
*/
|
||||||
|
data class BlockchainToDerive(
|
||||||
|
val blockchain: Blockchain,
|
||||||
|
val derivationPath: DerivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finder of missed derivations
|
* Finder of missed derivations
|
||||||
*
|
*
|
||||||
* @property userWallet User wallet to find derivations for
|
* @property source Source of derivations data (UserWallet or ScanResponse)
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
internal class MissedDerivationsFinder(private val userWallet: UserWallet) {
|
class MissedDerivationsFinder private constructor(private val source: DerivationsSource) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary constructor for backward compatibility with UserWallet
|
||||||
|
*/
|
||||||
|
constructor(userWallet: UserWallet) : this(DerivationsSource.FromUserWallet(userWallet))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary constructor for ScanResponse
|
||||||
|
*/
|
||||||
|
constructor(scanResponse: ScanResponse) : this(DerivationsSource.FromScanResponse(scanResponse))
|
||||||
|
|
||||||
/** Find missed derivations for given currencies [currencies] */
|
/** Find missed derivations for given currencies [currencies] */
|
||||||
fun find(currencies: List<CryptoCurrency>): Derivations {
|
fun find(currencies: List<CryptoCurrency>): Derivations {
|
||||||
return currencies.map { it.network }.let(::findByNetworks)
|
return currencies.map { it.network }.let(::findByNetworks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Find missed derivations for given [Network] list */
|
||||||
fun findByNetworks(networks: List<Network>): Derivations {
|
fun findByNetworks(networks: List<Network>): Derivations {
|
||||||
|
val blockchainsToDerive = networks.mapNotNull { network ->
|
||||||
|
val blockchain = network.toBlockchain()
|
||||||
|
val derivationPath = network.derivationPath.value?.let(::DerivationPath)
|
||||||
|
?: return@mapNotNull null
|
||||||
|
|
||||||
|
BlockchainToDerive(blockchain, derivationPath)
|
||||||
|
}
|
||||||
|
return findByBlockchainsToDerive(blockchainsToDerive)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find missed derivations for given [BlockchainToDerive] list */
|
||||||
|
fun findByBlockchainsToDerive(blockchainsToDerive: Collection<BlockchainToDerive>): Derivations {
|
||||||
|
val enrichedBlockchains = blockchainsToDerive.enrichBlockchains()
|
||||||
|
return findDerivationsInternal(enrichedBlockchains)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common implementation for finding derivations
|
||||||
|
*/
|
||||||
|
private fun findDerivationsInternal(items: Collection<BlockchainToDerive>): Derivations {
|
||||||
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||||
networks
|
items
|
||||||
.mapToNewDerivations()
|
.mapNotNull(::mapToNewDerivation)
|
||||||
.forEach { data ->
|
.forEach { data ->
|
||||||
val current = this[data.first]
|
val current = this[data.first]
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
current.addAll(data.second)
|
current.addAll(data.second)
|
||||||
current.distinct()
|
this[data.first] = current.distinct().toMutableList()
|
||||||
} else {
|
} else {
|
||||||
this[data.first] = data.second.toMutableList()
|
this[data.first] = data.second.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
@ -49,31 +84,17 @@ internal class MissedDerivationsFinder(private val userWallet: UserWallet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Network>.mapToNewDerivations(): List<DerivationData> {
|
/**
|
||||||
return mapNotNull { network ->
|
* Maps a single BlockchainToDerive to derivation data (public key -> derivation paths)
|
||||||
val blockchain = network.toBlockchain()
|
*/
|
||||||
val curve = userWallet.curvesConfig.primaryCurve(blockchain) ?: return@mapNotNull null
|
private fun mapToNewDerivation(input: BlockchainToDerive): DerivationData? {
|
||||||
|
val curve = source.curvesConfig.primaryCurve(input.blockchain) ?: return null
|
||||||
|
if (!input.blockchain.getSupportedCurves().contains(curve)) return null
|
||||||
|
|
||||||
val walletPublicKey = when (userWallet) {
|
val publicKey = source.getWalletPublicKey(curve) ?: return null
|
||||||
is UserWallet.Cold -> {
|
|
||||||
val wallet = userWallet.scanResponse.card.wallets.firstOrNull { it.curve == curve }
|
|
||||||
wallet?.publicKey
|
|
||||||
}
|
|
||||||
is UserWallet.Hot -> {
|
|
||||||
val wallet = userWallet.wallets?.firstOrNull { it.curve == curve }
|
|
||||||
wallet?.publicKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walletPublicKey?.let {
|
val derivationCandidates = input.blockchain
|
||||||
findNewDerivations(curve = curve, publicKey = it, network = network)
|
.getDerivationCandidates(input.derivationPath)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findNewDerivations(curve: EllipticCurve, publicKey: ByteArray, network: Network): DerivationData? {
|
|
||||||
val derivationCandidates = network
|
|
||||||
.getDerivationCandidates(curve)
|
|
||||||
.ifEmpty { return null }
|
.ifEmpty { return null }
|
||||||
.filterAlreadyDerivedKeys(publicKey.toMapKey())
|
.filterAlreadyDerivedKeys(publicKey.toMapKey())
|
||||||
.ifEmpty { return null }
|
.ifEmpty { return null }
|
||||||
|
|
@ -81,59 +102,63 @@ internal class MissedDerivationsFinder(private val userWallet: UserWallet) {
|
||||||
return publicKey.toMapKey() to derivationCandidates
|
return publicKey.toMapKey() to derivationCandidates
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Network.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
/**
|
||||||
val blockchain = this.toBlockchain()
|
* Gets all possible derivation paths for a blockchain
|
||||||
|
*/
|
||||||
|
private fun Blockchain.getDerivationCandidates(derivationPath: DerivationPath): List<DerivationPath> {
|
||||||
return buildList {
|
return buildList {
|
||||||
add(blockchain.getDerivationPath(curve = curve))
|
// Default derivation path for blockchain
|
||||||
add(blockchain.getCustomDerivationPath(curve = curve, network = this@getDerivationCandidates))
|
add(getDerivationPath())
|
||||||
add(blockchain.getCardanoDerivationPathIfNeeded(network = this@getDerivationCandidates))
|
|
||||||
|
// The specified derivation path (can be either default or custom)
|
||||||
|
add(derivationPath)
|
||||||
|
|
||||||
|
// Extended Cardano derivation path if needed
|
||||||
|
add(getCardanoExtendedDerivationPath(derivationPath))
|
||||||
}
|
}
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.distinct()
|
.distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Blockchain.getDerivationPath(curve: EllipticCurve): DerivationPath? {
|
private fun Blockchain.getDerivationPath(): DerivationPath? {
|
||||||
return if (getSupportedCurves().contains(curve)) {
|
return derivationPath(style = source.derivationStyleProvider.getDerivationStyle())
|
||||||
derivationPath(style = userWallet.derivationStyleProvider.getDerivationStyle())
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, network: Network): DerivationPath? {
|
private fun Blockchain.getCardanoExtendedDerivationPath(customDerivationPath: DerivationPath): DerivationPath? {
|
||||||
return if (getSupportedCurves().contains(curve)) {
|
if (this != Blockchain.Cardano) return null
|
||||||
network.derivationPath.value?.let(::DerivationPath)
|
return CardanoUtils.extendedDerivationPath(derivationPath = customDerivationPath)
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Blockchain.getCardanoDerivationPathIfNeeded(network: Network): DerivationPath? {
|
|
||||||
return if (this == Blockchain.Cardano) {
|
|
||||||
network.derivationPath.value?.let {
|
|
||||||
CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<DerivationPath>.filterAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
|
private fun List<DerivationPath>.filterAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
|
||||||
val alreadyDerivedPaths = getAlreadyDerivedKeys(publicKey)
|
val alreadyDerivedPaths = source.getDerivedKeys(publicKey).keys.toList()
|
||||||
return filterNot(alreadyDerivedPaths::contains)
|
return filterNot(alreadyDerivedPaths::contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAlreadyDerivedKeys(publicKey: KeyWalletPublicKey): List<DerivationPath> {
|
// region Blockchain enrichment logic
|
||||||
val extendedPublicKeysMap = when (userWallet) {
|
|
||||||
is UserWallet.Cold -> userWallet.scanResponse.derivedKeys[publicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
/**
|
||||||
is UserWallet.Hot -> {
|
* Enriches blockchains collection:
|
||||||
val wallets = userWallet.wallets ?: return emptyList()
|
* - Adds Ethereum if HD wallet is allowed
|
||||||
wallets.firstOrNull { it.publicKey.contentEquals(publicKey.bytes) }?.derivedKeys
|
* - Removes unnecessary blockchains that share derivation path with Ethereum (for cards without old style derivation)
|
||||||
?: ExtendedPublicKeysMap(emptyMap())
|
*/
|
||||||
}
|
private fun Collection<BlockchainToDerive>.enrichBlockchains(): Collection<BlockchainToDerive> {
|
||||||
|
if (!source.isHDWalletAllowed) return this
|
||||||
|
|
||||||
|
val derivationStyle = source.derivationStyleProvider.getDerivationStyle()
|
||||||
|
val ethereumDerivationPath = Blockchain.Ethereum.derivationPath(derivationStyle) ?: return this
|
||||||
|
|
||||||
|
val withEthereum = this + BlockchainToDerive(Blockchain.Ethereum, ethereumDerivationPath)
|
||||||
|
|
||||||
|
// For cards with old style derivation, keep all blockchains
|
||||||
|
if (source.hasOldStyleDerivation) {
|
||||||
|
return withEthereum.distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
return extendedPublicKeysMap.keys.toList()
|
// For new cards: filter out blockchains with same derivation path as Ethereum (except Ethereum itself)
|
||||||
|
return withEthereum
|
||||||
|
.filter { it.derivationPath != ethereumDerivationPath || it.blockchain == Blockchain.Ethereum }
|
||||||
|
.distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -14,11 +14,13 @@ import com.tangem.domain.card.configs.GenericCardConfig
|
||||||
import com.tangem.domain.card.configs.MultiWalletCardConfig
|
import com.tangem.domain.card.configs.MultiWalletCardConfig
|
||||||
import com.tangem.domain.card.configs.Wallet2CardConfig
|
import com.tangem.domain.card.configs.Wallet2CardConfig
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||||
import org.junit.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
internal class MissedDerivationsFinderTest {
|
internal class MissedDerivationsFinderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -97,9 +99,8 @@ internal class MissedDerivationsFinderTest {
|
||||||
val currencies = MockCryptoCurrencyFactory(userWallet).cardano.let(::listOf)
|
val currencies = MockCryptoCurrencyFactory(userWallet).cardano.let(::listOf)
|
||||||
val actual = finder.find(currencies)
|
val actual = finder.find(currencies)
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactly(
|
val expected = mapOf(
|
||||||
ByteArrayKey(EllipticCurve.Ed25519.name.toByteArray()),
|
ByteArrayKey(EllipticCurve.Ed25519.name.toByteArray()) to listOf(
|
||||||
listOf(
|
|
||||||
DerivationConfigV2.derivations(Blockchain.Cardano).values.first(),
|
DerivationConfigV2.derivations(Blockchain.Cardano).values.first(),
|
||||||
CardanoUtils.extendedDerivationPath(
|
CardanoUtils.extendedDerivationPath(
|
||||||
derivationPath = DerivationPath(
|
derivationPath = DerivationPath(
|
||||||
|
|
@ -108,7 +109,12 @@ internal class MissedDerivationsFinderTest {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ByteArrayKey(EllipticCurve.Secp256k1.name.toByteArray()) to listOf(
|
||||||
|
DerivationConfigV2.derivations(Blockchain.Ethereum).values.first(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue