Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-09 14:42:49 +02:00
parent 26b3c3d576
commit 95f66c8798
18 changed files with 89 additions and 64 deletions

View file

@ -8,7 +8,7 @@ import java.util.UUID
class ExpressAuthProviderImpl(
private val userWalletsStore: UserWalletsStore,
private val configManager: ConfigManager
private val configManager: ConfigManager,
) : ExpressAuthProvider, ExpressSessionIdGenerator {
private var uuid = UUID.randomUUID()

View file

@ -31,7 +31,7 @@ class AuthModule {
): ExpressAuthProvider {
return ExpressAuthProviderImpl(
userWalletsStore = userWalletsStore,
configManager = configManager
configManager = configManager,
)
}
}

View file

@ -6,7 +6,6 @@ import com.tangem.datasource.api.express.models.request.PairsRequestBody
import com.tangem.datasource.api.express.models.response.*
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Query
import java.math.BigDecimal
@ -15,12 +14,10 @@ import java.math.BigDecimal
* Interface of Tangem Express API (new swap mechanism)
*/
@Suppress("LongParameterList")
interface ExpressApi {
interface TangemExpressApi {
@POST("assets")
suspend fun getAssets(
@Body body: AssetsRequestBody,
): ApiResponse<List<Asset>>
suspend fun getAssets(@Body body: AssetsRequestBody): ApiResponse<List<Asset>>
@POST("pairs")
suspend fun getPairs(@Body body: PairsRequestBody): ApiResponse<List<SwapPair>>

View file

@ -0,0 +1,5 @@
package com.tangem.datasource.api.express.models
object TangemExpressValues {
const val EMPTY_CONTRACT_ADDRESS_VALUE = "0"
}

View file

@ -3,5 +3,6 @@ package com.tangem.datasource.api.express.models.request
import com.squareup.moshi.Json
data class AssetsRequestBody(
@Json(name = "filter") val filter: List<LeastTokenInfo>?,
@Json(name = "tokensList") val tokensList: List<LeastTokenInfo>?,
@Json(name = "onlyActive") val onlyActive: Boolean,
)

View file

@ -106,7 +106,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
swapReferrerAccount = configValues.swapReferrerAccount,
walletConnectProjectId = configValues.walletConnectProjectId,
tangemComAuthorization = configValues.tangemComAuthorization,
tangemExpressApiKey = configValues.tangemExpressApiKey
tangemExpressApiKey = configValues.tangemExpressApiKey,
)
}
}

View file

@ -19,5 +19,5 @@ data class Config(
val swapReferrerAccount: SwapReferrerAccount? = null,
val walletConnectProjectId: String = "",
val tangemComAuthorization: String? = null,
val tangemExpressApiKey: String = ""
val tangemExpressApiKey: String = "",
)

View file

@ -1,8 +1,8 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.token.DefaultUserMarketCoinsStore
import com.tangem.datasource.local.token.UserMarketCoinsStore
import com.tangem.datasource.local.token.DefaultAssetsStore
import com.tangem.datasource.local.token.AssetsStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -11,11 +11,11 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object MarketCoinsStoreModule {
internal object AssetsStoreModule {
@Provides
@Singleton
fun provideUserMarketCoinsStore(): UserMarketCoinsStore {
return DefaultUserMarketCoinsStore(dataStore = RuntimeDataStore())
fun provideAssetsStore(): AssetsStore {
return DefaultAssetsStore(dataStore = RuntimeDataStore())
}
}

View file

@ -3,10 +3,9 @@ package com.tangem.datasource.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.api.express.ExpressApi
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.promotion.PromotionApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.utils.RequestHeader
import com.tangem.datasource.utils.RequestHeader.*
import com.tangem.datasource.utils.addHeaders
import com.tangem.datasource.utils.addLoggers
@ -33,8 +32,7 @@ class NetworkModule {
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
expressAuthProvider: ExpressAuthProvider,
): ExpressApi {
): TangemExpressApi {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
@ -46,7 +44,7 @@ class NetworkModule {
.build(),
)
.build()
.create(ExpressApi::class.java)
.create(TangemExpressApi::class.java)
}
@Provides

View file

@ -0,0 +1,11 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.domain.wallets.models.UserWalletId
interface AssetsStore {
suspend fun getSyncOrNull(userWalletId: UserWalletId): List<Asset>?
suspend fun store(userWalletId: UserWalletId, item: List<Asset>)
}

View file

@ -1,18 +1,18 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.wallets.models.UserWalletId
internal class DefaultUserMarketCoinsStore(
private val dataStore: StringKeyDataStore<CoinsResponse>,
) : UserMarketCoinsStore {
internal class DefaultAssetsStore(
private val dataStore: StringKeyDataStore<List<Asset>>,
) : AssetsStore {
override suspend fun getSyncOrNull(userWalletId: UserWalletId): CoinsResponse? {
override suspend fun getSyncOrNull(userWalletId: UserWalletId): List<Asset>? {
return dataStore.getSyncOrNull(userWalletId.stringValue)
}
override suspend fun store(userWalletId: UserWalletId, item: CoinsResponse) {
override suspend fun store(userWalletId: UserWalletId, item: List<Asset>) {
dataStore.store(userWalletId.stringValue, item)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.domain.wallets.models.UserWalletId
interface UserMarketCoinsStore {
suspend fun getSyncOrNull(userWalletId: UserWalletId): CoinsResponse?
suspend fun store(userWalletId: UserWalletId, item: CoinsResponse)
}

View file

@ -20,9 +20,9 @@ sealed class RequestHeader(vararg pairs: Pair<String, () -> String>) {
"card_public_key" to { authProvider.getCardPublicKey() },
)
class Express(expressAuthProvider: ExpressAuthProvider): RequestHeader(
class Express(expressAuthProvider: ExpressAuthProvider) : RequestHeader(
"api-key" to { expressAuthProvider.getApiKey() },
"user-id" to { expressAuthProvider.getUserId() },
"session-id" to { expressAuthProvider.getSessionId() }
"session-id" to { expressAuthProvider.getSessionId() },
)
}

View file

@ -5,11 +5,12 @@ import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
import com.tangem.data.tokens.repository.DefaultMarketCryptoCurrencyRepository
import com.tangem.data.tokens.repository.DefaultNetworksRepository
import com.tangem.data.tokens.repository.DefaultQuotesRepository
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.token.UserMarketCoinsStore
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -32,17 +33,19 @@ internal object TokensDataModule {
@Singleton
fun provideCurrenciesRepository(
tangemTechApi: TangemTechApi,
tangemExpressApi: TangemExpressApi,
userTokensStore: UserTokensStore,
userWalletsStore: UserWalletsStore,
userMarketCoinsStore: UserMarketCoinsStore,
assetsStore: AssetsStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): CurrenciesRepository {
return DefaultCurrenciesRepository(
tangemTechApi = tangemTechApi,
tangemExpressApi = tangemExpressApi,
userTokensStore = userTokensStore,
userWalletsStore = userWalletsStore,
userMarketCoinsStore = userMarketCoinsStore,
assetsStore = assetsStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
@ -88,9 +91,7 @@ internal object TokensDataModule {
@Provides
@Singleton
fun provideDefaultMarketCoinsRepository(
userMarketCoinsStore: UserMarketCoinsStore,
): MarketCryptoCurrencyRepository {
return DefaultMarketCryptoCurrencyRepository(userMarketCoinsStore)
fun provideDefaultMarketCoinsRepository(assetsStore: AssetsStore): MarketCryptoCurrencyRepository {
return DefaultMarketCryptoCurrencyRepository(assetsStore)
}
}

View file

@ -5,9 +5,14 @@ import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.*
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
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.UserMarketCoinsStore
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.extensions.toCoinId
@ -27,12 +32,13 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
@Suppress("LargeClass")
@Suppress("LargeClass", "LongParameterList")
internal class DefaultCurrenciesRepository(
private val tangemTechApi: TangemTechApi,
private val tangemExpressApi: TangemExpressApi,
private val userTokensStore: UserTokensStore,
private val userWalletsStore: UserWalletsStore,
private val userMarketCoinsStore: UserMarketCoinsStore,
private val assetsStore: AssetsStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : CurrenciesRepository {
@ -341,14 +347,25 @@ internal class DefaultCurrenciesRepository(
userTokens: UserTokensResponse,
) {
try {
val networkIds = userTokens.tokens
val tokensList = userTokens.tokens
.distinctBy { it.networkId }
.joinToString(separator = ",") { it.networkId }
val response = tangemTechApi.getCoins(networkIds = networkIds, exchangeable = true)
.map {
LeastTokenInfo(
contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE,
network = it.networkId,
)
}
userMarketCoinsStore.store(userWalletId, response)
val response = tangemExpressApi.getAssets(
AssetsRequestBody(
tokensList = tokensList,
onlyActive = true,
),
)
assetsStore.store(userWalletId, response.getOrThrow())
} catch (e: Throwable) {
Timber.e(e, "Unable to fetch user market coins for: ${userWalletId.stringValue}")
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
}
}

View file

@ -1,22 +1,28 @@
package com.tangem.data.tokens.repository
import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.local.token.UserMarketCoinsStore
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.local.token.AssetsStore
import com.tangem.domain.common.extensions.toNetworkId
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 userMarketCoinsStore: UserMarketCoinsStore,
private val assetsStore: AssetsStore,
) : MarketCryptoCurrencyRepository {
override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Boolean {
override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
val cryptoCurrencyId = cryptoCurrency.id
val blockchain = Blockchain.fromId(cryptoCurrencyId.rawNetworkId)
val apiNetworkId = blockchain.toNetworkId()
return userMarketCoinsStore.getSyncOrNull(userWalletId)?.coins
?.firstOrNull { it.id == cryptoCurrencyId.rawCurrencyId }
?.networks
?.firstOrNull { it.networkId == apiNetworkId }?.exchangeable ?: false
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE
return assetsStore.getSyncOrNull(userWalletId)?.find {
it.network == apiNetworkId &&
it.token == cryptoCurrencyId.rawCurrencyId &&
it.contractAddress == contractAddress &&
it.isActive
}?.exchangeAvailable ?: false
}
}

View file

@ -84,7 +84,7 @@ class GetCryptoCurrencyActionsUseCase(
activeList.add(TokenActionsState.ActionState.Receive(true))
// swap
if (marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency.id)) {
if (marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency)) {
activeList.add(TokenActionsState.ActionState.Swap(true))
} else {
disabledList.add(TokenActionsState.ActionState.Swap(false))

View file

@ -8,5 +8,5 @@ import com.tangem.domain.wallets.models.UserWalletId
*/
interface MarketCryptoCurrencyRepository {
suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Boolean
suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean
}