diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 4674adf0e6..18cfcf9ff3 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -12,6 +12,7 @@ import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.domain.DomainLayer import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.AndroidAssetReader +import com.tangem.tap.common.AssetReader import com.tangem.tap.common.analytics.GlobalAnalyticsHandler import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager @@ -25,7 +26,7 @@ import com.tangem.tap.domain.configurable.config.Config import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.domain.tokens.CurrenciesRepository +import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.persistence.PreferencesStorage @@ -39,16 +40,14 @@ val store = Store( state = AppState(), ) val logConfig = LogConfig() - lateinit var foregroundActivityObserver: ForegroundActivityObserver - lateinit var preferencesStorage: PreferencesStorage -lateinit var currenciesRepository: CurrenciesRepository lateinit var walletConnectRepository: WalletConnectRepository lateinit var shopService: TangemShopService +lateinit var assetReader: AssetReader +lateinit var userTokensRepository: UserTokensRepository class TapApplication : Application(), ImageLoaderFactory { - override fun onCreate() { super.onCreate() @@ -62,14 +61,19 @@ class TapApplication : Application(), ImageLoaderFactory { DomainLayer.init() NetworkConnectivity.createInstance(store, this) preferencesStorage = PreferencesStorage(this) - currenciesRepository = CurrenciesRepository(this, store.state.domainNetworks.tangemTechService) walletConnectRepository = WalletConnectRepository(this) - val configLoader = FeaturesLocalLoader(AndroidAssetReader(this), MoshiConverter.defaultMoshi()) + assetReader = AndroidAssetReader(this) + val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.defaultMoshi()) initConfigManager(configLoader, ::initWithConfigDependency) initWarningMessagesManager() BlockchainSdkRetrofitBuilder.enableNetworkLogging = BuildConfig.DEBUG + + userTokensRepository = UserTokensRepository.init( + context = this, + tangemTechService = store.state.domainNetworks.tangemTechService, + ) } override fun newImageLoader(): ImageLoader { diff --git a/app/src/main/java/com/tangem/tap/common/FileReader.kt b/app/src/main/java/com/tangem/tap/common/FileReader.kt new file mode 100644 index 0000000000..49e08b5bcb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/FileReader.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.common + +import android.content.Context +import com.tangem.tap.common.extensions.readFile +import com.tangem.tap.common.extensions.rewriteFile + +interface FileReader { + fun readFile(fileName: String): String + fun rewriteFile(content: String, fileName: String) +} + +class AndroidFileReader(private val context: Context) : FileReader { + override fun readFile(fileName: String): String { + return context.readFile(fileName) + } + + override fun rewriteFile(content: String, fileName: String) { + context.rewriteFile(content, fileName) + } +} \ 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 4d227fcd92..bc9d9399dc 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 @@ -11,7 +11,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 @@ -24,6 +23,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 @@ -134,7 +134,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 674487ff78..a2b994507a 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 @@ -66,7 +67,6 @@ sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemE object CardNotSupportedByRelease : TapSdkError(R.string.error_update_app) } - fun TapErrors.assembleErrors(): MutableList?>> { val idList = mutableListOf?>>() when (this) { @@ -74,4 +74,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 55e86e7368..4f64bc1d20 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,23 +23,21 @@ 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 { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() } - private val walletManagersThrottler = ThrottlerWithValues>(10000) @@ -65,16 +62,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, + ), ) } } @@ -139,22 +136,52 @@ class TapWalletManager { } private suspend fun loadMultiWalletData( - scanResponse: ScanResponse + scanResponse: ScanResponse, ) { - val savedCurrencies = currenciesRepository.loadSavedCurrencies( - scanResponse.card.cardId, scanResponse.card.settings.isHDWalletAllowed - ) - if (savedCurrencies.isEmpty()) return + when (val tokensResult = userTokensRepository.getUserTokens(scanResponse.card)) { + is Result.Success -> { + withMainContext { + val blockchainNetworks = tokensResult.data.toBlockchainNetworks() + val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, tokensResult.data) + store.dispatch( + WalletAction.MultiWallet.AddBlockchains( + blockchains = blockchainNetworks, + walletManagers = walletManagers, + save = false, + ), + ) - val walletManagers = - walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies) - dispatchOnMain( - WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers), - ) - savedCurrencies.map { - if (it.tokens.isNotEmpty()) { - dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it)) + blockchainNetworks.filter { it.tokens.isNotEmpty() } + .map { + store.dispatch( + WalletAction.MultiWallet.AddTokens( + tokens = it.tokens, + blockchain = it, + save = false, + ), + ) + } + checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) + } } + is Result.Failure -> { + return + } + } + } + + private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) { + blockchainNetworks.map { + if (it.tokens.isNotEmpty()) { + 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)) } } @@ -172,9 +199,10 @@ class TapWalletManager { } dispatchOnMain( WalletAction.MultiWallet.AddBlockchains( - listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), - listOf(primaryWalletManager) - ) + blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), + walletManagers = listOf(primaryWalletManager), + save = false, + ), ) } } 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 34b8289884..90981e2359 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 @@ -1,6 +1,10 @@ package com.tangem.tap.domain.extensions -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve @@ -99,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 e54c3744cb..485ee0eaea 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 @@ -32,16 +32,14 @@ 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.CurrenciesRepository +import com.tangem.tap.domain.tokens.UserTokensRepository 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( @@ -62,7 +60,7 @@ class ScanProductTask( val commandProcessor = when { card.isTangemNote() -> ScanNoteProcessor() card.isTangemTwins() -> ScanTwinProcessor() - else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive) + else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive) } commandProcessor.proceed(card, session) { processorResult -> when (processorResult) { @@ -111,8 +109,8 @@ private class ScanNoteProcessor : ProductCommandProcessor { } 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 @@ -184,47 +182,41 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - scope.launch { - 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 - } + 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 + } - 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)) + 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.settings.isHDWalletAllowed).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), @@ -236,7 +228,7 @@ private class ScanWalletProcessor( listOf( BlockchainNetwork(Blockchain.Ethereum, card), BlockchainNetwork(Blockchain.EthereumTestnet, card), - ) + ), ) } if (additionalBlockchainsToDerive != null) { @@ -256,16 +248,14 @@ 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 - val key = wallet.publicKey.toMapKey() val path = blockchain.derivationPath?.let { DerivationPath(it) } if (path != null) { 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..58c3fb08a4 --- /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): Result> { + if (DemoHelper.isDemoCardId(card.cardId)) { + return Result.Success(loadDemoCurrencies()) + } + val userId = card.getUserId() + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + return Result.Success(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) + Result.Success(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, + ): Result> { + return when (error) { + is NoDataError -> { + val tokens = storageService.getUserTokens(card) + coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = tokens) } } + Result.Success(tokens) + } + else -> { + val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) + Result.Success(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 = "AccountID" + 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/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/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 7825d58a8f..0351eb207b 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 @@ -108,11 +107,9 @@ class DetailsMiddleware { } 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 -> @@ -123,7 +120,6 @@ class DetailsMiddleware { ) } } - } } } } 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 8fdcfa10ce..9a898f708d 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,6 +2,7 @@ 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 @@ -12,12 +13,11 @@ 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 @@ -189,15 +189,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( @@ -230,20 +225,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( @@ -253,11 +238,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( @@ -306,17 +287,27 @@ 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 blockchainNetwork = BlockchainNetwork( + blockchain = blockchainToMake, + derivationPath = wallet.derivationPath?.rawPath, + tokens = emptyList(), + ) + return walletState.getWalletManager(blockchainNetwork) } } \ No newline at end of file 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 374b8d4406..6b0bf8e8e0 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 @@ -78,30 +78,35 @@ 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)) - 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/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 391d65cb96..25fc8e2f38 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 @@ -94,8 +94,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) @@ -273,15 +273,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/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/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 0d6e8b528a..ed617762f4 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 @@ -54,22 +53,21 @@ sealed class WalletAction : Action { 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 +80,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..d6f0def028 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,7 @@ data class WalletState( val isTestnet: Boolean = false, val totalBalance: TotalBalance? = null, val showBackupWarning: Boolean = false, + val missingDerivations: List = emptyList(), ) : StateType { // if you do not delegate - the application crashes on startup, @@ -131,32 +129,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) 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 ecf9579bb9..256e72fd37 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..52ab9c4d75 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 } @@ -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 a4b679512e..79aa56c3df 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 @@ -145,18 +145,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( 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 73504ddbfc..9579399e28 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 @@ -12,7 +12,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 @@ -76,13 +76,14 @@ 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.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card store.dispatch( TokensAction.LoadCurrencies( - supportedBlockchains = currenciesRepository.getBlockchains( + supportedBlockchains = CurrenciesRepository.getBlockchains( card.firmwareVersion, card.isTestCard, ), @@ -111,6 +112,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/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/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index d5daad21ea..274f8303fd 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -93,17 +93,6 @@ app:barrierDirection="bottom" app:constraint_referenced_ids="iv_card,tv_twin_card_number" /> - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 13aa0a643a..f32a97ae89 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -86,4 +86,6 @@ 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 \ 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 737bdd59c1..ef550053ca 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -86,4 +86,6 @@ 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 \ 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 737bdd59c1..ef550053ca 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -86,4 +86,6 @@ 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 \ 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 09b2609172..b1ffa0ba10 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -102,4 +102,6 @@ Недопустимый Memo. Он не будет добавлен в транзакцию. Недопустимый Tag. Он не будет добавлен в транзакцию. Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. + Отсканируйте карту + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index adee69f918..4585bee0c1 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -99,4 +99,6 @@ 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 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/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 3bf3bd232e..9f93eddcf9 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 @@ -13,18 +13,16 @@ import kotlinx.coroutines.withContext */ class TangemTechService { private val headerInterceptors = mutableListOf( - CacheControlHttpInterceptor(cacheMaxAge) + CacheControlHttpInterceptor(cacheMaxAge), ) - private var api: TangemTechApi = createApi() - suspend fun coins( contractAddress: String? = null, networkIds: String? = null, active: Boolean? = null, searchText: String? = null, offset: Int? = null, - limit: Int? = null + limit: Int? = null, ): Result = withContext(Dispatchers.IO) { performRequest { api.coins( @@ -33,14 +31,14 @@ class TangemTechService { active = active, searchText = searchText, offset = offset, - limit = limit + limit = limit, ) } } suspend fun rates( currency: String, - ids: List + ids: List, ): Result = withContext(Dispatchers.IO) { performRequest { api.rates(currency.lowercase(), ids.joinToString(",")) @@ -55,6 +53,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) @@ -65,7 +72,7 @@ class TangemTechService { val retrofit = createRetrofitInstance( baseUrl = baseUrl, interceptors = headerInterceptors.toList(), -// logEnabled = true, + logEnabled = true, ) return retrofit.create(TangemTechApi::class.java) }