Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-24 10:50:03 +01:00
commit bc84d8f5f8
579 changed files with 13604 additions and 3422 deletions

View file

@ -0,0 +1,69 @@
package com.tangem.data.settings
import android.os.SystemClock
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.PreferencesKeys.getIsFirstTimeAskingPermission
import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionDaysCount
import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionLaunchCount
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowInitialPermissionScreen
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowPermission
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.settings.repositories.PermissionRepository
internal class DefaultPermissionRepository(
private val appPreferencesStore: AppPreferencesStore,
) : PermissionRepository {
override suspend fun shouldInitiallyShowPermissionScreen(permission: String): Boolean {
val key = getShouldShowInitialPermissionScreen(permission)
val initialPermissionScreen = appPreferencesStore.getSyncOrDefault(key = key, default = true)
if (initialPermissionScreen) appPreferencesStore.store(key = key, value = false)
return initialPermissionScreen
}
override suspend fun isFirstTimeAskingPermission(permission: String): Boolean =
appPreferencesStore.getSyncOrDefault(
key = getIsFirstTimeAskingPermission(permission),
default = true,
)
override suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean) {
appPreferencesStore.store(
key = getIsFirstTimeAskingPermission(permission),
value = value,
)
}
override suspend fun shouldAskPermission(permission: String): Boolean {
val shouldAskPermission = appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true)
val delayedLaunches = appPreferencesStore.getSyncOrDefault(getPermissionLaunchCount(permission), 0)
val delayedDays = appPreferencesStore.getSyncOrDefault(getPermissionDaysCount(permission), 0)
val currentLaunchCounter = appPreferencesStore.getSyncOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
val nowMillis = SystemClock.elapsedRealtime()
val isDaysDelayed = delayedDays < nowMillis
val isLaunchesDelayed = delayedLaunches < currentLaunchCounter
return shouldAskPermission && isDaysDelayed && isLaunchesDelayed
}
override suspend fun neverAskPermission(permission: String) {
appPreferencesStore.store(key = getShouldShowPermission(permission), value = false)
}
override suspend fun delayPermissionAsking(permission: String) {
appPreferencesStore.editData {
val appLaunchCounter = it.getOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
val nowMillis = SystemClock.elapsedRealtime()
it[getPermissionLaunchCount(permission)] = appLaunchCounter + DELAY_LAUNCH_COUNT
it[getPermissionDaysCount(permission)] = nowMillis + DELAY_DAYS_COUNT
}
}
private companion object {
const val DELAY_LAUNCH_COUNT = 5
const val DELAY_DAYS_COUNT = 3L * 24 * 3600 * 1000 // 3 days in millis
}
}

View file

@ -1,12 +1,14 @@
package com.tangem.data.settings.di
import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.data.settings.DefaultPromoSettingsRepository
import com.tangem.data.settings.DefaultPermissionRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -34,4 +36,10 @@ internal object SettingsDataModule {
fun providePromoSettingsSettingsRepository(appPreferencesStore: AppPreferencesStore): PromoSettingsRepository {
return DefaultPromoSettingsRepository(appPreferencesStore = appPreferencesStore)
}
@Provides
@Singleton
fun providePushPermissionRepository(appPreferencesStore: AppPreferencesStore): PermissionRepository {
return DefaultPermissionRepository(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -14,8 +14,8 @@ dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.domain.tokens.models)
implementation(projects.domain.staking)
implementation(projects.features.staking.api)
// region DI
@ -29,6 +29,7 @@ dependencies {
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(projects.libs.blockchainSdk)
implementation(deps.tangem.blockchain) {
exclude(module = "joda-time")
}

View file

@ -1,29 +1,61 @@
package com.tangem.data.staking
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.data.staking.converters.StakingNetworkTypeConverter
import com.tangem.data.staking.converters.TokenConverter
import com.tangem.data.staking.converters.YieldConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
internal class DefaultStakingRepository(
private val stakeKitApi: StakeKitApi,
private val stakingFeatureToggles: StakingFeatureToggles,
private val stakingYieldsStore: StakingYieldsStore,
private val dispatchers: CoroutineDispatcherProvider,
) : StakingRepository {
override fun getStakingAvailability(blockchainId: String): StakingAvailability {
if (!stakingFeatureToggles.isStakingEnabled) {
return StakingAvailability.Unavailable
}
private val stakingNetworkTypeConverter = StakingNetworkTypeConverter()
return integrationIdMap[Blockchain.fromId(blockchainId)]?.let {
StakingAvailability.Available(it)
} ?: StakingAvailability.Unavailable
private val tokenConverter = TokenConverter(
stakingNetworkTypeConverter = stakingNetworkTypeConverter,
)
private val yieldConverter = YieldConverter(
tokenConverter = tokenConverter,
)
override fun isStakingSupported(currencyId: String): Boolean {
return integrationIds.contains(currencyId)
}
override suspend fun fetchEnabledYields() {
withContext(dispatchers.io) {
val stakingTokensWithYields = stakeKitApi.getMultipleYields().getOrThrow()
stakingYieldsStore.store(stakingTokensWithYields.data)
}
}
override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield {
return withContext(dispatchers.io) {
val yields = getEnabledYields() ?: error("No yields found")
val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: error("Staking custom tokens is not available")
val prefetchedYield = findPrefetchedYield(
yields = yields,
currencyId = rawCurrencyId,
symbol = symbol,
)
prefetchedYield ?: error("Staking is unavailable")
}
}
override suspend fun getEntryInfo(integrationId: String): StakingEntryInfo {
@ -38,31 +70,53 @@ internal class DefaultStakingRepository(
}
}
companion object {
private const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
private const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
private const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking"
private const val ETHEREUM_INTEGRATION_ID = "ethereum-matic-native-staking"
private const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking"
private const val TRON_INTEGRATION_ID = "tron-trx-native-staking"
private const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking"
private const val BINANCE_INTEGRATION_ID = "binance-bnb-native-staking"
private const val KAVA_INTEGRATION_ID = "kava-kava-native-staking"
private const val NEAR_INTEGRATION_ID = "near-near-native-staking"
private const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking"
override suspend fun getStakingAvailabilityForActions(
cryptoCurrencyId: CryptoCurrency.ID,
symbol: String,
): StakingAvailability {
val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: return StakingAvailability.Unavailable
private val integrationIdMap = mapOf(
Blockchain.Solana to SOLANA_INTEGRATION_ID,
Blockchain.Cosmos to COSMOS_INTEGRATION_ID,
Blockchain.Polkadot to POLKADOT_INTEGRATION_ID,
Blockchain.Polygon to ETHEREUM_INTEGRATION_ID,
Blockchain.Avalanche to AVALANCHE_INTEGRATION_ID,
Blockchain.Tron to TRON_INTEGRATION_ID,
Blockchain.Cronos to CRONOS_INTEGRATION_ID,
Blockchain.Binance to BINANCE_INTEGRATION_ID,
Blockchain.Kava to KAVA_INTEGRATION_ID,
Blockchain.Near to NEAR_INTEGRATION_ID,
Blockchain.Tezos to TEZOS_INTEGRATION_ID,
return withContext(dispatchers.io) {
val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, symbol)
val isSupported = isStakingSupported(rawCurrencyId)
when {
prefetchedYield != null && isSupported -> {
StakingAvailability.Available(prefetchedYield.id)
}
prefetchedYield == null && isSupported -> {
StakingAvailability.TemporaryDisabled
}
else -> StakingAvailability.Unavailable
}
}
}
private fun findPrefetchedYield(yields: List<Yield>, currencyId: String, symbol: String): Yield? {
return yields
.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
}
private suspend fun getEnabledYields(): List<Yield>? {
val yields = stakingYieldsStore.getSyncOrNull() ?: return null
return yields.map { yieldConverter.convert(it) }
}
companion object {
private val integrationIds = setOf(
Blockchain.Solana.toCoinId(),
Blockchain.Cosmos.toCoinId(),
Blockchain.Polkadot.toCoinId(),
Blockchain.Polygon.toCoinId(),
Blockchain.Avalanche.toCoinId(),
Blockchain.Tron.toCoinId(),
Blockchain.Cronos.toCoinId(),
Blockchain.Binance.toCoinId(),
Blockchain.Kava.toCoinId(),
Blockchain.Near.toCoinId(),
Blockchain.Tezos.toCoinId(),
)
}
}

View file

@ -0,0 +1,81 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
import com.tangem.domain.staking.model.NetworkType
import com.tangem.utils.converter.Converter
@Suppress("CyclomaticComplexMethod", "LongMethod")
class StakingNetworkTypeConverter : Converter<NetworkTypeDTO, NetworkType> {
override fun convert(value: NetworkTypeDTO): NetworkType {
return when (value) {
NetworkTypeDTO.AVALANCHE_C -> NetworkType.AVALANCHE_C
NetworkTypeDTO.AVALANCHE_ATOMIC -> NetworkType.AVALANCHE_ATOMIC
NetworkTypeDTO.AVALANCHE_P -> NetworkType.AVALANCHE_P
NetworkTypeDTO.ARBITRUM -> NetworkType.ARBITRUM
NetworkTypeDTO.BINANCE -> NetworkType.BINANCE
NetworkTypeDTO.CELO -> NetworkType.CELO
NetworkTypeDTO.ETHEREUM -> NetworkType.ETHEREUM
NetworkTypeDTO.ETHEREUM_GOERLI -> NetworkType.ETHEREUM_GOERLI
NetworkTypeDTO.ETHEREUM_HOLESKY -> NetworkType.ETHEREUM_HOLESKY
NetworkTypeDTO.FANTOM -> NetworkType.FANTOM
NetworkTypeDTO.HARMONY -> NetworkType.HARMONY
NetworkTypeDTO.OPTIMISM -> NetworkType.OPTIMISM
NetworkTypeDTO.POLYGON -> NetworkType.POLYGON
NetworkTypeDTO.GNOSIS -> NetworkType.GNOSIS
NetworkTypeDTO.MOONRIVER -> NetworkType.MOONRIVER
NetworkTypeDTO.OKC -> NetworkType.OKC
NetworkTypeDTO.ZKSYNC -> NetworkType.ZKSYNC
NetworkTypeDTO.VICTION -> NetworkType.VICTION
NetworkTypeDTO.AGORIC -> NetworkType.AGORIC
NetworkTypeDTO.AKASH -> NetworkType.AKASH
NetworkTypeDTO.AXELAR -> NetworkType.AXELAR
NetworkTypeDTO.BAND_PROTOCOL -> NetworkType.BAND_PROTOCOL
NetworkTypeDTO.BITSONG -> NetworkType.BITSONG
NetworkTypeDTO.CANTO -> NetworkType.CANTO
NetworkTypeDTO.CHIHUAHUA -> NetworkType.CHIHUAHUA
NetworkTypeDTO.COMDEX -> NetworkType.COMDEX
NetworkTypeDTO.COREUM -> NetworkType.COREUM
NetworkTypeDTO.COSMOS -> NetworkType.COSMOS
NetworkTypeDTO.CRESCENT -> NetworkType.CRESCENT
NetworkTypeDTO.CRONOS -> NetworkType.CRONOS
NetworkTypeDTO.CUDOS -> NetworkType.CUDOS
NetworkTypeDTO.DESMOS -> NetworkType.DESMOS
NetworkTypeDTO.DYDX -> NetworkType.DYDX
NetworkTypeDTO.EVMOS -> NetworkType.EVMOS
NetworkTypeDTO.FETCH_AI -> NetworkType.FETCH_AI
NetworkTypeDTO.GRAVITY_BRIDGE -> NetworkType.GRAVITY_BRIDGE
NetworkTypeDTO.INJECTIVE -> NetworkType.INJECTIVE
NetworkTypeDTO.IRISNET -> NetworkType.IRISNET
NetworkTypeDTO.JUNO -> NetworkType.JUNO
NetworkTypeDTO.KAVA -> NetworkType.KAVA
NetworkTypeDTO.KI_NETWORK -> NetworkType.KI_NETWORK
NetworkTypeDTO.MARS_PROTOCOL -> NetworkType.MARS_PROTOCOL
NetworkTypeDTO.NYM -> NetworkType.NYM
NetworkTypeDTO.OKEX_CHAIN -> NetworkType.OKEX_CHAIN
NetworkTypeDTO.ONOMY -> NetworkType.ONOMY
NetworkTypeDTO.OSMOSIS -> NetworkType.OSMOSIS
NetworkTypeDTO.PERSISTENCE -> NetworkType.PERSISTENCE
NetworkTypeDTO.QUICKSILVER -> NetworkType.QUICKSILVER
NetworkTypeDTO.REGEN -> NetworkType.REGEN
NetworkTypeDTO.SECRET -> NetworkType.SECRET
NetworkTypeDTO.SENTINEL -> NetworkType.SENTINEL
NetworkTypeDTO.SOMMELIER -> NetworkType.SOMMELIER
NetworkTypeDTO.STAFI -> NetworkType.STAFI
NetworkTypeDTO.STARGAZE -> NetworkType.STARGAZE
NetworkTypeDTO.STRIDE -> NetworkType.STRIDE
NetworkTypeDTO.TERITORI -> NetworkType.TERITORI
NetworkTypeDTO.TGRADE -> NetworkType.TGRADE
NetworkTypeDTO.UMEE -> NetworkType.UMEE
NetworkTypeDTO.POLKADOT -> NetworkType.POLKADOT
NetworkTypeDTO.KUSAMA -> NetworkType.KUSAMA
NetworkTypeDTO.WESTEND -> NetworkType.WESTEND
NetworkTypeDTO.BINANCEBEACON -> NetworkType.BINANCEBEACON
NetworkTypeDTO.NEAR -> NetworkType.NEAR
NetworkTypeDTO.SOLANA -> NetworkType.SOLANA
NetworkTypeDTO.TEZOS -> NetworkType.TEZOS
NetworkTypeDTO.TRON -> NetworkType.TRON
else -> NetworkType.UNKNOWN
}
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
import com.tangem.domain.staking.model.StakingToken
import com.tangem.domain.staking.model.StakingTokenWithYield
import com.tangem.utils.converter.Converter
class StakingTokenConverter : Converter<TokenWithYieldDTO, StakingTokenWithYield> {
override fun convert(value: TokenWithYieldDTO): StakingTokenWithYield {
return StakingTokenWithYield(
token = StakingToken(
name = value.token.name,
symbol = value.token.symbol,
decimals = value.token.decimals,
contractAddress = value.token.address,
coinGeckoId = value.token.coinGeckoId,
),
availableYieldIds = value.availableYieldIds,
)
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
import com.tangem.domain.staking.model.Token
import com.tangem.utils.converter.Converter
class TokenConverter(
private val stakingNetworkTypeConverter: StakingNetworkTypeConverter,
) : Converter<TokenDTO, Token> {
override fun convert(value: TokenDTO): Token {
return Token(
name = value.name,
network = stakingNetworkTypeConverter.convert(value.network),
symbol = value.symbol,
decimals = value.decimals,
address = value.address,
coinGeckoId = value.coinGeckoId,
logoURI = value.logoURI,
isPoints = value.isPoints,
)
}
}

View file

@ -0,0 +1,121 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.domain.staking.model.*
import com.tangem.utils.converter.Converter
class YieldConverter(
private val tokenConverter: TokenConverter,
) : Converter<YieldDTO, Yield> {
override fun convert(value: YieldDTO): Yield {
return Yield(
id = value.id,
token = tokenConverter.convert(value.token),
tokens = value.tokens.map { tokenConverter.convert(it) },
args = convertArgs(value.args),
status = convertStatus(value.status),
apy = value.apy,
rewardRate = value.rewardRate,
rewardType = convertRewardType(value.rewardType),
metadata = convertMetadata(value.metadata),
validators = value.validators.map { convertValidator(it) },
isAvailable = value.isAvailable,
)
}
private fun convertArgs(argsDTO: YieldDTO.ArgsDTO): Yield.Args {
return Yield.Args(
enter = convertEnter(argsDTO.enter),
exit = argsDTO.exit?.let { convertEnter(it) },
)
}
private fun convertEnter(enterDTO: YieldDTO.ArgsDTO.Enter): Yield.Args.Enter {
return Yield.Args.Enter(
addresses = convertAddresses(enterDTO.addresses),
args = enterDTO.args.mapValues { convertAddressArgument(it.value) },
)
}
private fun convertAddresses(addressesDTO: YieldDTO.ArgsDTO.Enter.Addresses): Yield.Args.Enter.Addresses {
return Yield.Args.Enter.Addresses(
address = convertAddressArgument(addressesDTO.address),
additionalAddresses = addressesDTO.additionalAddresses?.mapValues { convertAddressArgument(it.value) },
)
}
private fun convertAddressArgument(addressArgumentDTO: AddressArgumentDTO): AddressArgument {
return AddressArgument(
required = addressArgumentDTO.required,
network = addressArgumentDTO.network,
minimum = addressArgumentDTO.minimum,
maximum = addressArgumentDTO.maximum,
)
}
private fun convertStatus(statusDTO: YieldDTO.StatusDTO): Yield.Status {
return Yield.Status(
enter = statusDTO.enter,
exit = statusDTO.exit,
)
}
private fun convertMetadata(metadataDTO: YieldDTO.MetadataDTO): Yield.Metadata {
return Yield.Metadata(
name = metadataDTO.name,
logoUri = metadataDTO.logoUri,
description = metadataDTO.description,
documentation = metadataDTO.documentation,
gasFeeToken = tokenConverter.convert(metadataDTO.gasFeeTokenDTO),
token = tokenConverter.convert(metadataDTO.tokenDTO),
tokens = metadataDTO.tokensDTO.map { tokenConverter.convert(it) },
type = metadataDTO.type,
rewardSchedule = metadataDTO.rewardSchedule,
cooldownPeriod = convertPeriod(metadataDTO.cooldownPeriod),
warmupPeriod = convertPeriod(metadataDTO.warmupPeriod),
rewardClaiming = metadataDTO.rewardClaiming,
defaultValidator = metadataDTO.defaultValidator,
minimumStake = metadataDTO.minimumStake,
supportsMultipleValidators = metadataDTO.supportsMultipleValidators,
revshare = convertEnabled(metadataDTO.revshare),
fee = convertEnabled(metadataDTO.fee),
)
}
private fun convertPeriod(periodDTO: YieldDTO.MetadataDTO.PeriodDTO): Yield.Metadata.Period {
return Yield.Metadata.Period(
days = periodDTO.days,
)
}
private fun convertEnabled(enabledDTO: YieldDTO.MetadataDTO.EnabledDTO): Yield.Metadata.Enabled {
return Yield.Metadata.Enabled(
enabled = enabledDTO.enabled,
)
}
private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO): Yield.Validator {
return Yield.Validator(
address = validatorDTO.address,
status = validatorDTO.status,
name = validatorDTO.name,
image = validatorDTO.image,
website = validatorDTO.website,
apr = validatorDTO.apr,
commission = validatorDTO.commission,
stakedBalance = validatorDTO.stakedBalance,
votingPower = validatorDTO.votingPower,
preferred = validatorDTO.preferred,
)
}
private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): Yield.RewardType {
return when (rewardTypeDTO) {
YieldDTO.RewardTypeDTO.APY -> Yield.RewardType.APY
YieldDTO.RewardTypeDTO.APR -> Yield.RewardType.APR
else -> Yield.RewardType.UNKNOWN
}
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.data.staking.converters.action
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
import com.tangem.domain.staking.model.action.StakingActionStatus
import com.tangem.utils.converter.Converter
class ActionStatusConverter : Converter<StakingActionStatusDTO, StakingActionStatus> {
override fun convert(value: StakingActionStatusDTO): StakingActionStatus {
return when (value) {
StakingActionStatusDTO.CANCELED -> StakingActionStatus.CANCELED
StakingActionStatusDTO.CREATED -> StakingActionStatus.CREATED
StakingActionStatusDTO.WAITING_FOR_NEXT -> StakingActionStatus.WAITING_FOR_NEXT
StakingActionStatusDTO.PROCESSING -> StakingActionStatus.PROCESSING
StakingActionStatusDTO.FAILED -> StakingActionStatus.FAILED
StakingActionStatusDTO.SUCCESS -> StakingActionStatus.SUCCESS
else -> StakingActionStatus.UNKNOWN
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.data.staking.converters.action
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse
import com.tangem.domain.staking.model.action.EnterAction
import com.tangem.utils.converter.Converter
class EnterActionResponseConverter(
private val actionStatusConverter: ActionStatusConverter,
private val stakingActionTypeConverter: StakingActionTypeConverter,
private val transactionConverter: StakingTransactionConverter,
) : Converter<EnterActionResponse, EnterAction> {
override fun convert(value: EnterActionResponse): EnterAction {
return EnterAction(
id = value.id,
integrationId = value.integrationId,
status = actionStatusConverter.convert(value.status),
type = stakingActionTypeConverter.convert(value.type),
currentStepIndex = value.currentStepIndex,
amount = value.amount,
validatorAddress = value.validatorAddress,
validatorAddresses = value.validatorAddresses,
transactions = value.transactions?.map(transactionConverter::convert),
createdAt = value.createdAt,
)
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.data.staking.converters.action
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
import com.tangem.domain.staking.model.action.StakingActionType
import com.tangem.utils.converter.Converter
@Suppress("CyclomaticComplexMethod")
class StakingActionTypeConverter : Converter<StakingActionTypeDTO, StakingActionType> {
override fun convert(value: StakingActionTypeDTO): StakingActionType {
return when (value) {
StakingActionTypeDTO.STAKE -> StakingActionType.STAKE
StakingActionTypeDTO.UNSTAKE -> StakingActionType.UNSTAKE
StakingActionTypeDTO.CLAIM_REWARDS -> StakingActionType.CLAIM_REWARDS
StakingActionTypeDTO.RESTAKE_REWARDS -> StakingActionType.RESTAKE_REWARDS
StakingActionTypeDTO.WITHDRAW -> StakingActionType.WITHDRAW
StakingActionTypeDTO.RESTAKE -> StakingActionType.RESTAKE
StakingActionTypeDTO.CLAIM_UNSTAKED -> StakingActionType.CLAIM_UNSTAKED
StakingActionTypeDTO.UNLOCK_LOCKED -> StakingActionType.UNLOCK_LOCKED
StakingActionTypeDTO.STAKE_LOCKED -> StakingActionType.STAKE_LOCKED
StakingActionTypeDTO.VOTE -> StakingActionType.VOTE
StakingActionTypeDTO.REVOKE -> StakingActionType.REVOKE
StakingActionTypeDTO.VOTE_LOCKED -> StakingActionType.VOTE_LOCKED
StakingActionTypeDTO.REVOTE -> StakingActionType.REVOTE
StakingActionTypeDTO.REBOND -> StakingActionType.REBOND
StakingActionTypeDTO.MIGRATE -> StakingActionType.MIGRATE
StakingActionTypeDTO.UNKNOWN -> StakingActionType.UNKNOWN
}
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.data.staking.converters.transaction
import com.tangem.data.staking.converters.TokenConverter
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO
import com.tangem.domain.staking.model.transaction.StakingGasEstimate
import com.tangem.utils.converter.Converter
class GasEstimateConverter(
private val tokenConverter: TokenConverter,
) : Converter<StakingGasEstimateDTO, StakingGasEstimate> {
override fun convert(value: StakingGasEstimateDTO): StakingGasEstimate {
return StakingGasEstimate(
amount = value.amount,
token = tokenConverter.convert(value.token),
gasLimit = value.gasLimit,
)
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.data.staking.converters.transaction
import com.tangem.data.staking.converters.StakingNetworkTypeConverter
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
import com.tangem.domain.staking.model.transaction.StakingTransaction
import com.tangem.utils.converter.Converter
class StakingTransactionConverter(
private val networkTypeConverter: StakingNetworkTypeConverter,
private val transactionStatusConverter: StakingTransactionStatusConverter,
private val transactionTypeConverter: StakingTransactionTypeConverter,
private val gasEstimateConverter: GasEstimateConverter,
) : Converter<StakingTransactionDTO, StakingTransaction> {
override fun convert(value: StakingTransactionDTO): StakingTransaction {
return StakingTransaction(
id = value.id,
network = networkTypeConverter.convert(value.network),
status = transactionStatusConverter.convert(value.status),
type = transactionTypeConverter.convert(value.type),
hash = value.hash,
signedTransaction = value.signedTransaction,
unsignedTransaction = value.unsignedTransaction,
stepIndex = value.stepIndex,
error = value.error,
gasEstimate = value.gasEstimate?.let { gasEstimateConverter.convert(it) },
stakeId = value.stakeId,
explorerUrl = value.explorerUrl,
ledgerHwAppId = value.ledgerHwAppId,
isMessage = value.isMessage,
)
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.staking.converters.transaction
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
import com.tangem.domain.staking.model.transaction.StakingTransactionStatus
import com.tangem.utils.converter.Converter
class StakingTransactionStatusConverter : Converter<StakingTransactionStatusDTO, StakingTransactionStatus> {
override fun convert(value: StakingTransactionStatusDTO): StakingTransactionStatus {
return when (value) {
StakingTransactionStatusDTO.NOT_FOUND -> StakingTransactionStatus.NOT_FOUND
StakingTransactionStatusDTO.CREATED -> StakingTransactionStatus.CREATED
StakingTransactionStatusDTO.BLOCKED -> StakingTransactionStatus.BLOCKED
StakingTransactionStatusDTO.WAITING_FOR_SIGNATURE -> StakingTransactionStatus.WAITING_FOR_SIGNATURE
StakingTransactionStatusDTO.SIGNED -> StakingTransactionStatus.SIGNED
StakingTransactionStatusDTO.BROADCASTED -> StakingTransactionStatus.BROADCASTED
StakingTransactionStatusDTO.PENDING -> StakingTransactionStatus.PENDING
StakingTransactionStatusDTO.CONFIRMED -> StakingTransactionStatus.CONFIRMED
StakingTransactionStatusDTO.FAILED -> StakingTransactionStatus.FAILED
StakingTransactionStatusDTO.SKIPPED -> StakingTransactionStatus.SKIPPED
StakingTransactionStatusDTO.UNKNOWN -> StakingTransactionStatus.UNKNOWN
}
}
}

View file

@ -0,0 +1,54 @@
package com.tangem.data.staking.converters.transaction
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
import com.tangem.domain.staking.model.transaction.StakingTransactionType
import com.tangem.utils.converter.Converter
@Suppress("CyclomaticComplexMethod")
class StakingTransactionTypeConverter : Converter<StakingTransactionTypeDTO, StakingTransactionType> {
override fun convert(value: StakingTransactionTypeDTO): StakingTransactionType {
return when (value) {
StakingTransactionTypeDTO.SWAP -> StakingTransactionType.SWAP
StakingTransactionTypeDTO.DEPOSIT -> StakingTransactionType.DEPOSIT
StakingTransactionTypeDTO.APPROVAL -> StakingTransactionType.APPROVAL
StakingTransactionTypeDTO.STAKE -> StakingTransactionType.STAKE
StakingTransactionTypeDTO.CLAIM_UNSTAKED -> StakingTransactionType.CLAIM_UNSTAKED
StakingTransactionTypeDTO.CLAIM_REWARDS -> StakingTransactionType.CLAIM_REWARDS
StakingTransactionTypeDTO.RESTAKE_REWARDS -> StakingTransactionType.RESTAKE_REWARDS
StakingTransactionTypeDTO.UNSTAKE -> StakingTransactionType.UNSTAKE
StakingTransactionTypeDTO.SPLIT -> StakingTransactionType.SPLIT
StakingTransactionTypeDTO.MERGE -> StakingTransactionType.MERGE
StakingTransactionTypeDTO.LOCK -> StakingTransactionType.LOCK
StakingTransactionTypeDTO.UNLOCK -> StakingTransactionType.UNLOCK
StakingTransactionTypeDTO.SUPPLY -> StakingTransactionType.SUPPLY
StakingTransactionTypeDTO.BRIDGE -> StakingTransactionType.BRIDGE
StakingTransactionTypeDTO.VOTE -> StakingTransactionType.VOTE
StakingTransactionTypeDTO.REVOKE -> StakingTransactionType.REVOKE
StakingTransactionTypeDTO.RESTAKE -> StakingTransactionType.RESTAKE
StakingTransactionTypeDTO.REBOND -> StakingTransactionType.REBOND
StakingTransactionTypeDTO.WITHDRAW -> StakingTransactionType.WITHDRAW
StakingTransactionTypeDTO.CREATE_ACCOUNT -> StakingTransactionType.CREATE_ACCOUNT
StakingTransactionTypeDTO.REVEAL -> StakingTransactionType.REVEAL
StakingTransactionTypeDTO.MIGRATE -> StakingTransactionType.MIGRATE
StakingTransactionTypeDTO.UTXO_P_TO_C_IMPORT -> StakingTransactionType.UTXO_P_TO_C_IMPORT
StakingTransactionTypeDTO.UTXO_C_TO_P_IMPORT -> StakingTransactionType.UTXO_C_TO_P_IMPORT
StakingTransactionTypeDTO.UNFREEZE_LEGACY -> StakingTransactionType.UNFREEZE_LEGACY
StakingTransactionTypeDTO.UNFREEZE_LEGACY_BANDWIDTH -> StakingTransactionType.UNFREEZE_LEGACY_BANDWIDTH
StakingTransactionTypeDTO.UNFREEZE_LEGACY_ENERGY -> StakingTransactionType.UNFREEZE_LEGACY_ENERGY
StakingTransactionTypeDTO.UNFREEZE_BANDWIDTH -> StakingTransactionType.UNFREEZE_BANDWIDTH
StakingTransactionTypeDTO.UNFREEZE_ENERGY -> StakingTransactionType.UNFREEZE_ENERGY
StakingTransactionTypeDTO.FREEZE_BANDWIDTH -> StakingTransactionType.FREEZE_BANDWIDTH
StakingTransactionTypeDTO.FREEZE_ENERGY -> StakingTransactionType.FREEZE_ENERGY
StakingTransactionTypeDTO.UNDELEGATE_BANDWIDTH -> StakingTransactionType.UNDELEGATE_BANDWIDTH
StakingTransactionTypeDTO.UNDELEGATE_ENERGY -> StakingTransactionType.UNDELEGATE_ENERGY
StakingTransactionTypeDTO.P2P_NODE_REQUEST -> StakingTransactionType.P2P_NODE_REQUEST
StakingTransactionTypeDTO.LUGANODES_PROVISION -> StakingTransactionType.LUGANODES_PROVISION
StakingTransactionTypeDTO.LUGANODES_EXIT_REQUEST -> StakingTransactionType.LUGANODES_EXIT_REQUEST
StakingTransactionTypeDTO.INFSTONES_PROVISION -> StakingTransactionType.INFSTONES_PROVISION
StakingTransactionTypeDTO.INFSTONES_EXIT_REQUEST -> StakingTransactionType.INFSTONES_EXIT_REQUEST
StakingTransactionTypeDTO.INFSTONES_CLAIM_REQUEST -> StakingTransactionType.INFSTONES_CLAIM_REQUEST
StakingTransactionTypeDTO.UNKNOWN -> StakingTransactionType.UNKNOWN
}
}
}

View file

@ -2,8 +2,8 @@ package com.tangem.data.staking.di
import com.tangem.data.staking.DefaultStakingRepository
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -19,13 +19,13 @@ internal object StakingDataModule {
@Singleton
fun provideStakingRepository(
stakeKitApi: StakeKitApi,
stakingFeatureToggles: StakingFeatureToggles,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
stakingTokenStore: StakingYieldsStore,
dispatchers: CoroutineDispatcherProvider,
): StakingRepository {
return DefaultStakingRepository(
stakeKitApi = stakeKitApi,
stakingFeatureToggles = stakingFeatureToggles,
dispatchers = coroutineDispatcherProvider,
stakingYieldsStore = stakingTokenStore,
dispatchers = dispatchers,
)
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.*
@ -31,7 +31,7 @@ internal object TokensDataModule {
userTokensStore: UserTokensStore,
userWalletsStore: UserWalletsStore,
walletManagersFacade: WalletManagersFacade,
assetsStore: AssetsStore,
expressAssetsStore: ExpressAssetsStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): CurrenciesRepository {
@ -41,7 +41,7 @@ internal object TokensDataModule {
userTokensStore = userTokensStore,
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
assetsStore = assetsStore,
expressAssetsStore = expressAssetsStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
@ -87,8 +87,8 @@ internal object TokensDataModule {
@Provides
@Singleton
fun provideDefaultMarketCoinsRepository(assetsStore: AssetsStore): MarketCryptoCurrencyRepository {
return DefaultMarketCryptoCurrencyRepository(assetsStore)
fun provideDefaultMarketCoinsRepository(expressAssetsStore: ExpressAssetsStore): MarketCryptoCurrencyRepository {
return DefaultMarketCryptoCurrencyRepository(expressAssetsStore)
}
@Provides

View file

@ -15,7 +15,7 @@ import com.tangem.datasource.api.express.models.request.AssetsRequestBody
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.derivationStyleProvider
@ -46,7 +46,7 @@ internal class DefaultCurrenciesRepository(
private val userTokensStore: UserTokensStore,
private val userWalletsStore: UserWalletsStore,
private val walletManagersFacade: WalletManagersFacade,
private val assetsStore: AssetsStore,
private val expressAssetsStore: ExpressAssetsStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : CurrenciesRepository {
@ -61,6 +61,18 @@ internal class DefaultCurrenciesRepository(
private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow(
value = emptyMap<UserWalletId, Boolean>(),
)
private val parallelTransactionsEnabledBlockchains = setOf(
Blockchain.Ethereum,
Blockchain.EthereumTestnet,
Blockchain.Polygon,
Blockchain.PolygonTestnet,
Blockchain.Arbitrum,
Blockchain.ArbitrumTestnet,
Blockchain.Binance,
Blockchain.BinanceTestnet,
Blockchain.Tron,
Blockchain.TronTestnet,
)
override suspend fun saveTokens(
userWalletId: UserWalletId,
@ -366,18 +378,19 @@ internal class DefaultCurrenciesRepository(
}
}
override fun hasPendingTransactions(
override fun isSendBlockedByPendingTransactions(
cryptoCurrencyStatus: CryptoCurrencyStatus,
coinStatus: CryptoCurrencyStatus?,
): Boolean {
val blockchain = Blockchain.fromId(cryptoCurrencyStatus.currency.network.id.value)
val isBitcoinBlockchain = blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
return if (cryptoCurrencyStatus.currency is CryptoCurrency.Coin && isBitcoinBlockchain) {
val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
outgoingTransactions.isNotEmpty()
} else {
coinStatus?.value?.hasCurrentNetworkTransactions == true
return when {
cryptoCurrencyStatus.currency is CryptoCurrency.Coin && isBitcoinBlockchain -> {
val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
outgoingTransactions.isNotEmpty()
}
parallelTransactionsEnabledBlockchains.contains(blockchain) -> false
else -> coinStatus?.value?.hasCurrentNetworkTransactions == true
}
}
@ -493,7 +506,7 @@ internal class DefaultCurrenciesRepository(
),
)
assetsStore.store(userWalletId, response.getOrThrow())
expressAssetsStore.store(userWalletId, response.getOrThrow())
}
} catch (e: Throwable) {
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")

View file

@ -1,13 +1,13 @@
package com.tangem.data.tokens.repository
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.wallets.models.UserWalletId
class DefaultMarketCryptoCurrencyRepository(
private val assetsStore: AssetsStore,
private val expressAssetsStore: ExpressAssetsStore,
) : MarketCryptoCurrencyRepository {
override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
@ -17,7 +17,7 @@ class DefaultMarketCryptoCurrencyRepository(
private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE
return assetsStore.getSyncOrNull(userWalletId)?.find {
return expressAssetsStore.getSyncOrNull(userWalletId)?.find {
it.network == cryptoCurrency.network.backendId &&
it.contractAddress.equals(contractAddress, ignoreCase = true)
}?.exchangeAvailable ?: false

View file

@ -10,10 +10,8 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.visa.utils.VisaConfig
import com.tangem.data.visa.utils.VisaCurrencyFactory
import com.tangem.data.visa.utils.VisaTxDetailsFactory
import com.tangem.data.visa.utils.VisaTxHistoryPagingSource
import com.tangem.data.visa.config.VisaLibLoader
import com.tangem.data.visa.utils.*
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.userwallet.UserWalletsStore
@ -24,8 +22,6 @@ import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.lib.visa.VisaContractInfoProvider
import com.tangem.lib.visa.api.VisaApi
import com.tangem.lib.visa.model.VisaTxHistoryResponse
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
@ -35,9 +31,8 @@ import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class DefaultVisaRepository(
private val visaContractInfoProvider: VisaContractInfoProvider,
private val visaLibLoader: VisaLibLoader,
private val tangemTechApi: TangemTechApi,
private val visaApi: VisaApi,
private val cacheRegistry: CacheRegistry,
private val userWalletsStore: UserWalletsStore,
private val dispatchers: CoroutineDispatcherProvider,
@ -76,9 +71,11 @@ internal class DefaultVisaRepository(
}
private suspend fun fetchVisaCurrency(address: String) {
val contractInfoProvider = visaLibLoader.getOrCreateProvider()
parZip(
dispatchers.io,
{ visaContractInfoProvider.getContractInfo(address) },
{ contractInfoProvider.getContractInfo(address) },
{ getFiatRate() },
{ contractInfo, fiatRate ->
fetchedCurrencies.update { value ->
@ -97,6 +94,7 @@ internal class DefaultVisaRepository(
): Flow<PagingData<VisaTxHistoryItem>> {
val userWallet = findVisaUserWallet(userWalletId)
val cardPubKey = getCardPubKey(userWallet)
val api = visaLibLoader.getOrCreateApi()
val pager = Pager(
config = PagingConfig(
pageSize = pageSize,
@ -110,7 +108,7 @@ internal class DefaultVisaRepository(
isRefresh = isRefresh,
),
cacheRegistry = cacheRegistry,
visaApi = visaApi,
visaApi = api,
fetchedItems = fetchedHistoryItems,
dispatchers = dispatchers,
)
@ -137,7 +135,7 @@ internal class DefaultVisaRepository(
}
private suspend fun makeAddress(userWalletId: UserWalletId): String {
if (IS_DEMO_MODE_ENABLED) return DEMO_ADDRESS
if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoAddress()
val userWallet = findVisaUserWallet(userWalletId)
val walletAddresses = makeWalletAddresses(userWallet)
@ -149,13 +147,13 @@ internal class DefaultVisaRepository(
}
private suspend fun getFiatRate(): BigDecimal? {
val fiatCurrencyId = VisaConfig.fiatCurrency.code.lowercase()
val fiatCurrencyId = VisaConstants.fiatCurrency.code.lowercase()
val quotes = tangemTechApi.getQuotes(
currencyId = fiatCurrencyId,
coinIds = VisaConfig.TOKEN_ID,
coinIds = VisaConstants.TOKEN_ID,
).getOrThrow()
return quotes.quotes[VisaConfig.TOKEN_ID]?.price
return quotes.quotes[VisaConstants.TOKEN_ID]?.price
}
private fun makeWalletAddresses(userWallet: UserWallet): Set<Address> {
@ -165,7 +163,7 @@ internal class DefaultVisaRepository(
}
private fun getCardPubKey(userWallet: UserWallet): String {
if (IS_DEMO_MODE_ENABLED) return DEMO_PUBLIC_KEY
if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoPublicKey()
val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull {
it.curve == EllipticCurve.Secp256k1
@ -189,12 +187,4 @@ internal class DefaultVisaRepository(
private fun getVisaCurrencyKey(address: String): String {
return "visa_currency_$address"
}
private companion object {
// Must be `false` in production
const val IS_DEMO_MODE_ENABLED = false
const val DEMO_ADDRESS = "0x40d8194b7168723ece51fa34d16825c60ba03dfa"
const val DEMO_PUBLIC_KEY = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9"
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.data.visa.config
import com.squareup.moshi.Json
internal data class VisaConfig(
@Json(name = "testnet")
val testnet: Addresses,
@Json(name = "mainnet")
val mainnet: Addresses,
@Json(name = "txHistoryAPIAdditionalHeaders")
val header: Header,
) {
data class Addresses(
@Json(name = "paymentAccountRegistry")
val paymentAccountRegistry: String,
@Json(name = "bridgeProcessor")
val bridgeProcessor: String,
)
data class Header(
@Json(name = "x-asn")
val xAsn: String,
)
}

View file

@ -0,0 +1,86 @@
package com.tangem.data.visa.config
import com.squareup.moshi.Moshi
import com.tangem.data.visa.BuildConfig
import com.tangem.data.visa.utils.VisaConstants
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.lib.visa.VisaContractInfoProvider
import com.tangem.lib.visa.api.VisaApi
import com.tangem.lib.visa.api.VisaApiBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import javax.inject.Inject
internal class VisaLibLoader @Inject constructor(
private val assetLoader: AssetLoader,
@NetworkMoshi private val moshi: Moshi,
private val dispatchers: CoroutineDispatcherProvider,
) {
private val createMutex = Mutex()
private var config: VisaConfig? = null
private var provider: VisaContractInfoProvider? = null
private var api: VisaApi? = null
suspend fun getOrCreateProvider(): VisaContractInfoProvider = provider ?: createProvider()
suspend fun getOrCreateApi(): VisaApi = api ?: createApi()
private suspend fun createProvider(): VisaContractInfoProvider = createMutex.withLock {
val config = getOrLoadConfig()
provider = VisaContractInfoProvider.Builder(
useTestnetRpc = VisaConstants.USE_TEST_ENV,
bridgeProcessorAddress = if (VisaConstants.USE_TEST_ENV) {
config.testnet.bridgeProcessor
} else {
config.mainnet.bridgeProcessor
},
paymentAccountRegistryAddress = if (VisaConstants.USE_TEST_ENV) {
config.testnet.paymentAccountRegistry
} else {
config.mainnet.paymentAccountRegistry
},
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
dispatchers = dispatchers,
).build()
return requireNotNull(provider) {
"Visa provider is not created"
}
}
private suspend fun createApi(): VisaApi = createMutex.withLock {
val config = getOrLoadConfig()
api = VisaApiBuilder(
useDevApi = VisaConstants.USE_TEST_ENV,
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
moshi = moshi,
headers = mapOf(
X_ASN_HEADER_NAME to config.header.xAsn,
),
).build()
return requireNotNull(api) {
"Visa API is not created"
}
}
private suspend fun getOrLoadConfig(): VisaConfig {
config = assetLoader.load<VisaConfig>(VISA_CONFIG_FILE_NAME)
return requireNotNull(config) {
"Visa config is not found"
}
}
companion object {
private const val VISA_CONFIG_FILE_NAME = "tangem-app-config/visa_config"
private const val X_ASN_HEADER_NAME = "x-asn"
}
}

View file

@ -1,15 +1,11 @@
package com.tangem.data.visa.di
import com.squareup.moshi.Moshi
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.visa.BuildConfig
import com.tangem.data.visa.DefaultVisaRepository
import com.tangem.data.visa.config.VisaLibLoader
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.lib.visa.VisaContractInfoProvider
import com.tangem.lib.visa.api.VisaApiBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -25,29 +21,16 @@ internal object ImplementedVisaDataModule {
@Singleton
@ImplementedVisaRepository
fun provideVisaRepository(
@NetworkMoshi moshi: Moshi,
visaLibLoader: VisaLibLoader,
tangemTechApi: TangemTechApi,
cacheRegistry: CacheRegistry,
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
): VisaRepository {
val contractInfoProvider = VisaContractInfoProvider.Builder(
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
dispatchers = dispatchers,
).build()
val visaApi = VisaApiBuilder(
useDevApi = true,
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
moshi = moshi,
).build()
return DefaultVisaRepository(
contractInfoProvider,
tangemTechApi,
visaApi,
cacheRegistry,
userWalletsStore,
dispatchers,
)
}
): VisaRepository = DefaultVisaRepository(
visaLibLoader,
tangemTechApi,
cacheRegistry,
userWalletsStore,
dispatchers,
)
}

View file

@ -7,9 +7,9 @@ import java.util.Currency
internal fun findCurrencyByNumericCode(code: Int): Currency {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Currency.getAvailableCurrencies().firstOrNull { it.numericCode == code }
?: Currency.getInstance(VisaConfig.fiatCurrency.code)
?: Currency.getInstance(VisaConstants.fiatCurrency.code)
} else {
Timber.w("Unable to get currency by numeric code on API level ${Build.VERSION.SDK_INT}")
Currency.getInstance(VisaConfig.fiatCurrency.code)
Currency.getInstance(VisaConstants.fiatCurrency.code)
}
}

View file

@ -1,16 +0,0 @@
package com.tangem.data.visa.utils
import com.tangem.domain.appcurrency.model.AppCurrency
internal object VisaConfig {
const val NETWORK_NAME = "Polygon PoS"
const val TOKEN_ID = "tether"
val fiatCurrency = AppCurrency(
code = "EUR",
name = "Euro",
symbol = "",
)
}

View file

@ -0,0 +1,44 @@
package com.tangem.data.visa.utils
import com.tangem.domain.appcurrency.model.AppCurrency
internal object VisaConstants {
const val NETWORK_NAME = "Polygon PoS"
const val TOKEN_ID = "tether"
val fiatCurrency = AppCurrency(
code = "EUR",
name = "Euro",
symbol = "",
)
/*
* Must be `false` in production
* Don't forget to change CardTypesResolver.isVisaWallet
* */
const val IS_DEMO_MODE_ENABLED = false
const val USE_TEST_ENV = true
const val DEMO_TESTNET_ADDRESS = "0x51d034eb1563d0d2e66379ef37756d3c14936c44"
const val DEMO_TESTNET_PUBLIC_KEY = "03FA1122B809079F79C4E0F657FE11337FEC88C3FB3C6341B2CE2E4F5D9241DD86"
const val DEMO_MAINNET_ADDRESS = "0x927e3ef2b3d85bacf9e520379f64f6627d323fcd"
const val DEMO_MAINNET_PUBLIC_KEY = "02AC61CD57B8011BEE8BB489FB744845CC113AD379132C56015EE70528B6A88E92"
}
internal fun getDemoAddress(): String {
return if (VisaConstants.USE_TEST_ENV) {
VisaConstants.DEMO_TESTNET_ADDRESS
} else {
VisaConstants.DEMO_MAINNET_ADDRESS
}
}
internal fun getDemoPublicKey(): String {
return if (VisaConstants.USE_TEST_ENV) {
VisaConstants.DEMO_TESTNET_PUBLIC_KEY
} else VisaConstants.DEMO_MAINNET_PUBLIC_KEY
}

View file

@ -21,10 +21,10 @@ internal class VisaCurrencyFactory {
return VisaCurrency(
symbol = contractInfo.token.symbol,
networkName = VisaConfig.NETWORK_NAME,
networkName = VisaConstants.NETWORK_NAME,
decimals = contractInfo.token.decimals,
fiatRate = fiatRate,
fiatCurrency = VisaConfig.fiatCurrency,
fiatCurrency = VisaConstants.fiatCurrency,
balances = with(contractInfo) {
VisaCurrency.Balances(
total = balances.total,