From b3957a170c8e3caa03ccf23d396e68d56c99544b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Dec 2021 14:41:32 +0300 Subject: [PATCH 01/15] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 258131199b..b16fbe21c7 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -94,8 +94,8 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState action.shouldDeriveWC, action.messageResId ) - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) withMainContext { + store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) when (result) { is CompletionResult.Success -> { tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data) From 2c35a0b39ac0754b78e78576f99a97d75e97cf4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Dec 2021 15:44:40 +0300 Subject: [PATCH 02/15] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt | 3 +++ .../java/com/tangem/tap/features/home/redux/HomeMiddleware.kt | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index f28523564f..fae5ee9060 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -13,6 +13,7 @@ import com.tangem.tap.common.transitions.FrontCardExitTransition import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_main.* @@ -36,6 +37,8 @@ class HomeFragment : BaseOnboardingFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + store.dispatch(BackupAction.CheckForUnfinishedBackup) + toolbar.hide() val shareTransition = FragmentShareTransition( listOf( diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index b93b3a2e7e..341aa3e64b 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -15,7 +15,6 @@ import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope @@ -39,7 +38,6 @@ private val homeMiddleware: Middleware = { dispatch, state -> store.dispatch(GlobalAction.RestoreAppCurrency) store.dispatch(GlobalAction.GetMoonPayStatus) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) - store.dispatch(BackupAction.CheckForUnfinishedBackup) } is HomeAction.ShouldScanCardOnResume -> { if (action.shouldScanCard) { From f3d868577657090df4adb8c25f200a721b196bc2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Dec 2021 21:38:37 +0300 Subject: [PATCH 03/15] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 5 +- .../tasks/product/CreateProductWalletTask.kt | 109 +++++++++++++----- .../domain/tasks/product/ScanProductTask.kt | 49 +++++++- .../note/redux/OnboardingNoteMiddleware.kt | 2 +- .../redux/OnboardingOtherCardsMiddleware.kt | 4 +- .../wallet/redux/OnboardingWalletAction.kt | 2 + .../redux/OnboardingWalletMiddleware.kt | 13 ++- .../wallet/redux/OnboardingWalletReducer.kt | 3 +- .../tap/persistence/UsedCardsPrefStorage.kt | 4 + 9 files changed, 149 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 7131feb7b7..af6b04b1e9 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -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 { + suspend fun createProductWallet( + scanResponse: ScanResponse + ): CompletionResult { return runTaskAsync( CreateProductWalletTask(scanResponse.productType), scanResponse.card.cardId, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 530b53ac4c..99e8cd0f62 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -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> = mapOf(), + val primaryCard: PrimaryCard? = null +) : CommandResponse + class CreateProductWalletTask( private val type: ProductType, -) : CardSessionRunnable { +) : CardSessionRunnable { - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> 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 { +private class CreateWalletTangemWallet : ProductCommandProcessor { + + private var primaryCard: PrimaryCard? = null + private var createWalletResponse: CreateWalletResponse? = null override fun proceed( card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit, + callback: (result: CompletionResult) -> 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) -> 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) -> 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 { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 4aa99403d2..278e424a2a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -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> = mapOf() + val derivedKeys: Map> = mapOf(), + val primaryCard: PrimaryCard? = null ) : CommandResponse { fun getBlockchain(): Blockchain { @@ -134,16 +138,48 @@ private class ScanWalletProcessor( private val shouldDeriveWC: Boolean ) : ProductCommandProcessor { + var primaryCard: PrimaryCard? = null + override fun proceed( card: Card, session: CardSession, callback: (result: CompletionResult) -> 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) -> 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)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 36e9c93cc1..6a8cc146b7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -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)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index f3419e67fa..29ca1fbb3b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index b3056d7bdd..d900eaf35f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -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() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index f9465751c3..14a8788714 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -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 -> diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 3947cd5d3f..09aad69d48 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -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 } } diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index 17238689f5..9d8d877f32 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -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 } From deed5020b641c41afc82a14ce14d72bb5adf9dbe Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Dec 2021 21:46:20 +0300 Subject: [PATCH 04/15] Updated on 2026-08-14 --- .../com/tangem/tap/domain/topup/TradeCryptoHelper.kt | 3 +++ .../redux/middlewares/TradeCryptoMiddleware.kt | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt index c8599f6d4c..ec92f9f0fe 100644 --- a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt @@ -38,6 +38,8 @@ class TradeCryptoHelper { private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId=" + private const val BASE_CURRENCY_USD = "USD" + fun getUrl( action: Action, blockchain: Blockchain?, @@ -56,6 +58,7 @@ class TradeCryptoHelper { baseUrl = BASE_URL_BUY originalQuery = API_KEY_PATH + apiKey.urlEncode() + CURRENCY_PATH + cryptoCurrencyName.urlEncode() + + BASE_CURRENCY_PATH + BASE_CURRENCY_USD.urlEncode() + WALLET_ADDRESS_PATH + walletAddress.urlEncode() + REDIRECT_URL_PATH + REDIRECT_URL_BUY.urlEncode() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 2b2087dd25..2cd5627545 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -58,12 +58,12 @@ class TradeCryptoMiddleware { } val url = TradeCryptoHelper.getUrl( - exchangeAction, - currency?.blockchain, - currencySymbol, - defaultAddress, - config.moonPayApiKey, - config.moonPayApiSecretKey + action = exchangeAction, + blockchain = currency?.blockchain, + cryptoCurrencyName = currencySymbol, + walletAddress = defaultAddress, + apiKey = config.moonPayApiKey, + secretKey = config.moonPayApiSecretKey ) Timber.d("Moonpay $exchangeAction URL: $url") store.dispatchOnMain(NavigationAction.OpenUrl(url)) From 9a2b966ca63dd783270b6f76863dfd12649c9e01 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Dec 2021 19:35:10 +0300 Subject: [PATCH 05/15] Updated on 2026-08-14 --- .../details/ui/DetailsSecurityFragment.kt | 4 ++++ .../res/layout/fragment_details_security.xml | 17 +++++++++++++++++ .../main/res/values/strings_untranslated.xml | 1 + 3 files changed, 22 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt index fe017dfec7..8ef33711e7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt @@ -5,6 +5,7 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater +import com.tangem.common.card.Card import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsAction @@ -72,6 +73,9 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security), ?: EnumSet.noneOf(SecurityOption::class.java) ) } + tv_access_code_unavailable_disclaimer.show( + state.scanResponse?.card?.backupStatus == Card.BackupStatus.NoBackup + ) } private fun selectSecurityOption(securityOption: SecurityOption?) { diff --git a/app/src/main/res/layout/fragment_details_security.xml b/app/src/main/res/layout/fragment_details_security.xml index 3e232e82d8..98f442ad29 100644 --- a/app/src/main/res/layout/fragment_details_security.xml +++ b/app/src/main/res/layout/fragment_details_security.xml @@ -183,6 +183,23 @@ app:layout_constraintBottom_toBottomOf="@id/tv_access_code_description" app:layout_constraintTop_toTopOf="@id/tv_access_code_title" /> + + + Reset to factory settings This action is irreversible. If, after resetting the card, someone sends funds to it, then you will not be able to withdraw them. + Access code is available only for cards with a backup. From 3b05c5db10d1d0de1432b2932190ee4e262ca0d0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Dec 2021 15:14:17 +0300 Subject: [PATCH 06/15] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/ProductType.kt | 3 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 2 +- .../com/tangem/tap/domain/TapWorkarounds.kt | 3 +- .../tasks/product/CreateProductWalletTask.kt | 76 +++---- .../domain/tasks/product/ScanProductTask.kt | 193 ++++++++++++------ .../tap/domain/twins/FinalizeTwinTask.kt | 3 +- .../features/details/redux/DetailsReducer.kt | 6 +- .../features/onboarding/OnboardingHelper.kt | 7 +- .../wallet/redux/OnboardingWalletAction.kt | 2 +- .../redux/OnboardingWalletMiddleware.kt | 9 +- .../wallet/redux/OnboardingWalletReducer.kt | 5 +- .../wallet/redux/OnboardingWalletState.kt | 3 +- .../products/wallet/ui/BackupCardsWidget.kt | 27 ++- .../wallet/ui/OnboardingWalletFragment.kt | 42 ++-- .../features/tokens/redux/TokensMiddleware.kt | 4 +- .../middlewares/MultiWalletMiddleware.kt | 4 +- 16 files changed, 240 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/ProductType.kt b/app/src/main/java/com/tangem/tap/domain/ProductType.kt index 07cbea8905..4f57e8949d 100644 --- a/app/src/main/java/com/tangem/tap/domain/ProductType.kt +++ b/app/src/main/java/com/tangem/tap/domain/ProductType.kt @@ -4,5 +4,6 @@ package com.tangem.tap.domain [REDACTED_AUTHOR] */ enum class ProductType { - Note, Wallet, Twins, Other + Note, Twins, Wallet + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index af6b04b1e9..455a6b973d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -45,7 +45,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) - return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository, shouldDeriveWC), null, message) + return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository), null, message) .also { sendScanFailuresToAnalytics(analyticsHandler, it) } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt index 45e19eb81a..ffc8df824c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -35,8 +35,7 @@ object TapWorkarounds { @Deprecated("Use ScanResponse.isTangemNote") fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId) - @Deprecated("Use ScanResponse.isTangemWallet") - fun isTangemWallet(card: Card): Boolean = tangemWalletBatches.contains(card.batchId) + fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId) fun getTangemNoteBlockchain(card: Card): Blockchain? = tangemNoteBatches[card.batchId] diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 99e8cd0f62..25e3d98836 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -2,7 +2,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card 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 @@ -45,8 +44,7 @@ class CreateProductWalletTask( val commandProcessor = when (type) { ProductType.Note -> CreateWalletTangemNote() ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") - ProductType.Wallet -> CreateWalletTangemWallet() - else -> CreateWalletOtherCards() + else -> CreateWalletTangemWallet() } commandProcessor.proceed(card, session) { when (it) { @@ -106,13 +104,27 @@ private class CreateWalletTangemWallet : ProductCommandProcessor) -> Unit, ) { - val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519) - val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList() + val curves = card.getCurvesForNonCreatedWallets() CreateWalletsTask(curves).run(session) { result -> when (result) { is CompletionResult.Success -> { createWalletResponse = result.data.createWalletResponses[0] - linkPrimaryCard(card, session, callback) + when { + card.settings.isBackupAllowed -> { + linkPrimaryCard(session, callback) + } + card.settings.isHDWalletAllowed -> { + deriveKeys(session, callback) + } + else -> { + callback( + CompletionResult.Success( + CreateProductWalletTaskResponse(card = session.environment.card!!) + ) + ) + } + } + } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } @@ -120,7 +132,6 @@ private class CreateWalletTangemWallet : ProductCommandProcessor) -> Unit, ) { @@ -128,7 +139,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor { primaryCard = result.data - deriveKeys(card, session, callback) + deriveKeys(session, callback) } is CompletionResult.Failure -> { callback(CompletionResult.Failure(result.error)) @@ -138,7 +149,6 @@ private class CreateWalletTangemWallet : ProductCommandProcessor) -> Unit, ) { @@ -149,42 +159,34 @@ private class CreateWalletTangemWallet : ProductCommandProcessor 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 - ))) + callback( + CompletionResult.Success( + CreateProductWalletTaskResponse( + card = session.environment.card!!, + derivedKeys = derivedKeys, + primaryCard = primaryCard + ) + ) + ) } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } -} - -private class CreateWalletOtherCards : ProductCommandProcessor { - - override fun proceed( - card: Card, - session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { - val firmwareVersion = card.firmwareVersion - val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) { - CreateWalletsTask(listOf(card.supportedCurves.first())) - } else { - CreateWalletsTask(card.getCurvesForNonCreatedWallets()) - } - - task.run(session) { result -> - when (result) { - is CompletionResult.Success -> callback(CompletionResult.Success(result.data.createWalletResponses[0])) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 278e424a2a..40c7a00ded 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -47,7 +47,8 @@ data class ScanResponse( ) : CommandResponse { fun getBlockchain(): Blockchain { - if (productType == ProductType.Note) return getTangemNoteBlockchain(card) ?: return Blockchain.Unknown + if (productType == ProductType.Note) return getTangemNoteBlockchain(card) + ?: return Blockchain.Unknown val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown return Blockchain.fromId(blockchainName) } @@ -66,17 +67,19 @@ data class ScanResponse( fun isTangemNote(): Boolean = productType == ProductType.Note fun isTangemWallet(): Boolean = productType == ProductType.Wallet fun isTangemTwins(): Boolean = productType == ProductType.Twins - fun isTangemOtherCards(): Boolean = productType == ProductType.Other - fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null + fun supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed + fun supportsBackup(): Boolean = card.settings.isBackupAllowed + + fun twinsIsTwinned(): Boolean = + card.isTangemTwins() && walletData != null && secondTwinPublicKey != null } typealias KeyWalletPublicKey = ByteArrayKey class ScanProductTask( val card: Card? = null, - private val currenciesRepository: CurrenciesRepository?, - private val shouldDeriveWC: Boolean + private val currenciesRepository: CurrenciesRepository? ) : CardSessionRunnable { override fun run( @@ -97,15 +100,22 @@ class ScanProductTask( val commandProcessor = when { TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor() card.isTangemTwins() -> ScanTwinProcessor() - TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor(currenciesRepository, shouldDeriveWC) - else -> ScanOtherCardsProcessor() + else -> ScanWalletProcessor(currenciesRepository) } commandProcessor.proceed(card, session) { processorResult -> when (processorResult) { is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult -> when (scanTaskResult) { - is CompletionResult.Success -> callback(CompletionResult.Success(processorResult.data)) - is CompletionResult.Failure -> callback(CompletionResult.Failure(scanTaskResult.error)) + is CompletionResult.Success -> callback( + CompletionResult.Success( + processorResult.data + ) + ) + is CompletionResult.Failure -> callback( + CompletionResult.Failure( + scanTaskResult.error + ) + ) } } is CompletionResult.Failure -> callback(CompletionResult.Failure(processorResult.error)) @@ -129,13 +139,20 @@ private class ScanNoteProcessor : ProductCommandProcessor { session: CardSession, callback: (result: CompletionResult) -> Unit ) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Note, session.environment.walletData))) + callback( + CompletionResult.Success( + ScanResponse( + card, + ProductType.Note, + session.environment.walletData + ) + ) + ) } } private class ScanWalletProcessor( - private val currenciesRepository: CurrenciesRepository?, - private val shouldDeriveWC: Boolean + private val currenciesRepository: CurrenciesRepository? ) : ProductCommandProcessor { var primaryCard: PrimaryCard? = null @@ -145,9 +162,60 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit ) { - val activationIsFinished = preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId) + createMissingWalletsIfNeeded(card, session, callback) + } - if (card.backupStatus?.isActive != true && !activationIsFinished && card.wallets.isNotEmpty()) { + private fun createMissingWalletsIfNeeded( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) { + startLinkingForBackupIfNeeded(card, session, callback) + return + } + + val curvesToCreate = card.getCurvesForNonCreatedWallets() + if (curvesToCreate.isEmpty()) { + startLinkingForBackupIfNeeded(card, session, callback) + return + } + + CreateWalletsTask(curvesToCreate).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + PreflightReadTask( + PreflightReadMode.FullCardRead, + card.cardId + ).run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> { + startLinkingForBackupIfNeeded(card, session, callback) + } + is CompletionResult.Failure -> callback( + CompletionResult.Failure( + readResult.error + ) + ) + } + } + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun startLinkingForBackupIfNeeded( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + val activationIsFinished = + preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId) + + if (card.backupStatus == Card.BackupStatus.NoBackup && + !activationIsFinished && card.wallets.isNotEmpty() + ) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { is CompletionResult.Success -> { @@ -162,7 +230,6 @@ private class ScanWalletProcessor( } else { deriveKeysIfNeeded(card, session, callback) } - } private fun deriveKeysIfNeeded( @@ -174,12 +241,16 @@ private class ScanWalletProcessor( val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) { - callback(CompletionResult.Success(ScanResponse( - card = card, - productType = ProductType.Wallet, - walletData = session.environment.walletData, - primaryCard = primaryCard - ))) + callback( + CompletionResult.Success( + ScanResponse( + card = card, + productType = ProductType.Wallet, + walletData = session.environment.walletData, + primaryCard = primaryCard + ) + ) + ) return } @@ -213,8 +284,14 @@ private class ScanWalletProcessor( (cardCurrencies.blockchains + tokenBlockchains).toMutableList() } - if (shouldDeriveWC) { - blockchainsToDerive.addAll(listOf(Blockchain.Ethereum, Blockchain.Binance, Blockchain.EthereumTestnet)) + if (card.settings.isHDWalletAllowed) { + blockchainsToDerive.addAll( + listOf( + Blockchain.Ethereum, + Blockchain.Binance, + Blockchain.EthereumTestnet + ) + ) } return blockchainsToDerive.toSet() @@ -234,17 +311,39 @@ private class ScanTwinProcessor : ProductCommandProcessor { is CompletionResult.Success -> { val publicKey = card.getSingleWallet()?.publicKey if (publicKey == null) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null))) + callback( + CompletionResult.Success( + ScanResponse( + card, + ProductType.Twins, + null + ) + ) + ) return@run } - val verified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) + val verified = + TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) if (verified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) val walletData = session.environment.walletData - val response = ScanResponse(card, ProductType.Twins, walletData, twinPublicKey.toHexString()) + val response = ScanResponse( + card, + ProductType.Twins, + walletData, + twinPublicKey.toHexString() + ) callback(CompletionResult.Success(response)) } else { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null))) + callback( + CompletionResult.Success( + ScanResponse( + card, + ProductType.Twins, + null + ) + ) + ) } } is CompletionResult.Failure -> @@ -255,43 +354,9 @@ private class ScanTwinProcessor : ProductCommandProcessor { } -private class ScanOtherCardsProcessor : ProductCommandProcessor { - - override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit) { - val walletData = session.environment.walletData - - if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) - return - } - - val curvesToCreate = card.getCurvesForNonCreatedWallets() - if (curvesToCreate.isEmpty()) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) - return - } - - CreateWalletsTask(curvesToCreate).run(session) { result -> - when (result) { - is CompletionResult.Success -> { - PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult -> - when (readResult) { - is CompletionResult.Success -> { - val response = ScanResponse(readResult.data, ProductType.Other, walletData) - callback(CompletionResult.Success(response)) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) - } - } - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } -} - fun Card.getCurvesForNonCreatedWallets(): List { - val curvesPresent = wallets.map { it.curve } - val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent) + val curvesPresent = wallets.map { it.curve }.toSet() + val curvesForNonCreatedWallets = supportedCurves + .subtract(curvesPresent + EllipticCurve.Secp256r1) return curvesForNonCreatedWallets.toList() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index 6fb4bb28c6..51ade7b4c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -23,7 +23,8 @@ class FinalizeTwinTask( PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult -> when (readResult) { is CompletionResult.Success -> - ScanProductTask(readResult.data, null, false).run(session, callback) + ScanProductTask(readResult.data, null) + .run(session, callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 71f4bd5fd7..7ea35d3284 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -51,7 +51,7 @@ private fun handlePrepareScreen( cardInfo = action.scanResponse.card.toCardInfo(), appCurrencyState = AppCurrencyState(action.fiatCurrencyName), cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), - createBackupAllowed = action.scanResponse.isTangemWallet() && !backupIsActive, + createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup, ) } @@ -65,7 +65,7 @@ private fun handleEraseWallet( val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false val notAllowedByCard = notAllowedByAnyWallet || (card?.isWalletDataSupported == true && - (!state.scanResponse.isTangemNote() && !state.scanResponse.isTangemWallet())) + (!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup())) val notEmpty = state.wallets.any { it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty() @@ -139,7 +139,7 @@ private fun handleSecurityAction( state.scanResponse?.isTangemNote() == true -> { EnumSet.of(SecurityOption.LongTap) } - state.scanResponse?.isTangemWallet() == true -> { + state.scanResponse?.supportsBackup() == true -> { EnumSet.of(state.securityScreenState?.currentOption) } else -> prepareAllowedSecurityOptions( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 6a097f6511..258701cabc 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -32,9 +32,12 @@ class OnboardingHelper { fun whereToNavigate(scanResponse: ScanResponse): AppScreen { return when (scanResponse.productType) { ProductType.Note -> AppScreen.OnboardingNote - ProductType.Wallet -> AppScreen.OnboardingWallet + ProductType.Wallet -> if (scanResponse.card.settings.isBackupAllowed) { + AppScreen.OnboardingWallet + } else { + AppScreen.OnboardingOther + } ProductType.Twins -> AppScreen.OnboardingTwins - ProductType.Other -> AppScreen.OnboardingOther } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index d900eaf35f..9bfdaaf381 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -22,7 +22,7 @@ sealed class OnboardingWalletAction : Action { sealed class BackupAction : Action { object DetermineBackupStep : BackupAction() - object IntroduceBackup : BackupAction() + data class IntroduceBackup(val buyCardsUrl: String? = null) : BackupAction() object StartBackup : BackupAction() object DismissBackup : BackupAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 14a8788714..1816465f6e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -110,7 +110,14 @@ private fun handleWalletAction(action: Action) { BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard is BackupService.State.FinalizingBackupCard -> BackupAction.PrepareToWriteBackupCard(backupState.index) - else -> BackupAction.IntroduceBackup + else -> { + val url = if (card?.issuer?.name?.lowercase()?.contains("tangem") == true) { + BUY_WALLET_URL + } else { + null + } + BackupAction.IntroduceBackup(url) + } } store.dispatch(newAction) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 09aad69d48..89542c532e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -39,9 +39,10 @@ class BackupReducer { ): BackupState { return when (action) { - BackupAction.IntroduceBackup -> BackupState( + is BackupAction.IntroduceBackup -> BackupState( backupStep = BackupStep.InitBackup, - canSkipBackup = state.canSkipBackup + canSkipBackup = state.canSkipBackup, + buyAdditionalCardsUrl = action.buyCardsUrl ) BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index d8d0c687b7..a048261a6d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -52,7 +52,8 @@ data class BackupState( val accessCodeError: AccessCodeError? = null, val backupStep: BackupStep = BackupStep.InitBackup, val maxBackupCards: Int = 2, - val canSkipBackup: Boolean = true + val canSkipBackup: Boolean = true, + val buyAdditionalCardsUrl: String? = null ) enum class AccessCodeError { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt index d5b5065f36..e4de53c9a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt @@ -15,7 +15,13 @@ class BackupCardsWidget( val getTopOfAnchorViewForActivateState: () -> Float, ) { + var currentState: WidgetState? = null + fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { + if (currentState == WidgetState.WELCOME) return + + currentState = WidgetState.WELCOME + val animator = createAnimator(animate, onEnd) animator.playTogether( createAnimator(BackupCardType.ORIGIN, createWelcomeProperties(BackupCardType.ORIGIN)), @@ -26,6 +32,10 @@ class BackupCardsWidget( } fun toFolded(animate: Boolean = true, onEnd: () -> Unit = {}) { + if (currentState == WidgetState.FOLDED) return + + currentState = WidgetState.FOLDED + val animator = createAnimator(animate, onEnd) animator.playTogether( createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), @@ -36,6 +46,10 @@ class BackupCardsWidget( } fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) { + if (currentState == WidgetState.FAN) return + + currentState = WidgetState.FAN + val animator = createAnimator(animate, onEnd) animator.playTogether( createAnimator(BackupCardType.ORIGIN, createFanProperties(BackupCardType.ORIGIN)), @@ -46,6 +60,8 @@ class BackupCardsWidget( } fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) { + currentState = WidgetState.LEAPFROG + val animator = createAnimator(animate, onEnd) animator.playTogether( createAnimator(BackupCardType.ORIGIN, createLeapfrogProperties(BackupCardType.ORIGIN)), @@ -60,15 +76,6 @@ class BackupCardsWidget( animator.start() } } -// -// fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) { -// val animator = createAnimator(animate, onEnd) -// animator.playTogether( -// createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), -// createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) -// ) -// animator.start() -// } private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { return AnimatorSet().apply { @@ -196,6 +203,8 @@ class BackupCardsWidget( fun getSecondBackupCardView(): ImageView { return getLeapViewByCardNumber(BackupCardType.SECOND_BACKUP).view as ImageView } + + enum class WidgetState { WELCOME, FOLDED, FAN, LEAPFROG } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index f415e176b9..5a1ccf6684 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -148,19 +148,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), } private fun showScanOriginCard(state: BackupState) { - - toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup) + prepareBackupView() cardsWidget.toFolded() - tv_header.show() - tv_body.show() - view_pager_backup_info.hide() - tab_layout_backup_info.hide() - - imv_first_backup_card.show() - imv_second_backup_card.show() - tv_header.text = getText(R.string.onboarding_title_scan_origin_card) tv_body.text = getString( R.string.onboarding_subtitle_scan_origin_card, @@ -171,9 +162,20 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) } } - private fun showAddBackupCards(state: BackupState) { + private fun prepareBackupView() { + toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup) + + tv_header.show() + tv_body.show() + view_pager_backup_info.hide() + tab_layout_backup_info.hide() + imv_first_backup_card.show() imv_second_backup_card.show() + } + + private fun showAddBackupCards(state: BackupState) { + prepareBackupView() accessCodeDialog?.dismiss() accessCodeDialog = null @@ -190,11 +192,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), when (state.backupCardsNumber) { 0 -> { - cardsWidget.toFan() + cardsWidget.toFan() { + cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).setDuration(200) + cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).setDuration(200) + } tv_header.text = getText(R.string.onboarding_title_no_backup_cards) tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards) - cardsWidget.getFirstBackupCardView().alpha = 0.6f - cardsWidget.getSecondBackupCardView().alpha = 0.2f } 1 -> { tv_header.text = getText(R.string.onboarding_title_one_backup_card) @@ -283,10 +286,6 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), cardsWidget.toLeapfrog() -// imv_front_card.alpha = 1f -// imv_first_backup_card.alpha = 1f -// imv_second_backup_card.alpha = 1f - btn_alternative_action.hide() } @@ -373,9 +372,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.shop, menu) - val backupStep = store.state.onboardingWalletState.backupState.backupStep + val backupState = store.state.onboardingWalletState.backupState + val backupStep = backupState.backupStep + val shopMenuShouldBeVisible = - backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards + (backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards) && + backupState.buyAdditionalCardsUrl != null menu.getItem(0).isVisible = shopMenuShouldBeVisible } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 2176e4ea51..92fe81b661 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -46,7 +46,7 @@ class TokensMiddleware { isTestNet = isTestcard ) val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() - if (scanResponse.isTangemWallet()) { + if (scanResponse.supportsHdWallet()) { currencies.forEach { when (it) { is CurrencyListItem.TitleListItem -> { @@ -81,7 +81,7 @@ class TokensMiddleware { .map { it.token } if (blockchains.isEmpty() && tokens.isEmpty()) return - if (scanResponse.isTangemWallet()) { + if (scanResponse.supportsHdWallet()) { deriveMissingBlockchains(scanResponse, blockchains, tokens) } else { submitAdd(blockchains, tokens) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 11d7232cd3..ace2084261 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -85,7 +85,7 @@ class MultiWalletMiddleware { } is WalletAction.MultiWallet.FindBlockchainsInUse -> { val scanResponse = globalState.scanResponse ?: return - if (scanResponse.isTangemWallet()) return + if (scanResponse.supportsHdWallet()) return val cardFirmware = scanResponse.card.firmwareVersion val blockchains = currenciesRepository.getBlockchains(cardFirmware) @@ -125,7 +125,7 @@ class MultiWalletMiddleware { } is WalletAction.MultiWallet.FindTokensInUse -> { val scanResponse = globalState.scanResponse ?: return - if (scanResponse.isTangemWallet()) return + if (scanResponse.supportsHdWallet()) return val walletFactory = tapWalletManager.walletManagerFactory val card = scanResponse.card From 38b7c1d0422cc30073c96378b180b5cb1dd48606 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Dec 2021 15:14:56 +0300 Subject: [PATCH 07/15] Updated on 2026-08-14 --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 041c1aa018..a8830f8137 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,7 +109,7 @@ You may experience NFC problems with some iPhone 7/7+ during the extraction This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. Warning - This card it is not designed to work with Tangem + This card is not designed to work with this app The card you scanned is a development card. Don’t accept it as a payment Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. We’re working hard with Apple to make it possible in future versions of iOS. From e922364e1adc9ddee8e12ecbf952e8f8ee8dba70 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Dec 2021 15:16:58 +0300 Subject: [PATCH 08/15] Updated on 2026-08-14 --- .../java/com/tangem/tap/features/feedback/FeedbackManager.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt index 0e4fe0a875..0ae3d143a8 100644 --- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt @@ -218,7 +218,6 @@ interface EmailData { fun joinTogether(infoHolder: AdditionalEmailInfo): String { return "$mainMessage\n\n\n\n" + - "Following information is optional. You can erase it if you don’t want to share it.\n" + createOptionalMessage(infoHolder) } } From 1f0173686fbfd6631553f9fc0f6af41d784cae50 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Dec 2021 20:46:37 +0300 Subject: [PATCH 09/15] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 12 +-- .../tap/domain/extensions/Blockchain.kt | 15 +++ .../tangem/tap/domain/tasks/DerivationTask.kt | 50 ++++++++++ .../tasks/product/CreateProductWalletTask.kt | 7 +- .../domain/tasks/product/ScanProductTask.kt | 45 ++++++--- .../features/tokens/redux/CurrencyListItem.kt | 2 +- .../features/tokens/redux/TokensMiddleware.kt | 95 ++++++++++++------- .../tokens/ui/adapters/CurrenciesAdapter.kt | 2 +- 8 files changed, 167 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 455a6b973d..449e1735ff 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -15,15 +15,16 @@ import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError import com.tangem.common.hdWallet.DerivationPath import com.tangem.operations.CommandResponse -import com.tangem.operations.derivation.DeriveWalletPublicKeysTask -import com.tangem.operations.derivation.ExtendedPublicKeyList import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.extensions.ByteArrayKey import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask +import com.tangem.tap.domain.tasks.DerivationTask +import com.tangem.tap.domain.tasks.DerivationTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse @@ -77,10 +78,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun derivePublicKeys( cardId: String, - walletPublicKey: ByteArray, - derivationPaths: List - ): CompletionResult { - return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId) + derivations: Map> + ): CompletionResult { + return runTaskAsyncReturnOnMain(DerivationTask(derivations), cardId) } suspend fun resetToFactorySettings(card: Card): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 5ec3ea7672..0563eb8482 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -2,6 +2,7 @@ package com.tangem.tap.domain.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.common.card.EllipticCurve import java.math.BigDecimal @@ -25,4 +26,18 @@ fun Blockchain.minimalAmount(): BigDecimal { return 1.toBigDecimal().movePointLeft(decimals()) } +fun Blockchain.getPrimaryCurve(): EllipticCurve? { + return when { + getSupportedCurves().contains(EllipticCurve.Secp256k1) -> { + EllipticCurve.Secp256k1 + } + getSupportedCurves().contains(EllipticCurve.Ed25519) -> { + EllipticCurve.Ed25519 + } + else -> { + null + } + } +} + private const val NODL = "NODL" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt new file mode 100644 index 0000000000..8f03e068b5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.common.CompletionResult +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.CompletionCallback +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.operations.CommandResponse +import com.tangem.operations.derivation.DeriveWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeyList +import com.tangem.tap.common.extensions.ByteArrayKey + +class DerivationTaskResponse( + val entries: Map +): CommandResponse + +class DerivationTask( + private val derivations: Map> +) : CardSessionRunnable { + + val response: MutableMap = mutableMapOf() + + override fun run(session: CardSession, callback: CompletionCallback) { + derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback) + } + + private fun derive( + keys: List, + index: Int, + session: CardSession, + callback: CompletionCallback + ) { + if (index == keys.count()) { + callback(CompletionResult.Success(DerivationTaskResponse(response.toMap()))) + return + } + + val key = keys[index] + val paths = derivations[key]!! + DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + response[key] = result.data + derive(keys = keys, index = index + 1, session = session, callback = callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 25e3d98836..89bdb12dd2 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -10,12 +10,12 @@ 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.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.DerivationTask import com.tangem.tap.domain.tasks.product.CreateWalletsTask import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey import com.tangem.tap.domain.tasks.product.ProductCommandProcessor @@ -170,16 +170,15 @@ private class CreateWalletTangemWallet : ProductCommandProcessor 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, + derivedKeys = result.data.entries, primaryCard = primaryCard ) ) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 40c7a00ded..506f5aa9c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -22,7 +22,6 @@ 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 import com.tangem.tap.common.extensions.toMapKey @@ -32,7 +31,9 @@ import com.tangem.tap.domain.TapWorkarounds import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain import com.tangem.tap.domain.TapWorkarounds.isExcluded import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease +import com.tangem.tap.domain.extensions.getPrimaryCurve import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.tasks.DerivationTask import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.twins.TwinsHelper import com.tangem.tap.preferencesStorage @@ -237,10 +238,8 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit ) { - val derivationPaths = collectDerivationPaths(card)?.distinct() - val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } - - if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) { + val derivations = collectDerivations(card) + if (derivations.isEmpty()) { callback( CompletionResult.Success( ScanResponse( @@ -254,15 +253,14 @@ private class ScanWalletProcessor( return } - DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result -> + DerivationTask(derivations).run(session) { result -> when (result) { is CompletionResult.Success -> { - val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data) val response = ScanResponse( card = card, productType = ProductType.Wallet, walletData = session.environment.walletData, - derivedKeys = derivedKeys, + derivedKeys = result.data.entries, primaryCard = primaryCard ) callback(CompletionResult.Success(response)) @@ -273,8 +271,8 @@ private class ScanWalletProcessor( } } - private fun collectDerivationPaths(card: Card): List? { - val currenciesRepository = currenciesRepository ?: return null + private fun getBlockchainsToDerive(card: Card): List { + val currenciesRepository = currenciesRepository ?: return emptyList() val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) val blockchainsToDerive = if (cardCurrencies == null) { @@ -293,10 +291,31 @@ private class ScanWalletProcessor( ) ) } + return blockchainsToDerive.distinct() + } - return blockchainsToDerive.toSet() - .filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true } - .mapNotNull { it.derivationPath() } + private fun collectDerivations(card: Card): Map> { + val blockchains = getBlockchainsToDerive(card) + val derivations = mutableMapOf>() + + blockchains.forEach { blockchain -> + val curve = blockchain.getPrimaryCurve() + + 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 } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt index bcf0bf5644..53acef169a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt @@ -7,7 +7,7 @@ import com.tangem.wallet.R sealed class CurrencyListItem { var isAdded: Boolean = false - var isLock: Boolean = false + var isLocked: Boolean = false data class TokenListItem(val token: Token) : CurrencyListItem() data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 92fe81b661..30db14cfbe 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -4,6 +4,9 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey +import com.tangem.tap.common.extensions.ByteArrayKey import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.common.redux.AppState @@ -13,6 +16,7 @@ import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapWorkarounds.isTestCard +import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope @@ -45,27 +49,8 @@ class TokensMiddleware { cardFirmware = scanResponse.card.firmwareVersion, isTestNet = isTestcard ) - val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() - if (scanResponse.supportsHdWallet()) { - currencies.forEach { - when (it) { - is CurrencyListItem.TitleListItem -> { - } - is CurrencyListItem.BlockchainListItem -> { - val firstCurve = it.blockchain.getSupportedCurves()?.firstOrNull() - if (firstCurve == EllipticCurve.Ed25519) { - it.isLock = true - } - } - is CurrencyListItem.TokenListItem -> { - val firstCurve = it.token.blockchain.getSupportedCurves()?.firstOrNull() - if (firstCurve == EllipticCurve.Ed25519) { - it.isLock = true - } - } - } - } - } + val currencies = + CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) } @@ -94,26 +79,33 @@ class TokensMiddleware { blockchains: List, tokens: List ) { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return - - val tokenBlockchains = tokens.map { it.blockchain } - val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct() - .mapNotNull { it.derivationPath() } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf() - val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath } - - val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } + val derivationDataList = listOfNotNull( + getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens), + getDerivations(EllipticCurve.Ed25519, scanResponse, blockchains, tokens) + ) + val derivations = derivationDataList.map { it.derivations }.toMap() scope.launch { - val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive) + val result = tangemSdkManager.derivePublicKeys( + scanResponse.card.cardId, + derivations + ) when (result) { is CompletionResult.Success -> { - val newDerivedKeys = result.data - alreadyDerivedKeys.addAll(newDerivedKeys) + val newDerivedKeys = result.data.entries + val updatedDerivedKeys = + mutableMapOf>() + + newDerivedKeys.forEach { entry -> + val derivationData = derivationDataList.find { + it.mapKeyOfWalletPublicKey == entry.key + } ?: return@forEach + updatedDerivedKeys[entry.key] = + derivationData.alreadyDerivedKeys + entry.value.toList() + } + val updatedScanResponse = scanResponse.copy( - derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList()) + derivedKeys = updatedDerivedKeys ) store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) submitAdd(blockchains, tokens) @@ -128,6 +120,37 @@ class TokensMiddleware { } } + private fun getDerivations( + curve: EllipticCurve, + scanResponse: ScanResponse, + blockchains: List, + tokens: List + ): DerivationData? { + val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null + + val tokenBlockchains = tokens.map { it.blockchain } + val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct() + .mapNotNull { it.derivationPath() } + + val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() + val alreadyDerivedKeys = + scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf() + val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath } + + val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } + return DerivationData( + derivations = mapKeyOfWalletPublicKey to toDerive, + alreadyDerivedKeys = alreadyDerivedKeys, + mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey + ) + } + + private class DerivationData( + val derivations: Pair>, + val alreadyDerivedKeys: List, + val mapKeyOfWalletPublicKey: ByteArrayKey + ) + private fun submitAdd(blockchains: List, tokens: List) { (blockchains.map { WalletAction.MultiWallet.AddBlockchain(it) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt index b3d0ecdc04..f0046353d1 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt @@ -196,7 +196,7 @@ class CurrenciesAdapter : ListAdapter } private fun modifyAddTokenButton(currency: CurrencyListItem) { - if (currency.isLock) { + if (currency.isLocked) { view.btn_add_token.setText(R.string.common_add) view.btn_add_token.isEnabled = false } else { From 3b0554ec5038a7da329e57aa7e82a5df50b26131 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Dec 2021 11:53:22 +0300 Subject: [PATCH 10/15] Updated on 2026-08-14 --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c8e2711c22..f9fe70e593 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,9 +77,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-51' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-106' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-106' + implementation 'com.tangem:blockchain:develop-52' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-114' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-114' // WebView implementation "androidx.browser:browser:1.3.0" From 4c4728814ec4fedab24f391f4b771811e26c35cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Dec 2021 16:12:30 +0300 Subject: [PATCH 11/15] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/domain/TangemSdkManager.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 449e1735ff..4c3f0c78d4 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -10,6 +10,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.SuccessResponse import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion +import com.tangem.common.core.CardIdDisplayFormat import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError @@ -137,7 +138,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse) { - tangemSdk.config.cardIdDisplayedNumbersCount = if (scanResponse.isTangemTwins()) 4 else null + tangemSdk.config.cardIdDisplayFormat = if (scanResponse.isTangemTwins()) { + CardIdDisplayFormat.LastLunh(4) + } else { + CardIdDisplayFormat.Full + } } companion object { From 204a386844589a097ac3ef8a51303dc575ee9ff8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Dec 2021 18:44:01 +0300 Subject: [PATCH 12/15] Updated on 2026-08-14 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f9fe70e593..ff53e7b789 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,8 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation 'com.tangem:blockchain:develop-52' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-114' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-114' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-115' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-115' // WebView implementation "androidx.browser:browser:1.3.0" From 3bfb8a13a780f8a6f3b05b3a0612d24ff4cfeef9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Dec 2021 15:52:31 +0300 Subject: [PATCH 13/15] Updated on 2026-08-14 --- app/src/main/assets/bsc_tokens.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/assets/bsc_tokens.json b/app/src/main/assets/bsc_tokens.json index 712d35c6dd..9859e13f27 100644 --- a/app/src/main/assets/bsc_tokens.json +++ b/app/src/main/assets/bsc_tokens.json @@ -778,5 +778,16 @@ "curve" : "secp256k1", "testnet" : false } + }, + { + "decimalCount" : 18, + "symbol" : "VOYRME", + "name" : "VOYR", + "contractAddress" : "0x33a887Cf76383be39DC51786e8f641c9D6665D84", + "blockchain" : { + "key" : "bsc", + "curve" : "secp256k1", + "testnet" : false + } } ] From 4a86d53346b81ca032c9774d1457da3f01138439 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Dec 2021 16:07:11 +0300 Subject: [PATCH 14/15] Updated on 2026-08-14 --- .../tangem/tap/features/feedback/FeedbackManager.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt index 0ae3d143a8..6da60e3904 100644 --- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt @@ -40,7 +40,7 @@ class FeedbackManager( val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null activity.sendEmail( email = getSupportEmail(), - subject = emailData.subject, message = emailData.joinTogether(infoHolder), + subject = emailData.subject, message = emailData.joinTogether(infoHolder, emailData !is ScanFailsEmail), file = fileLog, onFail = onFail ) @@ -216,8 +216,14 @@ interface EmailData { fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String - fun joinTogether(infoHolder: AdditionalEmailInfo): String { + fun joinTogether(infoHolder: AdditionalEmailInfo, allowToErasePhoneInfo: Boolean): String { + val allowToErasePhoneInfoDisclaimer = if (allowToErasePhoneInfo) { + "Following information is optional. You can erase it if you don’t want to share it.\n\n" + } else { + "" + } return "$mainMessage\n\n\n\n" + + allowToErasePhoneInfoDisclaimer + createOptionalMessage(infoHolder) } } From fd53fcd9a364686bba6a2bf3dc0b7663b5e77b34 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Dec 2021 11:04:00 +0300 Subject: [PATCH 15/15] Updated on 2026-08-14 --- .../com/tangem/tap/common/extensions/Picasso.kt | 4 ++++ app/src/main/res/drawable/ic_voyr.png | Bin 0 -> 43978 bytes 2 files changed, 4 insertions(+) create mode 100644 app/src/main/res/drawable/ic_voyr.png diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt index be71883ecc..60c8a1ec9d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt @@ -32,6 +32,9 @@ fun Picasso.loadCurrenciesIcon( token?.symbol == QCX -> { this.load(R.drawable.ic_qcx)?.into(imageView) } + token?.symbol == VOYR -> { + this.load(R.drawable.ic_voyr)?.into(imageView) + } url != null -> { if (token != null) { setTokenImage(imageView, textView, token) @@ -60,6 +63,7 @@ fun Picasso.loadCurrenciesIcon( } private const val QCX = "QCX" +private const val VOYR = "VOYRME" private fun setOfflineCurrencyImage( imageView: ImageView, diff --git a/app/src/main/res/drawable/ic_voyr.png b/app/src/main/res/drawable/ic_voyr.png new file mode 100644 index 0000000000000000000000000000000000000000..545990d041f45e9a2705ba8d20d8c25d422c71c2 GIT binary patch literal 43978 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 g`pddz5N8WO-BwNmy~wxUDLpD>lB; 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