Updated on 2026-08-14
This commit is contained in:
parent
369ea273c2
commit
4fa88e1959
13 changed files with 110 additions and 237 deletions
|
|
@ -1,16 +1,14 @@
|
|||
{
|
||||
"configValues": [
|
||||
{
|
||||
"name": "coinMarketCapKey",
|
||||
"value": "f6622117-c043-47a0-8975-9d673ce484de"
|
||||
},
|
||||
{
|
||||
"name": "moonPayApiKey",
|
||||
"value": "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE"
|
||||
},
|
||||
{
|
||||
"name": "moonPayApiSecretKey",
|
||||
"value": "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C"
|
||||
}
|
||||
]
|
||||
}
|
||||
[
|
||||
{
|
||||
"name": "coinMarketCapKey",
|
||||
"value": "f6622117-c043-47a0-8975-9d673ce484de"
|
||||
},
|
||||
{
|
||||
"name": "moonPayApiKey",
|
||||
"value": "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE"
|
||||
},
|
||||
{
|
||||
"name": "moonPayApiSecretKey",
|
||||
"value": "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,20 +1,18 @@
|
|||
{
|
||||
"features": [
|
||||
{
|
||||
"name": "payIdIsEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "usePayId",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "showWelcomeMessageAtStart",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "useTopUp",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
}
|
||||
[
|
||||
{
|
||||
"name": "payIdIsEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "usePayId",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "showWelcomeMessageAtStart",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "useTopUp",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
|
|
@ -8,7 +8,10 @@ import com.tangem.tap.common.images.PicassoHelper
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.config.*
|
||||
import com.tangem.tap.domain.config.ConfigManager
|
||||
import com.tangem.tap.domain.config.ConfigNameResolver
|
||||
import com.tangem.tap.domain.config.LocalLoader
|
||||
import com.tangem.tap.domain.config.RemoteLoader
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
|
|
@ -47,11 +50,11 @@ class TapApplication : Application() {
|
|||
|
||||
|
||||
private fun loadConfigs() {
|
||||
val moshiAdapter = createMoshi().adapter(ConfigModel::class.java)
|
||||
val moshi = createMoshi()
|
||||
val nameResolver = ConfigNameResolver.get()
|
||||
|
||||
val localLoader = LocalLoader(this, nameResolver, moshiAdapter)
|
||||
val remoteLoader = RemoteLoader(nameResolver, moshiAdapter)
|
||||
val localLoader = LocalLoader(this, nameResolver, moshi)
|
||||
val remoteLoader = RemoteLoader(nameResolver, moshi)
|
||||
val configManager = ConfigManager(localLoader, remoteLoader)
|
||||
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap
|
||||
|
||||
object TapConfig {
|
||||
const val usePayId: Boolean = true
|
||||
const val useTopUp: Boolean = true
|
||||
const val coinMarketCapKey = "f6622117-c043-47a0-8975-9d673ce484de"
|
||||
const val moonPayApiKey = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE"
|
||||
const val moonPayApiSecretKey = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C"
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.WalletManager
|
|||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
|
|
@ -26,7 +25,6 @@ import java.math.BigDecimal
|
|||
class TapWalletManager {
|
||||
private val payIdManager = PayIdManager()
|
||||
private val coinMarketCapService = CoinMarketCapService()
|
||||
private var tapWorkarounds: TapWorkarounds? = null
|
||||
|
||||
suspend fun loadWalletData() {
|
||||
val walletManager = store.state.globalState.scanNoteResponse?.walletManager
|
||||
|
|
@ -84,7 +82,7 @@ class TapWalletManager {
|
|||
if (addAnalyticsEvent) {
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
|
||||
}
|
||||
tapWorkarounds = TapWorkarounds(data.card)
|
||||
store.state.globalState.configManager?.onCardScanned(data.card)
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
|
|
@ -101,9 +99,11 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
|
||||
return@withContext
|
||||
}
|
||||
val config = store.state.globalState.configManager?.config?: return@withContext
|
||||
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id,
|
||||
tapWorkarounds?.isStart2Coin != true && TapConfig.useTopUp
|
||||
!config.isStart2Coin && config.useTopUp
|
||||
))
|
||||
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
|
||||
store.dispatch(WalletAction.LoadFiatRate)
|
||||
|
|
@ -150,8 +150,9 @@ class TapWalletManager {
|
|||
|
||||
|
||||
private suspend fun loadPayIdIfNeeded(): Result<String?>? {
|
||||
val config = store.state.globalState.configManager?.config
|
||||
val scanNoteResponse = store.state.globalState.scanNoteResponse
|
||||
if (!TapConfig.usePayId ||
|
||||
if (config?.usePayId == false ||
|
||||
scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -169,7 +170,8 @@ class TapWalletManager {
|
|||
is Result.Success -> {
|
||||
val payId = result.data
|
||||
if (payId == null) {
|
||||
if (tapWorkarounds?.isPayIdCreationEnabled() == false) {
|
||||
val config = store.state.globalState.configManager?.config?: return@withContext
|
||||
if (!config.payIdIsEnabled) {
|
||||
store.dispatch(WalletAction.DisablePayId)
|
||||
} else {
|
||||
store.dispatch(WalletAction.LoadPayId.NotCreated)
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.commands.Card
|
||||
|
||||
class TapWorkarounds(val card: Card) {
|
||||
|
||||
val isStart2Coin: Boolean = card.cardData?.issuerName == START_2_COIN_ISSUER
|
||||
|
||||
fun isPayIdCreationEnabled(): Boolean {
|
||||
if (isStart2Coin) return false
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val START_2_COIN_ISSUER = "start2coin"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Condition {
|
||||
fun isMet(): Boolean
|
||||
}
|
||||
|
||||
class CardIsStart2CoinCondition(private val card: Card?) : Condition {
|
||||
|
||||
override fun isMet(): Boolean = card?.cardData?.issuerName?.toLowerCase(Locale.US) == "start2coin"
|
||||
}
|
||||
|
||||
class ConditionsFactory {
|
||||
companion object {
|
||||
fun create(name: String): Condition? {
|
||||
return when (name) {
|
||||
Feature.payIdIsEnabled -> CardIsStart2CoinCondition(null)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import android.content.Context
|
|||
import com.google.firebase.ktx.Firebase
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -11,7 +13,7 @@ import timber.log.Timber
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ConfigLoader {
|
||||
fun loadConfig(onComplete: (Config) -> Unit)
|
||||
fun loadConfig(onComplete: (ConfigModel) -> Unit)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -41,25 +43,29 @@ class ConfigNameResolver {
|
|||
}
|
||||
}
|
||||
|
||||
class KeyValueModel<T>(val name: String, value: T?)
|
||||
|
||||
class LocalLoader(
|
||||
private val context: Context,
|
||||
private val nameResolver: NameResolver,
|
||||
private val adapter: JsonAdapter<ConfigModel>
|
||||
private val moshi: Moshi
|
||||
) : ConfigLoader {
|
||||
|
||||
override fun loadConfig(onComplete: (Config) -> Unit) {
|
||||
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
||||
val config = try {
|
||||
val jsonFeatures = readAssetAsString(nameResolver.getFeaturesName())
|
||||
var configModel = adapter.fromJson(jsonFeatures)
|
||||
val features = configModel?.toFeatures(ConfigType.Local) ?: mutableMapOf()
|
||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
||||
val valuesType = Types.newParameterizedType(List::class.java, ConfigValueModel::class.java)
|
||||
val valuesAdapter: JsonAdapter<List<ConfigValueModel>> = moshi.adapter(valuesType)
|
||||
|
||||
val jsonFeatures = readAssetAsString(nameResolver.getFeaturesName())
|
||||
val jsonConfigValues = readAssetAsString(nameResolver.getConfigValuesName())
|
||||
configModel = adapter.fromJson(jsonConfigValues)
|
||||
val configValues = configModel?.toConfigValues() ?: mutableMapOf()
|
||||
Config(features, configValues)
|
||||
|
||||
ConfigModel(featureAdapter.fromJson(jsonFeatures) ?: listOf(),
|
||||
valuesAdapter.fromJson(jsonConfigValues) ?: listOf())
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
Config.empty()
|
||||
ConfigModel.empty()
|
||||
}
|
||||
onComplete(config)
|
||||
}
|
||||
|
|
@ -71,11 +77,11 @@ class LocalLoader(
|
|||
|
||||
class RemoteLoader(
|
||||
private val nameResolver: NameResolver,
|
||||
private val adapter: JsonAdapter<ConfigModel>
|
||||
private val moshi: Moshi
|
||||
) : ConfigLoader {
|
||||
|
||||
override fun loadConfig(onComplete: (Config) -> Unit) {
|
||||
val emptyConfig = Config.empty()
|
||||
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
||||
val emptyConfig = ConfigModel.empty()
|
||||
val remoteConfig = Firebase.remoteConfig
|
||||
remoteConfig.fetchAndActivate().addOnCompleteListener {
|
||||
if (it.isSuccessful) {
|
||||
|
|
@ -85,10 +91,9 @@ class RemoteLoader(
|
|||
onComplete(emptyConfig)
|
||||
return@addOnCompleteListener
|
||||
}
|
||||
val configModel = adapter.fromJson(jsonConfig)
|
||||
val features = configModel?.toFeatures(ConfigType.Remote) ?: mutableMapOf()
|
||||
val configValues = configModel?.toConfigValues() ?: mutableMapOf()
|
||||
onComplete(Config(features, configValues))
|
||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
||||
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig) ?: listOf(), listOf()))
|
||||
} else {
|
||||
onComplete(emptyConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +1,56 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class ConfigType {
|
||||
Local, Remote
|
||||
}
|
||||
|
||||
class Config(
|
||||
val features: MutableMap<String, Feature>,
|
||||
val configValues: MutableMap<String, ConfigurationValue>
|
||||
) {
|
||||
companion object {
|
||||
fun empty(): Config = Config(mutableMapOf(), mutableMapOf())
|
||||
}
|
||||
}
|
||||
data class ConfigState(
|
||||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val payIdIsEnabled: Boolean = true,
|
||||
val usePayId: Boolean = true,
|
||||
val useTopUp: Boolean = true,
|
||||
val isStart2Coin: Boolean = false
|
||||
)
|
||||
|
||||
class ConfigManager(
|
||||
private val localLoader: ConfigLoader,
|
||||
private val remoteLoader: ConfigLoader
|
||||
) {
|
||||
private var localConfig: Config = Config.empty()
|
||||
private var remoteConfig: Config = Config.empty()
|
||||
private val usePayId = "usePayId"
|
||||
private val payIdIsEnabled = "payIdIsEnabled"
|
||||
private val useTopUp = "useTopUp"
|
||||
private val isStart2Coin = "isStart2Coin"
|
||||
|
||||
var config: ConfigState = ConfigState()
|
||||
private set
|
||||
|
||||
fun load(onComplete: VoidCallback? = null) {
|
||||
localLoader.loadConfig { localConfig = it }
|
||||
localLoader.loadConfig {
|
||||
it.features?.forEach { feature -> updateFeature(feature.name, feature.value) }
|
||||
}
|
||||
remoteLoader.loadConfig {
|
||||
remoteConfig = it
|
||||
it.features?.forEach { feature -> updateFeature(feature.name, feature.value) }
|
||||
onComplete?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun featureIsActive(name: String): Boolean {
|
||||
return getFeatureWithHighestPriority(name)?.isActive() ?: false
|
||||
fun onCardScanned(card: Card) {
|
||||
updateFeature(isStart2Coin, card.cardData?.issuerName?.toLowerCase(Locale.US) == "start2coin")
|
||||
}
|
||||
|
||||
fun getFeature(name: String, type: ConfigType? = null): Feature? {
|
||||
return when (type) {
|
||||
ConfigType.Local -> localConfig.features[name]
|
||||
ConfigType.Remote -> remoteConfig.features[name]
|
||||
else -> getFeatureWithHighestPriority(name)
|
||||
private fun updateFeature(name: String, value: Boolean?) {
|
||||
val newValue = value ?: return
|
||||
|
||||
when (name) {
|
||||
usePayId -> config = config.copy(usePayId = newValue)
|
||||
payIdIsEnabled -> config = config.copy(payIdIsEnabled = newValue)
|
||||
useTopUp -> config = config.copy(useTopUp = newValue)
|
||||
isStart2Coin -> config = config.copy(isStart2Coin = newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFeatureWithHighestPriority(name: String): Feature? {
|
||||
// if a localFeature doesn't exist then a remoteFeature won't exist to
|
||||
val localFeature = localConfig.features[name] ?: return null
|
||||
|
||||
// if the localFeature has a condition, then it has higher priority than a remoteFeature
|
||||
return if (localFeature.hasCondition()) {
|
||||
localFeature
|
||||
} else {
|
||||
// if not then the remoteFeature has higher priority than the localFeature
|
||||
// if the remoteFeature doesn't exist - used the localFeature
|
||||
remoteConfig.features[name] ?: localFeature
|
||||
}
|
||||
}
|
||||
|
||||
fun getConfigValue(name: String): ConfigurationValue? {
|
||||
return remoteConfig.configValues[name] ?: localConfig.configValues[name]
|
||||
}
|
||||
|
||||
fun updateCondition(name: String, condition: Condition?) {
|
||||
localConfig.features[name]?.updateCondition(condition)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,18 +19,8 @@ class ConfigValueModel(
|
|||
override val value: String
|
||||
) : BaseConfigModel<String>
|
||||
|
||||
class ConfigModel(val features: List<FeatureModel>?, val configValues: List<ConfigValueModel>?)
|
||||
|
||||
fun ConfigModel.toFeatures(type: ConfigType): MutableMap<String, Feature> {
|
||||
return features?.map { AppFeature(type, it, ConditionsFactory.create(it.name)) }
|
||||
?.associateBy { it.name }
|
||||
?.toMutableMap()
|
||||
?: mutableMapOf()
|
||||
}
|
||||
|
||||
fun ConfigModel.toConfigValues(): MutableMap<String, ConfigurationValue> {
|
||||
return configValues?.map { ConfigurationValue(it.name, it.value) }
|
||||
?.associateBy { it.name }
|
||||
?.toMutableMap() ?: mutableMapOf()
|
||||
}
|
||||
|
||||
class ConfigModel(val features: List<FeatureModel>?, val configValues: List<ConfigValueModel>?) {
|
||||
companion object {
|
||||
fun empty(): ConfigModel = ConfigModel(listOf(), listOf())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface Feature {
|
||||
val name: String
|
||||
val type: ConfigType
|
||||
fun isActive(): Boolean
|
||||
fun reset()
|
||||
fun updateCondition(condition: Condition?)
|
||||
fun hasCondition(): Boolean
|
||||
|
||||
companion object {
|
||||
const val usePayId = "usePayId"
|
||||
const val payIdIsEnabled = "payIdIsEnabled"
|
||||
const val useTopUp = "useTopUp"
|
||||
}
|
||||
}
|
||||
|
||||
class AppFeature(
|
||||
override val type: ConfigType,
|
||||
private val featureModel: FeatureModel,
|
||||
private var condition: Condition?
|
||||
) : Feature {
|
||||
override val name: String = featureModel.name
|
||||
|
||||
override fun isActive(): Boolean {
|
||||
return if (condition == null) featureModel.value ?: false
|
||||
else condition!!.isMet()
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
condition = null
|
||||
}
|
||||
|
||||
override fun updateCondition(condition: Condition?) {
|
||||
if (type == ConfigType.Local) this.condition = condition
|
||||
}
|
||||
|
||||
override fun hasCondition(): Boolean {
|
||||
return condition != null
|
||||
}
|
||||
}
|
||||
|
||||
data class ConfigurationValue(val name: String, val value: String) {
|
||||
companion object {
|
||||
const val coinMarketCapKey = "coinMarketCapKey"
|
||||
const val moonPayApiKey = "moonPayApiKey"
|
||||
const val moonPayApiSecretKey = "moonPayApiSecretKey"
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TopUpHelper
|
||||
import com.tangem.tap.domain.config.ConfigurationValue
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
|
|
@ -254,14 +253,12 @@ private class TopUpMiddleware {
|
|||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val configManager = store.state.globalState.configManager ?: return
|
||||
val apiKey = configManager.getConfigValue(ConfigurationValue.moonPayApiKey)?.value ?: ""
|
||||
val secretKey = configManager.getConfigValue(ConfigurationValue.moonPayApiSecretKey)?.value ?: ""
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val url = TopUpHelper.getUrl(
|
||||
store.state.walletState.currencyData.currencySymbol!!,
|
||||
store.state.walletState.addressData!!.address,
|
||||
apiKey,
|
||||
secretKey
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
Timber.d(url)
|
||||
store.dispatch(WalletAction.TopUpAction.Start(url, TopUpHelper.REDIRECT_URL))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.network.coinmarketcap
|
|||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.commands.common.network.performRequest
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.config.ConfigurationValue
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -13,7 +12,7 @@ class CoinMarketCapService() {
|
|||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create(getApiKey()) }
|
||||
|
||||
private fun getApiKey(): String {
|
||||
return store.state.globalState.configManager?.getConfigValue(ConfigurationValue.coinMarketCapKey)?.value!!
|
||||
return store.state.globalState.configManager?.config?.coinMarketCapKey ?: ""
|
||||
}
|
||||
|
||||
suspend fun getRate(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue