Updated on 2026-08-14
This commit is contained in:
parent
6480caeeed
commit
e960e9b068
8 changed files with 21 additions and 410 deletions
|
|
@ -5,12 +5,10 @@ import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||||
import com.tangem.domain.card.BuildConfig
|
import com.tangem.domain.card.BuildConfig
|
||||||
import com.tangem.domain.card.repository.CardRepository
|
import com.tangem.domain.card.repository.CardRepository
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
|
||||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
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
|
||||||
|
|
@ -34,8 +32,6 @@ internal class TangemSdkManagerModule {
|
||||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||||
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||||
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||||
dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
|
||||||
blockchainToDeriveFinder: BlockchainToDeriveFinder,
|
|
||||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||||
cardRepository: CardRepository,
|
cardRepository: CardRepository,
|
||||||
): TangemSdkManager {
|
): TangemSdkManager {
|
||||||
|
|
@ -49,8 +45,6 @@ internal class TangemSdkManagerModule {
|
||||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||||
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
||||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||||
dynamicAddressesFeatureToggles = dynamicAddressesFeatureToggles,
|
|
||||||
blockchainToDeriveFinder = blockchainToDeriveFinder,
|
|
||||||
analyticsErrorHandler = analyticsErrorHandler,
|
analyticsErrorHandler = analyticsErrorHandler,
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.card.repository.CardRepository
|
import com.tangem.domain.card.repository.CardRepository
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
|
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
@ -58,6 +57,7 @@ import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
||||||
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
|
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
|
||||||
import com.tangem.tap.domain.twins.FinalizeTwinTask
|
import com.tangem.tap.domain.twins.FinalizeTwinTask
|
||||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||||
|
import com.tangem.utils.logging.TangemLogger
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
@ -73,8 +73,6 @@ internal class DefaultTangemSdkManager(
|
||||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||||
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||||
private val dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
|
|
||||||
private val blockchainToDeriveFinder: BlockchainToDeriveFinder,
|
|
||||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||||
private val cardRepository: CardRepository,
|
private val cardRepository: CardRepository,
|
||||||
) : TangemSdkManager {
|
) : TangemSdkManager {
|
||||||
|
|
@ -145,12 +143,10 @@ internal class DefaultTangemSdkManager(
|
||||||
runTaskAsyncReturnOnMain(
|
runTaskAsyncReturnOnMain(
|
||||||
runnable = ScanProductTask(
|
runnable = ScanProductTask(
|
||||||
card = null,
|
card = null,
|
||||||
blockchainToDeriveFinder = blockchainToDeriveFinder,
|
|
||||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||||
visaCardScanHandler = visaCardScanHandler,
|
visaCardScanHandler = visaCardScanHandler,
|
||||||
visaCoroutineScope = this,
|
visaCoroutineScope = this,
|
||||||
shouldCheckIsAlreadyActivated = shouldCheckIsAlreadyActivated,
|
shouldCheckIsAlreadyActivated = shouldCheckIsAlreadyActivated,
|
||||||
isDynamicAddressesEnabled = dynamicAddressesFeatureToggles.isDynamicAddressesEnabled,
|
|
||||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
),
|
),
|
||||||
|
|
@ -242,6 +238,7 @@ internal class DefaultTangemSdkManager(
|
||||||
Analytics.send(event = analyticsEvent.withParams(params.toMap()))
|
Analytics.send(event = analyticsEvent.withParams(params.toMap()))
|
||||||
}
|
}
|
||||||
.doOnFailure { tangemError ->
|
.doOnFailure { tangemError ->
|
||||||
|
TangemLogger.e("scanProduct failed: code=${tangemError.code}, message=${tangemError.customMessage}")
|
||||||
(tangemError as? TangemSdkError)?.let { error ->
|
(tangemError as? TangemSdkError)?.let { error ->
|
||||||
Analytics.sendErrorEvent(TangemSdkErrorEvent(error))
|
Analytics.sendErrorEvent(TangemSdkErrorEvent(error))
|
||||||
}
|
}
|
||||||
|
|
@ -470,7 +467,6 @@ internal class DefaultTangemSdkManager(
|
||||||
runnable = FinalizeTwinTask(
|
runnable = FinalizeTwinTask(
|
||||||
twinPublicKey = secondCardPublicKey,
|
twinPublicKey = secondCardPublicKey,
|
||||||
issuerKeys = issuerKeyPair,
|
issuerKeys = issuerKeyPair,
|
||||||
isDynamicAddressesEnabled = dynamicAddressesFeatureToggles.isDynamicAddressesEnabled,
|
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
),
|
),
|
||||||
cardId = cardId,
|
cardId = cardId,
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,8 +12,6 @@ import com.tangem.common.extensions.*
|
||||||
import com.tangem.common.tlv.Tlv
|
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.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
|
||||||
|
|
@ -32,25 +30,21 @@ import com.tangem.operations.PreflightReadMode
|
||||||
import com.tangem.operations.ScanTask
|
import com.tangem.operations.ScanTask
|
||||||
import com.tangem.operations.backup.PrimaryCard
|
import com.tangem.operations.backup.PrimaryCard
|
||||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
|
||||||
import com.tangem.operations.files.ReadFilesTask
|
import com.tangem.operations.files.ReadFilesTask
|
||||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||||
import com.tangem.tap.domain.TapSdkError
|
import com.tangem.tap.domain.TapSdkError
|
||||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||||
import com.tangem.tap.mainScope
|
import com.tangem.tap.mainScope
|
||||||
import com.tangem.tap.scope
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
internal class ScanProductTask(
|
internal class ScanProductTask(
|
||||||
private val card: Card?,
|
private val card: Card?,
|
||||||
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?,
|
||||||
private val shouldCheckIsAlreadyActivated: Boolean,
|
private val shouldCheckIsAlreadyActivated: Boolean,
|
||||||
private val isDynamicAddressesEnabled: Boolean,
|
|
||||||
private val cardRepository: CardRepository,
|
private val cardRepository: CardRepository,
|
||||||
override val allowsRequestAccessCodeFromRepository: Boolean = false,
|
override val allowsRequestAccessCodeFromRepository: Boolean = false,
|
||||||
) : CardSessionRunnable<ScanResponse> {
|
) : CardSessionRunnable<ScanResponse> {
|
||||||
|
|
@ -80,8 +74,6 @@ internal class ScanProductTask(
|
||||||
session = session,
|
session = session,
|
||||||
cardDto = cardDto,
|
cardDto = cardDto,
|
||||||
scanWalletProcessor = ScanWalletProcessor(
|
scanWalletProcessor = ScanWalletProcessor(
|
||||||
blockchainToDeriveFinder = blockchainToDeriveFinder,
|
|
||||||
isDynamicAddressesEnabled = isDynamicAddressesEnabled,
|
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
),
|
),
|
||||||
callback = callback,
|
callback = callback,
|
||||||
|
|
@ -92,8 +84,6 @@ internal class ScanProductTask(
|
||||||
val commandProcessor = when {
|
val commandProcessor = when {
|
||||||
cardDto.isTangemTwins -> ScanTwinProcessor()
|
cardDto.isTangemTwins -> ScanTwinProcessor()
|
||||||
else -> ScanWalletProcessor(
|
else -> ScanWalletProcessor(
|
||||||
blockchainToDeriveFinder = blockchainToDeriveFinder,
|
|
||||||
isDynamicAddressesEnabled = isDynamicAddressesEnabled,
|
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +92,8 @@ internal class ScanProductTask(
|
||||||
is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult ->
|
is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult ->
|
||||||
when (scanTaskResult) {
|
when (scanTaskResult) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
// it needed because processorResult.data.card doesn't contains attestation result
|
// It's needed because processorResult.data.card doesn't contain the attestation
|
||||||
// and CardWallet.derivedKeys
|
// result or the existing CardWallet.derivedKeys read from the card.
|
||||||
val processorScanResponseWithNewCard = processorResult.data.copy(
|
val processorScanResponseWithNewCard = processorResult.data.copy(
|
||||||
card = CardDTO(scanTaskResult.data),
|
card = CardDTO(scanTaskResult.data),
|
||||||
)
|
)
|
||||||
|
|
@ -176,8 +166,6 @@ internal class ScanProductTask(
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScanWalletProcessor(
|
private class ScanWalletProcessor(
|
||||||
private val blockchainToDeriveFinder: BlockchainToDeriveFinder?,
|
|
||||||
private val isDynamicAddressesEnabled: Boolean,
|
|
||||||
private val cardRepository: CardRepository,
|
private val cardRepository: CardRepository,
|
||||||
) : ProductCommandProcessor<ScanResponse> {
|
) : ProductCommandProcessor<ScanResponse> {
|
||||||
|
|
||||||
|
|
@ -281,48 +269,34 @@ private class ScanWalletProcessor(
|
||||||
when (linkingResult) {
|
when (linkingResult) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
primaryCard = linkingResult.data
|
primaryCard = linkingResult.data
|
||||||
deriveKeysIfNeeded(card, session, callback)
|
completeScan(card, session, callback)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
deriveKeysIfNeeded(card, session, callback)
|
completeScan(card, session, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deriveKeysIfNeeded(card, session, callback)
|
completeScan(card, session, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deriveKeysIfNeeded(
|
// Keys are no longer derived during scan: default derivations are created up front in
|
||||||
|
// CreateProductWalletTask, and derivations for additional tokens are handled by
|
||||||
|
// DefaultColdMapDerivationsRepository when the user explicitly adds a token.
|
||||||
|
private fun completeScan(
|
||||||
card: CardDTO,
|
card: CardDTO,
|
||||||
session: CardSession,
|
session: CardSession,
|
||||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val productType = getWalletProductType(card)
|
val scanResponse = ScanResponse(
|
||||||
scope.launch {
|
card = card,
|
||||||
val scanResponse = ScanResponse(
|
productType = getWalletProductType(card),
|
||||||
card = card,
|
walletData = session.environment.walletData,
|
||||||
productType = productType,
|
primaryCard = primaryCard,
|
||||||
walletData = session.environment.walletData,
|
)
|
||||||
primaryCard = primaryCard,
|
callback(CompletionResult.Success(scanResponse))
|
||||||
)
|
|
||||||
val derivations = collectDerivations(card, scanResponse)
|
|
||||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
|
||||||
callback(CompletionResult.Success(scanResponse))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
|
||||||
when (result) {
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
val response = scanResponse.copy(derivedKeys = result.data.entries)
|
|
||||||
callback(CompletionResult.Success(response))
|
|
||||||
}
|
|
||||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWalletProductType(card: CardDTO): ProductType {
|
private fun getWalletProductType(card: CardDTO): ProductType {
|
||||||
|
|
@ -334,17 +308,6 @@ private class ScanWalletProcessor(
|
||||||
else -> ProductType.Wallet
|
else -> ProductType.Wallet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun collectDerivations(
|
|
||||||
card: CardDTO,
|
|
||||||
scanResponse: ScanResponse,
|
|
||||||
): Map<ByteArrayKey, List<DerivationPath>> {
|
|
||||||
val blockchains = blockchainToDeriveFinder
|
|
||||||
?.find(card)
|
|
||||||
?: return emptyMap()
|
|
||||||
|
|
||||||
return MissedDerivationsFinder(scanResponse, isDynamicAddressesEnabled).findByBlockchainsToDerive(blockchains)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||||
class FinalizeTwinTask(
|
class FinalizeTwinTask(
|
||||||
private val twinPublicKey: ByteArray,
|
private val twinPublicKey: ByteArray,
|
||||||
private val issuerKeys: KeyPair,
|
private val issuerKeys: KeyPair,
|
||||||
private val isDynamicAddressesEnabled: Boolean,
|
|
||||||
private val cardRepository: CardRepository,
|
private val cardRepository: CardRepository,
|
||||||
) : CardSessionRunnable<ScanResponse> {
|
) : CardSessionRunnable<ScanResponse> {
|
||||||
|
|
||||||
|
|
@ -31,11 +30,9 @@ class FinalizeTwinTask(
|
||||||
is CompletionResult.Success ->
|
is CompletionResult.Success ->
|
||||||
ScanProductTask(
|
ScanProductTask(
|
||||||
card = readResult.data,
|
card = readResult.data,
|
||||||
blockchainToDeriveFinder = null,
|
|
||||||
visaCardScanHandler = null,
|
visaCardScanHandler = null,
|
||||||
visaCoroutineScope = null,
|
visaCoroutineScope = null,
|
||||||
shouldCheckIsAlreadyActivated = false,
|
shouldCheckIsAlreadyActivated = false,
|
||||||
isDynamicAddressesEnabled = isDynamicAddressesEnabled,
|
|
||||||
onboardingV2FeatureToggles = null,
|
onboardingV2FeatureToggles = null,
|
||||||
cardRepository = cardRepository,
|
cardRepository = cardRepository,
|
||||||
).run(session, callback)
|
).run(session, callback)
|
||||||
|
|
|
||||||
|
|
@ -325,11 +325,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||||
.doOnSuccess { sensitiveInfo ->
|
.doOnSuccess { sensitiveInfo ->
|
||||||
updateWallets { wallets ->
|
updateWallets { wallets ->
|
||||||
// It is necessary to update derivations because when scanning we obtain the missing keys
|
wallets?.updateWith(walletIdToSensitiveInformation = sensitiveInfo)
|
||||||
wallets?.updateWith(
|
|
||||||
walletIdToSensitiveInformation = sensitiveInfo,
|
|
||||||
walletIdToDerivedKeys = mapOf(userWallet.walletId to scanResponse.derivedKeys),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
trackSignInEvent(userWallet, AnalyticsParam.SignInType.Card)
|
trackSignInEvent(userWallet, AnalyticsParam.SignInType.Card)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package com.tangem.tap.domain.userWalletList.utils
|
package com.tangem.tap.domain.userWalletList.utils
|
||||||
|
|
||||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
|
||||||
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
|
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
|
||||||
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation
|
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation
|
||||||
|
|
||||||
|
|
@ -74,10 +72,7 @@ internal fun List<UserWalletPublicInformation>.toUserWallets(): List<UserWallet>
|
||||||
return this.map { it.toUserWallet() }
|
return this.map { it.toUserWallet() }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun UserWallet.updateWith(
|
internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInformation): UserWallet {
|
||||||
sensitiveInformation: UserWalletSensitiveInformation,
|
|
||||||
derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap>?,
|
|
||||||
): UserWallet {
|
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is UserWallet.Cold -> {
|
is UserWallet.Cold -> {
|
||||||
copy(
|
copy(
|
||||||
|
|
@ -85,7 +80,6 @@ internal fun UserWallet.updateWith(
|
||||||
card = scanResponse.card.copy(
|
card = scanResponse.card.copy(
|
||||||
wallets = requireNotNull(sensitiveInformation.wallets),
|
wallets = requireNotNull(sensitiveInformation.wallets),
|
||||||
),
|
),
|
||||||
derivedKeys = derivedKeys ?: scanResponse.derivedKeys,
|
|
||||||
// visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus,
|
// visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -98,17 +92,14 @@ internal fun UserWallet.updateWith(
|
||||||
|
|
||||||
internal fun List<UserWallet>.updateWith(
|
internal fun List<UserWallet>.updateWith(
|
||||||
walletIdToSensitiveInformation: Map<UserWalletId, UserWalletSensitiveInformation>,
|
walletIdToSensitiveInformation: Map<UserWalletId, UserWalletSensitiveInformation>,
|
||||||
walletIdToDerivedKeys: Map<UserWalletId, Map<KeyWalletPublicKey, ExtendedPublicKeysMap>>? = null,
|
|
||||||
): List<UserWallet> {
|
): List<UserWallet> {
|
||||||
return if (walletIdToSensitiveInformation.isEmpty()) {
|
return if (walletIdToSensitiveInformation.isEmpty()) {
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
this.map { wallet ->
|
this.map { wallet ->
|
||||||
val sensitiveInformation = walletIdToSensitiveInformation[wallet.walletId]
|
val sensitiveInformation = walletIdToSensitiveInformation[wallet.walletId]
|
||||||
val derivedKeys = walletIdToDerivedKeys?.get(wallet.walletId)
|
|
||||||
|
|
||||||
if (sensitiveInformation != null) {
|
if (sensitiveInformation != null) {
|
||||||
wallet.updateWith(sensitiveInformation, derivedKeys)
|
wallet.updateWith(sensitiveInformation)
|
||||||
} else {
|
} else {
|
||||||
wallet
|
wallet
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,252 +0,0 @@
|
||||||
package com.tangem.tap.domain.tasks.product
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
|
||||||
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.account.GetWalletAccountsResponse
|
|
||||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
|
||||||
import io.mockk.*
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.jupiter.api.AfterEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class BlockchainToDeriveFinderTest {
|
|
||||||
|
|
||||||
private val walletAccountsFetcher = mockk<WalletAccountsFetcher>()
|
|
||||||
private val finder = BlockchainToDeriveFinder(
|
|
||||||
walletAccountsFetcher = walletAccountsFetcher,
|
|
||||||
)
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
fun tearDown() {
|
|
||||||
clearMocks(walletAccountsFetcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN card is not HD wallet THEN return empty set`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val card = mockk<CardDTO> {
|
|
||||||
every { this@mockk.settings.isHDWalletAllowed } returns false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Truth.assertThat(actual).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN card has empty wallets THEN return empty set`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val card = mockk<CardDTO> {
|
|
||||||
every { this@mockk.settings.isHDWalletAllowed } returns true
|
|
||||||
every { this@mockk.wallets } returns emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Truth.assertThat(actual).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN saved bitcoin THEN return only bitcoin`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val card = createCardDTO()
|
|
||||||
|
|
||||||
val response = createResponse(Blockchain.Bitcoin)
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = setOf(
|
|
||||||
createExpected(Blockchain.Bitcoin),
|
|
||||||
)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN empty store and common demo card THEN return demo blockchains`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val demoCardId = "AC01000000045754"
|
|
||||||
val card = createCardDTO(cardId = demoCardId)
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = setOf(
|
|
||||||
createExpected(Blockchain.Bitcoin),
|
|
||||||
createExpected(Blockchain.Ethereum),
|
|
||||||
createExpected(Blockchain.Dogecoin),
|
|
||||||
createExpected(Blockchain.Solana),
|
|
||||||
)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN empty store and DE00 demo card THEN return demo blockchains`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val demoCardId = "DE00"
|
|
||||||
val card = createCardDTO(cardId = demoCardId)
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = setOf(
|
|
||||||
createExpected(Blockchain.Bitcoin),
|
|
||||||
createExpected(Blockchain.Ethereum),
|
|
||||||
createExpected(Blockchain.Dogecoin),
|
|
||||||
)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN empty store THEN return default blockchains`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val card = createCardDTO()
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns null
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = setOf(
|
|
||||||
createExpected(Blockchain.Bitcoin),
|
|
||||||
createExpected(Blockchain.Ethereum),
|
|
||||||
)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN saved cardano THEN return only cardano`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val card = createCardDTO()
|
|
||||||
|
|
||||||
val response = createResponse(Blockchain.Cardano)
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = setOf(
|
|
||||||
createExpected(Blockchain.Cardano),
|
|
||||||
)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `GIVEN saved eth-like blockchains THEN return all saved blockchains without filtering`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val card = createCardDTO()
|
|
||||||
|
|
||||||
val blockchains = listOf(Blockchain.Ethereum, Blockchain.BSC, Blockchain.Polygon)
|
|
||||||
|
|
||||||
val response = createResponse(*blockchains.toTypedArray())
|
|
||||||
|
|
||||||
coEvery { walletAccountsFetcher.getSaved(userWalletId) } returns response
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = finder.find(card)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = blockchains.mapTo(hashSetOf(), ::createExpected)
|
|
||||||
|
|
||||||
Truth.assertThat(actual).containsExactlyElementsIn(expected)
|
|
||||||
|
|
||||||
coVerify(exactly = 1) { walletAccountsFetcher.getSaved(userWalletId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createCardDTO(cardId: String = "0001", batchId: String = "AC10"): CardDTO {
|
|
||||||
val wallet = mockk<CardDTO.Wallet> {
|
|
||||||
every { this@mockk.publicKey } returns byteArrayOf(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mockk<CardDTO> {
|
|
||||||
every { this@mockk.cardId } returns cardId
|
|
||||||
every { this@mockk.batchId } returns batchId
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createResponse(vararg blockchains: Blockchain): GetWalletAccountsResponse {
|
|
||||||
val tokens = blockchains.map { blockchain ->
|
|
||||||
mockk<UserTokensResponse.Token> {
|
|
||||||
every { this@mockk.networkId } returns blockchain.toNetworkId()
|
|
||||||
every { this@mockk.derivationPath } returns blockchain.getDerivationPath().rawPath
|
|
||||||
every { this@mockk.contractAddress } returns null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val account = mockk<WalletAccountDTO> {
|
|
||||||
every { this@mockk.tokens } returns tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
return mockk {
|
|
||||||
every { this@mockk.accounts } returns listOf(account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createExpected(
|
|
||||||
blockchain: Blockchain,
|
|
||||||
derivationPath: DerivationPath = blockchain.getDerivationPath(),
|
|
||||||
): BlockchainToDerive {
|
|
||||||
return BlockchainToDerive(blockchain = blockchain, derivationPath = derivationPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Blockchain.getDerivationPath(): DerivationPath {
|
|
||||||
return derivationPath(DerivationStyle.V3)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
// for byteArrayOf(0)
|
|
||||||
val userWalletId = UserWalletId("41448576B8DA24C7D8F5F0F79863D20D7D8312A7F9E50D3248304136DDB7AAD7")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue