diff --git a/app/build.gradle b/app/build.gradle index 42f01b8a33..c28376784a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-71' + implementation 'com.tangem:blockchain:develop-76' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142' diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index e1e7139851..3ba511f652 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -1,13 +1,13 @@ { - "tokens" : [ + "coins" : [ { - "id" : "polygon-pos/test", + "id" : "matic-token", "symbol" : "MATIC", "name" : "Polygon", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0x0000000000000000000000000000000000001010", + "contractAddress" : "0x0000000000000000000000000000000000001010", "decimalCount" : 18 } ] @@ -16,20 +16,15 @@ "id" : "Dai Stablecoin-DAI", "symbol" : "DAI", "name" : "Dai Stablecoin", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1", + "contractAddress" : "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1", "decimalCount" : 18 }, { "networkId" : "binance-smart-chain/test", - "address" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867", - "decimalCount" : 18 - }, - { - "networkId" : "binance-smart-chain/test", - "address" : "0x8a9424745056eb399fd19a0ec26a14316684e274", + "contractAddress" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867", "decimalCount" : 18 } ] @@ -38,10 +33,10 @@ "id" : "Dummy ERC20-DERC20", "symbol" : "DERC20", "name" : "Dummy ERC20", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1", + "contractAddress" : "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1", "decimalCount" : 18 } ] @@ -50,15 +45,15 @@ "id" : "Ether-ETH", "symbol" : "ETH", "name" : "Ether", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323", + "contractAddress" : "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323", "decimalCount" : 18 }, { "networkId" : "binance-smart-chain/test", - "address" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378", + "contractAddress" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378", "decimalCount" : 18 } ] @@ -67,10 +62,10 @@ "id" : "Test Token-TST", "symbol" : "TST", "name" : "Test Token", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e", + "contractAddress" : "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e", "decimalCount" : 18 } ] @@ -79,20 +74,15 @@ "id" : "Tether USD-USDT", "symbol" : "USDT", "name" : "Tether USD", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0x3813e82e6f7098b9583FC0F33a962D02018B6803", + "contractAddress" : "0x3813e82e6f7098b9583FC0F33a962D02018B6803", "decimalCount" : 6 }, { "networkId" : "binance-smart-chain/test", - "address" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd", - "decimalCount" : 18 - }, - { - "networkId" : "binance-smart-chain/test", - "address" : "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684", + "contractAddress" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd", "decimalCount" : 18 } ] @@ -101,10 +91,10 @@ "id" : "Wrapped Ether-WETH", "symbol" : "WETH", "name" : "Wrapped Ether", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa", + "contractAddress" : "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa", "decimalCount" : 18 } ] @@ -113,30 +103,32 @@ "id" : "Wrapped Matic-WMATIC", "symbol" : "WMATIC", "name" : "Wrapped Matic", - "contracts" : [ + "networks" : [ { "networkId" : "polygon-pos/test", - "address" : "0xd0A1E359811322d97991E03f863a0C30C2cF029C", + "contractAddress" : "0xd0A1E359811322d97991E03f863a0C30C2cF029C", "decimalCount" : 18 } ] }, { - "id" : "solana/test", + "id" : "solana", "symbol" : "SOL", "name" : "Solana", - "contracts" : [ - + "networks" : [ + { + "networkId" : "solana/test" + } ] }, { "id" : "Tangem Coin A-TCA", "symbol" : "TCA", "name" : "Tangem Coin A", - "contracts" : [ + "networks" : [ { "networkId" : "solana/test", - "address" : "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR", + "contractAddress" : "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR", "decimalCount" : 9 } ] @@ -145,50 +137,47 @@ "id" : "Tangem Coin B-TCB", "symbol" : "TCB", "name" : "Tangem Coin B", - "contracts" : [ + "networks" : [ { "networkId" : "solana/test", - "address" : "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4", + "contractAddress" : "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4", "decimalCount" : 9 } ] }, { - "id" : "binancecoin/test", + "id" : "binancecoin", "symbol" : "BNB", "name" : "Binance", - "contracts" : [ - + "networks" : [ + { + "networkId" : "binancecoin/test" + }, + { + "networkId" : "binance-smart-chain/test" + } ] }, { "id" : "Hemster - 452-HEM", "symbol" : "HEM", "name" : "Hemster - 452", - "contracts" : [ + "networks" : [ { "networkId" : "binancecoin/test", - "address" : "HEM-452", + "contractAddress" : "HEM-452", "decimalCount" : 8 } ] }, - { - "id" : "binance-smart-chain/test", - "symbol" : "BNB", - "name" : "Bsc", - "contracts" : [ - - ] - }, { "id" : "Binance-Peg BTCB Token-BTCB", "symbol" : "BTCB", "name" : "Binance-Peg BTCB Token", - "contracts" : [ + "networks" : [ { "networkId" : "binance-smart-chain/test", - "address" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8", + "contractAddress" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8", "decimalCount" : 18 } ] @@ -197,10 +186,10 @@ "id" : "Binance-Peg BUSD Token-BUSD", "symbol" : "BUSD", "name" : "Binance-Peg BUSD Token", - "contracts" : [ + "networks" : [ { "networkId" : "binance-smart-chain/test", - "address" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee", + "contractAddress" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee", "decimalCount" : 18 } ] @@ -209,10 +198,10 @@ "id" : "Binance-Peg USDC Token-USDC", "symbol" : "USDC", "name" : "Binance-Peg USDC Token", - "contracts" : [ + "networks" : [ { "networkId" : "binance-smart-chain/test", - "address" : "0x64544969ed7ebf5f083679233325356ebe738930", + "contractAddress" : "0x64544969ed7ebf5f083679233325356ebe738930", "decimalCount" : 18 } ] @@ -221,30 +210,32 @@ "id" : "Binance-Peg XRP-XRP", "symbol" : "XRP", "name" : "Binance-Peg XRP", - "contracts" : [ + "networks" : [ { "networkId" : "binance-smart-chain/test", - "address" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9", + "contractAddress" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9", "decimalCount" : 18 } ] }, { - "id" : "ethereum/test", + "id" : "ethereum", "symbol" : "ETH", "name" : "Ethereum", - "contracts" : [ - + "networks" : [ + { + "networkId" : "ethereum/test" + } ] }, { "id" : "Weenus-WEENUS", "symbol" : "WEENUS", "name" : "Weenus", - "contracts" : [ + "networks" : [ { - "networkId" : "ethereum", - "address" : "0xaFF4481D10270F50f203E0763e2597776068CBc5", + "networkId" : "ethereum/test", + "contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5", "decimalCount" : 18 } ] @@ -253,10 +244,10 @@ "id" : "Xeenus-XEENUS", "symbol" : "XEENUS", "name" : "Xeenus", - "contracts" : [ + "networks" : [ { "networkId" : "ethereum/test", - "address" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c", + "contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c", "decimalCount" : 18 } ] @@ -265,10 +256,10 @@ "id" : "Yeenus-YEENUS", "symbol" : "YEENUS", "name" : "Yeenus", - "contracts" : [ + "networks" : [ { "networkId" : "ethereum/test", - "address" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7", + "contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7", "decimalCount" : 8 } ] @@ -277,30 +268,32 @@ "id" : "Zeenus-ZEENUS", "symbol" : "ZEENUS", "name" : "Zeenus", - "contracts" : [ + "networks" : [ { "networkId" : "ethereum/test", - "address" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e", + "contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e", "decimalCount" : 0 } ] }, { - "id" : "avalanche/test", + "id" : "avalanche-2", "symbol" : "AVAX", "name" : "Avalanche", - "contracts" : [ - + "networks" : [ + { + "networkId" : "avalanche/test" + } ] }, { "id" : "The Fuji stablecoin-FUJISTABLE", "symbol" : "FUJISTABLE", "name" : "The Fuji stablecoin", - "contracts" : [ + "networks" : [ { "networkId" : "avalanche/test", - "address" : "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22", + "contractAddress" : "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22", "decimalCount" : 6 } ] @@ -309,30 +302,32 @@ "id" : "To the Moon-FUJIMOON", "symbol" : "FUJIMOON", "name" : "To the Moon", - "contracts" : [ + "networks" : [ { "networkId" : "avalanche/test", - "address" : "0x97132C109c6816525F7f338DCb7435E1412A7668", + "contractAddress" : "0x97132C109c6816525F7f338DCb7435E1412A7668", "decimalCount" : 9 } ] }, { - "id" : "fantom/test", + "id" : "fantom", "symbol" : "FTM", "name" : "Fantom", - "contracts" : [ - + "networks" : [ + { + "networkId" : "fantom/test" + } ] }, { "id" : "Fantom USD-FUSD", "symbol" : "FUSD", "name" : "Fantom USD", - "contracts" : [ + "networks" : [ { "networkId" : "fantom/test", - "address" : "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074", + "contractAddress" : "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074", "decimalCount" : 18 } ] @@ -341,29 +336,36 @@ "id" : "Wrapped Fantom-WFTM", "symbol" : "WFTM", "name" : "Wrapped Fantom", - "contracts" : [ + "networks" : [ { "networkId" : "fantom/test", - "address" : "0xf1277d1Ed8AD466beddF92ef448A132661956621", + "contractAddress" : "0xf1277d1Ed8AD466beddF92ef448A132661956621", "decimalCount" : 18 } ] }, { - "id" : "bitcoin/test", + "id" : "bitcoin", "symbol" : "BTC", "name" : "Bitcoin", - "contracts" : [ - + "networks" : [ + { + "networkId" : "bitcoin/test" + } ] }, { - "id" : "stellar/test", + "id" : "stellar", "symbol" : "XLM", "name" : "Stellar", - "contracts" : [ - + "networks" : [ + { + "networkId" : "stellar/test" + } ] } - ] -} \ No newline at end of file + ], + + "total" : 0, + "imageHost" : "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/" +} diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt index fd51dd7e45..d0182be40e 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions import androidx.annotation.DrawableRes import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.remove import com.tangem.wallet.R @DrawableRes @@ -57,4 +58,7 @@ fun Blockchain.getNetworkName(): String { Blockchain.Binance, Blockchain.BinanceTestnet -> "BEP2" else -> "" } -} \ No newline at end of file +} + +val Blockchain.fullNameWithoutTestnet +get() = this.fullName.remove(" Testnet") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt new file mode 100644 index 0000000000..4c9cafbdee --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt @@ -0,0 +1,83 @@ +package com.tangem.tap.domain + +import com.tangem.common.services.Result +import com.tangem.domain.common.ThrottlerWithValues +import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.store +import java.math.BigDecimal + +//TODO: refactoring: move to domain +class RatesRepository { + + private val tangemTechService: TangemTechService + get() = store.state.domainNetworks.tangemTechService + + private val throttler = ThrottlerWithValues?>(60000) + + suspend fun loadFiatRate(currencyId: String, coinsList: List): Result { +// get and submit previous result of equivalents. + val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map { + Pair(it, throttler.geValue(it)) + } + if (throttledResult.isNotEmpty()) { + return handleFiatRatesResult(throttledResult.toMap()) + } + + val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) } + val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct() + if (coinIds.isEmpty()) return EMPTY_RESULT + + return when (val result = tangemTechService.rates(currencyId, coinIds)) { + is Result.Success -> { + val ratesResultList: Map> = result.data.rates.mapValues { + Result.Success(it.value.toBigDecimal()) + } + val updatedCurrencies = mutableMapOf?>() + currenciesToUpdate.forEach { currency -> + ratesResultList[currency.coinId]?.let { + updatedCurrencies[currency] = it + throttler.updateThrottlingTo(currency) + throttler.setValue(currency, it) + } + } + handleFiatRatesResult(updatedCurrencies) + } + is Result.Failure -> Result.Failure(result.error) + } + } + + private fun handleFiatRatesResult(rates: Map?>): Result.Success { + val success = mutableMapOf() + val failures = mutableMapOf() + + rates.mapNotNull { (currency, priceResult) -> + when (priceResult) { + is Result.Success -> success[currency] = priceResult.data + is Result.Failure -> failures[currency] = priceResult.error + else -> null + } + } + + return Result.Success(success to failures) + } + + fun clear() { + throttler.clear() + } + + companion object { + val EMPTY_RESULT = Result.Success(Pair( + mutableMapOf(), + mutableMapOf() + )) + } +} + +typealias RatesResult = Pair, MutableMap> + +val RatesResult.loadedRates + get() = this.first + +val RatesResult.failedRates + get() = this.second \ No newline at end of file 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 87de3dfcd7..42f82fe17c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -6,12 +6,10 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.domain.common.ThrottlerWithValues import com.tangem.domain.common.extensions.withMainContext -import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.tap.common.ThrottlerWithValues import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.configurable.config.ConfigManager @@ -20,28 +18,24 @@ import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.domain.tokens.BlockchainNetwork import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.math.BigDecimal class TapWalletManager { val walletManagerFactory: WalletManagerFactory by lazy { WalletManagerFactory(blockchainSdkConfig) } - private val tangemTechService: TangemTechService - get() = store.state.domainNetworks.tangemTechService + val rates: RatesRepository = RatesRepository() private val blockchainSdkConfig by lazy { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() } private val walletManagersThrottler = ThrottlerWithValues>(10000) - private val fiatRatesThrottler = ThrottlerWithValues?>(60000) suspend fun loadWalletData(walletManager: WalletManager) { val blockchain = walletManager.wallet.blockchain @@ -49,7 +43,10 @@ class TapWalletManager { val result = if (walletManagersThrottler.isStillThrottled(blockchain)) { walletManagersThrottler.geValue(blockchain)!! } else { - updateThrottlingForWalletManager(walletManager) + val safeUpdateResult = walletManager.safeUpdate() + walletManagersThrottler.updateThrottlingTo(blockchain) + walletManagersThrottler.setValue(blockchain, safeUpdateResult) + safeUpdateResult } when (result) { is Result.Success -> { @@ -58,92 +55,25 @@ class TapWalletManager { is Result.Failure -> { when (result.error) { is TapError.WalletManager.NoAccountError -> { - dispatchOnMain( - WalletAction.LoadWallet.NoAccount( - walletManager.wallet, - blockchainNetwork, - (result.error as TapError.WalletManager.NoAccountError).customMessage - ) - ) - } - else -> { - dispatchOnMain( - WalletAction.LoadWallet.Failure( - walletManager.wallet, - result.error.localizedMessage - ) - ) - } - } - } - } - } - - private suspend fun updateThrottlingForWalletManager(walletManager: WalletManager): Result { - val newResult = walletManager.safeUpdate() - val blockchain = walletManager.wallet.blockchain - walletManagersThrottler.updateThrottlingTo(blockchain) - walletManagersThrottler.setValue(blockchain, newResult) - return newResult - } - - suspend fun loadFiatRate(currencyId: FiatCurrencyName, wallet: Wallet) { - val coinsList = wallet.getTokens() - .map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) } - .plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath)) - loadFiatRate(currencyId, coinsList) - } - - suspend fun loadFiatRate(currencyId: FiatCurrencyName, coinsList: List) { - suspend fun handleFiatRatesResult(rates: Map?>) { - rates.forEach { (currency, priceResult) -> - when (priceResult) { - is Result.Success -> { - dispatchOnMain(WalletAction.LoadFiatRate.Success( - currency to priceResult.data + dispatchOnMain(WalletAction.LoadWallet.NoAccount( + walletManager.wallet, + blockchainNetwork, + (result.error as TapError.WalletManager.NoAccountError).customMessage )) } - is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure) - null -> {} - } - } - } - - // get and submit previous result of equivalents. - val throttledResult = coinsList.filter { fiatRatesThrottler.isStillThrottled(it) }.map { - Pair(it, fiatRatesThrottler.geValue(it)) - } - if (throttledResult.isNotEmpty()) { - handleFiatRatesResult(throttledResult.toMap()) - } - - val currenciesToUpdate = coinsList.filter { !fiatRatesThrottler.isStillThrottled(it) } - val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct() - if (coinIds.isEmpty()) return - - //TODO: refactoring: move fiatRatesThrottler to the TangemTechRepository - when (val result = tangemTechService.rates(currencyId, coinIds)) { - is Result.Success -> { - val ratesResultList: Map> = result.data.rates.mapValues { - Result.Success(it.value.toBigDecimal()) - } - val updatedCurrencies = mutableMapOf?>() - currenciesToUpdate.forEach { currency -> - ratesResultList[currency.coinId]?.let { - updatedCurrencies[currency] = it - fiatRatesThrottler.updateThrottlingTo(currency) - fiatRatesThrottler.setValue(currency, it) + else -> { + dispatchOnMain(WalletAction.LoadWallet.Failure( + walletManager.wallet, + result.error.localizedMessage + )) } } - handleFiatRatesResult(updatedCurrencies) } - is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure) } } suspend fun onCardScanned(data: ScanResponse) { walletManagersThrottler.clear() -// fiatRatesThrottler.clear() store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data) updateConfigManager(data) @@ -194,22 +124,18 @@ class TapWalletManager { if (data.card.isMultiwalletAllowed) { loadMultiWalletData(data, blockchain, primaryWalletManager) } else { - dispatchOnMain( - WalletAction.MultiWallet.AddBlockchains( - listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), - listOf(primaryWalletManager) - ) - ) + dispatchOnMain(WalletAction.MultiWallet.AddBlockchains( + listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), + listOf(primaryWalletManager) + )) } } else { if (data.card.isMultiwalletAllowed) { loadMultiWalletData(data, blockchain, null) } } - dispatchOnMain( - WalletAction.LoadWallet(), - WalletAction.LoadFiatRate() - ) + dispatchOnMain(WalletAction.LoadWallet()) + dispatchOnMain(WalletAction.LoadFiatRate()) } private suspend fun loadMultiWalletData( @@ -233,7 +159,6 @@ class TapWalletManager { ) } else { - val derivationStyle = scanResponse.card.derivationStyle val blockchainNetworks = listOf( BlockchainNetwork(Blockchain.Bitcoin, scanResponse.card), BlockchainNetwork(Blockchain.Ethereum, scanResponse.card) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt index 1d39e53861..d3f7730510 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt @@ -21,12 +21,15 @@ fun CurrencyExchangeStatus.buyIsAllowed(currency: Currency): Boolean { if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false if (!isBuyAllowed) return false + //TODO: temporary, for the 3.32 release, unlock all buy button + return true + return when (currency) { is Currency.Blockchain -> { val blockchain = currency.blockchain when { blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null - blockchain == Blockchain.Unknown || blockchain == Blockchain.BSC -> false + blockchain == Blockchain.Unknown -> false else -> availableToBuy.contains(currency.currencySymbol) } } 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 3bd207b288..9f1b6b6187 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 @@ -136,6 +136,7 @@ class DetailsMiddleware { fun handle(action: DetailsAction.AppCurrencyAction) { when (action) { is DetailsAction.AppCurrencyAction.SelectAppCurrency -> { + store.state.globalState.tapWalletManager.rates.clear() preferencesStorage.saveAppCurrency(action.fiatCurrencyName) store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName)) store.dispatch(WalletAction.LoadFiatRate()) diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt index 8406eb54e6..ad5a091c99 100644 --- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt @@ -336,7 +336,7 @@ class EmailDataBuilder( appendLine() tokens.forEach { token -> builder.appendKeyValue("Name", token.name) - if (token.id != null) builder.appendKeyValue("Id", token.id ?: "") + builder.appendKeyValue("ID", token.id ?: "[custom token]") builder.appendKeyValue("Contract address", token.contractAddress) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 37f541c7fe..f1dd728d67 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -115,6 +115,9 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.popular_tokens, menu) + val addCustomTokenAllowed = store.state.tokensState.allowToAdd + menu.findItem(R.id.menu_navigate_add_custom_token).isVisible = addCustomTokenAllowed + val menuItem = menu.findItem(R.id.menu_search) val searchView: SearchView = menuItem.actionView as SearchView searchView.queryHint = searchView.getString(R.string.add_token_search_hint) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt index b9d68a1da9..9b75a86359 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt @@ -15,12 +15,15 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.SubcomposeAsyncImage import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.tap.common.extensions.fullNameWithoutTestnet import com.tangem.tap.common.extensions.getGreyedOutIconRes import com.tangem.tap.common.extensions.getNetworkName import com.tangem.tap.common.extensions.getRoundIconRes @@ -90,17 +93,13 @@ fun NetworkItem( .align(Alignment.CenterVertically) ) { Text( - text = blockchain.fullName.uppercase(), - fontSize = 13.sp, + text = prepareNetworkNameSpannableText( + blockchain = blockchain, + contractAddress = contract.address + ), fontWeight = FontWeight.SemiBold, - color = if (added) Color.Black else Color(0xFF848488), - ) - Spacer(modifier = Modifier.size(3.dp)) - Text( - text = if (contract.address == null) "MAIN" else blockchain.getNetworkName().uppercase(), fontSize = 13.sp, - fontWeight = FontWeight.Normal, - color = if (contract.address != null) Color(0xFF8E8E93) else Color(0xFF1ACE80), + color = if (added) Color.Black else Color(0xFF848488), ) } @@ -136,4 +135,33 @@ fun NetworkItem( ) } } +} + + +@Composable +fun prepareNetworkNameSpannableText( + blockchain: Blockchain, + contractAddress: String? +): AnnotatedString { + + val blockchainName = blockchain.fullNameWithoutTestnet.uppercase() + val additionalText = + if (contractAddress == null) "MAIN" else blockchain.getNetworkName().uppercase() + + val text = "$blockchainName $additionalText" + + val startOfAdditionalText = + if (additionalText.isNotBlank()) text.indexOf(additionalText) else text.length + + val spanStyles = listOf( + AnnotatedString.Range( + SpanStyle( + fontWeight = FontWeight.Normal, + color = if (contractAddress != null) Color(0xFF8E8E93) else Color(0xFF1ACE80) + ), + start = startOfAdditionalText, + end = text.length + ) + ) + return AnnotatedString(text = text, spanStyles = spanStyles) } \ No newline at end of file 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 b77bbb3b08..034122f2c1 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 @@ -22,6 +22,8 @@ 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.domain.extensions.toSendableAmounts +import com.tangem.tap.domain.failedRates +import com.tangem.tap.domain.loadedRates import com.tangem.tap.domain.tokens.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction @@ -39,6 +41,7 @@ import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction import org.rekotlin.Middleware +import timber.log.Timber import java.math.BigDecimal class WalletMiddleware { @@ -103,25 +106,32 @@ class WalletMiddleware { is WalletAction.LoadFiatRate -> { val appCurrencyId = globalState.appCurrency scope.launch { - when { + val coinsList = when { action.wallet != null -> { - globalState.tapWalletManager.loadFiatRate( - currencyId = appCurrencyId, - wallet = action.wallet, - ) + val wallet = action.wallet + wallet.getTokens() + .map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) } + .plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath)) } - action.coinsList != null -> { - globalState.tapWalletManager.loadFiatRate( - currencyId = appCurrencyId, - coinsList = action.coinsList, - ) + action.coinsList != null -> action.coinsList + else -> walletState.walletsData.map { it.currency } + } + val ratesResult = globalState.tapWalletManager.rates.loadFiatRate( + currencyId = appCurrencyId, + coinsList = coinsList, + ) + when (ratesResult) { + is Result.Success -> { + ratesResult.data.loadedRates.forEach { + dispatchOnMain(WalletAction.LoadFiatRate.Success(it.toPair())) + } + ratesResult.data.failedRates.forEach { (currency, throwable) -> + Timber.e(throwable, "Loading rates failed for [%s]", currency.currencySymbol) + } } - else -> { - val coinsList = walletState.walletsData.map { it.currency } - globalState.tapWalletManager.loadFiatRate( - currencyId = appCurrencyId, - coinsList = coinsList, - ) + is Result.Failure -> { + store.dispatchDebugErrorNotification("LoadFiatRate.Failure") + dispatchOnMain(WalletAction.LoadFiatRate.Failure) } } } diff --git a/domain/build.gradle b/domain/build.gradle index 8180476f6b..2a927011a4 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -50,7 +50,7 @@ dependencies { implementation implementation(project(path: ':common')) // Tangem sdk's - implementation 'com.tangem:blockchain:develop-71' + implementation 'com.tangem:blockchain:develop-76' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142' diff --git a/app/src/main/java/com/tangem/tap/common/Throttling.kt b/domain/src/main/java/com/tangem/domain/common/Throttling.kt similarity index 97% rename from app/src/main/java/com/tangem/tap/common/Throttling.kt rename to domain/src/main/java/com/tangem/domain/common/Throttling.kt index e0d3c6ee86..b7ef3d7783 100644 --- a/app/src/main/java/com/tangem/tap/common/Throttling.kt +++ b/domain/src/main/java/com/tangem/domain/common/Throttling.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common +package com.tangem.domain.common /** [REDACTED_AUTHOR] diff --git a/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt b/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt index 7b5235d365..5da31c0d87 100644 --- a/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt +++ b/domain/src/main/java/com/tangem/domain/common/form/FieldsValidators.kt @@ -1,11 +1,11 @@ package com.tangem.domain.common.form -import com.tangem.blockchain.blockchains.binance.BinanceAddressService import com.tangem.blockchain.blockchains.ethereum.EthereumAddressService import com.tangem.blockchain.blockchains.solana.SolanaAddressService import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressService import com.tangem.common.Validator +import com.tangem.common.card.EllipticCurve import com.tangem.domain.AddCustomTokenError import timber.log.Timber @@ -48,10 +48,9 @@ class TokenContractAddressValidator : CustomTokenValidator() { private fun getAddressService(): AddressService { return when (blockchain) { - Blockchain.Unknown -> EthereumAddressService() + Blockchain.Unknown -> successAddressValidator + Blockchain.Binance, Blockchain.BinanceTestnet -> successAddressValidator Blockchain.Solana, Blockchain.SolanaTestnet -> SolanaAddressService() - Blockchain.Binance -> BinanceAddressService() - Blockchain.BinanceTestnet -> BinanceAddressService(true) else -> { if (blockchain.isEvm()) { EthereumAddressService() @@ -62,6 +61,14 @@ class TokenContractAddressValidator : CustomTokenValidator() { } } } + + private val successAddressValidator = object : AddressService() { + override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String { + throw UnsupportedOperationException() + } + + override fun validate(address: String): Boolean = true + } } class TokenNetworkValidator : CustomTokenValidator() { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index 5d21995d73..55c6e9f760 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -67,16 +67,15 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT if (!action.blockchainNetwork.isUserInput) return val contractAddress = ContractAddress.getFieldValue() - when (validateContractAddressAndNotify(contractAddress)) { - is AddCustomTokenError.InvalidContractAddress -> { - val isAlreadyAdded = isBlockchainPersistIntoAppSavedTokensList( - selectedNetwork = action.blockchainNetwork.value - ) - updateWarningAlreadyAdded(isAlreadyAdded) - checkAndUpdateAddButton() - } - else -> {} + validateContractAddressAndNotify(contractAddress) + + if (!hubState.tokensAnyFieldsIsFilled()) { + val isAlreadyAdded = isBlockchainPersistIntoAppSavedTokensList( + selectedNetwork = action.blockchainNetwork.value + ) + updateWarningAlreadyAdded(isAlreadyAdded) } + checkAndUpdateAddButton() } is OnTokenDerivationPathChanged -> { val isAlreadyAdded = if (ContractAddress.isFilled()) { @@ -121,6 +120,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT } private suspend fun validateContractAddressAndNotify(contractAddress: String): AddCustomTokenError? { + if (!Network.isFilled()) return null + val error = ContractAddress.validateValue(contractAddress) when (error) { null -> {