Updated on 2026-08-14
This commit is contained in:
commit
c1cc66fea8
162 changed files with 3408 additions and 1585 deletions
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
|||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -70,7 +69,6 @@ internal object ManageTokensDomainModule {
|
|||
customTokensRepository: CustomTokensRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
|
|
@ -83,7 +81,6 @@ internal object ManageTokensDomainModule {
|
|||
customTokensRepository = customTokensRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
derivationsRepository = derivationsRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
|||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import dagger.Module
|
||||
|
|
@ -70,7 +69,6 @@ object MarketsDomainModule {
|
|||
derivationsRepository: DerivationsRepository,
|
||||
marketsTokenRepository: MarketsTokenRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -82,7 +80,6 @@ object MarketsDomainModule {
|
|||
derivationsRepository = derivationsRepository,
|
||||
marketsTokenRepository = marketsTokenRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ internal object TokensDomainModule {
|
|||
@Singleton
|
||||
fun provideAddCryptoCurrenciesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -47,7 +46,6 @@ internal object TokensDomainModule {
|
|||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
|
|
@ -62,7 +60,6 @@ internal object TokensDomainModule {
|
|||
fun provideFetchTokenListUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
|
|
@ -71,7 +68,6 @@ internal object TokensDomainModule {
|
|||
): FetchTokenListUseCase {
|
||||
return FetchTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
|
|
@ -121,8 +117,8 @@ internal object TokensDomainModule {
|
|||
fun provideGetCurrencyUseCase(
|
||||
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCurrencyStatusUpdatesUseCase {
|
||||
return GetCurrencyStatusUpdatesUseCase(
|
||||
): GetSingleCryptoCurrencyStatusUseCase {
|
||||
return GetSingleCryptoCurrencyStatusUseCase(
|
||||
currencyStatusOperations = baseCurrencyStatusOperations,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
@ -162,24 +158,11 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetPrimaryCurrencyUseCase(
|
||||
currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetPrimaryCurrencyStatusUpdatesUseCase {
|
||||
return GetPrimaryCurrencyStatusUpdatesUseCase(
|
||||
currencyStatusOperations = currencyStatusOperations,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchCurrencyStatusUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
|
|
@ -188,7 +171,6 @@ internal object TokensDomainModule {
|
|||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
|
|
@ -203,7 +185,6 @@ internal object TokensDomainModule {
|
|||
fun provideFetchCardTokenListUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
|
|
@ -212,7 +193,6 @@ internal object TokensDomainModule {
|
|||
): FetchCardTokenListUseCase {
|
||||
return FetchCardTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
|
|
@ -222,14 +202,6 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetCryptoCurrencyStatusSyncUseCase(
|
||||
currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
): GetCryptoCurrencyStatusSyncUseCase {
|
||||
return GetCryptoCurrencyStatusSyncUseCase(currencyStatusOperations)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase {
|
||||
|
|
@ -328,14 +300,10 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateDelayedCurrencyStatusUseCase(
|
||||
networksRepository: NetworksRepository,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): UpdateDelayedNetworkStatusUseCase {
|
||||
return UpdateDelayedNetworkStatusUseCase(
|
||||
networksRepository = networksRepository,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -413,7 +381,6 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
|
|
@ -427,7 +394,6 @@ internal object TokensDomainModule {
|
|||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
|
|
@ -447,7 +413,6 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
|
|
@ -461,7 +426,6 @@ internal object TokensDomainModule {
|
|||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
|
|
@ -43,7 +41,6 @@ internal object TransactionDomainModule {
|
|||
transactionRepository: TransactionRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): SendTransactionUseCase {
|
||||
return SendTransactionUseCase(
|
||||
demoConfig = DemoConfig(),
|
||||
|
|
@ -51,7 +48,6 @@ internal object TransactionDomainModule {
|
|||
transactionRepository = transactionRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -61,17 +57,13 @@ internal object TransactionDomainModule {
|
|||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AssociateAssetUseCase {
|
||||
return AssociateAssetUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase
|
|||
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
|
@ -28,6 +30,17 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletsDomainModule {
|
||||
|
||||
@Provides
|
||||
fun providesUserWalletsSyncDelegate(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsSyncDelegate {
|
||||
return DefaultUserWalletsSyncDelegate(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase {
|
||||
|
|
@ -102,10 +115,13 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesRenameWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
walletsRepository: WalletsRepository,
|
||||
userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||
): RenameWalletUseCase {
|
||||
return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers)
|
||||
return RenameWalletUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
userWalletsSyncDelegate = userWalletsSyncDelegate,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -197,4 +213,16 @@ internal object WalletsDomainModule {
|
|||
nftFeatureToggles = nftFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUpdateRemoteWalletsInfoUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||
): UpdateRemoteWalletsInfoUseCase {
|
||||
return UpdateRemoteWalletsInfoUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
userWalletsSyncDelegate = userWalletsSyncDelegate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles(
|
|||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokensFeatureToggles {
|
||||
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED")
|
||||
|
||||
override val isQuotesLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED")
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.walletconnect.WcPairService
|
|||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
|
|
@ -64,14 +65,18 @@ internal class DefaultLegacyWalletConnectRepositoryFacade constructor(
|
|||
if (isNewWc) stub.updateSessions() else legacy.updateSessions()
|
||||
}
|
||||
|
||||
override fun pair(uri: String, source: SourceType) {
|
||||
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) {
|
||||
val src = when (source) {
|
||||
SourceType.QR -> WcPairRequest.Source.QR
|
||||
SourceType.DEEPLINK -> WcPairRequest.Source.DEEPLINK
|
||||
SourceType.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD
|
||||
SourceType.ETC -> WcPairRequest.Source.ETC
|
||||
}
|
||||
if (isNewWc) wcPairService.pair(WcPairRequest(uri, src)) else legacy.pair(uri, source)
|
||||
if (isNewWc) {
|
||||
wcPairService.pair(WcPairRequest(uri = uri, source = src, userWalletId = userWalletId))
|
||||
} else {
|
||||
legacy.pair(userWalletId = userWalletId, uri = uri, source = source)
|
||||
}
|
||||
}
|
||||
|
||||
override fun disconnect(topic: String) {
|
||||
|
|
@ -110,7 +115,7 @@ internal class LegacyWalletConnectRepositoryStub : LegacyWalletConnectRepository
|
|||
|
||||
override fun updateSessions() = Unit
|
||||
|
||||
override fun pair(uri: String, source: SourceType) = Unit
|
||||
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) = Unit
|
||||
|
||||
override fun disconnect(topic: String) = Unit
|
||||
|
||||
|
|
@ -405,7 +410,7 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
this.userNamespaces = userNamespaces
|
||||
}
|
||||
|
||||
override fun pair(uri: String, source: SourceType) {
|
||||
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) {
|
||||
analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source))
|
||||
WalletKit.pair(
|
||||
params = Wallet.Params.Pair(uri),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -19,7 +20,7 @@ interface LegacyWalletConnectRepository {
|
|||
|
||||
fun updateSessions()
|
||||
|
||||
fun pair(uri: String, source: SourceType)
|
||||
fun pair(userWalletId: UserWalletId, uri: String, source: SourceType)
|
||||
|
||||
fun disconnect(topic: String)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsReposit
|
|||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
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.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
|
@ -139,7 +140,11 @@ class WalletConnectInteractor(
|
|||
if (deeplinkStack.empty()) return
|
||||
val lastDeeplink = deeplinkStack.pop()
|
||||
val action = WalletConnectAction
|
||||
.OpenSession(lastDeeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK)
|
||||
.OpenSession(
|
||||
wcUri = lastDeeplink,
|
||||
source = WalletConnectAction.OpenSession.SourceType.DEEPLINK,
|
||||
userWalletId = UserWalletId(userWalletId),
|
||||
)
|
||||
store.dispatchOnMain(action)
|
||||
}.onFailure {
|
||||
Timber.e("WC deeplink handling failed. $it")
|
||||
|
|
@ -393,7 +398,11 @@ class WalletConnectInteractor(
|
|||
}
|
||||
|
||||
if (isWalletConnectReadyForDeepLinks) {
|
||||
val action = WalletConnectAction.OpenSession(deeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK)
|
||||
val action = WalletConnectAction.OpenSession(
|
||||
wcUri = deeplink,
|
||||
source = WalletConnectAction.OpenSession.SourceType.DEEPLINK,
|
||||
userWalletId = UserWalletId(userWalletId),
|
||||
)
|
||||
store.dispatchOnMain(action)
|
||||
} else {
|
||||
deeplinkStack.push(deeplink)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
|
|
@ -14,6 +15,7 @@ sealed class WalletConnectAction : Action {
|
|||
data class OpenSession(
|
||||
val wcUri: String,
|
||||
val source: SourceType,
|
||||
val userWalletId: UserWalletId,
|
||||
) : WalletConnectAction() {
|
||||
enum class SourceType { QR, DEEPLINK, CLIPBOARD, ETC }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ class WalletConnectMiddleware {
|
|||
is WalletConnectAction.OpenSession -> {
|
||||
val index = action.wcUri.indexOf("@")
|
||||
when (action.wcUri[index + 1]) {
|
||||
'2' -> walletConnectRepository.pair(uri = action.wcUri, source = action.source)
|
||||
'2' -> walletConnectRepository.pair(
|
||||
uri = action.wcUri,
|
||||
source = action.source,
|
||||
userWalletId = action.userWalletId,
|
||||
)
|
||||
'1' -> {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest)
|
||||
store.dispatchOnMain(
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import org.rekotlin.StoreSubscriber
|
|||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultWalletConnectComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
@Assisted params: WalletConnectComponent.Params,
|
||||
) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber<WalletConnectState> {
|
||||
|
||||
private val model: WalletConnectModel = getOrCreateModel()
|
||||
private val model: WalletConnectModel = getOrCreateModel(params)
|
||||
|
||||
private var screenState: MutableState<WalletConnectScreenState> =
|
||||
mutableStateOf(model.updateState(store.state.walletConnectState))
|
||||
|
|
@ -65,6 +65,9 @@ internal class DefaultWalletConnectComponent @AssistedInject constructor(
|
|||
|
||||
@AssistedFactory
|
||||
interface Factory : WalletConnectComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectComponent
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: WalletConnectComponent.Params,
|
||||
): DefaultWalletConnectComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@ import androidx.compose.runtime.Stable
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -22,13 +24,22 @@ import javax.inject.Inject
|
|||
internal class WalletConnectModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<WalletConnectComponent.Params>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
|
||||
.getOrElse { emptyFlow() }
|
||||
.map { WalletConnectAction.OpenSession(it, WalletConnectAction.OpenSession.SourceType.QR) }
|
||||
.map {
|
||||
WalletConnectAction.OpenSession(
|
||||
wcUri = it,
|
||||
source = WalletConnectAction.OpenSession.SourceType.QR,
|
||||
userWalletId = params.userWalletId,
|
||||
)
|
||||
}
|
||||
.collect { store.dispatch(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.tap.features.details.ui.walletconnect.api
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface WalletConnectComponent : ComposableContentComponent {
|
||||
interface Factory : ComponentFactory<Unit, WalletConnectComponent>
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
interface Factory : ComponentFactory<Params, WalletConnectComponent>
|
||||
}
|
||||
|
|
@ -13,4 +13,10 @@ internal class DefaultAuthProvider(private val userWalletsListManager: UserWalle
|
|||
override fun getCardId(): String {
|
||||
return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: ""
|
||||
}
|
||||
|
||||
override fun getCardsPublicKeys(): Map<String, String> {
|
||||
return userWalletsListManager.userWalletsSync.associate {
|
||||
it.scanResponse.card.cardId to it.scanResponse.card.cardPublicKey.toHexString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -291,13 +291,13 @@ internal class ChildFactory @Inject constructor(
|
|||
if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
params = RedesignedWalletConnectComponent.Params(route.userWalletId),
|
||||
componentFactory = redesignedWalletConnectComponentFactory,
|
||||
)
|
||||
} else {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
params = WalletConnectComponent.Params(route.userWalletId),
|
||||
componentFactory = walletConnectComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data object WalletConnectSessions : AppRoute(path = "/wallet_connect_sessions")
|
||||
data class WalletConnectSessions(val userWalletId: UserWalletId) : AppRoute(path = "/wallet_connect_sessions")
|
||||
|
||||
@Serializable
|
||||
data class QrScanning(val source: Source) : AppRoute(path = "/$source/qr_scanning${source.path}") {
|
||||
|
|
|
|||
|
|
@ -51,10 +51,6 @@
|
|||
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NETWORKS_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "QUOTES_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.24.0"
|
||||
|
|
|
|||
|
|
@ -11,4 +11,9 @@ interface AuthProvider {
|
|||
fun getCardPublicKey(): String
|
||||
|
||||
fun getCardId(): String
|
||||
|
||||
/**
|
||||
* Returns map where keys(cardId) associated with cardPublicKey
|
||||
*/
|
||||
fun getCardsPublicKeys(): Map<String, String>
|
||||
}
|
||||
|
|
@ -172,6 +172,9 @@ interface TangemTechApi {
|
|||
|
||||
@GET("user-wallets/wallets/{wallet_id}")
|
||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||
|
||||
@GET("user-wallets/wallets/by-app/{app_id}")
|
||||
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardInfoBody(
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_public_key") val cardPublicKey: String,
|
||||
)
|
||||
|
|
@ -6,4 +6,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class WalletIdBody(
|
||||
@Json(name = "id") val walletId: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "cards") val cards: List<CardInfoBody>,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di.local
|
||||
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.DefaultUserTokensResponseStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object LocalTokenModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserTokensResponseStore(appPreferencesStore: AppPreferencesStore): UserTokensResponseStore {
|
||||
return DefaultUserTokensResponseStore(appPreferencesStore = appPreferencesStore)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,14 @@ package com.tangem.datasource.local.nft
|
|||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.datasource.local.nft.custom.NFTPriceId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultNFTPersistenceStore(
|
||||
private val collectionsPersistenceStore: DataStore<List<NFTCollection>>,
|
||||
private val pricesPersistenceStore: DataStore<Map<NFTAsset.Identifier, NFTAsset.SalePrice>>,
|
||||
private val pricesPersistenceStore: DataStore<List<NFTPriceId>>,
|
||||
) : NFTPersistenceStore {
|
||||
|
||||
override fun getCollections(): Flow<List<NFTCollection>?> = collectionsPersistenceStore.data
|
||||
|
|
@ -27,11 +28,12 @@ internal class DefaultNFTPersistenceStore(
|
|||
}
|
||||
|
||||
override fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTAsset.SalePrice?> = pricesPersistenceStore.data
|
||||
.map { it[assetId] }
|
||||
.map { data -> data.associate { it.assetId to it.price }[assetId] }
|
||||
|
||||
override suspend fun getSalePricesSync(): Map<NFTAsset.Identifier, NFTAsset.SalePrice>? = pricesPersistenceStore
|
||||
.data
|
||||
.firstOrNull()
|
||||
?.associate { it.assetId to it.price }
|
||||
|
||||
override suspend fun saveCollections(collections: List<NFTCollection>) {
|
||||
collectionsPersistenceStore.updateData {
|
||||
|
|
@ -41,7 +43,7 @@ internal class DefaultNFTPersistenceStore(
|
|||
|
||||
override suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice) {
|
||||
pricesPersistenceStore.updateData {
|
||||
it.toMutableMap().apply { this[assetId] = salePrice }
|
||||
it.toMutableList().plus(NFTPriceId(assetId = assetId, price = salePrice))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ import androidx.datastore.core.DataStore
|
|||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.nft.custom.NFTPriceId
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.listTypes
|
||||
import com.tangem.datasource.utils.mapWithCustomKeyTypes
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -48,8 +47,8 @@ class NFTPersistenceStoreFactory @Inject constructor(
|
|||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_prices
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_prices
|
||||
fileName = "nft_${userWalletStringId}_${networkStringId}_prices",
|
||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
||||
defaultValue = emptyMap(),
|
||||
types = listTypes<NFTPriceId>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.datasource.local.nft.custom
|
||||
|
||||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
|
||||
data class NFTPriceId(
|
||||
val assetId: NFTAsset.Identifier,
|
||||
val price: NFTAsset.SalePrice,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Default implementation of [UserTokensResponseStore]
|
||||
*
|
||||
* @property appPreferencesStore app preferences store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultUserTokensResponseStore(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : UserTokensResponseStore {
|
||||
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse? {
|
||||
return appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Store of [UserTokensResponse]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface UserTokensResponseStore {
|
||||
|
||||
/** Get [UserTokensResponse] synchronously by [userWalletId] or null */
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse?
|
||||
}
|
||||
|
|
@ -278,7 +278,7 @@
|
|||
<string name="exchange_tokens_unavailable_tokens_header">Kann nicht gegen %s ausgetauscht werden</string>
|
||||
<string name="express_by_provider">Bereitgestellt von</string>
|
||||
<string name="express_cex_status_button_title">Status</string>
|
||||
<string name="express_choose_providers_subtitle">Tangem bietet Token-Swaps über Drittanbieter gemäß den jeweiligen Bedingungen des jeweiligen Anbieters an.</string>
|
||||
<string name="express_choose_providers_subtitle">Anbieter erleichtern Transaktionen</string>
|
||||
<string name="express_choose_providers_title">Anbieter</string>
|
||||
<string name="express_error_code">Es ist ein Fehler aufgetreten. Code: %s</string>
|
||||
<string name="express_error_provider_amount_roundup">Fehler %1$s. Der ausgewählte Anbieter kann die angegebene Transaktion nicht verarbeiten. Bitte runde den Wert auf %2$s auf oder änder diesen.</string>
|
||||
|
|
@ -319,11 +319,11 @@
|
|||
<string name="express_exchange_status_verifying">Verifizierung erforderlich</string>
|
||||
<string name="express_exchange_status_waiting_tx_hash">Wartet auf Transaktions-Hash</string>
|
||||
<string name="express_exchange_token_list_subtitle">Liste aller Token, die deiner Wallet hinzugefügt wurden</string>
|
||||
<string name="express_fetch_best_rates">Beste Preise werden abgerufen …</string>
|
||||
<string name="express_fetch_best_rates">Aktuelle Preise werden abgerufen ...</string>
|
||||
<string name="express_floating_rate">Variabler Zinssatz</string>
|
||||
<string name="express_legal_one_placeholder">Durch die Nutzung der Swap-Funktion erklärst du dich mit den folgenden Bedingungen des Anbieters einverstanden %s</string>
|
||||
<string name="express_legal_two_placeholders">Durch die Nutzung der Swap-Funktionalität erklärst du dich mit des Anbieters %1$s und %2$s einverstanden</string>
|
||||
<string name="express_more_providers_soon">Weitere Anbieter werden bald folgen.\nBleib dabei!</string>
|
||||
<string name="express_more_providers_soon">Weitere Anbieter folgen in Kürze</string>
|
||||
<string name="express_provider">Anbieter</string>
|
||||
<string name="express_provider_best_rate">Bester Preis</string>
|
||||
<string name="express_provider_max_amount">Verfügbar bis zu %s</string>
|
||||
|
|
@ -419,6 +419,7 @@
|
|||
<string name="markets_add_to_my_portfolio_unavailable_description">Dieses Asset wird derzeit in der Wallet nicht unterstützt.</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">Dieses Asset ist für dieses Wallet nicht verfügbar</string>
|
||||
<string name="markets_add_token">Hinzufügen</string>
|
||||
<string name="markets_apy_placeholder">APY %s</string>
|
||||
<string name="markets_available_networks">Verfügbare Netzwerke</string>
|
||||
<string name="markets_common_my_portfolio">Mein Portfolio</string>
|
||||
<string name="markets_common_title">Markt</string>
|
||||
|
|
@ -448,6 +449,9 @@
|
|||
<string name="markets_sort_by_top_gainers_title">Top-Gewinner</string>
|
||||
<string name="markets_sort_by_top_losers_title">Top-Verlierer</string>
|
||||
<string name="markets_sort_by_trending_title">Beliebt</string>
|
||||
<string name="markets_staking_banner_description_placeholder">Staking ist der einfachste Weg, um Belohnungen für Deine Kryptowährung zu erhalten. %s</string>
|
||||
<string name="markets_staking_banner_description_show_more">Mehr anzeigen</string>
|
||||
<string name="markets_staking_banner_title">Verdiene bis zu %s APY</string>
|
||||
<string name="markets_token_details_about_token_title">Über %s</string>
|
||||
<plurals name="markets_token_details_amount_exchanges">
|
||||
<item quantity="one">%d börse</item>
|
||||
|
|
@ -530,6 +534,13 @@
|
|||
<string name="nft_details_base_information">Basisinformationen</string>
|
||||
<string name="nft_details_chain">Netzwerk</string>
|
||||
<string name="nft_details_contract_address">Vertragsadresse</string>
|
||||
<string name="nft_details_info_chain">Kontrollabschnitt</string>
|
||||
<string name="nft_details_info_contract_address">Kontrollabschnitt</string>
|
||||
<string name="nft_details_info_rarity_label">Kontrollabschnitt</string>
|
||||
<string name="nft_details_info_rarity_rank">Kontrollabschnitt</string>
|
||||
<string name="nft_details_info_token_address">Kontrollabschnitt</string>
|
||||
<string name="nft_details_info_token_id">Kontrollabschnitt</string>
|
||||
<string name="nft_details_info_token_standard">Kontrollabschnitt</string>
|
||||
<string name="nft_details_last_sale_price">Letzter Verkaufspreis</string>
|
||||
<string name="nft_details_rarity_label">Raritätsetikett</string>
|
||||
<string name="nft_details_rarity_rank">Seltenheitsgrad</string>
|
||||
|
|
@ -1051,6 +1062,8 @@
|
|||
<string name="transaction_history_transaction_validator">Validierer: %s</string>
|
||||
<string name="transfer_min_amount_error">Minimum %s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Der Mindesttransaktionsbetrag beträgt %1$s.</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Die Netzwerkgebühren für beliebte Token im Tron-Netzwerk können höher sein. Das Staking von TRX kann helfen, die Transaktionskosten zu senken.</string>
|
||||
<string name="tron_will_be_send_token_fee_title">Spare bei den Tron-Netzwerkgebühren</string>
|
||||
<string name="try_to_load_data_again_button_title">Versuche es erneut</string>
|
||||
<string name="twin_error_same_card">Du hast dieselbe Karte oder Ring gescannt. Um ein Zwillings-Wallet zu erstellen, musst du die Karte oder Ring mit der Nummer %d scannen.</string>
|
||||
<string name="twin_error_wrong_twin">Du hast die falsche Doppelkarte oder Ring gescannt. Bitte versuche eine andere Karte oder Ring</string>
|
||||
|
|
@ -1237,6 +1250,7 @@
|
|||
<string name="wc_alert_connection_timeout_description">Zeitüberschreitungsfehler. Bitte versuche es später erneut.</string>
|
||||
<string name="wc_alert_connection_timeout_title">WalletConnect konnte nicht hergestellt werden</string>
|
||||
<string name="wc_alert_domain_issues_description">Diese Domäne kann nicht verifiziert werden. Überprüfe die Anfrage sorgfältig und bestätige diese dann.</string>
|
||||
<string name="wc_alert_session_disconnected_description">Geh zurück zu Deinem Browser und stell erneut eine Verbindung über WalletConnect her.</string>
|
||||
<string name="wc_alert_session_disconnected_title">WalletConnect-Sitzung wurde getrennt</string>
|
||||
<string name="wc_alert_unknown_error_description">Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
|
||||
<string name="wc_alert_unknown_error_title">Wir haben einen unbekannten Fehler festgestellt.</string>
|
||||
|
|
@ -1244,14 +1258,22 @@
|
|||
<string name="wc_alert_unsupported_networks_title">Nicht unterstützte Netzwerke</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem unterstützt ein erforderliches Netzwerk um %s.</string>
|
||||
<string name="wc_alert_verified_domain_title">Verifizierte Domain</string>
|
||||
<string name="wc_alert_wrong_card_description">Falsche Karte oder falscher Ring in der Tangem-App ausgewählt</string>
|
||||
<string name="wc_alert_wrong_card_title">Wir haben eine Art Problem</string>
|
||||
<string name="wc_common_connect">Verbinden</string>
|
||||
<string name="wc_common_network">Netzwerk</string>
|
||||
<string name="wc_common_networks">Netzwerke</string>
|
||||
<string name="wc_common_wallet">Wallet</string>
|
||||
<string name="wc_connected_networks">Verbundene Netzwerke</string>
|
||||
<string name="wc_connection_reqeust_can_view_balance">Anzeige des Kontostands und der Aktivitäten in Deiner Wallet</string>
|
||||
<string name="wc_connection_reqeust_cant_sign">Signiere die Transaktionen ohne eine Vorankündigung</string>
|
||||
<string name="wc_connection_reqeust_request_approval">Genehmigung für Transaktionen anfordern</string>
|
||||
<string name="wc_connection_reqeust_will_not">Wird nicht in der Lage sein,</string>
|
||||
<string name="wc_connection_reqeust_would_like">Möchte</string>
|
||||
<string name="wc_connection_request">Verbindungsanfrage</string>
|
||||
<string name="wc_connections">Verbindungen</string>
|
||||
<string name="wc_contents">Inhalt</string>
|
||||
<string name="wc_copy_data_button_text">Daten kopieren</string>
|
||||
<string name="wc_disconnect_all">Alle trennen</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Text über die Trennung aller dApps</string>
|
||||
<string name="wc_disconnect_all_alert_title">Alle dApps trennen</string>
|
||||
|
|
@ -1262,6 +1284,11 @@
|
|||
<string name="wc_no_sessions_title">Keine Sitzungen</string>
|
||||
<string name="wc_notification_security_risk_subtitle">Diese Domain wird von mehreren Sicherheitsanbietern als unsicher eingestuft. Verlasse diese umgehend, um Dein Vermögen zu schützen.</string>
|
||||
<string name="wc_notification_security_risk_title">Bekanntes Sicherheitsrisiko</string>
|
||||
<string name="wc_request_from">Anfrage von</string>
|
||||
<string name="wc_signature_type">Art der Signatur</string>
|
||||
<string name="wc_transaction_request">Transaktionsanfrage</string>
|
||||
<string name="wc_transaction_request_title">Transaktionsanfrage</string>
|
||||
<string name="wc_wallet_connect">Wallet verbinden</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Verwerfen</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">Du hast eine unterbrochene Sicherung. Möchtest du diese fortsetzen?</string>
|
||||
<string name="welcome_interrupted_backup_alert_resume">Ja, fortsetzen</string>
|
||||
|
|
|
|||
|
|
@ -527,6 +527,13 @@
|
|||
<string name="nft_details_base_information">基本情報</string>
|
||||
<string name="nft_details_chain">チェーン</string>
|
||||
<string name="nft_details_contract_address">コントラクトアドレス</string>
|
||||
<string name="nft_details_info_chain">スタブ</string>
|
||||
<string name="nft_details_info_contract_address">スタブ</string>
|
||||
<string name="nft_details_info_rarity_label">スタブ</string>
|
||||
<string name="nft_details_info_rarity_rank">スタブ</string>
|
||||
<string name="nft_details_info_token_address">スタブ</string>
|
||||
<string name="nft_details_info_token_id">スタブ</string>
|
||||
<string name="nft_details_info_token_standard">スタブ</string>
|
||||
<string name="nft_details_last_sale_price">最終販売価格</string>
|
||||
<string name="nft_details_rarity_label">レアリティ・ラベル</string>
|
||||
<string name="nft_details_rarity_rank">レアリティ・ランク</string>
|
||||
|
|
@ -1044,7 +1051,7 @@
|
|||
<string name="transaction_history_transaction_validator">バリデーター: %s</string>
|
||||
<string name="transfer_min_amount_error">最小%s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">最小取引金額は%1$sです。</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Tronネットワークの人気トークンの手数料は高めです。TRXをステーキングすると、より安く、あるいは無料で取引できます。</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Tronネットワークの人気トークンの手数料は高い可能性があります。TRXをステーキングすると、取引コストを削減できる可能性があります。</string>
|
||||
<string name="tron_will_be_send_token_fee_title">Tronネットワーク手数料を節約</string>
|
||||
<string name="try_to_load_data_again_button_title">もう一度やり直してください</string>
|
||||
<string name="twin_error_same_card">同じカードをスキャンしました。ツインウォレットを作成するには、番号%dのカードをスキャンする必要があります。</string>
|
||||
|
|
@ -1237,6 +1244,7 @@
|
|||
<string name="wc_alert_verified_domain_title">検証済みドメイン</string>
|
||||
<string name="wc_alert_wrong_card_description">Tangemアプリで誤ったカードまたはリングが選択されました</string>
|
||||
<string name="wc_alert_wrong_card_title">問題が起きています</string>
|
||||
<string name="wc_common_address">アドレス</string>
|
||||
<string name="wc_common_connect">接続する</string>
|
||||
<string name="wc_common_network">ネットワーク</string>
|
||||
<string name="wc_common_networks">ネットワーク</string>
|
||||
|
|
@ -1249,6 +1257,8 @@
|
|||
<string name="wc_connection_reqeust_would_like">したい</string>
|
||||
<string name="wc_connection_request">接続リクエスト</string>
|
||||
<string name="wc_connections">接続</string>
|
||||
<string name="wc_contents">内容</string>
|
||||
<string name="wc_copy_data_button_text">データをコピー</string>
|
||||
<string name="wc_disconnect_all">すべての接続を解除する</string>
|
||||
<string name="wc_disconnect_all_alert_desc">すべてのdAppsの接続解除に関するテキスト</string>
|
||||
<string name="wc_disconnect_all_alert_title">すべてのdAppを接続解除する</string>
|
||||
|
|
@ -1259,6 +1269,11 @@
|
|||
<string name="wc_no_sessions_title">セッションなし</string>
|
||||
<string name="wc_notification_security_risk_subtitle">このドメインは複数のセキュリティプロバイダーから安全でないとの警告を受けています。あなたの資産を守るため、直ちにアクセスを中止してください。</string>
|
||||
<string name="wc_notification_security_risk_title">既知のセキュリティリスク</string>
|
||||
<string name="wc_request_from">リクエスト元</string>
|
||||
<string name="wc_signature_type">署名タイプ</string>
|
||||
<string name="wc_transaction_info_to_title">宛先</string>
|
||||
<string name="wc_transaction_request">取引リクエスト</string>
|
||||
<string name="wc_transaction_request_title">取引リクエスト</string>
|
||||
<string name="wc_wallet_connect">ウォレットコネクト</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">破棄</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">バックアップが中断されました。再開しますか?</string>
|
||||
|
|
|
|||
|
|
@ -1063,7 +1063,7 @@
|
|||
<string name="transaction_history_transaction_validator">validator: %s</string>
|
||||
<string name="transfer_min_amount_error">Minimum %s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">The minimum transaction amount is %1$s.</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Tron network fees for popular tokens are higher. Stake some TRX for cheaper or free transactions.</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Tron network fees for popular tokens can be higher. Staking TRX may help reduce transaction costs.</string>
|
||||
<string name="tron_will_be_send_token_fee_title">Save on Tron network fees</string>
|
||||
<string name="try_to_load_data_again_button_title">Try again</string>
|
||||
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
|
||||
|
|
@ -1305,6 +1305,7 @@
|
|||
<string name="wc_alert_verified_domain_title">Verified domain</string>
|
||||
<string name="wc_alert_wrong_card_description">Wrong card or ring selected in Tangem App</string>
|
||||
<string name="wc_alert_wrong_card_title">We\'ve got some kind of problem</string>
|
||||
<string name="wc_common_address">Address</string>
|
||||
<string name="wc_common_connect">Connect</string>
|
||||
<string name="wc_common_network">Network</string>
|
||||
<string name="wc_common_networks">Networks</string>
|
||||
|
|
@ -1331,6 +1332,7 @@
|
|||
<string name="wc_notification_security_risk_title">Known security risk</string>
|
||||
<string name="wc_request_from">Request from</string>
|
||||
<string name="wc_signature_type">Signature Type</string>
|
||||
<string name="wc_transaction_info_to_title">To</string>
|
||||
<string name="wc_transaction_request">Transaction request</string>
|
||||
<string name="wc_transaction_request_title">Transaction request</string>
|
||||
<string name="wc_wallet_connect">Wallet connect</string>
|
||||
|
|
|
|||
21
core/ui/src/main/res/drawable/ic_fee_new_24.xml
Normal file
21
core/ui/src/main/res/drawable/ic_fee_new_24.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="18">
|
||||
<path
|
||||
android:pathData="M9,0.547C8.407,0.547 7.867,0.701 7.283,0.956C6.721,1.201 6.073,1.562 5.271,2.008L2.073,3.788C1.611,4.045 1.214,4.374 0.939,4.825C0.662,5.278 0.547,5.793 0.547,6.357L0.547,6.411C0.546,6.602 0.546,6.825 0.6,7.027C0.733,7.53 1.055,7.86 1.448,8.035C1.663,8.13 1.887,8.175 2.088,8.194V13.66C1.167,14.188 0.547,15.181 0.547,16.318C0.547,16.945 1.055,17.453 1.682,17.453H8.23C8.644,17.453 8.98,17.117 8.98,16.703C8.98,16.289 8.644,15.953 8.23,15.953H2.09C2.254,15.267 2.872,14.757 3.608,14.757H8.23C8.644,14.757 8.98,14.421 8.98,14.007C8.98,13.593 8.644,13.257 8.23,13.257H6.669V8.21H15.572C15.834,8.21 16.205,8.19 16.553,8.035C16.946,7.86 17.268,7.53 17.401,7.027C17.455,6.825 17.454,6.602 17.454,6.411L17.454,6.357C17.454,5.793 17.339,5.278 17.062,4.825C16.787,4.374 16.39,4.045 15.928,3.788L12.73,2.008C11.928,1.562 11.28,1.201 10.718,0.956C10.134,0.701 9.594,0.547 9,0.547ZM5.968,3.337C6.81,2.868 7.395,2.543 7.883,2.33C8.356,2.124 8.685,2.047 9,2.047C9.316,2.047 9.645,2.124 10.118,2.33C10.606,2.543 11.191,2.868 12.033,3.337L15.198,5.098C15.506,5.269 15.679,5.437 15.782,5.606C15.883,5.772 15.954,6 15.954,6.357C15.954,6.489 15.954,6.563 15.951,6.617C15.95,6.635 15.95,6.646 15.949,6.652C15.948,6.657 15.946,6.66 15.945,6.663L15.942,6.664C15.923,6.673 15.884,6.686 15.817,6.695C15.75,6.705 15.67,6.71 15.572,6.71H2.429C2.331,6.71 2.251,6.705 2.184,6.695C2.116,6.686 2.078,6.673 2.059,6.664L2.056,6.663C2.055,6.66 2.053,6.657 2.052,6.652C2.051,6.646 2.051,6.635 2.05,6.617C2.047,6.563 2.047,6.489 2.047,6.357C2.047,6 2.118,5.772 2.219,5.606C2.322,5.437 2.495,5.269 2.802,5.098L5.968,3.337ZM3.608,13.257L3.588,13.257V8.21H5.169V13.257H3.608Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M10.396,15.788C10.103,16.081 10.103,16.556 10.396,16.849C10.689,17.142 11.164,17.142 11.457,16.849L16.849,11.456C17.142,11.163 17.142,10.689 16.849,10.396C16.556,10.103 16.081,10.103 15.788,10.396L10.396,15.788Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M10.176,11.503C10.176,10.77 10.77,10.176 11.504,10.176C12.237,10.176 12.832,10.77 12.832,11.503C12.832,12.237 12.237,12.831 11.504,12.831C10.77,12.831 10.176,12.237 10.176,11.503ZM11.504,11.676C11.599,11.676 11.676,11.599 11.676,11.503C11.676,11.408 11.599,11.331 11.504,11.331C11.409,11.331 11.332,11.408 11.332,11.503C11.332,11.599 11.409,11.676 11.504,11.676Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M15.741,14.412C15.008,14.412 14.413,15.007 14.413,15.74C14.413,16.473 15.008,17.068 15.741,17.068C16.474,17.068 17.069,16.473 17.069,15.74C17.069,15.007 16.474,14.412 15.741,14.412ZM15.913,15.74C15.913,15.835 15.836,15.912 15.741,15.912C15.646,15.912 15.569,15.835 15.569,15.74C15.569,15.645 15.646,15.568 15.741,15.568C15.836,15.568 15.913,15.645 15.913,15.74Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
17
core/ui/src/main/res/drawable/ic_user_square_24.xml
Normal file
17
core/ui/src/main/res/drawable/ic_user_square_24.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M9.992,4.701C8.321,4.701 6.964,6.054 6.964,7.726C6.964,9.397 8.321,10.75 9.992,10.75C11.663,10.75 13.02,9.397 13.02,7.726C13.02,6.054 11.663,4.701 9.992,4.701ZM8.464,7.726C8.464,6.885 9.147,6.201 9.992,6.201C10.837,6.201 11.52,6.885 11.52,7.726C11.52,8.567 10.837,9.25 9.992,9.25C9.147,9.25 8.464,8.567 8.464,7.726Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M6.448,15.067C8.288,13.14 11.709,13.068 13.542,15.058C13.823,15.362 14.298,15.382 14.602,15.101C14.907,14.82 14.926,14.346 14.645,14.041C12.191,11.377 7.766,11.515 5.363,14.031C5.077,14.331 5.088,14.806 5.387,15.092C5.687,15.378 6.162,15.367 6.448,15.067Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M10.056,0.606H9.942C7.953,0.606 6.384,0.606 5.157,0.771C3.897,0.941 2.886,1.296 2.091,2.092C1.295,2.887 0.94,3.898 0.77,5.158C0.605,6.385 0.605,7.954 0.605,9.943V10.057C0.605,12.046 0.605,13.615 0.77,14.842C0.94,16.102 1.295,17.113 2.091,17.908C2.886,18.704 3.897,19.059 5.157,19.229C6.384,19.394 7.953,19.394 9.942,19.394H10.056C12.045,19.394 13.614,19.394 14.841,19.229C16.101,19.059 17.112,18.704 17.907,17.908C18.703,17.113 19.058,16.102 19.228,14.842C19.393,13.615 19.393,12.046 19.393,10.057V9.943C19.393,7.954 19.393,6.385 19.228,5.158C19.058,3.898 18.703,2.887 17.907,2.092C17.112,1.296 16.101,0.941 14.841,0.771C13.614,0.606 12.045,0.606 10.056,0.606ZM3.152,3.153C3.622,2.682 4.263,2.405 5.357,2.258C6.472,2.108 7.941,2.106 9.999,2.106C12.057,2.106 13.526,2.108 14.641,2.258C15.735,2.405 16.376,2.682 16.846,3.153C17.317,3.623 17.594,4.264 17.741,5.358C17.891,6.473 17.893,7.941 17.893,10C17.893,12.059 17.891,13.527 17.741,14.642C17.594,15.736 17.317,16.377 16.846,16.847C16.376,17.318 15.735,17.595 14.641,17.742C13.526,17.892 12.057,17.894 9.999,17.894C7.941,17.894 6.472,17.892 5.357,17.742C4.263,17.595 3.622,17.318 3.152,16.847C2.681,16.377 2.404,15.736 2.257,14.642C2.107,13.527 2.105,12.059 2.105,10C2.105,7.941 2.107,6.473 2.257,5.358C2.404,4.264 2.681,3.623 3.152,3.153Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
|
|
@ -14,9 +14,11 @@ dependencies {
|
|||
implementation(projects.core.datasource)
|
||||
|
||||
/* Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Libs - SDK */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
@ -28,8 +30,16 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/* Libs - Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.timber)
|
||||
|
||||
/* Test */
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Factory for creating list of [CryptoCurrency] for selected card
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface CardCryptoCurrencyFactory {
|
||||
|
||||
/**
|
||||
* Universal method for creating list of [CryptoCurrency] in [network] for any card
|
||||
*
|
||||
* @param userWalletId user wallet id that determines type of card
|
||||
* @param network network
|
||||
*/
|
||||
@Throws
|
||||
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency>
|
||||
|
||||
/**
|
||||
* Create default coins for multi currency card
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
*/
|
||||
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin>
|
||||
|
||||
/**
|
||||
* Create primary currency for single currency card
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
*/
|
||||
@Throws
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency
|
||||
|
||||
/**
|
||||
* Create currencies for single currency card with token (like, NODL)
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
*/
|
||||
@Throws
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency>
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Default implementation of factory for creating list of [CryptoCurrency] for selected card
|
||||
*
|
||||
* @property demoConfig demo config
|
||||
* @property excludedBlockchains excluded blockchains
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property userTokensResponseStore user tokens response store
|
||||
*/
|
||||
internal class DefaultCardCryptoCurrencyFactory(
|
||||
private val demoConfig: DemoConfig,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
) : CardCryptoCurrencyFactory {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
|
||||
|
||||
override suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
if (cardBlockchain != blockchain) return emptyList()
|
||||
|
||||
// single-currency wallet with token (NODL)
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
}
|
||||
|
||||
// single-currency wallet
|
||||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
}
|
||||
|
||||
override fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
val card = scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
demoConfig.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
||||
if (card.isTestCard) {
|
||||
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
|
||||
}
|
||||
|
||||
return blockchains.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
primaryToken ?: coin
|
||||
}
|
||||
}
|
||||
|
||||
override fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
listOfNotNull(coin, primaryToken)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
?: return emptyList()
|
||||
|
||||
val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
|
||||
return responseCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter {
|
||||
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
|
||||
},
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSingleWalletCurrencies(scanResponse: ScanResponse): SingleWalletCurrencies {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
val primaryToken = resolver.getPrimaryToken()?.let { token ->
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
return SingleWalletCurrencies(coin = coin, primaryToken = primaryToken)
|
||||
}
|
||||
|
||||
private data class SingleWalletCurrencies(val coin: CryptoCurrency, val primaryToken: CryptoCurrency?)
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.data.common.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.DefaultCardCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object DataCommonModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCardCryptoCurrencyFactory(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
userTokensResponseStore: UserTokensResponseStore,
|
||||
): CardCryptoCurrencyFactory {
|
||||
return DefaultCardCryptoCurrencyFactory(
|
||||
demoConfig = DemoConfig(),
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.card.WalletData
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.configs.GenericCardConfig
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultCardCryptoCurrencyFactoryTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk()
|
||||
|
||||
private val factory = DefaultCardCryptoCurrencyFactory(
|
||||
demoConfig = DemoConfig(),
|
||||
excludedBlockchains = ExcludedBlockchains(),
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(Uri::class)
|
||||
every { Uri.parse(any()) } returns mockk()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is not empty`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(ethereum),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is empty`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is null`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns null
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse does not contain currency of selected network`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(bitcoin),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if single wallet has another primary network`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = bitcoin.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
|
||||
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if card is single wallet`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
|
||||
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if card is single wallet with token`() = runTest {
|
||||
val singleWallet = createSingleWalletWithToken()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
|
||||
|
||||
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWallet.scanResponse,
|
||||
)
|
||||
val expected = listOf(ethereum, token)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is prod`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(bitcoin, ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is test`() = runTest {
|
||||
val multiWallet = createMultiWallet().let {
|
||||
it.copy(
|
||||
scanResponse = it.scanResponse.copy(
|
||||
card = it.scanResponse.card.copy(cardId = "FF99", batchId = "99FF"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.BitcoinTestnet),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.EthereumTestnet).setCanHandleTokens(true),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is demo`() = runTest {
|
||||
val multiWallet = createMultiWallet().let {
|
||||
it.copy(
|
||||
scanResponse = it.scanResponse.copy(
|
||||
card = it.scanResponse.card.copy(cardId = "AC01000000041225"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(
|
||||
bitcoin,
|
||||
ethereum,
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Dogecoin),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Solana),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if unable to create token`() = runTest {
|
||||
val singleWallet = UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
val actual = runCatching {
|
||||
factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
}
|
||||
|
||||
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
|
||||
|
||||
Truth.assertThat(actual.isFailure).isTrue()
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is null`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
|
||||
val expected = ethereum
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is not null`() = runTest {
|
||||
val singleWallet = createSingleWalletWithToken()
|
||||
|
||||
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
|
||||
val expected = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWallet.scanResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if unable to create token`() = runTest {
|
||||
val singleWalletWithToken = UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
val actual = runCatching {
|
||||
factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = singleWalletWithToken.scanResponse)
|
||||
}
|
||||
|
||||
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
|
||||
|
||||
Truth.assertThat(actual.isFailure).isTrue()
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is null`() = runTest {
|
||||
val singleWalletWithToken = createSingleWallet()
|
||||
|
||||
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is not null`() = runTest {
|
||||
val singleWalletWithToken = createSingleWalletWithToken()
|
||||
|
||||
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWalletWithToken.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val expected = listOf(ethereum, token)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun createMultiWallet(): UserWallet {
|
||||
return UserWallet(
|
||||
name = "Wallet 1",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = true,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSingleWallet(): UserWallet {
|
||||
return UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
).let {
|
||||
it.copy(
|
||||
card = it.card.copy(batchId = "AB10"),
|
||||
productType = ProductType.Note,
|
||||
)
|
||||
},
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSingleWalletWithToken(): UserWallet {
|
||||
return UserWallet(
|
||||
name = "NODL",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
).copy(
|
||||
productType = ProductType.Note,
|
||||
walletData = WalletData(
|
||||
blockchain = "ETH",
|
||||
token = WalletData.Token(
|
||||
name = "Ethereum",
|
||||
symbol = "ETH",
|
||||
contractAddress = "0x",
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
val ethereum = cryptoCurrencyFactory.ethereum.setCanHandleTokens(value = true)
|
||||
|
||||
val bitcoin = cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Bitcoin)
|
||||
|
||||
fun CryptoCurrency.setCanHandleTokens(value: Boolean): CryptoCurrency {
|
||||
return when (this) {
|
||||
is CryptoCurrency.Coin -> copy(network = network.copy(canHandleTokens = value))
|
||||
is CryptoCurrency.Token -> copy(network = network.copy(canHandleTokens = value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,12 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.getBlockchain
|
||||
import com.tangem.data.common.utils.retryOnError
|
||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
import com.tangem.data.managetokens.utils.ManagedCryptoCurrencyFactory
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
|
|
@ -26,7 +26,6 @@ import com.tangem.domain.common.extensions.canHandleToken
|
|||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.supportedTokens
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.managetokens.model.*
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
|
|
@ -48,12 +47,12 @@ internal class DefaultManageTokensRepository(
|
|||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val testnetTokensStorage: TestnetTokensStorage,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ManageTokensRepository {
|
||||
|
||||
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(DemoConfig(), excludedBlockchains)
|
||||
|
||||
// region getTokenListBatchFlow
|
||||
override fun getTokenListBatchFlow(
|
||||
|
|
@ -77,7 +76,7 @@ internal class DefaultManageTokensRepository(
|
|||
prefetchDistance = batchSize,
|
||||
batchSize = batchSize,
|
||||
subFetcher = { request, _, isFirstBatchFetching ->
|
||||
val userWallet = request.params.userWalletId?.let { getUserWallet(it) }
|
||||
val userWallet = request.params.userWalletId?.let(userWalletsStore::getSyncStrict)
|
||||
|
||||
if (userWallet?.scanResponse?.card?.isTestCard == true) {
|
||||
fetchTestnetCurrencies(userWallet, request)
|
||||
|
|
@ -190,17 +189,11 @@ internal class DefaultManageTokensRepository(
|
|||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find a user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSupportedBlockchains(userWallet: UserWallet?): List<Blockchain> {
|
||||
return userWallet?.scanResponse?.let {
|
||||
it.card.supportedBlockchains(it.cardTypesResolver, excludedBlockchains)
|
||||
|
|
@ -261,7 +254,7 @@ internal class DefaultManageTokensRepository(
|
|||
userWalletId: UserWalletId,
|
||||
sourceNetwork: SourceNetwork,
|
||||
): CurrencyUnsupportedState? {
|
||||
val userWallet = getUserWallet(userWalletId = userWalletId)
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
val blockchain = getBlockchain(sourceNetwork.id)
|
||||
return when (sourceNetwork) {
|
||||
is SourceNetwork.Default -> checkTokenUnsupportedState(userWallet = userWallet, blockchain = blockchain)
|
||||
|
|
@ -274,7 +267,7 @@ internal class DefaultManageTokensRepository(
|
|||
rawNetworkId: String,
|
||||
isMainNetwork: Boolean,
|
||||
): CurrencyUnsupportedState? {
|
||||
val userWallet = getUserWallet(userWalletId = userWalletId)
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId)
|
||||
?: error("Can not create blockchain with given networkId -> $rawNetworkId")
|
||||
return if (isMainNetwork) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.managetokens.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.managetokens.DefaultCustomTokensRepository
|
||||
import com.tangem.data.managetokens.DefaultManageTokensRepository
|
||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
|
|
@ -32,15 +33,17 @@ internal object ManageTokensDataModule {
|
|||
testnetTokensStorage: TestnetTokensStorage,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): ManageTokensRepository {
|
||||
return DefaultManageTokensRepository(
|
||||
tangemTechApi,
|
||||
userWalletsStore,
|
||||
manageTokensUpdateFetcher,
|
||||
appPreferencesStore,
|
||||
testnetTokensStorage,
|
||||
excludedBlockchains,
|
||||
dispatchers,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsStore = userWalletsStore,
|
||||
manageTokensUpdateFetcher = manageTokensUpdateFetcher,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
testnetTokensStorage = testnetTokensStorage,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,14 @@
|
|||
package com.tangem.data.networks.single
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
|
@ -30,27 +17,20 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Default implementation of [SingleNetworkStatusFetcher]
|
||||
*
|
||||
* @param excludedBlockchains excluded blockchains
|
||||
* @property walletManagersFacade wallet managers facade
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property appPreferencesStore app preferences store
|
||||
* @property dispatchers dispatchers
|
||||
* @property walletManagersFacade wallet managers facade
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property cardCryptoCurrencyFactory card crypto currency factory
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleNetworkStatusFetcher {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val networkStatusFactory = NetworkStatusFactory()
|
||||
|
||||
override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) {
|
||||
|
|
@ -59,9 +39,10 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
is SingleNetworkStatusFetcher.Params.Simple -> {
|
||||
networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network)
|
||||
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
|
||||
|
||||
createCurrencies(userWallet = userWallet, network = params.network)
|
||||
cardCryptoCurrencyFactory.create(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,36 +86,4 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
Timber.e("Failed to fetch network status for $params: $it")
|
||||
networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network)
|
||||
}
|
||||
|
||||
private suspend fun createCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
if (cardBlockchain != blockchain) return emptyList()
|
||||
|
||||
// single-currency wallet with token (NODL)
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
return cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
}
|
||||
|
||||
// single-currency wallet
|
||||
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
}
|
||||
|
||||
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
val response = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWallet.walletId.stringValue),
|
||||
) ?: return emptyList()
|
||||
|
||||
return responseCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter {
|
||||
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
|
||||
},
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ package com.tangem.data.networks.single
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -22,35 +22,35 @@ import org.junit.Test
|
|||
*/
|
||||
internal class DefaultSingleNetworkStatusFetcherTest {
|
||||
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk(relaxed = true)
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxed = true)
|
||||
private val userWalletsStore: UserWalletsStore = mockk(relaxed = true)
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk(relaxUnitFun = true)
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true)
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
|
||||
private val fetcher = DefaultSingleNetworkStatusFetcher(
|
||||
excludedBlockchains = mockk(relaxed = true),
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
userWalletsStore = userWalletsStore,
|
||||
appPreferencesStore = mockk(relaxed = true),
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `fetch network status successfully`() = runTest {
|
||||
val params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network)
|
||||
val params = createParams()
|
||||
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns listOf(ethereum)
|
||||
|
||||
val result = UpdateWalletManagerResult.MissedDerivation
|
||||
coEvery { walletManagersFacade.update(userWalletId, network, emptySet()) } returns result
|
||||
coEvery { walletManagersFacade.update(params.userWalletId, params.network, emptySet()) } returns result
|
||||
|
||||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.refresh(userWalletId = userWalletId, network = network)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
walletManagersFacade.update(userWalletId, network, emptySet())
|
||||
networksStatusesStore.refresh(params.userWalletId, params.network)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.network)
|
||||
walletManagersFacade.update(params.userWalletId, params.network, emptySet())
|
||||
networksStatusesStore.storeSuccess(
|
||||
userWalletId = userWalletId,
|
||||
value = NetworkStatus(network, NetworkStatus.MissedDerivation),
|
||||
userWalletId = params.userWalletId,
|
||||
value = NetworkStatus(params.network, NetworkStatus.MissedDerivation),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -59,17 +59,17 @@ internal class DefaultSingleNetworkStatusFetcherTest {
|
|||
|
||||
@Test
|
||||
fun `fetch network status failure`() = runTest {
|
||||
val params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network)
|
||||
val params = createParams()
|
||||
|
||||
val exception = IllegalStateException()
|
||||
coEvery { userWalletsStore.getSyncStrict(key = userWalletId) } throws exception
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws exception
|
||||
|
||||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.refresh(userWalletId = userWalletId, network = network)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
networksStatusesStore.storeError(userWalletId = userWalletId, network = network)
|
||||
networksStatusesStore.refresh(userWalletId = params.userWalletId, network = params.network)
|
||||
cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
|
||||
networksStatusesStore.storeError(userWalletId = params.userWalletId, network = params.network)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
|
|
@ -81,8 +81,15 @@ internal class DefaultSingleNetworkStatusFetcherTest {
|
|||
Truth.assertThat(actual.leftOrNull()).isEqualTo(exception)
|
||||
}
|
||||
|
||||
private fun createParams(): SingleNetworkStatusFetcher.Params {
|
||||
return SingleNetworkStatusFetcher.Params.Simple(
|
||||
userWalletId = UserWalletId("011"),
|
||||
network = ethereum.network,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId("011")
|
||||
val network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
val ethereum = MockCryptoCurrencyFactory().ethereum
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,11 @@ import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConv
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.NotificationApplicationCreateBody
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletBody
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.*
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -23,7 +22,7 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NotificationsRepository {
|
||||
|
||||
override suspend fun createApplicationId(pushToken: String?): String = withContext(dispatchers.io) {
|
||||
override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) {
|
||||
tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = appInfoProvider.platform,
|
||||
|
|
@ -33,15 +32,16 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
timezone = appInfoProvider.timezone,
|
||||
pushToken = pushToken,
|
||||
),
|
||||
).getOrThrow().appId
|
||||
).getOrThrow().appId.let(::ApplicationId)
|
||||
}
|
||||
|
||||
override suspend fun saveApplicationId(appId: String) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId)
|
||||
override suspend fun saveApplicationId(appId: ApplicationId) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value)
|
||||
}
|
||||
|
||||
override suspend fun getApplicationId(): String? {
|
||||
override suspend fun getApplicationId(): ApplicationId? {
|
||||
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
|
||||
?.let(::ApplicationId)
|
||||
}
|
||||
|
||||
override suspend fun incrementTronTokenFeeNotificationShowCounter() {
|
||||
|
|
@ -61,31 +61,10 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>) =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
applicationId = appId,
|
||||
body = wallets.map {
|
||||
WalletIdBody(it)
|
||||
},
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId,
|
||||
WalletBody(name = walletName),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun getWalletName(walletId: String): String? = withContext(dispatchers.io) {
|
||||
tangemTechApi.getWalletById(walletId).getOrThrow().name
|
||||
}
|
||||
|
||||
override suspend fun sendPushToken(appId: String, pushToken: String) {
|
||||
override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId,
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.squareup.moshi.Moshi
|
|||
import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.*
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
|
@ -41,9 +42,9 @@ class DefaultNotificationsRepositoryTest {
|
|||
fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest {
|
||||
// GIVEN
|
||||
val pushToken = "test-push-token"
|
||||
val expectedAppId = "test-app-id"
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
val expectedAppIdResponse = NotificationApplicationIdResponse(
|
||||
appId = expectedAppId,
|
||||
appId = expectedAppId.value,
|
||||
)
|
||||
coEvery { appInfoProvider.platform } returns "android"
|
||||
coEvery { appInfoProvider.device } returns "test-device"
|
||||
|
|
@ -76,7 +77,7 @@ class DefaultNotificationsRepositoryTest {
|
|||
@Test
|
||||
fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest {
|
||||
// GIVEN
|
||||
val appId = "test-app-id"
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns preferences
|
||||
|
||||
|
|
@ -90,10 +91,10 @@ class DefaultNotificationsRepositoryTest {
|
|||
@Test
|
||||
fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest {
|
||||
// GIVEN
|
||||
val expectedAppId = "test-app-id"
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
val key = stringPreferencesKey(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY.name)
|
||||
every { preferences[key] } returns expectedAppId
|
||||
every { preferences[key] } returns expectedAppId.value
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
|
||||
// WHEN
|
||||
|
|
@ -103,75 +104,24 @@ class DefaultNotificationsRepositoryTest {
|
|||
assertThat(result).isEqualTo(expectedAppId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id and wallet list WHEN associateApplicationIdWithWallets THEN associates them`() = runTest {
|
||||
// GIVEN
|
||||
val appId = "test-app-id"
|
||||
val wallets = listOf("wallet1", "wallet2")
|
||||
coEvery {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
appId,
|
||||
wallets.map { WalletIdBody(it) },
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.associateApplicationIdWithWallets(appId, wallets)
|
||||
|
||||
// THEN
|
||||
coVerify { tangemTechApi.associateApplicationIdWithWallets(appId, wallets.map { WalletIdBody(it) }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallet id and name WHEN setWalletName THEN updates wallet name`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = "test-wallet-id"
|
||||
val walletName = "Test Wallet"
|
||||
coEvery {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId,
|
||||
WalletBody(name = walletName),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.setWalletName(walletId, walletName)
|
||||
|
||||
// THEN
|
||||
coVerify { tangemTechApi.updateWallet(walletId, WalletBody(name = walletName)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallet id WHEN getWalletName THEN returns wallet name`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = "test-wallet-id"
|
||||
val expectedName = "Test Wallet"
|
||||
coEvery { tangemTechApi.getWalletById(walletId) } returns ApiResponse.Success(
|
||||
WalletResponse(
|
||||
notifyStatus = false,
|
||||
name = expectedName,
|
||||
id = walletId,
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletName(walletId)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest {
|
||||
// GIVEN
|
||||
val appId = "test-app-id"
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val pushToken = "test-push-token"
|
||||
coEvery {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId,
|
||||
NotificationApplicationCreateBody(pushToken = pushToken),
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
platform = null,
|
||||
device = null,
|
||||
systemVersion = null,
|
||||
language = null,
|
||||
timezone = null,
|
||||
),
|
||||
)
|
||||
} returns ApiResponse.Success(appId)
|
||||
} returns ApiResponse.Success(appId.value)
|
||||
|
||||
// WHEN
|
||||
repository.sendPushToken(appId, pushToken)
|
||||
|
|
@ -179,8 +129,15 @@ class DefaultNotificationsRepositoryTest {
|
|||
// THEN
|
||||
coVerify {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId,
|
||||
NotificationApplicationCreateBody(pushToken = pushToken),
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
platform = null,
|
||||
device = null,
|
||||
systemVersion = null,
|
||||
language = null,
|
||||
timezone = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package com.tangem.data.quotes.di
|
|||
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteUpdater
|
||||
import com.tangem.data.quotes.single.DefaultSingleQuoteFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteUpdater
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -21,4 +23,8 @@ internal interface QuoteFetcherModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindMultiQuoteUpdater(impl: DefaultMultiQuoteUpdater): MultiQuoteUpdater
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSingleQuoteFetcher(impl: DefaultSingleQuoteFetcher): SingleQuoteFetcher
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.data.quotes.single
|
||||
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultSingleQuoteFetcher @Inject constructor(
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
) : SingleQuoteFetcher {
|
||||
|
||||
override suspend fun invoke(params: SingleQuoteFetcher.Params) = multiQuoteFetcher.invoke(
|
||||
MultiQuoteFetcher.Params(
|
||||
currenciesIds = setOf(params.rawCurrencyId),
|
||||
appCurrencyId = params.appCurrencyId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.data.quotes.single
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.data.quote.MockQuoteResponseFactory
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultSingleQuoteFetcherTest {
|
||||
|
||||
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
|
||||
private val appCurrencyResponseStore = mockk<AppCurrencyResponseStore>(relaxed = true)
|
||||
private val quotesStore = mockk<QuotesStoreV2>(relaxed = true)
|
||||
|
||||
private val multiFetcher = DefaultMultiQuoteFetcher(
|
||||
tangemTechApi = tangemTechApi,
|
||||
appCurrencyResponseStore = appCurrencyResponseStore,
|
||||
quotesStore = quotesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val singleFetcher = DefaultSingleQuoteFetcher(multiFetcher)
|
||||
|
||||
@Test
|
||||
fun `fetch single quote successfully`() = runTest {
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null)
|
||||
|
||||
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
|
||||
|
||||
val coinIds = "BTC"
|
||||
coEvery {
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
} returns ApiResponse.Success(successResponse)
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(params.rawCurrencyId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeError(currenciesIds = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote successfully if appCurrencyId from params is not null`() = runTest {
|
||||
val appCurrencyId = "usd"
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = appCurrencyId)
|
||||
|
||||
val coinIds = "BTC"
|
||||
coEvery {
|
||||
tangemTechApi.getQuotes(currencyId = appCurrencyId, coinIds = coinIds)
|
||||
} returns ApiResponse.Success(successResponse)
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote failure because appCurrencyId from params is blank`() = runTest {
|
||||
val appCurrencyId = ""
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = appCurrencyId)
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
Truth.assertThat(actual.leftOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
Truth.assertThat(actual.leftOrNull()).hasMessageThat()
|
||||
.isEqualTo("Unable to get AppCurrency for updating quotes")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote failure because api request failed`() = runTest {
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null)
|
||||
|
||||
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
|
||||
|
||||
val coinIds = "BTC"
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val errorResponse = ApiResponse.Error(ApiResponseError.NetworkException) as ApiResponse<QuotesResponse>
|
||||
coEvery { tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) } returns errorResponse
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeActual(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch single quote failure because app currency not found`() = runTest {
|
||||
val params = SingleQuoteFetcher.Params(rawCurrencyId = currenciesId, appCurrencyId = null)
|
||||
|
||||
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns null
|
||||
|
||||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
|
||||
quotesStore.storeActual(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val currenciesId = CryptoCurrency.RawID(value = "BTC")
|
||||
|
||||
val usdAppCurrency = CurrenciesResponse.Currency(
|
||||
id = "USD".lowercase(),
|
||||
code = "USD",
|
||||
name = "US Dollar",
|
||||
unit = "$",
|
||||
type = "fiat",
|
||||
rateBTC = "",
|
||||
)
|
||||
|
||||
val successResponse = QuotesResponse(
|
||||
quotes = mapOf(
|
||||
"BTC" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.tokens.di
|
|||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.tokens.repository.*
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||
|
|
@ -33,6 +34,7 @@ internal object TokensDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
expressServiceLoader: ExpressServiceLoader,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -43,6 +45,7 @@ internal object TokensDataModule {
|
|||
expressServiceLoader = expressServiceLoader,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +77,7 @@ internal object TokensDataModule {
|
|||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): NetworksRepository {
|
||||
return DefaultNetworksRepository(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
|
|
@ -83,6 +87,7 @@ internal object TokensDataModule {
|
|||
cacheRegistry = cacheRegistry,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.*
|
|||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.*
|
||||
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
|
|
@ -51,12 +50,12 @@ internal class DefaultCurrenciesRepository(
|
|||
private val expressServiceLoader: ExpressServiceLoader,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
) : CurrenciesRepository {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers)
|
||||
|
|
@ -223,7 +222,7 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency), refresh)
|
||||
currency
|
||||
}
|
||||
|
|
@ -237,8 +236,8 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currencies = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
userWallet.scanResponse,
|
||||
val currencies = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, currencies, refresh)
|
||||
currencies
|
||||
|
|
@ -253,7 +252,9 @@ internal class DefaultCurrenciesRepository(
|
|||
val userWallet = getUserWallet(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
.find { it.id == id }
|
||||
requireNotNull(currency) { "Unable to find currency with provided ID: $id" }
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, listOf(currency))
|
||||
|
|
@ -672,7 +673,7 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.blockchain.common.address.AddressType
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
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.tokens.utils.CardCryptoCurrenciesFactory
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.network.NetworksStatusesStore
|
||||
|
|
@ -15,7 +15,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
|
|||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -38,12 +37,11 @@ internal class DefaultNetworksRepository(
|
|||
private val userWalletsStore: UserWalletsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : NetworksRepository {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig, excludedBlockchains)
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val networkStatusFactory = NetworkStatusFactory()
|
||||
|
||||
|
|
@ -225,10 +223,14 @@ internal class DefaultNetworksRepository(
|
|||
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
|
||||
} else {
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
.asSequence()
|
||||
} else {
|
||||
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
|
||||
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
|
||||
sequenceOf(currency)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
class CardCryptoCurrenciesFactory(
|
||||
private val demoConfig: DemoConfig,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
|
||||
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
val card = scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
demoConfig.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
||||
if (card.isTestCard) {
|
||||
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
|
||||
}
|
||||
|
||||
return blockchains.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
val primaryToken = resolver.getPrimaryToken()?.let { token ->
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
return primaryToken ?: coin
|
||||
}
|
||||
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
||||
val primaryToken = resolver.getPrimaryToken()?.let { token ->
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
|
||||
return listOfNotNull(coin, primaryToken)
|
||||
}
|
||||
}
|
||||
|
|
@ -190,12 +190,19 @@ internal class DefaultTransactionRepository(
|
|||
null
|
||||
}
|
||||
|
||||
val contractAddress = when (val identifier = nftAsset.identifier) {
|
||||
is NFTAsset.Identifier.EVM -> identifier.tokenAddress
|
||||
is NFTAsset.Identifier.Solana -> identifier.tokenAddress
|
||||
is NFTAsset.Identifier.TON -> identifier.tokenAddress
|
||||
NFTAsset.Identifier.Unknown -> ""
|
||||
}
|
||||
|
||||
return@withContext createTransaction(
|
||||
amount = Amount(
|
||||
value = nftAsset.amount?.toBigDecimal() ?: error("Invalid amount"),
|
||||
token = Token(
|
||||
symbol = blockchain.currency,
|
||||
contractAddress = "",
|
||||
contractAddress = contractAddress,
|
||||
decimals = nftAsset.decimals ?: error("Invalid decimals"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Suppress("TooManyFunctions")
|
||||
internal object WalletConnectDataModule {
|
||||
|
||||
@Provides
|
||||
|
|
@ -86,6 +87,7 @@ internal object WalletConnectDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
legacyStore: WalletConnectSessionsRepository,
|
||||
getWallets: GetWalletsUseCase,
|
||||
associateNetworks: AssociateNetworksDelegate,
|
||||
): DefaultWcSessionsManager {
|
||||
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
|
||||
return DefaultWcSessionsManager(
|
||||
|
|
@ -93,6 +95,7 @@ internal object WalletConnectDataModule {
|
|||
dispatchers = dispatchers,
|
||||
legacyStore = legacyStore,
|
||||
getWallets = getWallets,
|
||||
associateNetworks = associateNetworks,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -121,12 +124,12 @@ internal object WalletConnectDataModule {
|
|||
@Singleton
|
||||
fun wcEthNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcEthNetwork.Factories,
|
||||
namespaceConverter: WcEthNetwork.NamespaceConverter,
|
||||
): WcEthNetwork = WcEthNetwork(
|
||||
moshi = moshi,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
namespaceConverter = namespaceConverter,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
)
|
||||
|
|
@ -135,7 +138,7 @@ internal object WalletConnectDataModule {
|
|||
@Singleton
|
||||
fun wcSolanaNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
namespaceConverter: WcSolanaNetwork.NamespaceConverter,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcSolanaNetwork.Factories,
|
||||
walletManager: UserWalletManager,
|
||||
|
|
@ -143,28 +146,28 @@ internal object WalletConnectDataModule {
|
|||
moshi = moshi,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
namespaceConverter = namespaceConverter,
|
||||
walletManager = walletManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun caipNamespaceDelegate(
|
||||
diHelperBox: DiHelperBox,
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): CaipNamespaceDelegate = CaipNamespaceDelegate(
|
||||
namespaceConverters = diHelperBox.converters,
|
||||
namespaceConverters = namespaceConverters,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun associateNetworksDelegate(
|
||||
diHelperBox: DiHelperBox,
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
getWallets: GetWalletsUseCase,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): AssociateNetworksDelegate = AssociateNetworksDelegate(
|
||||
namespaceConverters = diHelperBox.converters,
|
||||
namespaceConverters = namespaceConverters,
|
||||
getWallets = getWallets,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
|
|
@ -176,10 +179,16 @@ internal object WalletConnectDataModule {
|
|||
ethNetwork,
|
||||
solanaNetwork,
|
||||
),
|
||||
converters = setOf(
|
||||
ethNetwork,
|
||||
solanaNetwork,
|
||||
),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun namespaceConverters(
|
||||
ethNamespaceConverter: WcEthNetwork.NamespaceConverter,
|
||||
solanaNamespaceConverter: WcSolanaNetwork.NamespaceConverter,
|
||||
): Set<@JvmSuppressWildcards WcNamespaceConverter> = setOf(
|
||||
ethNamespaceConverter,
|
||||
solanaNamespaceConverter,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -188,6 +197,20 @@ internal object WalletConnectDataModule {
|
|||
return DefaultWcRequestUseCaseFactory(diHelperBox.handlers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcEthNetworkNamespaceConverter(excludedBlockchains: ExcludedBlockchains): WcEthNetwork.NamespaceConverter {
|
||||
return WcEthNetwork.NamespaceConverter(excludedBlockchains)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcSolanaNetworkNamespaceConverter(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): WcSolanaNetwork.NamespaceConverter {
|
||||
return WcSolanaNetwork.NamespaceConverter(excludedBlockchains)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWcDisconnectUseCase(sessionsManager: WcSessionsManager): WcDisconnectUseCase {
|
||||
|
|
@ -195,7 +218,6 @@ internal object WalletConnectDataModule {
|
|||
}
|
||||
|
||||
internal class DiHelperBox(
|
||||
val converters: Set<WcNamespaceConverter>,
|
||||
val handlers: Set<WcRequestToUseCaseConverter>,
|
||||
)
|
||||
}
|
||||
|
|
@ -22,12 +22,10 @@ import jakarta.inject.Inject
|
|||
|
||||
internal class WcEthNetwork(
|
||||
private val moshi: Moshi,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
) : WcRequestToUseCaseConverter, WcNamespaceConverter {
|
||||
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
|
||||
private val namespaceConverter: NamespaceConverter,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? {
|
||||
val methodKey = request.request.method
|
||||
|
|
@ -39,7 +37,7 @@ internal class WcEthNetwork(
|
|||
val name = toWcMethodName(request) ?: return null
|
||||
val method: WcEthMethod = name.toMethod(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val accountAddress = when (method) {
|
||||
is WcEthMethod.MessageSign -> method.account
|
||||
is WcEthMethod.SendTransaction -> method.transaction.from
|
||||
|
|
@ -100,24 +98,31 @@ internal class WcEthNetwork(
|
|||
return WcEthMethod.SignTypedData(params = params, account = account, dataForSign = data)
|
||||
}
|
||||
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
internal class NamespaceConverter constructor(
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : WcNamespaceConverter {
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
val ethChainId = chainId.reference.toIntOrNull() ?: return null
|
||||
return Blockchain.fromChainId(ethChainId)
|
||||
}
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
if (!blockchain.isEvm()) return null
|
||||
val chainId = blockchain.getChainId() ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId.toString(),
|
||||
)
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
val ethChainId = chainId.reference.toIntOrNull() ?: return null
|
||||
return Blockchain.fromChainId(ethChainId)
|
||||
}
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
if (!blockchain.isEvm()) return null
|
||||
val chainId = blockchain.getChainId() ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class Factories @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -26,11 +26,9 @@ internal class WcSolanaNetwork(
|
|||
private val moshi: Moshi,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val namespaceConverter: NamespaceConverter,
|
||||
private val walletManager: UserWalletManager,
|
||||
) : WcNamespaceConverter, WcRequestToUseCaseConverter {
|
||||
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? {
|
||||
val methodKey = request.request.method
|
||||
|
|
@ -42,7 +40,7 @@ internal class WcSolanaNetwork(
|
|||
val name = toWcMethodName(request) ?: return null
|
||||
val method: WcSolanaMethod = name.toMethod(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val accountAddress = getAccountAddress(network)
|
||||
val context = WcMethodUseCaseContext(
|
||||
session = session,
|
||||
|
|
@ -66,31 +64,38 @@ internal class WcSolanaNetwork(
|
|||
}
|
||||
}
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when (chainId.reference) {
|
||||
MAINNET_CHAIN_ID -> Blockchain.Solana
|
||||
TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
internal class NamespaceConverter @Inject constructor(
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : WcNamespaceConverter {
|
||||
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val chainId = when (blockchain) {
|
||||
Blockchain.Solana -> MAINNET_CHAIN_ID
|
||||
Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID
|
||||
else -> null
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when (chainId.reference) {
|
||||
MAINNET_CHAIN_ID -> Blockchain.Solana
|
||||
TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
return toNetwork(chainId, wallet, excludedBlockchains)
|
||||
}
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val chainId = when (blockchain) {
|
||||
Blockchain.Solana -> MAINNET_CHAIN_ID
|
||||
Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID
|
||||
else -> null
|
||||
}
|
||||
chainId ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId,
|
||||
)
|
||||
}
|
||||
chainId ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.walletconnect.pair
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.Wallet.Model.Namespace
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -16,6 +17,16 @@ internal class AssociateNetworksDelegate constructor(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
suspend fun associate(wallet: UserWallet, namespaces: Map<String, Namespace.Session>): Set<Network> {
|
||||
val walletNetworks = getWalletNetworks(wallet)
|
||||
val namespacesSet = namespaces.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
return namespacesSet.mapNotNullTo(mutableSetOf()) { chainId ->
|
||||
val wcNetwork = namespaceConverters
|
||||
.firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } ?: return@mapNotNullTo null
|
||||
walletNetworks.find { network -> wcNetwork.id == network.id }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(WcPairError.UnsupportedNetworks::class)
|
||||
suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map<UserWallet, ProposalNetwork> {
|
||||
val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency }
|
||||
|
|
@ -31,9 +42,7 @@ internal class AssociateNetworksDelegate constructor(
|
|||
requiredNamespaces: Set<String>,
|
||||
optionalNamespaces: Set<String>,
|
||||
): ProposalNetwork {
|
||||
val walletNetworks = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId)
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.map { it.network }
|
||||
val walletNetworks = getWalletNetworks(wallet)
|
||||
|
||||
val unknownRequired = mutableSetOf<String>()
|
||||
val missingRequired = mutableSetOf<Network>()
|
||||
|
|
@ -74,6 +83,11 @@ internal class AssociateNetworksDelegate constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getWalletNetworks(wallet: UserWallet): List<Network> =
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId)
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.map { it.network }
|
||||
|
||||
private fun Map<String, Wallet.Model.Namespace.Proposal>.setOfChainId(): Set<String> =
|
||||
this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
|||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -78,9 +77,11 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
sessionForApprove = sessionForApprove,
|
||||
sdkSessionProposal = sdkSessionProposal,
|
||||
).map { settledSession ->
|
||||
val newSession = settledSession.session.toDomain(
|
||||
val newSession = WcSession(
|
||||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(settledSession.session),
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
networks = sessionForApprove.network.toSet(),
|
||||
)
|
||||
sessionsManager.saveSession(newSession)
|
||||
newSession
|
||||
|
|
@ -141,14 +142,6 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
}
|
||||
},)
|
||||
|
||||
private fun Wallet.Model.Session.toDomain(wallet: UserWallet, securityStatus: CheckDAppResult): WcSession {
|
||||
return WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(this),
|
||||
securityStatus = securityStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private sealed interface TerminalAction {
|
||||
data class Approve(val sessionForApprove: WcSessionApprove) : TerminalAction
|
||||
data object Reject : TerminalAction
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import arrow.core.right
|
|||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
|
|
@ -29,6 +30,7 @@ internal class DefaultWcSessionsManager(
|
|||
private val legacyStore: WalletConnectSessionsRepository,
|
||||
private val getWallets: GetWalletsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val associateNetworks: AssociateNetworksDelegate,
|
||||
private val scope: CoroutineScope,
|
||||
) : WcSessionsManager, WcSdkObserver {
|
||||
|
||||
|
|
@ -76,10 +78,12 @@ internal class DefaultWcSessionsManager(
|
|||
?: return@withContext null
|
||||
val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null
|
||||
val wallet = storedSession.wallet
|
||||
val networks = associateNetworks.associate(wallet, sdkSession.namespaces)
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
securityStatus = storedSession.securityStatus,
|
||||
networks = networks,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +118,7 @@ internal class DefaultWcSessionsManager(
|
|||
return mustSaveInNewStore.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun associate(
|
||||
private suspend fun associate(
|
||||
inSdk: List<Wallet.Model.Session>,
|
||||
inStore: Set<WcSessionDTO>,
|
||||
wallets: List<UserWallet>,
|
||||
|
|
@ -122,7 +126,13 @@ internal class DefaultWcSessionsManager(
|
|||
val wcSessions = inStore.mapNotNull { session ->
|
||||
val wallet = wallets.find { it.walletId == session.walletId } ?: return@mapNotNull null
|
||||
val sdkSession = inSdk.find { it.topic == session.topic } ?: return@mapNotNull null
|
||||
WcSession(wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession), session.securityStatus)
|
||||
val networks = associateNetworks.associate(wallet, sdkSession.namespaces)
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
securityStatus = session.securityStatus,
|
||||
networks = networks,
|
||||
)
|
||||
}
|
||||
return wcSessions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.domain.walletconnect.model.WcSession
|
|||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
|
|
@ -91,6 +92,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(this),
|
||||
securityStatus = CheckDAppResult.SAFE,
|
||||
networks = setOf(),
|
||||
)
|
||||
|
||||
private fun useCaseFactory() = DefaultWcPairUseCase(
|
||||
|
|
@ -99,7 +101,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
caipNamespaceDelegate = caipNamespaceDelegate,
|
||||
sdkDelegate = sdkDelegate,
|
||||
blockAidVerifier = blockAidVerifier,
|
||||
pairRequest = WcPairRequest(url, source),
|
||||
pairRequest = WcPairRequest(userWalletId = UserWalletId(""), uri = url, source = source),
|
||||
)
|
||||
|
||||
@Before
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ dependencies {
|
|||
implementation(deps.arrow.core)
|
||||
|
||||
/** tests */
|
||||
testImplementation(projects.domain.models)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.data.wallets
|
||||
|
||||
import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter
|
||||
import com.tangem.data.wallets.converters.WalletIdBodyConverter
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -17,7 +20,9 @@ import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
|||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.WEEK_MILLIS
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -28,12 +33,14 @@ import kotlinx.coroutines.withContext
|
|||
|
||||
typealias SeedPhraseNotificationsStatuses = Map<UserWalletId, SeedPhraseNotificationsStatus>
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal class DefaultWalletsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<SeedPhraseNotificationsStatuses>,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val authProvider: AuthProvider,
|
||||
) : WalletsRepository {
|
||||
|
||||
override suspend fun shouldSaveUserWalletsSync(): Boolean {
|
||||
|
|
@ -254,6 +261,53 @@ internal class DefaultWalletsRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId = walletId,
|
||||
body = WalletBody(name = walletName),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo = withContext(dispatchers.io) {
|
||||
UserWalletRemoteInfoConverter.convert(
|
||||
value = tangemTechApi.getWalletById(walletId).getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean): List<UserWalletRemoteInfo> =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.getWallets(applicationId)
|
||||
.getOrThrow()
|
||||
.map { walletInfo ->
|
||||
val userWallet = UserWalletRemoteInfoConverter.convert(
|
||||
value = walletInfo,
|
||||
)
|
||||
if (updateCache) {
|
||||
setNotificationsEnabledLocally(
|
||||
userWalletId = userWallet.walletId,
|
||||
isEnabled = userWallet.isNotificationsEnabled,
|
||||
)
|
||||
}
|
||||
userWallet
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun associateWallets(applicationId: String, wallets: List<UserWallet>) =
|
||||
withContext(dispatchers.io) {
|
||||
val publicKeys = authProvider.getCardsPublicKeys()
|
||||
val walletsBody = wallets.map { userWallet ->
|
||||
WalletIdBodyConverter.convert(
|
||||
userWallet = userWallet,
|
||||
publicKeys = publicKeys.filterKeys { userWallet.cardsInWallet.contains(it) },
|
||||
)
|
||||
}
|
||||
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
applicationId = applicationId,
|
||||
body = walletsBody,
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean {
|
||||
val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow()
|
||||
val isEnabled = walletResponse.notifyStatus
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.wallets.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object UserWalletRemoteInfoConverter : Converter<WalletResponse, UserWalletRemoteInfo> {
|
||||
override fun convert(value: WalletResponse): UserWalletRemoteInfo {
|
||||
return UserWalletRemoteInfo(
|
||||
walletId = UserWalletId(value.id),
|
||||
name = value.name.orEmpty(),
|
||||
isNotificationsEnabled = value.notifyStatus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.data.wallets.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CardInfoBody
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
internal object WalletIdBodyConverter {
|
||||
|
||||
fun convert(userWallet: UserWallet, publicKeys: Map<String, String>): WalletIdBody {
|
||||
return WalletIdBody(
|
||||
walletId = userWallet.walletId.stringValue,
|
||||
name = userWallet.name,
|
||||
cards = publicKeys.map {
|
||||
CardInfoBody(
|
||||
cardId = it.key,
|
||||
cardPublicKey = it.value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.wallets.di
|
|||
|
||||
import com.tangem.data.wallets.DefaultWalletNamesMigrationRepository
|
||||
import com.tangem.data.wallets.DefaultWalletsRepository
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
|
|
@ -26,6 +27,7 @@ internal object WalletsDataModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
authProvider: AuthProvider,
|
||||
): WalletsRepository {
|
||||
return DefaultWalletsRepository(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
|
|
@ -33,6 +35,7 @@ internal object WalletsDataModule {
|
|||
userWalletsStore = userWalletsStore,
|
||||
seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()),
|
||||
dispatchers = dispatchers,
|
||||
authProvider = authProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import io.mockk.coVerify
|
|||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ class DefaultWalletsRepositoryTest {
|
|||
userWalletsStore = mockk(),
|
||||
seedPhraseNotificationVisibilityStore = mockk(),
|
||||
dispatchers = dispatchers,
|
||||
authProvider = mockk(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -173,4 +176,131 @@ class DefaultWalletsRepositoryTest {
|
|||
}
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
WalletResponse(
|
||||
id = wallet2Id,
|
||||
notifyStatus = false,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = true)
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id)
|
||||
assertThat(result[1].isNotificationsEnabled).isFalse()
|
||||
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 2) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = false)
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 0) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallets and application ID WHEN associateWallets THEN should convert and send to API`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val card1PublicKey = "card1_public_key"
|
||||
val card2PublicKey = "card2_public_key"
|
||||
|
||||
val userWallets = listOf(
|
||||
mockk<UserWallet> {
|
||||
every { cardsInWallet } returns setOf(card1PublicKey)
|
||||
every { walletId } returns UserWalletId(wallet1Id)
|
||||
every { name } returns "Wallet 1"
|
||||
},
|
||||
mockk<UserWallet> {
|
||||
every { cardsInWallet } returns setOf(card2PublicKey)
|
||||
every { walletId } returns UserWalletId(wallet2Id)
|
||||
every { name } returns "Wallet 2"
|
||||
},
|
||||
)
|
||||
|
||||
val publicKeys = mapOf(
|
||||
card1PublicKey to "public_key_1",
|
||||
card2PublicKey to "public_key_2",
|
||||
)
|
||||
|
||||
val authProvider = mockk<AuthProvider> {
|
||||
every { getCardsPublicKeys() } returns publicKeys
|
||||
}
|
||||
|
||||
repository = DefaultWalletsRepository(
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsStore = mockk(),
|
||||
seedPhraseNotificationVisibilityStore = mockk(),
|
||||
dispatchers = dispatchers,
|
||||
authProvider = authProvider,
|
||||
)
|
||||
|
||||
coEvery {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
eq(applicationId),
|
||||
any(),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.associateWallets(applicationId, userWallets)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
eq(applicationId),
|
||||
match { body ->
|
||||
body.size == 2 &&
|
||||
body.any {
|
||||
it.walletId == wallet1Id && it.cards.any { card -> card.cardPublicKey == "public_key_1" } &&
|
||||
it.name == "Wallet 1"
|
||||
} &&
|
||||
body.any {
|
||||
it.walletId == wallet2Id && it.cards.any { card -> card.cardPublicKey == "public_key_2" } &&
|
||||
it.name == "Wallet 2"
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.data.wallets.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CardInfoBody
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
|
||||
class WalletIdBodyConverterTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet with cards WHEN convert THEN should return correct WalletIdBody`() {
|
||||
// GIVEN
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
val walletName = "Test Wallet"
|
||||
val userWallet = UserWallet(
|
||||
walletId = walletId,
|
||||
name = walletName,
|
||||
cardsInWallet = setOf("card1", "card2"),
|
||||
isMultiCurrency = true,
|
||||
hasBackupError = false,
|
||||
scanResponse = mockk(),
|
||||
)
|
||||
val publicKeys = mapOf(
|
||||
"card1" to "public_key_1",
|
||||
"card2" to "public_key_2",
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = WalletIdBodyConverter.convert(userWallet, publicKeys)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(
|
||||
WalletIdBody(
|
||||
walletId = walletId.stringValue,
|
||||
name = walletName,
|
||||
cards = listOf(
|
||||
CardInfoBody(
|
||||
cardId = "card1",
|
||||
cardPublicKey = "public_key_1",
|
||||
),
|
||||
CardInfoBody(
|
||||
cardId = "card2",
|
||||
cardPublicKey = "public_key_2",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet without cards WHEN convert THEN should return WalletIdBody with empty cards list`() {
|
||||
// GIVEN
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
val walletName = "Test Wallet"
|
||||
val userWallet = UserWallet(
|
||||
walletId = walletId,
|
||||
name = walletName,
|
||||
cardsInWallet = emptySet(),
|
||||
isMultiCurrency = true,
|
||||
hasBackupError = false,
|
||||
scanResponse = mockk(),
|
||||
)
|
||||
val publicKeys = emptyMap<String, String>()
|
||||
|
||||
// WHEN
|
||||
val result = WalletIdBodyConverter.convert(userWallet, publicKeys)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(
|
||||
WalletIdBody(
|
||||
walletId = walletId.stringValue,
|
||||
name = walletName,
|
||||
cards = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.tokens.TokensFeatureToggles
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -24,7 +23,6 @@ class SaveManagedTokensUseCase(
|
|||
private val customTokensRepository: CustomTokensRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
|
|
@ -102,20 +100,12 @@ class SaveManagedTokensUseCase(
|
|||
val networkToUpdate = currenciesToAdd.map { it.network }
|
||||
.subtract(existingCurrencies.map { it.network }.toSet())
|
||||
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = networksToUpdate + networkToUpdate,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = networksToUpdate + networkToUpdate,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.tokens.TokensFeatureToggles
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
|
|
@ -30,7 +29,6 @@ class SaveMarketTokensUseCase(
|
|||
private val derivationsRepository: DerivationsRepository,
|
||||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -83,20 +81,12 @@ class SaveMarketTokensUseCase(
|
|||
}
|
||||
|
||||
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.domain.notifications.models
|
||||
|
||||
@JvmInline
|
||||
value class ApplicationId(val value: String)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.notifications
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
|
@ -10,7 +11,7 @@ class GetApplicationIdUseCase(
|
|||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, String> = Either.catch {
|
||||
suspend operator fun invoke(): Either<Throwable, ApplicationId> = Either.catch {
|
||||
val localApplicationId = notificationsRepository.getApplicationId()
|
||||
if (localApplicationId != null) return@catch localApplicationId
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +1,23 @@
|
|||
package com.tangem.domain.notifications.repository
|
||||
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
|
||||
|
||||
interface NotificationsRepository {
|
||||
|
||||
@Throws
|
||||
suspend fun createApplicationId(pushToken: String? = null): String
|
||||
suspend fun createApplicationId(pushToken: String? = null): ApplicationId
|
||||
|
||||
suspend fun saveApplicationId(appId: String)
|
||||
suspend fun saveApplicationId(appId: ApplicationId)
|
||||
|
||||
suspend fun getApplicationId(): String?
|
||||
suspend fun getApplicationId(): ApplicationId?
|
||||
|
||||
suspend fun getTronTokenFeeNotificationShowCounter(): Int
|
||||
|
||||
suspend fun incrementTronTokenFeeNotificationShowCounter()
|
||||
|
||||
@Throws
|
||||
suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>)
|
||||
|
||||
@Throws
|
||||
suspend fun setWalletName(walletId: String, walletName: String)
|
||||
|
||||
@Throws
|
||||
suspend fun getWalletName(walletId: String): String?
|
||||
|
||||
@Throws
|
||||
suspend fun sendPushToken(appId: String, pushToken: String)
|
||||
suspend fun sendPushToken(appId: ApplicationId, pushToken: String)
|
||||
|
||||
@Throws
|
||||
suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.notifications
|
|||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
|
@ -20,7 +21,7 @@ class GetApplicationIdUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN local application ID exists WHEN invoke THEN return local application ID`() = runTest {
|
||||
// GIVEN
|
||||
val expectedApplicationId = "test-app-id"
|
||||
val expectedApplicationId = ApplicationId("test-app-id")
|
||||
coEvery { notificationsRepository.getApplicationId() } returns expectedApplicationId
|
||||
|
||||
// WHEN
|
||||
|
|
@ -39,7 +40,7 @@ class GetApplicationIdUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN local application ID does not exist WHEN invoke THEN create and save new application ID`() = runTest {
|
||||
// GIVEN
|
||||
val newApplicationId = "new-app-id"
|
||||
val newApplicationId = ApplicationId("new-app-id")
|
||||
coEvery { notificationsRepository.getApplicationId() } returns null
|
||||
coEvery { notificationsRepository.createApplicationId() } returns newApplicationId
|
||||
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
|
||||
|
|
@ -81,7 +82,7 @@ class GetApplicationIdUseCaseTest {
|
|||
fun `GIVEN no local application ID WHEN multiple concurrent invokes THEN create only one application ID`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val newApplicationId = "new-app-id"
|
||||
val newApplicationId = ApplicationId("new-app-id")
|
||||
var isIdCreated = false
|
||||
|
||||
coEvery { notificationsRepository.getApplicationId() } answers {
|
||||
|
|
@ -115,7 +116,7 @@ class GetApplicationIdUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = runTest {
|
||||
// GIVEN
|
||||
val newApplicationId = "new-app-id"
|
||||
val newApplicationId = ApplicationId("new-app-id")
|
||||
var isIdCreated = false
|
||||
|
||||
coEvery { notificationsRepository.getApplicationId() } answers {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.notifications
|
|||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import io.mockk.coEvery
|
||||
|
|
@ -33,7 +34,7 @@ class SendPushTokenUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN valid application ID and token WHEN invoke THEN token is sent successfully`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test-app-id"
|
||||
val applicationId = ApplicationId("test-app-id")
|
||||
val token = "test-token"
|
||||
coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId)
|
||||
coEvery { pushNotificationsTokenProvider.getToken() } returns token
|
||||
|
|
@ -62,7 +63,7 @@ class SendPushTokenUseCaseTest {
|
|||
@Test
|
||||
fun `GIVEN repository throws error WHEN invoke THEN returns error`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test-app-id"
|
||||
val applicationId = ApplicationId("test-app-id")
|
||||
val token = "test-token"
|
||||
val expectedError = RuntimeException("Network error")
|
||||
coEvery { getApplicationIdUseCase() } returns Either.Right(applicationId)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.domain.quotes.single
|
||||
|
||||
import com.tangem.domain.core.flow.FlowFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Fetcher of quote for [CryptoCurrency.RawID]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SingleQuoteFetcher : FlowFetcher<SingleQuoteFetcher.Params> {
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property rawCurrencyId crypto currency id
|
||||
*/
|
||||
data class Params(
|
||||
val rawCurrencyId: CryptoCurrency.RawID,
|
||||
val appCurrencyId: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -29,7 +28,6 @@ import kotlinx.coroutines.coroutineScope
|
|||
@Suppress("LongParameterList")
|
||||
class AddCryptoCurrenciesUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -139,26 +137,13 @@ class AddCryptoCurrenciesUseCase(
|
|||
}
|
||||
?.network
|
||||
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOfNotNull(networksToUpdate, networkToUpdate),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
catch(
|
||||
{
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOfNotNull(networksToUpdate, networkToUpdate),
|
||||
refresh = true,
|
||||
)
|
||||
},
|
||||
) {
|
||||
raise(it)
|
||||
}
|
||||
}
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOfNotNull(networksToUpdate, networkToUpdate),
|
||||
),
|
||||
)
|
||||
.bind()
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(userWalletId: UserWalletId, addedCurrency: CryptoCurrency) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.domain.tokens.error.TokenListError
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -23,7 +22,6 @@ import kotlinx.coroutines.coroutineScope
|
|||
@Suppress("LongParameterList")
|
||||
class FetchCardTokenListUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -41,7 +39,6 @@ class FetchCardTokenListUseCase(
|
|||
fetchNetworksStatuses(
|
||||
userWalletId = userWalletId,
|
||||
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
|
||||
refresh = refresh,
|
||||
)
|
||||
}
|
||||
val fetchQuotes = async {
|
||||
|
|
@ -80,19 +77,12 @@ class FetchCardTokenListUseCase(
|
|||
private suspend fun Raise<TokenListError>.fetchNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks),
|
||||
)
|
||||
.mapLeft { TokenListError.DataError(it) }
|
||||
} else {
|
||||
catch(
|
||||
block = { networksRepository.getNetworkStatusesSync(userWalletId, networks, refresh) },
|
||||
catch = { raise(TokenListError.DataError(it)) },
|
||||
)
|
||||
}
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks),
|
||||
)
|
||||
.mapLeft(TokenListError::DataError)
|
||||
.bind()
|
||||
}
|
||||
|
||||
private suspend fun fetchQuotes(currenciesIds: Set<CryptoCurrency.RawID>, refresh: Boolean) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.domain.tokens.error.CurrencyStatusError
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -26,14 +25,12 @@ import kotlinx.coroutines.coroutineScope
|
|||
* by providing a specific currency ID or fetching the status of the primary currency.
|
||||
*
|
||||
* @param currenciesRepository The repository for retrieving currency-related data.
|
||||
* @param networksRepository The repository for retrieving network-related data.
|
||||
* @param quotesRepository The repository for retrieving cryptocurrency quotes.
|
||||
*/
|
||||
// TODO: Add tests
|
||||
@Suppress("LongParameterList")
|
||||
class FetchCurrencyStatusUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
|
|
@ -86,7 +83,7 @@ class FetchCurrencyStatusUseCase(
|
|||
refresh: Boolean,
|
||||
) = coroutineScope {
|
||||
val fetchStatus = async {
|
||||
fetchNetworkStatus(userWalletId, currency.network, refresh)
|
||||
fetchNetworkStatus(userWalletId, currency.network)
|
||||
}
|
||||
val fetchQuote = async {
|
||||
fetchQuote(currency.id, refresh)
|
||||
|
|
@ -120,23 +117,12 @@ class FetchCurrencyStatusUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchNetworkStatus(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.mapLeft { CurrencyStatusError.DataError(it) }
|
||||
} else {
|
||||
catch(
|
||||
block = { networksRepository.getNetworkStatusesSync(userWalletId, setOf(network), refresh) },
|
||||
) {
|
||||
raise(CurrencyStatusError.DataError(it))
|
||||
}
|
||||
}
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) {
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.mapLeft { CurrencyStatusError.DataError(it) }
|
||||
.bind()
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchQuote(currencyId: CryptoCurrency.ID, refresh: Boolean) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.domain.tokens.error.TokenListError
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -27,7 +26,6 @@ import kotlinx.coroutines.coroutineScope
|
|||
* network statuses, and quotes for tokens associated with a user's wallet.
|
||||
*
|
||||
* @param currenciesRepository The repository for retrieving currency-related data.
|
||||
* @param networksRepository The repository for retrieving network-related data.
|
||||
* @param quotesRepository The repository for retrieving cryptocurrency quotes.
|
||||
* @param stakingRepository The repository for retrieving staking-related data.
|
||||
*/
|
||||
|
|
@ -35,7 +33,6 @@ import kotlinx.coroutines.coroutineScope
|
|||
@Suppress("LongParameterList")
|
||||
class FetchTokenListUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
|
|
@ -71,7 +68,6 @@ class FetchTokenListUseCase(
|
|||
fetchNetworksStatuses(
|
||||
userWalletId = userWalletId,
|
||||
networks = currencies.mapTo(hashSetOf()) { it.network },
|
||||
refresh = mode.refreshNetworksStatuses,
|
||||
)
|
||||
}
|
||||
val fetchQuotes = async {
|
||||
|
|
@ -111,23 +107,12 @@ class FetchTokenListUseCase(
|
|||
private suspend fun Raise<TokenListError>.fetchNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
if (refresh) {
|
||||
multiNetworkStatusFetcher(
|
||||
params = MultiNetworkStatusFetcher.Params(userWalletId, networks),
|
||||
)
|
||||
.mapLeft { TokenListError.DataError(it) }
|
||||
.bind()
|
||||
}
|
||||
} else {
|
||||
catch(
|
||||
block = { networksRepository.getNetworkStatusesSync(userWalletId, networks, refresh) },
|
||||
) {
|
||||
raise(TokenListError.DataError(it))
|
||||
}
|
||||
}
|
||||
multiNetworkStatusFetcher(
|
||||
params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks),
|
||||
)
|
||||
.mapLeft(TokenListError::DataError)
|
||||
.bind()
|
||||
}
|
||||
|
||||
private suspend fun fetchQuotes(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean) {
|
||||
|
|
@ -175,25 +160,21 @@ class FetchTokenListUseCase(
|
|||
*/
|
||||
enum class RefreshMode(
|
||||
internal val refreshCurrencies: Boolean,
|
||||
internal val refreshNetworksStatuses: Boolean,
|
||||
internal val refreshQuotes: Boolean,
|
||||
internal val refreshYieldBalances: Boolean,
|
||||
) {
|
||||
NONE(
|
||||
refreshCurrencies = false,
|
||||
refreshNetworksStatuses = false,
|
||||
refreshQuotes = false,
|
||||
refreshYieldBalances = false,
|
||||
),
|
||||
FULL(
|
||||
refreshCurrencies = true,
|
||||
refreshNetworksStatuses = true,
|
||||
refreshQuotes = true,
|
||||
refreshYieldBalances = true,
|
||||
),
|
||||
SKIP_CURRENCIES(
|
||||
refreshCurrencies = false,
|
||||
refreshNetworksStatuses = true,
|
||||
refreshQuotes = true,
|
||||
refreshYieldBalances = true,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class GetCryptoCurrencyStatusSyncUseCase(
|
||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
) {
|
||||
|
||||
// multi-currency
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
isSingleWalletWithTokens: Boolean = false,
|
||||
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
|
||||
return currencyStatusOperations.getCurrencyStatusSync(userWalletId, cryptoCurrencyId, isSingleWalletWithTokens)
|
||||
.mapLeft { error -> error.mapToCurrencyError() }
|
||||
}
|
||||
|
||||
// single-currency
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<CurrencyStatusError, CryptoCurrencyStatus> {
|
||||
return currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId)
|
||||
.mapLeft { error -> error.mapToCurrencyError() }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Use case for fetching the status of a cryptocurrency associated with a user wallet.
|
||||
*
|
||||
*/
|
||||
class GetCurrencyStatusUpdatesUseCase(
|
||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Invokes the use case.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @param currencyId The unique identifier of the cryptocurrency.
|
||||
* @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards)
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
isSingleWalletWithTokens: Boolean,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return flow {
|
||||
emitAll(
|
||||
getCurrencyStatus(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = currencyId,
|
||||
isSingleWalletWithTokens = isSingleWalletWithTokens,
|
||||
),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private suspend fun getCurrencyStatus(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
isSingleWalletWithTokens: Boolean,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = currencyId,
|
||||
isSingleWalletWithTokens = isSingleWalletWithTokens,
|
||||
)
|
||||
|
||||
return currencyFlow.map { maybeCurrency ->
|
||||
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,25 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class GetCryptoCurrencyStatusesSyncUseCase(
|
||||
class GetMultiCryptoCurrencyStatusUseCase(
|
||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
/**
|
||||
* Returns synchronously list of cryptocurrency statuses for Multi-Currency wallet
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
suspend fun invokeMultiWalletSync(userWalletId: UserWalletId): Either<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
return currencyStatusOperations.getCurrenciesStatusesSync(userWalletId)
|
||||
.mapLeft { error -> error.mapToTokenListError() }
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Use case for fetching the status of the primary cryptocurrency associated with a user wallet.
|
||||
*
|
||||
* @property dispatchers Provides coroutine dispatchers.
|
||||
*/
|
||||
class GetPrimaryCurrencyStatusUpdatesUseCase(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Invokes the use case.
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return flow {
|
||||
emitAll(getPrimaryCurrency(userWalletId))
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private suspend fun getPrimaryCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWalletId).map { maybeCurrency ->
|
||||
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Use case for fetching the status of a cryptocurrency associated with a user wallet.
|
||||
*
|
||||
*/
|
||||
class GetSingleCryptoCurrencyStatusUseCase(
|
||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Returns cryptocurrency status flow for Multi-Currency wallet
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @param currencyId The unique identifier of the cryptocurrency.
|
||||
* @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards)
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
fun invokeMultiWallet(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
isSingleWalletWithTokens: Boolean,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return flow {
|
||||
emitAll(
|
||||
getCurrencyStatus(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = currencyId,
|
||||
isSingleWalletWithTokens = isSingleWalletWithTokens,
|
||||
),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cryptocurrency status flow for primary currency for Single-Currency wallet
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
fun invokeSingleWallet(userWalletId: UserWalletId): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
return flow {
|
||||
emitAll(
|
||||
currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWalletId).map { maybeCurrency ->
|
||||
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
|
||||
},
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns synchronously cryptocurrency status for Multi-Currency wallet
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @param cryptoCurrencyId The unique identifier of the cryptocurrency.
|
||||
* @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards)
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
suspend fun invokeMultiWalletSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
isSingleWalletWithTokens: Boolean = false,
|
||||
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
|
||||
return currencyStatusOperations.getCurrencyStatusSync(userWalletId, cryptoCurrencyId, isSingleWalletWithTokens)
|
||||
.mapLeft { error -> error.mapToCurrencyError() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns synchronously cryptocurrency status for primary currency for Single-Currency wallet
|
||||
*
|
||||
* @param userWalletId The unique identifier of the user's wallet.
|
||||
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
|
||||
*/
|
||||
suspend fun invokeSingleWalletSync(userWalletId: UserWalletId): Either<CurrencyStatusError, CryptoCurrencyStatus> {
|
||||
return currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId)
|
||||
.mapLeft { error -> error.mapToCurrencyError() }
|
||||
}
|
||||
|
||||
private suspend fun getCurrencyStatus(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
isSingleWalletWithTokens: Boolean,
|
||||
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
|
||||
val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = currencyId,
|
||||
isSingleWalletWithTokens = isSingleWalletWithTokens,
|
||||
)
|
||||
|
||||
return currencyFlow.map { maybeCurrency ->
|
||||
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,6 @@ package com.tangem.domain.tokens
|
|||
*/
|
||||
interface TokensFeatureToggles {
|
||||
|
||||
val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
|
||||
val isQuotesLoadingRefactoringEnabled: Boolean
|
||||
|
||||
val isStakingLoadingRefactoringEnabled: Boolean
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ package com.tangem.domain.tokens
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -15,14 +13,9 @@ import kotlinx.coroutines.delay
|
|||
* Use case responsible for fetching currency status information, including network status
|
||||
* and quotes for a given cryptocurrency. It provides methods to fetch currency status either
|
||||
* by providing a specific currency ID or fetching the status of the primary currency.
|
||||
*
|
||||
* @param networksRepository The repository for retrieving network-related data.
|
||||
*/
|
||||
|
||||
class UpdateDelayedNetworkStatusUseCase(
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -30,37 +23,24 @@ class UpdateDelayedNetworkStatusUseCase(
|
|||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param network Network of the cryptocurrency.
|
||||
* @param refresh Indicates whether to force a refresh of the status data.
|
||||
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
delayMillis: Long = 0L,
|
||||
refresh: Boolean = false,
|
||||
): Either<CurrencyStatusError, Unit> {
|
||||
delay(delayMillis)
|
||||
return either {
|
||||
fetchNetworkStatus(userWalletId, network, refresh)
|
||||
fetchNetworkStatus(userWalletId, network)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchNetworkStatus(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
refresh: Boolean,
|
||||
) {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.mapLeft { CurrencyStatusError.DataError(it) }
|
||||
} else {
|
||||
catch(
|
||||
block = { networksRepository.getNetworkStatusesSync(userWalletId, setOf(network), refresh) },
|
||||
) {
|
||||
raise(CurrencyStatusError.DataError(it))
|
||||
}
|
||||
}
|
||||
private suspend fun Raise<CurrencyStatusError>.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) {
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params.Simple(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.mapLeft(CurrencyStatusError::DataError)
|
||||
.bind()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.tokens.TokensFeatureToggles
|
|||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -33,7 +32,6 @@ import kotlinx.coroutines.flow.*
|
|||
*
|
||||
* @property currenciesRepository repository for currencies
|
||||
* @property quotesRepository repository for quotes
|
||||
* @property networksRepository repository for networks
|
||||
* @property stakingRepository repository for staking
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -43,7 +41,6 @@ abstract class BaseCurrencyStatusOperations(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val quotesRepositoryV2: QuotesRepositoryV2,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
|
|
@ -203,24 +200,14 @@ abstract class BaseCurrencyStatusOperations(
|
|||
?.right()
|
||||
?: Error.EmptyQuotes.left()
|
||||
|
||||
val networkStatuses = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(
|
||||
userWalletId = userWalletId,
|
||||
network = currency.network,
|
||||
),
|
||||
)
|
||||
.firstOrNull()
|
||||
.right()
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
val networkStatuses = singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(currency.network),
|
||||
refresh = false,
|
||||
)
|
||||
.firstOrNull { it.network == currency.network }
|
||||
.right()
|
||||
}
|
||||
network = currency.network,
|
||||
),
|
||||
)
|
||||
.firstOrNull()
|
||||
.right()
|
||||
|
||||
val yieldBalances = getYieldBalanceSync(userWalletId, currency)
|
||||
|
||||
|
|
@ -270,7 +257,7 @@ abstract class BaseCurrencyStatusOperations(
|
|||
val nonEmptyCurrencies =
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull()
|
||||
?: return emptyList<CryptoCurrencyStatus>().right()
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
val (_, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
val rawIds = currenciesIds.mapNotNull { it.rawCurrencyId }.toSet()
|
||||
|
||||
val quotes = if (tokensFeatureToggles.isQuotesLoadingRefactoringEnabled) {
|
||||
|
|
@ -279,16 +266,13 @@ abstract class BaseCurrencyStatusOperations(
|
|||
quotesRepository.getQuotesSync(rawIds, false).right()
|
||||
}
|
||||
|
||||
val networkStatuses = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusSupplier(
|
||||
params = MultiNetworkStatusProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
.firstOrNull()
|
||||
.orEmpty()
|
||||
.right()
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
|
||||
}
|
||||
val networkStatuses = multiNetworkStatusSupplier(
|
||||
params = MultiNetworkStatusProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
.firstOrNull()
|
||||
.orEmpty()
|
||||
.right()
|
||||
|
||||
val yieldBalances = getYieldBalancesSync(userWalletId, nonEmptyCurrencies)
|
||||
|
||||
return currencyStatusProxyCreator.createCurrenciesStatuses(
|
||||
|
|
@ -326,25 +310,11 @@ abstract class BaseCurrencyStatusOperations(
|
|||
)
|
||||
}
|
||||
|
||||
val networkStatus = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(
|
||||
userWalletId = userWalletId,
|
||||
network = currency.network,
|
||||
),
|
||||
)
|
||||
.firstOrNull()
|
||||
.right()
|
||||
} else {
|
||||
catch(
|
||||
block = {
|
||||
networksRepository.getNetworkStatusesSync(userWalletId, setOf(currency.network))
|
||||
.firstOrNull { it.network == currency.network }
|
||||
.right()
|
||||
},
|
||||
catch = { Error.DataError(it).left() },
|
||||
)
|
||||
}
|
||||
val networkStatus = singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = currency.network),
|
||||
)
|
||||
.firstOrNull()
|
||||
.right()
|
||||
|
||||
val yieldBalances = getYieldBalanceSync(userWalletId, currency)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import com.tangem.domain.tokens.error.TokenListError
|
|||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.tokens.utils.extractAddress
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -43,7 +42,6 @@ class CachedCurrenciesStatusesOperations(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
|
|
@ -58,7 +56,6 @@ class CachedCurrenciesStatusesOperations(
|
|||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
|
|
@ -156,7 +153,7 @@ class CachedCurrenciesStatusesOperations(
|
|||
|
||||
combine(
|
||||
flow = getQuotes(currenciesIds),
|
||||
flow2 = getNetworksStatuses(userWalletId, networks),
|
||||
flow2 = getNetworkStatusesUpdates(userWalletId, networks),
|
||||
flow3 = getYieldBalances(userWalletId, currencies),
|
||||
flow4 = fetchingState.map {
|
||||
val state = it[userWalletId] ?: return@map false
|
||||
|
|
@ -184,13 +181,9 @@ class CachedCurrenciesStatusesOperations(
|
|||
coroutineScope {
|
||||
awaitAll(
|
||||
async {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
multiNetworkStatusFetcher(
|
||||
params = MultiNetworkStatusFetcher.Params(userWalletId, networks),
|
||||
)
|
||||
} else {
|
||||
networksRepository.fetchNetworkStatuses(userWalletId, networks)
|
||||
}
|
||||
multiNetworkStatusFetcher(
|
||||
params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks),
|
||||
)
|
||||
},
|
||||
async {
|
||||
val rawCurrenciesIds = currenciesIds.mapNotNullTo(mutableSetOf()) { it.rawCurrencyId }
|
||||
|
|
@ -327,44 +320,12 @@ class CachedCurrenciesStatusesOperations(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): EitherFlow<Error, Set<NetworkStatus>> {
|
||||
return if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.map<NetworkStatus, Either<Error, Set<NetworkStatus>>> { setOf(it).right() }
|
||||
.distinctUntilChanged()
|
||||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network))
|
||||
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(Error.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: NonEmptySet<Network>,
|
||||
): EitherFlow<TokenListError, Set<NetworkStatus>> {
|
||||
return if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
getNetworkStatusesUpdates(userWalletId, networks)
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesUpdates(userWalletId, networks)
|
||||
.map<Set<NetworkStatus>, Either<TokenListError, Set<NetworkStatus>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(TokenListError.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
return singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.map<NetworkStatus, Either<Error, Set<NetworkStatus>>> { setOf(it).right() }
|
||||
.distinctUntilChanged()
|
||||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
|
||||
private fun getYieldBalances(
|
||||
|
|
|
|||
|
|
@ -1,197 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.mock.MockNetworks
|
||||
import com.tangem.domain.tokens.mock.MockQuotes
|
||||
import com.tangem.domain.tokens.mock.MockTokens
|
||||
import com.tangem.domain.tokens.mock.MockTokensStates
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.MockCurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.MockNetworksRepository
|
||||
import com.tangem.domain.tokens.repository.MockQuotesRepository
|
||||
import com.tangem.domain.tokens.repository.MockStakingRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.mockk
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest {
|
||||
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
private val userWalletId = UserWalletId(value = null)
|
||||
|
||||
@Test
|
||||
fun `when all data received then token should be received`() = runTest {
|
||||
// Given
|
||||
val expectedResult = MockTokensStates.loadedTokensStates.first().right()
|
||||
|
||||
val useCase = getUseCase()
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when token getting failed then error should be received`() = runTest {
|
||||
// Given
|
||||
val expectedResult = CurrencyStatusError.DataError(DataError.NetworkError.NoInternetConnection).left()
|
||||
|
||||
val useCase = getUseCase(token = DataError.NetworkError.NoInternetConnection.left())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes getting failed then currency with no quote status should be received`() = runTest {
|
||||
// Given
|
||||
val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right()
|
||||
|
||||
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when networks statuses getting failed then error should be received`() = runTest {
|
||||
// Given
|
||||
val expectedResult = CurrencyStatusError.DataError(DataError.NetworkError.NoInternetConnection).left()
|
||||
|
||||
val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when networks statuses flow is empty then error should be received`() = runTest {
|
||||
val expectedResult = CurrencyStatusError.UnableToCreateCurrency.left()
|
||||
|
||||
val useCase = getUseCase(statuses = flowOf())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes flow is empty then no quote status should be received`() = runTest {
|
||||
val expectedResult = MockTokensStates.noQuotesTokensStatuses.first().right()
|
||||
|
||||
val useCase = getUseCase(quotes = flowOf(emptySet<Quote>().right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes are empty and statuses are verified then token without quote should be received`() = runTest {
|
||||
val expectedResult = with(MockTokensStates.tokenState1) {
|
||||
copy(
|
||||
value = CryptoCurrencyStatus.NoQuote(
|
||||
amount = BigDecimal.TEN,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary),
|
||||
),
|
||||
yieldBalance = null,
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
}
|
||||
.right()
|
||||
|
||||
val useCase = getUseCase(
|
||||
statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
quotes = flowOf(emptySet<Quote>().right()),
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when quotes are loaded and statuses are empty then error be received`() = runTest {
|
||||
val expectedResult = CurrencyStatusError.UnableToCreateCurrency.left()
|
||||
|
||||
val useCase = getUseCase(
|
||||
statuses = flowOf(emptySet<NetworkStatus>().right()),
|
||||
quotes = flowOf(MockQuotes.quotes.right()),
|
||||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
|
||||
private fun getUseCase(
|
||||
token: Either<DataError, CryptoCurrency> = MockTokens.token1.right(),
|
||||
removeCurrencyResult: Either<DataError, Unit> = Unit.right(),
|
||||
quotes: Flow<Either<DataError, Set<Quote>>> = flowOf(MockQuotes.quotes.right()),
|
||||
statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
) = GetPrimaryCurrencyStatusUpdatesUseCase(
|
||||
currencyStatusOperations = CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = MockCurrenciesRepository(
|
||||
sortTokensResult = Unit.right(),
|
||||
removeCurrencyResult = removeCurrencyResult,
|
||||
token = token,
|
||||
tokens = flowOf(),
|
||||
isGrouped = flowOf(),
|
||||
isSortedByBalance = flowOf(),
|
||||
),
|
||||
quotesRepository = MockQuotesRepository(quotes),
|
||||
networksRepository = MockNetworksRepository(statuses),
|
||||
stakingRepository = MockStakingRepository(),
|
||||
|
||||
tokensFeatureToggles = object : TokensFeatureToggles {
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean = false
|
||||
override val isQuotesLoadingRefactoringEnabled: Boolean = false
|
||||
override val isStakingLoadingRefactoringEnabled: Boolean = false
|
||||
},
|
||||
singleNetworkStatusSupplier = mockk(),
|
||||
multiNetworkStatusFetcher = mockk(),
|
||||
multiNetworkStatusSupplier = mockk(),
|
||||
multiQuoteFetcher = mockk(),
|
||||
singleQuoteSupplier = mockk(),
|
||||
quotesRepositoryV2 = mockk(),
|
||||
singleYieldBalanceSupplier = mockk(),
|
||||
multiYieldBalanceFetcher = mockk(),
|
||||
),
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,12 +7,10 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -23,9 +21,7 @@ class AssociateAssetUseCase(
|
|||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
|
|
@ -59,20 +55,13 @@ class AssociateAssetUseCase(
|
|||
}
|
||||
|
||||
private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean {
|
||||
val networkStatus = if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(
|
||||
userWalletId = userWalletId,
|
||||
network = currency.network,
|
||||
),
|
||||
)
|
||||
.firstOrNull()
|
||||
} else {
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
val networkStatus = singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(currency.network),
|
||||
).find { it.network == currency.network }
|
||||
}
|
||||
network = currency.network,
|
||||
),
|
||||
)
|
||||
.firstOrNull()
|
||||
|
||||
val networkCoinAmountStatus = (networkStatus?.value as? NetworkStatus.Verified)
|
||||
?.amounts
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
|||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.demo.DemoTransactionSender
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
|
|
@ -33,7 +32,6 @@ class SendTransactionUseCase(
|
|||
private val transactionRepository: TransactionRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
txsData: List<TransactionData>,
|
||||
|
|
@ -80,16 +78,15 @@ class SendTransactionUseCase(
|
|||
}
|
||||
|
||||
cardSdkConfigRepository.setLinkedTerminal(linkedTerminal)
|
||||
|
||||
return sendResult
|
||||
.onRight {
|
||||
if (tokensFeatureToggles.isNetworksLoadingRefactoringEnabled) {
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params.Simple(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = network,
|
||||
),
|
||||
)
|
||||
}
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params.Simple(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = network,
|
||||
),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
ifRight = { result -> result.hashes.right() },
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class WcPairRequest(
|
||||
val uri: String,
|
||||
val source: Source,
|
||||
val userWalletId: UserWalletId,
|
||||
) {
|
||||
enum class Source { QR, DEEPLINK, CLIPBOARD, ETC }
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
data class WcSession(
|
||||
val wallet: UserWallet,
|
||||
val networks: Set<Network>,
|
||||
val sdkModel: WcSdkSession,
|
||||
val securityStatus: CheckDAppResult,
|
||||
)
|
||||
|
|
@ -26,6 +26,7 @@ dependencies {
|
|||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.notifications.models)
|
||||
// endregion
|
||||
|
||||
// region Tangem libraries
|
||||
|
|
@ -37,4 +38,11 @@ dependencies {
|
|||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// end
|
||||
|
||||
// region Tests
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
// end
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.domain.wallets.models
|
||||
|
||||
class UserWalletRemoteInfo(
|
||||
val walletId: UserWalletId,
|
||||
val name: String,
|
||||
val isNotificationsEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.domain.wallets.delegate
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DefaultUserWalletsSyncDelegate(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : UserWalletsSyncDelegate {
|
||||
|
||||
override suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet> {
|
||||
return renameUserWallet(userWalletId, name)
|
||||
}
|
||||
|
||||
override suspend fun syncWallets(list: List<UserWalletRemoteInfo>): Either<UpdateWalletError, Unit> = either {
|
||||
list.forEach { userWallet ->
|
||||
renameUserWallet(userWallet.walletId, userWallet.name).bind()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove dispatchers whnen UserWalletsListManager will be main safe
|
||||
private suspend fun renameUserWallet(
|
||||
userWalletId: UserWalletId,
|
||||
name: String,
|
||||
): Either<UpdateWalletError, UserWallet> = withContext(dispatchers.io) {
|
||||
either {
|
||||
val existingNames = userWalletsListManager.userWalletsSync
|
||||
|
||||
ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) {
|
||||
UpdateWalletError.NameAlreadyExists
|
||||
}
|
||||
|
||||
val previousName = existingNames.firstOrNull { it.walletId == userWalletId }?.name.orEmpty()
|
||||
if (previousName == name) {
|
||||
raise(UpdateWalletError.NameAlreadyExists)
|
||||
}
|
||||
|
||||
return@withContext when (
|
||||
val result =
|
||||
userWalletsListManager.update(userWalletId) { it.copy(name = name) }
|
||||
) {
|
||||
is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error))
|
||||
is CompletionResult.Success -> result.data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.domain.wallets.delegate
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
|
||||
interface UserWalletsSyncDelegate {
|
||||
|
||||
suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet>
|
||||
|
||||
suspend fun syncWallets(list: List<UserWalletRemoteInfo>): Either<UpdateWalletError, Unit>
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
package com.tangem.domain.wallets.repository
|
||||
|
||||
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface WalletsRepository {
|
||||
|
||||
suspend fun shouldSaveUserWalletsSync(): Boolean
|
||||
|
|
@ -43,4 +46,16 @@ interface WalletsRepository {
|
|||
|
||||
@Throws
|
||||
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)
|
||||
|
||||
@Throws
|
||||
suspend fun setWalletName(walletId: String, walletName: String)
|
||||
|
||||
@Throws
|
||||
suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo
|
||||
|
||||
@Throws
|
||||
suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean = true): List<UserWalletRemoteInfo>
|
||||
|
||||
@Throws
|
||||
suspend fun associateWallets(applicationId: String, wallets: List<UserWallet>)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
class AssociateWalletsWithApplicationIdUseCase(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(applicationId: ApplicationId): Either<Throwable, Unit> = Either.catch {
|
||||
val wallets = userWalletsListManager.userWalletsSync
|
||||
walletsRepository.associateWallets(applicationId.value, wallets)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,38 +2,23 @@ package com.tangem.domain.wallets.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
/**
|
||||
* Use case for rename user wallet
|
||||
*
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*/
|
||||
class RenameWalletUseCase(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet> =
|
||||
withContext(dispatchers.io) {
|
||||
either {
|
||||
val existingNames = userWalletsListManager.userWalletsSync
|
||||
|
||||
ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) {
|
||||
UpdateWalletError.NameAlreadyExists
|
||||
}
|
||||
|
||||
when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) {
|
||||
is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error))
|
||||
is CompletionResult.Success -> result.data
|
||||
}
|
||||
either {
|
||||
runCatching {
|
||||
walletsRepository.setWalletName(userWalletId.stringValue, name)
|
||||
}
|
||||
|
||||
userWalletsSyncDelegate.syncWallet(userWalletId, name).bind()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
class UpdateRemoteWalletsInfoUseCase(
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(applicationId: ApplicationId): Either<UpdateWalletError, Unit> = either {
|
||||
val walletsInfo = walletsRepository.getWalletsInfo(applicationId.value)
|
||||
userWalletsSyncDelegate.syncWallets(walletsInfo).bind()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue