Updated on 2026-08-14
This commit is contained in:
commit
369ed25ac6
515 changed files with 6100 additions and 12756 deletions
|
|
@ -34,12 +34,6 @@ internal class DefaultCardSdkConfigRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override fun isBiometricsRequestPolicy(): Boolean {
|
||||
return with(sdk.config.userCodeRequestPolicy) {
|
||||
this is UserCodeRequestPolicy.AlwaysWithBiometrics && codeType == UserCodeType.AccessCode
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetCardIdDisplayFormat() {
|
||||
sdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.data.common.currency
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import timber.log.Timber
|
||||
|
|
@ -39,14 +39,14 @@ class CryptoCurrencyFactory {
|
|||
sdkToken: SdkToken,
|
||||
blockchain: Blockchain,
|
||||
extraDerivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): CryptoCurrency.Token? {
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
|
||||
return null
|
||||
}
|
||||
|
||||
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
|
||||
val network = getNetwork(blockchain, extraDerivationPath, scanResponse) ?: return null
|
||||
val id = getTokenId(network, sdkToken)
|
||||
|
||||
return CryptoCurrency.Token(
|
||||
|
|
@ -64,24 +64,20 @@ class CryptoCurrencyFactory {
|
|||
fun createCoin(
|
||||
blockchain: Blockchain,
|
||||
extraDerivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): CryptoCurrency.Coin? {
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
|
||||
return null
|
||||
}
|
||||
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
|
||||
val network = getNetwork(blockchain, extraDerivationPath, scanResponse) ?: return null
|
||||
|
||||
return createCoin(network)
|
||||
}
|
||||
|
||||
fun createCoin(
|
||||
networkId: String,
|
||||
extraDerivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
): CryptoCurrency.Coin? {
|
||||
fun createCoin(networkId: String, extraDerivationPath: String?, scanResponse: ScanResponse): CryptoCurrency.Coin? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: Blockchain.Unknown
|
||||
return createCoin(blockchain, extraDerivationPath, derivationStyleProvider)
|
||||
return createCoin(blockchain, extraDerivationPath, scanResponse)
|
||||
}
|
||||
|
||||
fun createCoin(network: Network): CryptoCurrency.Coin {
|
||||
|
|
@ -102,7 +98,7 @@ class CryptoCurrencyFactory {
|
|||
token: Token,
|
||||
networkId: String,
|
||||
extraDerivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): CryptoCurrency.Token? {
|
||||
val sdkToken = SdkToken(
|
||||
name = token.name,
|
||||
|
|
@ -116,7 +112,7 @@ class CryptoCurrencyFactory {
|
|||
sdkToken = sdkToken,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.FeePaidCurrency
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -11,26 +15,11 @@ fun getBlockchain(networkId: Network.ID): Blockchain {
|
|||
return Blockchain.fromId(networkId.value)
|
||||
}
|
||||
|
||||
fun getNetwork(networkId: Network.ID, derivationPath: Network.DerivationPath): Network {
|
||||
val blockchain = getBlockchain(networkId)
|
||||
|
||||
return Network(
|
||||
id = networkId,
|
||||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = derivationPath,
|
||||
currencySymbol = blockchain.currency,
|
||||
standardType = getNetworkStandardType(blockchain),
|
||||
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||
canHandleTokens = blockchain.canHandleTokens(),
|
||||
)
|
||||
}
|
||||
|
||||
fun getNetwork(
|
||||
blockchain: Blockchain,
|
||||
extraDerivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider?,
|
||||
canHandleTokens: Boolean,
|
||||
): Network? {
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to convert Unknown blockchain to the domain network model")
|
||||
|
|
@ -42,11 +31,44 @@ fun getNetwork(
|
|||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = getNetworkDerivationPath(blockchain, extraDerivationPath, derivationStyleProvider),
|
||||
derivationPath = getNetworkDerivationPath(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
cardDerivationStyleProvider = derivationStyleProvider,
|
||||
),
|
||||
currencySymbol = blockchain.currency,
|
||||
standardType = getNetworkStandardType(blockchain),
|
||||
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||
canHandleTokens = blockchain.canHandleTokens(),
|
||||
canHandleTokens = canHandleTokens,
|
||||
)
|
||||
}
|
||||
|
||||
fun getNetwork(networkId: Network.ID, derivationPath: Network.DerivationPath, scanResponse: ScanResponse): Network? {
|
||||
val blockchain = getBlockchain(networkId)
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to convert Unknown blockchain to the domain network model")
|
||||
return null
|
||||
}
|
||||
|
||||
return Network(
|
||||
id = networkId,
|
||||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = derivationPath,
|
||||
currencySymbol = blockchain.currency,
|
||||
standardType = getNetworkStandardType(blockchain),
|
||||
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||
canHandleTokens = scanResponse.card.canHandleToken(blockchain, scanResponse.cardTypesResolver),
|
||||
)
|
||||
}
|
||||
|
||||
fun getNetwork(blockchain: Blockchain, extraDerivationPath: String?, scanResponse: ScanResponse): Network? {
|
||||
return getNetwork(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
canHandleTokens = scanResponse.card.canHandleToken(blockchain, scanResponse.cardTypesResolver),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import com.tangem.blockchainsdk.compatibility.l2BlockchainsList
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import timber.log.Timber
|
||||
|
|
@ -42,17 +40,15 @@ class ResponseCryptoCurrenciesFactory {
|
|||
return null
|
||||
}
|
||||
|
||||
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTestCard()) {
|
||||
blockchain = blockchain.getTestnetVersion() ?: blockchain
|
||||
}
|
||||
|
||||
val sdkToken = createSdkToken(responseToken)
|
||||
return if (sdkToken == null) {
|
||||
createCoin(blockchain, responseToken, cardDerivationStyleProvider)
|
||||
createCoin(blockchain, responseToken, scanResponse)
|
||||
} else {
|
||||
createToken(blockchain, sdkToken, responseToken.derivationPath, cardDerivationStyleProvider)
|
||||
createToken(blockchain, sdkToken, responseToken.derivationPath, scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,9 +67,9 @@ class ResponseCryptoCurrenciesFactory {
|
|||
private fun createCoin(
|
||||
blockchain: Blockchain,
|
||||
responseToken: UserTokensResponse.Token,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): CryptoCurrency.Coin? {
|
||||
val network = getNetwork(blockchain, responseToken.derivationPath, derivationStyleProvider) ?: return null
|
||||
val network = getNetwork(blockchain, responseToken.derivationPath, scanResponse) ?: return null
|
||||
|
||||
return CryptoCurrency.Coin(
|
||||
id = getCoinId(network, blockchain.toCoinId()),
|
||||
|
|
@ -117,9 +113,9 @@ class ResponseCryptoCurrenciesFactory {
|
|||
blockchain: Blockchain,
|
||||
sdkToken: Token,
|
||||
responseDerivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): CryptoCurrency.Token? {
|
||||
val network = getNetwork(blockchain, responseDerivationPath, derivationStyleProvider)
|
||||
val network = getNetwork(blockchain, responseDerivationPath, scanResponse)
|
||||
?: return null
|
||||
val id = getTokenId(network, sdkToken)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,10 @@ dependencies {
|
|||
|
||||
// region Core modules
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.utils)
|
||||
// endregion
|
||||
|
||||
implementation(projects.core.utils)
|
||||
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.legacy)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
package com.tangem.data.feedback
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.Build
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.data.feedback.converters.BlockchainInfoConverter
|
||||
import com.tangem.data.feedback.converters.CardInfoConverter
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.BlockchainInfo
|
||||
import com.tangem.domain.feedback.models.PhoneInfo
|
||||
import com.tangem.domain.feedback.models.UserWalletsInfo
|
||||
import com.tangem.domain.feedback.models.*
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import timber.log.Timber
|
||||
|
|
@ -27,7 +24,8 @@ import java.io.File
|
|||
* @property appLogsStore app logs store
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
* @property walletManagersStore wallet managers store
|
||||
* @property context context for getting app version
|
||||
* @property emailSender email sender
|
||||
* @property appVersionProvider app version provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -35,7 +33,8 @@ internal class DefaultFeedbackRepository(
|
|||
private val appLogsStore: AppLogsStore,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val context: Context,
|
||||
private val emailSender: EmailSender,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
) : FeedbackRepository {
|
||||
|
||||
private val blockchainsErrors = MutableStateFlow<Map<UserWalletId, BlockchainErrorInfo>>(emptyMap())
|
||||
|
|
@ -73,7 +72,7 @@ internal class DefaultFeedbackRepository(
|
|||
return PhoneInfo(
|
||||
phoneModel = Build.MODEL,
|
||||
osVersion = Build.VERSION.SDK_INT.toString(),
|
||||
appVersion = getAppVersion(),
|
||||
appVersion = appVersionProvider.versionName,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -95,14 +94,14 @@ internal class DefaultFeedbackRepository(
|
|||
|
||||
override fun getLogFile(): File? = appLogsStore.getFile()
|
||||
|
||||
private fun getAppVersion(): String {
|
||||
return runCatching { context.packageManager.getPackageInfo(context.packageName, 0) }
|
||||
.fold(
|
||||
onSuccess = PackageInfo::versionName,
|
||||
onFailure = {
|
||||
Timber.e(it)
|
||||
"x.y.z"
|
||||
},
|
||||
)
|
||||
override fun sendEmail(feedbackEmail: FeedbackEmail) {
|
||||
emailSender.send(
|
||||
EmailSender.Email(
|
||||
address = feedbackEmail.address,
|
||||
subject = feedbackEmail.subject,
|
||||
message = feedbackEmail.message,
|
||||
attachment = feedbackEmail.file,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.data.feedback.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.data.feedback.DefaultFeedbackRepository
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -23,13 +23,15 @@ internal object FeedbackRepositoryModule {
|
|||
appLogsStore: AppLogsStore,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
walletManagersStore: WalletManagersStore,
|
||||
@ApplicationContext context: Context,
|
||||
emailSender: EmailSender,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
): FeedbackRepository {
|
||||
return DefaultFeedbackRepository(
|
||||
appLogsStore = appLogsStore,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
walletManagersStore = walletManagersStore,
|
||||
context = context,
|
||||
emailSender = emailSender,
|
||||
appVersionProvider = appVersionProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,10 +18,8 @@ import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
|||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.extensions.canHandleBlockchain
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.managetokens.model.AddCustomTokenForm
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
|
|
@ -65,9 +63,12 @@ internal class DefaultCustomTokensRepository(
|
|||
contractAddress: String?,
|
||||
): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
val storedCurrencies: UserTokensResponse = appPreferencesStore.getObjectSyncOrNull(
|
||||
val storedCurrencies = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||
) ?: error("User tokens not found")
|
||||
)
|
||||
requireNotNull(storedCurrencies) {
|
||||
"User tokens not found for user wallet [$userWalletId] while checking if currency is not added"
|
||||
}
|
||||
|
||||
storedCurrencies.tokens.none { token ->
|
||||
Blockchain.fromId(networkId.value).toNetworkId() == token.networkId &&
|
||||
|
|
@ -83,9 +84,12 @@ internal class DefaultCustomTokensRepository(
|
|||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Token? = withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId)
|
||||
?: error("User wallet not found")
|
||||
val network = getNetwork(networkId, derivationPath)
|
||||
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"User wallet [$userWalletId] not found while finding token"
|
||||
}
|
||||
val network = requireNotNull(getNetwork(networkId, derivationPath, userWallet.scanResponse)) {
|
||||
"Network [$networkId] not found while finding token"
|
||||
}
|
||||
val tokenAddress = tokenAddressConverter.convertTokenAddress(
|
||||
networkId,
|
||||
contractAddress,
|
||||
|
|
@ -125,13 +129,22 @@ internal class DefaultCustomTokensRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override fun createCoin(networkId: Network.ID, derivationPath: Network.DerivationPath): CryptoCurrency.Coin {
|
||||
val network = getNetwork(networkId, derivationPath)
|
||||
override suspend fun createCoin(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Coin {
|
||||
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"User wallet [$userWalletId] not found while creating coin"
|
||||
}
|
||||
val network = requireNotNull(getNetwork(networkId, derivationPath, userWallet.scanResponse)) {
|
||||
"Network [$networkId] not found while creating coin"
|
||||
}
|
||||
|
||||
return cryptoCurrencyFactory.createCoin(network)
|
||||
}
|
||||
|
||||
override fun createToken(
|
||||
override suspend fun createToken(
|
||||
managedCryptoCurrency: ManagedCryptoCurrency.Token,
|
||||
sourceNetwork: ManagedCryptoCurrency.SourceNetwork.Default,
|
||||
rawId: String?,
|
||||
|
|
@ -147,11 +160,17 @@ internal class DefaultCustomTokensRepository(
|
|||
}
|
||||
|
||||
override suspend fun createCustomToken(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
formValues: AddCustomTokenForm.Validated.All,
|
||||
): CryptoCurrency.Token {
|
||||
val network = getNetwork(networkId, derivationPath)
|
||||
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"User wallet [$userWalletId] not found while creating custom token"
|
||||
}
|
||||
val network = requireNotNull(getNetwork(networkId, derivationPath, userWallet.scanResponse)) {
|
||||
"Network [$networkId] not found while creating custom token"
|
||||
}
|
||||
val tokenAddress = tokenAddressConverter.convertTokenAddress(
|
||||
networkId,
|
||||
formValues.contractAddress,
|
||||
|
|
@ -171,7 +190,11 @@ internal class DefaultCustomTokensRepository(
|
|||
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) =
|
||||
withContext(dispatchers.io) {
|
||||
val cryptoCurrency = when (currency) {
|
||||
is ManagedCryptoCurrency.Custom.Coin -> createCoin(currency.network.id, currency.network.derivationPath)
|
||||
is ManagedCryptoCurrency.Custom.Coin -> createCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = currency.network.id,
|
||||
derivationPath = currency.network.derivationPath,
|
||||
)
|
||||
is ManagedCryptoCurrency.Custom.Token -> cryptoCurrencyFactory.createToken(
|
||||
network = currency.network,
|
||||
rawId = currency.currencyId.rawCurrencyId,
|
||||
|
|
@ -181,15 +204,17 @@ internal class DefaultCustomTokensRepository(
|
|||
contractAddress = currency.contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
val savedCurrencies = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = { "Saved tokens empty. Can not perform remove currency action" },
|
||||
val storedCurrencies = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||
)
|
||||
requireNotNull(storedCurrencies) {
|
||||
"User tokens not found for user wallet [$userWalletId] while removing currency"
|
||||
}
|
||||
|
||||
val token = userTokensResponseFactory.createResponseToken(cryptoCurrency)
|
||||
storeAndPushTokens(
|
||||
userWalletId = userWalletId,
|
||||
response = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token }),
|
||||
response = storedCurrencies.copy(tokens = storedCurrencies.tokens.filterNot { it == token }),
|
||||
)
|
||||
when (cryptoCurrency) {
|
||||
is CryptoCurrency.Coin -> walletManagersFacade.remove(userWalletId, setOf(cryptoCurrency.network))
|
||||
|
|
@ -198,8 +223,9 @@ internal class DefaultCustomTokensRepository(
|
|||
}
|
||||
|
||||
override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> = withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId)
|
||||
?: error("User wallet not found")
|
||||
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"User wallet [$userWalletId] not found while getting supported networks"
|
||||
}
|
||||
val scanResponse = userWallet.scanResponse
|
||||
|
||||
Blockchain.entries
|
||||
|
|
@ -208,9 +234,7 @@ internal class DefaultCustomTokensRepository(
|
|||
getNetwork(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)?.copy(
|
||||
canHandleTokens = scanResponse.card.canHandleToken(blockchain, scanResponse.cardTypesResolver),
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
@ -238,13 +262,7 @@ internal class DefaultCustomTokensRepository(
|
|||
|
||||
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
|
||||
Timber.e(it, "Unable to save user tokens for: ${userWalletId.stringValue}")
|
||||
Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getSavedUserTokensResponseSync(key: UserWalletId): UserTokensResponse? {
|
||||
return appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(key.stringValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,10 @@ import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.testnet.TestnetTokensStorage
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.canHandleBlockchain
|
||||
|
|
@ -23,7 +23,6 @@ import com.tangem.domain.common.extensions.canHandleToken
|
|||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.supportedTokens
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.managetokens.model.*
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
|
|
@ -119,13 +118,13 @@ internal class DefaultManageTokensRepository(
|
|||
managedCryptoCurrencyFactory.createWithCustomTokens(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
} else {
|
||||
managedCryptoCurrencyFactory.create(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider,
|
||||
scanResponse = userWallet?.scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +154,7 @@ internal class DefaultManageTokensRepository(
|
|||
testnetTokensConfig
|
||||
},
|
||||
tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId),
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
|
||||
return BatchFetchResult.Success(
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.tangem.data.managetokens.DefaultCustomTokensRepository
|
|||
import com.tangem.data.managetokens.DefaultManageTokensRepository
|
||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.testnet.TestnetTokensStorage
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
|
|
|
|||
|
|
@ -12,10 +12,14 @@ import com.tangem.data.common.currency.getNetwork
|
|||
import com.tangem.data.common.currency.getTokenId
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
|
||||
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
||||
internal class ManagedCryptoCurrencyFactory {
|
||||
|
|
@ -23,20 +27,20 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
fun create(
|
||||
coinsResponse: CoinsResponse,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
derivationStyleProvider: DerivationStyleProvider?,
|
||||
scanResponse: ScanResponse?,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
return coinsResponse.coins.mapNotNull { coin ->
|
||||
createToken(coin, tokensResponse, coinsResponse.imageHost, derivationStyleProvider)
|
||||
createToken(coin, tokensResponse, coinsResponse.imageHost, scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
fun createWithCustomTokens(
|
||||
coinsResponse: CoinsResponse,
|
||||
tokensResponse: UserTokensResponse,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
val customTokens = createCustomTokens(tokensResponse, derivationStyleProvider)
|
||||
val tokens = create(coinsResponse, tokensResponse, derivationStyleProvider)
|
||||
val customTokens = createCustomTokens(tokensResponse, scanResponse)
|
||||
val tokens = create(coinsResponse, tokensResponse, scanResponse)
|
||||
|
||||
return customTokens + tokens
|
||||
}
|
||||
|
|
@ -44,10 +48,10 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
fun createTestnetWithCustomTokens(
|
||||
testnetTokensConfig: TestnetTokensConfig,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
val customTokens = tokensResponse
|
||||
?.let { createCustomTokens(it, derivationStyleProvider) }
|
||||
?.let { createCustomTokens(it, scanResponse) }
|
||||
?: emptyList()
|
||||
val testnetTokens = testnetTokensConfig.tokens.map { testnetToken ->
|
||||
ManagedCryptoCurrency.Token(
|
||||
|
|
@ -60,10 +64,10 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
networkId = network.id,
|
||||
contractAddress = network.address,
|
||||
decimals = network.decimalCount ?: return@mapNotNull null,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
} ?: emptyList(),
|
||||
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, derivationStyleProvider),
|
||||
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, scanResponse),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -72,29 +76,29 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
|
||||
private fun createCustomTokens(
|
||||
tokensResponse: UserTokensResponse,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): List<ManagedCryptoCurrency> = tokensResponse.tokens
|
||||
.mapNotNull { token ->
|
||||
maybeCreateCustomToken(token, null, derivationStyleProvider)
|
||||
maybeCreateCustomToken(token, null, scanResponse)
|
||||
}
|
||||
|
||||
private fun maybeCreateCustomToken(
|
||||
token: UserTokensResponse.Token,
|
||||
imageHost: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
scanResponse: ScanResponse,
|
||||
): ManagedCryptoCurrency? {
|
||||
val blockchain = Blockchain.fromNetworkId(token.networkId)
|
||||
?.takeIf { it.isSupportedInApp() }
|
||||
?: return null
|
||||
|
||||
if (!checkIsCustomToken(token, blockchain, derivationStyleProvider)) {
|
||||
if (!checkIsCustomToken(token, blockchain, scanResponse.derivationStyleProvider)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val network = getNetwork(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = token.derivationPath,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
) ?: return null
|
||||
val contractAddress = token.contractAddress
|
||||
|
||||
|
|
@ -123,7 +127,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
coinResponse: CoinsResponse.Coin,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
imageHost: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider?,
|
||||
scanResponse: ScanResponse?,
|
||||
): ManagedCryptoCurrency? {
|
||||
if (coinResponse.networks.isEmpty() || !coinResponse.active) return null
|
||||
|
||||
|
|
@ -134,7 +138,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
networkId = network.networkId,
|
||||
contractAddress = network.contractAddress,
|
||||
decimals = network.decimalCount?.toInt(),
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
.ifEmpty { return null }
|
||||
|
|
@ -145,7 +149,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
symbol = coinResponse.symbol,
|
||||
iconUrl = getIconUrl(coinResponse.id, imageHost),
|
||||
availableNetworks = availableNetworks,
|
||||
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, derivationStyleProvider),
|
||||
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, scanResponse),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -153,14 +157,21 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
networkId: String,
|
||||
contractAddress: String?,
|
||||
decimals: Int?,
|
||||
derivationStyleProvider: DerivationStyleProvider?,
|
||||
scanResponse: ScanResponse?,
|
||||
extraDerivationPath: String? = null,
|
||||
): SourceNetwork? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
?.takeIf { it.isSupportedInApp() }
|
||||
?: return null
|
||||
|
||||
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
|
||||
val network = getNetwork(
|
||||
blockchain,
|
||||
extraDerivationPath,
|
||||
scanResponse?.derivationStyleProvider,
|
||||
canHandleTokens = scanResponse?.let {
|
||||
it.card.canHandleToken(blockchain, it.cardTypesResolver)
|
||||
} ?: true,
|
||||
) ?: return null
|
||||
|
||||
return when {
|
||||
contractAddress.isNullOrBlank() -> SourceNetwork.Main(
|
||||
|
|
@ -180,7 +191,7 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
private fun findAddedInNetworks(
|
||||
currencyId: String,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
derivationStyleProvider: DerivationStyleProvider?,
|
||||
scanResponse: ScanResponse?,
|
||||
): Set<Network> {
|
||||
if (tokensResponse == null) return emptySet()
|
||||
|
||||
|
|
@ -191,9 +202,12 @@ internal class ManagedCryptoCurrencyFactory {
|
|||
|
||||
if (blockchain != null && blockchain.isSupportedInApp()) {
|
||||
getNetwork(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = token.derivationPath,
|
||||
derivationStyleProvider = derivationStyleProvider,
|
||||
blockchain,
|
||||
token.derivationPath,
|
||||
scanResponse?.derivationStyleProvider,
|
||||
canHandleTokens = scanResponse?.let {
|
||||
it.card.canHandleToken(blockchain, it.cardTypesResolver)
|
||||
} ?: true,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -245,13 +244,13 @@ internal class DefaultMarketsTokenRepository(
|
|||
CryptoCurrencyFactory().createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
} else {
|
||||
val currencyNetwork = getNetwork(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
) ?: return null
|
||||
|
||||
CryptoCurrencyFactory().createToken(
|
||||
|
|
@ -265,6 +264,14 @@ internal class DefaultMarketsTokenRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getTokenExchanges(tokenId: String): List<TokenMarketExchange> {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow()
|
||||
|
||||
TokenMarketExchangeConverter.convertList(input = response.exchanges)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> catchApiErrorAndSendEvent(errorEvent: MarketsDataAnalyticsEvent, block: () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.data.markets.converters
|
||||
|
||||
import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse
|
||||
import com.tangem.domain.markets.TokenMarketExchange
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [TokenMarketExchangesResponse.Exchange] to [TokenMarketExchange]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object TokenMarketExchangeConverter : Converter<TokenMarketExchangesResponse.Exchange, TokenMarketExchange> {
|
||||
|
||||
private val RISKY_INTERVAL = 0..3
|
||||
private val CAUTION_INTERVAL = 4..7
|
||||
|
||||
override fun convert(value: TokenMarketExchangesResponse.Exchange): TokenMarketExchange {
|
||||
return with(value) {
|
||||
TokenMarketExchange(
|
||||
id = id,
|
||||
name = name,
|
||||
imageUrl = imageUrl,
|
||||
isCentralized = isCentralized,
|
||||
volumeInUsd = volumeInUsd,
|
||||
trustScore = when (trustScore) {
|
||||
null,
|
||||
in RISKY_INTERVAL,
|
||||
-> TokenMarketExchange.TrustScore.Risky
|
||||
in CAUTION_INTERVAL -> TokenMarketExchange.TrustScore.Caution
|
||||
else -> TokenMarketExchange.TrustScore.Trusted
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
|
|||
metrics = metrics?.convert(),
|
||||
links = links?.convert(),
|
||||
pricePerformance = pricePerformance?.convert(),
|
||||
exchangesAmount = exchangesAmount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
data/onramp/.gitignore
vendored
Normal file
1
data/onramp/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
50
data/onramp/build.gradle.kts
Normal file
50
data/onramp/build.gradle.kts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.onramp"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.onramp)
|
||||
|
||||
// region DI
|
||||
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Others dependencies
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.tangem.data.onramp.converters.error.OnrampErrorConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
|
||||
internal class DefaultOnrampErrorResolver(
|
||||
private val onrampErrorConverter: OnrampErrorConverter,
|
||||
) : OnrampErrorResolver {
|
||||
|
||||
override fun resolve(throwable: Throwable): OnrampError {
|
||||
return if (throwable is ApiResponseError.HttpException) {
|
||||
onrampErrorConverter.convert(throwable.errorBody.orEmpty())
|
||||
} else {
|
||||
OnrampError.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.tangem.data.onramp.converters.CountryConverter
|
||||
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultOnrampRepository(
|
||||
private val onrampApi: OnrampApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : OnrampRepository {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
private val countryConverter = CountryConverter()
|
||||
|
||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||
onrampApi.getCurrencies()
|
||||
.getOrThrow()
|
||||
.map(currencyConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun getCountries(): List<OnrampCountry> = withContext(dispatchers.io) {
|
||||
onrampApi.getCountries()
|
||||
.getOrThrow()
|
||||
.map(countryConverter::convert)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.data.onramp.converters
|
||||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class CountryConverter : Converter<OnrampCountryDTO, OnrampCountry> {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
|
||||
override fun convert(value: OnrampCountryDTO) = OnrampCountry(
|
||||
name = value.name,
|
||||
code = value.code,
|
||||
image = value.image,
|
||||
alpha3 = value.alpha3,
|
||||
continent = value.continent,
|
||||
defaultCurrency = currencyConverter.convert(value.defaultCurrency),
|
||||
onrampAvailable = value.onrampAvailable,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.data.onramp.converters
|
||||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency> {
|
||||
|
||||
override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency(
|
||||
name = value.name,
|
||||
code = value.code,
|
||||
image = value.image,
|
||||
precision = value.precision,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.data.onramp.converters.error
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class OnrampErrorConverter : Converter<String, OnrampError> {
|
||||
|
||||
override fun convert(value: String): OnrampError {
|
||||
return OnrampError.UnknownError
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.data.onramp.di
|
||||
|
||||
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
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 OnrampDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampRepository(onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider): OnrampRepository {
|
||||
return DefaultOnrampRepository(
|
||||
onrampApi = onrampApi,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,5 +31,6 @@ dependencies {
|
|||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,31 @@
|
|||
package com.tangem.data.settings
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.GeoResponse
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
||||
internal class DefaultSettingsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val appLogsStore: AppLogsStore,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SettingsRepository {
|
||||
|
||||
private val userCountryFlow = MutableStateFlow<UserCountry?>(value = null)
|
||||
|
||||
override suspend fun shouldShowSaveUserWalletScreen(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY,
|
||||
|
|
@ -99,4 +113,38 @@ internal class DefaultSettingsRepository(
|
|||
override suspend fun setMarketsTooltipShown(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY, value = !value)
|
||||
}
|
||||
|
||||
override suspend fun getUserCountryCodeSync(): UserCountry? {
|
||||
// If user country code is already set, return it
|
||||
val countryCode = userCountryFlow.value
|
||||
if (countryCode != null) return countryCode
|
||||
|
||||
coroutineScope {
|
||||
launch { fetchUserCountryCode() }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun fetchUserCountryCode() {
|
||||
Timber.i("Start fetching user country code")
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
val country = runCatching { tangemTechApi.getUserCountryCode() }
|
||||
.fold(
|
||||
onSuccess = GeoResponse::code,
|
||||
onFailure = { Locale.getDefault().country },
|
||||
)
|
||||
.lowercase()
|
||||
|
||||
val code = when (country) {
|
||||
UserCountry.Russia.code -> UserCountry.Russia
|
||||
else -> UserCountry.Other(code = country)
|
||||
}
|
||||
|
||||
Timber.i("User code country is $code")
|
||||
|
||||
userCountryFlow.value = code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import com.tangem.data.settings.DefaultAppRatingRepository
|
|||
import com.tangem.data.settings.DefaultPermissionRepository
|
||||
import com.tangem.data.settings.DefaultPromoSettingsRepository
|
||||
import com.tangem.data.settings.DefaultSettingsRepository
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.settings.repositories.AppRatingRepository
|
||||
import com.tangem.domain.settings.repositories.PermissionRepository
|
||||
import com.tangem.domain.settings.repositories.PromoSettingsRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -25,8 +27,15 @@ internal object SettingsDataModule {
|
|||
fun provideSettingsRepository(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
appLogsStore: AppLogsStore,
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SettingsRepository {
|
||||
return DefaultSettingsRepository(appPreferencesStore = appPreferencesStore, appLogsStore = appLogsStore)
|
||||
return DefaultSettingsRepository(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
appLogsStore = appLogsStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import android.util.Base64
|
|||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.catch
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.blockchainsdk.utils.toMigratedCointId
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toCompressedPublicKey
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
|
|
@ -56,6 +58,7 @@ import com.tangem.utils.extensions.orZero
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultStakingRepository(
|
||||
|
|
@ -110,8 +113,8 @@ internal class DefaultStakingRepository(
|
|||
rawNetworkId.plus(rawCurrencyId)
|
||||
}
|
||||
|
||||
override fun isStakingSupported(integrationKey: String): Boolean {
|
||||
return integrationIdMap.containsKey(integrationKey)
|
||||
override fun getSupportedIntegrationId(cryptoCurrencyId: CryptoCurrency.ID): String? {
|
||||
return integrationIdMap.getOrDefault(getIntegrationKey(cryptoCurrencyId), null)
|
||||
}
|
||||
|
||||
override suspend fun fetchEnabledYields(refresh: Boolean) {
|
||||
|
|
@ -120,10 +123,10 @@ internal class DefaultStakingRepository(
|
|||
key = YIELDS_STORE_KEY,
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val stakingTokensWithYields = stakeKitApi.getMultipleYields(preferredValidatorsOnly = false)
|
||||
val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)
|
||||
.getOrThrow()
|
||||
|
||||
stakingYieldsStore.store(stakingTokensWithYields.data)
|
||||
stakingYieldsStore.store(stakingTokensWithYields.data.filter { it.isAvailable })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -131,11 +134,10 @@ internal class DefaultStakingRepository(
|
|||
|
||||
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,
|
||||
yields = getEnabledYields(),
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = symbol,
|
||||
)
|
||||
|
|
@ -156,7 +158,7 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getStakingAvailability(
|
||||
override fun getStakingAvailability(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): StakingAvailability {
|
||||
|
|
@ -164,21 +166,22 @@ internal class DefaultStakingRepository(
|
|||
|
||||
val rawCurrencyId = cryptoCurrency.id.rawCurrencyId ?: return StakingAvailability.Unavailable
|
||||
|
||||
return withContext(dispatchers.io) {
|
||||
val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
|
||||
val isSupportedInMobileApp = getSupportedIntegrationId(cryptoCurrency.id).isNullOrEmpty().not()
|
||||
|
||||
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, cryptoCurrency.symbol)
|
||||
val isSupported = isStakingSupported(getIntegrationKey(cryptoCurrency.id))
|
||||
val prefetchedYield = findPrefetchedYield(
|
||||
yields = getEnabledYields(),
|
||||
currencyId = rawCurrencyId,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
||||
when {
|
||||
prefetchedYield != null && isSupported -> {
|
||||
StakingAvailability.Available(prefetchedYield.id)
|
||||
}
|
||||
prefetchedYield == null && isSupported -> {
|
||||
StakingAvailability.TemporaryDisabled
|
||||
}
|
||||
else -> StakingAvailability.Unavailable
|
||||
return when {
|
||||
prefetchedYield != null && isSupportedInMobileApp -> {
|
||||
StakingAvailability.Available(prefetchedYield.id)
|
||||
}
|
||||
prefetchedYield == null && isSupportedInMobileApp -> {
|
||||
StakingAvailability.TemporaryUnavailable
|
||||
}
|
||||
else -> StakingAvailability.Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,6 +267,7 @@ internal class DefaultStakingRepository(
|
|||
override suspend fun constructTransaction(
|
||||
networkId: String,
|
||||
fee: Fee,
|
||||
amount: Amount,
|
||||
transactionId: String,
|
||||
): Pair<StakingTransaction, TransactionData.Compiled> {
|
||||
return withContext(dispatchers.io) {
|
||||
|
|
@ -277,6 +281,7 @@ internal class DefaultStakingRepository(
|
|||
val transactionData = TransactionData.Compiled(
|
||||
value = getTransactionDataType(networkId, unsignedTransaction),
|
||||
fee = fee,
|
||||
amount = amount,
|
||||
status = TransactionStatus.Unconfirmed,
|
||||
)
|
||||
|
||||
|
|
@ -300,7 +305,10 @@ internal class DefaultStakingRepository(
|
|||
|
||||
if (integrationId == null || address.isNullOrBlank()) {
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
error("IntegrationId or address is null")
|
||||
Timber.w(
|
||||
"IntegrationId or address is null fetching ${cryptoCurrency.name} staking balance",
|
||||
)
|
||||
return@invokeOnExpire
|
||||
}
|
||||
|
||||
val requestBody = getBalanceRequestData(address, integrationId)
|
||||
|
|
@ -335,8 +343,13 @@ internal class DefaultStakingRepository(
|
|||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
stakingBalanceStore.get(userWalletId, address, integrationId)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest {
|
||||
send(yieldBalanceConverter.convert(it))
|
||||
if (it != null) {
|
||||
send(yieldBalanceConverter.convert(it))
|
||||
} else {
|
||||
error("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -384,12 +397,13 @@ internal class DefaultStakingRepository(
|
|||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val yields = getEnabledYields()
|
||||
val availableCurrencies = cryptoCurrencies
|
||||
.mapNotNull { currency ->
|
||||
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
|
||||
|
||||
if (integrationId != null) {
|
||||
if (integrationId != null && yields.any { it.id == integrationId }) {
|
||||
addresses to integrationId
|
||||
} else {
|
||||
null
|
||||
|
|
@ -537,9 +551,9 @@ internal class DefaultStakingRepository(
|
|||
|
||||
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval {
|
||||
return when (getIntegrationKey(cryptoCurrency.id)) {
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() -> {
|
||||
StakingApproval.Needed(ETHEREUM_POLYGON_APPROVE_SPENDER)
|
||||
}
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId(),
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCointId(),
|
||||
-> StakingApproval.Needed(ETHEREUM_POLYGON_APPROVE_SPENDER)
|
||||
else -> StakingApproval.Empty
|
||||
}
|
||||
}
|
||||
|
|
@ -563,12 +577,15 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
|
||||
private fun findPrefetchedYield(yields: List<Yield>, currencyId: String, symbol: String): Yield? {
|
||||
return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
|
||||
return yields.find { yield ->
|
||||
yield.tokens.any { it.coinGeckoId == currencyId && it.symbol == symbol }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEnabledYields(): List<Yield>? {
|
||||
val yields = stakingYieldsStore.getSyncOrNull() ?: return null
|
||||
return yields.map { yieldConverter.convert(it) }
|
||||
private fun getEnabledYields(): List<Yield> {
|
||||
return stakingYieldsStore
|
||||
.get()
|
||||
.map { yieldConverter.convert(it) }
|
||||
}
|
||||
|
||||
private fun getBalanceRequestData(address: String, integrationId: String): YieldBalanceRequestBody {
|
||||
|
|
@ -621,8 +638,9 @@ internal class DefaultStakingRepository(
|
|||
Blockchain.Solana.run { id + toCoinId() } to SOLANA_INTEGRATION_ID,
|
||||
Blockchain.Cosmos.run { id + toCoinId() } to COSMOS_INTEGRATION_ID,
|
||||
Blockchain.Tron.run { id + toCoinId() } to TRON_INTEGRATION_ID,
|
||||
// Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID,
|
||||
// Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID,
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID,
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCointId() to ETHEREUM_POLYGON_INTEGRATION_ID,
|
||||
Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID,
|
||||
// Blockchain.Polkadot.run { id + toCoinId() } to POLKADOT_INTEGRATION_ID,
|
||||
// Blockchain.Avalanche.run { id + toCoinId() } to AVALANCHE_INTEGRATION_ID,
|
||||
// Blockchain.Cronos.run { id + toCoinId() } to CRONOS_INTEGRATION_ID,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ internal object PendingTransactionItemConverter : Converter<PendingTransaction,
|
|||
override fun convert(value: PendingTransaction): BalanceItem? {
|
||||
return BalanceItem(
|
||||
groupId = value.groupId ?: return null,
|
||||
token = value.token,
|
||||
type = value.type,
|
||||
amount = value.amount,
|
||||
rawCurrencyId = value.rawCurrencyId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO.BalanceTypeDTO
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class BalanceTypeConverter : Converter<BalanceTypeDTO, BalanceType> {
|
||||
|
||||
override fun convert(value: BalanceTypeDTO): BalanceType {
|
||||
return when (value) {
|
||||
BalanceTypeDTO.AVAILABLE -> BalanceType.AVAILABLE
|
||||
BalanceTypeDTO.STAKED -> BalanceType.STAKED
|
||||
BalanceTypeDTO.PREPARING -> BalanceType.PREPARING
|
||||
BalanceTypeDTO.LOCKED -> BalanceType.LOCKED
|
||||
BalanceTypeDTO.UNSTAKING -> BalanceType.UNSTAKING
|
||||
BalanceTypeDTO.UNLOCKING -> BalanceType.UNLOCKING
|
||||
BalanceTypeDTO.UNSTAKED -> BalanceType.UNSTAKED
|
||||
BalanceTypeDTO.REWARDS -> BalanceType.REWARDS
|
||||
BalanceTypeDTO.UNKNOWN -> BalanceType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,17 @@ package com.tangem.data.staking.converters
|
|||
|
||||
import com.tangem.data.staking.converters.action.PendingActionConverter
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceItem
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBalance> {
|
||||
|
||||
private val pendingActionConverter by lazy(LazyThreadSafetyMode.NONE) { PendingActionConverter() }
|
||||
|
||||
private val networkTypeConverter by lazy(LazyThreadSafetyMode.NONE) { StakingNetworkTypeConverter() }
|
||||
private val tokenConverter by lazy(LazyThreadSafetyMode.NONE) { TokenConverter(networkTypeConverter) }
|
||||
private val balanceTypeConverter by lazy(LazyThreadSafetyMode.NONE) { BalanceTypeConverter() }
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance {
|
||||
return if (value.balances.isEmpty()) {
|
||||
YieldBalance.Empty
|
||||
|
|
@ -19,7 +23,8 @@ internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBa
|
|||
items = value.balances.map { item ->
|
||||
BalanceItem(
|
||||
groupId = item.groupId,
|
||||
type = BalanceType.valueOf(item.type.name),
|
||||
token = tokenConverter.convert(item.tokenDTO),
|
||||
type = balanceTypeConverter.convert(item.type),
|
||||
amount = item.amount,
|
||||
rawCurrencyId = item.tokenDTO.coinGeckoId,
|
||||
// tron-specific. operates validatorAddresses instead of validatorAddress
|
||||
|
|
|
|||
|
|
@ -93,29 +93,6 @@ internal object TokensDataModule {
|
|||
return DefaultMarketCryptoCurrencyRepository(expressAssetsStore, coroutineDispatcherProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesTokensListRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
quotesRepository: QuotesRepository,
|
||||
): TokensListRepository {
|
||||
return DefaultTokensListRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
quotesRepository = quotesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCardNetworksRepository(
|
||||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): NetworksCompatibilityRepository {
|
||||
return DefaultNetworksCompatibilityRepository(userWalletsStore = userWalletsStore, dispatchers = dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
|||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.lce.lceFlow
|
||||
|
|
@ -138,7 +136,7 @@ internal class DefaultCurrenciesRepository(
|
|||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = getBlockchain(networkId = it.network.id),
|
||||
extraDerivationPath = it.network.derivationPath.value,
|
||||
derivationStyleProvider = getUserWallet(userWalletId).scanResponse.derivationStyleProvider,
|
||||
scanResponse = getUserWallet(userWalletId).scanResponse,
|
||||
)
|
||||
}
|
||||
.distinct()
|
||||
|
|
@ -376,24 +374,6 @@ internal class DefaultCurrenciesRepository(
|
|||
}.cancellable()
|
||||
}
|
||||
|
||||
override fun getMissedAddressesCryptoCurrencies(userWalletId: UserWalletId): Flow<List<CryptoCurrency>> {
|
||||
return channelFlow {
|
||||
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
val userWallet = getUserWallet(userWalletId = userWalletId)
|
||||
getMultiCurrencyWalletCurrencies(userWallet = userWallet)
|
||||
.map {
|
||||
it.filter { currency ->
|
||||
val blockchain = Blockchain.fromId(id = currency.network.id.value)
|
||||
val derivationPath = currency.network.derivationPath.value
|
||||
|
||||
derivationPath != null && !userWallet.scanResponse.hasDerivation(blockchain, derivationPath)
|
||||
}
|
||||
}
|
||||
.collectLatest(::send)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isSendBlockedByPendingTransactions(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
coinStatus: CryptoCurrencyStatus?,
|
||||
|
|
@ -475,7 +455,7 @@ internal class DefaultCurrenciesRepository(
|
|||
token = token,
|
||||
networkId = networkId,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
) ?: error("Unable to create token")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
package com.tangem.data.tokens.repository
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.extensions.*
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.NetworksCompatibilityRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultNetworksCompatibilityRepository(
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NetworksCompatibilityRepository {
|
||||
|
||||
/**
|
||||
* @return returns true if either the network is not Solana (check is not relevant) or if it is Solana and
|
||||
* UserWallet supports tokens on Solana Network
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override suspend fun areSolanaTokensSupportedIfRelevant(networkId: String, userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
|
||||
val blockchain = getBlockchainOrThrow(networkId)
|
||||
val blockchainsSupportingTokens = scanResponse.card.supportedTokens(scanResponse.cardTypesResolver)
|
||||
blockchain != Blockchain.Solana || blockchainsSupportingTokens.contains(Blockchain.Solana)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override suspend fun areTokensSupportedByNetwork(networkId: String, userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
|
||||
val blockchain = getBlockchainOrThrow(networkId)
|
||||
val blockchainsSupportingTokens = scanResponse.card.supportedTokens(scanResponse.cardTypesResolver)
|
||||
scanResponse.card.canHandleToken(
|
||||
supportedTokens = blockchainsSupportingTokens,
|
||||
blockchain = blockchain,
|
||||
cardTypesResolver = scanResponse.cardTypesResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override suspend fun isNetworkSupported(networkId: String, userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
|
||||
val blockchain = getBlockchainOrThrow(networkId)
|
||||
scanResponse.card.canHandleBlockchain(
|
||||
blockchain = blockchain,
|
||||
cardTypesResolver = scanResponse.cardTypesResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> {
|
||||
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
|
||||
return Blockchain.entries
|
||||
.filter { blockchain ->
|
||||
scanResponse.card.supportedBlockchains(scanResponse.cardTypesResolver).contains(blockchain)
|
||||
}
|
||||
.sortedBy(Blockchain::fullName)
|
||||
.mapNotNull { blockchain ->
|
||||
getNetwork(blockchain, null, scanResponse.derivationStyleProvider)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requiresHardenedDerivationOnly(networkId: String, userWalletId: UserWalletId): Boolean {
|
||||
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return false
|
||||
|
||||
return config.primaryCurve(blockchain) == EllipticCurve.Ed25519Slip0010 &&
|
||||
scanResponse.cardTypesResolver.isWallet2()
|
||||
}
|
||||
|
||||
override fun areTokensSupportedByNetwork(networkId: String): Boolean {
|
||||
return Blockchain.fromNetworkId(networkId)?.canHandleTokens() ?: false
|
||||
}
|
||||
|
||||
private suspend fun getWalletOrThrow(userWalletId: UserWalletId): UserWallet {
|
||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Requested UserWallet not found"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBlockchainOrThrow(networkId: String): Blockchain {
|
||||
return requireNotNull(Blockchain.fromNetworkId(networkId)) {
|
||||
"Requested network not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package com.tangem.data.tokens.repository
|
||||
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.tokens.paging.CoinsPagingSource
|
||||
import com.tangem.data.tokens.utils.FoundTokenConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.tokens.model.FoundToken
|
||||
import com.tangem.domain.tokens.model.Token
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.tokens.repository.TokensListRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Default repository implementation for managing operations related to a complete set of tokens
|
||||
*
|
||||
* @property tangemTechApi Tangem Tech API
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
* @property quotesRepository responsible for providing cryptocurrency quotes data
|
||||
*
|
||||
*/
|
||||
internal class DefaultTokensListRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
) : TokensListRepository {
|
||||
|
||||
override fun getTokens(searchText: String?): Flow<PagingData<Token>> {
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 100,
|
||||
prefetchDistance = 70,
|
||||
enablePlaceholders = false,
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
CoinsPagingSource(
|
||||
api = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
searchText = searchText,
|
||||
quotesRepository = quotesRepository,
|
||||
)
|
||||
},
|
||||
).flow
|
||||
}
|
||||
|
||||
override suspend fun findToken(contractAddress: String, networkId: String): FoundToken? {
|
||||
return withContext(dispatchers.io) {
|
||||
val foundCoin = tangemTechApi.getCoins(
|
||||
contractAddress = contractAddress,
|
||||
networkIds = networkId,
|
||||
).getOrThrow().coins.firstNotNullOfOrNull { coin ->
|
||||
val tokenNetwork = coin.networks.filter { network ->
|
||||
network.contractAddress != null && network.decimalCount != null &&
|
||||
network.contractAddress?.equals(contractAddress, ignoreCase = true) == true &&
|
||||
networkId == network.networkId
|
||||
}
|
||||
if (tokenNetwork.isNotEmpty()) {
|
||||
coin.copy(networks = tokenNetwork)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
foundCoin?.let { FoundTokenConverter.convert(foundCoin) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun validateAddress(contractAddress: String, networkId: String): Boolean {
|
||||
return when (val blockchain = Blockchain.fromNetworkId(networkId) ?: Blockchain.Unknown) {
|
||||
Blockchain.Unknown, Blockchain.Binance, Blockchain.BinanceTestnet -> true
|
||||
else -> blockchain.validateAddress(contractAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -14,7 +13,6 @@ internal class CardCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
|
||||
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
|
||||
val card = scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
|
|
@ -31,20 +29,19 @@ internal class CardCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = cardDerivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = cardDerivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
|
|
@ -53,7 +50,7 @@ internal class CardCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = cardDerivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -61,14 +58,13 @@ internal class CardCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
}
|
||||
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = cardDerivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
|
|
@ -77,7 +73,7 @@ internal class CardCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
|
|||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = cardDerivationStyleProvider,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue