Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-10 15:56:02 +04:00
commit 369ed25ac6
515 changed files with 6100 additions and 12756 deletions

View file

@ -1,7 +1,7 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.utils.RequestHeader
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.utils.Provider
@ -10,12 +10,12 @@ import com.tangem.utils.version.AppVersionProvider
/**
* Express [ApiConfig]
*
* @property configManager config manager
* @property expressAuthProvider express auth provider
* @property appVersionProvider app version provider
* @property environmentConfigStorage environment config storage
* @property expressAuthProvider express auth provider
* @property appVersionProvider app version provider
*/
internal class Express(
private val configManager: ConfigManager,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val expressAuthProvider: ExpressAuthProvider,
private val appVersionProvider: AppVersionProvider,
) : ApiConfig() {
@ -56,9 +56,9 @@ internal class Express(
private fun getApiKey(isProd: Boolean): String {
return if (isProd) {
configManager.config.express
environmentConfigStorage.getConfigSync().express
} else {
configManager.config.devExpress
environmentConfigStorage.getConfigSync().devExpress
}
?.apiKey
?: error("No express config provided")

View file

@ -34,6 +34,9 @@ interface TangemTechMarketsApi {
@Query("interval") interval: String,
): ApiResponse<TokenMarketChartResponse>
@GET("coins/{coin_id}/exchanges")
suspend fun getCoinExchanges(@Path("coin_id") coinId: String): ApiResponse<TokenMarketExchangesResponse>
@GET("coins/history_preview")
suspend fun getCoinsListCharts(
@Query("coin_ids") coinIds: String,

View file

@ -0,0 +1,38 @@
package com.tangem.datasource.api.markets.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
/**
* Token market exchanges response
*
* @property exchanges list of exchanges
*
[REDACTED_AUTHOR]
*/
@JsonClass(generateAdapter = true)
data class TokenMarketExchangesResponse(
@Json(name = "exchanges") val exchanges: List<Exchange>,
) {
/**
* Exchange
*
* @property id id
* @property name name
* @property imageUrl image url
* @property isCentralized CEX (true), DEX (false)
* @property volumeInUsd aggregated volume in USD
* @property trustScore trust score
*/
@JsonClass(generateAdapter = true)
data class Exchange(
@Json(name = "exchange_id") val id: String,
@Json(name = "name") val name: String,
@Json(name = "image") val imageUrl: String?,
@Json(name = "centralized") val isCentralized: Boolean,
@Json(name = "volume_usd") val volumeInUsd: BigDecimal,
@Json(name = "trust_score") val trustScore: Int?,
)
}

View file

@ -30,6 +30,8 @@ data class TokenMarketInfoResponse(
val links: Links?,
@Json(name = "price_performance")
val pricePerformance: PricePerformance?,
@Json(name = "exchanges_amount")
val exchangesAmount: Int?,
) {
@JsonClass(generateAdapter = true)

View file

@ -0,0 +1,66 @@
package com.tangem.datasource.api.onramp
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
interface OnrampApi {
@GET("currencies")
suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>>
@GET("countries")
suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>>
@GET("country-by-ip")
suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO>
@GET("payment-methods")
suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>>
@POST("onramp-pairs")
suspend fun getPairs(@Body body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>>
@GET("onramp-quote")
suspend fun getQuote(
@Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("paymentMethod") paymentMethod: String,
@Query("countryCode") countryCode: String,
@Query("fromAmount") fromAmount: String,
@Query("toDecimals") toDecimals: Int,
@Query("providerId") providerId: String,
): ApiResponse<OnrampQuoteResponse>
@GET("onramp-data")
suspend fun getData(
@Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("toContractAddress") toContractAddress: String,
@Query("toNetwork") toNetwork: String,
@Query("paymentMethod") paymentMethod: String,
@Query("countryCode") countryCode: String,
@Query("fromAmount") fromAmount: String,
@Query("toDecimals") toDecimals: Int,
@Query("providerId") providerId: String,
@Query("toAddress") toAddress: String,
@Query("redirectUrl") redirectUrl: String,
@Query("language") language: String?,
@Query("theme") theme: String?,
@Query("requestId") requestId: String,
): ApiResponse<OnrampDataResponse>
@GET("onramp-status")
suspend fun getStatus(@Query("txId") txId: String): ApiResponse<OnrampStatusResponse>
}

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.api.onramp.models.common
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampDestinationDTO(
@Json(name = "contractAddress")
val contractAddress: String,
@Json(name = "network")
val network: String,
)

View file

@ -0,0 +1,17 @@
package com.tangem.datasource.api.onramp.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
@JsonClass(generateAdapter = true)
data class OnrampPairsRequest(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String?,
@Json(name = "countryCode")
val countryCode: String,
@Json(name = "to")
val to: List<OnrampDestinationDTO>,
)

View file

@ -0,0 +1,69 @@
package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
data class OnrampDataResponseWithTxDetails(
val dataResponse: OnrampDataResponse,
val txDetails: OnrampTxDetails,
)
@JsonClass(generateAdapter = true)
data class OnrampDataResponse(
@Json(name = "txId")
val txId: String,
@Json(name = "dataJson")
val dataJson: String,
@Json(name = "signature")
val signature: String,
)
@JsonClass(generateAdapter = true)
data class OnrampTxDetails(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String,
@Json(name = "toContractAddress")
val toContractAddress: String,
@Json(name = "toNetwork")
val toNetwork: String,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "toDecimals")
val toDecimals: Int,
@Json(name = "providerId")
val providerId: String,
@Json(name = "toAddress")
val toAddress: String,
@Json(name = "redirectUrl")
val redirectUrl: String,
@Json(name = "language")
val language: String?,
@Json(name = "theme")
val theme: String?,
@Json(name = "requestId")
val requestId: String,
@Json(name = "externalTxId")
val externalTxId: String,
@Json(name = "widgetUrl")
val widgetUrl: String,
)

View file

@ -0,0 +1,46 @@
package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampQuoteResponse(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String,
@Json(name = "toContractAddress")
val toContractAddress: String,
@Json(name = "toNetwork")
val toNetwork: String,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "toAmount")
val toAmount: String,
@Json(name = "toDecimals")
val toDecimals: Int,
@Json(name = "providerId")
val providerId: String,
@Json(name = "minFromAmount")
val minFromAmount: String,
@Json(name = "maxFromAmount")
val maxFromAmount: String,
@Json(name = "minToAmount")
val minToAmount: String,
@Json(name = "maxToAmount")
val maxToAmount: String,
)

View file

@ -0,0 +1,61 @@
package com.tangem.datasource.api.onramp.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampStatusResponse(
@Json(name = "txId")
val txId: String,
@Json(name = "providerId")
val providerId: String,
@Json(name = "payoutAddress")
val payoutAddress: String,
// @Json(name = "status")
// val status: ???
@Json(name = "failReason")
val failReason: String?,
@Json(name = "externalTxId")
val externalTxId: String,
@Json(name = "externalTxUrl")
val externalTxUrl: String?,
@Json(name = "payoutHash")
val payoutHash: String?,
@Json(name = "createdAt")
val createdAt: String,
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String,
@Json(name = "fromAmount")
val fromAmount: String,
@Json(name = "toContractAddress")
val toContractAddress: String,
@Json(name = "toNetwork")
val toNetwork: String,
@Json(name = "toDecimals")
val toDecimals: String,
@Json(name = "toAmount")
val toAmount: String,
@Json(name = "toActualAmount")
val toActualAmount: String,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
)

View file

@ -0,0 +1,28 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampCountryDTO(
@Json(name = "name")
val name: String,
@Json(name = "code")
val code: String,
@Json(name = "image")
val image: String,
@Json(name = "alpha3")
val alpha3: String,
@Json(name = "continent")
val continent: String,
@Json(name = "defaultCurrency")
val defaultCurrency: OnrampCurrencyDTO,
@Json(name = "onrampAvailable")
val onrampAvailable: Boolean,
)

View file

@ -0,0 +1,19 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampCurrencyDTO(
@Json(name = "name")
val name: String,
@Json(name = "code")
val code: String,
@Json(name = "image")
val image: String,
@Json(name = "precision")
val precision: Int,
)

View file

@ -0,0 +1,17 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
@JsonClass(generateAdapter = true)
data class OnrampPairDTO(
@Json(name = "fromCurrencyCode")
val fromCurrencyCode: String?,
@Json(name = "to")
val to: OnrampDestinationDTO,
@Json(name = "providers")
val providers: List<OnrampProviderDTO>,
)

View file

@ -0,0 +1,13 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OnrampProviderDTO(
@Json(name = "providerId")
val providerId: String,
@Json(name = "paymentMethods")
val paymentMethods: List<String>,
)

View file

@ -0,0 +1,16 @@
package com.tangem.datasource.api.onramp.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PaymentMethodDTO(
@Json(name = "id")
val id: String,
@Json(name = "name")
val name: String,
@Json(name = "image")
val image: String,
)

View file

@ -5,9 +5,7 @@ import com.tangem.datasource.api.stakekit.models.request.*
import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse
import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
import retrofit2.http.*
@ -16,7 +14,7 @@ import retrofit2.http.*
interface StakeKitApi {
@GET("yields/enabled")
suspend fun getMultipleYields(
suspend fun getEnabledYields(
@Query("preferredValidatorsOnly") preferredValidatorsOnly: Boolean? = null,
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean? = null,
@Query("type") type: YieldType? = null,
@ -26,12 +24,6 @@ interface StakeKitApi {
@Query("limit") limit: Int? = null,
): ApiResponse<EnabledYieldsResponse>
@GET("yields/{integrationId}")
suspend fun getSingleYield(
@Path("integrationId") integrationId: String,
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean = false,
): ApiResponse<YieldDTO>
@POST("yields/balances")
suspend fun getMultipleYieldBalances(
@Body body: List<YieldBalanceRequestBody>,
@ -43,9 +35,6 @@ interface StakeKitApi {
@Body body: YieldBalanceRequestBody,
): ApiResponse<List<BalanceDTO>>
@GET("tokens")
suspend fun getTokens(): ApiResponse<List<TokenWithYieldDTO>>
@POST("actions/enter")
suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse<EnterActionResponse>

View file

@ -1,10 +0,0 @@
package com.tangem.datasource.api.stakekit.models.response.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class TokenWithYieldDTO(
@Json(name = "token") val token: TokenDTO,
@Json(name = "availableYields") val availableYieldIds: List<String>,
)

View file

@ -4,7 +4,7 @@ import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.datasource.api.tangemTech.models.*
import com.tangem.datasource.api.utils.ReadTimeout
import com.tangem.datasource.config.models.ProviderModel
import com.tangem.datasource.local.config.providers.models.ProviderModel
import retrofit2.http.*
import java.util.concurrent.TimeUnit

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class GeoResponse(
@Json(name = "code") val code: String,
)

View file

@ -1,23 +0,0 @@
package com.tangem.datasource.config
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ConfigModel
/**
[REDACTED_AUTHOR]
*/
interface ConfigManager {
val config: Config
suspend fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)? = null)
fun turnOff(name: String)
fun resetToDefault(name: String)
companion object {
const val IS_CREATING_TWIN_CARDS_ALLOWED = "isCreatingTwinCardsAllowed"
const val IS_TOP_UP_ENABLED = "isTopUpEnabled"
}
}

View file

@ -1,162 +0,0 @@
package com.tangem.datasource.config
import com.tangem.blockchain.common.*
import com.tangem.datasource.config.ConfigManager.Companion.IS_CREATING_TWIN_CARDS_ALLOWED
import com.tangem.datasource.config.ConfigManager.Companion.IS_TOP_UP_ENABLED
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ConfigModel
import com.tangem.datasource.config.models.ConfigValueModel
import com.tangem.datasource.config.models.FeatureModel
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
override var config: Config = Config()
private set
private var defaultConfig = Config()
override suspend fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)?) {
configLoader.load { configModel ->
setupFeature(configModel.features)
setupConfigValues(configModel.configValues)
onComplete?.invoke(config)
}
}
override fun turnOff(name: String) {
when (name) {
IS_TOP_UP_ENABLED -> config = config.copy(isTopUpEnabled = false)
IS_CREATING_TWIN_CARDS_ALLOWED -> config = config.copy(isCreatingTwinCardsAllowed = false)
}
}
override fun resetToDefault(name: String) {
when (name) {
IS_TOP_UP_ENABLED -> {
config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
}
IS_CREATING_TWIN_CARDS_ALLOWED -> {
config = config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
}
else -> Unit
}
}
private fun setupFeature(featureModel: FeatureModel?) {
val model = featureModel ?: return
config = config.copy(
isTopUpEnabled = model.isTopUpEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
)
defaultConfig = defaultConfig.copy(
isTopUpEnabled = model.isTopUpEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
)
}
private fun setupConfigValues(configValues: ConfigValueModel?) {
val values = configValues ?: return
config = createConfig(config, values)
defaultConfig = config.copy()
}
private fun createConfig(config: Config, configValues: ConfigValueModel): Config {
return config.copy(
coinMarketCapKey = configValues.coinMarketCapKey,
moonPayApiKey = configValues.moonPayApiKey,
moonPayApiSecretKey = configValues.moonPayApiSecretKey,
mercuryoWidgetId = configValues.mercuryoWidgetId,
mercuryoSecret = configValues.mercuryoSecret,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairCredentials = BlockchairCredentials(
apiKey = configValues.blockchairApiKeys,
authToken = configValues.blockchairAuthorizationToken,
),
blockcypherTokens = configValues.blockcypherTokens,
quickNodeSolanaCredentials = QuickNodeCredentials(
apiKey = configValues.quiknodeApiKey,
subdomain = configValues.quiknodeSubdomain,
),
quickNodeBscCredentials = QuickNodeCredentials(
apiKey = configValues.bscQuiknodeApiKey,
subdomain = configValues.bscQuiknodeSubdomain,
),
infuraProjectId = configValues.infuraProjectId,
tronGridApiKey = configValues.tronGridApiKey,
nowNodeCredentials = NowNodeCredentials(configValues.nowNodesApiKey),
getBlockCredentials = createGetBlockCredentials(configValues),
kaspaSecondaryApiUrl = configValues.kaspaSecondaryApiUrl,
tonCenterCredentials = TonCenterCredentials(
mainnetApiKey = configValues.tonCenterKeys.mainnet,
testnetApiKey = configValues.tonCenterKeys.testnet,
),
chiaFireAcademyApiKey = configValues.chiaFireAcademyApiKey,
chiaTangemApiKey = configValues.chiaTangemApiKey,
hederaArkhiaApiKey = configValues.hederaArkhiaKey,
polygonScanApiKey = configValues.polygonScanApiKey,
bittensorDwellirApiKey = configValues.bittensorDwellirApiKey,
bittensorOnfinalityApiKey = configValues.bittensorOnfinalityKey,
koinosProApiKey = configValues.koinosProApiKey,
),
amplitudeApiKey = configValues.amplitudeApiKey,
sprinklr = configValues.sprinklr,
walletConnectProjectId = configValues.walletConnectProjectId,
tangemComAuthorization = configValues.tangemComAuthorization,
express = configValues.express,
devExpress = configValues.devExpress,
stakeKitApiKey = configValues.stakeKitApiKey,
)
}
private fun createGetBlockCredentials(configValues: ConfigValueModel): GetBlockCredentials? {
return configValues.getBlockAccessTokens?.let { accessTokens ->
GetBlockCredentials(
xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC),
cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta),
avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC),
eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC),
etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC),
fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC),
rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC),
bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC),
polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC),
gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC),
cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC),
solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC),
ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC),
tron = GetBlockAccessToken(rest = accessTokens.tron?.rest),
cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest),
near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC),
aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest),
dogecoin = GetBlockAccessToken(
jsonRpc = accessTokens.dogecoin?.jsonRPC,
blockBookRest = accessTokens.dogecoin?.blockBookRest,
),
litecoin = GetBlockAccessToken(
jsonRpc = accessTokens.litecoin?.jsonRPC,
blockBookRest = accessTokens.litecoin?.blockBookRest,
),
dash = GetBlockAccessToken(
jsonRpc = accessTokens.dash?.jsonRPC,
blockBookRest = accessTokens.dash?.blockBookRest,
),
bitcoin = GetBlockAccessToken(
jsonRpc = accessTokens.bitcoin?.jsonRPC,
blockBookRest = accessTokens.bitcoin?.blockBookRest,
),
algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest),
zkSyncEra = GetBlockAccessToken(rest = accessTokens.zksync?.jsonRPC),
polygonZkEvm = GetBlockAccessToken(rest = accessTokens.polygonZkevm?.jsonRPC),
base = GetBlockAccessToken(rest = accessTokens.base?.jsonRPC),
blast = GetBlockAccessToken(jsonRpc = accessTokens.blast?.jsonRPC),
filecoin = GetBlockAccessToken(jsonRpc = accessTokens.filecoin?.jsonRPC),
)
}
}
}

View file

@ -1,25 +0,0 @@
package com.tangem.datasource.config
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.config.models.ConfigModel
import com.tangem.datasource.config.models.ConfigValueModel
import com.tangem.datasource.config.models.FeatureModel
/**
[REDACTED_AUTHOR]
*/
class FeaturesLocalLoader(
private val assetLoader: AssetLoader,
buildEnvironment: String,
) : Loader<ConfigModel> {
private val featuresName = "features_$buildEnvironment"
private val configValuesName = "tangem-app-config/config_$buildEnvironment"
override suspend fun load(onComplete: (ConfigModel) -> Unit) {
ConfigModel(
features = assetLoader.load<FeatureModel>(fileName = featuresName),
configValues = assetLoader.load<ConfigValueModel>(fileName = configValuesName),
).also(onComplete)
}
}

View file

@ -1,8 +0,0 @@
package com.tangem.datasource.config
/**
[REDACTED_AUTHOR]
*/
interface Loader<T> {
suspend fun load(onComplete: (T) -> Unit)
}

View file

@ -1,13 +0,0 @@
package com.tangem.datasource.config.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
sealed interface ChatConfig
@JsonClass(generateAdapter = true)
data class SprinklrConfig(
@Json(name = "appID") val appId: String,
@Json(name = "apiKey") val apiKey: String,
@Json(name = "environment") val environment: String,
) : ChatConfig

View file

@ -1,12 +0,0 @@
package com.tangem.datasource.config.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ExpressModel(
@Json(name = "apiKey")
val apiKey: String,
@Json(name = "signVerifierPublicKey")
val signVerifierPublicKey: String,
)

View file

@ -1,12 +0,0 @@
package com.tangem.datasource.config.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class TonCenterKeys(
@Json(name = "mainnet")
val mainnet: String,
@Json(name = "testnet")
val testnet: String,
)

View file

@ -5,10 +5,10 @@ import com.tangem.crypto.CryptoUtils
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
internal class Sha256SignatureVerifier(
private val configManager: ConfigManager,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val apiConfigsManager: ApiConfigsManager,
) : DataSignatureVerifier {
@ -24,8 +24,8 @@ internal class Sha256SignatureVerifier(
private fun getPubKey(): String? {
val expressConfig = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.Express)
return when (expressConfig.environment) {
ApiEnvironment.PROD -> configManager.config.express?.signVerifierPublicKey
else -> configManager.config.devExpress?.signVerifierPublicKey
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().express?.signVerifierPublicKey
else -> environmentConfigStorage.getConfigSync().devExpress?.signVerifierPublicKey
}
}
}

View file

@ -4,7 +4,7 @@ import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.Express
import com.tangem.datasource.api.common.config.StakeKit
import com.tangem.datasource.api.common.config.TangemTech
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.version.AppVersionProvider
@ -21,11 +21,11 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideExpressConfig(
configManager: ConfigManager,
environmentConfigStorage: EnvironmentConfigStorage,
expressAuthProvider: ExpressAuthProvider,
appVersionProvider: AppVersionProvider,
): ApiConfig {
return Express(configManager, expressAuthProvider, appVersionProvider)
return Express(environmentConfigStorage, expressAuthProvider, appVersionProvider)
}
@Provides

View file

@ -1,16 +0,0 @@
package com.tangem.datasource.di
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.ConfigManagerImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal interface ConfigModule {
@Binds
fun bindConfigManager(configManager: ConfigManagerImpl): ConfigManager
}

View file

@ -4,11 +4,11 @@ import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.datasource.api.common.adapter.*
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
import com.tangem.datasource.api.common.adapter.DateTimeAdapter
import com.tangem.datasource.api.common.adapter.LocalDateAdapter
import com.tangem.datasource.config.models.ProviderModel
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
import com.tangem.datasource.local.config.providers.models.ProviderModel
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View file

@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
@ -94,6 +95,27 @@ internal object NetworkModule {
)
}
@Provides
@Singleton
fun provideOnrampApi(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
apiConfigsManager: ApiConfigsManager,
appLogsStore: AppLogsStore,
): OnrampApi {
return createApi(
id = ApiConfig.ID.Express,
moshi = moshi,
context = context,
apiConfigsManager = apiConfigsManager,
clientBuilder = {
addInterceptor(
NetworkLogsSaveInterceptor(appLogsStore),
)
},
)
}
@Provides
@Singleton
fun provideTangemTechApi(

View file

@ -1,9 +1,9 @@
package com.tangem.datasource.di
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.crypto.Sha256SignatureVerifier
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -17,9 +17,9 @@ internal object SecurityModule {
@Provides
@Singleton
fun provideDataSignatureVerifier(
configManager: ConfigManager,
environmentConfigStorage: EnvironmentConfigStorage,
apiConfigsManager: ApiConfigsManager,
): DataSignatureVerifier {
return Sha256SignatureVerifier(configManager, apiConfigsManager)
return Sha256SignatureVerifier(environmentConfigStorage, apiConfigsManager)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.token.DefaultStakingYieldsStore
import com.tangem.datasource.local.token.StakingYieldsStore
import dagger.Module
@ -16,6 +15,6 @@ internal object StakingTokensStoreModule {
@Provides
@Singleton
fun provideStakingTokensStore(): StakingYieldsStore {
return DefaultStakingYieldsStore(dataStore = RuntimeDataStore())
return DefaultStakingYieldsStore()
}
}

View file

@ -1,24 +0,0 @@
package com.tangem.datasource.di
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.testnet.DefaultTestnetTokensStorage
import com.tangem.datasource.local.testnet.TestnetTokensStorage
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
[REDACTED_AUTHOR]
*/
@Module
@InstallIn(SingletonComponent::class)
internal object TestnetTokensStorageModule {
@Provides
@Singleton
fun providesTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage {
return DefaultTestnetTokensStorage(assetLoader)
}
}

View file

@ -0,0 +1,45 @@
package com.tangem.datasource.di.local.config
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.environment.DefaultEnvironmentConfigStorage
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
import com.tangem.datasource.local.config.providers.DefaultBlockchainProvidersStorage
import com.tangem.datasource.local.config.testnet.DefaultTestnetTokensStorage
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.datastore.RuntimeStateStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object ConfigModule {
@Provides
@Singleton
fun provideEnvironmentConfigStorage(assetLoader: AssetLoader): EnvironmentConfigStorage {
return DefaultEnvironmentConfigStorage(
assetLoader = assetLoader,
environmentConfigStore = RuntimeStateStore(defaultValue = EnvironmentConfig()),
)
}
@Provides
@Singleton
fun providesTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage {
return DefaultTestnetTokensStorage(assetLoader)
}
@Provides
@Singleton
fun provideProvidersOrderConfigStorage(assetLoader: AssetLoader): BlockchainProvidersStorage {
return DefaultBlockchainProvidersStorage(
assetLoader = assetLoader,
runtimeStateStore = RuntimeStateStore(defaultValue = emptyMap()),
)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.datasource.local.config.environment
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.environment.converter.EnvironmentConfigConverter
import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel
import com.tangem.datasource.local.datastore.RuntimeStateStore
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
/**
* Default implementation for storing [EnvironmentConfig]
*
* @property assetLoader asset loader
* @property environmentConfigStore config store
*/
internal class DefaultEnvironmentConfigStorage(
private val assetLoader: AssetLoader,
private val environmentConfigStore: RuntimeStateStore<EnvironmentConfig>,
) : EnvironmentConfigStorage {
override suspend fun initialize(): EnvironmentConfig {
val environmentConfigModel = assetLoader.load<EnvironmentConfigModel>(fileName = CONFIG_FILE_NAME)
?: return environmentConfigStore.get().value
val config = EnvironmentConfigConverter.convert(value = environmentConfigModel)
environmentConfigStore.store(value = config)
Timber.i("Config [$CONFIG_FILE_NAME] loaded successfully")
return config
}
override fun getConfig(): Flow<EnvironmentConfig> = environmentConfigStore.get()
override fun getConfigSync(): EnvironmentConfig = environmentConfigStore.get().value
private companion object {
const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
}
}

View file

@ -1,21 +1,17 @@
package com.tangem.datasource.config.models
package com.tangem.datasource.local.config.environment
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.datasource.local.config.environment.models.ExpressModel
data class Config(
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
data class EnvironmentConfig(
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
val mercuryoWidgetId: String = "",
val mercuryoSecret: String = "",
val amplitudeApiKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isTopUpEnabled: Boolean = false,
@Deprecated("Not relevant since version 3.23")
val isCreatingTwinCardsAllowed: Boolean = false,
val sprinklr: SprinklrConfig? = null,
val walletConnectProjectId: String = "",
val tangemComAuthorization: String? = null,
val express: ExpressModel? = null,
val devExpress: ExpressModel? = null,
val stakeKitApiKey: String? = null,

View file

@ -0,0 +1,20 @@
package com.tangem.datasource.local.config.environment
import kotlinx.coroutines.flow.Flow
/**
* Storage for [EnvironmentConfig]
*
[REDACTED_AUTHOR]
*/
interface EnvironmentConfigStorage {
/** Initialize and return [EnvironmentConfig] */
suspend fun initialize(): EnvironmentConfig
/** Get [EnvironmentConfig] as [Flow] */
fun getConfig(): Flow<EnvironmentConfig>
/** Get [EnvironmentConfig] synchronously */
fun getConfigSync(): EnvironmentConfig
}

View file

@ -0,0 +1,93 @@
package com.tangem.datasource.local.config.environment.converter
import com.tangem.blockchain.common.*
import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel
import com.tangem.utils.converter.Converter
/**
* Converts [EnvironmentConfigModel] to [BlockchainSdkConfig]
*
[REDACTED_AUTHOR]
*/
internal object BlockchainSDKConfigConverter : Converter<EnvironmentConfigModel, BlockchainSdkConfig> {
override fun convert(value: EnvironmentConfigModel): BlockchainSdkConfig {
return BlockchainSdkConfig(
blockchairCredentials = BlockchairCredentials(
apiKey = value.blockchairApiKeys,
authToken = value.blockchairAuthorizationToken,
),
blockcypherTokens = value.blockcypherTokens,
quickNodeSolanaCredentials = QuickNodeCredentials(
apiKey = value.quiknodeApiKey,
subdomain = value.quiknodeSubdomain,
),
quickNodeBscCredentials = QuickNodeCredentials(
apiKey = value.bscQuiknodeApiKey,
subdomain = value.bscQuiknodeSubdomain,
),
infuraProjectId = value.infuraProjectId,
tronGridApiKey = value.tronGridApiKey,
nowNodeCredentials = NowNodeCredentials(value.nowNodesApiKey),
getBlockCredentials = createGetBlockCredentials(value),
kaspaSecondaryApiUrl = value.kaspaSecondaryApiUrl,
tonCenterCredentials = TonCenterCredentials(
mainnetApiKey = value.tonCenterKeys.mainnet,
testnetApiKey = value.tonCenterKeys.testnet,
),
chiaFireAcademyApiKey = value.chiaFireAcademyApiKey,
chiaTangemApiKey = value.chiaTangemApiKey,
hederaArkhiaApiKey = value.hederaArkhiaKey,
polygonScanApiKey = value.polygonScanApiKey,
bittensorDwellirApiKey = value.bittensorDwellirApiKey,
bittensorOnfinalityApiKey = value.bittensorOnfinalityKey,
koinosProApiKey = value.koinosProApiKey,
)
}
private fun createGetBlockCredentials(configValues: EnvironmentConfigModel): GetBlockCredentials? {
return configValues.getBlockAccessTokens?.let { accessTokens ->
GetBlockCredentials(
xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC),
cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta),
avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC),
eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC),
etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC),
fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC),
rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC),
bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC),
polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC),
gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC),
cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC),
solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC),
ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC),
tron = GetBlockAccessToken(rest = accessTokens.tron?.rest),
cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest),
near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC),
aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest),
dogecoin = GetBlockAccessToken(
jsonRpc = accessTokens.dogecoin?.jsonRPC,
blockBookRest = accessTokens.dogecoin?.blockBookRest,
),
litecoin = GetBlockAccessToken(
jsonRpc = accessTokens.litecoin?.jsonRPC,
blockBookRest = accessTokens.litecoin?.blockBookRest,
),
dash = GetBlockAccessToken(
jsonRpc = accessTokens.dash?.jsonRPC,
blockBookRest = accessTokens.dash?.blockBookRest,
),
bitcoin = GetBlockAccessToken(
jsonRpc = accessTokens.bitcoin?.jsonRPC,
blockBookRest = accessTokens.bitcoin?.blockBookRest,
),
algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest),
zkSyncEra = GetBlockAccessToken(jsonRpc = accessTokens.zksync?.jsonRPC),
polygonZkEvm = GetBlockAccessToken(jsonRpc = accessTokens.polygonZkevm?.jsonRPC),
base = GetBlockAccessToken(jsonRpc = accessTokens.base?.jsonRPC),
blast = GetBlockAccessToken(jsonRpc = accessTokens.blast?.jsonRPC),
filecoin = GetBlockAccessToken(jsonRpc = accessTokens.filecoin?.jsonRPC),
)
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.datasource.local.config.environment.converter
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel
import com.tangem.utils.converter.Converter
/**
* Converter from [EnvironmentConfigModel] to [EnvironmentConfig]
*
[REDACTED_AUTHOR]
*/
internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, EnvironmentConfig> {
override fun convert(value: EnvironmentConfigModel): EnvironmentConfig {
return EnvironmentConfig(
moonPayApiKey = value.moonPayApiKey,
moonPayApiSecretKey = value.moonPayApiSecretKey,
mercuryoWidgetId = value.mercuryoWidgetId,
mercuryoSecret = value.mercuryoSecret,
blockchainSdkConfig = BlockchainSDKConfigConverter.convert(value = value),
amplitudeApiKey = value.amplitudeApiKey,
walletConnectProjectId = value.walletConnectProjectId,
express = value.express,
devExpress = value.devExpress,
stakeKitApiKey = value.stakeKitApiKey,
)
}
}

View file

@ -1,49 +1,37 @@
package com.tangem.datasource.config.models
package com.tangem.datasource.local.config.environment.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
[REDACTED_AUTHOR]
*/
// TODO remove
class FeatureModel(
val isTopUpEnabled: Boolean,
val isCreatingTwinCardsAllowed: Boolean,
)
@Suppress("LongParameterList")
class ConfigValueModel(
val coinMarketCapKey: String,
val mercuryoWidgetId: String,
val mercuryoSecret: String,
val moonPayApiKey: String,
val moonPayApiSecretKey: String,
val blockchairApiKeys: List<String>,
val blockchairAuthorizationToken: String?,
val quiknodeSubdomain: String,
val quiknodeApiKey: String,
val bscQuiknodeSubdomain: String,
val bscQuiknodeApiKey: String,
val nowNodesApiKey: String,
@JsonClass(generateAdapter = true)
class EnvironmentConfigModel(
@Json(name = "mercuryoWidgetId") val mercuryoWidgetId: String,
@Json(name = "mercuryoSecret") val mercuryoSecret: String,
@Json(name = "moonPayApiKey") val moonPayApiKey: String,
@Json(name = "moonPayApiSecretKey") val moonPayApiSecretKey: String,
@Json(name = "blockchairApiKeys") val blockchairApiKeys: List<String>,
@Json(name = "blockchairAuthorizationToken") val blockchairAuthorizationToken: String?,
@Json(name = "quiknodeSubdomain") val quiknodeSubdomain: String,
@Json(name = "quiknodeApiKey") val quiknodeApiKey: String,
@Json(name = "bscQuiknodeSubdomain") val bscQuiknodeSubdomain: String,
@Json(name = "bscQuiknodeApiKey") val bscQuiknodeApiKey: String,
@Json(name = "nowNodesApiKey") val nowNodesApiKey: String,
@Json(name = "getBlockAccessTokens") val getBlockAccessTokens: GetBlockAccessTokens?,
@Json(name = "tonCenterApiKey") val tonCenterKeys: TonCenterKeys,
val blockcypherTokens: Set<String>?,
val infuraProjectId: String?,
val sprinklr: SprinklrConfig?,
val tronGridApiKey: String,
val amplitudeApiKey: String,
val kaspaSecondaryApiUrl: String,
val walletConnectProjectId: String,
val tangemComAuthorization: String?,
val chiaFireAcademyApiKey: String?,
val chiaTangemApiKey: String?,
val devExpress: ExpressModel?,
val express: ExpressModel?,
@Json(name = "blockcypherTokens") val blockcypherTokens: Set<String>?,
@Json(name = "infuraProjectId") val infuraProjectId: String?,
@Json(name = "tronGridApiKey") val tronGridApiKey: String,
@Json(name = "amplitudeApiKey") val amplitudeApiKey: String,
@Json(name = "kaspaSecondaryApiUrl") val kaspaSecondaryApiUrl: String,
@Json(name = "walletConnectProjectId") val walletConnectProjectId: String,
@Json(name = "chiaFireAcademyApiKey") val chiaFireAcademyApiKey: String?,
@Json(name = "chiaTangemApiKey") val chiaTangemApiKey: String?,
@Json(name = "devExpress") val devExpress: ExpressModel?,
@Json(name = "express") val express: ExpressModel?,
@Json(name = "hederaArkhiaKey") val hederaArkhiaKey: String?,
val polygonScanApiKey: String?,
val stakeKitApiKey: String?,
@Json(name = "polygonScanApiKey") val polygonScanApiKey: String?,
@Json(name = "stakeKitApiKey") val stakeKitApiKey: String?,
@Json(name = "bittensorDwellirKey") val bittensorDwellirApiKey: String?,
@Json(name = "bittensorOnfinalityKey") val bittensorOnfinalityKey: String?,
@Json(name = "koinosProApiKey") val koinosProApiKey: String?,
@ -80,6 +68,12 @@ data class GetBlockAccessTokens(
@Json(name = "filecoin") val filecoin: GetBlockToken?,
)
@JsonClass(generateAdapter = true)
data class TonCenterKeys(
@Json(name = "mainnet") val mainnet: String,
@Json(name = "testnet") val testnet: String,
)
@JsonClass(generateAdapter = true)
data class GetBlockToken(
@Json(name = "jsonRpc") val jsonRPC: String?,
@ -88,11 +82,10 @@ data class GetBlockToken(
@Json(name = "rosetta") val rosetta: String?,
)
class ConfigModel(
val features: FeatureModel?,
val configValues: ConfigValueModel?,
) {
companion object {
fun empty(): ConfigModel = ConfigModel(null, null)
}
}
@JsonClass(generateAdapter = true)
data class ExpressModel(
@Json(name = "apiKey")
val apiKey: String,
@Json(name = "signVerifierPublicKey")
val signVerifierPublicKey: String,
)

View file

@ -0,0 +1,14 @@
package com.tangem.datasource.local.config.providers
import com.tangem.datasource.local.config.providers.models.ProviderModel
/**
* Blockchain providers storage
*
[REDACTED_AUTHOR]
*/
interface BlockchainProvidersStorage {
/** Get config */
suspend fun getConfigSync(): Map<String, List<ProviderModel>>
}

View file

@ -0,0 +1,33 @@
package com.tangem.datasource.local.config.providers
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.datasource.local.datastore.RuntimeStateStore
/**
* Default blockchain providers storage
*
* @property assetLoader asset loader
* @property runtimeStateStore runtime state store
*/
internal class DefaultBlockchainProvidersStorage(
private val assetLoader: AssetLoader,
private val runtimeStateStore: RuntimeStateStore<Map<String, List<ProviderModel>>>,
) : BlockchainProvidersStorage {
override suspend fun getConfigSync(): Map<String, List<ProviderModel>> {
val cachedData = runtimeStateStore.get().value
if (cachedData.isNotEmpty()) return cachedData
val config = assetLoader.load<Map<String, List<ProviderModel>>>(fileName = PROVIDER_TYPES_FILE_NAME).orEmpty()
runtimeStateStore.store(value = config)
return config
}
private companion object {
const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order"
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.config.models
package com.tangem.datasource.local.config.providers.models
import com.squareup.moshi.Json

View file

@ -1,7 +1,7 @@
package com.tangem.datasource.local.testnet
package com.tangem.datasource.local.config.testnet
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
/**
* Default implementation for storing testnet tokens data

View file

@ -1,6 +1,6 @@
package com.tangem.datasource.local.testnet
package com.tangem.datasource.local.config.testnet
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
/**
* Storage of testnet tokens data

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.local.testnet.models
package com.tangem.datasource.local.config.testnet.models
import com.squareup.moshi.Json
@ -7,6 +7,7 @@ import com.squareup.moshi.Json
*
* @property tokens testnet tokens list
*/
// TODO: use CoinsResponse
data class TestnetTokensConfig(
@Json(name = "coins") val tokens: List<Token>,
) {

View file

@ -1,109 +0,0 @@
package com.tangem.datasource.local.datastore
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.files.FileReader
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.utils.Trigger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
@Deprecated("Use shared preferences data store instead")
internal class FileDataStore<Value : Any>(
private val fileReader: FileReader,
private val adapter: JsonAdapter<Value>,
) : StringKeyDataStore<Value> {
private val mutex = Mutex()
private val writeTrigger = Trigger()
override suspend fun isEmpty(): Boolean {
val e = NotImplementedError("`isEmpty()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun contains(key: String): Boolean = getSyncOrNull(key) != null
override fun get(key: String): Flow<Value> {
return writeTrigger
.map {
mutex.withLock {
getInternal(key)
}
}
.filterNotNull()
.distinctUntilChanged()
}
override fun getAll(): Flow<List<Value>> {
val e = NotImplementedError("`getAll()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun getSyncOrNull(key: String): Value? {
return getInternal(key)
}
override suspend fun getAllSyncOrNull(): List<Value>? {
val e = NotImplementedError("`getAllSyncOrNull()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun store(key: String, value: Value) {
try {
mutex.withLock {
val json = adapter.toJson(value)
fileReader.rewriteFile(json, key)
writeTrigger.trigger()
}
} catch (e: Throwable) {
Timber.e(e, "Unable to write file: $key")
}
}
override suspend fun store(values: Map<String, Value>) {
values.forEach { (key, item) ->
store(key, item)
}
}
override suspend fun remove(key: String) {
fileReader.removeFile(key)
writeTrigger.trigger()
}
override suspend fun remove(keys: Collection<String>) {
val e = NotImplementedError("`remove(keys)` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun clear() {
val e = NotImplementedError("`clear()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
private fun getInternal(fileName: String): Value? {
return try {
val json = fileReader.readFile(fileName)
adapter.fromJson(json)
} catch (e: Throwable) {
Timber.e(e, "Unable to read file: $fileName")
null
}
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.datasource.local.datastore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* Runtime store
*
[REDACTED_AUTHOR]
*/
interface RuntimeStateStore<T> {
/** Get flow of elements [T] */
fun get(): StateFlow<T>
/** Store [value] */
suspend fun store(value: T)
companion object {
/**
* Create [RuntimeStateStore]
*
* @param T type of stored value
* @param defaultValue default value
*/
operator fun <T> invoke(defaultValue: T): RuntimeStateStore<T> = object : RuntimeStateStore<T> {
private val flow = MutableStateFlow(value = defaultValue)
override fun get(): StateFlow<T> = flow
override suspend fun store(value: T) {
flow.value = value
}
}
}
}

View file

@ -1,35 +0,0 @@
package com.tangem.datasource.local.datastore.utils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
/**
* Represents a trigger mechanism to emit values on-demand.
*
* This class provides a mechanism to trigger emissions via the [trigger] method.
*
* @property triggerFlow The internal flow that gets toggled to trigger emissions.
*/
internal class Trigger(
private val triggerFlow: MutableStateFlow<Boolean> = MutableStateFlow(value = false),
) : Flow<Unit> {
/**
* Collects values emitted by this flow.
*
* Overrides the default collection mechanism to emit a [Unit] value whenever [triggerFlow] changes.
*
* @param collector The collector responsible for handling emitted values.
*/
override suspend fun collect(collector: FlowCollector<Unit>): Nothing {
triggerFlow.collect { collector.emit(Unit) }
}
/**
* Triggers an emission.
*/
fun trigger() {
triggerFlow.value = !triggerFlow.value
}
}

View file

@ -103,7 +103,9 @@ class AppLogsStore @Inject constructor(
private fun launchWithLock(callback: () -> Unit) {
scope.launch {
mutex.withLock {
callback()
runCatching {
callback()
}.onFailure(Timber::e)
}
}
}

View file

@ -5,7 +5,7 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -29,9 +29,13 @@ internal class DefaultStakingBalanceStore(
}
}
override fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalanceWrapperDTO> {
override fun get(
userWalletId: UserWalletId,
address: String,
integrationId: String,
): Flow<YieldBalanceWrapperDTO?> {
return dataStore.get(userWalletId.stringValue)
.mapNotNull { balances ->
.map { balances ->
balances.firstOrNull { it.integrationId == integrationId && it.addresses.address == address }
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.staking.model.StakingTokenWithYield
internal class DefaultStakingTokensStore(
private val dataStore: StringKeyDataStore<List<StakingTokenWithYield>>,
) : StakingTokensStore {
override suspend fun getSyncOrNull(): List<StakingTokenWithYield>? {
return dataStore.getSyncOrNull(STAKING_TOKENS_KEY)
}
override suspend fun store(items: List<StakingTokenWithYield>) {
dataStore.store(STAKING_TOKENS_KEY, items)
}
companion object {
private const val STAKING_TOKENS_KEY = "STAKING_TOKENS_KEY"
}
}

View file

@ -1,21 +1,16 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
internal class DefaultStakingYieldsStore(
private val dataStore: StringKeyDataStore<List<YieldDTO>>,
) : StakingYieldsStore {
internal class DefaultStakingYieldsStore : StakingYieldsStore {
override suspend fun getSyncOrNull(): List<YieldDTO>? {
return dataStore.getSyncOrNull(STAKING_YIELDS_KEY)
private var yields = mutableListOf<YieldDTO>()
override fun get(): List<YieldDTO> {
return yields
}
override suspend fun store(items: List<YieldDTO>) {
dataStore.store(STAKING_YIELDS_KEY, items)
}
companion object {
private const val STAKING_YIELDS_KEY = "STAKING_YIELDS_KEY"
override fun store(items: List<YieldDTO>) {
yields = items.toMutableList()
}
}

View file

@ -12,7 +12,7 @@ interface StakingBalanceStore {
suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>)
fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalanceWrapperDTO>
fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalanceWrapperDTO?>
suspend fun getSyncOrNull(
userWalletId: UserWalletId,

View file

@ -1,10 +0,0 @@
package com.tangem.datasource.local.token
import com.tangem.domain.staking.model.StakingTokenWithYield
interface StakingTokensStore {
suspend fun getSyncOrNull(): List<StakingTokenWithYield>?
suspend fun store(items: List<StakingTokenWithYield>)
}

View file

@ -4,7 +4,7 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
interface StakingYieldsStore {
suspend fun getSyncOrNull(): List<YieldDTO>?
fun get(): List<YieldDTO>
suspend fun store(items: List<YieldDTO>)
fun store(items: List<YieldDTO>)
}

View file

@ -1,24 +0,0 @@
package com.tangem.datasource.api.common.config.managers
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.Loader
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ConfigModel
import com.tangem.datasource.config.models.ExpressModel
/**
* Mock [ConfigManager] implementation for [ProdApiConfigsManagerTest]
*
[REDACTED_AUTHOR]
*/
internal class MockConfigManager : ConfigManager {
override val config = Config(
express = ExpressModel(apiKey = ProdApiConfigsManagerTest.EXPRESS_API_KEY, signVerifierPublicKey = ""),
devExpress = ExpressModel(apiKey = ProdApiConfigsManagerTest.EXPRESS_DEV_API_KEY, signVerifierPublicKey = ""),
)
override suspend fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)?) = Unit
override fun turnOff(name: String) = Unit
override fun resetToDefault(name: String) = Unit
}

View file

@ -0,0 +1,28 @@
package com.tangem.datasource.api.common.config.managers
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.environment.models.ExpressModel
import kotlinx.coroutines.flow.flowOf
/**
* Mock [EnvironmentConfigStorage] implementation for [ProdApiConfigsManagerTest]
*
[REDACTED_AUTHOR]
*/
internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
private val environmentConfig = EnvironmentConfig(
express = ExpressModel(apiKey = EXPRESS_API_KEY, signVerifierPublicKey = "vocibus"),
devExpress = ExpressModel(apiKey = EXPRESS_DEV_API_KEY, signVerifierPublicKey = "pellentesque"),
)
override suspend fun initialize() = environmentConfig
override fun getConfig() = flowOf(environmentConfig)
override fun getConfigSync() = environmentConfig
companion object {
const val EXPRESS_API_KEY = "express_api_key"
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
}
}

View file

@ -19,7 +19,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
private val configManager = MockConfigManager()
private val configManager = MockEnvironmentConfigStorage()
private val appVersionProvider = mockk<AppVersionProvider>()
private val expressAuthProvider = mockk<ExpressAuthProvider>()
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
@ -66,9 +66,6 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
const val VERSION_NAME = "debug"
const val EXPRESS_USER_ID = "express_user_id"
const val EXPRESS_SESSION_ID = "express_session_id"
const val EXPRESS_REF_CODE = "express_ref_code"
const val EXPRESS_API_KEY = "express_api_key"
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
const val STAKE_KIT_API_KEY = "stake_kit_api_key"
@JvmStatic
@ -109,7 +106,11 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
},
headers = mapOf(
"api-key" to Provider {
if (environment == ApiEnvironment.PROD) EXPRESS_API_KEY else EXPRESS_DEV_API_KEY
if (environment == ApiEnvironment.PROD) {
MockEnvironmentConfigStorage.EXPRESS_API_KEY
} else {
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
}
},
"user-id" to Provider { EXPRESS_USER_ID },
"session-id" to Provider { EXPRESS_SESSION_ID },