Updated on 2026-08-14
This commit is contained in:
commit
85920292c9
19 changed files with 159 additions and 251 deletions
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -52,7 +52,7 @@ fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: FiatCurrencyName): String
|
|||
return "≈ ${fiatCurrencyName} $this"
|
||||
}
|
||||
|
||||
fun Coins.CurrenciesResponse.Currency.toFormattedString(): String = "${this.name} (${this.code}) - ${this.unit}"
|
||||
fun CurrenciesResponse.Currency.toFormattedString(): String = "${this.name} (${this.code}) - ${this.unit}"
|
||||
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
||||
|
|
|
|||
|
|
@ -87,22 +87,21 @@ class TapWalletManager {
|
|||
return newResult
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
|
||||
val currencies = wallet.getTokens()
|
||||
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(fiatCurrency, currencies)
|
||||
loadFiatRate(currencyId, coinsList)
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencies: List<Currency>) {
|
||||
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.LoadFiatRate.Success(
|
||||
currency to priceResult.data
|
||||
))
|
||||
}
|
||||
is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure)
|
||||
null -> {}
|
||||
|
|
@ -111,26 +110,26 @@ class TapWalletManager {
|
|||
}
|
||||
|
||||
// get and submit previous result of equivalents.
|
||||
val throttledResult = currencies.filter { fiatRatesThrottler.isStillThrottled(it) }.map {
|
||||
val throttledResult = coinsList.filter { fiatRatesThrottler.isStillThrottled(it) }.map {
|
||||
Pair(it, fiatRatesThrottler.geValue(it))
|
||||
}
|
||||
if (throttledResult.isNotEmpty()) {
|
||||
handleFiatRatesResult(throttledResult.toMap())
|
||||
}
|
||||
|
||||
val toUpdateCurrencies = currencies.filter { !fiatRatesThrottler.isStillThrottled(it) }
|
||||
val toUpdateIds = toUpdateCurrencies.mapNotNull { it.coinId }.distinct()
|
||||
if (toUpdateIds.isEmpty()) return
|
||||
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.coins.prices(fiatCurrency, toUpdateIds)) {
|
||||
when (val result = tangemTechService.rates(currencyId, coinIds)) {
|
||||
is Result.Success -> {
|
||||
val priceResultList: Map<String, Result<BigDecimal>> = result.data.prices.mapValues {
|
||||
val ratesResultList: Map<String, Result<BigDecimal>> = result.data.rates.mapValues {
|
||||
Result.Success(it.value.toBigDecimal())
|
||||
}
|
||||
val updatedCurrencies = mutableMapOf<Currency, Result<BigDecimal>?>()
|
||||
toUpdateCurrencies.forEach { currency ->
|
||||
priceResultList[currency.coinId]?.let {
|
||||
currenciesToUpdate.forEach { currency ->
|
||||
ratesResultList[currency.coinId]?.let {
|
||||
updatedCurrencies[currency] = it
|
||||
fiatRatesThrottler.updateThrottlingTo(currency)
|
||||
fiatRatesThrottler.setValue(currency, it)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Wallet
|
|||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
|
|
@ -48,7 +48,7 @@ sealed class DetailsAction : Action {
|
|||
object CreateBackup : DetailsAction()
|
||||
|
||||
sealed class AppCurrencyAction : DetailsAction() {
|
||||
data class SetCurrencies(val currencies: List<Coins.CurrenciesResponse.Currency>) : AppCurrencyAction()
|
||||
data class SetCurrencies(val currencies: List<CurrenciesResponse.Currency>) : AppCurrencyAction()
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
object Cancel : AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName) : AppCurrencyAction()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
|
|
@ -77,14 +76,12 @@ class DetailsMiddleware {
|
|||
|
||||
scope.launch {
|
||||
val tangemTechService = action.tangemTechService
|
||||
when (val result = tangemTechService.coins.currencies()) {
|
||||
when (val result = tangemTechService.currencies()) {
|
||||
is Result.Success -> {
|
||||
val fiatCurrencies = result.data.currencies.filter {
|
||||
it.type == Coins.CurrenciesResponse.CurrencyType.Fiat.type
|
||||
}
|
||||
if (fiatCurrencies.isNotEmpty() && fiatCurrencies.toSet() != storedFiatCurrencies.toSet()) {
|
||||
fiatCurrenciesPrefStorage.save(fiatCurrencies)
|
||||
dispatchOnMain(DetailsAction.AppCurrencyAction.SetCurrencies(fiatCurrencies))
|
||||
val currenciesList = result.data.currencies
|
||||
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
|
||||
fiatCurrenciesPrefStorage.save(currenciesList)
|
||||
dispatchOnMain(DetailsAction.AppCurrencyAction.SetCurrencies(currenciesList))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.features.details.redux
|
|||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
|
|
@ -55,5 +55,5 @@ enum class SecurityOption { LongTap, PassCode, AccessCode }
|
|||
data class AppCurrencyState(
|
||||
val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
|
||||
val showAppCurrencyDialog: Boolean = false,
|
||||
val fiatCurrencies: List<Coins.CurrenciesResponse.Currency>? = null,
|
||||
val fiatCurrencies: List<CurrenciesResponse.Currency>? = null,
|
||||
)
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui
|
|||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
|
|
@ -13,11 +13,11 @@ class CurrencySelectionDialog {
|
|||
|
||||
var dialog: AlertDialog? = null
|
||||
|
||||
fun show(currencies: List<Coins.CurrenciesResponse.Currency>, currentAppCurrency: FiatCurrencyName, context: Context) {
|
||||
fun show(currenciesList: List<CurrenciesResponse.Currency>, currentAppCurrency: FiatCurrencyName, context: Context) {
|
||||
|
||||
if (dialog == null) {
|
||||
val currenciesToShow = currencies.map { it.toFormattedString() }.toTypedArray()
|
||||
var currentSelection = currencies.indexOfFirst { it.code == currentAppCurrency }
|
||||
val currenciesToShow = currenciesList.map { it.toFormattedString() }.toTypedArray()
|
||||
var currentSelection = currenciesList.indexOfFirst { it.code == currentAppCurrency }
|
||||
|
||||
dialog = AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.details_row_title_currency))
|
||||
|
|
@ -25,7 +25,7 @@ class CurrencySelectionDialog {
|
|||
store.dispatch(DetailsAction.AppCurrencyAction.Cancel)
|
||||
}
|
||||
.setPositiveButton(context.getString(R.string.common_done)) { _, _ ->
|
||||
val selectedCurrency = currencies[currentSelection]
|
||||
val selectedCurrency = currenciesList[currentSelection]
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.code))
|
||||
}
|
||||
.setOnDismissListener {
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ fun SelectTokenNetworkDialog(dialog: DomainDialog.SelectTokenDialog, onDismissRe
|
|||
items = dialog.items,
|
||||
onSelect = dialog.onSelect,
|
||||
onDismissRequest = onDismissRequest
|
||||
) { contract -> TitleSubtitle(dialog.networkIdConverter(contract.networkId), contract.address) }
|
||||
) { network -> TitleSubtitle(dialog.networkIdConverter(network.networkId), network.address ?: "") }
|
||||
}
|
||||
|
|
@ -12,14 +12,10 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.AddCustomTokenService
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -40,7 +36,7 @@ fun TestAddCustomTokenActions() {
|
|||
ActionRow("All in one") { AllInOne() }
|
||||
|
||||
// Any action
|
||||
// ActionRow("CustomActions - find tokens active=false, decimals != null") { CustomActions() }
|
||||
// ActionRow("CustomActions - find coins active=false, decimals != null") { CustomActions() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,41 +143,6 @@ private fun UnknownContracts() {
|
|||
|
||||
@Composable
|
||||
private fun CustomActions() {
|
||||
|
||||
CustomActionButton(
|
||||
name = "Find tokens in several networks",
|
||||
action = {
|
||||
val manager = AddCustomTokenService(TangemTechService())
|
||||
val currencies = manager.tokens()
|
||||
val asdfsd = mutableMapOf<String, MutableList<Any>>()
|
||||
val contractAddresses = currencies.mapNotNull { currency ->
|
||||
currency.contracts?.map { it.address }
|
||||
}.flatten()
|
||||
contractAddresses.take(500).forEachIndexed() { index, address ->
|
||||
when (val result = manager.checkAddress(address)) {
|
||||
is Result.Success -> {
|
||||
val contractList = mutableListOf<Any>()
|
||||
result.data.forEach { token ->
|
||||
token.contracts.forEach { contract ->
|
||||
if (!contract.active && contract.decimalCount != null) {
|
||||
contractList.add(contract)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contractList.isNotEmpty()) {
|
||||
val list = asdfsd[address] ?: mutableListOf()
|
||||
list.addAll(contractList)
|
||||
asdfsd[address] = list
|
||||
}
|
||||
Timber.e("Success. handle $index item from size ${contractAddresses.size}. Result = ${asdfsd.size}")
|
||||
}
|
||||
is Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
val result = asdfsd.filter { it.value.size > 1 }
|
||||
if (result.isEmpty()) return@CustomActionButton
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
data class LoadFiatRate(
|
||||
val wallet: Wallet? = null, val currencyList: List<Currency>? = null,
|
||||
val wallet: Wallet? = null, val coinsList: List<Currency>? = null,
|
||||
) : WalletAction() {
|
||||
data class Success(val fiatRate: Pair<Currency, BigDecimal?>) : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
|
|
|
|||
|
|
@ -55,19 +55,17 @@ class MultiWalletMiddleware {
|
|||
blockchainNetwork = action.blockchain
|
||||
)
|
||||
}
|
||||
store.dispatch(
|
||||
WalletAction.LoadFiatRate(
|
||||
currencyList = listOf(
|
||||
Currency.Blockchain(
|
||||
action.blockchain.blockchain,
|
||||
action.blockchain.derivationPath
|
||||
)
|
||||
store.dispatch(WalletAction.LoadFiatRate(
|
||||
coinsList = listOf(
|
||||
Currency.Blockchain(
|
||||
action.blockchain.blockchain,
|
||||
action.blockchain.derivationPath
|
||||
)
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet(action.blockchain, action.walletManager)
|
||||
)
|
||||
))
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
action.blockchain, action.walletManager
|
||||
))
|
||||
}
|
||||
is WalletAction.MultiWallet.SaveCurrencies -> {
|
||||
globalState.scanResponse?.card?.cardId?.let {
|
||||
|
|
@ -225,7 +223,7 @@ class MultiWalletMiddleware {
|
|||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, it))
|
||||
} ?: return
|
||||
|
||||
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokens.map { token ->
|
||||
store.dispatch(WalletAction.LoadFiatRate(coinsList = tokens.map { token ->
|
||||
Currency.Token(
|
||||
token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath
|
||||
)
|
||||
|
|
|
|||
|
|
@ -101,27 +101,26 @@ class WalletMiddleware {
|
|||
warningsMiddleware.tryToShowAppRatingWarning(action.wallet)
|
||||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
val tapWalletManager = globalState.tapWalletManager
|
||||
val fiatAppCurrency = globalState.appCurrency
|
||||
val appCurrencyId = globalState.appCurrency
|
||||
scope.launch {
|
||||
when {
|
||||
action.wallet != null -> {
|
||||
globalState.tapWalletManager.loadFiatRate(
|
||||
fiatCurrency = fiatAppCurrency,
|
||||
currencyId = appCurrencyId,
|
||||
wallet = action.wallet,
|
||||
)
|
||||
}
|
||||
action.currencyList != null -> {
|
||||
action.coinsList != null -> {
|
||||
globalState.tapWalletManager.loadFiatRate(
|
||||
fiatCurrency = fiatAppCurrency,
|
||||
currencies = action.currencyList,
|
||||
currencyId = appCurrencyId,
|
||||
coinsList = action.coinsList,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val currencyList = walletState.walletsData.map { it.currency }
|
||||
val coinsList = walletState.walletsData.map { it.currency }
|
||||
globalState.tapWalletManager.loadFiatRate(
|
||||
fiatCurrency = fiatAppCurrency,
|
||||
currencies = currencyList,
|
||||
currencyId = appCurrencyId,
|
||||
coinsList = coinsList,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.persistence
|
|||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -21,14 +21,14 @@ class FiatCurrenciesPrefStorage(
|
|||
}
|
||||
}
|
||||
|
||||
fun save(currencies: List<Coins.CurrenciesResponse.Currency>) {
|
||||
fun save(currencies: List<CurrenciesResponse.Currency>) {
|
||||
val json: String = converter.toJson(currencies)
|
||||
return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply()
|
||||
}
|
||||
|
||||
fun restore(): List<Coins.CurrenciesResponse.Currency> {
|
||||
fun restore(): List<CurrenciesResponse.Currency> {
|
||||
val json = preferences.getString(FIAT_CURRENCIES_KEY, "")
|
||||
val type = converter.typedList(Coins.CurrenciesResponse.Currency::class.java)
|
||||
val type = converter.typedList(CurrenciesResponse.Currency::class.java)
|
||||
if (json.isNullOrBlank()) return emptyList()
|
||||
|
||||
return converter.fromJson(json, type) ?: emptyList()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.domain
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -11,9 +11,9 @@ sealed interface DomainDialog {
|
|||
data class DialogError(val error: DomainError) : DomainDialog
|
||||
|
||||
data class SelectTokenDialog(
|
||||
val items: List<Coins.CheckAddressResponse.Token.Contract>,
|
||||
val items: List<CoinsResponse.Coin.Network>,
|
||||
val networkIdConverter: (String) -> String,
|
||||
val onSelect: (Coins.CheckAddressResponse.Token.Contract) -> Unit,
|
||||
val onSelect: (CoinsResponse.Coin.Network) -> Unit,
|
||||
val onClose: VoidCallback = {}
|
||||
) : DomainDialog
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.domain.features.addCustomToken
|
||||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
|
||||
/**
|
||||
|
|
@ -11,41 +11,31 @@ class AddCustomTokenService(
|
|||
private val tangemTechService: TangemTechService
|
||||
) {
|
||||
|
||||
suspend fun checkAddress(
|
||||
suspend fun findToken(
|
||||
contractAddress: String,
|
||||
networkId: String? = null
|
||||
): Result<List<Coins.CheckAddressResponse.Token>> {
|
||||
val result = tangemTechService.coins.checkAddress(contractAddress, networkId)
|
||||
networkId: String? = null,
|
||||
active: Boolean? = null,
|
||||
): Result<List<CoinsResponse.Coin>> {
|
||||
val result = tangemTechService.coins(contractAddress, networkId, active)
|
||||
return when (result) {
|
||||
is Result.Success -> {
|
||||
val resultTokens = result.data.tokens
|
||||
var tokensList = mutableListOf<Coins.CheckAddressResponse.Token>()
|
||||
resultTokens.forEach { token ->
|
||||
val contractsWithTheSameAddress = token.contracts
|
||||
var coinsList = mutableListOf<CoinsResponse.Coin>()
|
||||
result.data.coins.forEach { coin ->
|
||||
val networksWithTheSameAddress = coin.networks
|
||||
.filter { it.address != null || it.decimalCount != null }
|
||||
.filter { it.address == contractAddress }
|
||||
.filter { it.decimalCount != null }
|
||||
if (contractsWithTheSameAddress.isNotEmpty()) {
|
||||
val newToken = token.copy(contracts = contractsWithTheSameAddress)
|
||||
tokensList.add(newToken)
|
||||
if (networksWithTheSameAddress.isNotEmpty()) {
|
||||
val newToken = coin.copy(networks = networksWithTheSameAddress)
|
||||
coinsList.add(newToken)
|
||||
}
|
||||
}
|
||||
if (tokensList.size > 1) {
|
||||
if (coinsList.size > 1) {
|
||||
// https://tangem.slack.com/archives/GMXC6PP71/p1649672562078679
|
||||
tokensList = mutableListOf(tokensList[0])
|
||||
coinsList = mutableListOf(coinsList[0])
|
||||
}
|
||||
Result.Success(tokensList)
|
||||
Result.Success(coinsList)
|
||||
}
|
||||
is Result.Failure -> result
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun tokens(): List<Coins.TokensResponse.Token> {
|
||||
return when (val result = tangemTechService.coins.tokens()) {
|
||||
is Result.Success -> {
|
||||
val tokens = result.data.tokens
|
||||
tokens.filter { it.contracts.isNullOrEmpty() }
|
||||
}
|
||||
is Result.Failure -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ import com.tangem.domain.redux.DomainState
|
|||
import com.tangem.domain.redux.dispatchOnMain
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.network.api.tangemTech.Coins
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -165,7 +165,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
}
|
||||
|
||||
/**
|
||||
* This feature is only needed until Solana tokens are added.
|
||||
* This feature is only needed until Solana coins are added.
|
||||
* While they are not there - this function excludes the Solana blockchain if the user has
|
||||
* filled in at least one field of the token.
|
||||
*/
|
||||
|
|
@ -210,7 +210,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
|
||||
private suspend fun requestInfoAboutToken(
|
||||
contractAddress: String,
|
||||
): List<Coins.CheckAddressResponse.Token> {
|
||||
): List<CoinsResponse.Coin> {
|
||||
val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager)
|
||||
dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true))))
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
// got the result faster than 500ms and the delay would only be the difference between them.
|
||||
delay(500)
|
||||
|
||||
val foundTokensResult = tangemTechServiceManager.checkAddress(contractAddress, selectedNetworkId)
|
||||
val foundTokensResult = tangemTechServiceManager.findToken(contractAddress, selectedNetworkId)
|
||||
val result = when (foundTokensResult) {
|
||||
is Result.Success -> foundTokensResult.data
|
||||
is Result.Failure -> {
|
||||
|
|
@ -236,7 +236,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
return result
|
||||
}
|
||||
|
||||
private suspend fun manageFoundTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) {
|
||||
private suspend fun manageFoundTokenChanges(foundTokens: List<CoinsResponse.Coin>) {
|
||||
if (foundTokens.isEmpty()) {
|
||||
// token not found - it's completely custom
|
||||
TokenAlreadyAdded.remove()
|
||||
|
|
@ -252,12 +252,12 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
val foundToken = foundTokens[0]
|
||||
dispatchOnMain(SetFoundTokenId(foundToken.id))
|
||||
when {
|
||||
foundToken.contracts.isEmpty() -> {
|
||||
foundToken.networks.isEmpty() -> {
|
||||
Timber.e("Unexpected state -> throw to FB")
|
||||
}
|
||||
foundToken.contracts.size == 1 -> {
|
||||
foundToken.networks.size == 1 -> {
|
||||
// token with single contract address
|
||||
val singleTokenContract = foundToken.contracts[0]
|
||||
val singleTokenContract = foundToken.networks[0]
|
||||
fillTokenFields(foundToken, singleTokenContract)
|
||||
|
||||
val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList()
|
||||
|
|
@ -266,7 +266,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
updateAddButton(false)
|
||||
PotentialScamToken.replace(TokenAlreadyAdded)
|
||||
} else {
|
||||
// not in the saved tokens list
|
||||
// not in the saved coins list
|
||||
if (singleTokenContract.active) {
|
||||
updateTokenDetailFields(false)
|
||||
updateAddButton(true)
|
||||
|
|
@ -286,7 +286,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
PotentialScamToken.replace(TokenAlreadyAdded)
|
||||
|
||||
val dialog = DomainDialog.SelectTokenDialog(
|
||||
items = foundToken.contracts,
|
||||
items = foundToken.networks,
|
||||
networkIdConverter = { networkId ->
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
if (blockchain == null || blockchain == Blockchain.Unknown) {
|
||||
|
|
@ -486,14 +486,14 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
}
|
||||
|
||||
private suspend fun fillTokenFields(
|
||||
token: Coins.CheckAddressResponse.Token,
|
||||
contract: Coins.CheckAddressResponse.Token.Contract,
|
||||
token: CoinsResponse.Coin,
|
||||
coinNetwork: CoinsResponse.Coin.Network,
|
||||
) {
|
||||
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: Blockchain.Unknown
|
||||
val blockchain = Blockchain.fromNetworkId(coinNetwork.networkId) ?: Blockchain.Unknown
|
||||
Network.setFieldValue(Field.Data(blockchain, false))
|
||||
Name.setFieldValue(Field.Data(token.name, false))
|
||||
Symbol.setFieldValue(Field.Data(token.symbol, false))
|
||||
Decimals.setFieldValue(Field.Data(contract.decimalCount.toString(), false))
|
||||
Decimals.setFieldValue(Field.Data(coinNetwork.decimalCount.toString(), false))
|
||||
dispatchOnMain(UpdateForm(hubState))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ data class AddCustomTokenState(
|
|||
Blockchain.Polygon,
|
||||
Blockchain.Avalanche,
|
||||
Blockchain.Fantom,
|
||||
Blockchain.Solana, // not evm. Should be unsupported for tokens until they are added to the Blockchain SDK
|
||||
Blockchain.Solana, // not evm. Should be unsupported for coins until they are added to the Blockchain SDK
|
||||
)
|
||||
if (type == CustomTokenType.Token) networks.remove(Blockchain.Solana)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,53 +8,43 @@ import java.math.BigDecimal
|
|||
interface HttpResponse
|
||||
sealed interface TangemTechResponse : HttpResponse
|
||||
|
||||
sealed class Coins : TangemTechResponse {
|
||||
data class PricesResponse(val prices: Map<String, Double>) : Coins()
|
||||
data class CoinsResponse(
|
||||
val imageHost: String,
|
||||
val coins: List<Coin>,
|
||||
val total: Int
|
||||
) : TangemTechResponse {
|
||||
|
||||
data class CheckAddressResponse(val imageHost: String?, val tokens: List<Token>, val total: Int) : Coins() {
|
||||
data class Token(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val active: Boolean,
|
||||
val contracts: List<Contract>
|
||||
) {
|
||||
data class Contract(
|
||||
val networkId: String,
|
||||
val address: String,
|
||||
val decimalCount: BigDecimal?,
|
||||
val active: Boolean
|
||||
)
|
||||
}
|
||||
data class Coin(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val active: Boolean,
|
||||
val networks: List<Network> = listOf()
|
||||
) : TangemTechResponse {
|
||||
|
||||
data class Network(
|
||||
val networkId: String,
|
||||
val address: String? = null,
|
||||
val decimalCount: BigDecimal? = null,
|
||||
) : TangemTechResponse
|
||||
}
|
||||
}
|
||||
|
||||
data class TokensResponse(val imageHost: String, val tokens: List<Token>, val total: Int) : Coins() {
|
||||
data class Token(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contracts: List<Contract>?
|
||||
) {
|
||||
data class Contract(
|
||||
val networkId: String,
|
||||
val address: String,
|
||||
val decimalCount: BigDecimal?,
|
||||
)
|
||||
}
|
||||
}
|
||||
//rates.keys = networkId's
|
||||
data class RatesResponse(val rates: Map<String, Double>) : TangemTechResponse
|
||||
|
||||
data class CurrenciesResponse(val currencies: List<Currency>) {
|
||||
data class Currency(
|
||||
val id: String,
|
||||
val code: String, // this is an uppercase id
|
||||
val name: String,
|
||||
val rateBTC: String,
|
||||
val unit: String, // $, €, ₽
|
||||
val type: String,
|
||||
)
|
||||
data class CurrenciesResponse(val currencies: List<Currency>) {
|
||||
|
||||
enum class CurrencyType(val type: String) {
|
||||
Fiat("fiat"), Crypto("crypto")
|
||||
}
|
||||
data class Currency(
|
||||
val id: String,
|
||||
val code: String, // this is an uppercase id
|
||||
val name: String,
|
||||
val rateBTC: String,
|
||||
val unit: String, // $, €, ₽
|
||||
val type: CurrencyType,
|
||||
) : TangemTechResponse
|
||||
|
||||
enum class CurrencyType(val type: String) {
|
||||
Fiat("fiat"), Crypto("crypto")
|
||||
}
|
||||
}
|
||||
|
|
@ -8,22 +8,20 @@ import retrofit2.http.Query
|
|||
*/
|
||||
interface TangemTechApi {
|
||||
|
||||
@GET("coins/prices")
|
||||
suspend fun coinsPrices(
|
||||
@Query("currency") currency: String,
|
||||
@Query("ids") ids: String,
|
||||
): Coins.PricesResponse
|
||||
|
||||
@GET("coins/check-address")
|
||||
suspend fun coinsCheckAddress(
|
||||
@Query("contractAddress") contractAddress: String,
|
||||
@GET("coins")
|
||||
suspend fun coins(
|
||||
@Query("contractAddress") contractAddress: String? = null,
|
||||
@Query("networkId") networkId: String? = null,
|
||||
): Coins.CheckAddressResponse
|
||||
@Query("active") active: Boolean? = null,
|
||||
): CoinsResponse
|
||||
|
||||
@GET("coins/currencies")
|
||||
suspend fun coinsCurrencies(): Coins.CurrenciesResponse
|
||||
@GET("rates")
|
||||
suspend fun rates(
|
||||
@Query("currencyId") currencyId: String,
|
||||
@Query("coinIds") coinIds: String,
|
||||
): RatesResponse
|
||||
|
||||
@GET("coins/tokens")
|
||||
suspend fun coinsTokens(): Coins.TokensResponse
|
||||
@GET("currencies")
|
||||
suspend fun currencies(): CurrenciesResponse
|
||||
|
||||
}
|
||||
|
|
@ -12,19 +12,33 @@ import kotlinx.coroutines.withContext
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TangemTechService {
|
||||
|
||||
val coins: CoinsRoute = CoinsRoute()
|
||||
|
||||
private val techRoutes: List<TangemTechRoute> = listOf(
|
||||
coins
|
||||
)
|
||||
|
||||
private val headerInterceptors = mutableListOf<AddHeaderInterceptor>(
|
||||
CacheControlHttpInterceptor(cacheMaxAge)
|
||||
)
|
||||
|
||||
|
||||
private var api: TangemTechApi = createApi()
|
||||
|
||||
suspend fun coins(
|
||||
contractAddress: String? = null,
|
||||
networkId: String? = null,
|
||||
active: Boolean? = null,
|
||||
): Result<CoinsResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.coins(contractAddress, networkId, active) }
|
||||
}
|
||||
|
||||
suspend fun rates(
|
||||
currency: String,
|
||||
ids: List<String>
|
||||
): Result<RatesResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest {
|
||||
api.rates(currency.lowercase(), ids.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun currencies(): Result<CurrenciesResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.currencies() }
|
||||
}
|
||||
|
||||
fun addHeaderInterceptors(interceptors: List<AddHeaderInterceptor>) {
|
||||
headerInterceptors.removeAll(interceptors)
|
||||
headerInterceptors.addAll(interceptors)
|
||||
|
|
@ -37,49 +51,11 @@ class TangemTechService {
|
|||
interceptors = headerInterceptors.toList(),
|
||||
// logEnabled = true,
|
||||
)
|
||||
return retrofit.create(TangemTechApi::class.java).apply {
|
||||
techRoutes.forEach { it.setApi(this) }
|
||||
}
|
||||
return retrofit.create(TangemTechApi::class.java)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val baseUrl = "https://api.tangem-tech.com/"
|
||||
const val baseUrl = "https://api.tangem-tech.com/v1/"
|
||||
const val cacheMaxAge = 600
|
||||
}
|
||||
}
|
||||
|
||||
private interface TangemTechRoute {
|
||||
fun setApi(api: TangemTechApi)
|
||||
}
|
||||
|
||||
class CoinsRoute : TangemTechRoute {
|
||||
private lateinit var api: TangemTechApi
|
||||
|
||||
override fun setApi(api: TangemTechApi) {
|
||||
this.api = api
|
||||
}
|
||||
|
||||
suspend fun prices(
|
||||
currency: String,
|
||||
ids: List<String>
|
||||
): Result<Coins.PricesResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest {
|
||||
api.coinsPrices(currency.lowercase(), ids.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkAddress(
|
||||
contractAddress: String,
|
||||
networkId: String? = null
|
||||
): Result<Coins.CheckAddressResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.coinsCheckAddress(contractAddress, networkId) }
|
||||
}
|
||||
|
||||
suspend fun currencies(): Result<Coins.CurrenciesResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.coinsCurrencies() }
|
||||
}
|
||||
|
||||
suspend fun tokens(): Result<Coins.TokensResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.coinsTokens() }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue