Updated on 2026-08-14

This commit is contained in:
Tangem 2021-12-14 18:41:30 +00:00
commit bf2cea8a46
9 changed files with 149 additions and 42 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain
import CreateProductWalletTask
import CreateProductWalletTaskResponse
import android.content.Context
import com.tangem.Message
import com.tangem.TangemSdk
@ -48,7 +49,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
}
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<Card> {
suspend fun createProductWallet(
scanResponse: ScanResponse
): CompletionResult<CreateProductWalletTaskResponse> {
return runTaskAsync(
CreateProductWalletTask(scanResponse.productType),
scanResponse.card.cardId,

View file

@ -1,33 +1,42 @@
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.hdWallet.ExtendedPublicKey
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeyList
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.tap.common.extensions.toMapKey
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
import com.tangem.tap.store
/**
[REDACTED_AUTHOR]
*/
data class CreateProductWalletTaskResponse(
val card: Card,
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf(),
val primaryCard: PrimaryCard? = null
) : CommandResponse
class CreateProductWalletTask(
private val type: ProductType,
) : CardSessionRunnable<Card> {
) : CardSessionRunnable<CreateProductWalletTaskResponse> {
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
override fun run(
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit
) {
val card = session.environment.card.guard {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
@ -41,7 +50,15 @@ class CreateProductWalletTask(
}
commandProcessor.proceed(card, session) {
when (it) {
is CompletionResult.Success -> callback(CompletionResult.Success(session.environment.card!!))
is CompletionResult.Success -> {
val result = when (commandProcessor) {
is CreateWalletTangemWallet -> {
it.data as CreateProductWalletTaskResponse
}
else -> CreateProductWalletTaskResponse(card = session.environment.card!!)
}
callback(CompletionResult.Success(result))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
}
}
@ -79,44 +96,74 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
}
}
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletResponse> {
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
private var primaryCard: PrimaryCard? = null
private var createWalletResponse: CreateWalletResponse? = null
override fun proceed(
card: Card,
session: CardSession,
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList()
CreateWalletsTask(curves).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
val response = result.data.createWalletResponses[0]
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
.mapNotNull { it.derivationPath() }
DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths).run(session) {
when (it) {
is CompletionResult.Success -> {
updateDerivedKeys(response.wallet, it.data)
callback(CompletionResult.Success(response))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
}
}
createWalletResponse = result.data.createWalletResponses[0]
linkPrimaryCard(card, session, callback)
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}
//TODO: updating derived wallet public keys - make it better later
private fun updateDerivedKeys(wallet: CardWallet, derivedKeys: ExtendedPublicKeyList) {
val onboardingManager = store.state.globalState.onboardingState.onboardingManager ?: return
onboardingManager.scanResponse = onboardingManager.scanResponse.copy(
derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys)
)
private fun linkPrimaryCard(
card: Card,
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
StartPrimaryCardLinkingTask().run(session) { result ->
when (result) {
is CompletionResult.Success -> {
primaryCard = result.data
deriveKeys(card, session, callback)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
private fun deriveKeys(
card: Card,
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
) {
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
.mapNotNull { it.derivationPath() }
val response = createWalletResponse.guard {
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
return
}
DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths)
.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
val derivedKeys = mapOf(response.wallet.publicKey.toMapKey() to result.data)
callback(CompletionResult.Success(CreateProductWalletTaskResponse(
card = session.environment.card!!,
derivedKeys = derivedKeys,
primaryCard = primaryCard
)))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}
private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletResponse> {

View file

@ -20,6 +20,8 @@ import com.tangem.operations.CommandResponse
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.ScanTask
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.common.extensions.ByteArrayKey
@ -33,13 +35,15 @@ import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.preferencesStorage
data class ScanResponse(
val card: Card,
val productType: ProductType,
val walletData: WalletData?,
val secondTwinPublicKey: String? = null,
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf()
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf(),
val primaryCard: PrimaryCard? = null
) : CommandResponse {
fun getBlockchain(): Blockchain {
@ -134,16 +138,48 @@ private class ScanWalletProcessor(
private val shouldDeriveWC: Boolean
) : ProductCommandProcessor<ScanResponse> {
var primaryCard: PrimaryCard? = null
override fun proceed(
card: Card,
session: CardSession,
callback: (result: CompletionResult<ScanResponse>) -> Unit
) {
val activationIsFinished = preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId)
if (card.backupStatus?.isActive != true && !activationIsFinished && card.wallets.isNotEmpty()) {
StartPrimaryCardLinkingTask().run(session) { linkingResult ->
when (linkingResult) {
is CompletionResult.Success -> {
primaryCard = linkingResult.data
deriveKeysIfNeeded(card, session, callback)
}
is CompletionResult.Failure -> {
deriveKeysIfNeeded(card, session, callback)
}
}
}
} else {
deriveKeysIfNeeded(card, session, callback)
}
}
private fun deriveKeysIfNeeded(
card: Card,
session: CardSession,
callback: (result: CompletionResult<ScanResponse>) -> Unit
) {
val derivationPaths = collectDerivationPaths(card)?.distinct()
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) {
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
callback(CompletionResult.Success(ScanResponse(
card = card,
productType = ProductType.Wallet,
walletData = session.environment.walletData,
primaryCard = primaryCard
)))
return
}
@ -152,10 +188,11 @@ private class ScanWalletProcessor(
is CompletionResult.Success -> {
val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data)
val response = ScanResponse(
card,
ProductType.Wallet,
session.environment.walletData,
derivedKeys = derivedKeys
card = card,
productType = ProductType.Wallet,
walletData = session.environment.walletData,
derivedKeys = derivedKeys,
primaryCard = primaryCard
)
callback(CompletionResult.Success(response))

View file

@ -80,7 +80,7 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
withMainContext {
when (result) {
is CompletionResult.Success -> {
val updatedResponse = scanResponse.copy(card = result.data)
val updatedResponse = scanResponse.copy(card = result.data.card)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))

View file

@ -67,7 +67,9 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
withMainContext {
when (result) {
is CompletionResult.Success -> {
val updatedResponse = onboardingManager.scanResponse.copy(card = result.data)
val updatedResponse = onboardingManager.scanResponse.copy(
card = result.data.card
)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)

View file

@ -25,6 +25,8 @@ sealed class BackupAction : Action {
object IntroduceBackup : BackupAction()
object StartBackup : BackupAction()
object DismissBackup : BackupAction()
object StartAddingPrimaryCard : BackupAction()
object ScanPrimaryCard : BackupAction()
object CheckForUnfinishedBackup : BackupAction()

View file

@ -76,7 +76,11 @@ private fun handleWalletAction(action: Action) {
is CompletionResult.Success -> {
//here we must use updated scanResponse after createWallet & derivation
val updatedResponse =
globalState.onboardingState.onboardingManager.scanResponse.copy(card = result.data)
globalState.onboardingState.onboardingManager.scanResponse.copy(
card = result.data.card,
derivedKeys = result.data.derivedKeys,
primaryCard = result.data.primaryCard
)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingWalletAction.ProceedBackup)
@ -164,6 +168,13 @@ private fun handleBackupAction(action: BackupAction) {
when (action) {
is BackupAction.StartBackup -> {
backupService.discardSavedBackup()
val primaryCard = scanResponse?.primaryCard
if (primaryCard != null) {
backupService.setPrimaryCard(primaryCard)
store.dispatch(BackupAction.StartAddingBackupCards)
} else {
store.dispatch(BackupAction.StartAddingPrimaryCard)
}
}
is BackupAction.ScanPrimaryCard -> {
backupService.readPrimaryCard(cardId = card?.cardId) { result ->

View file

@ -44,7 +44,7 @@ class BackupReducer {
canSkipBackup = state.canSkipBackup
)
BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard)
BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard)
BackupAction.StartAddingBackupCards -> {
state.copy(backupStep = BackupStep.AddBackupCards)
@ -127,6 +127,7 @@ class BackupReducer {
BackupAction.DiscardBackup -> state
BackupAction.ResumeBackup -> state
BackupAction.DiscardSavedBackup -> state
BackupAction.StartBackup -> state
}
}

View file

@ -44,6 +44,10 @@ class UsedCardsPrefStorage(
save(foundItem, restoredList)
}
fun isActivationFinished(cardId: String): Boolean {
return !(findCardInfo(cardId)?.isActivationStarted ?: true)
}
fun activationIsStarted(cardId: String): Boolean {
return findCardInfo(cardId)?.isActivationStarted ?: false
}