diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 097bcd4c84..eabd10f670 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -119,6 +119,7 @@ dependencies { implementation(projects.domain.card) implementation(projects.domain.demo) implementation(projects.domain.demo.models) + implementation(projects.domain.express) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.settings) diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index e963c1f140..d0cead55f7 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -1,10 +1,10 @@ package com.tangem.tap.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.express.ExpressServiceFetcher import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -46,14 +46,14 @@ internal object ActivityModule { @Singleton fun provideDefaultRampManager( appStateHolder: AppStateHolder, - expressServiceLoader: ExpressServiceLoader, + expressServiceFetcher: ExpressServiceFetcher, currenciesRepository: CurrenciesRepository, excludedBlockchains: ExcludedBlockchains, dispatchers: CoroutineDispatcherProvider, ): RampStateManager { return DefaultRampManager( sellService = Provider { requireNotNull(appStateHolder.sellService) }, - expressServiceLoader = expressServiceLoader, + expressServiceFetcher = expressServiceFetcher, currenciesRepository = currenciesRepository, dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 5cf37e3a1c..22b5f4c524 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -6,12 +6,11 @@ import arrow.core.raise.either import arrow.core.raise.ensure import arrow.core.right import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE -import com.tangem.datasource.api.express.models.response.Asset -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.domain.core.lce.Lce import com.tangem.domain.exchange.ExpressAvailabilityState import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.express.ExpressServiceFetcher +import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet @@ -26,10 +25,9 @@ import com.tangem.utils.isNullOrZero import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull -@Suppress("LongParameterList") internal class DefaultRampManager( private val sellService: Provider, - private val expressServiceLoader: ExpressServiceLoader, + private val expressServiceFetcher: ExpressServiceFetcher, private val currenciesRepository: CurrenciesRepository, private val dispatchers: CoroutineDispatcherProvider, excludedBlockchains: ExcludedBlockchains, @@ -107,7 +105,7 @@ internal class DefaultRampManager( } override fun getExpressInitializationStatus(userWalletId: UserWalletId): Flow { - return expressServiceLoader.getInitializationStatus(userWalletId) + return expressServiceFetcher.getInitializationStatus(userWalletId) } override suspend fun getSendUnavailabilityReason( @@ -147,14 +145,14 @@ internal class DefaultRampManager( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): ExpressAvailabilityState { - val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() + val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull() ?: return ExpressAvailabilityState.Loading return when (asset) { is Lce.Error -> ExpressAvailabilityState.Error is Lce.Loading -> ExpressAvailabilityState.Loading is Lce.Content -> { - val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } - foundAsset?.exchangeAvailable?.toSwapAvailabilityState() + val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) } + foundAsset?.isExchangeAvailable?.toSwapAvailabilityState() ?: ExpressAvailabilityState.AssetNotFound } } @@ -164,15 +162,15 @@ internal class DefaultRampManager( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): ExpressAvailabilityState { - val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() + val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull() ?: return ExpressAvailabilityState.Loading return when (asset) { is Lce.Error -> ExpressAvailabilityState.Error is Lce.Loading -> ExpressAvailabilityState.Loading is Lce.Content -> { - val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } - foundAsset?.onrampAvailable?.toOnrampAvailabilityState() + val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) } + foundAsset?.isOnrampAvailable?.toOnrampAvailabilityState() ?: ExpressAvailabilityState.AssetNotFound } } @@ -207,8 +205,13 @@ internal class DefaultRampManager( } } - private fun CryptoCurrency.findAssetPredicate(asset: Asset): Boolean { - val contractAddress = (this as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE - return asset.network == network.backendId && asset.contractAddress.equals(contractAddress, ignoreCase = true) + private fun CryptoCurrency.findAssetPredicate(assetId: ExpressAsset.ID): Boolean { + val currencyAssedId = ExpressAsset.ID( + networkId = this.network.backendId, + contractAddress = (this as? CryptoCurrency.Token)?.contractAddress, + ) + + return assetId.networkId == currencyAssedId.networkId && + assetId.contractAddress.equals(currencyAssedId.contractAddress, ignoreCase = true) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/TangemExpressValues.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/TangemExpressValues.kt deleted file mode 100644 index 984184791c..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/TangemExpressValues.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.datasource.api.express.models - -object TangemExpressValues { - const val EMPTY_CONTRACT_ADDRESS_VALUE = "0" -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt deleted file mode 100644 index d9ae44675f..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.datasource.di.exchangeservice - -import com.tangem.datasource.exchangeservice.swap.DefaultExpressServiceLoader -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface ExchangeServiceLoaderModule { - - @Binds - @Singleton - fun bindExpressServiceLoader(defaultExpressServiceLoader: DefaultExpressServiceLoader): ExpressServiceLoader -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt deleted file mode 100644 index e591ee227e..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/ExpressServiceLoader.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.datasource.exchangeservice.swap - -import com.tangem.datasource.api.express.models.request.LeastTokenInfo -import com.tangem.datasource.api.express.models.response.Asset -import com.tangem.domain.core.lce.Lce -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import kotlinx.coroutines.flow.Flow - -/** - * Express service loader - * -[REDACTED_AUTHOR] - */ -interface ExpressServiceLoader { - - /** Update service using [userWallet] and [userTokens] */ - suspend fun update(userWallet: UserWallet, userTokens: List) - - /** Get initialization status by [userWalletId] */ - fun getInitializationStatus(userWalletId: UserWalletId): Flow>> -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt b/data/express/src/main/java/com/tangem/data/express/DefaultExpressServiceFetcher.kt similarity index 65% rename from core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt rename to data/express/src/main/java/com/tangem/data/express/DefaultExpressServiceFetcher.kt index 11192e7af9..65e0556973 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultExpressServiceLoader.kt +++ b/data/express/src/main/java/com/tangem/data/express/DefaultExpressServiceFetcher.kt @@ -1,17 +1,23 @@ -package com.tangem.datasource.exchangeservice.swap +package com.tangem.data.express +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.data.express.converter.ExpressAssetConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.express.TangemExpressApi 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.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.userwallet.UserWalletsStore import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.utils.catchOn import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.express.ExpressServiceFetcher +import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -19,56 +25,72 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject -typealias InitializationStatusFlow = MutableStateFlow>> +typealias InitializationStatusFlow = MutableStateFlow>> /** - * Default implementation of [ExpressServiceLoader] + * Default implementation of [ExpressServiceFetcher] * * @property tangemExpressApi express api * @property expressAssetsStore local storage * [REDACTED_AUTHOR] */ -internal class DefaultExpressServiceLoader @Inject constructor( +internal class DefaultExpressServiceFetcher @Inject constructor( private val tangemExpressApi: TangemExpressApi, private val expressAssetsStore: ExpressAssetsStore, private val appPreferencesStore: AppPreferencesStore, + private val userWalletsStore: UserWalletsStore, private val dispatchers: CoroutineDispatcherProvider, -) : ExpressServiceLoader { +) : ExpressServiceFetcher { private val initializationStatuses = MutableStateFlow>(value = emptyMap()) - override suspend fun update(userWallet: UserWallet, userTokens: List) { - withContext(dispatchers.io) { + override suspend fun fetch(userWalletId: UserWalletId, assetIds: Set): Either = + either { + val userWallet = arrow.core.raise.catch( + block = { userWalletsStore.getSyncStrict(userWalletId) }, + catch = ::raise, + ) + + fetch(userWallet = userWallet, assetIds = assetIds).bind() + } + + override suspend fun fetch(userWallet: UserWallet, assetIds: Set): Either { + return Either.catchOn(dispatchers.io) { val initializationStatus = getInitializationStatusInternal(userWallet.walletId) try { - if (userTokens.isNotEmpty()) { + if (assetIds.isNotEmpty()) { + val tokenList = assetIds.map { + LeastTokenInfo(contractAddress = it.contractAddress, network = it.networkId) + } + val response = tangemExpressApi.getAssets( userWalletId = userWallet.walletId.stringValue, refCode = getRefCode(userWallet, appPreferencesStore), - body = AssetsRequestBody(tokensList = userTokens), + body = AssetsRequestBody(tokensList = tokenList), ).getOrThrow() expressAssetsStore.store(userWallet.walletId, response) - initializationStatus.update { response.lceContent() } + val expressAssets = ExpressAssetConverter.convertList(response) + initializationStatus.update { expressAssets.lceContent() } } } catch (e: Throwable) { if (expressAssetsStore.getSyncOrNull(userWallet.walletId) == null) { initializationStatus.update { e.lceError() } } Timber.e(e, "Unable to fetch assets for: ${userWallet.walletId.stringValue}") + throw e } } } - override fun getInitializationStatus(userWalletId: UserWalletId): Flow>> { + override fun getInitializationStatus(userWalletId: UserWalletId): Flow>> { return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } } } @@ -77,7 +99,7 @@ internal class DefaultExpressServiceLoader @Inject constructor( val initializationStatus = initializationStatuses.value[userWalletId] if (initializationStatus != null) return initializationStatus - val cached = expressAssetsStore.getSyncOrNull(userWalletId) + val cached = expressAssetsStore.getSyncOrNull(userWalletId)?.let(ExpressAssetConverter::convertList) val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading()) initializationStatuses.update { statuses -> diff --git a/data/express/src/main/java/com/tangem/data/express/converter/ExpressAssetConverter.kt b/data/express/src/main/java/com/tangem/data/express/converter/ExpressAssetConverter.kt new file mode 100644 index 0000000000..9c5ccace5f --- /dev/null +++ b/data/express/src/main/java/com/tangem/data/express/converter/ExpressAssetConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.data.express.converter + +import com.tangem.datasource.api.express.models.response.Asset +import com.tangem.domain.express.models.ExpressAsset +import com.tangem.utils.converter.Converter + +/** + * Converts an [Asset] from the data layer to an [ExpressAsset] in the domain layer. + * +[REDACTED_AUTHOR] + */ +internal object ExpressAssetConverter : Converter { + + override fun convert(value: Asset): ExpressAsset { + return ExpressAsset( + id = ExpressAsset.ID( + networkId = value.network, + contractAddress = value.contractAddress, + ), + isExchangeAvailable = value.exchangeAvailable, + isOnrampAvailable = value.onrampAvailable, + ) + } +} \ No newline at end of file diff --git a/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt b/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt index 299b631162..c696e08aec 100644 --- a/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt +++ b/data/express/src/main/java/com/tangem/data/express/di/ExpressDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.express.di import com.squareup.moshi.Moshi import com.tangem.data.express.DefaultExpressRepository +import com.tangem.data.express.DefaultExpressServiceFetcher import com.tangem.data.express.converter.ExpressErrorConverter import com.tangem.data.express.error.DefaultExpressErrorResolver import com.tangem.datasource.api.express.TangemExpressApi @@ -10,6 +11,7 @@ import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.express.ExpressErrorResolver import com.tangem.domain.express.ExpressRepository +import com.tangem.domain.express.ExpressServiceFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -43,4 +45,10 @@ internal object ExpressDataModule { dispatchers = dispatchers, ) } + + @Provides + @Singleton + fun provideExpressServiceFetcher(impl: DefaultExpressServiceFetcher): ExpressServiceFetcher { + return impl + } } \ No newline at end of file diff --git a/data/onramp/build.gradle.kts b/data/onramp/build.gradle.kts index 47909aa6bf..ada7a8f3b0 100644 --- a/data/onramp/build.gradle.kts +++ b/data/onramp/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(projects.domain.walletManager) implementation(projects.domain.appTheme.models) implementation(projects.domain.models) + implementation(projects.domain.express.models) // region DI diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 603a4a806f..be4b3d4460 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -12,7 +12,6 @@ import com.tangem.data.onramp.converters.error.OnrampErrorConverter import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.express.TangemExpressApi -import com.tangem.datasource.api.express.models.TangemExpressValues import com.tangem.datasource.api.express.models.response.ExchangeProvider import com.tangem.datasource.api.express.models.response.ExchangeProviderType import com.tangem.datasource.api.express.models.response.ExpressErrorResponse @@ -38,6 +37,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObject import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.storeObject +import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -560,7 +560,7 @@ internal class DefaultOnrampRepository( } private fun CryptoCurrency.getContractAddress(): String = when (this) { - is CryptoCurrency.Coin -> TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE + is CryptoCurrency.Coin -> ExpressAsset.EMPTY_CONTRACT_ADDRESS_VALUE is CryptoCurrency.Token -> this.contractAddress } diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index eb3fe93e66..d54ae54bf7 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.domain.card) implementation(projects.domain.core) implementation(projects.domain.demo) + implementation(projects.domain.express) implementation(projects.domain.legacy) implementation(projects.domain.models) implementation(projects.domain.staking) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcher.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcher.kt index b1f250701b..8fc010aa4d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcher.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcher.kt @@ -7,15 +7,14 @@ import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.datasource.api.common.response.ApiResponseError -import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE -import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.core.utils.catchOn import com.tangem.domain.demo.models.DemoConfig +import com.tangem.domain.express.ExpressServiceFetcher +import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher @@ -32,7 +31,7 @@ import timber.log.Timber * @property userTokensResponseStore store of [UserTokensResponse] * @property userTokensSaver user tokens saver * @property cardCryptoCurrencyFactory factory for creating crypto currencies for specified card - * @property expressServiceLoader express service loader + * @property expressServiceFetcher express service loader * @property dispatchers dispatchers * [REDACTED_AUTHOR] @@ -46,7 +45,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher( private val userTokensResponseStore: UserTokensResponseStore, private val userTokensSaver: UserTokensSaver, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, - private val expressServiceLoader: ExpressServiceLoader, + private val expressServiceFetcher: ExpressServiceFetcher, private val dispatchers: CoroutineDispatcherProvider, ) : MultiWalletCryptoCurrenciesFetcher { @@ -109,14 +108,14 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher( } private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) { - val tokens = userTokens.tokens.map { token -> - LeastTokenInfo( - contractAddress = token.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, - network = token.networkId, + val tokens = userTokens.tokens.mapTo(hashSetOf()) { token -> + ExpressAsset.ID( + networkId = token.networkId, + contractAddress = token.contractAddress, ) } - expressServiceLoader.update(userWallet = userWallet, userTokens = tokens) + expressServiceFetcher.fetch(userWallet = userWallet, assetIds = tokens) } private fun createDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt index c360054a04..8b6594ce3b 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt @@ -7,11 +7,11 @@ import com.tangem.data.tokens.AccountListCryptoCurrenciesFetcher import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesFetcher import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.demo.models.DemoConfig +import com.tangem.domain.express.ExpressServiceFetcher import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -33,7 +33,7 @@ internal class MultiWalletCryptoCurrenciesFetcherModule { userTokensResponseStore: UserTokensResponseStore, userTokensSaver: UserTokensSaver, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, - expressServiceLoader: ExpressServiceLoader, + expressServiceFetcher: ExpressServiceFetcher, walletAccountsFetcher: WalletAccountsFetcher, dispatchers: CoroutineDispatcherProvider, ): MultiWalletCryptoCurrenciesFetcher { @@ -56,7 +56,7 @@ internal class MultiWalletCryptoCurrenciesFetcherModule { userTokensResponseStore = userTokensResponseStore, userTokensSaver = userTokensSaver, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, - expressServiceLoader = expressServiceLoader, + expressServiceFetcher = expressServiceFetcher, dispatchers = dispatchers, ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 23f10b2f4d..e41afb0cdc 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -5,22 +5,14 @@ import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.UserTokensSaver -import com.tangem.data.tokens.repository.DefaultCurrenciesRepository -import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository -import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository -import com.tangem.data.tokens.repository.DefaultTokenReceiveWarningsViewedRepository -import com.tangem.data.tokens.repository.DefaultYieldSupplyWarningsViewedRepository +import com.tangem.data.tokens.repository.* import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.token.TokenReceiveWarningActionStore import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.CurrencyChecksRepository -import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository -import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository -import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository +import com.tangem.domain.express.ExpressServiceFetcher +import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -42,7 +34,7 @@ internal object TokensDataModule { walletManagersFacade: WalletManagersFacade, cacheRegistry: CacheRegistry, dispatchers: CoroutineDispatcherProvider, - expressServiceLoader: ExpressServiceLoader, + expressServiceFetcher: ExpressServiceFetcher, excludedBlockchains: ExcludedBlockchains, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, tokensSaver: UserTokensSaver, @@ -54,7 +46,7 @@ internal object TokensDataModule { walletManagersFacade = walletManagersFacade, cacheRegistry = cacheRegistry, userTokensResponseStore = userTokensResponseStore, - expressServiceLoader = expressServiceLoader, + expressServiceFetcher = expressServiceFetcher, dispatchers = dispatchers, excludedBlockchains = excludedBlockchains, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 686fcb7e8d..4c0102f124 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -10,17 +10,16 @@ import com.tangem.data.common.currency.* import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE -import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.core.error.DataError import com.tangem.domain.demo.models.DemoConfig +import com.tangem.domain.express.ExpressServiceFetcher +import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network @@ -40,7 +39,7 @@ internal class DefaultCurrenciesRepository( private val userWalletsStore: UserWalletsStore, private val walletManagersFacade: WalletManagersFacade, private val cacheRegistry: CacheRegistry, - private val expressServiceLoader: ExpressServiceLoader, + private val expressServiceFetcher: ExpressServiceFetcher, private val dispatchers: CoroutineDispatcherProvider, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val userTokensSaver: UserTokensSaver, @@ -649,15 +648,15 @@ internal class DefaultCurrenciesRepository( // TODO [REDACTED_JIRA] private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) { - val tokens = userTokens.tokens.map { token -> - LeastTokenInfo( - contractAddress = token.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, - network = token.networkId, + val tokens = userTokens.tokens.mapTo(hashSetOf()) { token -> + ExpressAsset.ID( + networkId = token.networkId, + contractAddress = token.contractAddress, ) } coroutineScope { - launch { expressServiceLoader.update(userWallet, tokens) } + launch { expressServiceFetcher.fetch(userWallet, tokens) } } } @@ -666,11 +665,11 @@ internal class DefaultCurrenciesRepository( cryptoCurrencies: List, refresh: Boolean = false, ) { - val tokens = cryptoCurrencies.map { currency -> + val tokens = cryptoCurrencies.mapTo(hashSetOf()) { currency -> val tokenCurrency = currency as? CryptoCurrency.Token - LeastTokenInfo( - contractAddress = tokenCurrency?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, - network = currency.network.backendId, + ExpressAsset.ID( + networkId = currency.network.backendId, + contractAddress = tokenCurrency?.contractAddress, ) } cacheRegistry.invokeOnExpire( @@ -678,7 +677,7 @@ internal class DefaultCurrenciesRepository( skipCache = refresh, block = { coroutineScope { - launch { expressServiceLoader.update(userWallet, tokens) } + launch { expressServiceFetcher.fetch(userWallet, tokens) } } }, ) diff --git a/data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcherTest.kt b/data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcherTest.kt index 9b685b565c..47187cae61 100644 --- a/data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcherTest.kt +++ b/data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesFetcherTest.kt @@ -11,14 +11,13 @@ import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.ApiResponseError -import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE -import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.demo.models.DemoConfig +import com.tangem.domain.express.ExpressServiceFetcher +import com.tangem.domain.express.models.ExpressAsset import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isMultiCurrency @@ -45,7 +44,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true) private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true) private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk() - private val expressServiceLoader: ExpressServiceLoader = mockk(relaxUnitFun = true) + private val expressServiceFetcher: ExpressServiceFetcher = mockk(relaxUnitFun = true) private val fetcher = DefaultMultiWalletCryptoCurrenciesFetcher( demoConfig = DemoConfig(), @@ -55,7 +54,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { userTokensResponseStore = userTokensResponseStore, userTokensSaver = userTokensSaver, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, - expressServiceLoader = expressServiceLoader, + expressServiceFetcher = expressServiceFetcher, dispatchers = TestingCoroutineDispatcherProvider(), ) @@ -67,7 +66,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { userTokensResponseStore, userTokensSaver, cardCryptoCurrencyFactory, - expressServiceLoader, + expressServiceFetcher, ) } @@ -131,6 +130,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) } returns userTokensResponse + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = userTokensResponse.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -144,7 +150,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId()) } } @@ -170,6 +176,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) } returns apiResponse.data + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = defaultResponse.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -183,7 +196,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId()) } coVerify(inverse = true) { @@ -212,6 +225,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) } returns apiResponse.data + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = apiResponse.data.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -224,7 +244,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId()) } coVerify(inverse = true) { @@ -273,6 +293,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) } returns userTokensResponse + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = userTokensResponse.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -286,7 +313,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId()) } coVerify(inverse = true) { @@ -317,6 +344,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) } returns defaultResponse + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = defaultResponse.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -330,7 +364,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId()) } coVerify(inverse = true) { @@ -383,6 +417,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) } returns userTokensResponse + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = userTokensResponse.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -398,7 +439,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { userTokensSaver.push(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId()) } } @@ -429,6 +470,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) } returns defaultResponse + coEvery { + expressServiceFetcher.fetch( + userWallet = mockUserWallet, + assetIds = defaultResponse.toAssetId(), + ) + } returns Unit.right() + // Act val actual = fetcher(params) @@ -443,7 +491,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { userTokensSaver.push(userWalletId = params.userWalletId, response = defaultResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse) - expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) + expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId()) } coVerify(inverse = true) { @@ -471,11 +519,11 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest { ), ) - fun UserTokensResponse.toLeastTokens(): List { - return tokens.map { token -> - LeastTokenInfo( - contractAddress = token.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, - network = token.networkId, + fun UserTokensResponse.toAssetId(): Set { + return tokens.mapTo(hashSetOf()) { token -> + ExpressAsset.ID( + networkId = token.networkId, + contractAddress = token.contractAddress, ) } } diff --git a/domain/express/build.gradle.kts b/domain/express/build.gradle.kts index fc0bfacaa9..3b501122fc 100644 --- a/domain/express/build.gradle.kts +++ b/domain/express/build.gradle.kts @@ -1,24 +1,10 @@ plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.serialization) id("configuration") } -android { - namespace = "com.tangem.domain.express.models" -} - dependencies { - /** Core */ - implementation(projects.core.utils) - implementation(projects.core.datasource) - - /** Domain */ - implementation(projects.domain.express.models) + api(projects.domain.express.models) api(projects.domain.models) - implementation(projects.domain.tokens.models) - - /** Other */ - implementation(deps.moshi.adapters) } \ No newline at end of file diff --git a/domain/express/models/build.gradle.kts b/domain/express/models/build.gradle.kts index a7e2520b52..701087a4b1 100644 --- a/domain/express/models/build.gradle.kts +++ b/domain/express/models/build.gradle.kts @@ -1,12 +1,10 @@ plugins { alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.kotlin.serialization) id("configuration") } dependencies { - /** Domain */ - implementation(projects.domain.tokens.models) - - /* Other */ implementation(deps.moshi.adapters) + implementation(deps.kotlin.serialization) } \ No newline at end of file diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressAsset.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressAsset.kt new file mode 100644 index 0000000000..0d8b046954 --- /dev/null +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressAsset.kt @@ -0,0 +1,48 @@ +package com.tangem.domain.express.models + +import kotlinx.serialization.Serializable + +/** + * Express asset model. + * + * @property id The unique identifier of the Express asset. + * @property isExchangeAvailable Indicates if exchange is available for this asset. + * @property isOnrampAvailable Indicates if onramp is available for this asset (nullable). + * +[REDACTED_AUTHOR] + */ +@Serializable +data class ExpressAsset( + val id: ID, + val isExchangeAvailable: Boolean, + val isOnrampAvailable: Boolean?, +) { + + /** + * Unique identifier for an Express asset, consisting of network ID and contract address. + * + * @property networkId The network ID of the asset. + * @property contractAddress The contract address of the asset. + */ + @Serializable + data class ID(val networkId: String, val contractAddress: String) { + + companion object { + + /** + * Creates an [ID] instance, defaulting the contract address to "0" if null. + * + * @param networkId The network ID of the asset. + * @param contractAddress The contract address of the asset, or null to default to "0". + * @return An [ID] instance with the specified network ID and contract address. + */ + operator fun invoke(networkId: String, contractAddress: String?): ID { + return ID(networkId = networkId, contractAddress = contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE) + } + } + } + + companion object { + const val EMPTY_CONTRACT_ADDRESS_VALUE = "0" + } +} \ No newline at end of file diff --git a/domain/express/src/main/java/com/tangem/domain/express/ExpressServiceFetcher.kt b/domain/express/src/main/java/com/tangem/domain/express/ExpressServiceFetcher.kt new file mode 100644 index 0000000000..1d7d7b1dbc --- /dev/null +++ b/domain/express/src/main/java/com/tangem/domain/express/ExpressServiceFetcher.kt @@ -0,0 +1,38 @@ +package com.tangem.domain.express + +import arrow.core.Either +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.express.models.ExpressAsset +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import kotlinx.coroutines.flow.Flow + +/** + * Service interface to fetch Express assets data and monitor initialization status. + */ +interface ExpressServiceFetcher { + + /** + * Fetches Express assets data for the given user wallet ID and asset IDs. + * + * @param userWalletId The ID of the user wallet. + * @param assetIds The list of Express asset IDs to fetch. + */ + suspend fun fetch(userWalletId: UserWalletId, assetIds: Set): Either + + /** + * Fetches Express assets data for the given user wallet and asset IDs. + * + * @param userWallet The user wallet for which to fetch assets. + * @param assetIds The list of Express asset IDs to fetch. + */ + suspend fun fetch(userWallet: UserWallet, assetIds: Set): Either + + /** + * Returns a flow that emits the initialization status of Express assets for the given user wallet ID. + * + * @param userWalletId The ID of the user wallet. + * @return A flow emitting Lce states containing either a list of Express assets or an error. + */ + fun getInitializationStatus(userWalletId: UserWalletId): Flow>> +} \ No newline at end of file