diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b460cf581a..4422e037f9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ android:name="android.hardware.nfc" android:required="true" /> - @@ -131,18 +130,6 @@ - - - - - - - - R.drawable.ic_dogecoin_round Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round Blockchain.Gnosis -> R.drawable.ic_gnosis_round + Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round + Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round + Blockchain.Kusama -> R.drawable.ic_kusama_round Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism Blockchain.Dash -> R.drawable.ic_dash else -> R.drawable.ic_tangem_logo @@ -58,7 +61,10 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color + Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_no_color + Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_no_color Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_no_color + Blockchain.Kusama -> R.drawable.ic_kusama_no_color Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color Blockchain.Dash -> R.drawable.ic_dash_no_color else -> R.drawable.ic_tangem_logo diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 70912f24a6..97c8a9e41f 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -7,16 +7,19 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.address.Address +import com.tangem.common.card.CardWallet import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.stripZeroPlainString class AdditionalFeedbackInfo { class EmailWalletInfo( var blockchain: Blockchain = Blockchain.Unknown, - var address: String = "", - var explorerLink: String = "", - var host: String = "", var derivationPath: String = "", + var outputsCount: String? = null, + var host: String = "", + var addresses: String = "", + var explorerLink: String = "", ) var appVersion: String = "" @@ -48,23 +51,14 @@ class AdditionalFeedbackInfo { cardBlockchain = data.walletData?.blockchain ?: "" cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name - signedHashesCount = data.card.wallets - .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" } + signedHashesCount = formatSignedHashes(data.card.wallets) } fun setWalletsInfo(walletManagers: List) { walletsInfo.clear() tokens.clear() walletManagers.forEach { manager -> - walletsInfo.add( - EmailWalletInfo( - blockchain = manager.wallet.blockchain, - address = getAddress(manager.wallet), - explorerLink = getExploreUri(manager.wallet), - host = manager.currentHost, - derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: "" - ) - ) + walletsInfo.add(createEmailWalletInfo(manager)) if (manager.cardTokens.isNotEmpty()) { tokens[manager.wallet.blockchain] = manager.cardTokens } @@ -72,45 +66,55 @@ class AdditionalFeedbackInfo { } fun updateOnSendError( - wallet: Wallet, - host: String, + walletManager: WalletManager, amountToSend: Amount, feeAmount: Amount, destinationAddress: String, ) { - onSendErrorWalletInfo = EmailWalletInfo( - blockchain = wallet.blockchain, - address = getAddress(wallet), - explorerLink = getExploreUri(wallet), - host = host, - derivationPath = wallet.publicKey.derivationPath?.rawPath ?: "" - ) - + onSendErrorWalletInfo = createEmailWalletInfo(walletManager) this.destinationAddress = destinationAddress amount = amountToSend.value?.stripZeroPlainString() ?: "0" fee = feeAmount.value?.stripZeroPlainString() ?: "0" token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else "" } - private fun getAddress(wallet: Wallet): String { - return if (wallet.addresses.size == 1) { - wallet.address - } else { - val addresses = wallet.addresses.joinToString(", ") { - "${it.type.javaClass.simpleName} - ${it.value}" - } - "Multiple address: $addresses" + private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo { + return EmailWalletInfo( + blockchain = walletManager.wallet.blockchain, + derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "", + outputsCount = walletManager.outputsCount?.toString(), + host = walletManager.currentHost, + addresses = formatAddresses(walletManager.wallet), + explorerLink = formatExploreUrls(walletManager.wallet), + ) + } + + private fun formatSignedHashes(wallets: List): String { + return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" } + } + + private fun formatAddresses(wallet: Wallet): String { + return wallet.formatAddressWith("Multiple address:") { + "${it.name} - ${it.value}" } } - private fun getExploreUri(wallet: Wallet): String { - return if (wallet.addresses.size == 1) { - wallet.getExploreUrl(wallet.address) - } else { - val links = wallet.addresses.joinToString(", ") { - "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}" - } - "Multiple explorers links: $links" + private fun formatExploreUrls(wallet: Wallet): String { + return wallet.formatAddressWith("Multiple explorers links:") { + "${it.name} - ${wallet.getExploreUrl(it.value)}" } } + + private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String { + return if (addresses.size == 1) { + getExploreUrl(address) + } else { + addresses.map { mapAddress(it) }.toMutableList() + .apply { add(0, with) } + .joinToString("\n") + } + } + + private val Address.name: String + get() = type.javaClass.simpleName } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt index a3a9ee5575..46cf302fee 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt @@ -3,7 +3,7 @@ package com.tangem.tap.common.feedback import com.tangem.tap.common.extensions.breakLine class FeedbackDataBuilder( - private val infoHolder: AdditionalFeedbackInfo + private val infoHolder: AdditionalFeedbackInfo, ) { val builder = StringBuilder() @@ -21,7 +21,7 @@ class FeedbackDataBuilder( builder.appendKeyValue("Card ID", infoHolder.cardId) builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain) - builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount) + builder.appendKeyValue("", infoHolder.signedHashesCount) return this } @@ -29,20 +29,22 @@ class FeedbackDataBuilder( infoHolder.walletsInfo.forEach { builder.appendDelimiter() builder.appendKeyValue("Blockchain", it.blockchain.fullName) - builder.appendKeyValue("Host", it.host) - builder.appendKeyValue("Wallet address", it.address) builder.appendKeyValue("Derivation path", it.derivationPath) - builder.appendKeyValue("Explorer link", it.explorerLink) + builder.appendKeyValue("Outputs count", it.outputsCount) infoHolder.tokens[it.blockchain]?.let { tokens -> builder.append("Tokens:") breakLine() tokens.forEach { token -> - builder.appendKeyValue("Name", token.name) builder.appendKeyValue("ID", token.id ?: "[custom token]") + builder.appendKeyValue("Name", token.name) builder.appendKeyValue("Contract address", token.contractAddress) } } + + builder.appendKeyValue("Host", it.host) + builder.appendKeyValue("Wallet address", it.addresses) + builder.appendKeyValue("Explorer link", it.explorerLink) } return this } @@ -55,7 +57,7 @@ class FeedbackDataBuilder( builder.appendKeyValue("Token", infoHolder.token) builder.appendKeyValue("Error", error) builder.appendDelimiter() - builder.appendKeyValue("Source address", walletInfo.address) + builder.appendKeyValue("Source address", walletInfo.addresses) builder.appendKeyValue("Destination address", infoHolder.destinationAddress) builder.appendKeyValue("Amount", infoHolder.amount) builder.appendKeyValue("Fee", infoHolder.fee) @@ -72,8 +74,10 @@ class FeedbackDataBuilder( fun build(): String = builder.toString() } -private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder { - return if (value.isNotBlank()) this.append("$key: $value\n") else this +private fun StringBuilder.appendKeyValue(key: String, value: String?): StringBuilder = when { + value.isNullOrBlank() -> this + key.isBlank() -> this.append("$value\n") + else -> this.append("$key: $value\n") } private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n") \ No newline at end of file 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 e3b1dfb9af..8173f76aae 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 @@ -12,7 +12,6 @@ import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState -import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.WalletAction @@ -25,6 +24,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -141,7 +141,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di scope.launch { val result = tangemSdkManager.scanProduct( store.state.globalState.analyticsHandlers, - currenciesRepository, + userTokensRepository, action.additionalBlockchainsToDerive, action.messageResId, ) 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 4833079ad0..34e58f0ba7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -32,7 +32,7 @@ import com.tangem.tap.common.analytics.AnalyticsParam 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.tokens.CurrenciesRepository +import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.features.demo.DemoHelper import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers @@ -45,7 +45,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanProduct( analyticsHandler: AnalyticsHandler?, - currenciesRepository: CurrenciesRepository, + userTokensRepository: UserTokensRepository, additionalBlockchainsToDerive: Collection? = null, messageRes: Int? = null, ): CompletionResult { @@ -53,8 +53,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( - runnable = ScanProductTask(null, currenciesRepository, additionalBlockchainsToDerive), - cardId = null, initialMessage = message + runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive), + cardId = null, initialMessage = message, ).also { sendScanResultsToAnalytics(analyticsHandler, it) } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 8219a52319..6e73a11fba 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -2,6 +2,7 @@ package com.tangem.tap.domain import androidx.annotation.StringRes import com.tangem.common.core.TangemError +import com.tangem.network.api.tangemTech.TangemTechError import com.tangem.wallet.R interface TapErrors @@ -41,7 +42,7 @@ sealed class TapError( data class UnsupportedState( val stateError: String, - val customMessage: String = "Unsupported state:" + val customMessage: String = "Unsupported state:", ) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError")) sealed class WalletManager { @@ -52,9 +53,14 @@ sealed class TapError( object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) } + sealed class WalletConnect { + object UnsupportedDapp : TapError(R.string.wallet_connect_error_unsupported_dapp) + object UnsupportedLink : TapError(R.string.wallet_connect_error_failed_to_connect) + } + data class ValidateTransactionErrors( override val errorList: List, - override val builder: (List) -> String + override val builder: (List) -> String, ) : TapError(-1), MultiMessageError } @@ -67,7 +73,6 @@ sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemE object ScanPrimaryCard : TapSdkError(R.string.saltpay_backup_warning) } - fun TapErrors.assembleErrors(): MutableList?>> { val idList = mutableListOf?>>() when (this) { @@ -75,4 +80,13 @@ fun TapErrors.assembleErrors(): MutableList?>> { is TapError -> idList.add(Pair(this.messageResource, this.args)) } return idList -} \ No newline at end of file +} + +fun TangemTechError.toTapError(): TapError { + return when (this.code) { + 404 -> NoDataError(this.description) + else -> TapError.CustomError(customMessage = this.description) + } +} + +class NoDataError(message: String) : TapError.CustomError(customMessage = message) 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 507e6023d4..eae80ac060 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -16,7 +16,6 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.extensions.makePrimaryWalletManager @@ -24,17 +23,17 @@ import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store +import com.tangem.tap.userTokensRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext - class TapWalletManager { val walletManagerFactory: WalletManagerFactory - by lazy { WalletManagerFactory(blockchainSdkConfig) } - + by lazy { WalletManagerFactory(blockchainSdkConfig) } val rates: RatesRepository = RatesRepository() private val blockchainSdkConfig by lazy { @@ -65,16 +64,16 @@ class TapWalletManager { WalletAction.LoadWallet.NoAccount( walletManager.wallet, blockchainNetwork, - (result.error as TapError.WalletManager.NoAccountError).customMessage - ) + (result.error as TapError.WalletManager.NoAccountError).customMessage, + ), ) } else -> { dispatchOnMain( WalletAction.LoadWallet.Failure( walletManager.wallet, - result.error.localizedMessage - ) + result.error.localizedMessage, + ), ) } } @@ -138,24 +137,23 @@ class TapWalletManager { dispatchOnMain(WalletAction.LoadFiatRate()) } - private suspend fun loadMultiWalletData( - scanResponse: ScanResponse - ) { - val savedCurrencies = currenciesRepository.loadSavedCurrencies( - scanResponse.card.cardId, scanResponse.card.settings.isHDWalletAllowed - ) - if (savedCurrencies.isEmpty()) return + private suspend fun loadMultiWalletData(scanResponse: ScanResponse) { + loadUserCurrencies(scanResponse, walletManagerFactory) + } - val walletManagers = - walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies) - dispatchOnMain( - WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers), - ) - savedCurrencies.map { + private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) { + blockchainNetworks.map { if (it.tokens.isNotEmpty()) { - dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it)) + WalletAction.MultiWallet.AddTokens(it.tokens, it, false) } } + val missingDerivations = blockchainNetworks + .filter { + it.derivationPath != null && !scanResponse.hasDerivation(it.blockchain, it.derivationPath) + } + if (missingDerivations.isNotEmpty()) { + store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations)) + } } private suspend fun loadSingleWalletData(data: ScanResponse) { @@ -163,7 +161,6 @@ class TapWalletManager { val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { - val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager) val primaryToken = data.getPrimaryToken() dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) @@ -173,14 +170,43 @@ class TapWalletManager { } dispatchOnMain( WalletAction.MultiWallet.AddBlockchains( - listOf(blockchainNetwork), - listOf(primaryWalletManager), + blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), + walletManagers = listOf(primaryWalletManager), + save = false, ), ) } } + private suspend fun loadUserCurrencies(scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory) { + val userTokens = userTokensRepository.getUserTokens(scanResponse.card) + withMainContext { + val blockchainNetworks = userTokens.toBlockchainNetworks() + val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, userTokens) + store.dispatch( + WalletAction.MultiWallet.AddBlockchains( + blockchains = blockchainNetworks, + walletManagers = walletManagers, + save = false, + ), + ) + + blockchainNetworks.filter { it.tokens.isNotEmpty() } + .map { + store.dispatch( + WalletAction.MultiWallet.AddTokens( + tokens = it.tokens, + blockchain = it, + save = false, + ), + ) + } + checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) + } + } + suspend fun reloadData(data: ScanResponse) { + loadUserCurrencies(data, walletManagerFactory) withContext(Dispatchers.Main) { getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { store.dispatch(it) 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 654eb2185c..175fb62544 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 @@ -103,9 +103,11 @@ fun WalletManagerFactory.makeWalletManagerForApp( } fun WalletManagerFactory.makeWalletManagersForApp( - scanResponse: ScanResponse, blockchains: List, + scanResponse: ScanResponse, blockchains: List, ): List { - return blockchains.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) } + return blockchains + .filter { it.isBlockchain() } + .mapNotNull { this.makeWalletManagerForApp(scanResponse, it) } } fun WalletManagerFactory.makePrimaryWalletManager( 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 1963d69143..cdef91880f 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 @@ -21,7 +21,6 @@ import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.TwinsHelper import com.tangem.domain.common.isTangemTwins -import com.tangem.domain.common.productType import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask @@ -32,19 +31,17 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.extensions.getPrimaryCurve import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.extensions.hasNoWallets import com.tangem.tap.domain.extensions.isHdWalletAllowedByApp import com.tangem.tap.domain.extensions.isMultiwalletAllowed -import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import kotlinx.coroutines.launch class ScanProductTask( val card: Card? = null, - private val currenciesRepository: CurrenciesRepository?, - private val additionalBlockchainsToDerive: Collection? = null + private val userTokensRepository: UserTokensRepository?, + private val additionalBlockchainsToDerive: Collection? = null, ) : CardSessionRunnable { override fun run( @@ -64,7 +61,7 @@ class ScanProductTask( val commandProcessor = when { card.isTangemTwins() -> ScanTwinProcessor() - else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive) + else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive) } commandProcessor.proceed(card, session) { processorResult -> when (processorResult) { @@ -95,8 +92,8 @@ class ScanProductTask( } private class ScanWalletProcessor( - private val currenciesRepository: CurrenciesRepository?, - private val additionalBlockchainsToDerive: Collection? = null + private val userTokensRepository: UserTokensRepository?, + private val additionalBlockchainsToDerive: Collection? = null, ) : ProductCommandProcessor { var primaryCard: PrimaryCard? = null @@ -169,47 +166,41 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - scope.launch { - val derivations = collectDerivations(card) - if (derivations.isEmpty() || !card.isHdWalletAllowedByApp) { - callback( - CompletionResult.Success( - ScanResponse( - card = card, - productType = card.productType, - walletData = session.environment.walletData, - primaryCard = primaryCard, - ), + val derivations = collectDerivations(card) + if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) { + callback( + CompletionResult.Success( + ScanResponse( + card = card, + productType = ProductType.Wallet, + walletData = session.environment.walletData, + primaryCard = primaryCard, ), - ) - return@launch - } + ), + ) + return + } - DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result -> - when (result) { - is CompletionResult.Success -> { - val response = ScanResponse( - card = card, - productType = card.productType, - walletData = session.environment.walletData, - derivedKeys = result.data.entries, - primaryCard = primaryCard, - ) - callback(CompletionResult.Success(response)) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val response = ScanResponse( + card = card, + productType = ProductType.Wallet, + walletData = session.environment.walletData, + derivedKeys = result.data.entries, + primaryCard = primaryCard, + ) + callback(CompletionResult.Success(response)) } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } - private suspend fun getBlockchainsToDerive(card: Card): List { - val currenciesRepository = currenciesRepository ?: return emptyList() - - val cardCurrencies = currenciesRepository - .loadSavedCurrencies(card.cardId, card.isHdWalletAllowedByApp).toMutableList() - - val blockchainsToDerive = cardCurrencies.ifEmpty { + private fun getBlockchainsToDerive(card: Card): List { + val userTokensRepository = userTokensRepository ?: return emptyList() + val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card).toMutableList().ifEmpty { mutableListOf( BlockchainNetwork(Blockchain.Bitcoin, card), BlockchainNetwork(Blockchain.Ethereum, card), @@ -221,7 +212,7 @@ private class ScanWalletProcessor( listOf( BlockchainNetwork(Blockchain.Ethereum, card), BlockchainNetwork(Blockchain.EthereumTestnet, card), - ) + ), ) } if (additionalBlockchainsToDerive != null) { @@ -241,13 +232,12 @@ private class ScanWalletProcessor( return blockchainsToDerive.distinct() } - private suspend fun collectDerivations(card: Card): Map> { + private fun collectDerivations(card: Card): Map> { val blockchains = getBlockchainsToDerive(card) val derivations = mutableMapOf>() blockchains.forEach { blockchain -> val curve = blockchain.blockchain.getPrimaryCurve() - val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach if (wallet.chainCode == null) return@forEach diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt index 2dafa32075..d3d5836701 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt @@ -1,247 +1,12 @@ package com.tangem.tap.domain.tokens -import android.app.Application -import android.content.Context -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Types import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.DerivationStyle -import com.tangem.blockchain.common.Token import com.tangem.common.card.FirmwareVersion -import com.tangem.common.services.Result -import com.tangem.domain.common.extensions.getTokens -import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.network.common.MoshiConverter -import com.tangem.tap.common.extensions.appendIf -import com.tangem.tap.common.extensions.readJsonFileToString -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.domain.tokens.models.ObsoleteTokenDao -import com.tangem.tap.domain.tokens.models.TokenDao -import com.tangem.tap.features.demo.DemoHelper -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import timber.log.Timber -import java.util.* - -class CurrenciesRepository( - private val context: Application, - private val tangemNetworkService: TangemTechService -) { - - private val moshi = MoshiConverter.defaultMoshi() - private val blockchainsAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, Blockchain::class.java) - ) - private val tokensAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, TokenDao::class.java) - ) - private val obsoleteTokensAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java) - ) - private val currenciesAdapter: JsonAdapter = - moshi.adapter(CurrenciesFromJson::class.java) - - private val blockchainNetworkAdapter: JsonAdapter> = - moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java)) - - fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) { - var changed = false - val currencies = loadSavedCurrenciesWithoutMigration(cardId).map { - if (it == blockchainNetwork) { - changed = true - blockchainNetwork - } else { - it - } - } - val updatedCurrencies = if (changed) currencies else currencies + blockchainNetwork - saveCurrencies(cardId, updatedCurrencies.distinct()) - } - - fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) { - val currencies = loadSavedCurrenciesWithoutMigration(cardId).map { - if (it == blockchainNetwork) { - it.copy(tokens = it.tokens.filterNot { it == token }) - } else { - it - } - } - saveCurrencies(cardId, currencies) - } - - fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) { - val currencies = loadSavedCurrenciesWithoutMigration(cardId) - .filterNot { it == blockchainNetwork } - saveCurrencies(cardId, currencies) - } - - fun removeCurrencies(cardId: String) { - saveCurrencies(cardId, emptyList()) - } - - @Deprecated("Use BlockchainNetwork instead") - private fun loadSavedTokens(cardId: String): List { - val json = try { - context.readFileText(getFileNameForTokens(cardId)) - } catch (exception: Exception) { - return emptyList() - } - - return try { - tokensAdapter.fromJson(json) ?: emptyList() - } catch (exception: Exception) { - emptyList() - } - } - - @Deprecated("Use BlockchainNetwork instead") - private fun loadSavedBlockchains(cardId: String): List { - return try { - val json = context.readFileText(getFileNameForBlockchains(cardId)) - blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList() - } catch (exception: Exception) { - emptyList() - } - } - - suspend fun loadSavedCurrencies( - cardId: String, - isHdWalletSupported: Boolean = false - ): List { - if (DemoHelper.isDemoCardId(cardId)) { - return loadDemoCurrencies() - } - return try { - val json = context.readFileText(getFileNameForBlockchains(cardId)) - blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList() - } catch (exception: Exception) { - tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported) - } - } - - fun loadSavedCurrenciesWithoutMigration( - cardId: String, - ): List { - if (DemoHelper.isDemoCardId(cardId)) { - return loadDemoCurrencies() - } - return try { - val json = context.readFileText(getFileNameForBlockchains(cardId)) - blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList() - } catch (exception: Exception) { - emptyList() - } - } - - private fun loadDemoCurrencies(): List { - return DemoHelper.config.demoBlockchains.map { - BlockchainNetwork( - blockchain = it, - derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath, - tokens = emptyList() - ) - } - } - - private suspend fun tryToLoadPreviousFormatAndMigrate( - cardId: String, - isHdWalletSupported: Boolean = false - ): List { - return try { - loadSavedCurrenciesOldWay( - cardId, - isHdWalletSupported - ) - } catch (exception: Exception) { - emptyList() - } - } - - private suspend fun loadSavedCurrenciesOldWay( - cardId: String, isHdWalletSupported: Boolean = false - ): List { - val blockchains = loadSavedBlockchains(cardId) - val tokens = loadSavedTokens(cardId) - val ids = getTokensIds(tokens) - val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null - val blockchainNetworks = blockchains.map { blockchain -> - BlockchainNetwork( - blockchain = blockchain, - derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath, - tokens = tokens - .filter { it.blockchainDao.toBlockchain() == blockchain } - .map { - val token = it.toToken() - token.copy(id = ids[token.contractAddress]) - } - ) - } - saveCurrencies(cardId, blockchainNetworks) // migrate saved currencies - return blockchainNetworks - } - - private suspend fun getTokensIds(tokens: List): Map = coroutineScope { - tokens.map { - async { - tangemNetworkService.getTokens( - contractAddress = it.contractAddress, - networkId = it.blockchainDao.toBlockchain().toNetworkId(), - active = true, - ) - } - }.map { it.await() } - .map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id } - .mapIndexedNotNull { index, id -> - if (id == null) null else tokens[index].contractAddress to id - }.toMap() - } - - fun saveCurrencies(cardId: String, currencies: List) { - val json = blockchainNetworkAdapter.toJson(currencies) - context.rewriteFile(json, getFileNameForBlockchains(cardId)) - } - - private fun Context.readFileText(fileName: String): String = - this.openFileInput(fileName).bufferedReader().readText() - - private fun Context.rewriteFile(content: String, fileName: String) { - this.openFileOutput(fileName, Context.MODE_PRIVATE).use { - it.write(content.toByteArray(), 0, content.length) - } - } - - fun getTestnetCoins(): List { - val json = context.assets.readJsonFileToString(FILE_NAME_TESTNET_COINS) - return currenciesAdapter.fromJson(json)!!.coins - .map { Currency.fromJsonObject(it) } - } - - private fun loadTokensJson(blockchain: Blockchain): String? { - val fileName = getFileName(blockchain) - return try { - context.assets.readJsonFileToString(fileName) - } catch (ex: Exception) { - Timber.e(ex, "Tokens with the file name %s not found", fileName) - null - } - } - - private fun getFileName(blockchain: Blockchain): String { - return StringBuilder().apply { - append(blockchain.id.lowercase(Locale.getDefault()).replace("/test", "")) - append("_tokens") - appendIf("_testnet") { blockchain.isTestnet() } - }.toString() - } - - private fun fromJsonToTokensDao(tokenJson: String, blockchain: Blockchain): List { - return obsoleteTokensAdapter.fromJson(tokenJson)!!.map { it.toTokenDao(blockchain) } - } +object CurrenciesRepository { fun getBlockchains( cardFirmware: FirmwareVersion, - isTestNet: Boolean = false + isTestNet: Boolean = false, ): List { val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) { Blockchain.secp256k1Blockchains(isTestNet) @@ -261,21 +26,6 @@ class CurrenciesRepository( ) } } - - companion object { - private const val FILE_NAME_PREFIX_TOKENS = "tokens" - private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains" - private const val FILE_NAME_TESTNET_COINS = "testnet_tokens" - - fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId" - fun getFileNameForBlockchains(cardId: String): String = - "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId" - } } -fun Blockchain.getTokensName(): String { - return when (this) { - Blockchain.Fantom -> "Fantom Opera" - else -> this.fullName - } -} + diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt index 5b806beecc..7df68e80c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt @@ -1,29 +1,36 @@ package com.tangem.tap.domain.tokens +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result import com.tangem.domain.common.extensions.getListOfCoins import com.tangem.domain.common.extensions.toNetworkId import com.tangem.network.api.tangemTech.CoinsResponse import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.network.common.MoshiConverter +import com.tangem.tap.common.AssetReader class LoadAvailableCoinsService( private val networkService: TangemTechService, - private val currenciesRepository: CurrenciesRepository + private val assetReader: AssetReader, ) { + private val moshi: Moshi by lazy { MoshiConverter.defaultMoshi() } + private val currenciesAdapter: JsonAdapter = + moshi.adapter(CurrenciesFromJson::class.java) suspend fun getSupportedTokens( isTestNet: Boolean = false, supportedBlockchains: List, page: Int, - searchInput: String? = null + searchInput: String? = null, ): Result { if (isTestNet) { return Result.Success( LoadedCoins( - currencies = currenciesRepository.getTestnetCoins().filter(searchInput), + currencies = getTestnetCoins().filter(searchInput), moreAvailable = false, - ) + ), ) } val offset = page * LOAD_PER_PAGE @@ -56,21 +63,28 @@ class LoadAvailableCoinsService( active = true, offset = offset, limit = LOAD_PER_PAGE, - searchText = searchInput + searchText = searchInput, ) } + fun getTestnetCoins(): List { + val json = assetReader.readAssetAsString(FILE_NAME_TESTNET_COINS) + return currenciesAdapter.fromJson(json)!!.coins + .map { Currency.fromJsonObject(it) } + } + private fun List.filter(searchInput: String?): List { if (searchInput.isNullOrBlank()) return this - return filter{ + return filter { it.symbol.contains(searchInput, ignoreCase = true) || - it.name.contains(searchInput, ignoreCase = true) + it.name.contains(searchInput, ignoreCase = true) } } companion object { const val LOAD_PER_PAGE = 100 + private const val FILE_NAME_TESTNET_COINS = "testnet_tokens" } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt new file mode 100644 index 0000000000..7418891662 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt @@ -0,0 +1,130 @@ +package com.tangem.tap.domain.tokens + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Types +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.common.services.Result +import com.tangem.domain.common.extensions.getTokens +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.network.common.MoshiConverter +import com.tangem.tap.common.FileReader +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.domain.tokens.models.TokenDao +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope + +@Deprecated("Use this only for migration") +class OldUserTokensRepository( + private val fileReader: FileReader, + private val tangemNetworkService: TangemTechService, +) { + private val moshi = MoshiConverter.defaultMoshi() + private val blockchainsAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, Blockchain::class.java), + ) + private val tokensAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, TokenDao::class.java), + ) + private val blockchainNetworkAdapter: JsonAdapter> = + moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java)) + + @Deprecated("Use BlockchainNetwork instead") + private fun loadSavedTokens(cardId: String): List { + val json = try { + fileReader.readFile(getFileNameForTokens(cardId)) + } catch (exception: Exception) { + return emptyList() + } + + return try { + tokensAdapter.fromJson(json) ?: emptyList() + } catch (exception: Exception) { + emptyList() + } + } + + @Deprecated("Use BlockchainNetwork instead") + private fun loadSavedBlockchains(cardId: String): List { + return try { + val json = fileReader.readFile(getFileNameForBlockchains(cardId)) + blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList() + } catch (exception: Exception) { + emptyList() + } + } + + @Deprecated("Use TokensRepository instead") + suspend fun loadSavedCurrencies( + cardId: String, + isHdWalletSupported: Boolean = false, + ): List { + return try { + val json = fileReader.readFile(getFileNameForBlockchains(cardId)) + blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList() + } catch (exception: Exception) { + tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported) + } + } + + private suspend fun tryToLoadPreviousFormatAndMigrate( + cardId: String, + isHdWalletSupported: Boolean = false, + ): List { + return try { + loadSavedCurrenciesOldWay( + cardId, + isHdWalletSupported, + ) + } catch (exception: Exception) { + emptyList() + } + } + + private suspend fun loadSavedCurrenciesOldWay( + cardId: String, isHdWalletSupported: Boolean = false, + ): List { + val blockchains = loadSavedBlockchains(cardId) + val tokens = loadSavedTokens(cardId) + val ids = getTokensIds(tokens) + val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null + val blockchainNetworks = blockchains.map { blockchain -> + BlockchainNetwork( + blockchain = blockchain, + derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath, + tokens = tokens + .filter { it.blockchainDao.toBlockchain() == blockchain } + .map { + val token = it.toToken() + token.copy(id = ids[token.contractAddress]) + }, + ) + } + return blockchainNetworks + } + + private suspend fun getTokensIds(tokens: List): Map = coroutineScope { + tokens.map { + async { + tangemNetworkService.getTokens( + contractAddress = it.contractAddress, + networkId = it.blockchainDao.toBlockchain().toNetworkId(), + active = true, + ) + } + }.map { it.await() } + .map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id } + .mapIndexedNotNull { index, id -> + if (id == null) null else tokens[index].contractAddress to id + }.toMap() + } + + companion object { + private const val FILE_NAME_PREFIX_TOKENS = "tokens" + private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains" + private fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId" + private fun getFileNameForBlockchains(cardId: String): String = + "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt new file mode 100644 index 0000000000..ad188c6913 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.tokens + +import com.tangem.common.core.TangemSdkError +import com.tangem.common.services.Result +import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.network.api.tangemTech.UserTokensResponse +import com.tangem.tap.domain.NoDataError +import com.tangem.tap.features.wallet.models.Currency + +class UserTokensNetworkService(private val tangemTechService: TangemTechService) { + suspend fun getUserTokens(userId: String): Result { + return when (val result = tangemTechService.getUserTokens(userId)) { + is Result.Success -> result + is Result.Failure -> { + val error = result.error + if (error is TangemSdkError.NetworkError && error.customMessage.contains("404")) { + return Result.Failure(NoDataError(error.customMessage)) + } else { + return result + } + } + } + } + + suspend fun saveUserTokens(userId: String, tokens: List): Result { + val tokensResponse = tokens.map { it.toTokenResponse() } + val data = UserTokensResponse(tokens = tokensResponse) + return tangemTechService.putUserTokens(userId, data) + } +} diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt new file mode 100644 index 0000000000..e418215ed7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -0,0 +1,117 @@ +package com.tangem.tap.domain.tokens + +import android.content.Context +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.common.card.Card +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.domain.common.extensions.calculateHmacSha256 +import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.tap.common.AndroidFileReader +import com.tangem.tap.domain.NoDataError +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.toBlockchainNetworks +import com.tangem.tap.features.wallet.models.toCurrencies +import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.store +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch + +class UserTokensRepository( + private val storageService: UserTokensStorageService, + private val networkService: UserTokensNetworkService, +) { + suspend fun getUserTokens(card: Card): List { + if (DemoHelper.isDemoCardId(card.cardId)) { + return loadDemoCurrencies() + } + val userId = card.getUserId() + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + return loadTokensOffline(card, userId) + } + + return when (val networkResult = networkService.getUserTokens(userId)) { + is Result.Success -> { + val tokens = networkResult.data.tokens.map { Currency.fromTokenResponse(it) } + storageService.saveUserTokens(card.getUserId(), tokens) + tokens + } + is Result.Failure -> { + handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) + } + } + } + + suspend fun saveUserTokens(card: Card, tokens: List) { + networkService.saveUserTokens(card.getUserId(), tokens) + storageService.saveUserTokens(card.getUserId(), tokens) + } + + suspend fun removeUserTokens(card: Card) { + networkService.saveUserTokens(card.getUserId(), emptyList()) + storageService.saveUserTokens(card.getUserId(), emptyList()) + } + + fun loadBlockchainsToDerive(card: Card): List { + return storageService.getUserTokens(card.getUserId())?.toBlockchainNetworks() ?: emptyList() + } + + private fun loadDemoCurrencies(): List { + return DemoHelper.config.demoBlockchains.map { + BlockchainNetwork( + blockchain = it, + derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath, + tokens = emptyList(), + ) + }.flatMap { it.toCurrencies() } + } + + private suspend fun handleGetUserTokensFailure( + card: Card, + userId: String, + error: Throwable, + ): List { + return when (error) { + is NoDataError -> { + val tokens = storageService.getUserTokens(card) + coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = tokens) } } + tokens + } + else -> { + val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) + tokens + } + } + } + + private suspend fun loadTokensOffline(card: Card, userId: String): List { + return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) + } + + private fun Card.getUserId(): String { + val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return "" + return calculateUserId(walletPublicKey) + } + + private fun calculateUserId(walletPublicKey: ByteArray): String { + val message = MESSAGE.toByteArray() + val keyHash = walletPublicKey.calculateSha256() + return message.calculateHmacSha256(keyHash).toHexString() + } + + companion object { + const val MESSAGE = "UserWalletID" + fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository { + val fileReader = AndroidFileReader(context) + val oldUserTokensRepository = OldUserTokensRepository( + fileReader, store.state.domainNetworks.tangemTechService, + ) + val storageService = UserTokensStorageService(oldUserTokensRepository, fileReader) + val networkService = UserTokensNetworkService(tangemTechService) + return UserTokensRepository(storageService, networkService) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt new file mode 100644 index 0000000000..f2011cbebb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -0,0 +1,48 @@ +package com.tangem.tap.domain.tokens + +import com.squareup.moshi.JsonAdapter +import com.tangem.Log +import com.tangem.common.card.Card +import com.tangem.network.api.tangemTech.UserTokensResponse +import com.tangem.network.common.MoshiConverter +import com.tangem.tap.common.FileReader +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.toCurrencies + +class UserTokensStorageService( + private val oldUserTokensRepository: OldUserTokensRepository, + private val fileReader: FileReader, +) { + private val moshi = MoshiConverter.defaultMoshi() + private val userTokensAdapter: JsonAdapter = + moshi.adapter(UserTokensResponse::class.java) + + fun getUserTokens(userId: String): List? { + return try { + val json = fileReader.readFile(getFileNameForUserTokens(userId)) + userTokensAdapter.fromJson(json)?.tokens?.map { Currency.fromTokenResponse(it) } + } catch (exception: Exception) { + Log.error { exception.stackTraceToString() } + null + } + } + + @Deprecated("") + suspend fun getUserTokens(card: Card): List { + val blockchainNetworks = + oldUserTokensRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed) + return blockchainNetworks.flatMap { it.toCurrencies() } + } + + fun saveUserTokens(userId: String, tokens: List) { + val tokensResponse = tokens.map { it.toTokenResponse() } + val data = UserTokensResponse(tokens = tokensResponse) + val json = userTokensAdapter.toJson(data) + fileReader.rewriteFile(json, getFileNameForUserTokens(userId)) + } + + companion object { + private const val FILE_NAME_PREFIX_USER_TOKENS = "user_tokens" + private fun getFileNameForUserTokens(userId: String): String = "${FILE_NAME_PREFIX_USER_TOKENS}_$userId" + } +} \ No newline at end of file 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 45c3dee2e3..de71029d2c 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 @@ -7,6 +7,8 @@ import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.walletconnect.extensions.isDappSupported import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession @@ -57,7 +59,15 @@ class WalletConnectManager { private var sessions: MutableMap = mutableMapOf() fun connect(wcUri: String) { - val session = WCSession.from(wcUri) ?: return + val session = WCSession.from(wcUri).guard { + store.dispatchOnMain( + WalletConnectAction.FailureEstablishingSession( + session = null, + error = TapError.WalletConnect.UnsupportedLink, + ), + ) + return + } if (sessions[session] != null) { store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession) return @@ -65,7 +75,19 @@ class WalletConnectManager { val client = WCClient(httpClient = okHttpClient) setListeners(client) val peerId = UUID.randomUUID().toString() - client.connect(session, tangemPeerMeta, peerId) + + try { + client.connect(session, tangemPeerMeta, peerId) + } catch (exception: IllegalArgumentException) { + store.dispatchOnMain( + WalletConnectAction.FailureEstablishingSession( + session = null, + error = TapError.WalletConnect.UnsupportedLink, + ), + ) + return + } + sessions[session] = WalletConnectActiveData( peerId = peerId, remotePeerId = null, @@ -331,21 +353,30 @@ class WalletConnectManager { val session = client.session val data = sessions[session]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId) if (data != null && session != null) { - sessions[session] = data - val sessionData = data.toWalletConnectSession() - sessionData?.let { + if (!peer.isDappSupported()) { store.dispatchOnMain( - WalletConnectAction.ScanCard( - session = sessionData, - chainId = client.chainId?.toIntOrNull() + WalletConnectAction.FailureEstablishingSession( + session = session, + error = TapError.WalletConnect.UnsupportedDapp, + ), + ) + } else { + sessions[session] = data + val sessionData = data.toWalletConnectSession() + sessionData?.let { + store.dispatchOnMain( + WalletConnectAction.ScanCard( + session = sessionData, + chainId = client.chainId?.toIntOrNull(), + ), ) + } + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Session( + Analytics.WcSessionEvent.Connect, peer.url, + ), ) } - store.state.globalState.analyticsHandlers?.logWcEvent( - Analytics.WcAnalyticsEvent.Session( - Analytics.WcSessionEvent.Connect, peer.url - ) - ) } } client.onSessionUpdate = { id: Long, update: WCSessionUpdate -> diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt deleted file mode 100644 index 144f3b873d..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.tangem.tap.domain.walletconnect - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.tap.domain.extensions.makeWalletManagerForApp -import com.tangem.tap.domain.tokens.CurrenciesRepository -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.details.redux.walletconnect.WalletForSession -import com.tangem.tap.features.wallet.redux.WalletState - -class WcWalletManagerFactory( - private val factory: WalletManagerFactory, - private val currenciesRepository: CurrenciesRepository, -) { - fun getWalletManager( - wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState, - ): WalletManager? { - val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) { - Blockchain.EthereumTestnet - } else { - blockchain - } - val blockchainNetwork = BlockchainNetwork( - blockchain = blockchainToMake, - derivationPath = wallet.derivationPath?.rawPath, - tokens = emptyList(), - ) - return walletState.getWalletManager(blockchainNetwork) - } - - suspend fun getWalletManager( - scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState, - ): WalletManager? { - val card = scanResponse.card - val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) { - Blockchain.EthereumTestnet - } else { - blockchain - } - val blockchainNetwork = BlockchainNetwork( - blockchain = blockchainToMake, - card = card, - ) - - return if (walletState.cardId == card.cardId) { - walletState.getWalletManager(blockchainNetwork) - } else { - if (currenciesRepository - .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed) - .contains(blockchainNetwork) - ) { - factory.makeWalletManagerForApp( - scanResponse = scanResponse, - blockchainNetwork = blockchainNetwork, - ) - } else { - null - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt new file mode 100644 index 0000000000..057caf7bd2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.domain.walletconnect.extensions + +import com.trustwallet.walletconnect.models.WCPeerMeta + +fun WCPeerMeta.isDappSupported(): Boolean { + return !unsupportedDappsList.any { this.url.contains(it) } +} + +private val unsupportedDappsList: List = listOf("dydx.exchange") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 650ce5ea09..b0d473cde0 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -448,6 +448,33 @@ class DemoConfig { "AB02000000049533", "AB02000000049541", "AB02000000049830", + // === more cids === + "AC03000000091418", + "AC03000000091400", + "AC03000000099007", + "AC03000000098991", + "AC03000000098942", + "AC03000000091715", + "AC03000000091301", + "AC03000000091343", + "AB01000000055705", + "AB01000000052918", + "AB01000000047710", + "AB01000000052306", + "AB01000000047645", + "AB01000000048957", + "AB01000000052900", + "AB01000000050391", + "AB01000000047363", + "AB02000000053998", + "AB02000000019809", + "AB02000000020872", + "AB02000000022027", + "AB02000000058955", + "AB02000000053253", + "AB02000000048063", + "AB02000000023736", + "AB02000000058187", ) private val testDemoCardIds = listOf( diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index a465940186..fa96a81962 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -11,7 +11,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.currenciesRepository import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -110,11 +109,9 @@ class DetailsMiddleware { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { val result = tangemSdkManager.resetToFactorySettings(card) - withContext(Dispatchers.Main) { when (result) { is CompletionResult.Success -> { - currenciesRepository.removeCurrencies(card.cardId) - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> @@ -125,7 +122,6 @@ class DetailsMiddleware { ) } } - } } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 0258af7316..67b215dd51 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.domain.TapError import com.tangem.wallet.R import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder @@ -52,7 +53,7 @@ sealed class WalletConnectAction : Action { val updatedSession: WalletConnectSession, ) : WalletConnectAction() - data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction() + data class FailureEstablishingSession(val session: WCSession?, val error: TapError? = null) : WalletConnectAction() data class SetSessionsRestored(val sessions: List) : WalletConnectAction() @@ -103,6 +104,6 @@ sealed class WalletConnectAction : Action { data class Sign( val id: Long, val data: ByteArray, val sessionData: WCSession, - ) : WalletConnectAction() + ) : WalletConnectAction() } } \ No newline at end of file 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 39529bf6f1..ed2c68c8d6 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 @@ -2,26 +2,28 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager import com.tangem.common.card.Card import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletconnect.BnbHelper import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils -import com.tangem.tap.domain.walletconnect.WcWalletManagerFactory import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.wallet.R import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -96,6 +98,16 @@ class WalletConnectMiddleware { store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout)) } is WalletConnectAction.FailureEstablishingSession -> { + if (action.error != null) { + store.dispatch( + GlobalAction.ShowDialog( + AppDialog.SimpleOkDialogRes( + headerId = R.string.common_warning, + messageId = action.error.messageResource, + ), + ), + ) + } if (action.session != null) { walletConnectManager.disconnect(action.session) } @@ -189,15 +201,10 @@ class WalletConnectMiddleware { store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) return } - val factory = WcWalletManagerFactory( - factory = store.state.globalState.tapWalletManager.walletManagerFactory, - currenciesRepository = currenciesRepository, - ) - val walletState = store.state.walletState - val walletManager = factory.getWalletManager( + val walletManager = getWalletManager( wallet = action.session.wallet, blockchain = blockchain, - walletState = walletState, + walletState = store.state.walletState, ).guard { store.dispatchOnMain( GlobalAction.ShowDialog( @@ -233,20 +240,10 @@ class WalletConnectMiddleware { handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain) } - private suspend fun getAvailableBlockchains(card: Card, walletState: WalletState): List { - return if (walletState.cardId == card.cardId) { - walletState.currencies.filter { - it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm() - }.map { it.blockchain } - } else { - currenciesRepository - .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed) - .filter { - it.blockchain.isEvm() && - it.derivationPath == it.blockchain.derivationPath(card.derivationStyle)?.rawPath - } - .map { it.blockchain } - } + private fun getAvailableBlockchains(card: Card, walletState: WalletState): List { + return walletState.currencies.filter { + it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm() + }.map { it.blockchain } } private suspend fun prepareWalletManager( @@ -256,11 +253,7 @@ class WalletConnectMiddleware { session: WalletConnectSession, walletConnectManager: WalletConnectManager, ) { - val factory = WcWalletManagerFactory( - factory = store.state.globalState.tapWalletManager.walletManagerFactory, - currenciesRepository = currenciesRepository, - ) - val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard { + val walletManager = getWalletManager(session.wallet, blockchain, walletState).guard { store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session)) store.dispatchOnMain( GlobalAction.ShowDialog( @@ -309,17 +302,29 @@ class WalletConnectMiddleware { NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain), ), ) + val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList() + store.dispatch( + GlobalAction.ShowDialog( + WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains), + ), + ) + } - scope.launch { - val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList() - - withMainContext { - store.dispatch( - GlobalAction.ShowDialog( - WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains), - ), - ) - } + private fun getWalletManager( + wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState, + ): WalletManager? { + val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) { + Blockchain.EthereumTestnet + } else { + blockchain } + val derivation = blockchainToMake.derivationPath(store.state.globalState.scanResponse?.card?.derivationStyle) + ?.rawPath + val blockchainNetwork = BlockchainNetwork( + blockchain = blockchainToMake, + derivationPath = derivation, + tokens = emptyList(), + ) + return walletState.getWalletManager(blockchainNetwork) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index 24129ec514..e0b4f1f186 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -86,7 +87,7 @@ fun WalletConnectDetailsItem( ) { Row( modifier = modifier - .height(84.dp) + .defaultMinSize(minHeight = 84.dp) .fillMaxWidth() .clickable { onItemsClick(SettingsElement.WalletConnect) }, horizontalArrangement = Arrangement.Start, @@ -99,7 +100,7 @@ fun WalletConnectDetailsItem( tint = colorResource(id = R.color.all_colors_azure), ) Column( - modifier = modifier.height(56.dp), + modifier = modifier.defaultMinSize(minHeight = 56.dp), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Center, ) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index eda5e31bcc..6f29a508b1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -1,6 +1,8 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -89,11 +91,14 @@ fun ResetCardView( color = colorResource(id = R.color.text_secondary), ) - Spacer(modifier = modifier.size(44.dp)) + Spacer(modifier = modifier.size(28.dp)) Row( modifier = modifier .fillMaxWidth() - .padding(end = 20.dp), + .clickable( + onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, + ) + .padding(top = 16.dp, bottom = 16.dp), ) { IconToggleButton( checked = state.accepted, @@ -120,13 +125,15 @@ fun ResetCardView( text = stringResource(id = R.string.reset_card_to_factory_warning_message), style = TangemTypography.body2, color = colorResource(id = R.color.text_secondary), + modifier = modifier + .padding(end = 20.dp), ) } - Spacer(modifier = modifier.size(32.dp)) + Spacer(modifier = modifier.size(16.dp)) Box( modifier = modifier - .padding(start = 16.dp, end = 16.dp), + .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), ) { DetailsMainButton( title = stringResource(id = R.string.reset_card_to_factory_button_title), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 8474878afc..168bd4a024 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -140,13 +140,12 @@ private fun WalletConnectSessions( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Column(modifier = modifier) { - Text( - text = session.description, - style = TangemTypography.subtitle1, - color = colorResource(id = R.color.text_primary_1), - ) - } + Text( + text = session.description, + style = TangemTypography.subtitle1, + color = colorResource(id = R.color.text_primary_1), + modifier = modifier.weight(1f), + ) IconButton( onClick = { state.onRemoveSession(session.sessionId) }, ) { 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 644f172e29..bd208b2baf 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 @@ -79,41 +79,46 @@ private fun handleReadCard() { store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } else { changeButtonState(ButtonState.PROGRESS) - store.dispatch(GlobalAction.ScanCard(onSuccess = { scanResponse -> - store.state.globalState.tapWalletManager.updateConfigManager(scanResponse) - store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + store.dispatch( + GlobalAction.ScanCard( + onSuccess = { scanResponse -> + store.state.globalState.tapWalletManager.updateConfigManager(scanResponse) + store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - // TODO: SaltPay: temporary excluding backup process for Visa cards - if (scanResponse.card.isSaltPayVisa) { - scope.launch { - store.onCardScanned(scanResponse) - withMainContext { - navigateTo(AppScreen.Wallet, null) + // TODO: SaltPay: temporary excluding backup process for Visa cards + if (scanResponse.card.isSaltPayVisa) { + scope.launch { + store.onCardScanned(scanResponse) + withMainContext { + navigateTo(AppScreen.Wallet, null) + } + } + return@ScanCard } - } - return@ScanCard - } - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - val navigateTo = OnboardingHelper.whereToNavigate(scanResponse) - store.dispatch(GlobalAction.Onboarding.Start(scanResponse)) - navigateTo(navigateTo) - } else { - scope.launch { - store.onCardScanned(scanResponse) - withMainContext { - if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { - store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly)) - navigateTo(AppScreen.OnboardingTwins) - } else { - navigateTo(AppScreen.Wallet, null) + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + val navigateTo = OnboardingHelper.whereToNavigate(scanResponse) + store.dispatch(GlobalAction.Onboarding.Start(scanResponse)) + navigateTo(navigateTo) + } else { + scope.launch { + withMainContext { + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly)) + navigateTo(AppScreen.OnboardingTwins) + } else { + navigateTo(AppScreen.Wallet, null) + } + } + store.onCardScanned(scanResponse) } } - } - } - }, onFailure = { - changeButtonState(ButtonState.ENABLED) - })) + }, + onFailure = { + changeButtonState(ButtonState.ENABLED) + }, + ), + ) } } 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 e470680dbc..02950c3b3a 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 @@ -79,17 +79,21 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { val primaryBlockchain = updatedResponse.getBlockchain() val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) { val primaryToken = updatedResponse.getPrimaryToken() - val blockchainNetwork = BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens( - listOfNotNull(primaryToken)) + val blockchainNetwork = + BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens( + listOfNotNull(primaryToken), + ) listOf(blockchainNetwork) } else { listOf( BlockchainNetwork(Blockchain.Bitcoin, updatedResponse.card), - BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card) + BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card), ) } - store.dispatch(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks)) + store.dispatch( + WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks, updatedResponse.card), + ) delay(DELAY_SDK_DIALOG_CLOSE) store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 0dc7e10566..e7d3a6501c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -18,7 +18,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.twins.TwinCardsManager @@ -271,7 +270,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } CreateTwinWalletMode.RecreateWallet -> { - currenciesRepository.removeCurrencies(scanResponse.card.cardId) store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) } } 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 016fee1817..1c41c5e27a 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 @@ -102,8 +102,8 @@ private fun handleWalletAction(action: Action) { store.dispatchOnMain( WalletAction.MultiWallet.SaveCurrencies( blockchainNetworks = blockchainNetworks, - cardId = result.data.card.cardId - ) + card = result.data.card, + ), ) onboardingManager.activationStarted(updatedResponse.card.cardId) store.dispatch(OnboardingWalletAction.ProceedBackup()) @@ -286,15 +286,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) backupService.proceedBackup { result -> when (result) { is CompletionResult.Success -> { - val blockchainNetworks = listOf( - BlockchainNetwork(Blockchain.Bitcoin, result.data), - BlockchainNetwork(Blockchain.Ethereum, result.data) - ) - store.dispatchOnMain( - WalletAction.MultiWallet.SaveCurrencies( - blockchainNetworks = blockchainNetworks, cardId = result.data.cardId - ) - ) if (backupService.currentState == BackupService.State.Finished) { store.dispatchOnMain(BackupAction.FinishBackup) } else { 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 54be369642..defca803e3 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 @@ -246,8 +246,7 @@ private fun sendTransaction( } is SimpleResult.Failure -> { store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError( - wallet = walletManager.wallet, - host = walletManager.currentHost, + walletManager = walletManager, amountToSend = amountToSend, feeAmount = feeAmount, destinationAddress = destinationAddress, 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 2ec703e629..96f742ec97 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 @@ -19,7 +19,8 @@ import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.* +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.assetReader import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -32,6 +33,9 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency 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 @@ -77,7 +81,7 @@ class TokensMiddleware { val loadCoinsService = LoadAvailableCoinsService( store.state.domainNetworks.tangemTechService, - currenciesRepository + assetReader, ) scope.launch { @@ -285,22 +289,28 @@ class TokensMiddleware { else -> DerivationParams.Custom(derivationPath) } } - val walletManager = factory.makeWalletManagerForApp( scanResponse = scanResponse, blockchain = currency.blockchain, - derivationParams = derivationParams + derivationParams = derivationParams, ) ?: return@mapNotNull null val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) - WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, walletManager) + WalletAction.MultiWallet.AddBlockchain( + blockchain = blockchainNetwork, + walletManager = walletManager, + save = true, + ) } is Currency.Token -> { val rawDerivationPath = currency.derivationPath ?: currency.blockchain.derivationPath(derivationStyle)?.rawPath - val blockchainNetwork = - BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList()) - WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork) + BlockchainNetwork(currency.blockchain, rawDerivationPath, listOf(currency.token)) + WalletAction.MultiWallet.AddToken( + token = currency.token, + blockchain = blockchainNetwork, + save = true, + ) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 75f752b278..1500a10a1e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -1,14 +1,18 @@ package com.tangem.tap.features.wallet.models +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.Token +import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency +import com.tangem.network.api.tangemTech.TokenResponse import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokenWithBlockchain sealed interface Currency { - val coinId: String? get() = when (this) { is Blockchain -> blockchain.toCoinId() @@ -17,24 +21,28 @@ sealed interface Currency { val blockchain: com.tangem.blockchain.common.Blockchain val currencySymbol: CryptoCurrencyName val derivationPath: String? - val currencyName: String get() = when (this) { is Blockchain -> blockchain.fullName is Token -> token.name } + val decimals + get() = when (this) { + is Blockchain -> blockchain.decimals() + is Token -> token.decimals + } data class Token( val token: com.tangem.blockchain.common.Token, override val blockchain: com.tangem.blockchain.common.Blockchain, - override val derivationPath: String? + override val derivationPath: String?, ) : Currency { override val currencySymbol = token.symbol } data class Blockchain( override val blockchain: com.tangem.blockchain.common.Blockchain, - override val derivationPath: String? + override val derivationPath: String?, ) : Currency { override val currencySymbol: CryptoCurrencyName = blockchain.currency } @@ -48,24 +56,35 @@ sealed interface Currency { } fun isBlockchain(): Boolean = this is Blockchain - fun isToken(): Boolean = this is Token + fun toTokenResponse(): TokenResponse { + return TokenResponse( + id = coinId ?: "", + networkId = blockchain.toNetworkId(), + derivationPath = derivationPath ?: DERIVATION_PATH_RAW_VALUE, + name = currencyName, + symbol = currencySymbol, + decimals = decimals, + contractAddress = if (this is Token) token.contractAddress else null, + ) + } companion object { + private const val DERIVATION_PATH_RAW_VALUE = "m/44/0'/0/0" fun fromBlockchainNetwork( blockchainNetwork: BlockchainNetwork, - token: com.tangem.blockchain.common.Token? = null + token: com.tangem.blockchain.common.Token? = null, ): Currency { return if (token != null) { Token( token = token, blockchain = blockchainNetwork.blockchain, - derivationPath = blockchainNetwork.derivationPath + derivationPath = blockchainNetwork.derivationPath, ) } else { Blockchain( blockchain = blockchainNetwork.blockchain, - derivationPath = blockchainNetwork.derivationPath + derivationPath = blockchainNetwork.derivationPath, ) } } @@ -74,7 +93,7 @@ sealed interface Currency { return when (customCurrency) { is CustomCurrency.CustomBlockchain -> Blockchain( blockchain = customCurrency.network, - derivationPath = customCurrency.derivationPath?.rawPath + derivationPath = customCurrency.derivationPath?.rawPath, ) is CustomCurrency.CustomToken -> Token( token = customCurrency.token, @@ -88,8 +107,53 @@ sealed interface Currency { return Token( token = tokenWithBlockchain.token, blockchain = tokenWithBlockchain.blockchain, - derivationPath = null + derivationPath = null, ) } + + fun fromTokenResponse(tokenResponse: TokenResponse): Currency { + val derivationPath = if (tokenResponse.derivationPath == DERIVATION_PATH_RAW_VALUE) { + null + } else { + tokenResponse.derivationPath + } + return when { + tokenResponse.contractAddress != null -> Token( + com.tangem.blockchain.common.Token( + name = tokenResponse.name, + symbol = tokenResponse.symbol, + contractAddress = tokenResponse.contractAddress!!, + decimals = tokenResponse.decimals, + id = tokenResponse.id, + ), + blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!, + derivationPath = derivationPath, + ) + else -> Blockchain( + blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenResponse.networkId)!!, + derivationPath = derivationPath, + ) + } + } } -} \ No newline at end of file +} + +fun BlockchainNetwork.toCurrencies(): List { + val blockchain = Currency.fromBlockchainNetwork(this) + val tokens = this.tokens.map { Currency.fromBlockchainNetwork(this, it) } + return listOf(blockchain) + tokens +} + +fun List.toCurrencies(): List { + return flatMap { it.toCurrencies() } +} + +fun List.toBlockchainNetworks(): List { + return this.filter { it.isBlockchain() }.map { BlockchainNetwork(it.blockchain, it.derivationPath, getTokens(it)) } +} + +private fun List.getTokens(currency: Currency): List { + return this + .filter { it.isToken() && it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } + .mapNotNull { if (it is Currency.Token) it.token else null } +} diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt index eb6c8caf49..3b80a95845 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt @@ -5,8 +5,7 @@ sealed class WalletWarning( ) { data class ExistentialDeposit( val currencyName: String, - val currencySymbols: String, - val existentialDepositString: String, + val edStringValueWithSymbol: String, ) : WalletWarning(1) object TransactionInProgress : WalletWarning(10) 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 0d6e8b528a..836d287739 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 @@ -31,7 +31,6 @@ sealed class WalletAction : Action { data class Failure(val error: TapError) : WalletAction() } - data class LoadWallet( val blockchain: BlockchainNetwork? = null, val walletManager: WalletManager? = null @@ -40,7 +39,7 @@ sealed class WalletAction : Action { data class NoAccount( val wallet: Wallet, val blockchain: BlockchainNetwork, - val amountToCreateAccount: String + val amountToCreateAccount: String, ) : WalletAction() data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction() @@ -48,28 +47,31 @@ sealed class WalletAction : Action { data class SetArtworkId(val artworkId: String?) : WalletAction() + sealed class UserTokens : WalletAction() { + object Loading : UserTokens() + object Loaded : UserTokens() + } sealed class MultiWallet : WalletAction() { data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet() data class AddBlockchain( val blockchain: BlockchainNetwork, - val walletManager: WalletManager? + val walletManager: WalletManager?, + val save: Boolean, ) : MultiWallet() data class AddBlockchains( - val blockchains: List, val walletManagers: List + val blockchains: List, val walletManagers: List, val save: Boolean, ) : MultiWallet() - data class AddTokens(val tokens: List, val blockchain: BlockchainNetwork) : + data class AddTokens(val tokens: List, val blockchain: BlockchainNetwork, val save: Boolean) : MultiWallet() - data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet() + data class AddToken(val token: Token, val blockchain: BlockchainNetwork, val save: Boolean) : MultiWallet() data class SaveCurrencies( - val blockchainNetworks: List, val cardId: String? = null + val blockchainNetworks: List, val card: Card? = null, ) : MultiWallet() -// object FindTokensInUse : MultiWallet() -// object FindBlockchainsInUse : MultiWallet() data class TokenLoaded( val amount: Amount, @@ -82,14 +84,15 @@ sealed class WalletAction : Action { data class TryToRemoveWallet(val currency: Currency) : MultiWallet() data class RemoveWallet( val currency: Currency, - val fromScreen: AppScreen + val fromScreen: AppScreen, ) : MultiWallet() data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet() data class SetPrimaryToken(val token: Token) : MultiWallet() - data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet() object BackupWallet : MultiWallet() + data class AddMissingDerivations(val blockchains: List) : MultiWallet() + object ScanToGetDerivations : MultiWallet() } sealed class Warnings : WalletAction() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 6fb9f9e73b..6219fea552 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -19,9 +19,6 @@ import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.models.WalletWarning -import com.tangem.tap.features.wallet.models.hasPendingTransactions -import com.tangem.tap.features.wallet.models.hasSendableAmounts -import com.tangem.tap.features.wallet.models.isSendableAmount import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount import com.tangem.tap.features.wallet.redux.reducers.findProgressState import com.tangem.tap.features.wallet.ui.BalanceStatus @@ -47,6 +44,8 @@ data class WalletState( val isTestnet: Boolean = false, val totalBalance: TotalBalance? = null, val showBackupWarning: Boolean = false, + val missingDerivations: List = emptyList(), + val loadingUserTokens: Boolean = false, ) : StateType { // if you do not delegate - the application crashes on startup, @@ -131,32 +130,6 @@ data class WalletState( return walletsData.find { it.currency == selectedCurrency } } - fun canBeRemoved(walletData: WalletData?): Boolean { - if (walletData == null) return false - - if (!isPrimaryCurrency(walletData)) { - val walletManager = getWalletManager(walletData.currency) - ?: return true - - if (walletData.currency is Currency.Blockchain && - walletManager.cardTokens.isNotEmpty() - ) { - return false - } - - val wallet = walletManager.wallet - - return when (walletData.currency) { - is Currency.Blockchain -> !wallet.hasPendingTransactions() && !wallet.hasSendableAmounts() - is Currency.Token -> { - val token = walletData.currency.token - !wallet.hasPendingTransactions(token) && !wallet.isSendableAmount(token) - } - } - } - return false - } - private fun isPrimaryCurrency(walletData: WalletData): Boolean { return (walletData.currency is Currency.Blockchain && walletData.currency.blockchain == store.state.walletState.primaryBlockchain) @@ -220,21 +193,28 @@ data class WalletState( fun removeWallet(walletData: WalletData?): WalletState { if (walletData == null) return this - return if (walletData.currency is Currency.Blockchain) { - val walletStores = wallets.filterNot { - it.blockchainNetwork.blockchain == walletData.currency.blockchain - && it.blockchainNetwork.derivationPath == walletData.currency.derivationPath + return when (val currency = walletData.currency) { + is Currency.Blockchain -> { + val walletStores = wallets.filterNot { + it.blockchainNetwork.blockchain == currency.blockchain + && it.blockchainNetwork.derivationPath == currency.derivationPath + } + copy(wallets = walletStores) + .updateTotalBalance() + .updateProgressState() + } + is Currency.Token -> { + val walletStore = getWalletStore(walletData.currency) + val walletDataList = walletStore?.walletsData + ?.filterNot { it.currency == walletData.currency } + ?: emptyList() + val updatedWalletManager = walletStore?.walletManager?.also { it.removeToken(currency.token) } + val updatedWalletStore = walletStore?.copy( + walletsData = walletDataList, + walletManager = updatedWalletManager, + ) + updateWalletStore(updatedWalletStore) } - copy(wallets = walletStores) - .updateTotalBalance() - .updateProgressState() - } else { - val walletStore = getWalletStore(walletData.currency) - val walletDataList = walletStore?.walletsData - ?.filterNot { it.currency == walletData.currency } - ?: emptyList() - val updatedWalletStore = walletStore?.copy(walletsData = walletDataList) - updateWalletStore(updatedWalletStore) } } @@ -380,8 +360,7 @@ data class WalletData( if (existentialDepositString != null) { val warning = WalletWarning.ExistentialDeposit( currencyName = currency.currencyName, - currencySymbols = currency.currencySymbol, - existentialDepositString = existentialDepositString, + edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}", ) walletWarnings.add(warning) } 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 4030bae09b..d7e6ceba13 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 @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.guard +import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.safeUpdate @@ -11,18 +12,20 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog +import com.tangem.tap.features.wallet.redux.reducers.toWallet import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userTokensRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -44,41 +47,47 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.AddToken -> { - addTokens(listOf(action.token), action.blockchain, walletState, globalState) + addTokens(listOf(action.token), action.blockchain, walletState, globalState, action.save) } is WalletAction.MultiWallet.AddTokens -> { - addTokens(action.tokens, action.blockchain, walletState, globalState) + addTokens(action.tokens, action.blockchain, walletState, globalState, action.save) } is WalletAction.MultiWallet.AddBlockchain -> { action.walletManager?.let { handleAddingWalletManagers(globalState, listOf(action.walletManager)) } + val currencies: List = + (walletState?.currencies ?: emptyList()) + action.blockchain.toCurrencies() - globalState.scanResponse?.let { - currenciesRepository.saveUpdatedCurrency( - cardId = it.card.cardId, - blockchainNetwork = action.blockchain - ) + + if (action.save && globalState.scanResponse != null) { + scope.launch { + userTokensRepository.saveUserTokens( + card = globalState.scanResponse.card, + tokens = currencies, + ) + } } + store.dispatch( WalletAction.LoadFiatRate( coinsList = listOf( Currency.Blockchain( action.blockchain.blockchain, - action.blockchain.derivationPath - ) - ) - ) + action.blockchain.derivationPath, + ), + ), + ), ) store.dispatch( WalletAction.LoadWallet( - action.blockchain, action.walletManager - ) + action.blockchain, action.walletManager, + ), ) } is WalletAction.MultiWallet.SaveCurrencies -> { - val cardId = action.cardId ?: globalState.scanResponse?.card?.cardId ?: return - currenciesRepository.saveCurrencies(cardId, action.blockchainNetworks) + val card = action.card ?: globalState.scanResponse?.card ?: return + scope.launch { userTokensRepository.saveUserTokens(card, action.blockchainNetworks.toCurrencies()) } } is WalletAction.MultiWallet.TryToRemoveWallet -> { val currency = action.currency @@ -89,54 +98,44 @@ class MultiWalletMiddleware { } if (currency.isBlockchain() && walletManager.cardTokens.isNotEmpty()) { - store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog( - currencyTitle = currency.currencyName, - currencySymbol = currency.currencySymbol - )) + store.dispatchDialogShow( + WalletDialog.TokensAreLinkedDialog( + currencyTitle = currency.currencyName, + currencySymbol = currency.currencySymbol, + ), + ) } else { - store.dispatchDialogShow(WalletDialog.RemoveWalletDialog( - currencyTitle = currency.currencyName, - onOk = { - store.dispatch(WalletAction.MultiWallet.RemoveWallet( - currency = currency, - fromScreen = AppScreen.WalletDetails - )) - store.dispatch(NavigationAction.PopBackTo()) - } - )) + store.dispatchDialogShow( + WalletDialog.RemoveWalletDialog( + currencyTitle = currency.currencyName, + onOk = { + store.dispatch( + WalletAction.MultiWallet.RemoveWallet( + currency = currency, + fromScreen = AppScreen.WalletDetails, + ), + ) + store.dispatch(NavigationAction.PopBackTo()) + }, + ), + ) } } is WalletAction.MultiWallet.RemoveWallet -> { val currency = action.currency - val cardId = globalState.scanResponse?.card?.cardId.guard { - store.dispatchErrorNotification(TapError.UnsupportedState("cardId is NULL")) + val card = globalState.scanResponse?.card.guard { + store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL")) store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) return } - - when (currency) { - is Currency.Blockchain -> { - currenciesRepository.removeBlockchain( - cardId = cardId, - blockchainNetwork = BlockchainNetwork( - blockchain = currency.blockchain, - derivationPath = currency.derivationPath, - tokens = emptyList() - ) - ) - } - is Currency.Token -> { - val walletManager = walletState?.getWalletManager(currency) - if (walletManager != null) { - walletManager.removeToken(currency.token) - currenciesRepository.removeToken( - cardId = cardId, - token = currency.token, - blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) - ) - } - } + var currencies = walletState?.currencies ?: emptyList() + currencies = currencies.filterNot { it == currency } + if (currency.isBlockchain()) { + currencies + .filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } } + scope.launch { userTokensRepository.saveUserTokens(card, currencies) } + if (action.fromScreen == AppScreen.AddTokens) { store.dispatch(WalletAction.MultiWallet.SelectWallet(null)) } @@ -148,94 +147,9 @@ class MultiWalletMiddleware { store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false)) } } -// is WalletAction.MultiWallet.FindBlockchainsInUse -> { -// val scanResponse = globalState.scanResponse ?: return -// if (scanResponse.supportsHdWallet()) return -// -// val cardFirmware = scanResponse.card.firmwareVersion -// val blockchains = currenciesRepository.getBlockchains(cardFirmware) -// .filterNot { walletState?.blockchains?.contains(it) == true } -// .map { BlockchainNetwork(it, null, emptyList()) } -// val walletManagers = -// tapWalletManager.walletManagerFactory.makeWalletManagersForApp( -// scanResponse, -// blockchains -// ) -// -// scope.launch { -// walletManagers.map { walletManager -> -// async(Dispatchers.IO) { -// walletManager.safeUpdate() -// val wallet = walletManager.wallet -// val coinAmount = wallet.amounts[AmountType.Coin]?.value -// if (coinAmount != null && !coinAmount.isZero()) { -// scope.launch(Dispatchers.Main) { -// val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) -// if (walletState?.getWalletData(blockchainNetwork) == null) { -// store.dispatch(WalletAction.MultiWallet.AddBlockchain( -// blockchainNetwork, walletManager -// )) -// store.dispatch(WalletAction.LoadWallet.Success( -// wallet = wallet, -// blockchain = blockchainNetwork -// )) -// } -// } -// } -// } -// } -// } -// } -// is WalletAction.MultiWallet.FindTokensInUse -> { -// val scanResponse = globalState.scanResponse ?: return -// if (scanResponse.supportsHdWallet()) return -// -// val walletFactory = tapWalletManager.walletManagerFactory -// val card = scanResponse.card -// -// val walletManager = walletState?.getWalletManager( -// Currency.Blockchain(Blockchain.Ethereum, null) -// ) -// ?: walletFactory.makeWalletManagerForApp( -// scanResponse, -// Currency.Blockchain(Blockchain.Ethereum, null) -// ) -// -// val tokenFinder = walletManager as? TokenFinder ?: return -// scope.launch { -// val result = tokenFinder.findTokens() -// -// withContext(Dispatchers.Main) { -// when (result) { -// is Result.Success -> { -// if (result.data.isNotEmpty()) { -// val blockchainNetwork = BlockchainNetwork( -// walletManager.wallet.blockchain, -// walletManager.wallet.publicKey.derivationPath?.rawPath, -// walletManager.cardTokens.toList() -// ) -// currenciesRepository.saveUpdatedCurrency( -// card.cardId, -// blockchainNetwork -// ) -// store.dispatch( -// WalletAction.MultiWallet.AddBlockchain( -// blockchainNetwork, -// walletManager -// ) -// ) -// store.dispatch( -// WalletAction.MultiWallet.AddTokens( -// walletManager.cardTokens.toList(), -// blockchainNetwork -// ) -// ) -// } -// } -// } -// } -// } -// } + is WalletAction.MultiWallet.ScanToGetDerivations -> { + store.dispatch(WalletAction.Scan) + } } } @@ -249,7 +163,7 @@ class MultiWalletMiddleware { private fun handleAddingWalletManagers( globalState: GlobalState, - walletManagers: List + walletManagers: List, ) { globalState.feedbackManager?.infoHolder?.setWalletsInfo(walletManagers) if (globalState.scanResponse?.isDemoCard() == true) { @@ -259,50 +173,59 @@ class MultiWalletMiddleware { private fun addTokens( tokens: List, blockchainNetwork: BlockchainNetwork, - walletState: WalletState?, globalState: GlobalState? + walletState: WalletState?, globalState: GlobalState?, + save: Boolean, ) { if (tokens.isEmpty()) return val scanResponse = globalState?.scanResponse ?: return val wmFactory = globalState.tapWalletManager.walletManagerFactory + val walletState = walletState ?: return + val walletManager = walletState.getWalletManager(blockchainNetwork)?.also { + if (save) { + val wallets = tokens.mapNotNull { token -> token.toWallet(walletState, blockchainNetwork) } + val currencies = walletState.updateWalletsData(wallets).currencies + scope.launch { userTokensRepository.saveUserTokens(scanResponse.card, currencies) } + } + } ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also { + store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork.updateTokens(tokens), it, save)) + } - val walletManager = walletState?.getWalletManager(blockchainNetwork) - ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also { - store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, it)) - } ?: return - - store.dispatch(WalletAction.LoadFiatRate(coinsList = tokens.map { token -> - Currency.Token( - token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath - ) - })) - if (tokens.isNotEmpty()) walletManager.addTokens(tokens) - - currenciesRepository.saveUpdatedCurrency( - cardId = scanResponse.card.cardId, - blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) + store.dispatch( + WalletAction.LoadFiatRate( + coinsList = tokens.map { token -> + Currency.Token( + token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath, + ) + }, + ), ) + if (tokens.isNotEmpty()) walletManager?.addTokens(tokens) + scope.launch { - when (val result = walletManager.safeUpdate()) { - is com.tangem.common.services.Result.Success -> { - val wallet = result.data - wallet.getTokens() - .filter { tokens.contains(it) } - .mapNotNull { token -> - wallet.getTokenAmount(token)?.let { Pair(token, it) } - } - .forEach { - withContext(Dispatchers.Main) { - store.dispatch( - WalletAction.MultiWallet.TokenLoaded( - it.second, - it.first, - blockchainNetwork - ) - ) + val result = walletManager?.safeUpdate() + withMainContext { + when (result) { + is com.tangem.common.services.Result.Success -> { + val wallet = result.data + wallet.getTokens() + .filter { tokens.contains(it) } + .mapNotNull { token -> + wallet.getTokenAmount(token)?.let { Pair(token, it) } } - } + .forEach { + withContext(Dispatchers.Main) { + store.dispatch( + WalletAction.MultiWallet.TokenLoaded( + it.second, + it.first, + blockchainNetwork, + ), + ) + } + } + } + else -> {} } - is com.tangem.common.services.Result.Failure -> {} } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b54a5168a9..19d4a51cf0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -108,10 +108,13 @@ class WalletMiddleware { val coinAmount = action.wallet.amounts[AmountType.Coin]?.value if (coinAmount != null && !coinAmount.isZero()) { if (walletState.getWalletData(action.blockchain) == null) { - store.dispatch(WalletAction.MultiWallet.AddBlockchain( - action.blockchain, - null - )) + store.dispatch( + WalletAction.MultiWallet.AddBlockchain( + action.blockchain, + null, + true, + ), + ) store.dispatch(WalletAction.LoadWallet.Success( action.wallet, action.blockchain diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 9821f1f6a7..892b5db149 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -34,11 +34,10 @@ class MultiWalletReducer { val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) - } ?: return@mapNotNull null - - val wallet = walletManager.wallet + } + val wallet = walletManager?.wallet val cardToken = if (!state.isMultiwalletAllowed) { - wallet.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) } + wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) } } else { null } @@ -66,7 +65,7 @@ class MultiWalletReducer { } val selectedCurrency = if (!state.isMultiwalletAllowed) { - wallets[0].walletsData[0].currency + wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency } else { state.selectedCurrency } @@ -168,9 +167,11 @@ class MultiWalletReducer { state.copy(primaryToken = action.token) is WalletAction.MultiWallet.SaveCurrencies -> state is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy( - showBackupWarning = action.show + showBackupWarning = action.show, ) + is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains) is WalletAction.MultiWallet.BackupWallet -> state + is WalletAction.MultiWallet.ScanToGetDerivations -> state } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 9579f7311e..3a180040a3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -146,18 +146,19 @@ private fun internalReduce(action: Action, state: AppState): WalletState { BalanceStatus.Loading } if (action.blockchain == null) { - val wallets = newState.wallets.map { - it.copy( - walletsData = it.walletsData.map { walletData -> + val wallets = newState.wallets.map { walletStore -> + walletStore.copy( + walletsData = walletStore.walletsData.map { walletData -> walletData.copy( currencyData = walletData.currencyData.copy( - status = balanceStatus, + status = + if (walletStore.walletManager != null) balanceStatus else BalanceStatus.Unreachable, currency = walletData.currencyData.currency, currencySymbol = walletData.currencyData.currencySymbol, ), mainButton = WalletMainButton.SendButton(false), ) - } + }, ) } newState = newState.copy( @@ -314,6 +315,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.AppCurrencyAction -> { newState = appCurrencyReducer.reduce(action, newState) } + is WalletAction.UserTokens.Loading -> newState = newState.copy(loadingUserTokens = true) + is WalletAction.UserTokens.Loaded -> newState = newState.copy(loadingUserTokens = false) else -> { /* no-op */ } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt index 6d34bbbf1f..624d34da3e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt @@ -18,7 +18,7 @@ class WalletWarningConverter( is WalletWarning.ExistentialDeposit -> { context.getString( R.string.warning_existential_deposit_message, - message.currencyName, message.currencySymbols, message.existentialDepositString, + message.currencyName, message.edStringValueWithSymbol, ) } is WalletWarning.BalanceNotEnoughForFee -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index af061d48f1..a688bb9e42 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -110,8 +110,12 @@ class WalletAdapter lContent.tvExchangeRate.text = wallet.fiatRateString ?: root.getString(id = R.string.token_item_no_rate) - cardWallet.setOnClickListener { - store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + if (wallet.walletAddresses != null) { + cardWallet.setOnClickListener { + store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + } + } else { + cardWallet.setOnClickListener(null) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 19c1eb1c8a..9d4fe68894 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -13,7 +13,7 @@ import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.currenciesRepository +import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.redux.ProgressState @@ -78,13 +78,16 @@ class MultiWalletView : WalletView() { handleTotalBalance(binding, state.totalBalance) handleBackupWarning(binding, state.showBackupWarning) + handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken) + binding.pbLoadingUserTokens.show(state.loadingUserTokens) + binding.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card store.dispatch( TokensAction.LoadCurrencies( - supportedBlockchains = currenciesRepository.getBlockchains( + supportedBlockchains = CurrenciesRepository.getBlockchains( card.firmwareVersion, card.isTestCard, ), @@ -113,6 +116,16 @@ class MultiWalletView : WalletView() { } } + private fun handleRescanWarning( + binding: FragmentWalletBinding, + showRescanWarning: Boolean, + ) = with(binding.lWalletRescanWarning) { + root.isVisible = showRescanWarning + root.setOnClickListener { + store.dispatch(WalletAction.MultiWallet.ScanToGetDerivations) + } + } + private fun handleTotalBalance( binding: FragmentWalletBinding, totalBalance: TotalBalance?, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index 1e348987fc..e765e738e8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -22,6 +22,7 @@ class SaltPaySingleWalletView : WalletView() { rowButtons.hide() rvPendingTransaction.hide() tvTwinCardNumber.hide() + pbLoadingUserTokens.hide() lCardBalance.root.hide() lAddress.root.hide() lSingleWalletBalance.root.show() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 9b8a2e759f..a0126391a3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth +import com.tangem.tap.common.extensions.getQuantityString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState @@ -36,6 +37,7 @@ class SingleWalletView : WalletView() { rvMultiwallet.hide() btnAddToken.hide() rvPendingTransaction.hide() + pbLoadingUserTokens.hide() lCardBalance.root.show() lAddress.root.show() lSingleWalletBalance.root.hide() @@ -85,11 +87,7 @@ class SingleWalletView : WalletView() { private fun setupTwinCards(twinCardsState: TwinCardsState?, binding: FragmentWalletBinding) = with(binding) { twinCardsState?.cardNumber?.let { cardNumber -> tvTwinCardNumber.show() - val number = when (cardNumber) { - TwinCardNumber.First -> 1 - TwinCardNumber.Second -> 2 - } - tvTwinCardNumber.text = fragment?.getString(R.string.wallet_twins_chip_format, number, 2) + tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, 2) } if (twinCardsState?.cardNumber == null) { tvTwinCardNumber.hide() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index dec1f05bae..35cc636808 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -76,7 +76,12 @@ class MercuryoService( if (!isBuyAllowed()) return false // blockchains which cant be defined by mercuryo service - val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum) + val unsupportedBlockchains = listOf( + Blockchain.Unknown, + Blockchain.Binance, + Blockchain.Arbitrum, + Blockchain.Optimism, + ) val blockchain = currency.blockchain return when (currency) { diff --git a/app/src/main/res/drawable/ic_ethereumfair_no_color.xml b/app/src/main/res/drawable/ic_ethereumfair_no_color.xml new file mode 100644 index 0000000000..dd68efa83e --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumfair_no_color.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumfair_round.xml b/app/src/main/res/drawable/ic_ethereumfair_round.xml new file mode 100644 index 0000000000..b8e6604a7a --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumfair_round.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumpow_no_color.xml b/app/src/main/res/drawable/ic_ethereumpow_no_color.xml new file mode 100644 index 0000000000..aa369fd0ab --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumpow_no_color.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ethereumpow_round.xml b/app/src/main/res/drawable/ic_ethereumpow_round.xml new file mode 100644 index 0000000000..052ac58398 --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereumpow_round.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_scan_card.xml b/app/src/main/res/drawable/ic_scan_card.xml new file mode 100644 index 0000000000..e30ad93bea --- /dev/null +++ b/app/src/main/res/drawable/ic_scan_card.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_tangem_warning.xml b/app/src/main/res/drawable/ic_tangem_warning.xml new file mode 100644 index 0000000000..aa1e0c73ee --- /dev/null +++ b/app/src/main/res/drawable/ic_tangem_warning.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 8bab86feb8..6b7fb30259 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -92,17 +92,6 @@ app:barrierDirection="bottom" app:constraint_referenced_ids="iv_card,tv_twin_card_number" /> - - + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_wallet_rescan_warning.xml b/app/src/main/res/layout/layout_wallet_rescan_warning.xml new file mode 100644 index 0000000000..c795c2c22a --- /dev/null +++ b/app/src/main/res/layout/layout_wallet_rescan_warning.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml index 5cb1360d71..66998f875e 100644 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ b/app/src/main/res/values-de/strings_blockchain.xml @@ -18,10 +18,6 @@ Der Gesamtbetrag geht über die Bilanz hinaus Die Gebühr geht über die Bilanz hinaus Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 16338c6931..7a4a70126b 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -86,5 +86,13 @@ Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network + Scan your card + To access all the networks you need to scan the card + Failed to establish WalletConnect session + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. + Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml index 5bbb85c2f3..197f42551c 100644 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ b/app/src/main/res/values-fr/strings_blockchain.xml @@ -18,10 +18,6 @@ Réduire de %s XTZ Non, envoyer toute la somme Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 42c604b593..5e1c532e79 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -86,5 +86,13 @@ Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network + Scan your card + To access all the networks you need to scan the card + Failed to establish WalletConnect session + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. + Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml index 775778dbb0..92df7b9703 100644 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ b/app/src/main/res/values-it/strings_blockchain.xml @@ -18,10 +18,6 @@ Riduci di %s XTZ No, invia l\'intero importo Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 42c604b593..5e1c532e79 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -86,5 +86,13 @@ Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network + Scan your card + To access all the networks you need to scan the card + Failed to establish WalletConnect session + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. + Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca3e7e93f1..38ee22a3ca 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -129,7 +129,6 @@ Tangem Twin Один кошелек. Две карты. Одна, которую Вы держите в руках, и вторая карта с номером #%s - Карта %1d из %2d Пересоздать Twin кошелек Условия использования карты Tangem Twin diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index 61a4c91467..9ccdfd276e 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -18,5 +18,6 @@ Уменьшить на %s XTZ Нет, отправить все Минимальный баланс: %s + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта. \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 9dae8e2e27..0f8e928ad5 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -97,9 +97,13 @@ Сброс к заводским настройкам Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. Выберите сеть + Отсканируйте карту + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту Memo Tag Недопустимый Memo. Он не будет добавлен в транзакцию. Недопустимый Tag. Он не будет добавлен в транзакцию. + Не удалось установить сессию WalletConnect + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап diff --git a/app/src/main/res/values-v29/zendesk_styles.xml b/app/src/main/res/values-v29/zendesk_styles.xml index 628e83211a..764f632e87 100644 --- a/app/src/main/res/values-v29/zendesk_styles.xml +++ b/app/src/main/res/values-v29/zendesk_styles.xml @@ -1,7 +1,6 @@ - - diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index 6a496314b1..8014c2a199 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -18,10 +18,6 @@ Reduce by %s XTZ No, send all Minimum balance is %s - Memo - Tag - Invalid Memo. It won\'t be added to the transaction - Invalid Tag. It won\'t be added to the transaction - %s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed. + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index b1c5e9121e..306301c3fa 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -99,5 +99,13 @@ Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network + Scan your card + To access all the networks you need to scan the card + Failed to establish WalletConnect session + Memo + Tag + Invalid Memo. It won\'t be added to the transaction. + Invalid Tag. It won\'t be added to the transaction. + Connection with this Dapp cannot be established due to its technical implementation. To start working with the card scan the first card and make a backup diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index afdc44412f..8d6c5017af 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -16,8 +16,6 @@ One wallet. Two cards. This one that you are holding in your hands and the other one with number #%s. - Card %1d of %2d - Re-create twin wallet Card terms of use diff --git a/app/src/main/res/values/zendesk_styles.xml b/app/src/main/res/values/zendesk_styles.xml index ebb4fcab6b..2513114d19 100644 --- a/app/src/main/res/values/zendesk_styles.xml +++ b/app/src/main/res/values/zendesk_styles.xml @@ -1,6 +1,9 @@ - diff --git a/dependencies.gradle b/dependencies.gradle index 465ca3866a..b23c84e56f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,9 +1,8 @@ ext.versions = [ kotlin : '1.6.10', build_gradle : '7.1.3', - // tangem_card_sdk : 'develop-159', - tangem_card_sdk: '0.0.1', - tangem_blockchain_sdk: 'develop-122', + tangem_card_sdk : 'develop-163', + tangem_blockchain_sdk: 'develop-121', // tangem_blockchain_sdk: '0.0.1', ] diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index f8ed090152..35ada82c64 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Blockchain fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { return when (networkId) { "arbitrum-one" -> Blockchain.Arbitrum - "arbitrum/test" -> Blockchain.ArbitrumTestnet + "arbitrum-one/test" -> Blockchain.ArbitrumTestnet "avalanche", "avalanche-2" -> Blockchain.Avalanche "avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet "binancecoin" -> Blockchain.Binance @@ -38,6 +38,9 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "tron/test" -> Blockchain.TronTestnet "xrp", "ripple" -> Blockchain.XRP "xdai" -> Blockchain.Gnosis + "ethereum-pow-iou" -> Blockchain.EthereumPow + "ethereum-pow-iou/test" -> Blockchain.EthereumPowTestnet + "ethereumfair" -> Blockchain.EthereumFair "polkadot" -> Blockchain.Polkadot "polkadot/test" -> Blockchain.PolkadotTestnet "kusama" -> Blockchain.Kusama @@ -87,6 +90,9 @@ fun Blockchain.toNetworkId(): String { Blockchain.Tron -> "tron" Blockchain.TronTestnet -> "tron/test" Blockchain.Gnosis -> "xdai" + Blockchain.EthereumPow -> "ethereum-pow-iou" + Blockchain.EthereumPowTestnet -> "ethereum-pow-iou/test" + Blockchain.EthereumFair -> "ethereumfair" Blockchain.Polkadot -> "polkadot" Blockchain.PolkadotTestnet -> "polkadot/test" Blockchain.Kusama -> "kusama" @@ -94,7 +100,6 @@ fun Blockchain.toNetworkId(): String { Blockchain.OptimismTestnet -> "optimistic-ethereum/test" Blockchain.Dash -> "dash" Blockchain.SaltPay -> "wxdai" - else -> "unknown" // TODO } } @@ -120,11 +125,13 @@ fun Blockchain.toCoinId(): String { Blockchain.Tezos -> "tezos" Blockchain.XRP -> "ripple" Blockchain.Dogecoin -> "dogecoin" - Blockchain.Gnosis, Blockchain.SaltPay -> "xdai" + Blockchain.Gnosis -> "xdai" + Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou" + Blockchain.EthereumFair -> "ethereumfair" Blockchain.Kusama -> "kusama" Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum" Blockchain.Dash -> "dash" + Blockchain.SaltPay -> "xdai" Blockchain.Unknown -> "unknown" - else -> "unknown" // TODO } } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/ByteArray.kt b/domain/src/main/java/com/tangem/domain/common/extensions/ByteArray.kt new file mode 100644 index 0000000000..fe59135906 --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/extensions/ByteArray.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.common.extensions + +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +fun ByteArray.calculateHmacSha256(key: ByteArray): ByteArray { + val mac: Mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec(key, "HmacSHA256")) + return mac.doFinal(this) +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt index 502452e476..a502ae5041 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt @@ -26,7 +26,7 @@ class AddCustomTokenService( result.data.coins.forEach { coin -> val networksWithTheSameAddress = coin.networks .filter { it.contractAddress != null || it.decimalCount != null } - .filter { it.contractAddress == contractAddress } + .filter { it.contractAddress?.equals(contractAddress, ignoreCase = true) == true } .filter { supportedTokenNetworkIds.contains(it.networkId) } if (networksWithTheSameAddress.isNotEmpty()) { val newToken = coin.copy(networks = networksWithTheSameAddress) @@ -45,6 +45,5 @@ class AddCustomTokenService( private fun selectNetworksForSearch(networkId: String?): String { return networkId ?: supportedTokenNetworkIds.joinToString(",") - } } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index 1c09b4116a..dfc524e857 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -5,7 +5,9 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.AddCustomTokenError -import com.tangem.domain.AddCustomTokenError.Warning.* +import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken +import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded +import com.tangem.domain.AddCustomTokenError.Warning.UnsupportedSolanaToken import com.tangem.domain.AddCustomTokenException import com.tangem.domain.DomainDialog import com.tangem.domain.DomainWrapped @@ -14,10 +16,40 @@ import com.tangem.domain.common.extensions.canHandleToken import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.form.* -import com.tangem.domain.features.addCustomToken.* -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* -import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.* +import com.tangem.domain.common.form.Field +import com.tangem.domain.common.form.Form +import com.tangem.domain.common.form.TokenContractAddressValidator +import com.tangem.domain.common.form.TokenDecimalsValidator +import com.tangem.domain.common.form.TokenNameValidator +import com.tangem.domain.common.form.TokenNetworkValidator +import com.tangem.domain.common.form.TokenSymbolValidator +import com.tangem.domain.features.addCustomToken.AddCustomTokenService +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol +import com.tangem.domain.features.addCustomToken.TokenBlockchainField +import com.tangem.domain.features.addCustomToken.TokenDerivationPathField +import com.tangem.domain.features.addCustomToken.TokenField +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.FieldError +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Init +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnAddCustomTokenClicked +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnCreate +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnDestroy +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Screen +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.SetFoundTokenInfo +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.UpdateForm +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.Warning +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState.Companion.createInitialScreenState import com.tangem.domain.redux.BaseStoreHub import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.ReStoreReducer @@ -569,12 +601,12 @@ private class AddCustomTokenReducer( tangemTechService = globalState.networkServices.tangemTechService, supportedTokenNetworkIds = supportedTokenNetworkIds ) - val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain)) state.copy( cardDerivationStyle = card.derivationStyle, form = form, tangemTechServiceManager = tangemTechServiceManager, + screenState = createInitialScreenState(card.settings.isHDWalletAllowed), ) } is OnDestroy -> { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index 97c652da19..2ecd595e8e 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -9,9 +9,31 @@ import com.tangem.domain.DomainWrapped import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.extensions.supportedBlockchains import com.tangem.domain.common.extensions.supportedTokens -import com.tangem.domain.common.form.* -import com.tangem.domain.features.addCustomToken.* -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* +import com.tangem.domain.common.form.CustomTokenValidator +import com.tangem.domain.common.form.DataField +import com.tangem.domain.common.form.FieldDataConverter +import com.tangem.domain.common.form.FieldId +import com.tangem.domain.common.form.FieldToJsonConverter +import com.tangem.domain.common.form.Form +import com.tangem.domain.common.form.StringIsEmptyValidator +import com.tangem.domain.common.form.StringIsNotEmptyValidator +import com.tangem.domain.common.form.TokenContractAddressValidator +import com.tangem.domain.common.form.TokenDecimalsValidator +import com.tangem.domain.common.form.TokenNameValidator +import com.tangem.domain.common.form.TokenNetworkValidator +import com.tangem.domain.common.form.TokenSymbolValidator +import com.tangem.domain.features.addCustomToken.AddCustomTokenService +import com.tangem.domain.features.addCustomToken.CustomCurrency +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol +import com.tangem.domain.features.addCustomToken.TokenBlockchainField +import com.tangem.domain.features.addCustomToken.TokenDerivationPathField +import com.tangem.domain.features.addCustomToken.TokenField import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.state.StringActionStateConverter import com.tangem.network.api.tangemTech.CoinsResponse @@ -119,7 +141,7 @@ data class AddCustomTokenState( formErrors = emptyMap(), foundToken = null, warnings = emptySet(), - screenState = createInitialScreenState(), + screenState = createInitialScreenState(card.settings.isHDWalletAllowed), tangemTechServiceManager = null, ) } @@ -223,15 +245,15 @@ data class AddCustomTokenState( return (listOf(Blockchain.Unknown) + evmBlockchains).sortByName() } - private fun createInitialScreenState(): ScreenState { + internal fun createInitialScreenState(showDerivationPathField: Boolean = false): ScreenState { return ScreenState( contractAddressField = ViewStates.TokenField(), network = ViewStates.TokenField(), name = ViewStates.TokenField(isEnabled = false), symbol = ViewStates.TokenField(isEnabled = false), decimals = ViewStates.TokenField(isEnabled = false), - derivationPath = ViewStates.TokenField(), - addButton = ViewStates.AddButton(isEnabled = false) + derivationPath = ViewStates.TokenField(isVisible = showDerivationPathField), + addButton = ViewStates.AddButton(isEnabled = false), ) } } diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt index 9a4b2cd8b1..4afd9ed2ef 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -46,5 +46,27 @@ data class CurrenciesResponse(val currencies: List) { } data class GeoResponse( - val code: String -) : TangemTechResponse \ No newline at end of file + val code: String, +) : TangemTechResponse + +data class UserTokensResponse( + val version: Int = 0, + val group: String = "", + val sort: String = "", + val tokens: List = emptyList(), +) : TangemTechResponse + +data class TokenResponse( + val id: String, + val networkId: String, + val derivationPath: String, + val name: String, + val symbol: String, + val decimals: Int, + val contractAddress: String?, +) : TangemTechResponse + +data class TangemTechError( + val code: Int, + val description: String, +) \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt index 80558732dd..518edd9fec 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt @@ -1,6 +1,9 @@ package com.tangem.network.api.tangemTech +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path import retrofit2.http.Query /** @@ -29,4 +32,10 @@ interface TangemTechApi { @GET("geo") suspend fun geo(): GeoResponse + + @GET("user-tokens/{user-id}") + suspend fun getUserTokens(@Path(value = "user-id") userId: String): UserTokensResponse + + @PUT("user-tokens/{user-id}") + suspend fun putUserTokens(@Path(value = "user-id") userId: String, @Body userTokens: UserTokensResponse) } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt index b52b34c013..6137e8dfa0 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt @@ -58,6 +58,15 @@ class TangemTechService( performRequest { api.currencies() } } + suspend fun getUserTokens(userId: String): Result = withContext(Dispatchers.IO) { + performRequest { api.getUserTokens(userId) } + } + + suspend fun putUserTokens(userId: String, userTokens: UserTokensResponse): Result = + withContext(Dispatchers.IO) { + performRequest { api.putUserTokens(userId, userTokens) } + } + fun addHeaderInterceptors(interceptors: List) { headerInterceptors.removeAll(interceptors) headerInterceptors.addAll(interceptors)