Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-13 18:23:53 +04:00
commit 74a8eec94b
62 changed files with 1490 additions and 24568 deletions

View file

@ -79,10 +79,9 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.tangem:blockchain:develop-66'
// implementation 'com.tangem:blockchain:0.0.1'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-140'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-140'
implementation 'com.tangem:blockchain:develop-70'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142'
// WebView
implementation "androidx.browser:browser:1.3.0"

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
[
{
"decimalCount": 6,
"customIconUrl": "https://i.imgur.com\/LPZDXoT.png",
"symbol": "FUJISTABLE",
"name": "The Fuji stablecoin",
"contractAddress": "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22"
},
{
"decimalCount": 9,
"customIconUrl": "https://i.imgur.com\/jNTqyQA.png",
"symbol": "FUJIMOON",
"name": "To the Moon",
"contractAddress": "0x97132C109c6816525F7f338DCb7435E1412A7668"
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,9 +0,0 @@
[
{
"decimalCount": 8,
"customIconUrl": "https:\/\/assets.trustwalletapp.com\/blockchains\/binance\/assets\/MATIC-84A\/logo.png",
"symbol": "HEM",
"name": "Hemster - 452",
"contractAddress": "HEM-452"
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,65 +0,0 @@
[
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c/logo.png",
"symbol": "BTCB",
"name": "Binance-Peg BTCB Token",
"contractAddress": "0x6ce8da28e2f864420840cf74474eff5fd80e65b8"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56/logo.png",
"symbol": "BUSD",
"name": "Binance-Peg BUSD Token",
"contractAddress": "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
"symbol": "DAI",
"name": "Binance-Peg Dai Token",
"contractAddress": "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
"symbol": "USDT",
"name": "Binance-Peg Tether USD",
"contractAddress": "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"symbol": "USDC",
"name": "Binance-Peg USDC Token",
"contractAddress": "0x64544969ed7ebf5f083679233325356ebe738930"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE/logo.png",
"symbol": "XRP",
"name": "Binance-Peg XRP",
"contractAddress": "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
"symbol": "DAI",
"name": "Dai Token",
"contractAddress": "0x8a9424745056eb399fd19a0ec26a14316684e274"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x2170Ed0880ac9A755fd29B2688956BD959F933F8/logo.png",
"symbol": "ETH",
"name": "Ethereum Token",
"contractAddress": "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
"symbol": "USDT",
"name": "Tether USD",
"contractAddress": "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684"
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
[
{
"decimalCount": 18,
"customIconUrl": "https://cdn.worldvectorlogo.com/logos/usd-1.svg",
"symbol": "FUSD",
"name": "Fantom USD",
"contractAddress": "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074"
},
{
"decimalCount": 18,
"customIconUrl": "https://cryptologos.cc/logos/fantom-ftm-logo.svg?v=003",
"symbol": "WFTM",
"name": "Wrapped Fantom",
"contractAddress": "0xf1277d1Ed8AD466beddF92ef448A132661956621"
}
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
[
{
"decimalCount": 9,
"symbol": "TCA",
"name": "Tangem Coin A",
"contractAddress": "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR"
},
{
"decimalCount": 9,
"symbol": "TCB",
"name": "Tangem Coin B",
"contractAddress": "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4"
}
]

File diff suppressed because one or more lines are too long

View file

@ -106,6 +106,7 @@ object TangemSdk {
is TangemSdkError.IssuerSignatureLoadingFailed -> TangemSdkError.IssuerSignatureLoadingFailed()
is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware()
is TangemSdkError.UserForgotTheCode -> TangemSdkError.UserForgotTheCode()
is TangemSdkError.BackupFailedIncompatibleBatch -> TangemSdkError.BackupFailedIncompatibleBatch()
}
}

View file

@ -4,36 +4,16 @@ import androidx.annotation.DrawableRes
import com.tangem.blockchain.common.Blockchain
import com.tangem.wallet.R
@DrawableRes
fun Blockchain.getIconRes(): Int {
return when (this) {
Blockchain.Unknown -> R.drawable.shape_circle
Blockchain.Ducatus -> R.drawable.ic_ducatus
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_btc
Blockchain.BitcoinCash -> R.drawable.ic_btc_cash
Blockchain.Litecoin -> R.drawable.ic_ltc
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth
Blockchain.RSK -> R.drawable.ic_rsk
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano
Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance
Blockchain.Tezos -> R.drawable.ic_tezos
Blockchain.XRP -> R.drawable.ic_xrp
Blockchain.Stellar -> R.drawable.ic_stellar
else -> R.drawable.shape_circle
}
}
@DrawableRes
fun Blockchain.getRoundIconRes(): Int {
return when (this) {
// Blockchain.Ducatus -> R.drawable.ic_ducatus
Blockchain.Ducatus -> R.drawable.ic_ducatus
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round
Blockchain.Litecoin -> R.drawable.ic_litecoin_round
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round
Blockchain.RSK -> R.drawable.ic_rsk_round
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round
Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance_round
Blockchain.Tezos -> R.drawable.ic_tezos_round
Blockchain.XRP -> R.drawable.ic_xrp_round
Blockchain.Stellar -> R.drawable.ic_stellar_round
@ -41,7 +21,7 @@ fun Blockchain.getRoundIconRes(): Int {
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round
Blockchain.BSC, Blockchain.BSCTestnet -> R.drawable.ic_bsc_round
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round
else -> R.drawable.ic_tangem_logo
}
}
@ -56,7 +36,6 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color
Blockchain.RSK -> R.drawable.ic_rsk_no_color
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance_no_color
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
Blockchain.XRP -> R.drawable.ic_xrp_no_color
Blockchain.Stellar -> R.drawable.ic_stellar_no_color
@ -64,7 +43,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
Blockchain.BSC, Blockchain.BSCTestnet -> R.drawable.ic_bsc_no_color
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
else -> R.drawable.ic_tangem_logo
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.extensions
import android.graphics.*
import android.net.Uri
import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import com.squareup.picasso.Callback
@ -11,6 +10,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.blockchain.common.Token
import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.tokens.getIconUrl
import com.tangem.tap.domain.tokens.toNetworkId
import com.tangem.wallet.R
fun Picasso.loadCurrenciesIcon(
@ -20,10 +21,11 @@ fun Picasso.loadCurrenciesIcon(
blockchain: Blockchain,
) {
val url = if (token != null) {
token.getCustomIconUrl()?.let { Uri.parse(it) } ?: IconsUtil.getTokenIconUri(blockchain, token)
val url: String? = if (token != null) {
token.id?.let { getIconUrl(it) } ?: token.getCustomIconUrl()
?: IconsUtil.getTokenIconUri(blockchain, token)?.toString()
} else {
IconsUtil.getBlockchainIconUri(blockchain)
getIconUrl(blockchain.toNetworkId())
}
imageView.setImageDrawable(null)
@ -39,7 +41,7 @@ fun Picasso.loadCurrenciesIcon(
}
url != null -> {
if (token != null) {
setTokenImage(imageView, textView, token)
setTokenImage(imageView, textView, token, blockchain)
}
this.load(url)
.transform(RoundedCornersTransform())
@ -76,7 +78,7 @@ private fun setOfflineCurrencyImage(
) {
when (token) {
null -> setBlockchainImage(imageView, textView, blockchain)
else -> setTokenImage(imageView, textView, token)
else -> setTokenImage(imageView, textView, token, blockchain)
}
}
@ -85,7 +87,7 @@ private fun setBlockchainImage(
textView: TextView,
blockchain: Blockchain,
) {
imageView.setImageResource(blockchain.getIconRes())
imageView.setImageResource(blockchain.getRoundIconRes())
imageView.colorFilter = null
if (blockchain.isTestnet()) imageView.saturation = 0f
textView.text = null
@ -95,9 +97,10 @@ private fun setTokenImage(
imageView: ImageFilterView,
textView: TextView,
token: Token,
tokenBlockchain: Blockchain
) {
imageView.setImageResource(R.drawable.shape_circle)
if (token.blockchain.isTestnet()) {
if (tokenBlockchain.isTestnet()) {
imageView.saturation = 0f
} else {
imageView.setColorFilter(token.getColor())

View file

@ -11,13 +11,14 @@ import com.tangem.tap.common.extensions.withMainContext
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.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.models.getPendingTransactions
@ -33,7 +34,7 @@ import java.math.BigDecimal
class TapWalletManager {
val walletManagerFactory: WalletManagerFactory
by lazy { WalletManagerFactory(blockchainSdkConfig) }
by lazy { WalletManagerFactory(blockchainSdkConfig) }
private val coinMarketCapService = CoinMarketCapService()
private val blockchainSdkConfig by lazy {
@ -45,6 +46,7 @@ class TapWalletManager {
suspend fun loadWalletData(walletManager: WalletManager) {
val blockchain = walletManager.wallet.blockchain
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
val result = if (walletManagersThrottler.isStillThrottled(blockchain)) {
walletManagersThrottler.geValue(blockchain)!!
} else {
@ -53,21 +55,26 @@ class TapWalletManager {
when (result) {
is Result.Success -> {
checkForRentWarning(walletManager)
dispatchOnMain(WalletAction.LoadWallet.Success(result.data))
dispatchOnMain(WalletAction.LoadWallet.Success(result.data, blockchainNetwork))
}
is Result.Failure -> {
when (result.error) {
is TapError.WalletManagerUpdate.NoAccountError -> {
dispatchOnMain(WalletAction.LoadWallet.NoAccount(
walletManager.wallet,
(result.error as TapError.WalletManagerUpdate.NoAccountError).customMessage
))
dispatchOnMain(
WalletAction.LoadWallet.NoAccount(
walletManager.wallet,
blockchainNetwork,
(result.error as TapError.WalletManagerUpdate.NoAccountError).customMessage
)
)
}
else -> {
dispatchOnMain(WalletAction.LoadWallet.Failure(
walletManager.wallet,
result.error.localizedMessage
))
dispatchOnMain(
WalletAction.LoadWallet.Failure(
walletManager.wallet,
result.error.localizedMessage
)
)
}
}
}
@ -84,8 +91,8 @@ class TapWalletManager {
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
val currencies = wallet.getTokens()
.map { Currency.Token(it) }
.plus(Currency.Blockchain(wallet.blockchain))
.map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) }
.plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath))
loadFiatRate(fiatCurrency, currencies)
}
@ -129,7 +136,8 @@ class TapWalletManager {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
configManager?.turnOff(ConfigManager.isTopUpEnabled)
} else if (blockchain == Blockchain.Bitcoin
|| data.walletData?.blockchain == Blockchain.Bitcoin.id) {
|| data.walletData?.blockchain == Blockchain.Bitcoin.id
) {
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
} else {
@ -160,8 +168,10 @@ class TapWalletManager {
loadMultiWalletData(data, blockchain, primaryWalletManager)
} else {
dispatchOnMain(
WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager),
WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))
WalletAction.MultiWallet.AddBlockchains(
listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
listOf(primaryWalletManager)
)
)
}
} else {
@ -176,32 +186,40 @@ class TapWalletManager {
}
private suspend fun loadMultiWalletData(
scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
scanResponse: ScanResponse,
primaryBlockchain: Blockchain?,
primaryWalletManager: WalletManager?
) {
val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList()
val savedCurrencies = currenciesRepository.loadCardCurrencies(scanResponse.card.cardId)
val savedCurrencies = currenciesRepository.loadSavedCurrencies(scanResponse.card.cardId, scanResponse.card.derivationStyle)
if (savedCurrencies == null) {
if (savedCurrencies.isEmpty()) {
if (primaryBlockchain != null && primaryWalletManager != null) {
val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager)
dispatchOnMain(
WalletAction.MultiWallet.SaveCurrencies(
CardCurrencies(blockchains = listOf(primaryBlockchain), tokens = primaryTokens)
WalletAction.MultiWallet.SaveCurrencies(listOf(blockchainNetwork)),
WalletAction.MultiWallet.AddBlockchains(
listOf(blockchainNetwork),
listOf(primaryWalletManager)
),
WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager),
WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)),
WalletAction.MultiWallet.AddTokens(primaryTokens.toList())
WalletAction.MultiWallet.AddTokens(primaryTokens.toList(), blockchainNetwork)
)
} else {
val blockchains = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
val derivationStyle = scanResponse.card.derivationStyle
val blockchainNetworks = listOf(
BlockchainNetwork(Blockchain.Bitcoin, scanResponse.card),
BlockchainNetwork(Blockchain.Ethereum, scanResponse.card)
)
val walletManagers = walletManagerFactory.makeWalletManagersForApp(
scanResponse,
blockchainNetworks
)
dispatchOnMain(
WalletAction.MultiWallet.SaveCurrencies(CardCurrencies(
blockchains = blockchains,
tokens = emptyList()
)),
WalletAction.MultiWallet.AddWalletManagers(walletManagers),
WalletAction.MultiWallet.AddBlockchains(blockchains.toList()),
WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks),
WalletAction.MultiWallet.AddBlockchains(blockchainNetworks, walletManagers),
)
}
dispatchOnMain(
@ -209,22 +227,25 @@ class TapWalletManager {
WalletAction.MultiWallet.FindTokensInUse,
)
} else {
val blockchains = savedCurrencies.blockchains.toList()
val walletManagers = if (
primaryTokens.isNotEmpty() &&
primaryWalletManager != null && primaryBlockchain != null
primaryWalletManager != null &&
primaryBlockchain != null
) {
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary)
.plus(primaryWalletManager)
val blockchainsWithoutPrimary = savedCurrencies.filterNot { it.blockchain == primaryBlockchain }
walletManagerFactory.makeWalletManagersForApp(
scanResponse,
blockchainsWithoutPrimary
).plus(primaryWalletManager)
} else {
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies)
}
dispatchOnMain(
WalletAction.MultiWallet.AddWalletManagers(walletManagers),
WalletAction.MultiWallet.AddBlockchains(blockchains),
WalletAction.MultiWallet.AddTokens(savedCurrencies.tokens.toList()),
WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
)
savedCurrencies.map {
dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
}
}
}
@ -268,10 +289,12 @@ class TapWalletManager {
when (val result = rentProvider.minimalBalanceForRentExemption()) {
is com.tangem.blockchain.extensions.Result.Success -> {
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance < rentExempt
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean =
balance < rentExempt
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
val outgoingTxs =
walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
val rentExempt = result.data
val show = if (outgoingTxs.isEmpty()) {
isNeedToShowWarning(balance, rentExempt)
@ -283,11 +306,13 @@ class TapWalletManager {
if (!show) return
val currency = walletManager.wallet.blockchain.currency
dispatchOnMain(WalletAction.SetWalletRent(
blockchain = walletManager.wallet.blockchain,
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
))
dispatchOnMain(
WalletAction.SetWalletRent(
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
)
)
}
is com.tangem.blockchain.extensions.Result.Failure -> {}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.card.Card
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
@ -13,7 +14,7 @@ import java.util.*
object TapWorkarounds {
fun isStart2CoinIssuer(cardIssuer: String?): Boolean {
return cardIssuer?.toLowerCase(Locale.US) == START_2_COIN_ISSUER
return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER
}
val Card.isStart2Coin: Boolean
@ -22,13 +23,25 @@ object TapWorkarounds {
val Card.isTestCard: Boolean
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
val Card.useOldStyleDerivation: Boolean
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
val Card.derivationStyle: DerivationStyle?
get() = if (!settings.isHDWalletAllowed) {
null
} else if (useOldStyleDerivation) {
DerivationStyle.LEGACY
} else {
DerivationStyle.NEW
}
fun Card.isExcluded(): Boolean {
val excludedBatch = excludedBatches.contains(batchId)
val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
return excludedBatch || excludedIssuerName
}
fun Card.isNotSupportedInThatRelease():Boolean {
fun Card.isNotSupportedInThatRelease(): Boolean {
return false
}
@ -44,27 +57,27 @@ object TapWorkarounds {
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val excludedBatches = listOf(
"0027",
"0030",
"0031",
"0035"
"0027",
"0030",
"0031",
"0035"
)
private val excludedIssuers = listOf(
"TTM BANK"
"TTM BANK"
)
private val tangemWalletBatches = listOf("AC01")
private val tangemNoteBatches = mapOf(
"AB01" to Blockchain.Bitcoin,
"AB02" to Blockchain.Ethereum,
"AB03" to Blockchain.CardanoShelley,
"AB04" to Blockchain.Dogecoin,
"AB05" to Blockchain.BSC,
"AB06" to Blockchain.XRP,
"AB07" to Blockchain.Bitcoin,
"AB08" to Blockchain.Ethereum,
"AB01" to Blockchain.Bitcoin,
"AB02" to Blockchain.Ethereum,
"AB03" to Blockchain.CardanoShelley,
"AB04" to Blockchain.Dogecoin,
"AB05" to Blockchain.BSC,
"AB06" to Blockchain.XRP,
"AB07" to Blockchain.Bitcoin,
"AB08" to Blockchain.Ethereum,
)
}

View file

@ -1,26 +1,30 @@
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.common.*
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.redux.Currency
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse, blockchain: Blockchain
scanResponse: ScanResponse, blockchain: Blockchain, derivationParams: DerivationParams?
): WalletManager? {
val card = scanResponse.card
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
val supportedCurves = blockchain.getSupportedCurves() ?: return null
val supportedCurves = blockchain.getSupportedCurves()
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(wallets) ?: return null
val environmentBlockchain = if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
val environmentBlockchain =
if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
val seedKey = wallet.extendedPublicKey
return when {
@ -31,16 +35,21 @@ fun WalletManagerFactory.makeWalletManagerForApp(
environmentBlockchain, wallet.curve
)
}
seedKey != null -> {
seedKey != null && derivationParams != null -> {
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
val derivedKey = derivedKeys?.get(blockchain.derivationPath())
val derivationPath = when (derivationParams) {
is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style)
is DerivationParams.Custom -> derivationParams.path
}
val derivedKey = derivedKeys?.get(derivationPath)
?: return null
makeWalletManager(
cardId = card.cardId,
blockchain = environmentBlockchain,
seedKey = wallet.publicKey,
derivedKey = derivedKey
derivedKey = derivedKey,
derivation = derivationParams
)
}
else -> {
@ -54,12 +63,44 @@ fun WalletManagerFactory.makeWalletManagerForApp(
}
}
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse, blockchainNetwork: BlockchainNetwork
): WalletManager? {
return makeWalletManagerForApp(
scanResponse,
blockchain = blockchainNetwork.blockchain,
derivationParams = getDerivationParams(blockchainNetwork.derivationPath, scanResponse.card)
)
}
private fun getDerivationParams(derivationPath: String?, card: Card): DerivationParams? {
return derivationPath?.let {
DerivationParams.Custom(
DerivationPath(it)
)
} ?: if (!card.settings.isHDWalletAllowed) {
null
} else if (card.useOldStyleDerivation) {
DerivationParams.Default(DerivationStyle.LEGACY)
} else {
DerivationParams.Default(DerivationStyle.NEW)
}
}
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse, currency: Currency
): WalletManager? {
return makeWalletManagerForApp(
scanResponse,
blockchain = currency.blockchain,
derivationParams = getDerivationParams(currency.derivationPath, scanResponse.card)
)
}
fun WalletManagerFactory.makeWalletManagersForApp(
scanResponse: ScanResponse, blockchains: List<Blockchain>,
scanResponse: ScanResponse, blockchains: List<BlockchainNetwork>,
): List<WalletManager> {
val isTestCard = scanResponse.card.isTestCard
val filteredBlockchains = blockchains.mapNotNull { if (isTestCard) it.getTestnetVersion() else it }
return filteredBlockchains.mapNotNull { makeWalletManagerForApp(scanResponse, it) }
return blockchains.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
}
fun WalletManagerFactory.makePrimaryWalletManager(
@ -70,7 +111,12 @@ fun WalletManagerFactory.makePrimaryWalletManager(
} else {
scanResponse.getBlockchain()
}
return makeWalletManagerForApp(scanResponse, blockchain)
val derivationParams = getDerivationParams(null, scanResponse.card)
return makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = blockchain,
derivationParams = derivationParams
)
}
private fun selectWallet(wallets: List<CardWallet>): CardWallet? {

View file

@ -17,6 +17,7 @@ import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
@ -177,7 +178,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
val blockchainsForCurve = getBlockchains(response.cardId).filter {
it.getSupportedCurves().contains(response.wallet.curve)
}
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath() }
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath(card.derivationStyle) }
if (derivationPaths.isNotEmpty()) {
map[response.wallet.publicKey.toMapKey()] = derivationPaths
}

View file

@ -28,11 +28,14 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.tap.domain.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.extensions.getPrimaryCurve
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.preferencesStorage
@ -60,7 +63,6 @@ data class ScanResponse(
cardToken.symbol,
cardToken.contractAddress,
cardToken.decimals,
Blockchain.fromId(walletData.blockchain)
)
}
@ -272,28 +274,38 @@ private class ScanWalletProcessor(
}
}
private fun getBlockchainsToDerive(card: Card): List<Blockchain> {
private fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
val currenciesRepository = currenciesRepository ?: return emptyList()
val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
val cardCurrencies = currenciesRepository.loadSavedCurrencies(card.cardId, card.derivationStyle).toMutableList()
val blockchainsToDerive = if (cardCurrencies == null) {
mutableListOf(Blockchain.Bitcoin, Blockchain.Ethereum)
} else {
val tokenBlockchains = cardCurrencies.tokens.map { it.blockchain }
(cardCurrencies.blockchains + tokenBlockchains).toMutableList()
val blockchainsToDerive = cardCurrencies.ifEmpty {
mutableListOf(
BlockchainNetwork(Blockchain.Bitcoin, card),
BlockchainNetwork(Blockchain.Ethereum, card))
}
if (card.settings.isHDWalletAllowed) {
blockchainsToDerive.addAll(
listOf(
Blockchain.Ethereum,
Blockchain.Binance,
Blockchain.EthereumTestnet
BlockchainNetwork(Blockchain.Ethereum, card),
BlockchainNetwork(Blockchain.Binance, card),
BlockchainNetwork(Blockchain.EthereumTestnet, card)
)
)
}
if (additionalBlockchainsToDerive != null) {
blockchainsToDerive.addAll(additionalBlockchainsToDerive)
blockchainsToDerive.addAll(additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) })
}
if (!card.useOldStyleDerivation) {
blockchainsToDerive.removeAll(
listOf(
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Polygon, Blockchain.PolygonTestnet,
Blockchain.RSK,
Blockchain.Fantom, Blockchain.FantomTestnet,
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
).map { BlockchainNetwork(it, card) }
)
}
return blockchainsToDerive.distinct()
}
@ -303,13 +315,13 @@ private class ScanWalletProcessor(
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
blockchains.forEach { blockchain ->
val curve = blockchain.getPrimaryCurve()
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()
val path = blockchain.derivationPath?.let { DerivationPath(it) }
if (path != null) {
val addedDerivations = derivations[key]
if (addedDerivations != null) {

View file

@ -7,11 +7,14 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
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.blockchain.common.WalletManager
import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.common.extensions.appendIf
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.extensions.setCustomIconUrl
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.network.createMoshi
@ -30,55 +33,48 @@ class CurrenciesRepository(val context: Application) {
private val obsoleteTokensAdapter: JsonAdapter<List<ObsoleteTokenDao>> = moshi.adapter(
Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java)
)
private val currenciesAdapter : JsonAdapter<CurrenciesFromJson> =
private val currenciesAdapter: JsonAdapter<CurrenciesFromJson> =
moshi.adapter(CurrenciesFromJson::class.java)
private val blockchainNetworkAdapter: JsonAdapter<List<BlockchainNetwork>> =
moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
fun loadCardCurrencies(cardId: String): CardCurrencies? {
val blockchains = loadSavedBlockchains(cardId).toMutableSet()
if (DemoHelper.isDemoCardId(cardId)) {
blockchains.addAll(DemoHelper.config.demoBlockchains)
fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) {
var changed = false
val currencies = loadSavedCurrencies(cardId).map {
if (it == blockchainNetwork) {
changed = true
blockchainNetwork
} else {
it
}
}
if (blockchains.isEmpty()) return null
return CardCurrencies(loadSavedTokens(cardId), blockchains.toList())
val updatedCurrencies = if (changed) currencies else currencies + blockchainNetwork
saveCurrencies(cardId, updatedCurrencies.distinct())
}
fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
saveTokens(cardId, currencies.tokens)
saveBlockchains(cardId, currencies.blockchains)
fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) {
val currencies = loadSavedCurrencies(cardId).map {
if (it == blockchainNetwork) {
it.copy(tokens = it.tokens.filterNot { it == token })
} else {
it
}
}
saveCurrencies(cardId, currencies)
}
fun saveAddedToken(cardId: String, token: Token) {
val tokens = loadSavedTokens(cardId) + token
saveTokens(cardId, tokens)
}
fun saveAddedTokens(cardId: String, tokens: Collection<Token>) {
saveTokens(cardId, loadSavedTokens(cardId) + tokens.distinct())
}
fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
val blockchains = loadSavedBlockchains(cardId) + blockchain
saveBlockchains(cardId, blockchains.distinct())
}
fun removeToken(cardId: String, token: Token) {
val tokens = loadSavedTokens(cardId).filterNot { it == token }
saveTokens(cardId, tokens)
}
fun removeBlockchain(cardId: String, blockchain: Blockchain) {
val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }
saveBlockchains(cardId, blockchains)
fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) {
val currencies = loadSavedCurrencies(cardId).filterNot { it == blockchainNetwork }
saveCurrencies(cardId, currencies)
}
fun removeCurrencies(cardId: String) {
saveTokens(cardId, emptyList())
saveBlockchains(cardId, emptyList())
saveCurrencies(cardId, emptyList())
}
private fun loadSavedTokens(cardId: String): List<Token> {
@Deprecated("Use BlockchainNetwork instead")
private fun loadSavedTokens(cardId: String): List<TokenDao> {
val json = try {
context.readFileText(getFileNameForTokens(cardId))
} catch (exception: Exception) {
@ -86,17 +82,13 @@ class CurrenciesRepository(val context: Application) {
}
return try {
tokensAdapter.fromJson(json)!!.map { it.toToken() }.distinct()
tokensAdapter.fromJson(json) ?: emptyList()
} catch (exception: Exception) {
emptyList()
}
}
private fun saveTokens(cardId: String, tokens: List<Token>) {
val json = tokensAdapter.toJson(tokens.distinct().map { TokenDao.fromToken(it) })
context.rewriteFile(json, getFileNameForTokens(cardId))
}
@Deprecated("Use BlockchainNetwork instead")
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
return try {
val json = context.readFileText(getFileNameForBlockchains(cardId))
@ -106,8 +98,61 @@ class CurrenciesRepository(val context: Application) {
}
}
private fun saveBlockchains(cardId: String, blockchains: List<Blockchain>) {
val json = blockchainsAdapter.toJson(blockchains.distinct())
fun loadSavedCurrencies(
cardId: String,
derivationStyle: DerivationStyle? = null
): List<BlockchainNetwork> {
if (DemoHelper.isDemoCardId(cardId)) {
return loadDemoCurrencies(cardId)
}
return try {
val json = context.readFileText(getFileNameForBlockchains(cardId))
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: loadSavedCurrenciesOldWay(
cardId,
derivationStyle
)
} catch (exception: Exception) {
emptyList()
}
}
private fun loadDemoCurrencies(cardId: String): List<BlockchainNetwork> {
return DemoHelper.config.demoBlockchains.map {
BlockchainNetwork(
blockchain = it,
derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath,
tokens = emptyList()
)
}
}
private fun loadSavedCurrenciesOldWay(
cardId: String, derivationStyle: DerivationStyle?
): List<BlockchainNetwork> {
val blockchains = loadSavedBlockchains(cardId)
val tokens = loadSavedTokens(cardId)
val currencies = getSupportedTokens()
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()
val id = currencies
.find { it.contracts?.find { it.address == token.contractAddress } != null }
?.id
token.copy(id = id)
}
)
}
saveCurrencies(cardId, blockchainNetworks) // migrate saved currencies
return blockchainNetworks
}
fun saveCurrencies(cardId: String, currencies: List<BlockchainNetwork>) {
val json = blockchainNetworkAdapter.toJson(currencies)
context.rewriteFile(json, getFileNameForBlockchains(cardId))
}
@ -148,17 +193,6 @@ class CurrenciesRepository(val context: Application) {
return obsoleteTokensAdapter.fromJson(tokenJson)!!.map { it.toTokenDao(blockchain) }
}
private fun getSupportedTokens(): List<Blockchain> {
return listOf(
Blockchain.Ethereum,
Blockchain.BSC,
Blockchain.Binance,
Blockchain.Polygon,
Blockchain.Avalanche,
Blockchain.Fantom,
)
}
fun getBlockchains(
cardFirmware: FirmwareVersion,
isTestNet: Boolean = false
@ -174,9 +208,11 @@ class CurrenciesRepository(val context: Application) {
// Use this list to temporarily exclude a blockchain from the list of tokens.
private fun excludeUnsupportedBlockchains(blockchains: List<Blockchain>): List<Blockchain> {
return blockchains.toMutableList().apply {
removeAll(listOf(
removeAll(
listOf(
// Any blockchain
))
)
)
}
}
@ -214,24 +250,11 @@ data class TokenDao(
symbol = symbol,
contractAddress = contractAddress,
decimals = decimalCount,
blockchain = blockchainDao.toBlockchain()
).apply {
customIconUrl?.let { this.setCustomIconUrl(it) }
}
}
companion object {
fun fromToken(token: Token): TokenDao {
return TokenDao(
name = token.name,
symbol = token.symbol,
contractAddress = token.contractAddress,
decimalCount = token.decimals,
blockchainDao = BlockchainDao.fromBlockchain(token.blockchain),
customIconUrl = token.getCustomIconUrl()
)
}
}
}
@JsonClass(generateAdapter = true)
@ -276,29 +299,78 @@ data class ObsoleteTokenDao(
}
}
@JsonClass(generateAdapter = true)
data class CardCurrenciesDao(
val tokens: List<TokenDao>,
val blockchains: List<Blockchain>,
) {
fun toCardCurrencies(): CardCurrencies {
return CardCurrencies(
tokens = tokens.map { it.toToken() }.distinct(),
blockchains = blockchains
)
}
companion object {
fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
return CardCurrenciesDao(
tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(),
blockchains = cardCurrencies.blockchains
)
}
}
}
//@JsonClass(generateAdapter = true)
//data class CardCurrenciesDao(
// val tokens: List<TokenDao>,
// val blockchains: List<Blockchain>,
//) {
// fun toCardCurrencies(): CardCurrencies {
// return CardCurrencies(
// tokens = tokens.map { it.toToken() }.distinct(),
// blockchains = blockchains
// )
// }
//
// companion object {
// fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
// return CardCurrenciesDao(
// tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(),
// blockchains = cardCurrencies.blockchains
// )
// }
// }
//}
data class CardCurrencies(
val tokens: List<Token>,
val blockchains: List<Blockchain>,
)
)
@JsonClass(generateAdapter = true)
data class BlockchainNetwork(
val blockchain: Blockchain,
val derivationPath: String?,
val tokens: List<Token>
) {
constructor(blockchain: Blockchain, card: Card) : this(
blockchain = blockchain,
derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null,
tokens = emptyList()
)
fun updateTokens(tokens: List<Token>): BlockchainNetwork {
return copy(
tokens = (this.tokens + tokens).distinct()
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BlockchainNetwork
if (blockchain != other.blockchain) return false
if (derivationPath != other.derivationPath) return false
return true
}
override fun hashCode(): Int {
var result = blockchain.hashCode()
result = 31 * result + (derivationPath?.hashCode() ?: 0)
return result
}
companion object {
fun fromWalletManager(walletManager: WalletManager): BlockchainNetwork {
return BlockchainNetwork(
walletManager.wallet.blockchain,
walletManager.wallet.publicKey.derivationPath?.rawPath,
walletManager.cardTokens.toList()
)
}
}
}

View file

@ -26,7 +26,7 @@ data class CurrenciesFromJson(
fun List<ContractFromJson>.toContracts(): List<Contract> {
return map { Contract.fromJsonObject(it) }
return mapNotNull { Contract.fromJsonObject(it) }
}
data class Currency(
@ -59,10 +59,11 @@ data class Contract(
) {
companion object {
fun fromJsonObject(contract: ContractFromJson): Contract {
fun fromJsonObject(contract: ContractFromJson): Contract? {
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: return null
return Contract(
networkId = contract.networkId,
blockchain = Blockchain.fromNetworkId(contract.networkId),
blockchain = blockchain,
address = contract.address,
decimalCount = contract.decimalCount,
iconUrl = getIconUrl(contract.networkId)
@ -76,7 +77,7 @@ fun getIconUrl(id: String): String {
}
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain {
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
return when (networkId) {
"avalanche" -> Blockchain.Avalanche
"binancecoin" -> Blockchain.Binance
@ -95,15 +96,15 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain {
"stellar" -> Blockchain.Stellar
"tezos" -> Blockchain.Tezos
"ripple" -> Blockchain.XRP
else -> Blockchain.Unknown
else -> null
}
}
fun Blockchain.toNetworkId(): String {
return when (this) {
Blockchain.Unknown -> "unknown"
Blockchain.Avalanche -> "avalaunch"
Blockchain.AvalancheTestnet -> "avalaunch"
Blockchain.Avalanche -> "avalanche"
Blockchain.AvalancheTestnet -> "avalaunche"
Blockchain.Binance -> "binancecoin"
Blockchain.BinanceTestnet -> "binancecoin"
Blockchain.BSC -> "binance-smart-chain"
@ -123,7 +124,7 @@ fun Blockchain.toNetworkId(): String {
Blockchain.Litecoin -> "litecoin"
Blockchain.Polygon -> "matic-network"
Blockchain.PolygonTestnet -> "matic-networks"
Blockchain.RSK -> "rsk"
Blockchain.RSK -> "rootstock"
Blockchain.Stellar -> "stellar"
Blockchain.StellarTestnet -> "stellar"
Blockchain.Solana -> "solana"

View file

@ -36,7 +36,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
val loadedBalance = noteState.walletBalance.copy(
value = balanceAmount.value!!,
currency = Currency.Blockchain(walletManager.wallet.blockchain),
currency = Currency.Blockchain(walletManager.wallet.blockchain, null),
state = ProgressState.Done,
error = null,
criticalError = null

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
import com.tangem.tap.common.extensions.dispatchOnMain
@ -10,14 +11,17 @@ 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.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.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.features.demo.DemoHelper
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@ -164,14 +168,16 @@ class WalletConnectMiddleware {
chainId = chainId, peer = session.peerMeta
) ?: Blockchain.Ethereum
store.dispatch(GlobalAction.ScanCard(additionalBlockchainsToDerive = listOf(blockchain),
onSuccess = { scanResponse ->
handleScanResponse(scanResponse, session, blockchain)
},
onFailure = {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
}, R.string.wallet_connect_scan_card_message
)
store.dispatch(
GlobalAction.ScanCard(
additionalBlockchainsToDerive = listOf(blockchain),
onSuccess = { scanResponse ->
handleScanResponse(scanResponse, session, blockchain)
},
onFailure = {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
}, R.string.wallet_connect_scan_card_message
)
)
}
@ -229,22 +235,35 @@ class WalletConnectMiddleware {
}
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
store.state.walletState.getWalletManager(blockchainToMake)
?: factory.makeWalletManagerForApp(scanResponse, blockchainToMake)
val derivationPath = blockchainToMake.derivationPath(card.derivationStyle)?.rawPath
store.state.walletState.getWalletManager(
Currency.Blockchain(blockchainToMake, derivationPath)
)
?: factory.makeWalletManagerForApp(
scanResponse,
blockchainToMake,
card.derivationStyle?.let { DerivationParams.Default(it) }
)
?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
store.dispatch(
WalletAction.MultiWallet.AddBlockchain(
BlockchainNetwork.fromWalletManager(walletManager), walletManager
)
)
}
} else {
val walletManager = factory.makeWalletManagerForApp(scanResponse, blockchainToMake)
if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains(
blockchainToMake
) != true
val walletManager = factory.makeWalletManagerForApp(
scanResponse,
blockchainToMake,
card.derivationStyle?.let { DerivationParams.Default(it) }
)
if (currenciesRepository.loadSavedCurrencies(card.cardId, card.derivationStyle)
.find { it.blockchain == blockchainToMake } != null
) {
walletManager?.let {
currenciesRepository.saveAddedBlockchain(
currenciesRepository.saveUpdatedCurrency(
card.cardId,
blockchainToMake
BlockchainNetwork.fromWalletManager(walletManager)
)
}
}

View file

@ -106,6 +106,7 @@ class AdditionalEmailInfo {
var address: String = "",
var explorerLink: String = "",
var host: String = "",
var derivationPath: String = "",
)
var appVersion: String = ""
@ -159,7 +160,8 @@ class AdditionalEmailInfo {
blockchain = manager.wallet.blockchain,
address = getAddress(manager.wallet),
explorerLink = getExploreUri(manager.wallet),
host = manager.currentHost
host = manager.currentHost,
derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
)
)
if (manager.cardTokens.isNotEmpty()) {
@ -173,7 +175,8 @@ class AdditionalEmailInfo {
blockchain = wallet.blockchain,
address = getAddress(wallet),
explorerLink = getExploreUri(wallet),
host = host
host = host,
derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
)
this.destinationAddress = destinationAddress
@ -325,6 +328,7 @@ class EmailDataBuilder(
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
builder.appendKeyValue("Host", it.host)
builder.appendKeyValue("Wallet address", it.address)
builder.appendKeyValue("Derivation path", it.derivationPath)
builder.appendKeyValue("Explorer link", it.explorerLink)
infoHolder.tokens[it.blockchain]?.let { tokens ->
@ -332,6 +336,7 @@ class EmailDataBuilder(
appendLine()
tokens.forEach { token ->
builder.appendKeyValue("Name", token.name)
if (token.id != null) builder.appendKeyValue("Id", token.id ?: "")
builder.appendKeyValue("Contract address", token.contractAddress)
}
}
@ -342,6 +347,7 @@ class EmailDataBuilder(
fun appendTxFailedBlockchainInfo(error: String): EmailDataBuilder {
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
builder.appendKeyValue("Host", walletInfo.host)
builder.appendKeyValue("Token", infoHolder.token)
builder.appendKeyValue("Error", error)

View file

@ -41,7 +41,7 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) },
onSearchTokensClick = {
store.dispatch(TokensAction.AllowToAddTokens(false))
store.dispatch(TokensAction.LoadCurrencies)
store.dispatch(TokensAction.LoadCurrencies())
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}
)

View file

@ -67,7 +67,9 @@ class OnboardingManager(
}
}
return balance.copy(currency = Currency.Blockchain(walletManager.wallet.blockchain))
return balance.copy(currency = Currency.Blockchain(
walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath)
)
}
fun activationStarted(cardId: String) {
@ -81,7 +83,7 @@ class OnboardingManager(
data class OnboardingWalletBalance(
val value: BigDecimal = BigDecimal.ZERO,
val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown),
val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown, null),
val hasIncomingTransaction: Boolean = false,
val state: ProgressState,
val error: TapError? = null,

View file

@ -112,7 +112,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading
val balanceIsLoading = noteState.walletBalance.copy(
currency = Currency.Blockchain(walletManager.wallet.blockchain),
currency = Currency.Blockchain(
walletManager.wallet.blockchain,
walletManager.wallet.publicKey.derivationPath?.rawPath
),
state = ProgressState.Loading,
error = null,
criticalError = null

View file

@ -214,7 +214,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
val isLoadedBefore = twinCardsState.walletBalance.state != ProgressState.Loading
val balanceIsLoading = twinCardsState.walletBalance.copy(
currency = Currency.Blockchain(walletManager.wallet.blockchain),
currency = Currency.Blockchain(
walletManager.wallet.blockchain,
walletManager.wallet.publicKey.derivationPath?.rawPath
),
state = ProgressState.Loading,
error = null,
criticalError = null

View file

@ -23,6 +23,7 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.demo.DemoTransactionSender
import com.tangem.tap.features.demo.isDemoWallet
import com.tangem.tap.features.send.redux.*
@ -202,11 +203,11 @@ private fun sendTransaction(
}
scope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain))
dispatch(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager)))
}
delay(11000) // more than 10000 to avoid throttling
withContext(Dispatchers.Main) {
dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain))
dispatch(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager)))
}
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.wallet.redux.WalletData
import org.rekotlin.Action
@ -12,15 +12,17 @@ sealed class TokensAction : Action {
data class AllowToAddTokens(val allow: Boolean) : TokensAction()
object LoadCurrencies : TokensAction() {
data class LoadCurrencies(val supportedBlockchains: List<Blockchain>? = null) : TokensAction() {
data class Success(val currencies: List<Currency>) : TokensAction()
}
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
data class SetAddedCurrencies(
val wallets: List<WalletData>, val derivationStyle: DerivationStyle?
) : TokensAction()
data class SetNonRemovableCurrencies(val wallets: List<WalletData>) : TokensAction()
data class SaveChanges(
val addedTokens: List<Token>,
val addedTokens: List<TokenWithBlockchain>,
val addedBlockchains: List<Blockchain>
) : TokensAction()
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.Token
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
@ -12,14 +13,16 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
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.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
@ -34,7 +37,7 @@ class TokensMiddleware {
{ next ->
{ action ->
when (action) {
is TokensAction.LoadCurrencies -> handleLoadCurrencies()
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
is TokensAction.SaveChanges -> handleSaveChanges(action)
}
next(action)
@ -42,11 +45,12 @@ class TokensMiddleware {
}
}
private fun handleLoadCurrencies() {
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
val scanResponse = store.state.globalState.scanResponse
val isTestcard = scanResponse?.card?.isTestCard ?: false
val currencies = currenciesRepository.getSupportedTokens(isTestcard)
.filter(action.supportedBlockchains?.toSet())
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
}
@ -55,13 +59,15 @@ class TokensMiddleware {
val scanResponse = store.state.globalState.scanResponse ?: return
val currentTokens = store.state.tokensState.addedWallets.toTokens()
val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains()
val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains(
store.state.tokensState.derivationStyle
)
val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) }
val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) }
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it) }
val tokensToRemove = currentTokens.filter { !action.addedTokens.contains(it) }
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it.token) }
val tokensToRemove = currentTokens.filter { token -> !action.addedTokens.any { it.token == token } }
removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove)
@ -73,7 +79,7 @@ class TokensMiddleware {
if (scanResponse.supportsHdWallet()) {
deriveMissingBlockchains(scanResponse, blockchainsToAdd, tokensToAdd)
} else {
submitAdd(blockchainsToAdd, tokensToAdd)
submitAdd(blockchainsToAdd, tokensToAdd, scanResponse)
store.dispatch(NavigationAction.PopBackTo())
}
}
@ -81,7 +87,7 @@ class TokensMiddleware {
private fun deriveMissingBlockchains(
scanResponse: ScanResponse,
blockchains: List<Blockchain>,
tokens: List<Token>
tokens: List<TokenWithBlockchain>
) {
val derivationDataList = listOfNotNull(
getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens),
@ -112,7 +118,7 @@ class TokensMiddleware {
derivedKeys = updatedDerivedKeys
)
store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
submitAdd(blockchains, tokens)
submitAdd(blockchains, tokens, scanResponse)
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.PopBackTo())
@ -128,13 +134,12 @@ class TokensMiddleware {
curve: EllipticCurve,
scanResponse: ScanResponse,
blockchains: List<Blockchain>,
tokens: List<Token>
tokens: List<TokenWithBlockchain>
): DerivationData? {
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
val tokenBlockchains = tokens.map { it.blockchain }
val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct()
.mapNotNull { it.derivationPath() }
val derivationPathsCandidates = (blockchains + tokens.map { it.blockchain }).distinct()
.mapNotNull { it.derivationPath(scanResponse.card.derivationStyle) }
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
val alreadyDerivedKeys: ExtendedPublicKeysMap =
@ -155,11 +160,20 @@ class TokensMiddleware {
val mapKeyOfWalletPublicKey: ByteArrayKey
)
private fun submitAdd(blockchains: List<Blockchain>, tokens: List<Token>) {
(blockchains.map {
WalletAction.MultiWallet.AddBlockchain(it)
private fun submitAdd(
blockchains: List<Blockchain>, tokens: List<TokenWithBlockchain>, scanResponse: ScanResponse,
) {
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
(blockchains.mapNotNull {
val walletManager = factory.makeWalletManagerForApp(
scanResponse, it,
scanResponse.card.derivationStyle?.let { DerivationParams.Default(it) }
) ?: return@mapNotNull null
WalletAction.MultiWallet.AddBlockchain(BlockchainNetwork.fromWalletManager(walletManager), walletManager)
} + tokens.map {
WalletAction.MultiWallet.AddToken(it)
val blockchainNetwork = BlockchainNetwork(it.blockchain, scanResponse.card)
WalletAction.MultiWallet.AddToken(it.token, blockchainNetwork)
}).forEach { store.dispatchOnMain(it) }
}

View file

@ -19,15 +19,17 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
tokensState.copy(currencies = action.currencies)
}
is TokensAction.SetAddedCurrencies -> {
tokensState.copy(
addedBlockchains = action.wallets.toBlockchains(),
addedTokens = action.wallets.toTokens(),
addedBlockchains = action.wallets.toBlockchains(action.derivationStyle),
addedTokens = action.wallets.toTokensWithBlockchains(action.derivationStyle),
addedWallets = action.wallets,
derivationStyle = action.derivationStyle
)
}
is TokensAction.SetNonRemovableCurrencies -> {
tokensState.copy(
nonRemovableBlockchains = action.wallets.toBlockchains(),
nonRemovableBlockchains = action.wallets.toBlockchains(tokensState.derivationStyle),
nonRemovableTokens = action.wallets.toTokensContractAddresses(),
)
}

View file

@ -1,19 +1,22 @@
package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.wallet.redux.WalletData
import org.rekotlin.StateType
data class TokensState(
val addedWallets: List<WalletData> = emptyList(),
val addedTokens: List<Token> = emptyList(),
val addedTokens: List<TokenWithBlockchain> = emptyList(),
val addedBlockchains: List<Blockchain> = emptyList(),
val nonRemovableTokens: List<ContractAddress> = emptyList(),
val nonRemovableBlockchains: List<Blockchain> = emptyList(),
val currencies: List<Currency> = emptyList(),
val allowToAdd: Boolean = true
val allowToAdd: Boolean = true,
val derivationStyle: DerivationStyle? = null
) : StateType
typealias ContractAddress = String
@ -26,6 +29,38 @@ fun List<WalletData>.toTokens(): List<Token> {
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }.distinct()
}
fun List<WalletData>.toBlockchains(): List<Blockchain> {
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Blockchain)?.blockchain }.distinct()
fun List<WalletData>.toTokensWithBlockchains(derivationStyle: DerivationStyle?): List<TokenWithBlockchain> {
return mapNotNull {
if (it.currency !is com.tangem.tap.features.wallet.redux.Currency.Token) return@mapNotNull null
if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
TokenWithBlockchain(it.currency.token, it.currency.blockchain)
}.distinct()
}
fun List<WalletData>.toBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
return mapNotNull {
if (it.currency.isCustomCurrency(derivationStyle)) {
null
} else {
(it.currency as? com.tangem.tap.features.wallet.redux.Currency.Blockchain)?.blockchain
}
}.distinct()
}
data class TokenWithBlockchain(
val token: Token,
val blockchain: Blockchain
)
fun List<Currency>.filter(supportedBlockchains: Set<Blockchain>?): List<Currency> {
if (supportedBlockchains == null) return this
return map {
it.copy(contracts =
it.contracts?.filter {
supportedBlockchains.contains(it.blockchain) && it.blockchain.canHandleTokens()
}
)
}.filterNot {
it.contracts.isNullOrEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(it.id))
}
}

View file

@ -14,8 +14,13 @@ import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.tokens.ui.compose.CurrenciesScreen
@ -68,14 +73,22 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
val onSaveChanges = { tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain> ->
store.dispatch(TokensAction.SaveChanges(tokens, blockchains))
}
val onNetworkItemClicked = { contractAddress: ContractAddress ->
context?.copyToClipboard(contractAddress)
store.dispatchNotification(R.string.contract_address_copied_message)
}
cvCurrencies.setContent {
AppCompatTheme {
CurrenciesScreen(
tokensState = tokensState,
searchInput = searchInput
) { tokens, blockchains ->
store.dispatch(TokensAction.SaveChanges(tokens, blockchains))
}
searchInput = searchInput,
onSaveChanges = onSaveChanges,
onNetworkItemClicked = onNetworkItemClicked
)
}
}
}

View file

@ -16,17 +16,17 @@ 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.getGreyedOutIconRes
import com.tangem.tap.common.extensions.getRoundIconRes
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.wallet.R
@Composable
fun CollapsedCurrencyItem(
currency: Currency,
addedTokens: List<Token>,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
onCurrencyClick: (String) -> Unit
) {
@ -36,11 +36,7 @@ fun CollapsedCurrencyItem(
.clickable(onClick = { onCurrencyClick(currency.id) })
) {
val blockchain = Blockchain.fromNetworkId(currency.id)
val iconRes = if (blockchain == Blockchain.Unknown) {
currency.iconUrl
} else {
blockchain.getRoundIconRes()
}
val iconRes = currency.iconUrl
SubcomposeAsyncImage(
model = iconRes,
@ -66,32 +62,36 @@ fun CollapsedCurrencyItem(
)
Spacer(modifier = Modifier.size(6.dp))
Row {
if (currency.contracts != null) {
if (!currency.contracts.isNullOrEmpty()) {
currency.contracts.map { contract ->
val added =
addedTokens.map { it.contractAddress }.contains(contract.address)
val icon = if (added) {
contract.blockchain.getRoundIconRes()
if (contract.address == currency.symbol) {
BlockchainNetworkItem(
blockchain = Blockchain.fromNetworkId(contract.networkId),
addedBlockchains = addedBlockchains
)
} else {
contract.blockchain.getGreyedOutIconRes()
val added =
addedTokens.map { it.token.contractAddress }
.contains(contract.address)
val icon = if (added) {
contract.blockchain.getRoundIconRes()
} else {
contract.blockchain.getGreyedOutIconRes()
}
Image(
painter = painterResource(id = icon),
contentDescription = null,
Modifier
.size(20.dp)
)
Spacer(modifier = Modifier.size(5.dp))
}
Image(
painter = painterResource(id = icon),
contentDescription = null,
Modifier
.size(20.dp)
)
Spacer(modifier = Modifier.size(5.dp))
}
} else {
val added = addedBlockchains.contains(blockchain)
val icon =
if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes()
Image(
painter = painterResource(id = icon),
contentDescription = null,
Modifier
.size(20.dp)
BlockchainNetworkItem(
blockchain = blockchain,
addedBlockchains = addedBlockchains
)
}
}
@ -105,4 +105,23 @@ fun CollapsedCurrencyItem(
)
}
}
@Composable
fun BlockchainNetworkItem(
blockchain: Blockchain?,
addedBlockchains: List<Blockchain>
) {
val added = addedBlockchains.contains(blockchain)
val icon =
if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
if (icon != null) {
Image(
painter = painterResource(id = icon),
contentDescription = null,
Modifier
.size(20.dp)
)
Spacer(modifier = Modifier.size(5.dp))
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FabPosition
@ -12,12 +13,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.tap.common.compose.Keyboard
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.common.extensions.pixelsToDp
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.store
import com.tangem.wallet.R
@ -26,7 +28,8 @@ import com.tangem.wallet.R
fun CurrenciesScreen(
tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState),
searchInput: MutableState<String>,
onSaveChanges: (List<Token>, List<Blockchain>) -> Unit
onSaveChanges: (List<TokenWithBlockchain>, List<Blockchain>) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
) {
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
@ -34,13 +37,13 @@ fun CurrenciesScreen(
val isKeyboardOpen by keyboardAsState()
val onAddCurrencyToggleClick = { currency: Currency, contract: Token? ->
if (contract != null) {
val onAddCurrencyToggleClick = { currency: Currency, token: TokenWithBlockchain? ->
if (token != null) {
val mutableList = addedTokensState.value.toMutableList()
if (mutableList.contains(contract)) {
mutableList.remove(contract)
if (mutableList.contains(token)) {
mutableList.remove(token)
} else {
mutableList.add(contract)
mutableList.add(token)
}
addedTokensState.value = mutableList
} else {
@ -49,7 +52,7 @@ fun CurrenciesScreen(
if (mutableList.contains(blockchain)) {
mutableList.remove(blockchain)
} else {
mutableList.add(blockchain)
blockchain?.let { mutableList.add(blockchain) }
}
addedBlockchainsState.value = mutableList
}
@ -65,16 +68,21 @@ fun CurrenciesScreen(
floatingActionButtonPosition = FabPosition.Center,
) {
ListOfCurrencies(
currencies = tokensState.value.currencies,
nonRemovableTokens = tokensState.value.nonRemovableTokens,
nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
addedTokens = addedTokensState.value,
addedBlockchains = addedBlockchainsState.value,
searchInput = searchInput.value,
allowToAdd = tokensState.value.allowToAdd,
onAddCurrencyToggled = onAddCurrencyToggleClick
)
Column {
if (tokensState.value.allowToAdd) CurrenciesWarning()
ListOfCurrencies(
currencies = tokensState.value.currencies,
nonRemovableTokens = tokensState.value.nonRemovableTokens,
nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
addedTokens = addedTokensState.value,
addedBlockchains = addedBlockchainsState.value,
searchInput = searchInput.value,
allowToAdd = tokensState.value.allowToAdd,
onAddCurrencyToggled = onAddCurrencyToggleClick,
onNetworkItemClicked = onNetworkItemClicked
)
}
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.wallet.R
@Composable
fun CurrenciesWarning() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(16.dp)
.background(Color(0xFFF2F2F2), shape = RoundedCornerShape(10.dp))
)
{
val warning = stringResource(id = R.string.alert_manage_tokens_addresses_message)
val end = warning.indexOf(" ")
val spanStyles = listOf(
AnnotatedString.Range(
SpanStyle(fontWeight = FontWeight.Bold),
start = 0,
end = end
)
)
Text(
text = AnnotatedString(text = warning, spanStyles = spanStyles),
textAlign = TextAlign.Center,
color = Color(0xFF848488),
modifier = Modifier.padding(
top = 8.dp,
bottom = 8.dp,
start = 16.dp,
end = 16.dp
),
fontSize = 13.sp,
fontWeight = FontWeight.Normal,
)
}
}

View file

@ -2,21 +2,22 @@ package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.runtime.Composable
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
@Composable
fun CurrencyItem(
currency: Currency,
nonRemovableTokens: List<ContractAddress>,
nonRemovableBlockchains: List<Blockchain>,
addedTokens: List<Token>,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
allowToAdd: Boolean,
expanded: Boolean,
onCurrencyClick: (String) -> Unit,
onAddCurrencyToggled: (Currency, Token?) -> Unit
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
) {
if (expanded) {
ExpandedCurrencyItem(
@ -26,7 +27,8 @@ fun CurrencyItem(
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
allowToAdd = allowToAdd,
onCurrencyClick = onCurrencyClick, onAddCurrencyToggled = onAddCurrencyToggled
onCurrencyClick = onCurrencyClick, onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked
)
} else {
CollapsedCurrencyItem(

View file

@ -18,11 +18,10 @@ 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.getRoundIconRes
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.domain.tokens.fromNetworkId
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.wallet.R
@Composable
@ -30,18 +29,15 @@ fun ExpandedCurrencyItem(
currency: Currency,
nonRemovableTokens: List<ContractAddress>,
nonRemovableBlockchains: List<Blockchain>,
addedTokens: List<Token>,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
allowToAdd: Boolean,
onCurrencyClick: (String) -> Unit,
onAddCurrencyToggled: (Currency, Token?) -> Unit
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
) {
val blockchain = Blockchain.fromNetworkId(currency.id)
val iconRes = if (blockchain == Blockchain.Unknown) {
currency.iconUrl
} else {
blockchain.getRoundIconRes()
}
val iconRes = currency.iconUrl
Column {
Row(
@ -89,7 +85,7 @@ fun ExpandedCurrencyItem(
)
}
val blockchains = currency.contracts?.map { it.blockchain } ?: listOf(
val blockchains = currency.contracts?.map { it.blockchain } ?: listOfNotNull(
Blockchain.fromNetworkId(currency.id)
)
@ -99,11 +95,18 @@ fun ExpandedCurrencyItem(
.fillMaxHeight()
) {
if (blockchains.size > 1) {
val dividerHeight = if (blockchains.size == 2) {
60
} else if (blockchains.size == 3) {
110
} else {
43 * blockchains.size
}
Divider(
color = Color(0xFFDEDEDE),
modifier = Modifier
.height((43 * blockchains.size - 1).dp)
.padding(start = 37.dp, top = 16.dp)
.height(dividerHeight.dp)
.padding(start = 37.5.dp)
.width(1.dp)
)
}
@ -130,12 +133,12 @@ fun ExpandedCurrencyItem(
) {
blockchains.map { blockchain ->
val contract = currency.contracts?.firstOrNull { it.blockchain == blockchain }
val added = if (contract != null) {
addedTokens.map { it.contractAddress }.contains(contract.address)
val added = if (contract != null && contract.address != currency.symbol) {
addedTokens.map { it.token.contractAddress }.contains(contract.address)
} else {
addedBlockchains.contains(blockchain)
}
val canBeRemoved = if (contract != null) {
val canBeRemoved = if (contract != null && contract.address != currency.symbol) {
!nonRemovableTokens.contains(contract.address)
} else {
!nonRemovableBlockchains.contains(blockchain)
@ -146,7 +149,8 @@ fun ExpandedCurrencyItem(
blockchain = blockchain, allowToAdd = allowToAdd,
added = added,
canBeRemoved = canBeRemoved,
onAddCurrencyToggled = onAddCurrencyToggled
onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked,
)
}
}

View file

@ -8,9 +8,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
@Composable
@ -18,11 +18,12 @@ fun ListOfCurrencies(
currencies: List<Currency>,
nonRemovableTokens: List<ContractAddress>,
nonRemovableBlockchains: List<Blockchain>,
addedTokens: List<Token>,
addedTokens: List<TokenWithBlockchain>,
addedBlockchains: List<Blockchain>,
searchInput: String,
allowToAdd: Boolean,
onAddCurrencyToggled: (Currency, Token?) -> Unit
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
) {
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
@ -59,7 +60,8 @@ fun ListOfCurrencies(
allowToAdd = allowToAdd,
expanded = expandedCurrencies.value.contains(currency.id),
onCurrencyClick = onCurrencyClick,
onAddCurrencyToggled = onAddCurrencyToggled
onAddCurrencyToggled = onAddCurrencyToggled,
onNetworkItemClicked = onNetworkItemClicked
)
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.features.tokens.ui.compose
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Switch
@ -21,18 +23,27 @@ import com.tangem.tap.common.extensions.getNetworkName
import com.tangem.tap.common.extensions.getRoundIconRes
import com.tangem.tap.domain.tokens.Contract
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NetworkItem(
currency: Currency, contract: Contract?,
blockchain: Blockchain, allowToAdd: Boolean,
added: Boolean, canBeRemoved: Boolean,
onAddCurrencyToggled: (Currency, Token?) -> Unit
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit
) {
val isBlockchain = contract == null || contract.address == currency.symbol
Row(
modifier = Modifier
.fillMaxWidth()
.combinedClickable {
if (contract != null) onNetworkItemClicked(contract.address)
}
) {
SubcomposeAsyncImage(
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
@ -45,10 +56,12 @@ fun NetworkItem(
.size(20.dp)
.align(Alignment.CenterVertically)
)
Row(modifier = Modifier
.fillMaxHeight()
.weight(1f)
.align(Alignment.CenterVertically)) {
Row(
modifier = Modifier
.fillMaxHeight()
.weight(1f)
.align(Alignment.CenterVertically)
) {
Text(
text = blockchain.name.uppercase(),
fontSize = 13.sp,
@ -57,29 +70,43 @@ fun NetworkItem(
)
Spacer(modifier = Modifier.size(3.dp))
Text(
text = if (contract != null) blockchain.getNetworkName().uppercase() else "MAIN",
text = if (isBlockchain) "MAIN" else blockchain.getNetworkName().uppercase(),
fontSize = 13.sp,
fontWeight = FontWeight.Normal,
color = if (contract != null) Color(0xFF8E8E93) else Color(0xFF1ACE80),
color = if (!isBlockchain) Color(0xFF8E8E93) else Color(0xFF1ACE80),
)
}
val token = if (contract != null) {
Token(
name = currency.name, symbol = currency.symbol, contractAddress = contract.address,
decimals = contract.decimalCount, blockchain = contract.blockchain
if (allowToAdd) {
val token = if (!isBlockchain) {
Token(
id = currency.id,
name = currency.name,
symbol = currency.symbol,
contractAddress = contract!!.address,
decimals = contract.decimalCount,
)
} else {
null
}
val tokenWithBlockchain =
token?.let { TokenWithBlockchain(token, contract!!.blockchain) }
val currencyToSave = if (isBlockchain && contract != null) {
currency.copy(id = contract.networkId)
} else {
currency
}
Switch(
checked = added,
enabled = canBeRemoved,
onCheckedChange = { onAddCurrencyToggled(currencyToSave, tokenWithBlockchain) },
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
colors = SwitchDefaults.colors(
checkedThumbColor = Color(0xFF1ACE80)
)
)
} else {
null
}
if (allowToAdd) Switch(
checked = added,
enabled = canBeRemoved,
onCheckedChange = { onAddCurrencyToggled(currency, token) },
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
colors = SwitchDefaults.colors(
checkedThumbColor = Color(0xFF1ACE80)
)
)
}
}

View file

@ -8,7 +8,7 @@ import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
@ -24,9 +24,14 @@ sealed class WalletAction : Action {
}
data class LoadWallet(val blockchain: Blockchain? = null) : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
data class LoadWallet(val blockchain: BlockchainNetwork? = null) : WalletAction() {
data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction()
data class NoAccount(
val wallet: Wallet,
val blockchain: BlockchainNetwork,
val amountToCreateAccount: String
) : WalletAction()
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
}
@ -35,19 +40,30 @@ sealed class WalletAction : Action {
sealed class MultiWallet : WalletAction() {
data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
data class AddWalletManagers(val walletManagers: List<WalletManager>) : MultiWallet() {
constructor(walletManager: WalletManager) : this(listOf(walletManager))
}
data class AddBlockchain(val blockchain: Blockchain) : MultiWallet()
data class AddBlockchains(val blockchains: List<Blockchain>) : MultiWallet()
data class AddTokens(val tokens: List<Token>) : MultiWallet()
data class AddToken(val token: Token) : MultiWallet()
data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet()
data class AddBlockchain(
val blockchain: BlockchainNetwork,
val walletManager: WalletManager?
) : MultiWallet()
data class AddBlockchains(
val blockchains: List<BlockchainNetwork>, val walletManagers: List<WalletManager>
) : MultiWallet()
data class AddTokens(val tokens: List<Token>, val blockchain: BlockchainNetwork) :
MultiWallet()
data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet()
data class SaveCurrencies(val blockchainNetworks: List<BlockchainNetwork>) : MultiWallet()
object FindTokensInUse : MultiWallet()
object FindBlockchainsInUse : MultiWallet()
data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet()
data class TokenLoaded(
val amount: Amount,
val token: Token,
val blockchain: BlockchainNetwork
) : MultiWallet()
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
data class RemoveWallet(val walletData: WalletData) : MultiWallet()
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
@ -135,5 +151,9 @@ sealed class WalletAction : Action {
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String) : WalletAction()
data class SetWalletRent(
val blockchain: BlockchainNetwork,
val minRent: String,
val rentExempt: String
) : WalletAction()
}

View file

@ -10,10 +10,12 @@ import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.buyIsAllowed
import com.tangem.tap.domain.extensions.sellIsAllowed
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.toPendingTransactions
@ -33,8 +35,7 @@ data class WalletState(
val hashesCountVerified: Boolean? = null,
val walletDialog: StateDialog? = null,
val mainWarningsList: List<WarningMessage> = mutableListOf(),
val walletsData: List<WalletData> = emptyList(),
val walletManagers: List<WalletManager> = emptyList(),
val wallets: List<WalletStore> = listOf(),
val isMultiwalletAllowed: Boolean = false,
val cardCurrency: CryptoCurrencyName? = null,
val selectedCurrency: Currency? = null,
@ -52,45 +53,96 @@ data class WalletState(
val isTangemTwins: Boolean
get() = store.state.globalState.scanResponse?.isTangemTwins() == true
val primaryWallet = if (walletsData.isNotEmpty()) walletsData[0] else null
val primaryWallet: WalletData? = if (wallets.isNotEmpty()) wallets[0].walletsData[0] else null
val primaryWalletManager: WalletManager? =
if (wallets.isNotEmpty()) wallets[0].walletManager else null
val shouldShowDetails: Boolean =
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
val blockchains: List<Blockchain>
get() = walletManagers.map { it.wallet.blockchain }
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
val currencies: List<Currency>
get() = walletsData.mapNotNull { it.currency }
get() = wallets.flatMap { it.walletsData }.map { it.currency }
val walletsData: List<WalletData>
get() = wallets.flatMap { it.walletsData }
val walletManagers: List<WalletManager>
get() = wallets.mapNotNull { it.walletManager }
fun getWalletManager(token: Token?): WalletManager? {
if (token == null) return null
return walletManagers.find { it.wallet.blockchain == token.blockchain }
return wallets
.mapNotNull { it.walletManager }
.find { walletManager ->
walletManager.cardTokens.any { it.contractAddress == token.contractAddress }
}
}
fun getWalletManager(currency: Currency?): WalletManager? {
if (currency?.blockchain == null) return null
return walletManagers.find { it.wallet.blockchain == currency.blockchain }
return wallets.map { it.walletManager }
.find { it?.wallet?.blockchain == currency.blockchain }
}
fun getWalletManager(blockchain: Blockchain): WalletManager? {
return walletManagers.find { it.wallet.blockchain == blockchain }
fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? {
return wallets.find { it.blockchainNetwork == blockchain }?.walletManager
}
fun getWalletData(blockchain: BlockchainNetwork?): WalletData? {
if (blockchain == null) return null
return walletsData.find {
it.currency is Currency.Blockchain &&
it.currency.blockchain == blockchain.blockchain &&
it.currency.derivationPath == blockchain.derivationPath
}
}
fun getWalletStore(currency: Currency?): WalletStore? {
if (currency == null) return null
return wallets.firstOrNull {
it.blockchainNetwork.derivationPath == currency.derivationPath &&
(it.blockchainNetwork.blockchain == currency.blockchain)
}
}
fun getWalletStore(wallet: Wallet?): WalletStore? {
if (wallet == null) return null
val currency =
Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath)
return getWalletStore(currency)
}
fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? {
if (blockchainNetwork == null) return null
return wallets.firstOrNull {
it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath &&
(it.blockchainNetwork.blockchain == blockchainNetwork.blockchain)
}
}
fun getWalletData(currency: Currency?): WalletData? {
if (currency == null) return null
return walletsData.find { it.currency == currency }
}
fun getWalletData(blockchain: Blockchain?): WalletData? {
if (blockchain == null) return null
return walletsData.find { (it.currency as? Currency.Blockchain)?.blockchain == blockchain }
return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency }
}
fun getWalletData(token: Token?): WalletData? {
if (token == null) return null
return walletsData.find { (it.currency as? Currency.Token)?.token == token }
return walletsData.find {
(it.currency as? Currency.Token)?.token == token &&
!it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle)
}
}
fun getWalletData(blockchain: Blockchain?): WalletData? {
if (blockchain == null) return null
return walletsData.find {
(it.currency as? Currency.Blockchain)?.blockchain == blockchain &&
!it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle)
}
}
fun getSelectedWalletData(): WalletData? {
@ -114,36 +166,90 @@ data class WalletState(
if (walletData.currency is Currency.Blockchain) {
return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() &&
wallet.amounts.toSendableAmounts().isEmpty()
wallet.amounts.toSendableAmounts().isEmpty()
} else if (walletData.currency is Currency.Token) (
return wallet.recentTransactions.toPendingTransactionsForToken(
walletData.currency.token, wallet.address).isEmpty()
&& wallet.amounts[AmountType.Token(token = walletData.currency.token)]
?.isAboveZero() != true
)
return wallet.recentTransactions.toPendingTransactionsForToken(
walletData.currency.token, wallet.address
).isEmpty()
&& wallet.amounts[AmountType.Token(token = walletData.currency.token)]
?.isAboveZero() != true
)
}
return false
}
private fun isPrimaryCurrency(walletData: WalletData): Boolean {
return (walletData.currency is Currency.Blockchain &&
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
|| (walletData.currency is Currency.Token &&
walletData.currency.token == store.state.walletState.primaryToken)
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
|| (walletData.currency is Currency.Token &&
walletData.currency.token == store.state.walletState.primaryToken)
}
fun replaceWalletInWallets(walletData: WalletData?): List<WalletData> {
if (walletData == null) return walletsData
fun replaceWalletInWallets(wallet: WalletStore?): List<WalletStore> {
if (wallet == null) return wallets
var changed = false
val updatedWallets = walletsData.map {
if (it.currency == walletData.currency) {
val updatedWallets = wallets.map {
if (it.blockchainNetwork == wallet.blockchainNetwork) {
changed = true
walletData
wallet
} else {
it
}
}
return if (changed) updatedWallets else walletsData + walletData
return if (changed) updatedWallets else wallets + wallet
}
fun updateWalletData(walletData: WalletData?): WalletState {
if (walletData == null) return this
return updateWalletsData(listOf(walletData))
}
fun updateWalletsData(
walletsData: List<WalletData>
): WalletState {
val walletStores = walletsData
.map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) }
.distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) }
return updateWalletStores(walletStores)
}
fun updateWalletStore(walletStore: WalletStore?): WalletState {
return copy(wallets = replaceWalletInWallets(walletStore))
}
fun updateWalletStores(walletStores: List<WalletStore>): WalletState {
val walletStores = walletStores.toMutableList()
val updatedWallets = wallets.map { oldWalletStore ->
val walletStore = walletStores.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork }
if (walletStore != null) {
walletStores.remove(walletStore)
walletStore
} else {
oldWalletStore
}
}
return copy(wallets = updatedWallets + walletStores)
}
fun removeWallet(walletData: WalletData?): WalletState {
if (walletData == null) return this
return if (walletData.currency is Currency.Blockchain) {
val walletStores = wallets.filterNot {
it.blockchainNetwork.blockchain == walletData.currency.blockchain
&& it.blockchainNetwork.derivationPath == walletData.currency.derivationPath
}
copy(wallets = walletStores)
} else {
val walletStore = getWalletStore(walletData.currency)
val walletDataList = walletStore?.walletsData
?.filterNot { it.currency == walletData.currency }
?: emptyList()
val updatedWalletStore = walletStore?.copy(walletsData = walletDataList)
updateWalletStore(updatedWalletStore)
}
}
fun replaceSomeWallets(newWallets: List<WalletData>): List<WalletData> {
@ -165,20 +271,26 @@ data class WalletState(
exchangeManager: CurrencyExchangeManager?,
walletData: WalletData
): WalletData {
return walletData.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData))
return walletData.copy(
tradeCryptoState = TradeCryptoState.from(
exchangeManager,
walletData
)
)
}
fun updateTradeCryptoState(
exchangeManager: CurrencyExchangeManager?,
walletDataList: List<WalletData>
): List<WalletData> {
return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, it)) }
}
fun addWalletManagers(newWalletManagers: List<WalletManager>): WalletState {
val updatedWalletManagers = this.walletManagers +
newWalletManagers.filterNot { this.blockchains.contains(it.wallet.blockchain) }
return copy(walletManagers = updatedWalletManagers)
return walletDataList.map {
it.copy(
tradeCryptoState = TradeCryptoState.from(
exchangeManager,
it
)
)
}
}
}
@ -224,7 +336,8 @@ data class Artwork(
const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png"
const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png"
const val TEMP_CARDANO = "https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659"
const val TEMP_CARDANO =
"https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659"
}
}
@ -233,7 +346,10 @@ data class TradeCryptoState(
val buyingAllowed: Boolean = false,
) {
companion object {
fun from(exchangeManager: CurrencyExchangeManager?, walletData: WalletData): TradeCryptoState {
fun from(
exchangeManager: CurrencyExchangeManager?,
walletData: WalletData
): TradeCryptoState {
val status = exchangeManager ?: return walletData.tradeCryptoState
val currency = walletData.currency
@ -267,7 +383,9 @@ data class WalletData(
fun shouldEnableTokenSendButton(): Boolean = !blockchainAmountIsEmpty() || !tokenAmountIsEmpty()
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() ?: false
private fun blockchainAmountIsEmpty(): Boolean =
currencyData.blockchainAmount?.isZero() ?: false
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
}
@ -280,17 +398,83 @@ sealed interface Currency {
val blockchain: com.tangem.blockchain.common.Blockchain
val currencySymbol: CryptoCurrencyName
val derivationPath: String?
data class Token(
val token: com.tangem.blockchain.common.Token
val token: com.tangem.blockchain.common.Token,
override val blockchain: com.tangem.blockchain.common.Blockchain,
override val derivationPath: String?
) : Currency {
override val blockchain = token.blockchain
override val currencySymbol: CryptoCurrencyName = token.symbol
override val currencySymbol = token.symbol
}
data class Blockchain(
override val blockchain: com.tangem.blockchain.common.Blockchain
override val blockchain: com.tangem.blockchain.common.Blockchain,
override val derivationPath: String?
) : Currency {
override val currencySymbol: CryptoCurrencyName = blockchain.currency
}
fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
if (derivationPath == null || derivationStyle == null) return false
return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
}
companion object {
fun fromBlockchainNetwork(
blockchainNetwork: BlockchainNetwork,
token: com.tangem.blockchain.common.Token? = null
): Currency {
return if (token != null) {
Currency.Token(
token = token,
blockchain = blockchainNetwork.blockchain,
derivationPath = blockchainNetwork.derivationPath
)
} else {
Currency.Blockchain(
blockchain = blockchainNetwork.blockchain,
derivationPath = blockchainNetwork.derivationPath
)
}
}
}
}
data class WalletStore(
val walletManager: WalletManager?,
val blockchainNetwork: BlockchainNetwork,
val walletsData: List<WalletData>
) {
fun updateWallets(walletDataList: List<WalletData>): WalletStore {
val relevantWalletDataList = walletDataList.filter {
it.currency.blockchain == blockchainNetwork.blockchain &&
it.currency.derivationPath == blockchainNetwork.derivationPath
}.toMutableList()
val updatedWalletDataList = walletsData.map { walletData ->
val matchingWalletData = relevantWalletDataList.find { it.currency == walletData.currency }
if (matchingWalletData != null) relevantWalletDataList.remove(matchingWalletData)
matchingWalletData ?: walletData
}
return copy(walletsData = updatedWalletDataList + relevantWalletDataList)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as WalletStore
if (walletManager != other.walletManager) return false
if (blockchainNetwork != other.blockchainNetwork) return false
return true
}
override fun hashCode(): Int {
var result = walletManager?.hashCode() ?: 0
result = 31 * result + blockchainNetwork.hashCode()
return result
}
}

View file

@ -10,6 +10,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.Currency
@ -31,11 +32,8 @@ class MultiWalletMiddleware {
val tapWalletManager = globalState.tapWalletManager
when (action) {
is WalletAction.MultiWallet.AddWalletManagers -> {
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
if (globalState.scanResponse?.isDemoCard() == true) {
addDummyBalances(action.walletManagers)
}
is WalletAction.MultiWallet.AddBlockchains -> {
handleAddingWalletManagers(globalState, action.walletManagers)
}
is WalletAction.MultiWallet.SelectWallet -> {
if (action.walletData != null) {
@ -43,33 +41,39 @@ class MultiWalletMiddleware {
}
}
is WalletAction.MultiWallet.AddToken -> {
globalState.scanResponse?.card?.cardId?.let {
currenciesRepository.saveAddedToken(it, action.token)
}
addTokens(listOf(action.token), walletState, globalState)
addTokens(listOf(action.token), action.blockchain, walletState, globalState)
}
is WalletAction.MultiWallet.AddTokens -> {
addTokens(action.tokens, walletState, globalState)
addTokens(action.tokens, action.blockchain, walletState, globalState)
}
is WalletAction.MultiWallet.AddBlockchain -> {
globalState.scanResponse?.let {
currenciesRepository.saveAddedBlockchain(it.card.cardId, action.blockchain)
if (walletState?.blockchains?.contains(action.blockchain) != true) {
tapWalletManager.walletManagerFactory
.makeWalletManagerForApp(it, action.blockchain)?.let { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
}
}
action.walletManager?.let {
handleAddingWalletManagers(globalState, listOf(action.walletManager))
}
store.dispatch(WalletAction.LoadFiatRate(
currencyList = listOf(Currency.Blockchain(action.blockchain)))
globalState.scanResponse?.let {
currenciesRepository.saveUpdatedCurrency(
cardId = it.card.cardId,
blockchainNetwork = action.blockchain
)
}
store.dispatch(
WalletAction.LoadFiatRate(
currencyList = listOf(
Currency.Blockchain(
action.blockchain.blockchain,
action.blockchain.derivationPath
)
)
)
)
store.dispatch(WalletAction.LoadWallet(action.blockchain)
store.dispatch(
WalletAction.LoadWallet(action.blockchain)
)
}
is WalletAction.MultiWallet.SaveCurrencies -> {
globalState.scanResponse?.card?.cardId?.let {
currenciesRepository.saveCardCurrencies(it, action.cardCurrencies)
currenciesRepository.saveCurrencies(it, action.blockchainNetworks)
}
}
is WalletAction.MultiWallet.RemoveWallet -> {
@ -77,13 +81,27 @@ class MultiWalletMiddleware {
when (val currency = action.walletData.currency) {
is Currency.Blockchain -> {
cardId?.let {
currenciesRepository.removeBlockchain(it, currency.blockchain)
currenciesRepository.removeBlockchain(
cardId = it,
blockchainNetwork = BlockchainNetwork(
currency.blockchain, currency.derivationPath,
emptyList()
)
)
}
}
is Currency.Token -> {
walletState?.getWalletManager(currency.token)
?.removeToken(currency.token)
cardId?.let { currenciesRepository.removeToken(it, currency.token) }
val walletManager = walletState?.getWalletManager(currency.token)
if (walletManager != null) {
walletManager.removeToken(currency.token)
cardId?.let {
currenciesRepository.removeToken(
cardId = it,
token = currency.token,
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
)
}
}
}
}
}
@ -94,8 +112,12 @@ class MultiWalletMiddleware {
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)
tapWalletManager.walletManagerFactory.makeWalletManagersForApp(
scanResponse,
blockchains
)
scope.launch {
walletManagers.map { walletManager ->
@ -105,18 +127,19 @@ class MultiWalletMiddleware {
val coinAmount = wallet.amounts[AmountType.Coin]?.value
if (coinAmount != null && !coinAmount.isZero()) {
scope.launch(Dispatchers.Main) {
if (walletState?.getWalletData(wallet.blockchain) == null) {
store.dispatch(
WalletAction.MultiWallet.AddWalletManagers(
listOfNotNull(walletManager)
)
)
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
if (walletState?.getWalletData(blockchainNetwork) == null) {
store.dispatch(
WalletAction.MultiWallet.AddBlockchain(
wallet.blockchain
blockchainNetwork, walletManager
)
)
store.dispatch(
WalletAction.LoadWallet.Success(
wallet = wallet,
blockchain = blockchainNetwork
)
)
store.dispatch(WalletAction.LoadWallet.Success(wallet))
}
}
}
@ -130,30 +153,42 @@ class MultiWalletMiddleware {
val walletFactory = tapWalletManager.walletManagerFactory
val card = scanResponse.card
val walletManager = walletState?.getWalletManager(Blockchain.Ethereum)
?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum)
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()) {
currenciesRepository.saveAddedTokens(card.cardId, result.data)
val blockchainNetwork = BlockchainNetwork(
walletManager.wallet.blockchain,
walletManager.wallet.publicKey.derivationPath?.rawPath,
walletManager.cardTokens.toList()
)
currenciesRepository.saveUpdatedCurrency(
card.cardId,
blockchainNetwork
)
store.dispatch(
WalletAction.MultiWallet.AddWalletManagers(
WalletAction.MultiWallet.AddBlockchain(
blockchainNetwork,
walletManager
)
)
store.dispatch(
WalletAction.MultiWallet.AddBlockchain(
walletManager.wallet.blockchain
)
)
store.dispatch(
WalletAction.MultiWallet.AddTokens(
walletManager.cardTokens.toList()
walletManager.cardTokens.toList(),
blockchainNetwork
)
)
}
@ -173,38 +208,60 @@ class MultiWalletMiddleware {
}
}
private fun addTokens(tokens: List<Token>, walletState: WalletState?, globalState: GlobalState?) {
private fun handleAddingWalletManagers(
globalState: GlobalState,
walletManagers: List<WalletManager>
) {
globalState.feedbackManager?.infoHolder?.setWalletsInfo(walletManagers)
if (globalState.scanResponse?.isDemoCard() == true) {
addDummyBalances(walletManagers)
}
}
private fun addTokens(
tokens: List<Token>, blockchainNetwork: BlockchainNetwork,
walletState: WalletState?, globalState: GlobalState?
) {
val scanResponse = globalState?.scanResponse ?: return
val wmFactory = globalState.tapWalletManager.walletManagerFactory
val groupedTokens = tokens.groupBy { it.blockchain }
val walletManagers = groupedTokens.mapNotNull { entry ->
val blockchain = entry.key
val tokensList = entry.value
val walletManager = walletState?.getWalletManager(blockchain)
?: wmFactory.makeWalletManagerForApp(scanResponse, blockchain)?.also {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
}
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokensList.map { Currency.Token(it) }))
walletManager?.apply { addTokens(tokensList) }
}
val walletManager = walletState?.getWalletManager(blockchainNetwork)
?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, it))
} ?: return
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokens.map { token ->
Currency.Token(
token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath
)
}))
walletManager.addTokens(tokens)
currenciesRepository.saveUpdatedCurrency(
cardId = scanResponse.card.cardId,
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
)
scope.launch {
walletManagers.forEach { walletManager ->
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))
}
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
)
)
}
}
is com.tangem.common.services.Result.Failure -> {}
}
}
is com.tangem.common.services.Result.Failure -> {}
}
}
}

View file

@ -5,8 +5,6 @@ import android.net.Uri
import androidx.core.content.ContextCompat
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.isZero
@ -24,10 +22,7 @@ import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.scope
import com.tangem.tap.store
@ -62,7 +57,11 @@ class WalletMiddleware {
when (action) {
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState)
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(
action,
walletState,
globalState
)
is WalletAction.LoadWallet -> {
scope.launch {
if (action.blockchain == null) {
@ -78,9 +77,19 @@ class WalletMiddleware {
is WalletAction.LoadWallet.Success -> {
val coinAmount = action.wallet.amounts[AmountType.Coin]?.value
if (coinAmount != null && !coinAmount.isZero()) {
if (walletState.getWalletData(action.wallet.blockchain) == null) {
store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain))
store.dispatch(WalletAction.LoadWallet.Success(action.wallet))
if (walletState.getWalletData(action.blockchain) == null) {
store.dispatch(
WalletAction.MultiWallet.AddBlockchain(
action.blockchain,
null
)
)
store.dispatch(
WalletAction.LoadWallet.Success(
action.wallet,
action.blockchain
)
)
}
}
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
@ -120,7 +129,8 @@ class WalletMiddleware {
)
when (result) {
is CompletionResult.Success -> {
val scanNoteResponse = globalState.scanResponse?.copy(card = result.data)
val scanNoteResponse =
globalState.scanResponse?.copy(card = result.data)
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) }
}
is CompletionResult.Failure -> {
@ -196,8 +206,10 @@ class WalletMiddleware {
}
}
is WalletAction.ShowDialog.QrCode -> {
val selectedWalletData = walletState.getWalletData(walletState.selectedCurrency) ?: return
val selectedAddressData = selectedWalletData.walletAddresses?.selectedAddress ?: return
val selectedWalletData =
walletState.getWalletData(walletState.selectedCurrency) ?: return
val selectedAddressData =
selectedWalletData.walletAddresses?.selectedAddress ?: return
val currency = selectedWalletData.currency
store.dispatchDialogShow(AppDialog.AddressInfoDialog(currency, selectedAddressData))
@ -208,28 +220,38 @@ class WalletMiddleware {
private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
val selectedWalletData = state?.getSelectedWalletData()
val currency = selectedWalletData?.currency
val walletManager = state?.getWalletManager(currency)
val wallet = walletManager?.wallet
val walletStore = state?.getWalletStore(currency)
return if (amount != null) {
if (amount.type is AmountType.Token) {
prepareSendActionForToken(amount, state, selectedWalletData, wallet, walletManager)
prepareSendActionForToken(amount, state, selectedWalletData, walletStore)
} else {
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletManager)
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager)
}
} else {
val amounts = wallet?.amounts?.toSendableAmounts()
val amounts = walletStore?.walletManager?.wallet?.amounts?.toSendableAmounts()
if (currency != null && state.isMultiwalletAllowed) {
when (currency) {
is Currency.Blockchain -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency }
?: return WalletAction.Send.ChooseCurrency(amounts)
PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager)
val amountToSend =
amounts?.find { it.currencySymbol == currency.blockchain.currency }
?: return WalletAction.Send.ChooseCurrency(amounts)
PrepareSendScreen(
coinAmount = amountToSend,
coinRate = selectedWalletData.fiatRate,
walletManager = walletStore.walletManager
)
}
is Currency.Token -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol }
?: return WalletAction.Send.ChooseCurrency(amounts)
prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager)
val amountToSend =
amounts?.find { it.currencySymbol == currency.token.symbol }
?: return WalletAction.Send.ChooseCurrency(amounts)
prepareSendActionForToken(
amount = amountToSend,
state = state,
selectedWalletData = selectedWalletData,
walletStore = walletStore
)
}
}
} else {
@ -237,24 +259,36 @@ class WalletMiddleware {
WalletAction.Send.ChooseCurrency(amounts)
} else {
val amountToSend = amounts?.first()
PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate, walletManager)
PrepareSendScreen(
coinAmount = amountToSend,
coinRate = selectedWalletData?.fiatRate,
walletManager = walletStore?.walletManager
)
}
}
}
}
private fun prepareSendActionForToken(
amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?,
walletManager: WalletManager?
amount: Amount,
state: WalletState?,
selectedWalletData: WalletData?,
walletStore: WalletStore?
): PrepareSendScreen {
val coinRate = state?.getWalletData(wallet?.blockchain)?.fiatRate
val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate
val tokenRate = if (state?.isMultiwalletAllowed == true) {
selectedWalletData?.fiatRate
} else {
selectedWalletData?.currencyData?.token?.fiatRate
}
val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin)
return PrepareSendScreen(
wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager,
amount, tokenRate)
coinAmount = coinAmount,
coinRate = coinRate,
walletManager = walletStore?.walletManager,
tokenAmount = amount,
tokenRate = tokenRate
)
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.common.extensions.isZero
import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.redux.*
@ -17,66 +18,85 @@ import com.tangem.tap.store
class MultiWalletReducer {
fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
return when (action) {
is WalletAction.MultiWallet.AddWalletManagers -> {
state.addWalletManagers(action.walletManagers)
}
is WalletAction.MultiWallet.AddBlockchains -> {
val walletsData = action.blockchains.map { blockchain ->
val wallet = state.getWalletManager(blockchain)?.wallet
val wallets: List<WalletStore> = action.blockchains.map { blockchain ->
val walletManager = action.walletManagers.first {
it.wallet.blockchain == blockchain.blockchain &&
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
}
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
}
WalletData(
val walletData = WalletData(
currencyData = BalanceWidgetData(
BalanceStatus.Loading,
blockchain.fullName,
currencySymbol = blockchain.currency,
blockchain.blockchain.fullName,
currencySymbol = blockchain.blockchain.currency,
token = cardToken
),
walletAddresses = createAddressList(wallet),
mainButton = WalletMainButton.SendButton(false),
currency = Currency.Blockchain(blockchain),
currency = Currency.Blockchain(
blockchain.blockchain,
blockchain.derivationPath
),
)
WalletStore(
walletManager = walletManager,
blockchainNetwork = blockchain,
walletsData = listOf(walletData)
)
}
val selectedCurrency = if (!state.isMultiwalletAllowed) {
walletsData[0].currency
wallets[0].walletsData[0].currency
} else {
state.selectedCurrency
}
state.copy(
walletsData = walletsData,
wallets = wallets,
selectedCurrency = selectedCurrency
)
}
is WalletAction.MultiWallet.AddBlockchain -> {
val wallet = state.getWalletManager(action.blockchain)?.wallet
val walletManager = action.walletManager ?: state.getWalletManager(action.blockchain)
val wallet = walletManager?.wallet
val walletData = WalletData(
currencyData = BalanceWidgetData(
BalanceStatus.Loading,
action.blockchain.fullName,
currencySymbol = action.blockchain.currency,
action.blockchain.blockchain.fullName,
currencySymbol = action.blockchain.blockchain.currency,
),
walletAddresses = createAddressList(wallet),
mainButton = WalletMainButton.SendButton(false),
currency = Currency.Blockchain(action.blockchain),
currency = Currency.Blockchain(
action.blockchain.blockchain,
action.blockchain.derivationPath
),
)
val newState = state.copy(walletsData = state.replaceWalletInWallets(walletData))
val walletStore = WalletStore(
walletManager = walletManager,
blockchainNetwork = action.blockchain,
walletsData = listOf(walletData)
)
val newState = state.updateWalletStore(walletStore)
if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) {
OnWalletLoadedReducer().reduce(wallet, newState)
OnWalletLoadedReducer().reduce(wallet, action.blockchain, newState)
} else {
newState
}
}
is WalletAction.MultiWallet.AddTokens -> {
val wallets = action.tokens.mapNotNull { token -> token.toWallet(state) }
state.copy(walletsData = state.replaceSomeWallets(wallets))
addTokens(action.tokens, action.blockchain, state)
}
is WalletAction.MultiWallet.AddToken -> {
val walletData = action.token.toWallet(state) ?: return state
state.copy(walletsData = state.replaceWalletInWallets(walletData))
addTokens(listOf(action.token), action.blockchain, state)
}
is WalletAction.MultiWallet.TokenLoaded -> {
val pendingTransactions = state.getWalletManager(action.token)
@ -84,7 +104,8 @@ class MultiWalletReducer {
wallet.recentTransactions.toPendingTransactions(wallet.address)
} ?: emptyList()
val sendButtonEnabled = action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
val sendButtonEnabled =
action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
val tokenPendingTransactions = pendingTransactions
.filter { it.currency == action.amount.currencySymbol }
val tokenBalanceStatus = when {
@ -107,10 +128,13 @@ class MultiWalletReducer {
),
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
currency = Currency.Token(action.token)
currency = Currency.Token(
token = action.token,
blockchain = action.blockchain.blockchain,
derivationPath = action.blockchain.derivationPath
)
)
val wallets = state.replaceWalletInWallets(newTokenWalletData)
state.copy(walletsData = wallets)
state.updateWalletData(newTokenWalletData)
}
is WalletAction.MultiWallet.SetIsMultiwalletAllowed ->
state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
@ -119,21 +143,7 @@ class MultiWalletReducer {
state.copy(selectedCurrency = action.walletData?.currency)
is WalletAction.MultiWallet.RemoveWallet -> {
val wallets = state.walletsData.filterNot {
it.currency == action.walletData.currency
}
if (action.walletData.currency is Currency.Blockchain) {
state.copy(
walletsData = wallets,
walletManagers = state.walletManagers.filterNot {
it.wallet.blockchain == action.walletData.currency.blockchain
}
)
} else {
state.copy(walletsData = wallets)
}
state.removeWallet(action.walletData)
}
is WalletAction.MultiWallet.SetPrimaryBlockchain ->
state.copy(primaryBlockchain = action.blockchain)
@ -147,7 +157,14 @@ class MultiWalletReducer {
}
}
fun Token.toWallet(state: WalletState): WalletData? {
private fun addTokens(
tokens: List<Token>, blockchain: BlockchainNetwork, state: WalletState
): WalletState {
val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) }
return state.updateWalletsData(wallets)
}
fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? {
if (!state.isMultiwalletAllowed) return null
if (state.currencies.any { it is Currency.Token && it.token == this }) {
return null
@ -164,6 +181,6 @@ fun Token.toWallet(state: WalletState): WalletData? {
),
walletAddresses = walletAddresses,
mainButton = WalletMainButton.SendButton(false),
currency = Currency.Token(this),
currency = Currency.fromBlockchainNetwork(blockchain, this)
)
}

View file

@ -8,6 +8,7 @@ import com.tangem.tap.common.extensions.toFiatValue
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.redux.*
@ -19,25 +20,30 @@ import java.math.RoundingMode
class OnWalletLoadedReducer {
fun reduce(wallet: Wallet, walletState: WalletState): WalletState {
fun reduce(wallet: Wallet, blockchainNetwork: BlockchainNetwork, walletState: WalletState): WalletState {
return if (!walletState.isMultiwalletAllowed) {
onSingleWalletLoaded(wallet, walletState)
} else {
onMultiWalletLoaded(wallet, walletState)
onMultiWalletLoaded(wallet, blockchainNetwork, walletState)
}
}
private fun onMultiWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {
private fun onMultiWalletLoaded(
wallet: Wallet,
blockchainNetwork: BlockchainNetwork,
walletState: WalletState
): WalletState {
val fiatCurrencySymbol = store.state.globalState.appCurrency
val exchangeManager = store.state.globalState.currencyExchangeManager
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
if (walletState.getWalletData(wallet.blockchain) == null) {
if (walletState.getWalletData(blockchainNetwork) == null) {
return walletState
}
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
wallet.blockchain.currency)
wallet.blockchain.currency
)
val pendingTransactions = wallet.recentTransactions
.toPendingTransactions(wallet.address)
@ -48,7 +54,7 @@ class OnWalletLoadedReducer {
} else {
BalanceStatus.VerifiedOnline
}
val walletData = walletState.getWalletData(wallet.blockchain)
val walletData = walletState.getWalletData(blockchainNetwork)
val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) }
val newWalletData = walletData?.copy(
@ -63,29 +69,34 @@ class OnWalletLoadedReducer {
),
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(coinSendButton),
currency = Currency.Blockchain(wallet.blockchain),
currency = Currency.fromBlockchainNetwork(blockchainNetwork),
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData),
)
val tokens = wallet.getTokens().mapNotNull { token ->
val tokenWalletData = walletState.getWalletData(token)
val tokenPendingTransactions = pendingTransactions.filter { it.currency == token.symbol }
val tokenPendingTransactions =
pendingTransactions.filter { it.currency == token.symbol }
val tokenBalanceStatus = when {
tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress
pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
else -> BalanceStatus.VerifiedOnline
}
val tokenAmountValue = wallet.getTokenAmount(token)?.value
val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) }
val tokenFiatAmount =
tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) }
val tokenSendButton = newWalletData?.shouldEnableTokenSendButton() == true
&& tokenPendingTransactions.isEmpty()
&& tokenPendingTransactions.isEmpty()
tokenWalletData?.copy(
currencyData = tokenWalletData.currencyData.copy(
status = tokenBalanceStatus,
blockchainAmount = coinAmountValue,
amount = tokenAmountValue,
amountFormatted = tokenAmountValue?.toFormattedCurrencyString(token.decimals, token.symbol),
amountFormatted = tokenAmountValue?.toFormattedCurrencyString(
token.decimals,
token.symbol
),
fiatAmount = tokenFiatAmount,
fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol)
),
@ -102,8 +113,9 @@ class OnWalletLoadedReducer {
} else {
ProgressState.Done
}
return walletState.copy(
state = state, walletsData = wallets, error = null
val newState = walletState.updateWalletsData(wallets)
return newState.copy(
state = state, error = null
)
}
@ -118,7 +130,8 @@ class OnWalletLoadedReducer {
val tokenAmount = wallet.getTokenAmount(token)
if (tokenAmount != null) {
val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
val tokenFiatAmount =
tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
TokenData(
tokenAmount.value?.toFormattedCurrencyString(
token.decimals, token.symbol
@ -134,7 +147,8 @@ class OnWalletLoadedReducer {
val amount = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = amount?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
wallet.blockchain.currency)
wallet.blockchain.currency
)
val fiatRate = walletState.primaryWallet?.fiatRate
val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN)
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
@ -161,9 +175,11 @@ class OnWalletLoadedReducer {
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet),
)
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
return walletState.copy(
state = ProgressState.Done, walletsData = wallets, error = null
val wallets = listOfNotNull(walletData)
val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets)
return walletState.updateWalletStore(updatedStore).copy(
state = ProgressState.Done, error = null
)
}
}

View file

@ -12,6 +12,7 @@ import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getArtworkUrl
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.ui.BalanceStatus
@ -46,11 +47,21 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
is WalletAction.EmptyWallet -> {
newState = newState.copy(
state = ProgressState.Done,
walletsData = listOf(
WalletData(
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
mainButton = WalletMainButton.CreateWalletButton(true),
currency = Currency.Blockchain(Blockchain.Unknown)
wallets = listOf(
WalletStore(
walletManager = null,
blockchainNetwork = BlockchainNetwork(
Blockchain.Unknown,
null,
emptyList()
),
walletsData = listOf(
WalletData(
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
mainButton = WalletMainButton.CreateWalletButton(true),
currency = Currency.Blockchain(Blockchain.Unknown, null)
)
)
)
)
)
@ -58,27 +69,43 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
is WalletAction.LoadData.Failure -> {
when (action.error) {
is TapError.NoInternetConnection -> {
val wallets = newState.walletsData
val wallets = newState.wallets
.map {
it.copy(
currencyData = it.currencyData.copy(
status = BalanceStatus.Unreachable
)
walletsData = it.walletsData.map {
it.copy(
currencyData = it.currencyData.copy(
status = BalanceStatus.Unreachable
)
)
}
)
}
newState = newState.copy(
state = ProgressState.Error,
error = ErrorType.NoInternetConnection,
walletsData = wallets
wallets = wallets
)
}
is TapError.UnknownBlockchain -> {
newState = newState.copy(
state = ProgressState.Done,
walletsData = listOf(
WalletData(
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
currency = Currency.Blockchain(Blockchain.Unknown)
wallets = listOf(
WalletStore(
walletManager = null,
blockchainNetwork = BlockchainNetwork(
Blockchain.Unknown,
null,
emptyList()
),
walletsData = listOf(
WalletData(
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
currency = Currency.Blockchain(Blockchain.Unknown, null)
)
)
)
)
)
@ -94,26 +121,37 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
}
is WalletAction.LoadWallet -> {
if (action.blockchain == null) {
val wallets = newState.walletsData.map { wallet ->
wallet.copy(
currencyData = wallet.currencyData.copy(
status = BalanceStatus.Loading,
currency = wallet.currencyData.currency,
currencySymbol = wallet.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
val wallets = newState.wallets.map {
it.copy(
walletsData = it.walletsData.map { walletData ->
walletData.copy(
currencyData = walletData.currencyData.copy(
status = BalanceStatus.Loading,
currency = walletData.currencyData.currency,
currencySymbol = walletData.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
tradeCryptoState = TradeCryptoState.from(
exchangeManager,
walletData
)
)
}
)
}
newState = newState.copy(
state = ProgressState.Loading,
walletsData = wallets,
wallets = wallets,
)
} else {
val walletManager = newState.getWalletManager(action.blockchain) ?: return newState
val blockchain = walletManager.wallet.blockchain
val currencies = listOf(Currency.Blockchain(blockchain)) +
walletManager.cardTokens.map { Currency.Token(it) }
val currencies = listOf(Currency.fromBlockchainNetwork(action.blockchain)) +
walletManager.cardTokens.map {
Currency.fromBlockchainNetwork(
action.blockchain,
it
)
}
val newWallets = newState.walletsData.filter { currencies.contains(it.currency) }
.map { wallet ->
wallet.copy(
@ -126,38 +164,52 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
)
}
val wallets = newState.replaceSomeWallets(newWallets)
newState = newState.copy(walletsData = newState.updateTradeCryptoState(exchangeManager, wallets))
val wallets = newState.updateTradeCryptoState(exchangeManager, newState.replaceSomeWallets(newWallets))
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets)
newState = newState.updateWalletStore(walletStore)
}
}
is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState)
is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(
wallet = action.wallet,
blockchainNetwork = action.blockchain,
walletState = newState
)
is WalletAction.LoadWallet.NoAccount -> {
val walletData = newState.getWalletData(action.wallet.blockchain)?.copy(
val walletData = newState.getWalletData(action.blockchain)?.copy(
currencyData = BalanceWidgetData(
BalanceStatus.NoAccount, action.wallet.blockchain.fullName,
currencySymbol = action.wallet.blockchain.currency,
amountToCreateAccount = action.amountToCreateAccount
)
)
val wallets = newState.replaceWalletInWallets(walletData)
var updatedWalletStore = newState.getWalletStore(action.blockchain)
?.updateWallets(listOfNotNull(walletData))
val progressState =
if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
if (updatedWalletStore?.walletsData?.any { it.currencyData.status == BalanceStatus.Loading } == true) {
ProgressState.Loading
} else {
ProgressState.Done
}
newState = newState.copy(
state = progressState,
walletsData = newState.updateTradeCryptoState(exchangeManager, wallets)
)
updatedWalletStore =
updatedWalletStore?.updateWallets(
newState.updateTradeCryptoState(exchangeManager, updatedWalletStore.walletsData)
)
newState = newState.updateWalletStore(updatedWalletStore)
.copy(
state = progressState
)
}
is WalletAction.LoadWallet.Failure -> {
val message = if (newState.error == ErrorType.NoInternetConnection) {
null
} else {
action.errorMessage
}
val walletData = newState.getWalletData(action.wallet.blockchain)
val walletStore = newState.getWalletStore(action.wallet)
val walletData = walletStore?.walletsData?.first { it.currency is Currency.Blockchain }
val newWalletData = walletData?.copy(
currencyData = walletData.currencyData.copy(
status = BalanceStatus.Unreachable,
@ -173,17 +225,23 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
)
}
val wallets = newState.replaceSomeWallets(listOfNotNull(newWalletData) + tokenWallets)
val updatedWallets =
newState.updateTradeCryptoState(
exchangeManager,
walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData
)
newState = newState.updateWalletsData(updatedWallets)
val progressState =
if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
if (newState.walletsData.any { it.currencyData.status == BalanceStatus.Loading }) {
ProgressState.Loading
} else {
ProgressState.Done
}
newState = newState.copy(
state = progressState,
walletsData = newState.updateTradeCryptoState(exchangeManager, wallets)
)
}
is WalletAction.SetArtworkId -> {
@ -230,25 +288,21 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
?: return newState
val address = walletAddresses.list.firstOrNull { it.type == action.type }
?: return newState
val wallets = newState.replaceWalletInWallets(
selectedWalletData?.copy(
walletAddresses = WalletAddresses(
address,
walletAddresses.list
)
newState = newState.updateWalletData(selectedWalletData?.copy(
walletAddresses = WalletAddresses(
address,
walletAddresses.list
)
)
newState = newState.copy(walletsData = wallets)
))
}
is WalletAction.SetWalletRent -> {
var walletData = newState.getWalletData(action.blockchain)
if (walletData == null) {
newState
} else {
walletData = walletData.copy(warningRent = WalletRent(action.minRent, action.rentExempt))
newState = newState.copy(
walletsData = newState.replaceSomeWallets(listOf(walletData))
)
walletData =
walletData.copy(warningRent = WalletRent(action.minRent, action.rentExempt))
newState = newState.updateWalletsData(listOf(walletData))
}
}
}
@ -322,8 +376,11 @@ private fun setMultiWalletFiatRate(
rate: BigDecimal, rateFormatted: String, currency: Currency,
appCurrency: FiatCurrencyName, state: WalletState
): WalletState {
val walletStore = state.getWalletStore(currency) ?: return state
val wallet = walletStore.walletManager?.wallet
val walletData = state.getWalletData(currency) ?: return state
val wallet = state.getWalletManager(currency)?.wallet
val fiatAmount = when (currency) {
is Currency.Blockchain ->
wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate)
@ -338,14 +395,14 @@ private fun setMultiWalletFiatRate(
),
fiatRate = rate, fiatRateString = rateFormatted
)
return state.copy(walletsData = state.replaceWalletInWallets(newWalletData))
return state.updateWalletData(newWalletData)
}
private fun setSingeWalletFiatRate(
rate: BigDecimal, rateFormatted: String, currency: Currency,
appCurrency: FiatCurrencyName, state: WalletState
): WalletState {
val wallet = state.walletManagers[0].wallet
val wallet = state.primaryWalletManager?.wallet ?: return state
val token = wallet.getFirstToken()
if (currency == state.primaryWallet?.currency) {
@ -356,7 +413,7 @@ private fun setSingeWalletFiatRate(
fiatRate = rate,
fiatRateString = rateFormatted
)
return state.copy(walletsData = listOf(walletData))
return state.updateWalletData(walletData)
} else if (currency is Currency.Token && currency.token == token) {
val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
val tokenData = state.primaryWallet?.currencyData?.token?.copy(
@ -371,7 +428,7 @@ private fun setSingeWalletFiatRate(
)
)
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
return state.copy(walletsData = wallets)
return state.updateWalletsData(wallets)
}
return state
}

View file

@ -18,6 +18,7 @@ import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.onboarding.getQRReceiveMessage
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.*
@ -114,7 +115,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
store.dispatch(
WalletAction.LoadWallet(
blockchain = selectedWallet.currency.blockchain
blockchain = BlockchainNetwork(selectedWallet.currency.blockchain, selectedWallet.currency.derivationPath, emptyList())
)
)
}
@ -188,7 +189,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
pendingTransactionAdapter.submitList(pendingTransactions)
binding.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
binding.rvPendingTransaction.show(pendingTransactions.isNotEmpty())
}
private fun setupAddressCard(state: WalletData) = with(binding.lWalletDetails) {
@ -212,7 +213,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
chipGroupAddressType.hide()
}
tvAddress.text = state.walletAddresses.selectedAddress.address
tvExplore?.setOnClickListener {
tvExplore.setOnClickListener {
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,

View file

@ -11,6 +11,7 @@ import com.tangem.blockchain.common.Token
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.loadCurrenciesIcon
import com.tangem.tap.common.extensions.show
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
@ -26,7 +27,11 @@ class WalletAdapter
return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0
}
fun submitList(list: List<WalletData>, primaryBlockchain: Blockchain?, primaryToken: Token? = null) {
fun submitList(
list: List<WalletData>,
primaryBlockchain: Blockchain?,
primaryToken: Token? = null
) {
// We used this method to sort the list of currencies. Sorting is disabled for now.
super.submitList(list)
}
@ -44,21 +49,22 @@ class WalletAdapter
object DiffUtilCallback : DiffUtil.ItemCallback<WalletData>() {
override fun areContentsTheSame(
oldItem: WalletData, newItem: WalletData
oldItem: WalletData, newItem: WalletData
) = oldItem == newItem
override fun areItemsTheSame(
oldItem: WalletData, newItem: WalletData
oldItem: WalletData, newItem: WalletData
) = oldItem == newItem
}
class WalletsViewHolder(val binding: ItemCurrencyWalletBinding) :
RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root) {
fun bind(wallet: WalletData) = with(binding) {
tvCurrency.text = wallet.currencyData.currency
tvAmount.text = wallet.currencyData.amountFormatted?.takeWhile { !it.isWhitespace() }
tvCurrencySymbol.text = wallet.currencyData.amountFormatted?.takeLastWhile { !it.isWhitespace() }
tvCurrencySymbol.text =
wallet.currencyData.amountFormatted?.takeLastWhile { !it.isWhitespace() }
tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted
tvExchangeRate.text = wallet.fiatRateString
cardWallet.setOnClickListener {
@ -67,6 +73,11 @@ class WalletAdapter
val blockchain = wallet.currency.blockchain
val token = (wallet.currency as? Currency.Token)?.token
val isCustom =
wallet.currency.isCustomCurrency(store.state.globalState.scanResponse?.card?.derivationStyle)
tvExchangeRate.show(!isCustom)
tvCustomCurrency.show(isCustom)
Picasso.get().loadCurrenciesIcon(
imageView = ivCurrency,
textView = tvTokenLetter,

View file

@ -8,6 +8,9 @@ import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.StateDialog
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.TapWorkarounds.derivationStyle
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletDialog
@ -97,11 +100,19 @@ class MultiWalletView : WalletView {
walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken)
binding.btnAddToken.setOnClickListener {
store.dispatch(TokensAction.LoadCurrencies)
val card = store.state.globalState.scanResponse!!.card
store.dispatch(TokensAction.LoadCurrencies(
supportedBlockchains = currenciesRepository.getBlockchains(
card.firmwareVersion,
card.isTestCard
)))
store.dispatch(TokensAction.AllowToAddTokens(true))
store.dispatch(TokensAction.SetAddedCurrencies(state.walletsData))
store.dispatch(TokensAction.SetAddedCurrencies(
wallets = state.walletsData,
derivationStyle = card.derivationStyle
))
store.dispatch(TokensAction.SetNonRemovableCurrencies(
state.wallets.filterNot { state.canBeRemoved(it) })
state.walletsData.filterNot { state.canBeRemoved(it) })
)
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}

View file

@ -117,6 +117,21 @@
app:layout_constraintTop_toTopOf="@id/guideline"
tools:text="USD 3 588" />
<TextView
android:id="@+id/tv_custom_currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="@color/darkGray2"
android:textSize="14sp"
android:background="@drawable/shape_rectangle_rounded_4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_currency"
app:layout_constraintTop_toTopOf="@id/guideline"
android:text="@string/common_custom" />
<TextView
android:id="@+id/tv_status_error_message"
android:layout_width="0dp"

View file

@ -38,5 +38,9 @@
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="common_custom">Custom</string>
</resources>

View file

@ -38,4 +38,9 @@
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="common_custom">Custom</string>
</resources>

View file

@ -38,5 +38,9 @@
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="common_custom">Custom</string>
</resources>

View file

@ -38,4 +38,9 @@
<string name="token_details_send_blocked_fee_format">Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек.</string>
<string name="currency_subtitle_expanded">Доступные сети</string>
<string name="alert_manage_tokens_addresses_message">Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства.</string>
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
<string name="common_custom">Пользовательский</string>
</resources>

View file

@ -38,4 +38,9 @@
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="common_custom">Custom</string>
</resources>

View file

@ -1,4 +1,4 @@
ext.versions = [
kotlin : '1.6.10',
build_gradle: '7.0.4',
build_gradle: '7.1.3',
]

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip