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