diff --git a/app/build.gradle b/app/build.gradle index 0e5a1cae34..11fe6e6a2e 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-48' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-100' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-100' + implementation 'com.tangem:blockchain:develop-50' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-102' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-102' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt b/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt new file mode 100644 index 0000000000..376b56f399 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.common.extensions + +/** +[REDACTED_AUTHOR] + */ + +data class ByteArrayKey(val bytes: ByteArray) { + override fun equals(other: Any?): Boolean { + return this === other || other is ByteArrayKey && this.bytes.contentEquals(other.bytes) + } + + override fun hashCode(): Int = bytes.contentHashCode() +} + +fun ByteArray.toMapKey(): ByteArrayKey = ByteArrayKey(this) + +fun ByteArrayKey.bytes(): ByteArray = this.bytes \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index af62d98fff..f448916049 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -36,6 +36,7 @@ sealed class GlobalAction : Action { } data class ScanCard( + val shouldDeriveWC: Boolean, val onSuccess: ((ScanResponse) -> Unit)? = null, val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, 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 06cc349269..258131199b 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 @@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.tap.* import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -13,10 +14,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.moonpay.MoonpayService -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -91,7 +88,12 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.ScanCard -> { scope.launch { - val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId) + val result = tangemSdkManager.scanProduct( + FirebaseAnalyticsHandler, + currenciesRepository, + action.shouldDeriveWC, + action.messageResId + ) store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) withMainContext { when (result) { 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 695f3b83f0..7131feb7b7 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,6 @@ package com.tangem.tap.domain -import CreateProductWalletAndRescanTask +import CreateProductWalletTask import android.content.Context import com.tangem.Message import com.tangem.TangemSdk @@ -12,7 +12,10 @@ import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSessionRunnable 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 @@ -23,6 +26,7 @@ import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -33,18 +37,20 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanProduct( analyticsHandler: AnalyticsHandler, + currenciesRepository: CurrenciesRepository, + shouldDeriveWC: Boolean, messageRes: Int? = null, ): CompletionResult { analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) - return runTaskAsyncReturnOnMain(ScanProductTask(), null, message) - .also { sendScanFailuresToAnalytics(analyticsHandler, it) } + return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository, shouldDeriveWC), null, message) + .also { sendScanFailuresToAnalytics(analyticsHandler, it) } } suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult { return runTaskAsync( - CreateProductWalletAndRescanTask(scanResponse.productType), + CreateProductWalletTask(scanResponse.productType), scanResponse.card.cardId, Message(context.getString(R.string.initial_message_create_wallet_body)) ) @@ -66,6 +72,14 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body))) } + suspend fun derivePublicKeys( + cardId: String, + walletPublicKey: ByteArray, + derivationPaths: List + ): CompletionResult { + return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId) + } + suspend fun resetToFactorySettings(card: Card): CompletionResult { return runTaskAsyncReturnOnMain( ResetToFactorySettingsTask(), @@ -104,13 +118,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult = - withContext(Dispatchers.Main) { - suspendCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> - continuation.resume(result) - } + withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> + continuation.resume(result) } } + } private suspend fun runTaskAsyncReturnOnMain( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index 978ca5288f..1042913d5d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -3,6 +3,7 @@ package com.tangem.tap.domain import com.tangem.Message import com.tangem.TangemSdk import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult import com.tangem.tap.domain.tasks.SignHashTask import com.tangem.tap.domain.tasks.SignHashesTask @@ -15,13 +16,9 @@ class TangemSigner( private val signerCallback: (TangemSignerResponse) -> Unit, ) : TransactionSigner { - override suspend fun sign( - hash: ByteArray, - cardId: String, - walletPublicKey: ByteArray, - ): CompletionResult = - suspendCoroutine { continuation -> - val command = SignHashTask(hash, walletPublicKey) + override suspend fun sign(hash: ByteArray, cardId: String, publicKey: Wallet.PublicKey): CompletionResult { + return suspendCoroutine { continuation -> + val command = SignHashTask(hash, publicKey.seedKey) tangemSdk.startSessionWithRunnable( runnable = command, cardId = cardId, @@ -42,15 +39,11 @@ class TangemSigner( } } } + } - - override suspend fun sign( - hashes: List, - cardId: String, - walletPublicKey: ByteArray, - ): CompletionResult> = - suspendCoroutine { continuation -> - val task = SignHashesTask(hashes, walletPublicKey) + override suspend fun sign(hashes: List, cardId: String, publicKey: Wallet.PublicKey): CompletionResult> { + return suspendCoroutine { continuation -> + val task = SignHashesTask(hashes, publicKey.seedKey) tangemSdk.startSessionWithRunnable( runnable = task, cardId = cardId, @@ -71,6 +64,7 @@ class TangemSigner( } } } + } } data class TangemSignerResponse( diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 10f1a76a87..844eeb0bfa 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain import com.tangem.blockchain.common.* -import com.tangem.common.card.Card import com.tangem.common.services.Result import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler @@ -131,7 +130,7 @@ class TapWalletManager { store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) } if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, blockchain, primaryWalletManager) + loadMultiWalletData(data, blockchain, primaryWalletManager) } else { store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))) @@ -139,7 +138,7 @@ class TapWalletManager { } else { if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, blockchain, null) + loadMultiWalletData(data, blockchain, null) } } val moonPayStatus = store.state.globalState.moonpayStatus @@ -149,10 +148,10 @@ class TapWalletManager { } private fun loadMultiWalletData( - card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? + scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList() - val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) + val savedCurrencies = currenciesRepository.loadCardCurrencies(scanResponse.card.cardId) if (savedCurrencies == null) { if (primaryBlockchain != null && primaryWalletManager != null) { @@ -169,11 +168,11 @@ class TapWalletManager { CardCurrencies(blockchains = blockchains, tokens = emptyList()) )) val walletManagers = - walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList()) + walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList()) store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList())) } - store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory)) + store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse) store.dispatch(WalletAction.MultiWallet.FindTokensInUse) } else { val blockchains = savedCurrencies.blockchains.toList() @@ -182,10 +181,10 @@ class TapWalletManager { primaryWalletManager != null && primaryBlockchain != null ) { val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain } - walletManagerFactory.makeWalletManagersForApp(card, blockchainsWithoutPrimary) + walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary) .plus(primaryWalletManager) } else { - walletManagerFactory.makeWalletManagersForApp(card, blockchains) + walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains) } store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index aa85331ab2..b505619739 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -3,74 +3,70 @@ package com.tangem.tap.domain.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.hexToBytes +import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.tasks.product.ScanResponse fun WalletManagerFactory.makeWalletManagerForApp( - card: Card, - blockchain: Blockchain, + scanResponse: ScanResponse, blockchain: Blockchain ): WalletManager? { + val card = scanResponse.card + if (card.isTestCard && blockchain.getTestnetVersion() == null) return null val supportedCurves = blockchain.getSupportedCurves() ?: return null + val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) } - val wallet = selectWallet(wallets) - val publicKey = wallet?.publicKey ?: return null - val curveToUse = wallet.curve ?: return null - return if (card.isTestCard) { - blockchain.getTestnetVersion()?.let { - makeWalletManager(card.cardId, publicKey, it, curveToUse) + val wallet = selectWallet(wallets) ?: return null + + val environmentBlockchain = if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain + + val seedKey = wallet.extendedPublicKey + return when { + scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> { + makeTwinWalletManager( + card.cardId, + wallet.publicKey, scanResponse.secondTwinPublicKey.hexToBytes(), + environmentBlockchain, wallet.curve + ) + } + seedKey != null -> { + val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()] + val derivedKey = derivedKeys?.firstOrNull { it.derivationPath == blockchain.derivationPath() } + ?: return null + + makeWalletManager(card.cardId, environmentBlockchain, seedKey, derivedKey) + } + else -> { + makeWalletManager(card.cardId, environmentBlockchain, wallet.publicKey, wallet.curve) } - } else { - makeWalletManager(card.cardId, publicKey, blockchain, curveToUse) } } +fun WalletManagerFactory.makeWalletManagersForApp( + scanResponse: ScanResponse, blockchains: List, +): List { + val isTestCard = scanResponse.card.isTestCard + val filteredBlockchains = blockchains.mapNotNull { if (isTestCard) it.getTestnetVersion() else it } + return filteredBlockchains.mapNotNull { makeWalletManagerForApp(scanResponse, it) } +} + +fun WalletManagerFactory.makePrimaryWalletManager( + scanResponse: ScanResponse, +): WalletManager? { + val blockchain = if (scanResponse.card.isTestCard) { + scanResponse.getBlockchain().getTestnetVersion() ?: return null + } else { + scanResponse.getBlockchain() + } + return makeWalletManagerForApp(scanResponse, blockchain) +} + private fun selectWallet(wallets: List): CardWallet? { return when (wallets.size) { 0 -> null 1 -> wallets[0] else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0] } -} - -fun WalletManagerFactory.makeWalletManagersForApp( - card: Card, blockchains: List, -): List { - return if (card.isTestCard) { - blockchains.mapNotNull { blockchain -> - blockchain.getTestnetVersion()?.let { makeWalletManagerForApp(card, it) } - } - } else { - blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) } - } -} - -fun WalletManagerFactory.makePrimaryWalletManager( - data: ScanResponse, -): WalletManager? { - val card = data.card - val blockchain = if (card.isTestCard) { - data.getBlockchain().getTestnetVersion() - } else { - data.getBlockchain() - } - val supportedCurves = blockchain?.getSupportedCurves() ?: return null - - val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) } - val wallet = selectWallet(wallets) - val publicKey = wallet?.publicKey ?: return null - val curveToUse = wallet.curve - - return if (data.isTangemTwins() && data.secondTwinPublicKey != null) { - makeMultisigWalletManager( - cardId = card.cardId, - walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(), - blockchain = blockchain, curve = curveToUse - ) - } else { - makeWalletManager(card.cardId, publicKey, blockchain, curveToUse) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt index 6de34f651a..c580b5389a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt @@ -9,7 +9,7 @@ import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.wallet.CreateWalletCommand -@Deprecated("Use CreateProductWalletsResponse instead") +@Deprecated("Use CreateProductWalletTask instead") class CreateWalletsTask(curves: List? = null) : CardSessionRunnable { private val curves = curves ?: listOf( diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt similarity index 66% rename from app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt rename to app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 12ce0fdb66..347965e1f5 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -1,25 +1,29 @@ +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.operations.PreflightReadMode -import com.tangem.operations.PreflightReadTask +import com.tangem.operations.derivation.DeriveWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeyList import com.tangem.operations.wallet.CreateWalletCommand import com.tangem.operations.wallet.CreateWalletResponse +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.CreateProductWalletsTask +import com.tangem.tap.domain.tasks.product.CreateWalletsTask import com.tangem.tap.domain.tasks.product.ProductCommandProcessor import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets +import com.tangem.tap.store /** [REDACTED_AUTHOR] */ -class CreateProductWalletAndRescanTask( +class CreateProductWalletTask( private val type: ProductType, ) : CardSessionRunnable { @@ -28,33 +32,19 @@ class CreateProductWalletAndRescanTask( callback(CompletionResult.Failure(TangemSdkError.CardError())) return } - CreateProductWalletTask(card, type).run(session) { result -> - when (result) { - is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run( - session, - callback) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } -} -private class CreateProductWalletTask( - private val card: Card, - private val type: ProductType, -) : CardSessionRunnable { - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit, - ) { val commandProcessor = when (type) { ProductType.Note -> CreateWalletTangemNote() ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") ProductType.Wallet -> CreateWalletTangemWallet() else -> CreateWalletOtherCards() } - commandProcessor.proceed(card, session, callback) + commandProcessor.proceed(card, session) { + when (it) { + is CompletionResult.Success -> callback(CompletionResult.Success(session.environment.card!!)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error)) + } + } } } @@ -85,12 +75,12 @@ private class CreateWalletTangemNote : ProductCommandProcessor { + override fun proceed( card: Card, session: CardSession, @@ -98,16 +88,37 @@ private class CreateWalletTangemWallet : ProductCommandProcessor + CreateWalletsTask(curves).run(session) { result -> when (result) { - is CompletionResult.Success -> - callback(CompletionResult.Success(result.data.createWalletResponses[0])) + 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)) + } + } + } 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.onboardingManager ?: return + onboardingManager.scanResponse = onboardingManager.scanResponse.copy( + derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys) + ) +} + private class CreateWalletOtherCards : ProductCommandProcessor { override fun proceed( @@ -117,9 +128,9 @@ private class CreateWalletOtherCards : ProductCommandProcessor diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt similarity index 82% rename from app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt rename to app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt index fc00138a57..4eb426dc26 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt @@ -11,17 +11,17 @@ import com.tangem.operations.wallet.CreateWalletResponse /** [REDACTED_AUTHOR] */ -class CreateProductWalletsResponse( +class CreateWalletsResponse( val createWalletResponses: List ) : CommandResponse -class CreateProductWalletsTask( +class CreateWalletsTask( private val curves: List, -) : CardSessionRunnable { +) : CardSessionRunnable { private val createdWalletsResponses = mutableListOf() - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val curve = curves[createdWalletsResponses.size] createWallet(curve, session, callback) } @@ -29,7 +29,7 @@ class CreateProductWalletsTask( private fun createWallet( curve: EllipticCurve, session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit ) { CreateWalletCommand(curve).run(session) { result -> @@ -37,7 +37,7 @@ class CreateProductWalletsTask( is CompletionResult.Success -> { createdWalletsResponses.add(result.data) if (createdWalletsResponses.size == curves.size) { - callback(CompletionResult.Success(CreateProductWalletsResponse(createdWalletsResponses))) + callback(CompletionResult.Success(CreateWalletsResponse(createdWalletsResponses))) return@run } createWallet(curves[createdWalletsResponses.size], session, callback) 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 14f454c6b5..6a82fd222f 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 @@ -14,11 +14,16 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey import com.tangem.operations.CommandResponse import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask +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 import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.TapWorkarounds @@ -26,6 +31,7 @@ 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.getSingleWallet +import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.twins.TwinsHelper data class ScanResponse( @@ -33,6 +39,7 @@ data class ScanResponse( val productType: ProductType, val walletData: WalletData?, val secondTwinPublicKey: String? = null, + val derivedKeys: Map> = mapOf() ) : CommandResponse { fun getBlockchain(): Blockchain { @@ -60,7 +67,13 @@ data class ScanResponse( fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null } -class ScanProductTask(val card: Card? = null) : CardSessionRunnable { +typealias KeyWalletPublicKey = ByteArrayKey + +class ScanProductTask( + val card: Card? = null, + private val currenciesRepository: CurrenciesRepository?, + private val shouldDeriveWC: Boolean +) : CardSessionRunnable { override fun run( session: CardSession, @@ -80,7 +93,7 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable ScanNoteProcessor() card.isTangemTwins() -> ScanTwinProcessor() - TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor() + TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor(currenciesRepository, shouldDeriveWC) else -> ScanOtherCardsProcessor() } commandProcessor.proceed(card, session) { processorResult -> @@ -116,13 +129,60 @@ private class ScanNoteProcessor : ProductCommandProcessor { } } -private class ScanWalletProcessor : ProductCommandProcessor { +private class ScanWalletProcessor( + private val currenciesRepository: CurrenciesRepository?, + private val shouldDeriveWC: Boolean +) : ProductCommandProcessor { + override fun proceed( card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit ) { - callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData))) + val derivationPaths = collectDerivationPaths(card) + 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))) + return + } + + DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data) + val response = ScanResponse( + card, + ProductType.Wallet, + session.environment.walletData, + derivedKeys = derivedKeys + ) + callback(CompletionResult.Success(response)) + + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun collectDerivationPaths(card: Card): List? { + val currenciesRepository = currenciesRepository ?: return null + val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) + + val blockchainsToDerive = if (cardCurrencies == null) { + mutableListOf(Blockchain.Bitcoin, Blockchain.Ethereum) + } else { + val tokenBlockchains = cardCurrencies.tokens.map { it.blockchain } + (cardCurrencies.blockchains + tokenBlockchains).toMutableList() + } + + if (shouldDeriveWC) { + blockchainsToDerive.addAll(listOf(Blockchain.Ethereum, Blockchain.Binance, Blockchain.EthereumTestnet)) + } + + return blockchainsToDerive.toSet() + .filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true } + .mapNotNull { it.derivationPath() } } } @@ -174,7 +234,7 @@ private class ScanOtherCardsProcessor : ProductCommandProcessor { return } - CreateProductWalletsTask(curvesToCreate).run(session) { result -> + CreateWalletsTask(curvesToCreate).run(session) { result -> when (result) { is CompletionResult.Success -> { PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult -> diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt index 775d3748a7..9dd6e91960 100644 --- a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt @@ -35,7 +35,7 @@ class TopUpManager { store.dispatch( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = walletManager.wallet.publicKey, + walletPublicKey = walletManager.wallet.publicKey.seedKey, remainingSignatures = signResponse.remainingSignatures ) ) 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 c035973ac7..6fb4bb28c6 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,7 @@ class FinalizeTwinTask( PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult -> when (readResult) { is CompletionResult.Success -> - ScanProductTask(readResult.data).run(session, callback) + ScanProductTask(readResult.data, null, false).run(session, callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index f93217e447..e7b23953b5 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard -import com.tangem.common.extensions.hexToBytes import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction @@ -97,10 +96,9 @@ class WalletConnectManager { val activeData = sessions[session] ?: return removeSimilarSessions(activeData) + val accounts = listOf(Blockchain.Ethereum.makeAddresses(activeData.wallet.walletPublicKey).first().value) val approved = activeData.client.approveSession( - accounts = listOf(Blockchain.Ethereum.makeAddresses( - activeData.wallet.walletPublicKey.hexToBytes()).first().value - ), + accounts = accounts, chainId = activeData.wallet.chainId ) if (approved) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index e2ac447999..739a1c621d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -5,10 +5,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumUtils import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.* import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError @@ -36,9 +33,14 @@ class WalletConnectSdkHelper { type: WcTransactionType, ): WcTransactionData? { val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val publicKey = Wallet.PublicKey( + session.wallet.walletPublicKey, + session.wallet.derivedPublicKey, + session.wallet.derivationPath, + ) val walletManager = factory.makeEthereumWalletManager( session.wallet.cardId, - session.wallet.walletPublicKey.hexToBytes(), + publicKey, emptyList(), isTestNet = session.wallet.isTestNet ) ?: return null @@ -151,7 +153,7 @@ class WalletConnectSdkHelper { val command = SignHashCommand( hash = dataToSign.hash, - walletPublicKey = data.walletManager.wallet.publicKey + walletPublicKey = data.walletManager.wallet.publicKey.seedKey ) val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message()) return when (result) { @@ -208,7 +210,7 @@ class WalletConnectSdkHelper { } suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? { - val publicKey = wallet.walletPublicKey.hexToBytes() + val publicKey = wallet.walletPublicKey val command = SignHashCommand(hashToSign, publicKey) return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) { is CompletionResult.Success -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index dff6c35c72..cca999bf87 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -1,9 +1,7 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.* -import com.tangem.common.card.Card import com.tangem.common.extensions.guard -import com.tangem.common.extensions.toHexString import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard @@ -14,6 +12,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.isMultiwalletAllowed +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.wallet.R @@ -128,24 +127,32 @@ class WalletConnectMiddleware { } private fun handleScanCard(wcUri: String) { - store.dispatch(GlobalAction.ScanCard({ scanResponse -> + store.dispatch(GlobalAction.ScanCard(true, { scanResponse -> val card = scanResponse.card if (!card.isMultiwalletAllowed) { store.dispatchOnMain(WalletConnectAction.UnsupportedCard) return@ScanCard } - val walletManager = getWalletManager(card).guard { + val walletManager = getWalletManager(scanResponse).guard { store.dispatchOnMain(WalletConnectAction.UnsupportedCard) return@ScanCard } - val key = walletManager.wallet.publicKey + val wallet = walletManager.wallet + val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { + null + } else { + walletManager.wallet.publicKey.blockchainKey + } store.dispatchOnMain(WalletConnectAction.OpenSession( wcUri = wcUri, wallet = WalletForSession( - card.cardId, key.toHexString(), - isTestNet = card.isTestCard + card.cardId, + wallet.publicKey.seedKey, + derivedKey, + wallet.publicKey.derivationPath, + card.isTestCard ), )) }, { @@ -153,12 +160,13 @@ class WalletConnectMiddleware { }, R.string.wallet_connect_scan_card_message)) } - private fun getWalletManager(card: Card): WalletManager? { + private fun getWalletManager(scanResponse: ScanResponse): WalletManager? { + val card = scanResponse.card val factory = store.state.globalState.tapWalletManager.walletManagerFactory return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) { store.state.walletState.getWalletManager(Blockchain.Ethereum) - ?: factory.makeWalletManagerForApp(card, Blockchain.Ethereum) + ?: factory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) ?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) @@ -167,10 +175,9 @@ class WalletConnectMiddleware { if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains( Blockchain.Ethereum) == true ) { - factory.makeWalletManagerForApp(card, Blockchain.Ethereum) + factory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) } else { - factory.makeWalletManagerForApp(card, - Blockchain.Ethereum) + factory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) ?.also { currenciesRepository.saveAddedBlockchain(card.cardId, Blockchain.Ethereum) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 38aeaabc84..2757d8cea9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.details.redux.walletconnect +import com.squareup.moshi.JsonClass import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData @@ -21,9 +23,12 @@ data class WalletConnectSession( val peerMeta: WCPeerMeta, ) +@JsonClass(generateAdapter = true) data class WalletForSession( val cardId: String, - val walletPublicKey: String, + val walletPublicKey: ByteArray, + val derivedPublicKey: ByteArray?, + val derivationPath: DerivationPath?, val isTestNet: Boolean = false, ) { val chainId 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 0dc437eb3c..0ad3f5d218 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 @@ -28,7 +28,7 @@ class HomeMiddleware { val handler = homeMiddleware const val CARD_SHOP_URI = - "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" + "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" } } @@ -61,7 +61,7 @@ private fun handleReadCard() { store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } else { changeButtonState(ButtonState.PROGRESS) - store.dispatch(GlobalAction.ScanCard({ scanResponse -> + store.dispatch(GlobalAction.ScanCard(false, { scanResponse -> store.state.globalState.tapWalletManager.updateConfigManager(scanResponse) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) 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 84979eafda..d2c733e206 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 @@ -71,7 +71,8 @@ private fun handleWalletAction(action: Action) { withMainContext { when (result) { is CompletionResult.Success -> { - val updatedResponse = scanResponse.copy(card = result.data) + //here we must use updated scanResponse after createWallet & derivation + val updatedResponse = globalState.onboardingManager.scanResponse.copy(card = result.data) onboardingManager.scanResponse = updatedResponse onboardingManager.activationStarted(updatedResponse.card.cardId) store.dispatch(OnboardingWalletAction.ProceedBackup) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 6701ac636d..ac8bfea3dd 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -158,7 +158,7 @@ private fun sendTransaction( store.dispatch( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = walletManager.wallet.publicKey, + walletPublicKey = walletManager.wallet.publicKey.seedKey, remainingSignatures = signResponse.remainingSignatures ) ) 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 98efd84540..bcf0bf5644 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 @@ -6,8 +6,12 @@ import com.tangem.blockchain.common.Token import com.tangem.wallet.R sealed class CurrencyListItem { + var isAdded: Boolean = false + var isLock: Boolean = false + data class TokenListItem(val token: Token) : CurrencyListItem() data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem() + data class TitleListItem( @StringRes val titleResId: Int, var isContentShown: Boolean = true, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt index 1be9e7c701..27db550708 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt @@ -7,6 +7,8 @@ import org.rekotlin.Action sealed class TokensAction : Action { + object ResetState : TokensAction() + object LoadCurrencies : TokensAction() { data class Success(val currencies: List) : TokensAction() } @@ -19,4 +21,9 @@ sealed class TokensAction : Action { data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction() + sealed class TokensList : TokensAction() { + data class AddBlockchain(val blockchain: Blockchain) : TokensList() + data class AddToken(val token: Token) : TokensList() + object SaveChanges : TokensList() + } } \ No newline at end of file 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 6ecff3f595..42311b8b1a 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 @@ -1,9 +1,22 @@ package com.tangem.tap.features.tokens.redux +import com.tangem.common.CompletionResult +import com.tangem.common.card.EllipticCurve +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.NavigationAction 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.features.wallet.redux.WalletAction +import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.rekotlin.Middleware class TokensMiddleware { @@ -12,22 +25,92 @@ class TokensMiddleware { { next -> { action -> when (action) { - is TokensAction.LoadCurrencies -> { - val card = state()?.globalState?.scanResponse?.card - val isTestcard = card?.isTestCard ?: false - val tokens = currenciesRepository.getPopularTokens(isTestcard) - val blockchains = currenciesRepository.getBlockchains( - cardFirmware = card?.firmwareVersion, - isTestNet = isTestcard - ) - val currencies = CurrencyListItem.createListOfCurrencies( - blockchains, tokens - ) - store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) - } + is TokensAction.LoadCurrencies -> handleLoadCurrencies(action) + is TokensAction.TokensList.SaveChanges -> handleSaveChanges(action) } next(action) } } } + + private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) { + val scanResponse = store.state.globalState.scanResponse ?: return + val isTestcard = scanResponse.card.isTestCard + + val tokens = currenciesRepository.getPopularTokens(isTestcard) + val blockchains = currenciesRepository.getBlockchains( + cardFirmware = scanResponse.card.firmwareVersion, + isTestNet = isTestcard + ) + val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() + if (scanResponse.isTangemWallet()) { + 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 + } + } + } + } + } + store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) + } + + private fun handleSaveChanges(action: TokensAction.TokensList.SaveChanges) { + val scanResponse = store.state.globalState.scanResponse ?: return + val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return + + val candidatesToAdd = store.state.tokensState.candidateToAdd + if (candidatesToAdd.isEmpty()) return + + val blockchains = candidatesToAdd.filterIsInstance() + .map { it.blockchain } + val tokens = candidatesToAdd.filterIsInstance() + .map { it.token } + if (blockchains.isEmpty() && tokens.isEmpty()) return + + val derivationPathsCandidates = (blockchains + tokens.map { it.blockchain }) + .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) } + + scope.launch { + val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive) + when (result) { + is CompletionResult.Success -> { + val newDerivedKeys = result.data + alreadyDerivedKeys.addAll(newDerivedKeys) + val updatedScanResponse = scanResponse.copy( + derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList()) + ) + + store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + + val actions = blockchains.map { WalletAction.MultiWallet.AddBlockchain(it) } + tokens.map { + WalletAction.MultiWallet.AddToken(it) + } + actions.forEach { store.dispatch(it) } + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatch(NavigationAction.PopBackTo()) + } + is CompletionResult.Failure -> { + store.dispatchErrorNotification(TapError.CustomError("Error adding tokens")) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt index f221424d67..f6ba05bc49 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt @@ -17,6 +17,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { val tokensState = state.tokensState return when (action) { + is TokensAction.ResetState -> TokensState() is TokensAction.LoadCurrencies.Success -> { tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies) } @@ -33,15 +34,25 @@ private fun internalReduce(action: Action, state: AppState): TokensState { if (action.isShown) { val shownCurrencies = tokensState.shownCurrencies .removeTokensForBlockchain(action.blockchain) - shownCurrencies.toggleHeaderContentShownValue(action.blockchain) + shownCurrencies.toggleHeaderContentShownValue(action.blockchain) tokensState.copy(shownCurrencies = shownCurrencies) } else { val shownCurrencies = tokensState.shownCurrencies .addTokensForBlockchain(action.blockchain, tokensState.currencies) - shownCurrencies.toggleHeaderContentShownValue(action.blockchain) + shownCurrencies.toggleHeaderContentShownValue(action.blockchain) tokensState.copy(shownCurrencies = shownCurrencies) } } + is TokensAction.TokensList.AddBlockchain -> { + val candidates = tokensState.candidateToAdd.toMutableSet() + candidates.add(CurrencyListItem.BlockchainListItem(action.blockchain)) + tokensState.copy(candidateToAdd = candidates.toList()) + } + is TokensAction.TokensList.AddToken -> { + val candidates = tokensState.candidateToAdd.toMutableSet() + candidates.add(CurrencyListItem.TokenListItem(action.token)) + tokensState.copy(candidateToAdd = candidates.toList()) + } else -> tokensState } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt index ef30a92229..d0c204ee45 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt @@ -10,6 +10,7 @@ data class TokensState( val addedCurrencies: CardCurrencies? = null, val currencies: List = emptyList(), val shownCurrencies: List = emptyList(), + val candidateToAdd: List = emptyList(), ) : StateType data class TokenWithAmount( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index b5d09b7074..e29365e728 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -12,6 +12,7 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter import com.tangem.tap.mainScope @@ -57,30 +58,31 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + btn_tokens_save_changes.setOnClickListener { store.dispatch(TokensAction.TokensList.SaveChanges)} setupPopularTokensRecyclerView() } - private fun setupPopularTokensRecyclerView() { viewAdapter = CurrenciesAdapter() rv_popular_tokens.layoutManager = LinearLayoutManager(context) rv_popular_tokens.adapter = viewAdapter } - override fun newState(state: TokensState) { if (activity == null) return + + btn_tokens_save_changes.isEnabled = state.candidateToAdd.isNotEmpty() + viewAdapter.addedCurrencies = state.addedCurrencies viewAdapter.submitUnfilteredList(state.shownCurrencies) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.menu_search -> { - true - } + R.id.menu_search -> true else -> super.onOptionsItemSelected(item) } } @@ -101,6 +103,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), .launchIn(mainScope) return super.onCreateOptionsMenu(menu, inflater); } + + override fun onDestroy() { + store.dispatch(TokensAction.ResetState) + super.onDestroy() + } } fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { 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 85a8462b81..5935014bf4 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 @@ -14,7 +14,6 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.domain.tokens.CardCurrencies import com.tangem.tap.features.tokens.redux.CurrencyListItem import com.tangem.tap.features.tokens.redux.TokensAction -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.item_currency_subtitle.view.* @@ -86,15 +85,15 @@ class CurrenciesAdapter : ListAdapter is CurrencyListItem.BlockchainListItem -> { element.blockchain.currency.toLowerCase(Locale.US) .contains(queryNormalized) || - element.blockchain.fullName.toLowerCase(Locale.US) - .contains(queryNormalized) + element.blockchain.fullName.toLowerCase(Locale.US) + .contains(queryNormalized) } is CurrencyListItem.TokenListItem -> { element.token.name.toLowerCase(Locale.US) .contains(queryNormalized) || - element.token.symbol.toLowerCase(Locale.US) - .contains(queryNormalized) + element.token.symbol.toLowerCase(Locale.US) + .contains(queryNormalized) } is CurrencyListItem.TitleListItem -> true } @@ -105,17 +104,16 @@ class CurrenciesAdapter : ListAdapter submitList(list) } - class CurrenciesViewHolder(val view: View) : - RecyclerView.ViewHolder(view) { + class CurrenciesViewHolder(val view: View) : RecyclerView.ViewHolder(view) { + fun bind(currency: CurrencyListItem, addedCurrencies: CardCurrencies?) { when (currency) { is CurrencyListItem.BlockchainListItem -> { val blockchain = currency.blockchain view.tv_currency_name.text = blockchain.fullName view.tv_currency_symbol.text = blockchain.currency - val isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true - view.btn_add_token.show(!isAdded) - view.btn_token_added.show(isAdded) + currency.isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true + modifyAddTokenButton(currency) Picasso.get().loadCurrenciesIcon( imageView = view.iv_currency, @@ -124,9 +122,9 @@ class CurrenciesAdapter : ListAdapter ) view.btn_add_token.setOnClickListener { - store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain)) - view.btn_add_token.hide() - view.btn_token_added.show() + store.dispatch(TokensAction.TokensList.AddBlockchain(blockchain)) + currency.isAdded = !currency.isAdded + modifyAddTokenButton(currency) } } is CurrencyListItem.TokenListItem -> { @@ -134,13 +132,11 @@ class CurrenciesAdapter : ListAdapter view.tv_currency_name.text = token.name view.tv_currency_symbol.text = token.symbol - val isAdded = addedCurrencies?.tokens + currency.isAdded = addedCurrencies?.tokens ?.any { it.symbol == token.symbol && it.contractAddress == token.contractAddress } == true - - view.btn_add_token.show(!isAdded) - view.btn_token_added.show(isAdded) + modifyAddTokenButton(currency) Picasso.get().loadCurrenciesIcon( imageView = view.iv_currency, @@ -148,13 +144,23 @@ class CurrenciesAdapter : ListAdapter token = token, blockchain = token.blockchain ) view.btn_add_token.setOnClickListener { - store.dispatch(WalletAction.MultiWallet.AddToken(token)) - view.btn_add_token.hide() - view.btn_token_added.show() + store.dispatch(TokensAction.TokensList.AddToken(token)) + currency.isAdded = !currency.isAdded + modifyAddTokenButton(currency) } } } } + + private fun modifyAddTokenButton(currency: CurrencyListItem) { + if (currency.isLock) { + view.btn_add_token.isEnabled = false + } else { + val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add + view.btn_add_token.setText(text) + view.btn_add_token.isEnabled = !currency.isAdded + } + } } class TitleViewHolder(val view: View) : diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 149550727b..1081a3303c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -50,8 +50,7 @@ sealed class WalletAction : Action { data class AddToken(val token: Token) : MultiWallet() data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet() object FindTokensInUse : MultiWallet() - data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) : - MultiWallet() + object FindBlockchainsInUse : MultiWallet() data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet() data class SelectWallet(val walletData: WalletData?) : MultiWallet() 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 8397f148e0..7d93d65312 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 @@ -24,10 +24,12 @@ class MultiWalletMiddleware { fun handle( action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?, ) { + val globalState = globalState ?: return + val tapWalletManager = globalState.tapWalletManager + when (action) { is WalletAction.MultiWallet.AddWalletManagers -> { - store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) + globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers) } is WalletAction.MultiWallet.SelectWallet -> { if (action.walletData != null) { @@ -35,7 +37,7 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.AddToken -> { - globalState?.scanResponse?.card?.cardId?.let { + globalState.scanResponse?.card?.cardId?.let { currenciesRepository.saveAddedToken(it, action.token) } addToken(action.token, walletState, globalState) @@ -44,35 +46,33 @@ class MultiWalletMiddleware { addTokens(action.tokens, walletState, globalState) } is WalletAction.MultiWallet.AddBlockchain -> { - globalState?.scanResponse?.card?.let { card -> - currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain) + globalState.scanResponse?.let { + currenciesRepository.saveAddedBlockchain(it.card.cardId, action.blockchain) if (walletState?.blockchains?.contains(action.blockchain) != true) { - globalState.tapWalletManager.walletManagerFactory - .makeWalletManagerForApp(card, action.blockchain)?.let { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it)) + tapWalletManager.walletManagerFactory + .makeWalletManagerForApp(it, action.blockchain)?.let { walletManager -> + store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) } } } store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain))) store.dispatch(WalletAction.LoadWallet( - moonpayStatus = globalState?.moonpayStatus, + moonpayStatus = globalState.moonpayStatus, blockchain = action.blockchain - ) + ) ) } is WalletAction.MultiWallet.SaveCurrencies -> { - val cardId = globalState?.scanResponse?.card?.cardId - cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) } + globalState.scanResponse?.card?.cardId?.let { + currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) + } } is WalletAction.MultiWallet.RemoveWallet -> { - val cardId = globalState?.scanResponse?.card?.cardId + val cardId = globalState.scanResponse?.card?.cardId when (val currency = action.walletData.currency) { is Currency.Blockchain -> { cardId?.let { - currenciesRepository.removeBlockchain( - it, - currency.blockchain - ) + currenciesRepository.removeBlockchain(it, currency.blockchain) } } is Currency.Token -> { @@ -83,11 +83,13 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindBlockchainsInUse -> { - val cardFirmware = globalState?.scanResponse?.card?.firmwareVersion + val scanResponse = globalState.scanResponse ?: return + + val cardFirmware = scanResponse.card.firmwareVersion val blockchains = currenciesRepository.getBlockchains(cardFirmware) .filterNot { walletState?.blockchains?.contains(it) == true } val walletManagers = - action.factory.makeWalletManagersForApp(action.card, blockchains) + tapWalletManager.walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains) scope.launch { walletManagers.map { walletManager -> @@ -120,12 +122,13 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindTokensInUse -> { - val card = globalState?.scanResponse?.card ?: return + val scanResponse = globalState.scanResponse ?: return + + val walletFactory = tapWalletManager.walletManagerFactory + val card = scanResponse.card val walletManager = walletState?.getWalletManager(Blockchain.Ethereum) - ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = Blockchain.Ethereum - ) + ?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) + val tokenFinder = walletManager as TokenFinder scope.launch { val result = tokenFinder.findTokens() @@ -159,19 +162,18 @@ class MultiWalletMiddleware { } private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) { - val card = globalState?.scanResponse?.card ?: return + val scanResponse = globalState?.scanResponse ?: return + val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = token.blockchain + scanResponse, + token.blockchain )?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } - store.dispatch( - WalletAction.LoadFiatRate(currency = Currency.Token(token)) - ) + store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token(token))) scope.launch { when (val result = walletManager?.addToken(token)) { @@ -189,12 +191,13 @@ class MultiWalletMiddleware { walletState: WalletState?, globalState: GlobalState?, ) { - val card = globalState?.scanResponse?.card ?: return + val scanResponse = globalState?.scanResponse ?: return + val tokensWithManagers = tokens.map { token -> val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = token.blockchain + scanResponse, + token.blockchain )?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) diff --git a/app/src/main/java/com/tangem/tap/network/Retrofit.kt b/app/src/main/java/com/tangem/tap/network/Retrofit.kt index dd16923173..a2188e6d64 100644 --- a/app/src/main/java/com/tangem/tap/network/Retrofit.kt +++ b/app/src/main/java/com/tangem/tap/network/Retrofit.kt @@ -4,6 +4,7 @@ import com.squareup.moshi.FromJson import com.squareup.moshi.Moshi import com.squareup.moshi.ToJson import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.common.json.TangemSdkAdapter import com.tangem.wallet.BuildConfig import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -41,7 +42,10 @@ fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.cre fun createMoshi(): Moshi = Moshi.Builder() .add(BigDecimalAdapter) - .add(KotlinJsonAdapterFactory()).build() + .add(KotlinJsonAdapterFactory()) + .add(TangemSdkAdapter.DerivationPathAdapter()) + .add(TangemSdkAdapter.DerivationNodeAdapter()) + .build() private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor { val logging = HttpLoggingInterceptor() diff --git a/app/src/main/res/layout/fragment_add_tokens.xml b/app/src/main/res/layout/fragment_add_tokens.xml index 792f48fc05..4d25c3cf14 100644 --- a/app/src/main/res/layout/fragment_add_tokens.xml +++ b/app/src/main/res/layout/fragment_add_tokens.xml @@ -13,8 +13,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/backgroundLightGray" - android:fitsSystemWindows="true" - > + android:fitsSystemWindows="true"> + + + diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml index 976047b14b..dc388c1dfa 100644 --- a/app/src/main/res/layout/item_popular_token.xml +++ b/app/src/main/res/layout/item_popular_token.xml @@ -74,33 +74,17 @@ app:layout_constraintVertical_bias="0" tools:text="NODLE" /> - - - @@ -110,7 +94,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="start" - app:constraint_referenced_ids="btn_add_token, btn_token_added" /> - + app:constraint_referenced_ids="btn_add_token" /> \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3bf6353760..93cbbcca62 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -10,7 +10,7 @@ @color/accent #661ACE80 @color/darkGray6 - #661C1C1E + #6A6A6A #5AC461 #C4291C