Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-23 13:28:11 +05:00
parent e87d946e40
commit a3a18854c8
48 changed files with 544 additions and 328 deletions

View file

@ -1,68 +1,14 @@
package com.tangem.tap.network.auth package com.tangem.tap.network.auth
import com.tangem.common.CardIdRangeDec
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.userwallet.UserWalletsStore
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import kotlinx.coroutines.runBlocking
import java.util.UUID import java.util.UUID
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
internal class DefaultExpressAuthProvider( internal class DefaultExpressAuthProvider : ExpressAuthProvider {
private val userWalletsStore: UserWalletsStore,
private val appPreferencesStore: AppPreferencesStore,
) : ExpressAuthProvider {
private var uuid = AtomicReference(UUID.randomUUID()) private var uuid = AtomicReference(UUID.randomUUID())
override fun getUserId(): String {
return userWalletsStore.selectedUserWalletOrNull?.walletId?.stringValue ?: error("No user id provided")
}
override fun getSessionId(): String { override fun getSessionId(): String {
return uuid.get().toString() return uuid.get().toString()
} }
override fun getRefCode(): String {
val selectedUserWallet = userWalletsStore.selectedUserWalletOrNull ?: error("Can not get selected user wallet")
return when {
isRing(selectedUserWallet) -> "ring"
isChangeNow(selectedUserWallet) -> "ChangeNow"
isPartner(selectedUserWallet) -> "partner"
else -> ""
}
}
private fun isRing(selectedUserWallet: UserWallet): Boolean {
val addedWalletsWithRings = runBlocking {
appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY,
default = emptySet(),
)
}
return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue)
}
private fun isChangeNow(selectedUserWallet: UserWallet): Boolean {
val changeNowRange = CardIdRangeDec(
start = "AF99001800554008",
end = "AF99001800559994",
)
val card = selectedUserWallet.scanResponse.card
return card.batchId == BATCH_ID_CHANGENOW || changeNowRange?.contains(card.cardId) == true
}
private fun isPartner(selectedUserWallet: UserWallet): Boolean {
return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER
}
private companion object {
const val BATCH_ID_CHANGENOW = "BB000013"
const val BATCH_ID_PARTNER = "AF990015"
}
} }

View file

@ -2,8 +2,6 @@ package com.tangem.tap.network.auth.di
import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider
@ -30,14 +28,8 @@ internal class AuthModule {
@Provides @Provides
@Singleton @Singleton
fun provideExpressAuthProvider( fun provideExpressAuthProvider(): ExpressAuthProvider {
userWalletsStore: UserWalletsStore, return DefaultExpressAuthProvider()
appPreferencesStore: AppPreferencesStore,
): ExpressAuthProvider {
return DefaultExpressAuthProvider(
userWalletsStore = userWalletsStore,
appPreferencesStore = appPreferencesStore,
)
} }
@Provides @Provides

View file

@ -48,10 +48,8 @@ internal class Express(
private fun createHeaders(isProd: Boolean) = buildMap { private fun createHeaders(isProd: Boolean) = buildMap {
put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) }) put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) })
put(key = "user-id", value = ProviderSuspend(expressAuthProvider::getUserId))
put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId)) put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId))
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values) putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values)
put(key = "refcode", value = ProviderSuspend(expressAuthProvider::getRefCode))
} }
private fun getApiKey(isProd: Boolean): String { private fun getApiKey(isProd: Boolean): String {

View file

@ -5,10 +5,7 @@ import com.tangem.datasource.api.express.models.request.AssetsRequestBody
import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody
import com.tangem.datasource.api.express.models.request.PairsRequestBody import com.tangem.datasource.api.express.models.request.PairsRequestBody
import com.tangem.datasource.api.express.models.response.* import com.tangem.datasource.api.express.models.response.*
import retrofit2.http.Body import retrofit2.http.*
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
/** /**
* Interface of Tangem Express API (new swap mechanism) * Interface of Tangem Express API (new swap mechanism)
@ -17,16 +14,29 @@ import retrofit2.http.Query
interface TangemExpressApi { interface TangemExpressApi {
@POST("assets") @POST("assets")
suspend fun getAssets(@Body body: AssetsRequestBody): ApiResponse<List<Asset>> suspend fun getAssets(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Body body: AssetsRequestBody,
): ApiResponse<List<Asset>>
@POST("pairs") @POST("pairs")
suspend fun getPairs(@Body body: PairsRequestBody): ApiResponse<List<SwapPair>> suspend fun getPairs(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Body body: PairsRequestBody,
): ApiResponse<List<SwapPair>>
@GET("providers") @GET("providers")
suspend fun getProviders(): ApiResponse<List<ExchangeProvider>> suspend fun getProviders(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
): ApiResponse<List<ExchangeProvider>>
@GET("exchange-quote") @GET("exchange-quote")
suspend fun getExchangeQuote( suspend fun getExchangeQuote(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Query("fromContractAddress") fromContractAddress: String, @Query("fromContractAddress") fromContractAddress: String,
@Query("fromNetwork") fromNetwork: String, @Query("fromNetwork") fromNetwork: String,
@Query("toContractAddress") toContractAddress: String, @Query("toContractAddress") toContractAddress: String,
@ -40,6 +50,8 @@ interface TangemExpressApi {
@GET("exchange-data") @GET("exchange-data")
suspend fun getExchangeData( suspend fun getExchangeData(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Query("fromContractAddress") fromContractAddress: String, @Query("fromContractAddress") fromContractAddress: String,
@Query("fromNetwork") fromNetwork: String, @Query("fromNetwork") fromNetwork: String,
@Query("toContractAddress") toContractAddress: String, @Query("toContractAddress") toContractAddress: String,
@ -57,8 +69,16 @@ interface TangemExpressApi {
): ApiResponse<ExchangeDataResponse> ): ApiResponse<ExchangeDataResponse>
@GET("exchange-status") @GET("exchange-status")
suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse<ExchangeStatusResponse> suspend fun getExchangeStatus(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Query("txId") txId: String,
): ApiResponse<ExchangeStatusResponse>
@POST("exchange-sent") @POST("exchange-sent")
suspend fun exchangeSent(@Body body: ExchangeSentRequestBody): ApiResponse<ExchangeSentResponseBody> suspend fun exchangeSent(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Body body: ExchangeSentRequestBody,
): ApiResponse<ExchangeSentResponseBody>
} }

View file

@ -3,37 +3,52 @@ package com.tangem.datasource.api.onramp
import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
import retrofit2.http.Body import retrofit2.http.*
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions") @Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
interface OnrampApi { interface OnrampApi {
@GET("currencies") @GET("currencies")
suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>> suspend fun getCurrencies(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
): ApiResponse<List<OnrampCurrencyDTO>>
@GET("countries") @GET("countries")
suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>> suspend fun getCountries(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
): ApiResponse<List<OnrampCountryDTO>>
@GET("country-by-ip") @GET("country-by-ip")
suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO> suspend fun getCountryByIp(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
): ApiResponse<OnrampCountryDTO>
@GET("payment-methods") @GET("payment-methods")
suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>> suspend fun getPaymentMethods(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
): ApiResponse<List<PaymentMethodDTO>>
@POST("onramp-pairs") @POST("onramp-pairs")
suspend fun getPairs(@Body body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>> suspend fun getPairs(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Body body: OnrampPairsRequest,
): ApiResponse<List<OnrampPairDTO>>
@GET("onramp-quote") @GET("onramp-quote")
suspend fun getQuote( suspend fun getQuote(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Query("fromCurrencyCode") fromCurrencyCode: String, @Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("fromPrecision") fromPrecision: Int, @Query("fromPrecision") fromPrecision: Int,
@Query("toContractAddress") toContractAddress: String, @Query("toContractAddress") toContractAddress: String,
@ -47,6 +62,8 @@ interface OnrampApi {
@GET("onramp-data") @GET("onramp-data")
suspend fun getData( suspend fun getData(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Query("fromCurrencyCode") fromCurrencyCode: String, @Query("fromCurrencyCode") fromCurrencyCode: String,
@Query("fromPrecision") fromPrecision: Int, @Query("fromPrecision") fromPrecision: Int,
@Query("toContractAddress") toContractAddress: String, @Query("toContractAddress") toContractAddress: String,
@ -64,5 +81,9 @@ interface OnrampApi {
): ApiResponse<OnrampDataResponse> ): ApiResponse<OnrampDataResponse>
@GET("onramp-status") @GET("onramp-status")
suspend fun getStatus(@Query("txId") txId: String): ApiResponse<OnrampStatusResponse> suspend fun getStatus(
@Header("user-id") userWalletId: String,
@Header("refcode") refCode: String,
@Query("txId") txId: String,
): ApiResponse<OnrampStatusResponse>
} }

View file

@ -5,11 +5,14 @@ import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.request.AssetsRequestBody import com.tangem.datasource.api.express.models.request.AssetsRequestBody
import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.express.models.response.Asset import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.exchangeservice.swap.ExpressUtils.getRefCode
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -33,31 +36,34 @@ typealias InitializationStatusFlow = MutableStateFlow<Lce<Throwable, List<Asset>
internal class DefaultExpressServiceLoader @Inject constructor( internal class DefaultExpressServiceLoader @Inject constructor(
private val tangemExpressApi: TangemExpressApi, private val tangemExpressApi: TangemExpressApi,
private val expressAssetsStore: ExpressAssetsStore, private val expressAssetsStore: ExpressAssetsStore,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : ExpressServiceLoader { ) : ExpressServiceLoader {
private val initializationStatuses = private val initializationStatuses =
MutableStateFlow<Map<UserWalletId, InitializationStatusFlow>>(value = emptyMap()) MutableStateFlow<Map<UserWalletId, InitializationStatusFlow>>(value = emptyMap())
override suspend fun update(userWalletId: UserWalletId, userTokens: List<LeastTokenInfo>) { override suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>) {
withContext(dispatchers.io) { withContext(dispatchers.io) {
val initializationStatus = getInitializationStatusInternal(userWalletId) val initializationStatus = getInitializationStatusInternal(userWallet.walletId)
try { try {
if (userTokens.isNotEmpty()) { if (userTokens.isNotEmpty()) {
val response = tangemExpressApi.getAssets( val response = tangemExpressApi.getAssets(
userWalletId = userWallet.walletId.stringValue,
refCode = getRefCode(userWallet, appPreferencesStore),
body = AssetsRequestBody(tokensList = userTokens), body = AssetsRequestBody(tokensList = userTokens),
).getOrThrow() ).getOrThrow()
expressAssetsStore.store(userWalletId, response) expressAssetsStore.store(userWallet.walletId, response)
initializationStatus.update { response.lceContent() } initializationStatus.update { response.lceContent() }
} }
} catch (e: Throwable) { } catch (e: Throwable) {
if (expressAssetsStore.getSyncOrNull(userWalletId) == null) { if (expressAssetsStore.getSyncOrNull(userWallet.walletId) == null) {
initializationStatus.update { e.lceError() } initializationStatus.update { e.lceError() }
} }
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") Timber.e(e, "Unable to fetch assets for: ${userWallet.walletId.stringValue}")
} }
} }
} }

View file

@ -3,6 +3,7 @@ package com.tangem.datasource.exchangeservice.swap
import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.express.models.response.Asset import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.Lce
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -13,8 +14,8 @@ import kotlinx.coroutines.flow.Flow
*/ */
interface ExpressServiceLoader { interface ExpressServiceLoader {
/** Update service using [userWalletId] and [userTokens] */ /** Update service using [userWallet] and [userTokens] */
suspend fun update(userWalletId: UserWalletId, userTokens: List<LeastTokenInfo>) suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>)
/** Get initialization status by [userWalletId] */ /** Get initialization status by [userWalletId] */
fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>> fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>>

View file

@ -0,0 +1,46 @@
package com.tangem.datasource.exchangeservice.swap
import com.tangem.common.CardIdRangeDec
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.domain.wallets.models.UserWallet
import kotlinx.coroutines.runBlocking
object ExpressUtils {
private const val BATCH_ID_CHANGENOW = "BB000013"
private const val BATCH_ID_PARTNER = "AF990015"
fun getRefCode(userWallet: UserWallet, appPreferencesStore: AppPreferencesStore): String {
return when {
isRing(userWallet, appPreferencesStore) -> "ring"
isChangeNow(userWallet) -> "ChangeNow"
isPartner(userWallet) -> "partner"
else -> ""
}
}
private fun isRing(selectedUserWallet: UserWallet, appPreferencesStore: AppPreferencesStore): Boolean {
val addedWalletsWithRings = runBlocking {
appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY,
default = emptySet(),
)
}
return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue)
}
private fun isChangeNow(selectedUserWallet: UserWallet): Boolean {
val changeNowRange = CardIdRangeDec(
start = "AF99001800554008",
end = "AF99001800559994",
)
val card = selectedUserWallet.scanResponse.card
return card.batchId == BATCH_ID_CHANGENOW || changeNowRange?.contains(card.cardId) == true
}
private fun isPartner(selectedUserWallet: UserWallet): Boolean {
return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER
}
}

View file

@ -50,9 +50,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
@Before @Before
fun setup() { fun setup() {
every { appVersionProvider.versionName } returns VERSION_NAME every { appVersionProvider.versionName } returns VERSION_NAME
every { expressAuthProvider.getUserId() } returns EXPRESS_USER_ID
every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID
every { expressAuthProvider.getRefCode() } returns EXPRESS_REF_CODE
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
every { appAuthProvider.getCardId() } returns APP_CARD_ID every { appAuthProvider.getCardId() } returns APP_CARD_ID
every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
@ -131,9 +129,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
} }
}, },
"user-id" to ProviderSuspend { EXPRESS_USER_ID },
"session-id" to ProviderSuspend { EXPRESS_SESSION_ID }, "session-id" to ProviderSuspend { EXPRESS_SESSION_ID },
"refcode" to ProviderSuspend { EXPRESS_REF_CODE },
"version" to ProviderSuspend { VERSION_NAME }, "version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "android" }, "platform" to ProviderSuspend { "android" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() }, "language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },

View file

@ -24,6 +24,7 @@ import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
@ -43,6 +44,7 @@ import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.tokens.model.Amount import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
@ -82,10 +84,16 @@ internal class DefaultOnrampRepository(
override fun getCurrencies(): Flow<List<OnrampCurrency>> = currenciesStore.get(CURRENCIES_KEY) override fun getCurrencies(): Flow<List<OnrampCurrency>> = currenciesStore.get(CURRENCIES_KEY)
override suspend fun fetchCurrencies() = withContext(dispatchers.io) { override suspend fun fetchCurrencies(userWallet: UserWallet) = withContext(dispatchers.io) {
if (!currenciesStore.getSyncOrNull(CURRENCIES_KEY).isNullOrEmpty()) return@withContext if (!currenciesStore.getSyncOrNull(CURRENCIES_KEY).isNullOrEmpty()) return@withContext
val result = onrampApi.getCurrencies() val result = onrampApi.getCurrencies(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
)
.getOrThrow() .getOrThrow()
.map(currencyConverter::convert) .map(currencyConverter::convert)
@ -98,10 +106,16 @@ internal class DefaultOnrampRepository(
return countriesStore.getSyncOrNull(COUNTRIES_KEY) return countriesStore.getSyncOrNull(COUNTRIES_KEY)
} }
override suspend fun fetchCountries(): List<OnrampCountry> = withContext(dispatchers.io) { override suspend fun fetchCountries(userWallet: UserWallet): List<OnrampCountry> = withContext(dispatchers.io) {
if (!countriesStore.getSyncOrNull(COUNTRIES_KEY).isNullOrEmpty()) return@withContext emptyList() if (!countriesStore.getSyncOrNull(COUNTRIES_KEY).isNullOrEmpty()) return@withContext emptyList()
val result = onrampApi.getCountries() val result = onrampApi.getCountries(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
)
.getOrThrow() .getOrThrow()
.map(countryConverter::convert) .map(countryConverter::convert)
@ -110,14 +124,27 @@ internal class DefaultOnrampRepository(
result result
} }
override suspend fun getCountryByIp(): OnrampCountry = withContext(dispatchers.io) { override suspend fun getCountryByIp(userWallet: UserWallet): OnrampCountry = withContext(dispatchers.io) {
onrampApi.getCountryByIp() onrampApi.getCountryByIp(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
)
.getOrThrow() .getOrThrow()
.let(countryConverter::convert) .let(countryConverter::convert)
} }
override suspend fun getStatus(txId: String): OnrampStatus = withContext(dispatchers.io) { override suspend fun getStatus(userWallet: UserWallet, txId: String): OnrampStatus = withContext(dispatchers.io) {
onrampApi.getStatus(txId) onrampApi.getStatus(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
txId = txId,
)
.getOrThrow() .getOrThrow()
.let(statusConverter::convert) .let(statusConverter::convert)
} }
@ -161,11 +188,19 @@ internal class DefaultOnrampRepository(
.map { it?.let(countryConverter::convert) } .map { it?.let(countryConverter::convert) }
} }
override suspend fun fetchPaymentMethodsIfAbsent() = withContext(dispatchers.io) { override suspend fun fetchPaymentMethodsIfAbsent(userWallet: UserWallet) = withContext(dispatchers.io) {
if (paymentMethodsStore.contains(PAYMENT_METHODS_KEY)) return@withContext if (paymentMethodsStore.contains(PAYMENT_METHODS_KEY)) return@withContext
val response = safeApiCall( val response = safeApiCall(
call = { onrampApi.getPaymentMethods().bind() }, call = {
onrampApi.getPaymentMethods(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
).bind()
},
onError = { onError = {
Timber.w(it, "Unable to fetch onramp payment methods") Timber.w(it, "Unable to fetch onramp payment methods")
throw it throw it
@ -174,100 +209,123 @@ internal class DefaultOnrampRepository(
paymentMethodsStore.store(PAYMENT_METHODS_KEY, response.removeApplePay()) paymentMethodsStore.store(PAYMENT_METHODS_KEY, response.removeApplePay())
} }
override suspend fun fetchPairs(currency: OnrampCurrency, country: OnrampCountry, cryptoCurrency: CryptoCurrency) = override suspend fun fetchPairs(
withContext(dispatchers.io) { userWallet: UserWallet,
val onrampPairs = async { currency: OnrampCurrency,
safeApiCall( country: OnrampCountry,
call = { cryptoCurrency: CryptoCurrency,
onrampApi.getPairs( ) = withContext(dispatchers.io) {
body = OnrampPairsRequest( val onrampPairs = async {
fromCurrencyCode = currency.code, safeApiCall(
countryCode = country.code, call = {
to = listOf( onrampApi.getPairs(
OnrampDestinationDTO( userWalletId = userWallet.walletId.stringValue,
contractAddress = cryptoCurrency.getContractAddress(), refCode = ExpressUtils.getRefCode(
network = cryptoCurrency.network.backendId, userWallet = userWallet,
), appPreferencesStore = appPreferencesStore,
),
body = OnrampPairsRequest(
fromCurrencyCode = currency.code,
countryCode = country.code,
to = listOf(
OnrampDestinationDTO(
contractAddress = cryptoCurrency.getContractAddress(),
network = cryptoCurrency.network.backendId,
), ),
), ),
).bind() ),
}, ).bind()
onError = { },
Timber.w(it, "Unable to fetch onramp pairs") onError = {
throw it Timber.w(it, "Unable to fetch onramp pairs")
}, throw it
) },
} )
val providers = async {
safeApiCall(
call = { expressApi.getProviders().bind() },
onError = {
Timber.w(it, "Unable to fetch express providers")
throw it
},
)
}
storeOnrampPairs(pairs = onrampPairs.await(), providers = providers.await())
} }
val providers = async {
safeApiCall(
call = {
expressApi.getProviders(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
).bind()
},
onError = {
Timber.w(it, "Unable to fetch express providers")
throw it
},
)
}
storeOnrampPairs(pairs = onrampPairs.await(), providers = providers.await())
}
override suspend fun fetchQuotes(cryptoCurrency: CryptoCurrency, amount: Amount) = withContext(dispatchers.io) { override suspend fun fetchQuotes(userWallet: UserWallet, cryptoCurrency: CryptoCurrency, amount: Amount) =
val pairs = requireNotNull(pairsStore.getSyncOrNull(PAIRS_KEY)) { withContext(dispatchers.io) {
"Unable to get pairs. At this point they must not be null." val pairs = requireNotNull(pairsStore.getSyncOrNull(PAIRS_KEY)) {
} "Unable to get pairs. At this point they must not be null."
val amountValue = requireNotNull(amount.value) { "Amount value must not be null" } }
val fromAmount = amountValue.movePointRight(amount.decimals).toString() val amountValue = requireNotNull(amount.value) { "Amount value must not be null" }
val currency = requireNotNull(getDefaultCurrencySync()) { "Default currency must not be null" } val fromAmount = amountValue.movePointRight(amount.decimals).toString()
val country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" } val currency = requireNotNull(getDefaultCurrencySync()) { "Default currency must not be null" }
val fromOnrampAmount = OnrampAmount( val country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" }
value = fromAmount val fromOnrampAmount = OnrampAmount(
.toBigDecimalOrDefault() value = fromAmount
.movePointLeft(currency.precision), .toBigDecimalOrDefault()
decimals = currency.precision, .movePointLeft(currency.precision),
symbol = amount.currencySymbol, decimals = currency.precision,
) symbol = amount.currencySymbol,
val quotes: List<OnrampQuote> = pairs.flatMap { pair -> )
pair.providers.flatMap { provider -> val quotes: List<OnrampQuote> = pairs.flatMap { pair ->
provider.paymentMethods.map { paymentMethod -> pair.providers.flatMap { provider ->
async { provider.paymentMethods.map { paymentMethod ->
safeApiCall( async {
call = { safeApiCall(
val response = onrampApi.getQuote( call = {
fromCurrencyCode = currency.code, val response = onrampApi.getQuote(
fromPrecision = currency.precision, fromCurrencyCode = currency.code,
toContractAddress = cryptoCurrency.getContractAddress(), fromPrecision = currency.precision,
toNetwork = cryptoCurrency.network.backendId, toContractAddress = cryptoCurrency.getContractAddress(),
paymentMethod = paymentMethod.id, toNetwork = cryptoCurrency.network.backendId,
countryCode = country.code, paymentMethod = paymentMethod.id,
fromAmount = fromAmount, countryCode = country.code,
toDecimals = cryptoCurrency.decimals, fromAmount = fromAmount,
providerId = provider.id, toDecimals = cryptoCurrency.decimals,
).bind() providerId = provider.id,
OnrampQuote.Data( userWalletId = userWallet.walletId.stringValue,
fromAmount = fromOnrampAmount, refCode = ExpressUtils.getRefCode(
toAmount = convertToAmount(response.toAmount, cryptoCurrency), userWallet = userWallet,
minFromAmount = convertToAmount(response.minFromAmount, cryptoCurrency), appPreferencesStore = appPreferencesStore,
maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency), ),
paymentMethod = paymentMethod, ).bind()
provider = provider, OnrampQuote.Data(
countryCode = response.countryCode, fromAmount = fromOnrampAmount,
) toAmount = convertToAmount(response.toAmount, cryptoCurrency),
}, minFromAmount = convertToAmount(response.minFromAmount, cryptoCurrency),
onError = { error -> maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency),
convertQuoteError( paymentMethod = paymentMethod,
error = error, provider = provider,
paymentMethod = paymentMethod, countryCode = response.countryCode,
provider = provider, )
fromOnrampAmount = fromOnrampAmount, },
countryCode = country.code, onError = { error ->
) convertQuoteError(
}, error = error,
) paymentMethod = paymentMethod,
provider = provider,
fromOnrampAmount = fromOnrampAmount,
countryCode = country.code,
)
},
)
}
} }
} }
} }.awaitAll().filterNotNull()
}.awaitAll().filterNotNull() quotesStore.store(QUOTES_KEY, quotes)
quotesStore.store(QUOTES_KEY, quotes) }
}
override fun getQuotes(): Flow<List<OnrampQuote>> { override fun getQuotes(): Flow<List<OnrampQuote>> {
return quotesStore.get(QUOTES_KEY) return quotesStore.get(QUOTES_KEY)
@ -278,14 +336,14 @@ internal class DefaultOnrampRepository(
} }
override suspend fun getOnrampData( override suspend fun getOnrampData(
userWalletId: UserWalletId, userWallet: UserWallet,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
quote: OnrampProviderWithQuote.Data, quote: OnrampProviderWithQuote.Data,
isDarkTheme: Boolean, isDarkTheme: Boolean,
): OnrampTransaction = withContext(dispatchers.io) { ): OnrampTransaction = withContext(dispatchers.io) {
try { try {
val address = requireNotNull( val address = requireNotNull(
value = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network), value = walletManagersFacade.getDefaultAddress(userWallet.walletId, cryptoCurrency.network),
lazyMessage = { "Address must not be null" }, lazyMessage = { "Address must not be null" },
) )
val fromAmountString = quote.fromAmount.value.movePointRight(quote.fromAmount.decimals).toString() val fromAmountString = quote.fromAmount.value.movePointRight(quote.fromAmount.decimals).toString()
@ -309,6 +367,11 @@ internal class DefaultOnrampRepository(
language = null, language = null,
theme = getTheme(isDarkTheme), theme = getTheme(isDarkTheme),
requestId = requestId, requestId = requestId,
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
).bind() ).bind()
}, },
onError = { e -> onError = { e ->
@ -326,7 +389,7 @@ internal class DefaultOnrampRepository(
txId = data.txId, txId = data.txId,
quote = quote, quote = quote,
onrampDataJson = dataJson, onrampDataJson = dataJson,
userWalletId = userWalletId, userWalletId = userWallet.walletId,
currency = currency, currency = currency,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
residency = country.name, residency = country.name,

View file

@ -618,7 +618,7 @@ internal class DefaultCurrenciesRepository(
} }
coroutineScope { coroutineScope {
launch { expressServiceLoader.update(userWalletId, tokens) } launch { expressServiceLoader.update(getUserWallet(userWalletId), tokens) }
} }
} }
@ -639,7 +639,7 @@ internal class DefaultCurrenciesRepository(
skipCache = refresh, skipCache = refresh,
block = { block = {
coroutineScope { coroutineScope {
launch { expressServiceLoader.update(userWalletId, tokens) } launch { expressServiceLoader.update(getUserWallet(userWalletId), tokens) }
} }
}, },
) )
@ -677,13 +677,13 @@ internal class DefaultCurrenciesRepository(
isSortedByBalance = false, isSortedByBalance = false,
) )
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet { private fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId" "Unable to find a user wallet with provided ID: $userWalletId"
} }
} }
private suspend fun ensureIsCorrectUserWallet(userWalletId: UserWalletId, isMultiCurrencyWalletExpected: Boolean) { private fun ensureIsCorrectUserWallet(userWalletId: UserWalletId, isMultiCurrencyWalletExpected: Boolean) {
val userWallet = getUserWallet(userWalletId) val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected)

View file

@ -6,27 +6,31 @@ import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.wallets.models.UserWallet
class CheckOnrampAvailabilityUseCase( class CheckOnrampAvailabilityUseCase(
private val repository: OnrampRepository, private val repository: OnrampRepository,
private val errorResolver: OnrampErrorResolver, private val errorResolver: OnrampErrorResolver,
) { ) {
suspend operator fun invoke(): Either<OnrampError, OnrampAvailability> { suspend operator fun invoke(userWallet: UserWallet): Either<OnrampError, OnrampAvailability> {
return Either.catch { return Either.catch {
repository.fetchPaymentMethodsIfAbsent() repository.fetchPaymentMethodsIfAbsent(userWallet)
val savedCountry = repository.getDefaultCountrySync() val savedCountry = repository.getDefaultCountrySync()
if (savedCountry != null) { if (savedCountry != null) {
proceedWithSavedCountry(savedCountry = savedCountry) proceedWithSavedCountry(userWallet = userWallet, savedCountry = savedCountry)
} else { } else {
val detectedCountry = repository.getCountryByIp() val detectedCountry = repository.getCountryByIp(userWallet)
OnrampAvailability.ConfirmResidency(detectedCountry) OnrampAvailability.ConfirmResidency(detectedCountry)
} }
}.mapLeft(errorResolver::resolve) }.mapLeft(errorResolver::resolve)
} }
private suspend fun proceedWithSavedCountry(savedCountry: OnrampCountry): OnrampAvailability { private suspend fun proceedWithSavedCountry(
val countries = repository.fetchCountries() userWallet: UserWallet,
savedCountry: OnrampCountry,
): OnrampAvailability {
val countries = repository.fetchCountries(userWallet)
val updatedCountry = countries.find { it.id == savedCountry.id } ?: savedCountry val updatedCountry = countries.find { it.id == savedCountry.id } ?: savedCountry
return if (updatedCountry.onrampAvailable) { return if (updatedCountry.onrampAvailable) {
val currency = repository.getDefaultCurrencySync() ?: run { val currency = repository.getDefaultCurrencySync() ?: run {

View file

@ -4,13 +4,14 @@ import arrow.core.Either
import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.wallets.models.UserWallet
class FetchOnrampCountriesUseCase( class FetchOnrampCountriesUseCase(
private val repository: OnrampRepository, private val repository: OnrampRepository,
private val errorResolver: OnrampErrorResolver, private val errorResolver: OnrampErrorResolver,
) { ) {
suspend operator fun invoke(): Either<OnrampError, Unit> { suspend operator fun invoke(userWallet: UserWallet): Either<OnrampError, Unit> {
return Either.catch<Unit> { repository.fetchCountries() }.mapLeft(errorResolver::resolve) return Either.catch<Unit> { repository.fetchCountries(userWallet) }.mapLeft(errorResolver::resolve)
} }
} }

View file

@ -4,13 +4,14 @@ import arrow.core.Either
import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.wallets.models.UserWallet
class FetchOnrampCurrenciesUseCase( class FetchOnrampCurrenciesUseCase(
private val repository: OnrampRepository, private val repository: OnrampRepository,
private val errorResolver: OnrampErrorResolver, private val errorResolver: OnrampErrorResolver,
) { ) {
suspend operator fun invoke(): Either<OnrampError, Unit> { suspend operator fun invoke(userWallet: UserWallet): Either<OnrampError, Unit> {
return Either.catch { repository.fetchCurrencies() }.mapLeft(errorResolver::resolve) return Either.catch { repository.fetchCurrencies(userWallet) }.mapLeft(errorResolver::resolve)
} }
} }

View file

@ -7,6 +7,7 @@ import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -24,8 +25,8 @@ class GetOnrampCountryUseCase(
} }
} }
suspend fun invokeSync(): Either<OnrampError, OnrampCountry> { suspend fun invokeSync(userWallet: UserWallet): Either<OnrampError, OnrampCountry> {
return Either.catch { repository.getDefaultCountrySync() ?: repository.getCountryByIp() } return Either.catch { repository.getDefaultCountrySync() ?: repository.getCountryByIp(userWallet) }
.mapLeft(errorResolver::resolve) .mapLeft(errorResolver::resolve)
} }
} }

View file

@ -7,7 +7,7 @@ import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWallet
class GetOnrampRedirectUrlUseCase( class GetOnrampRedirectUrlUseCase(
private val repository: OnrampRepository, private val repository: OnrampRepository,
@ -16,14 +16,14 @@ class GetOnrampRedirectUrlUseCase(
) { ) {
suspend operator fun invoke( suspend operator fun invoke(
userWalletId: UserWalletId, userWallet: UserWallet,
quote: OnrampProviderWithQuote.Data, quote: OnrampProviderWithQuote.Data,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
isDarkTheme: Boolean, isDarkTheme: Boolean,
): Either<OnrampError, String> { ): Either<OnrampError, String> {
return Either.catch { return Either.catch {
val transaction = repository.getOnrampData( val transaction = repository.getOnrampData(
userWalletId = userWalletId, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
quote = quote, quote = quote,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,

View file

@ -5,15 +5,16 @@ import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.wallets.models.UserWallet
class GetOnrampStatusUseCase( class GetOnrampStatusUseCase(
private val onrampRepository: OnrampRepository, private val onrampRepository: OnrampRepository,
private val errorResolver: OnrampErrorResolver, private val errorResolver: OnrampErrorResolver,
) { ) {
suspend operator fun invoke(txId: String): Either<OnrampError, OnrampStatus> { suspend operator fun invoke(userWallet: UserWallet, txId: String): Either<OnrampError, OnrampStatus> {
return Either.catch { return Either.catch {
onrampRepository.getStatus(txId) onrampRepository.getStatus(userWallet = userWallet, txId = txId)
}.mapLeft(errorResolver::resolve) }.mapLeft(errorResolver::resolve)
} }
} }

View file

@ -5,17 +5,23 @@ import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
class OnrampFetchPairsUseCase( class OnrampFetchPairsUseCase(
private val repository: OnrampRepository, private val repository: OnrampRepository,
private val errorResolver: OnrampErrorResolver, private val errorResolver: OnrampErrorResolver,
) { ) {
suspend operator fun invoke(cryptoCurrency: CryptoCurrency): Either<OnrampError, Unit> { suspend operator fun invoke(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Either<OnrampError, Unit> {
return Either.catch { return Either.catch {
val country = requireNotNull(repository.getDefaultCountrySync()) { "Country must not be null" } val country = requireNotNull(repository.getDefaultCountrySync()) { "Country must not be null" }
val currency = requireNotNull(repository.getDefaultCurrencySync()) { "Currency must not be null" } val currency = requireNotNull(repository.getDefaultCurrencySync()) { "Currency must not be null" }
repository.fetchPairs(currency = currency, country = country, cryptoCurrency = cryptoCurrency) repository.fetchPairs(
userWallet = userWallet,
currency = currency,
country = country,
cryptoCurrency = cryptoCurrency,
)
}.mapLeft(errorResolver::resolve) }.mapLeft(errorResolver::resolve)
} }
} }

View file

@ -5,14 +5,16 @@ import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.tokens.model.Amount import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
class OnrampFetchQuotesUseCase( class OnrampFetchQuotesUseCase(
private val repository: OnrampRepository, private val repository: OnrampRepository,
private val errorResolver: OnrampErrorResolver, private val errorResolver: OnrampErrorResolver,
) { ) {
suspend operator fun invoke(amount: Amount, cryptoCurrency: CryptoCurrency) = Either.catch { suspend operator fun invoke(userWallet: UserWallet, amount: Amount, cryptoCurrency: CryptoCurrency) = Either.catch {
repository.fetchQuotes( repository.fetchQuotes(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
amount = amount, amount = amount,
) )

View file

@ -4,7 +4,7 @@ import com.tangem.domain.onramp.model.*
import com.tangem.domain.onramp.model.cache.OnrampTransaction import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.tokens.model.Amount import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
@ -13,15 +13,21 @@ interface OnrampRepository {
fun getCurrencies(): Flow<List<OnrampCurrency>> fun getCurrencies(): Flow<List<OnrampCurrency>>
fun getCountries(): Flow<List<OnrampCountry>> fun getCountries(): Flow<List<OnrampCountry>>
suspend fun getCountriesSync(): List<OnrampCountry>? suspend fun getCountriesSync(): List<OnrampCountry>?
suspend fun getCountryByIp(): OnrampCountry suspend fun getCountryByIp(userWallet: UserWallet): OnrampCountry
suspend fun getStatus(txId: String): OnrampStatus suspend fun getStatus(userWallet: UserWallet, txId: String): OnrampStatus
suspend fun fetchCurrencies() suspend fun fetchCurrencies(userWallet: UserWallet)
suspend fun fetchCountries(): List<OnrampCountry> suspend fun fetchCountries(userWallet: UserWallet): List<OnrampCountry>
suspend fun fetchPaymentMethodsIfAbsent() suspend fun fetchPaymentMethodsIfAbsent(userWallet: UserWallet)
suspend fun fetchPairs(currency: OnrampCurrency, country: OnrampCountry, cryptoCurrency: CryptoCurrency) suspend fun fetchPairs(
suspend fun fetchQuotes(cryptoCurrency: CryptoCurrency, amount: Amount) userWallet: UserWallet,
currency: OnrampCurrency,
country: OnrampCountry,
cryptoCurrency: CryptoCurrency,
)
suspend fun fetchQuotes(userWallet: UserWallet, cryptoCurrency: CryptoCurrency, amount: Amount)
suspend fun getOnrampData( suspend fun getOnrampData(
userWalletId: UserWalletId, userWallet: UserWallet,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
quote: OnrampProviderWithQuote.Data, quote: OnrampProviderWithQuote.Data,
isDarkTheme: Boolean, isDarkTheme: Boolean,

View file

@ -4,10 +4,12 @@ import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
data class Params( data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency, val cryptoCurrency: CryptoCurrency,
val country: OnrampCountry, val country: OnrampCountry,
val onDismiss: () -> Unit, val onDismiss: () -> Unit,

View file

@ -69,6 +69,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
is ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create( is ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = SelectCountryComponent.Params( params = SelectCountryComponent.Params(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
onDismiss = { isCountrySelected -> onDismiss = { isCountrySelected ->
// Dismiss country select sheet // Dismiss country select sheet

View file

@ -54,6 +54,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
is OnrampMainBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create( is OnrampMainBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = ConfirmResidencyComponent.Params( params = ConfirmResidencyComponent.Params(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
country = config.country, country = config.country,
onDismiss = { onDismiss = {
@ -65,6 +66,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
is OnrampMainBottomSheetConfig.CurrenciesList -> selectCurrencyComponentFactory.create( is OnrampMainBottomSheetConfig.CurrenciesList -> selectCurrencyComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = SelectCurrencyComponent.Params( params = SelectCurrencyComponent.Params(
userWallet = model.userWallet,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
onDismiss = model.bottomSheetNavigation::dismiss, onDismiss = model.bottomSheetNavigation::dismiss,
), ),

View file

@ -57,6 +57,9 @@ internal class OnrampMainComponentModel @Inject constructor(
) : Model(), OnrampIntents { ) : Model(), OnrampIntents {
private val params: OnrampMainComponent.Params = paramsContainer.require() private val params: OnrampMainComponent.Params = paramsContainer.require()
val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
private val stateFactory = OnrampStateFactory( private val stateFactory = OnrampStateFactory(
currentStateProvider = Provider { _state.value }, currentStateProvider = Provider { _state.value },
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
@ -68,7 +71,6 @@ internal class OnrampMainComponentModel @Inject constructor(
onrampIntents = this, onrampIntents = this,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
) )
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
private val _state: MutableStateFlow<OnrampMainComponentUM> = MutableStateFlow( private val _state: MutableStateFlow<OnrampMainComponentUM> = MutableStateFlow(
value = stateFactory.getInitialState( value = stateFactory.getInitialState(
currency = params.cryptoCurrency.name, currency = params.cryptoCurrency.name,
@ -107,7 +109,7 @@ internal class OnrampMainComponentModel @Inject constructor(
private fun checkResidenceCountry() { private fun checkResidenceCountry() {
modelScope.launch { modelScope.launch {
checkOnrampAvailabilityUseCase() checkOnrampAvailabilityUseCase(userWallet)
.onRight(::handleOnrampAvailability) .onRight(::handleOnrampAvailability)
.onLeft(::handleOnrampError) .onLeft(::handleOnrampError)
} }
@ -158,7 +160,7 @@ internal class OnrampMainComponentModel @Inject constructor(
if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) { if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) {
_state.update { amountStateFactory.getAmountSecondaryLoadingState() } _state.update { amountStateFactory.getAmountSecondaryLoadingState() }
} }
fetchPairsUseCase.invoke(params.cryptoCurrency).fold( fetchPairsUseCase.invoke(userWallet, params.cryptoCurrency).fold(
ifLeft = ::handleOnrampError, ifLeft = ::handleOnrampError,
ifRight = { _state.update { amountStateFactory.getAmountSecondaryResetState() } }, ifRight = { _state.update { amountStateFactory.getAmountSecondaryResetState() } },
) )
@ -184,6 +186,7 @@ internal class OnrampMainComponentModel @Inject constructor(
val content = state.value as? OnrampMainComponentUM.Content ?: return@runCatching val content = state.value as? OnrampMainComponentUM.Content ?: return@runCatching
if (content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()) return@runCatching if (content.amountBlockState.amountFieldModel.fiatAmount.value.isNullOrZero()) return@runCatching
fetchQuotesUseCase.invoke( fetchQuotesUseCase.invoke(
userWallet = userWallet,
amount = content.amountBlockState.amountFieldModel.fiatAmount, amount = content.amountBlockState.amountFieldModel.fiatAmount,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
).onLeft(::handleOnrampError) ).onLeft(::handleOnrampError)
@ -216,7 +219,7 @@ internal class OnrampMainComponentModel @Inject constructor(
} }
override fun onBuyClick(quote: OnrampProviderWithQuote.Data) { override fun onBuyClick(quote: OnrampProviderWithQuote.Data) {
if (isDemoCardUseCase.invoke(selectedUserWallet.cardId)) { if (isDemoCardUseCase.invoke(userWallet.cardId)) {
showDemoWarning() showDemoWarning()
} else { } else {
val currentContentState = state.value as? OnrampMainComponentUM.Content ?: return val currentContentState = state.value as? OnrampMainComponentUM.Content ?: return

View file

@ -14,6 +14,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.onramp.GetOnrampRedirectUrlUseCase import com.tangem.domain.onramp.GetOnrampRedirectUrlUseCase
import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.redirect.OnrampRedirectComponent import com.tangem.features.onramp.redirect.OnrampRedirectComponent
import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM
@ -29,6 +30,7 @@ internal class OnrampRedirectModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase, private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val messageSender: UiMessageSender, private val messageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
router: Router, router: Router,
@ -36,6 +38,8 @@ internal class OnrampRedirectModel @Inject constructor(
) : Model() { ) : Model() {
private val params: OnrampRedirectComponent.Params = paramsContainer.require() private val params: OnrampRedirectComponent.Params = paramsContainer.require()
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
val state = OnrampRedirectUM( val state = OnrampRedirectUM(
topBarConfig = OnrampRedirectTopBarUM( topBarConfig = OnrampRedirectTopBarUM(
title = combinedReference( title = combinedReference(
@ -62,7 +66,7 @@ internal class OnrampRedirectModel @Inject constructor(
fun getRedirectUrl(isDarkTheme: Boolean) { fun getRedirectUrl(isDarkTheme: Boolean) {
modelScope.launch { modelScope.launch {
getOnrampRedirectUrlUseCase.invoke( getOnrampRedirectUrlUseCase.invoke(
userWalletId = params.userWalletId, userWallet = selectedUserWallet,
quote = params.onrampProviderWithQuote, quote = params.onrampProviderWithQuote,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,

View file

@ -61,8 +61,9 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
OnrampChild.Settings -> settingsComponentFactory.create( OnrampChild.Settings -> settingsComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = OnrampSettingsComponent.Params( params = OnrampSettingsComponent.Params(
params.cryptoCurrency, userWalletId = params.userWalletId,
navigation::pop, cryptoCurrency = params.cryptoCurrency,
onBack = navigation::pop,
), ),
) )
OnrampChild.Main -> onrampMainComponentFactory.create( OnrampChild.Main -> onrampMainComponentFactory.create(

View file

@ -3,10 +3,15 @@ package com.tangem.features.onramp.selectcountry
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface SelectCountryComponent : ComposableBottomSheetComponent { internal interface SelectCountryComponent : ComposableBottomSheetComponent {
data class Params(val cryptoCurrency: CryptoCurrency, val onDismiss: (Boolean) -> Unit) data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val onDismiss: (Boolean) -> Unit,
)
interface Factory : ComponentFactory<Params, SelectCountryComponent> interface Factory : ComponentFactory<Params, SelectCountryComponent>
} }

View file

@ -13,6 +13,7 @@ import com.tangem.domain.onramp.GetOnrampCountryUseCase
import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selectcountry.SelectCountryComponent import com.tangem.features.onramp.selectcountry.SelectCountryComponent
import com.tangem.features.onramp.selectcountry.entity.CountryItemState import com.tangem.features.onramp.selectcountry.entity.CountryItemState
@ -44,11 +45,15 @@ internal class OnrampSelectCountryModel @Inject constructor(
private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase, private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase,
private val getOnrampCountryUseCase: GetOnrampCountryUseCase, private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
private val fetchOnrampCountriesUseCase: FetchOnrampCountriesUseCase, private val fetchOnrampCountriesUseCase: FetchOnrampCountriesUseCase,
getWalletsUseCase: GetWalletsUseCase,
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
) : Model() { ) : Model() {
val state: StateFlow<CountryListUM> get() = controller.state
private val params: SelectCountryComponent.Params = paramsContainer.require() private val params: SelectCountryComponent.Params = paramsContainer.require()
private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
val state: StateFlow<CountryListUM> get() = controller.state
private val controller = CountryListUMController( private val controller = CountryListUMController(
searchBarUM = createSearchBarUM(), searchBarUM = createSearchBarUM(),
loadingItems = loadingItems, loadingItems = loadingItems,
@ -103,7 +108,7 @@ internal class OnrampSelectCountryModel @Inject constructor(
private fun updateCountriesList() { private fun updateCountriesList() {
modelScope.launch { modelScope.launch {
fetchOnrampCountriesUseCase().onLeft { fetchOnrampCountriesUseCase(userWallet).onLeft {
controller.update(UpdateCountryItemsErrorTransformer(onRetry = ::onRetry)) controller.update(UpdateCountryItemsErrorTransformer(onRetry = ::onRetry))
} }
} }

View file

@ -3,10 +3,15 @@ package com.tangem.features.onramp.selectcurrency
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
internal interface SelectCurrencyComponent : ComposableBottomSheetComponent { internal interface SelectCurrencyComponent : ComposableBottomSheetComponent {
data class Params(val cryptoCurrency: CryptoCurrency, val onDismiss: () -> Unit) data class Params(
val userWallet: UserWallet,
val cryptoCurrency: CryptoCurrency,
val onDismiss: () -> Unit,
)
interface Factory : ComponentFactory<Params, SelectCurrencyComponent> interface Factory : ComponentFactory<Params, SelectCurrencyComponent>
} }

View file

@ -97,7 +97,7 @@ internal class OnrampSelectCurrencyModel @Inject constructor(
private fun updateCurrenciesList() { private fun updateCurrenciesList() {
modelScope.launch { modelScope.launch {
fetchOnrampCurrenciesUseCase().onLeft { fetchOnrampCurrenciesUseCase(userWallet = params.userWallet).onLeft {
controller.update(UpdateCurrencyItemsErrorTransformer(onRetry = ::onRetry)) controller.update(UpdateCurrencyItemsErrorTransformer(onRetry = ::onRetry))
} }
} }

View file

@ -52,7 +52,8 @@ internal class DefaultOnrampSettingsComponent @AssistedInject constructor(
OnrampSettingsConfig.SelectCountry -> selectCountryComponentFactory.create( OnrampSettingsConfig.SelectCountry -> selectCountryComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = SelectCountryComponent.Params( params = SelectCountryComponent.Params(
params.cryptoCurrency, userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency,
onDismiss = { model.bottomSheetNavigation.dismiss() }, onDismiss = { model.bottomSheetNavigation.dismiss() },
), ),
) )

View file

@ -3,10 +3,15 @@ package com.tangem.features.onramp.settings
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampSettingsComponent : ComposableContentComponent { internal interface OnrampSettingsComponent : ComposableContentComponent {
data class Params(val cryptoCurrency: CryptoCurrency, val onBack: () -> Unit) data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val onBack: () -> Unit,
)
interface Factory : ComponentFactory<Params, OnrampSettingsComponent> interface Factory : ComponentFactory<Params, OnrampSettingsComponent>
} }

View file

@ -97,7 +97,7 @@ internal class OnrampSuccessComponentModel @Inject constructor(
return return
} }
getOnrampStatusUseCase(txId = transaction.txId) getOnrampStatusUseCase(userWallet = userWallet, txId = transaction.txId)
.fold( .fold(
ifLeft = { error -> ifLeft = { error ->
analyticsEventHandler.sendOnrampErrorEvent( analyticsEventHandler.sendOnrampErrorEvent(

View file

@ -19,6 +19,7 @@ import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
@ -52,11 +53,13 @@ internal class AvailableSwapPairsModel @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getAvailablePairsUseCase: GetAvailablePairsUseCase, private val getAvailablePairsUseCase: GetAvailablePairsUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
) : Model() { ) : Model() {
val state: StateFlow<TokenListUM> = tokenListUMController.state val state: StateFlow<TokenListUM> = tokenListUMController.state
private var params: AvailableSwapPairsComponent.Params = paramsContainer.require() private var params: AvailableSwapPairsComponent.Params = paramsContainer.require()
private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
private val tokenListFlow = getTokenListUseCaseFlow() private val tokenListFlow = getTokenListUseCaseFlow()
@ -212,6 +215,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
availablePairsByNetworkFlow.update(networkInfo = networkInfo, state = lceLoading()) availablePairsByNetworkFlow.update(networkInfo = networkInfo, state = lceLoading())
getAvailablePairsUseCase( getAvailablePairsUseCase(
userWallet = userWallet,
initialCurrency = networkInfo, initialCurrency = networkInfo,
currencies = statuses.map(CryptoCurrencyStatus::currency), currencies = statuses.map(CryptoCurrencyStatus::currency),
) )

View file

@ -21,9 +21,12 @@ import com.tangem.datasource.api.express.models.response.SwapPair
import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders
import com.tangem.datasource.api.express.models.response.TxDetails import com.tangem.datasource.api.express.models.response.TxDetails
import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.converters.* import com.tangem.feature.swap.converters.*
import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.api.SwapRepository
@ -48,6 +51,7 @@ internal class DefaultSwapRepository(
private val userWalletsListManager: UserWalletsListManager, private val userWalletsListManager: UserWalletsListManager,
private val errorsDataConverter: ErrorsDataConverter, private val errorsDataConverter: ErrorsDataConverter,
private val dataSignatureVerifier: DataSignatureVerifier, private val dataSignatureVerifier: DataSignatureVerifier,
private val appPreferencesStore: AppPreferencesStore,
moshi: Moshi, moshi: Moshi,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
) : SwapRepository { ) : SwapRepository {
@ -60,6 +64,7 @@ internal class DefaultSwapRepository(
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java) private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
override suspend fun getPairs( override suspend fun getPairs(
userWallet: UserWallet,
initialCurrency: LeastTokenInfo, initialCurrency: LeastTokenInfo,
currencyList: List<CryptoCurrency>, currencyList: List<CryptoCurrency>,
): PairsWithProviders { ): PairsWithProviders {
@ -73,6 +78,7 @@ internal class DefaultSwapRepository(
val pairsDeferred = async { val pairsDeferred = async {
getPairsInternal( getPairsInternal(
userWallet = userWallet,
from = arrayListOf(initial), from = arrayListOf(initial),
to = currenciesList, to = currenciesList,
) )
@ -80,6 +86,7 @@ internal class DefaultSwapRepository(
val reversedPairsDeferred = async { val reversedPairsDeferred = async {
getPairsInternal( getPairsInternal(
userWallet = userWallet,
from = currenciesList, from = currenciesList,
to = arrayListOf(initial), to = arrayListOf(initial),
) )
@ -90,7 +97,13 @@ internal class DefaultSwapRepository(
val allPairs = pairs + reversedPairs val allPairs = pairs + reversedPairs
val providers = tangemExpressApi.getProviders().getOrThrow() val providers = tangemExpressApi.getProviders(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
).getOrThrow()
return@withContext swapPairInfoConverter.convert( return@withContext swapPairInfoConverter.convert(
SwapPairsWithProviders( SwapPairsWithProviders(
@ -109,6 +122,7 @@ internal class DefaultSwapRepository(
} }
override suspend fun getPairsOnly( override suspend fun getPairsOnly(
userWallet: UserWallet,
initialCurrency: LeastTokenInfo, initialCurrency: LeastTokenInfo,
currencyList: List<CryptoCurrency>, currencyList: List<CryptoCurrency>,
): PairsWithProviders { ): PairsWithProviders {
@ -122,6 +136,7 @@ internal class DefaultSwapRepository(
val pairsDeferred = async { val pairsDeferred = async {
getPairsInternal( getPairsInternal(
userWallet = userWallet,
from = arrayListOf(initial), from = arrayListOf(initial),
to = currenciesList, to = currenciesList,
) )
@ -129,6 +144,7 @@ internal class DefaultSwapRepository(
val reversedPairsDeferred = async { val reversedPairsDeferred = async {
getPairsInternal( getPairsInternal(
userWallet = userWallet,
from = currenciesList, from = currenciesList,
to = arrayListOf(initial), to = arrayListOf(initial),
) )
@ -156,25 +172,42 @@ internal class DefaultSwapRepository(
} }
private suspend fun getPairsInternal( private suspend fun getPairsInternal(
userWallet: UserWallet,
from: List<NetworkLeastTokenInfo>, from: List<NetworkLeastTokenInfo>,
to: List<NetworkLeastTokenInfo>, to: List<NetworkLeastTokenInfo>,
): ApiResponse<List<SwapPair>> { ): ApiResponse<List<SwapPair>> {
return tangemExpressApi.getPairs( return tangemExpressApi.getPairs(
PairsRequestBody( userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
body = PairsRequestBody(
from = from, from = from,
to = to, to = to,
), ),
) )
} }
override suspend fun getExchangeStatus(txId: String): Either<UnknownError, ExchangeStatusModel> { override suspend fun getExchangeStatus(
userWallet: UserWallet,
txId: String,
): Either<UnknownError,
ExchangeStatusModel,> {
return withContext(coroutineDispatcher.io) { return withContext(coroutineDispatcher.io) {
either { either {
catch( catch(
block = { block = {
exchangeStatusConverter.convert( exchangeStatusConverter.convert(
tangemExpressApi tangemExpressApi
.getExchangeStatus(txId) .getExchangeStatus(
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
txId = txId,
)
.getOrThrow(), .getOrThrow(),
) )
}, },
@ -188,7 +221,7 @@ internal class DefaultSwapRepository(
} }
override suspend fun findBestQuote( override suspend fun findBestQuote(
userWalletId: UserWalletId, userWallet: UserWallet,
fromContractAddress: String, fromContractAddress: String,
fromNetwork: String, fromNetwork: String,
toContractAddress: String, toContractAddress: String,
@ -211,6 +244,11 @@ internal class DefaultSwapRepository(
toDecimals = toDecimals, toDecimals = toDecimals,
providerId = providerId, providerId = providerId,
rateType = rateType.name.lowercase(), rateType = rateType.name.lowercase(),
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
).getOrThrow() ).getOrThrow()
QuoteModel( QuoteModel(
toTokenAmount = createFromAmountWithOffset(response.toAmount, response.toDecimals), toTokenAmount = createFromAmountWithOffset(response.toAmount, response.toDecimals),
@ -223,6 +261,7 @@ internal class DefaultSwapRepository(
} }
override suspend fun getExchangeData( override suspend fun getExchangeData(
userWallet: UserWallet,
fromContractAddress: String, fromContractAddress: String,
fromNetwork: String, fromNetwork: String,
toContractAddress: String, toContractAddress: String,
@ -255,6 +294,11 @@ internal class DefaultSwapRepository(
requestId = requestId, requestId = requestId,
refundAddress = refundAddress, refundAddress = refundAddress,
refundExtraId = refundExtraId, refundExtraId = refundExtraId,
userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
).getOrThrow() ).getOrThrow()
if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) { if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) {
val txDetails = parseTxDetails(response.txDetailsJson) val txDetails = parseTxDetails(response.txDetailsJson)
@ -281,6 +325,7 @@ internal class DefaultSwapRepository(
} }
override suspend fun exchangeSent( override suspend fun exchangeSent(
userWallet: UserWallet,
txId: String, txId: String,
fromNetwork: String, fromNetwork: String,
fromAddress: String, fromAddress: String,
@ -290,7 +335,12 @@ internal class DefaultSwapRepository(
): Either<ExpressDataError, Unit> = withContext(coroutineDispatcher.io) { ): Either<ExpressDataError, Unit> = withContext(coroutineDispatcher.io) {
try { try {
tangemExpressApi.exchangeSent( tangemExpressApi.exchangeSent(
ExchangeSentRequestBody( userWalletId = userWallet.walletId.stringValue,
refCode = ExpressUtils.getRefCode(
userWallet = userWallet,
appPreferencesStore = appPreferencesStore,
),
body = ExchangeSentRequestBody(
txId = txId, txId = txId,
fromNetwork = fromNetwork, fromNetwork = fromNetwork,
fromAddress = fromAddress, fromAddress = fromAddress,

View file

@ -7,8 +7,8 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMapSync import com.tangem.datasource.local.preferences.utils.getObjectMapSync
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.converters.SavedSwapTransactionListConverter import com.tangem.feature.swap.converters.SavedSwapTransactionListConverter
import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.feature.swap.domain.SwapTransactionRepository
@ -80,9 +80,8 @@ internal class DefaultSwapTransactionRepository(
} }
override suspend fun getTransactions( override suspend fun getTransactions(
userWalletId: UserWalletId, userWallet: UserWallet,
cryptoCurrencyId: CryptoCurrency.ID, cryptoCurrencyId: CryptoCurrency.ID,
scanResponse: ScanResponse,
): Flow<List<SavedSwapTransactionListModel>?> { ): Flow<List<SavedSwapTransactionListModel>?> {
return withContext(dispatchers.io) { return withContext(dispatchers.io) {
val txStatuses = appPreferencesStore.getObjectMapSync<ExchangeStatusModel>( val txStatuses = appPreferencesStore.getObjectMapSync<ExchangeStatusModel>(
@ -93,7 +92,7 @@ internal class DefaultSwapTransactionRepository(
).map { savedTransactions -> ).map { savedTransactions ->
val currencyTxs = savedTransactions val currencyTxs = savedTransactions
?.filter { ?.filter {
it.userWalletId == userWalletId.stringValue && it.userWalletId == userWallet.walletId.stringValue &&
( (
it.toCryptoCurrencyId == cryptoCurrencyId.value || it.toCryptoCurrencyId == cryptoCurrencyId.value ||
it.fromCryptoCurrencyId == cryptoCurrencyId.value it.fromCryptoCurrencyId == cryptoCurrencyId.value
@ -103,7 +102,7 @@ internal class DefaultSwapTransactionRepository(
currencyTxs?.mapNotNull { currencyTxs?.mapNotNull {
converter.convertBack( converter.convertBack(
value = it, value = it,
scanResponse = scanResponse, scanResponse = userWallet.scanResponse,
txStatuses = txStatuses, txStatuses = txStatuses,
) )
} }

View file

@ -36,6 +36,7 @@ internal class SwapDataModule {
errorsDataConverter: ErrorsDataConverter, errorsDataConverter: ErrorsDataConverter,
@NetworkMoshi moshi: Moshi, @NetworkMoshi moshi: Moshi,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
appPreferencesStore: AppPreferencesStore,
): SwapRepository { ): SwapRepository {
return DefaultSwapRepository( return DefaultSwapRepository(
tangemExpressApi = tangemExpressApi, tangemExpressApi = tangemExpressApi,
@ -46,6 +47,7 @@ internal class SwapDataModule {
dataSignatureVerifier = dataSignature, dataSignatureVerifier = dataSignature,
moshi = moshi, moshi = moshi,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
appPreferencesStore = appPreferencesStore,
) )
} }

View file

@ -2,6 +2,7 @@ package com.tangem.feature.swap.domain.api
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.domain.*
@ -9,16 +10,24 @@ import java.math.BigDecimal
interface SwapRepository { interface SwapRepository {
suspend fun getPairs(initialCurrency: LeastTokenInfo, currencyList: List<CryptoCurrency>): PairsWithProviders suspend fun getPairs(
userWallet: UserWallet,
initialCurrency: LeastTokenInfo,
currencyList: List<CryptoCurrency>,
): PairsWithProviders
/** Express getPairs request variant without providers request */ /** Express getPairs request variant without providers request */
suspend fun getPairsOnly(initialCurrency: LeastTokenInfo, currencyList: List<CryptoCurrency>): PairsWithProviders suspend fun getPairsOnly(
userWallet: UserWallet,
initialCurrency: LeastTokenInfo,
currencyList: List<CryptoCurrency>,
): PairsWithProviders
suspend fun getExchangeStatus(txId: String): Either<UnknownError, ExchangeStatusModel> suspend fun getExchangeStatus(userWallet: UserWallet, txId: String): Either<UnknownError, ExchangeStatusModel>
@Suppress("LongParameterList") @Suppress("LongParameterList")
suspend fun findBestQuote( suspend fun findBestQuote(
userWalletId: UserWalletId, userWallet: UserWallet,
fromContractAddress: String, fromContractAddress: String,
fromNetwork: String, fromNetwork: String,
toContractAddress: String, toContractAddress: String,
@ -43,6 +52,7 @@ interface SwapRepository {
@Suppress("LongParameterList") @Suppress("LongParameterList")
suspend fun getExchangeData( suspend fun getExchangeData(
userWallet: UserWallet,
fromContractAddress: String, fromContractAddress: String,
fromNetwork: String, fromNetwork: String,
toContractAddress: String, toContractAddress: String,
@ -61,6 +71,7 @@ interface SwapRepository {
// TODO: Add target error handling, remove either ([REDACTED_JIRA]) // TODO: Add target error handling, remove either ([REDACTED_JIRA])
@Suppress("LongParameterList") @Suppress("LongParameterList")
suspend fun exchangeSent( suspend fun exchangeSent(
userWallet: UserWallet,
txId: String, txId: String,
fromNetwork: String, fromNetwork: String,
fromAddress: String, fromAddress: String,

View file

@ -1,6 +1,7 @@
package com.tangem.feature.swap.domain package com.tangem.feature.swap.domain
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
@ -10,9 +11,14 @@ class GetAvailablePairsUseCase(
) { ) {
suspend operator fun invoke( suspend operator fun invoke(
userWallet: UserWallet,
initialCurrency: LeastTokenInfo, initialCurrency: LeastTokenInfo,
currencies: List<CryptoCurrency>, currencies: List<CryptoCurrency>,
): List<SwapPairLeast> { ): List<SwapPairLeast> {
return swapRepository.getPairsOnly(initialCurrency, currencies).pairs return swapRepository.getPairsOnly(
userWallet = userWallet,
initialCurrency = initialCurrency,
currencyList = currencies,
).pairs
} }
} }

View file

@ -114,6 +114,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
} }
val pairsLeast = getPairs( val pairsLeast = getPairs(
userWallet = userWallet,
initialCurrency = LeastTokenInfo( initialCurrency = LeastTokenInfo(
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0", contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
network = currency.network.backendId, network = currency.network.backendId,
@ -197,10 +198,15 @@ internal class SwapInteractorImpl @AssistedInject constructor(
} }
private suspend fun getPairs( private suspend fun getPairs(
userWallet: UserWallet,
initialCurrency: LeastTokenInfo, initialCurrency: LeastTokenInfo,
currenciesList: List<CryptoCurrency>, currenciesList: List<CryptoCurrency>,
): PairsWithProviders { ): PairsWithProviders {
return repository.getPairs(initialCurrency, currenciesList) return repository.getPairs(
userWallet = userWallet,
initialCurrency = initialCurrency,
currencyList = currenciesList,
)
} }
override suspend fun givePermissionToSwap( override suspend fun givePermissionToSwap(
@ -308,7 +314,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
isBalanceWithoutFeeEnough: Boolean, isBalanceWithoutFeeEnough: Boolean,
): Pair<SwapProvider, SwapState> { ): Pair<SwapProvider, SwapState> {
val maybeQuotes = repository.findBestQuote( val maybeQuotes = repository.findBestQuote(
userWalletId = userWalletId, userWallet = userWallet,
fromContractAddress = fromToken.currency.getContractAddress(), fromContractAddress = fromToken.currency.getContractAddress(),
fromNetwork = fromToken.currency.network.backendId, fromNetwork = fromToken.currency.network.backendId,
toContractAddress = toToken.currency.getContractAddress(), toContractAddress = toToken.currency.getContractAddress(),
@ -619,6 +625,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
return result.fold( return result.fold(
ifRight = { txHash -> ifRight = { txHash ->
repository.exchangeSent( repository.exchangeSent(
userWallet = userWallet,
txId = swapData.transaction.txId, txId = swapData.transaction.txId,
fromNetwork = currencyToSendStatus.currency.network.backendId, fromNetwork = currencyToSendStatus.currency.network.backendId,
fromAddress = currencyToSendStatus.value.networkAddress?.defaultAddress?.value.orEmpty(), fromAddress = currencyToSendStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
@ -674,6 +681,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapProvider: SwapProvider, swapProvider: SwapProvider,
): SwapTransactionState { ): SwapTransactionState {
val exchangeData = repository.getExchangeData( val exchangeData = repository.getExchangeData(
userWallet = userWallet,
fromContractAddress = currencyToSend.currency.getContractAddress(), fromContractAddress = currencyToSend.currency.getContractAddress(),
fromNetwork = currencyToSend.currency.network.backendId, fromNetwork = currencyToSend.currency.network.backendId,
toContractAddress = currencyToGet.currency.getContractAddress(), toContractAddress = currencyToGet.currency.getContractAddress(),
@ -726,6 +734,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
ifLeft = { SwapTransactionState.Error.TransactionError(it) }, ifLeft = { SwapTransactionState.Error.TransactionError(it) },
ifRight = { txHash -> ifRight = { txHash ->
repository.exchangeSent( repository.exchangeSent(
userWallet = userWallet,
txId = exchangeDataCex.txId, txId = exchangeDataCex.txId,
fromNetwork = currencyToSend.currency.network.backendId, fromNetwork = currencyToSend.currency.network.backendId,
fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(), fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(),
@ -1132,7 +1141,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
} }
val quotes = repository.findBestQuote( val quotes = repository.findBestQuote(
userWalletId = userWalletId, userWallet = userWallet,
fromContractAddress = fromToken.getContractAddress(), fromContractAddress = fromToken.getContractAddress(),
fromNetwork = fromToken.network.backendId, fromNetwork = fromToken.network.backendId,
toContractAddress = toToken.getContractAddress(), toContractAddress = toToken.getContractAddress(),
@ -1388,6 +1397,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
selectedFee: FeeType, selectedFee: FeeType,
): SwapState { ): SwapState {
return repository.getExchangeData( return repository.getExchangeData(
userWallet = userWallet,
fromContractAddress = fromToken.currency.getContractAddress(), fromContractAddress = fromToken.currency.getContractAddress(),
fromNetwork = fromToken.currency.network.backendId, fromNetwork = fromToken.currency.network.backendId,
toContractAddress = toToken.currency.getContractAddress(), toContractAddress = toToken.currency.getContractAddress(),

View file

@ -1,7 +1,7 @@
package com.tangem.feature.swap.domain package com.tangem.feature.swap.domain
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel
@ -18,9 +18,8 @@ interface SwapTransactionRepository {
) )
suspend fun getTransactions( suspend fun getTransactions(
userWalletId: UserWalletId, userWallet: UserWallet,
cryptoCurrencyId: CryptoCurrency.ID, cryptoCurrencyId: CryptoCurrency.ID,
scanResponse: ScanResponse,
): Flow<List<SavedSwapTransactionListModel>?> ): Flow<List<SavedSwapTransactionListModel>?>
suspend fun removeTransaction( suspend fun removeTransaction(

View file

@ -6,6 +6,8 @@ import arrow.core.getOrElse
import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.bottomsheet.receive.AddressModel
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -17,8 +19,6 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.common.ui.bottomsheet.receive.AddressModel
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
@ -166,7 +166,7 @@ internal class TokenDetailsModel @Inject constructor(
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value }, appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
currentStateProvider = Provider { uiState.value }, currentStateProvider = Provider { uiState.value },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletId = userWalletId, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
) )
} }

View file

@ -12,8 +12,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.domain.*
@ -29,7 +28,6 @@ import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -39,14 +37,13 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
private val quotesRepository: QuotesRepository, private val quotesRepository: QuotesRepository,
private val quotesRepositoryV2: QuotesRepositoryV2, private val quotesRepositoryV2: QuotesRepositoryV2,
private val tokensFeatureToggles: TokensFeatureToggles, private val tokensFeatureToggles: TokensFeatureToggles,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val swapTransactionStatusStore: SwapTransactionStatusStore, private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val analyticsEventsHandler: AnalyticsEventHandler, private val analyticsEventsHandler: AnalyticsEventHandler,
@Assisted private val clickIntents: TokenDetailsClickIntents, @Assisted private val clickIntents: TokenDetailsClickIntents,
@Assisted private val appCurrencyProvider: Provider<AppCurrency>, @Assisted private val appCurrencyProvider: Provider<AppCurrency>,
@Assisted private val currentStateProvider: Provider<TokenDetailsState>, @Assisted private val currentStateProvider: Provider<TokenDetailsState>,
@Assisted private val userWalletId: UserWalletId, @Assisted private val userWallet: UserWallet,
@Assisted private val cryptoCurrency: CryptoCurrency, @Assisted private val cryptoCurrency: CryptoCurrency,
) { ) {
@ -60,14 +57,9 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
} }
suspend operator fun invoke(): Flow<PersistentList<ExchangeUM>> { suspend operator fun invoke(): Flow<PersistentList<ExchangeUM>> {
val selectedWallet = getSelectedWalletSyncUseCase().fold(
ifLeft = { return emptyFlow() },
ifRight = { it },
)
return swapTransactionRepository.getTransactions( return swapTransactionRepository.getTransactions(
userWalletId = userWalletId, userWallet = userWallet,
cryptoCurrencyId = cryptoCurrency.id, cryptoCurrencyId = cryptoCurrency.id,
scanResponse = selectedWallet.scanResponse,
).conflate() ).conflate()
.map { savedTransactions -> .map { savedTransactions ->
val quotes = savedTransactions val quotes = savedTransactions
@ -91,7 +83,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
val shouldDispose = selectedTx.activeStatus?.isAutoDisposable == true || isForceDispose val shouldDispose = selectedTx.activeStatus?.isAutoDisposable == true || isForceDispose
if (shouldDispose) { if (shouldDispose) {
swapTransactionRepository.removeTransaction( swapTransactionRepository.removeTransaction(
userWalletId = userWalletId, userWalletId = userWallet.walletId,
fromCryptoCurrency = selectedTx.fromCryptoCurrency, fromCryptoCurrency = selectedTx.fromCryptoCurrency,
toCryptoCurrency = selectedTx.toCryptoCurrency, toCryptoCurrency = selectedTx.toCryptoCurrency,
txId = selectedTx.info.txId, txId = selectedTx.info.txId,
@ -117,7 +109,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
} }
private suspend fun getExchangeStatus(txId: String, provider: SwapProvider): ExchangeStatusModel? { private suspend fun getExchangeStatus(txId: String, provider: SwapProvider): ExchangeStatusModel? {
return swapRepository.getExchangeStatus(txId) return swapRepository.getExchangeStatus(userWallet = userWallet, txId = txId)
.fold( .fold(
ifLeft = { null }, ifLeft = { null },
ifRight = { statusModel -> ifRight = { statusModel ->
@ -157,7 +149,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
val refundContractAddress = status.refundContractAddress val refundContractAddress = status.refundContractAddress
if (refundNetwork != null && refundContractAddress != null) { if (refundNetwork != null && refundContractAddress != null) {
return addCryptoCurrenciesUseCase( return addCryptoCurrenciesUseCase(
userWalletId = userWalletId, userWalletId = userWallet.walletId,
contractAddress = refundContractAddress, contractAddress = refundContractAddress,
networkId = refundNetwork, networkId = refundNetwork,
).getOrNull() ).getOrNull()
@ -219,7 +211,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
clickIntents: TokenDetailsClickIntents, clickIntents: TokenDetailsClickIntents,
appCurrencyProvider: Provider<AppCurrency>, appCurrencyProvider: Provider<AppCurrency>,
currentStateProvider: Provider<TokenDetailsState>, currentStateProvider: Provider<TokenDetailsState>,
userWalletId: UserWalletId, userWallet: UserWallet,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
): ExchangeStatusFactory ): ExchangeStatusFactory
} }

View file

@ -10,12 +10,12 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -36,7 +36,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
@Assisted private val clickIntents: TokenDetailsClickIntents, @Assisted private val clickIntents: TokenDetailsClickIntents,
@Assisted private val cryptoCurrency: CryptoCurrency, @Assisted private val cryptoCurrency: CryptoCurrency,
@Assisted appCurrencyProvider: Provider<AppCurrency>, @Assisted appCurrencyProvider: Provider<AppCurrency>,
@Assisted userWalletId: UserWalletId, @Assisted userWallet: UserWallet,
@Assisted cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>, @Assisted cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventsHandler: AnalyticsEventHandler, private val analyticsEventsHandler: AnalyticsEventHandler,
@ -49,7 +49,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
clickIntents = clickIntents, clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider, appCurrencyProvider = appCurrencyProvider,
currentStateProvider = currentStateProvider, currentStateProvider = currentStateProvider,
userWalletId = userWalletId, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
) )
} }
@ -61,7 +61,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
appCurrencyProvider = appCurrencyProvider, appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents, clickIntents = clickIntents,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
userWalletId = userWalletId, userWallet = userWallet,
) )
} }
@ -210,7 +210,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
clickIntents: TokenDetailsClickIntents, clickIntents: TokenDetailsClickIntents,
appCurrencyProvider: Provider<AppCurrency>, appCurrencyProvider: Provider<AppCurrency>,
currentStateProvider: Provider<TokenDetailsState>, currentStateProvider: Provider<TokenDetailsState>,
userWalletId: UserWalletId, userWallet: UserWallet,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>, cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
): ExpressStatusFactory ): ExpressStatusFactory

View file

@ -14,8 +14,8 @@ import com.tangem.domain.onramp.model.OnrampStatus.Status.*
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter
import com.tangem.utils.Provider import com.tangem.utils.Provider
@ -39,7 +39,7 @@ internal class OnrampStatusFactory @AssistedInject constructor(
@Assisted private val appCurrencyProvider: Provider<AppCurrency>, @Assisted private val appCurrencyProvider: Provider<AppCurrency>,
@Assisted private val clickIntents: TokenDetailsClickIntents, @Assisted private val clickIntents: TokenDetailsClickIntents,
@Assisted private val cryptoCurrency: CryptoCurrency, @Assisted private val cryptoCurrency: CryptoCurrency,
@Assisted private val userWalletId: UserWalletId, @Assisted private val userWallet: UserWallet,
) { ) {
private val onrampTransactionStateConverter by lazy(LazyThreadSafetyMode.NONE) { private val onrampTransactionStateConverter by lazy(LazyThreadSafetyMode.NONE) {
@ -54,7 +54,7 @@ internal class OnrampStatusFactory @AssistedInject constructor(
operator fun invoke(): Flow<List<ExpressTransactionStateUM.OnrampUM>> { operator fun invoke(): Flow<List<ExpressTransactionStateUM.OnrampUM>> {
return getOnrampTransactionsUseCase( return getOnrampTransactionsUseCase(
userWalletId = userWalletId, userWalletId = userWallet.walletId,
cryptoCurrencyId = cryptoCurrency.id, cryptoCurrencyId = cryptoCurrency.id,
).map { maybeTransaction -> ).map { maybeTransaction ->
maybeTransaction.fold( maybeTransaction.fold(
@ -82,7 +82,7 @@ internal class OnrampStatusFactory @AssistedInject constructor(
return if (onrampTx.activeStatus.isTerminal) { return if (onrampTx.activeStatus.isTerminal) {
onrampTx onrampTx
} else { } else {
getOnrampStatusUseCase(onrampTx.info.txId).fold( getOnrampStatusUseCase(userWallet = userWallet, onrampTx.info.txId).fold(
ifLeft = { ifLeft = {
Timber.e("Couldn't update onramp status. $it") Timber.e("Couldn't update onramp status. $it")
onrampTx onrampTx
@ -150,7 +150,7 @@ internal class OnrampStatusFactory @AssistedInject constructor(
appCurrencyProvider: Provider<AppCurrency>, appCurrencyProvider: Provider<AppCurrency>,
clickIntents: TokenDetailsClickIntents, clickIntents: TokenDetailsClickIntents,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
userWalletId: UserWalletId, userWallet: UserWallet,
): OnrampStatusFactory ): OnrampStatusFactory
} }
} }

View file

@ -15,6 +15,7 @@ import com.tangem.domain.nft.FetchNFTCollectionsUseCase
import com.tangem.domain.settings.* import com.tangem.domain.settings.*
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
@ -107,9 +108,7 @@ internal class WalletModel @Inject constructor(
subscribeToUserWalletsUpdates() subscribeToUserWalletsUpdates()
subscribeOnBalanceHiding() subscribeOnBalanceHiding()
subscribeOnSelectedWalletFlow() subscribeOnSelectedWalletFlow()
subscribeToScreenBackgroundState()
subscribeOnPushNotificationsPermission() subscribeOnPushNotificationsPermission()
subscribeOnExpressTransactionsUpdates()
subscribeOnNFTUpdates() subscribeOnNFTUpdates()
clickIntents.initialize(innerWalletRouter, modelScope) clickIntents.initialize(innerWalletRouter, modelScope)
@ -223,6 +222,8 @@ internal class WalletModel @Inject constructor(
} }
walletDeepLinksHandler.registerForWallet(scope = modelScope, userWallet = selectedWallet) walletDeepLinksHandler.registerForWallet(scope = modelScope, userWallet = selectedWallet)
subscribeOnExpressTransactionsUpdates(selectedWallet)
subscribeToScreenBackgroundState(selectedWallet)
} }
.flowOn(dispatchers.main) .flowOn(dispatchers.main)
.launchIn(modelScope) .launchIn(modelScope)
@ -231,7 +232,7 @@ internal class WalletModel @Inject constructor(
// We need to update the current wallet quotes if the application was in the background for more than 10 seconds // We need to update the current wallet quotes if the application was in the background for more than 10 seconds
// and then returned to the foreground // and then returned to the foreground
private fun subscribeToScreenBackgroundState() { private fun subscribeToScreenBackgroundState(userWallet: UserWallet) {
screenLifecycleProvider.isBackgroundState screenLifecycleProvider.isBackgroundState
.onEach { isBackground -> .onEach { isBackground ->
expressTxStatusTaskScheduler.cancelTask() expressTxStatusTaskScheduler.cancelTask()
@ -241,30 +242,31 @@ internal class WalletModel @Inject constructor(
isBackground -> needToRefreshTimer() isBackground -> needToRefreshTimer()
needToRefreshWallet && !isBackground -> { needToRefreshWallet && !isBackground -> {
triggerRefreshWalletQuotes() triggerRefreshWalletQuotes()
subscribeOnExpressTransactionsUpdates() subscribeOnExpressTransactionsUpdates(userWallet)
} }
!isBackground -> subscribeOnExpressTransactionsUpdates() !isBackground -> subscribeOnExpressTransactionsUpdates(userWallet)
} }
} }
.launchIn(modelScope) .launchIn(modelScope)
.saveIn(expressStatusJobHolder)
} }
private fun subscribeOnExpressTransactionsUpdates() { private fun subscribeOnExpressTransactionsUpdates(userWallet: UserWallet) {
modelScope.launch(dispatchers.main) { expressTxStatusTaskScheduler.cancelTask()
expressTxStatusTaskScheduler.cancelTask() expressTxStatusTaskScheduler.scheduleTask(
expressTxStatusTaskScheduler.scheduleTask( modelScope,
modelScope, PeriodicTask(
PeriodicTask( isDelayFirst = false,
isDelayFirst = false, delay = EXPRESS_STATUS_UPDATE_DELAY,
delay = EXPRESS_STATUS_UPDATE_DELAY, task = {
task = { runCatching {
runCatching { onrampStatusFactory.updateOnrmapTransactionStatuses() } onrampStatusFactory.updateOnrmapTransactionStatuses(userWallet)
}, }
onSuccess = { /* no-op */ }, },
onError = { /* no-op */ }, onSuccess = { /* no-op */ },
), onError = { /* no-op */ },
) ),
}.saveIn(expressStatusJobHolder) )
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)

View file

@ -11,6 +11,7 @@ import com.tangem.domain.onramp.OnrampUpdateTransactionStatusUseCase
import com.tangem.domain.onramp.model.OnrampStatus import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.OnrampStatus.Status.* import com.tangem.domain.onramp.model.OnrampStatus.Status.*
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,22 +41,22 @@ internal class OnrampStatusFactory @Inject constructor(
} }
} }
suspend fun updateOnrmapTransactionStatuses() = withContext(dispatchers.io) { suspend fun updateOnrmapTransactionStatuses(userWallet: UserWallet) = withContext(dispatchers.io) {
val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content
?: return@withContext ?: return@withContext
singleWalletState.expressTxs.map { tx -> singleWalletState.expressTxs.map { tx ->
async { async {
if (tx is ExpressTransactionStateUM.OnrampUM) { if (tx is ExpressTransactionStateUM.OnrampUM) {
updateOnrampTxStatus(tx) updateOnrampTxStatus(userWallet, tx)
} }
} }
}.awaitAll() }.awaitAll()
} }
private suspend fun updateOnrampTxStatus(onrampTx: ExpressTransactionStateUM.OnrampUM) { private suspend fun updateOnrampTxStatus(userWallet: UserWallet, onrampTx: ExpressTransactionStateUM.OnrampUM) {
if (!onrampTx.activeStatus.isTerminal) { if (!onrampTx.activeStatus.isTerminal) {
getOnrampStatusUseCase(onrampTx.info.txId).fold( getOnrampStatusUseCase(userWallet = userWallet, onrampTx.info.txId).fold(
ifLeft = { ifLeft = {
Timber.e("Couldn't update onramp status. $it") Timber.e("Couldn't update onramp status. $it")
}, },

View file

@ -1,10 +1,5 @@
package com.tangem.lib.auth package com.tangem.lib.auth
interface ExpressAuthProvider { interface ExpressAuthProvider {
fun getUserId(): String
fun getSessionId(): String fun getSessionId(): String
fun getRefCode(): String
} }