Updated on 2026-08-14
This commit is contained in:
commit
3cbc2dadfb
822 changed files with 12838 additions and 5366 deletions
|
|
@ -13,6 +13,7 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.wallets.models)
|
||||
api(projects.domain.yieldSupply.models)
|
||||
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
|
|
|||
12
domain/account/detekt-baseline-main.xml
Normal file
12
domain/account/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:GetArchivedAccountsUseCase.kt$GetArchivedAccountsUseCase${ send(it.lceError()) return }</ID>
|
||||
<ID>MultilineLambdaItParameter:GetArchivedAccountsUseCase.kt$GetArchivedAccountsUseCase${ send(it.lceError()) return@channelFlow }</ID>
|
||||
<ID>NamedArguments:AddCryptoPortfolioUseCase.kt$AddCryptoPortfolioUseCase$createAccount(userWalletId, accountName, icon, derivationIndex)</ID>
|
||||
<ID>UnnecessaryAbstractClass:MultiAccountListSupplier.kt$MultiAccountListSupplier$MultiAccountListSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleAccountListSupplier.kt$SingleAccountListSupplier$SingleAccountListSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleAccountSupplier.kt$SingleAccountSupplier$SingleAccountSupplier</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -28,6 +28,7 @@ data class AccountList private constructor(
|
|||
val userWalletId: UserWalletId,
|
||||
val accounts: List<Account>,
|
||||
val totalAccounts: Int,
|
||||
val totalArchivedAccounts: Int,
|
||||
val sortType: TokensSortType,
|
||||
val groupType: TokensGroupType,
|
||||
) {
|
||||
|
|
@ -60,6 +61,7 @@ data class AccountList private constructor(
|
|||
userWalletId = this.userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = this.totalAccounts + if (isNewAccount) 1 else 0,
|
||||
totalArchivedAccounts = this.totalArchivedAccounts,
|
||||
sortType = this.sortType,
|
||||
groupType = this.groupType,
|
||||
)
|
||||
|
|
@ -82,6 +84,7 @@ data class AccountList private constructor(
|
|||
userWalletId = this.userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = this.totalAccounts - if (isExistingAccount) 1 else 0,
|
||||
totalArchivedAccounts = this.totalArchivedAccounts,
|
||||
sortType = this.sortType,
|
||||
groupType = this.groupType,
|
||||
)
|
||||
|
|
@ -152,6 +155,7 @@ data class AccountList private constructor(
|
|||
companion object {
|
||||
|
||||
const val MAX_ACCOUNTS_COUNT = 20
|
||||
const val MAX_ARCHIVED_ACCOUNTS_COUNT = 1000
|
||||
private const val MAX_MAIN_ACCOUNTS_COUNT = 1
|
||||
|
||||
/**
|
||||
|
|
@ -166,6 +170,7 @@ data class AccountList private constructor(
|
|||
userWalletId: UserWalletId,
|
||||
accounts: List<Account>,
|
||||
totalAccounts: Int,
|
||||
totalArchivedAccounts: Int,
|
||||
sortType: TokensSortType = TokensSortType.NONE,
|
||||
groupType: TokensGroupType = TokensGroupType.NONE,
|
||||
): Either<Error, AccountList> = either {
|
||||
|
|
@ -200,6 +205,7 @@ data class AccountList private constructor(
|
|||
userWalletId = userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = totalAccounts,
|
||||
totalArchivedAccounts = totalArchivedAccounts,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
|
|
@ -225,6 +231,7 @@ data class AccountList private constructor(
|
|||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ data class AccountStatusList(
|
|||
val userWalletId: UserWalletId,
|
||||
val accountStatuses: List<AccountStatus>,
|
||||
val totalAccounts: Int,
|
||||
val totalArchivedAccounts: Int,
|
||||
val totalFiatBalance: TotalFiatBalance,
|
||||
val sortType: TokensSortType,
|
||||
val groupType: TokensGroupType,
|
||||
|
|
@ -47,6 +48,7 @@ data class AccountStatusList(
|
|||
userWalletId = userWalletId,
|
||||
accounts = accountStatuses.map(AccountStatus::account),
|
||||
totalAccounts = totalAccounts,
|
||||
totalArchivedAccounts = totalArchivedAccounts,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,10 +55,11 @@ class ApplyAccountListSortingUseCase(
|
|||
val updatedAccountList = withError(
|
||||
transform = { Error.DataOperationFailed("Unable to create AccountList: $it") },
|
||||
) {
|
||||
AccountList(
|
||||
AccountList.invoke(
|
||||
userWalletId = accountList.userWalletId,
|
||||
accounts = sortedAccounts,
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
)
|
||||
|
|
@ -69,9 +70,16 @@ class ApplyAccountListSortingUseCase(
|
|||
return@eitherOn
|
||||
}
|
||||
|
||||
accountsCRUDRepository.saveAccounts(accountList = updatedAccountList)
|
||||
applySorting(accountList = updatedAccountList)
|
||||
}
|
||||
|
||||
private suspend fun applySorting(accountList: AccountList) {
|
||||
catch(
|
||||
block = { accountsCRUDRepository.saveAccounts(accountList) },
|
||||
catch = { accountsCRUDRepository.saveAccountsLocally(accountList) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
||||
return catch(
|
||||
block = { accountsCRUDRepository.getAccountListSync(userWalletId = userWalletId) },
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = accounts.size,
|
||||
totalArchivedAccounts = 0,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
|
|
@ -96,6 +97,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = accounts.size,
|
||||
totalArchivedAccounts = 0,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
)
|
||||
|
|
@ -110,6 +112,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = model.accounts,
|
||||
totalAccounts = model.totalAccounts,
|
||||
totalArchivedAccounts = 0,
|
||||
)
|
||||
|
||||
// Assert
|
||||
|
|
@ -206,12 +209,14 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList(
|
||||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount, newAccount),
|
||||
totalAccounts = 2,
|
||||
totalArchivedAccounts = 0,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -226,12 +231,14 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList(
|
||||
userWalletId = userWalletId,
|
||||
accounts = listOf(newAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -252,6 +259,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList.Error.MainAccountNotFound.left(),
|
||||
|
|
@ -268,6 +276,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList.Error.ExceedsMaxMainAccountsCount.left(),
|
||||
|
|
@ -284,6 +293,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList.Error.DuplicateAccountNames.left(),
|
||||
|
|
@ -296,6 +306,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = createAccounts(count = 20),
|
||||
totalAccounts = 20,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toAdd = createAccount(derivationIndex = 21),
|
||||
expected = AccountList.Error.ExceedsMaxAccountsCount.left(),
|
||||
|
|
@ -335,12 +346,14 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount, secondaryAccount),
|
||||
totalAccounts = 2,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toRemove = secondaryAccount,
|
||||
expected = AccountList(
|
||||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -354,6 +367,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
)
|
||||
|
||||
MinusTestModel(
|
||||
|
|
@ -372,6 +386,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toRemove = mainAccount,
|
||||
expected = AccountList.Error.EmptyAccountsList.left(),
|
||||
|
|
@ -388,6 +403,7 @@ internal class AccountListTest {
|
|||
userWalletId = userWalletId,
|
||||
accounts = listOf(mainAccount, secondaryAccount),
|
||||
totalAccounts = 2,
|
||||
totalArchivedAccounts = 0,
|
||||
).getOrNull()!!,
|
||||
toRemove = mainAccount,
|
||||
expected = AccountList.Error.MainAccountNotFound.left(),
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ class ApplyAccountListSortingUseCaseTest {
|
|||
userWalletId = accountList.userWalletId,
|
||||
accounts = accountList.accounts.reversed(),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
).getOrNull()!!
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ dependencies {
|
|||
api(projects.domain.quotes)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.networks)
|
||||
api(projects.domain.nft)
|
||||
api(projects.domain.referral)
|
||||
api(projects.domain.staking)
|
||||
api(projects.domain.tokens)
|
||||
api(projects.domain.walletManager)
|
||||
api(projects.domain.wallets)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@
|
|||
<ID>MultilineLambdaItParameter:AccountCryptoCurrencyStatusFinder.kt$AccountCryptoCurrencyStatusFinder${ val currency = it.currency val isContractAddressMatch = contractAddress == null || currency.id.contractAddress.equals(contractAddress, ignoreCase = true) currency.network.rawId == networkId.rawId.value && currency.network.derivationPath.value == derivationPath.value && isContractAddressMatch }</ID>
|
||||
<ID>MultilineLambdaItParameter:ApplyTokenListSortingUseCaseV2.kt$ApplyTokenListSortingUseCaseV2${ errors[account.accountId] = it return@map account }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultMultiAccountStatusListProducer.kt$DefaultMultiAccountStatusListProducer${ singleAccountStatusListSupplier( params = SingleAccountStatusListProducer.Params(it.walletId), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ ExpressAsset.ID( networkId = it.network.backendId, contractAddress = (it as? CryptoCurrency.Token)?.contractAddress, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:ManageCryptoCurrenciesUseCase.kt$ManageCryptoCurrenciesUseCase${ it.network.backendId == networkId && !it.isCustom && it.contractAddress.equals(contractAddress, true) }</ID>
|
||||
<ID>UnnecessaryAbstractClass:MultiAccountStatusListSupplier.kt$MultiAccountStatusListSupplier$MultiAccountStatusListSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleAccountStatusListSupplier.kt$SingleAccountStatusListSupplier$SingleAccountStatusListSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleAccountStatusSupplier.kt$SingleAccountStatusSupplier$SingleAccountStatusSupplier</ID>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
|||
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -95,6 +96,7 @@ internal object AccountStatusUseCaseModule {
|
|||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
networksCleaner: NetworksCleaner,
|
||||
|
|
@ -107,6 +109,7 @@ internal object AccountStatusUseCaseModule {
|
|||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
derivationsRepository = derivationsRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
networksCleaner = networksCleaner,
|
||||
|
|
@ -120,18 +123,16 @@ internal object AccountStatusUseCaseModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideCryptoCurrencyBalanceFetcher(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): CryptoCurrencyBalanceFetcher {
|
||||
return CryptoCurrencyBalanceFetcher(
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
multiStakingBalanceFetcher = multiStakingBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
accountStatuses = accountStatuses.toList(),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
)
|
||||
|
|
@ -194,6 +195,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
}
|
||||
},
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ class ApplyTokenListSortingUseCaseV2(
|
|||
userWalletId = accountList.userWalletId,
|
||||
accounts = accountList.accounts.sortTokens(sortedTokensIdsByAccount, errors),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
sortType = if (isSortedByBalance) TokensSortType.BALANCE else TokensSortType.NONE,
|
||||
groupType = if (isGroupedByNetwork) TokensGroupType.NETWORK else TokensGroupType.NONE,
|
||||
).getOrElse {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import com.tangem.domain.networks.utils.NetworksCleaner
|
|||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.utils.StakingCleaner
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -49,6 +51,7 @@ class ManageCryptoCurrenciesUseCase(
|
|||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val networksCleaner: NetworksCleaner,
|
||||
|
|
@ -83,27 +86,19 @@ class ManageCryptoCurrenciesUseCase(
|
|||
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
||||
.modify(add = add, remove = remove)
|
||||
|
||||
if (!modifiedCurrencyList.hasChanges) {
|
||||
Timber.d("No changes in currencies, skipping")
|
||||
return@withContext
|
||||
}
|
||||
|
||||
saveAccount(
|
||||
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
||||
)
|
||||
|
||||
val isDerivingFailed = derivePublicKeys(
|
||||
userWalletId = userWalletId,
|
||||
currencies = modifiedCurrencyList.added,
|
||||
).isLeft()
|
||||
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
||||
|
||||
parallelUpdatingScope.launch {
|
||||
/*
|
||||
* If only removal of currencies happened, we need to sync tokens. Otherwise, tokens will be synced
|
||||
* when balances are refreshed for added currencies.
|
||||
*/
|
||||
val isOnlyRemoval = modifiedCurrencyList.added.isEmpty() && modifiedCurrencyList.removed.isNotEmpty()
|
||||
|
||||
if (isDerivingFailed || isOnlyRemoval) {
|
||||
launch { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
}
|
||||
|
||||
if (isDerivingFailed) return@launch
|
||||
syncTokens(userWalletId, modifiedCurrencyList)
|
||||
|
||||
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
||||
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
|
||||
|
|
@ -124,10 +119,10 @@ class ManageCryptoCurrenciesUseCase(
|
|||
|
||||
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
||||
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
||||
.firstOrNull {
|
||||
it.network.backendId == networkId &&
|
||||
!it.isCustom &&
|
||||
it.contractAddress.equals(contractAddress, true)
|
||||
.firstOrNull { token ->
|
||||
token.network.backendId == networkId &&
|
||||
!token.isCustom &&
|
||||
token.contractAddress.equals(contractAddress, true)
|
||||
}
|
||||
|
||||
if (foundToken != null) return@withContext foundToken
|
||||
|
|
@ -140,6 +135,8 @@ class ManageCryptoCurrenciesUseCase(
|
|||
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
||||
|
||||
parallelUpdatingScope.launch {
|
||||
syncTokens(userWalletId, modifiedCurrencyList)
|
||||
|
||||
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
|
||||
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
|
||||
}
|
||||
|
|
@ -263,15 +260,40 @@ class ManageCryptoCurrenciesUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun syncTokens(userWalletId: UserWalletId, modifiedCurrencyList: ModifiedCurrencyList) {
|
||||
createWalletManagers(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
|
||||
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { Timber.e(it, "Failed to sync tokens for wallet $userWalletId") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||
*/
|
||||
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||
|
||||
for (network in networks) {
|
||||
runSuspendCatching {
|
||||
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||
}
|
||||
.onFailure { Timber.e(it, "Failed to create wallet manager for network ${network.id}") }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) return
|
||||
|
||||
coroutineScope {
|
||||
launch {
|
||||
val assetIds = currencies.mapTo(hashSetOf()) {
|
||||
val assetIds = currencies.mapTo(hashSetOf()) { currency ->
|
||||
ExpressAsset.ID(
|
||||
networkId = it.network.backendId,
|
||||
contractAddress = (it as? CryptoCurrency.Token)?.contractAddress,
|
||||
networkId = currency.network.backendId,
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -328,5 +350,8 @@ class ManageCryptoCurrenciesUseCase(
|
|||
val added: List<CryptoCurrency>,
|
||||
val removed: List<CryptoCurrency>,
|
||||
val total: List<CryptoCurrency>,
|
||||
)
|
||||
) {
|
||||
|
||||
val hasChanges get() = added.isNotEmpty() || removed.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import arrow.core.raise.Raise
|
|||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.ArchivedAccount
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
|
|
@ -23,6 +24,8 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
*
|
||||
* @property crudRepository repository for performing CRUD operations on accounts
|
||||
* @property mainAccountTokensMigration handles the migration of tokens from the main account to the recovered account
|
||||
* @property cryptoCurrencyBalanceFetcher Fetcher for updating crypto currency balances.
|
||||
* @property singleAccountListFetcher fetches the list of accounts for a single user wallet
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -30,6 +33,7 @@ class RecoverCryptoPortfolioUseCase(
|
|||
private val crudRepository: AccountsCRUDRepository,
|
||||
private val mainAccountTokensMigration: MainAccountTokensMigration,
|
||||
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -38,6 +42,8 @@ class RecoverCryptoPortfolioUseCase(
|
|||
* @param accountId the unique identifier of the account to recover
|
||||
*/
|
||||
suspend operator fun invoke(accountId: AccountId): Either<Error, Account.CryptoPortfolio> = either {
|
||||
fetchAccountList(userWalletId = accountId.userWalletId)
|
||||
|
||||
val accountList = getAccountList(userWalletId = accountId.userWalletId)
|
||||
|
||||
ensure(accountList.canAddMoreAccounts) {
|
||||
|
|
@ -62,6 +68,12 @@ class RecoverCryptoPortfolioUseCase(
|
|||
recoveredAccount
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.fetchAccountList(userWalletId: UserWalletId) {
|
||||
singleAccountListFetcher(params = SingleAccountListFetcher.Params(userWalletId)).onLeft {
|
||||
raise(Error.DataOperationFailed(cause = it))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
||||
return catch(
|
||||
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||
import com.tangem.domain.tokens.wallet.FetchingSource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
|
@ -19,20 +17,18 @@ import timber.log.Timber
|
|||
* Utility class responsible for fetching and refreshing the balances of various crypto currencies
|
||||
* associated with a user's wallet.
|
||||
*
|
||||
* @property accountsCRUDRepository Repository for managing account data.
|
||||
* @property multiNetworkStatusFetcher Fetcher for updating network statuses.
|
||||
* @property multiQuoteStatusFetcher Fetcher for updating quote statuses.
|
||||
* @property multiYieldBalanceFetcher Fetcher for updating yield balances.
|
||||
* @property multiStakingBalanceFetcher Fetcher for updating staking balances.
|
||||
* @property stakingIdFactory Factory for creating staking IDs.
|
||||
* @property parallelUpdatingScope Coroutine scope for parallel balance updates.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CryptoCurrencyBalanceFetcher(
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val parallelUpdatingScope: CoroutineScope,
|
||||
) {
|
||||
|
|
@ -56,7 +52,10 @@ class CryptoCurrencyBalanceFetcher(
|
|||
FetchingSource.NETWORK to refreshNetworks(userWalletId = userWalletId, currencies = currencies)
|
||||
},
|
||||
async {
|
||||
FetchingSource.STAKING to refreshYieldBalances(userWalletId = userWalletId, currencies = currencies)
|
||||
FetchingSource.STAKING to refreshStakingBalances(
|
||||
userWalletId = userWalletId,
|
||||
currencies = currencies,
|
||||
)
|
||||
},
|
||||
async { FetchingSource.QUOTE to refreshQuotes(currencies = currencies) },
|
||||
)
|
||||
|
|
@ -80,25 +79,16 @@ class CryptoCurrencyBalanceFetcher(
|
|||
private suspend fun refreshNetworks(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> = either {
|
||||
val either = multiNetworkStatusFetcher(
|
||||
): Either<Throwable, Unit> {
|
||||
return multiNetworkStatusFetcher(
|
||||
params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network),
|
||||
),
|
||||
)
|
||||
|
||||
arrow.core.raise.catch(
|
||||
block = { accountsCRUDRepository.syncTokens(userWalletId) },
|
||||
catch = {
|
||||
Timber.e(it, "Failed to sync tokens for wallet: $userWalletId")
|
||||
},
|
||||
)
|
||||
|
||||
return either
|
||||
}
|
||||
|
||||
private suspend fun refreshYieldBalances(
|
||||
private suspend fun refreshStakingBalances(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> {
|
||||
|
|
@ -106,8 +96,8 @@ class CryptoCurrencyBalanceFetcher(
|
|||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull()
|
||||
}
|
||||
|
||||
return multiYieldBalanceFetcher(
|
||||
params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
return multiStakingBalanceFetcher(
|
||||
params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.domain.models.network.Network
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.network.getAddress
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
|
|
@ -16,8 +16,8 @@ import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
|||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
|
||||
import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -28,7 +28,7 @@ import javax.inject.Inject
|
|||
*
|
||||
* @property singleNetworkStatusSupplier Supplier for obtaining network status.
|
||||
* @property singleQuoteStatusSupplier Supplier for obtaining quote status.
|
||||
* @property singleYieldBalanceSupplier Supplier for obtaining yield balance.
|
||||
* @property singleStakingBalanceSupplier Supplier for obtaining staking balance.
|
||||
* @property stakingIdFactory Factory for creating staking IDs.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -36,7 +36,7 @@ import javax.inject.Inject
|
|||
internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
private val singleStakingBalanceSupplier: SingleStakingBalanceSupplier,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
) {
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
|||
currency = currency,
|
||||
maybeNetworkStatus = statusSources.networkStatus.toOption(),
|
||||
maybeQuoteStatus = statusSources.quoteStatus.toOption(),
|
||||
maybeYieldBalance = statusSources.yieldBalance.toOption(),
|
||||
maybeStakingBalance = statusSources.stakingBalance.toOption(),
|
||||
)
|
||||
}
|
||||
.onEmpty {
|
||||
|
|
@ -71,10 +71,10 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
|||
): Flow<CryptoCurrencyStatusSources> {
|
||||
val networkStatusFlow = getNetworkStatusFlow(userWalletId = userWallet.walletId, network = currency.network)
|
||||
|
||||
val yieldBalanceFlow = if (userWallet.isMultiCurrency) {
|
||||
val stakingBalanceFlow = if (userWallet.isMultiCurrency) {
|
||||
networkStatusFlow.flatMapLatest { networkStatus ->
|
||||
if (networkStatus != null) {
|
||||
getYieldBalanceFlow(
|
||||
getStakingBalanceFlow(
|
||||
userWalletId = userWallet.walletId,
|
||||
currencyId = currency.id,
|
||||
networkStatus = networkStatus,
|
||||
|
|
@ -89,26 +89,26 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
|||
|
||||
val quoteStatusFlow = currency.id.rawCurrencyId?.let(::getQuoteStatusFlow)
|
||||
|
||||
return combine(networkStatusFlow, yieldBalanceFlow, quoteStatusFlow)
|
||||
return combine(networkStatusFlow, stakingBalanceFlow, quoteStatusFlow)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun combine(
|
||||
networkStatusFlow: Flow<NetworkStatus?>,
|
||||
yieldBalanceFlow: Flow<YieldBalance?>?,
|
||||
stakingBalanceFlow: Flow<StakingBalance?>?,
|
||||
quoteStatusFlow: Flow<QuoteStatus>?,
|
||||
): Flow<CryptoCurrencyStatusSources> {
|
||||
return when {
|
||||
yieldBalanceFlow != null && quoteStatusFlow != null -> {
|
||||
stakingBalanceFlow != null && quoteStatusFlow != null -> {
|
||||
combine(
|
||||
flow = networkStatusFlow,
|
||||
flow2 = yieldBalanceFlow,
|
||||
flow2 = stakingBalanceFlow,
|
||||
flow3 = quoteStatusFlow,
|
||||
transform = ::CryptoCurrencyStatusSources,
|
||||
)
|
||||
}
|
||||
yieldBalanceFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = yieldBalanceFlow, transform = ::CryptoCurrencyStatusSources)
|
||||
stakingBalanceFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = stakingBalanceFlow, transform = ::CryptoCurrencyStatusSources)
|
||||
}
|
||||
quoteStatusFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = quoteStatusFlow) { networkStatus, quoteStatus ->
|
||||
|
|
@ -136,11 +136,11 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
|||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getYieldBalanceFlow(
|
||||
private fun getStakingBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
networkStatus: NetworkStatus,
|
||||
): Flow<YieldBalance?> {
|
||||
): Flow<StakingBalance?> {
|
||||
val stakingId = stakingIdFactory.create(
|
||||
currencyId = currencyId,
|
||||
defaultAddress = networkStatus.getAddress(),
|
||||
|
|
@ -148,8 +148,8 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
|||
.getOrNull()
|
||||
|
||||
return if (stakingId != null) {
|
||||
singleYieldBalanceSupplier(
|
||||
params = SingleYieldBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
singleStakingBalanceSupplier(
|
||||
params = SingleStakingBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
} else {
|
||||
|
|
@ -159,7 +159,7 @@ internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
|||
|
||||
private data class CryptoCurrencyStatusSources(
|
||||
val networkStatus: NetworkStatus? = null,
|
||||
val yieldBalance: YieldBalance? = null,
|
||||
val stakingBalance: StakingBalance? = null,
|
||||
val quoteStatus: QuoteStatus? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -90,6 +90,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
|
|
@ -132,6 +133,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
|
|
@ -153,6 +155,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
sortType = updatedAccountList.sortType,
|
||||
groupType = updatedAccountList.groupType,
|
||||
|
|
@ -192,6 +195,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
|
|
@ -273,6 +277,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
|
|
|
|||
|
|
@ -348,6 +348,7 @@ internal class ApplyTokenListSortingUseCaseTest {
|
|||
customAccount, // unchanged due to error
|
||||
),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalArchivedAccounts = accountList.totalArchivedAccounts,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -295,6 +295,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
|
|||
)
|
||||
},
|
||||
totalAccounts = totalAccounts,
|
||||
totalArchivedAccounts = totalArchivedAccounts,
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.left
|
|||
import arrow.core.right
|
||||
import arrow.core.toOption
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.ArchivedAccount
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
|
|
@ -27,17 +28,19 @@ import kotlin.random.Random
|
|||
class RecoverCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher = mockk()
|
||||
private val mainAccountTokensMigration: MainAccountTokensMigration = mockk()
|
||||
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher = mockk(relaxUnitFun = true)
|
||||
private val useCase = RecoverCryptoPortfolioUseCase(
|
||||
crudRepository = crudRepository,
|
||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||
singleAccountListFetcher = singleAccountListFetcher,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, mainAccountTokensMigration, cryptoCurrencyBalanceFetcher)
|
||||
clearMocks(crudRepository, mainAccountTokensMigration, cryptoCurrencyBalanceFetcher, singleAccountListFetcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -56,6 +59,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
|
||||
val updatedAccountList = (accountList + account).getOrNull()!!
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption()
|
||||
coEvery { mainAccountTokensMigration.migrate(userWalletId, account.derivationIndex) } returns Unit.right()
|
||||
|
|
@ -69,6 +73,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.getArchivedAccountSync(account.accountId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
|
|
@ -85,6 +90,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
derivationIndex = DerivationIndex.Companion.Main,
|
||||
)
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
|
||||
|
||||
// Act
|
||||
|
|
@ -95,7 +101,8 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
Truth.assertThat(actual.cause).isInstanceOf(expected::class.java)
|
||||
Truth.assertThat(actual.cause).hasMessageThat().isEqualTo(expected.message)
|
||||
|
||||
coVerifySequence { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getArchivedAccountSync(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
|
|
@ -111,6 +118,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
)
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
|
|
@ -120,7 +128,8 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
val expected = DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifySequence { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getArchivedAccountSync(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
|
|
@ -134,6 +143,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
val accountList = AccountList.Companion.empty(userWalletId)
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } throws exception
|
||||
|
||||
|
|
@ -145,6 +155,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.getArchivedAccountSync(account.accountId)
|
||||
}
|
||||
|
|
@ -157,6 +168,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
val account = createAccount(userWalletId)
|
||||
val accountList = AccountList.Companion.empty(userWalletId)
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns None
|
||||
|
||||
|
|
@ -169,6 +181,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
Truth.assertThat(actual.cause).hasMessageThat().isEqualTo(expected.message)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.getArchivedAccountSync(account.accountId)
|
||||
}
|
||||
|
|
@ -192,6 +205,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
val updatedAccountList = (accountList + account).getOrNull()!!
|
||||
val exception = IllegalStateException("Save failed")
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption()
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||
|
|
@ -204,12 +218,41 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.getArchivedAccountSync(account.accountId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if fetch is failed`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId.Companion.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex.Companion.Main,
|
||||
)
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns exception.left()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
}
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getAccountListSync(any())
|
||||
crudRepository.getArchivedAccountSync(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccount(
|
||||
userWalletId: UserWalletId,
|
||||
name: String = "Test Account",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class ToggleTokenListGroupingUseCaseV2Test {
|
|||
userWalletId = userWalletId,
|
||||
accountStatuses = emptyList(),
|
||||
totalAccounts = 0,
|
||||
totalArchivedAccounts = 0,
|
||||
totalFiatBalance = TotalFiatBalance.Failed,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
|
|
@ -205,6 +206,7 @@ class ToggleTokenListGroupingUseCaseV2Test {
|
|||
userWalletId = userWalletId,
|
||||
accountStatuses = listOf(accountStatus),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
totalFiatBalance = tokenList.totalFiatBalance,
|
||||
sortType = tokenList.sortedBy,
|
||||
groupType = groupType,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class ToggleTokenListSortingUseCaseV2Test {
|
|||
userWalletId = userWalletId,
|
||||
accountStatuses = emptyList(),
|
||||
totalAccounts = 0,
|
||||
totalArchivedAccounts = 0,
|
||||
totalFiatBalance = TotalFiatBalance.Failed,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
|
|
@ -145,6 +146,7 @@ class ToggleTokenListSortingUseCaseV2Test {
|
|||
userWalletId = userWalletId,
|
||||
accountStatuses = listOf(accountStatus),
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
totalFiatBalance = tokenList.totalFiatBalance,
|
||||
sortType = tokenList.sortedBy,
|
||||
groupType = TokensGroupType.NONE,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.models.network.NetworkAddress
|
|||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
|
|
@ -20,8 +20,8 @@ import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
|||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
|
||||
import com.tangem.test.core.getEmittedValues
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
|
@ -41,13 +41,13 @@ class CryptoCurrencyStatusesFlowFactoryTest {
|
|||
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier = mockk()
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier = mockk()
|
||||
private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier = mockk()
|
||||
private val singleStakingBalanceSupplier: SingleStakingBalanceSupplier = mockk()
|
||||
private val stakingIdFactory: StakingIdFactory = mockk()
|
||||
|
||||
private val factory = CryptoCurrencyStatusesFlowFactory(
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
singleStakingBalanceSupplier = singleStakingBalanceSupplier,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ class CryptoCurrencyStatusesFlowFactoryTest {
|
|||
clearMocks(
|
||||
singleNetworkStatusSupplier,
|
||||
singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier,
|
||||
singleStakingBalanceSupplier,
|
||||
stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -96,13 +96,13 @@ class CryptoCurrencyStatusesFlowFactoryTest {
|
|||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
} returns stakingId.right()
|
||||
|
||||
val yieldBalance = YieldBalance.Empty(stakingId = stakingId, source = StatusSource.ACTUAL)
|
||||
val yieldBalanceFlow = flowOf(yieldBalance)
|
||||
val stakingBalance = StakingBalance.Empty(stakingId = stakingId, source = StatusSource.ACTUAL)
|
||||
val stakingBalanceFlow = flowOf(stakingBalance)
|
||||
every {
|
||||
singleYieldBalanceSupplier(
|
||||
params = SingleYieldBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
singleStakingBalanceSupplier(
|
||||
params = SingleStakingBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
} returns yieldBalanceFlow
|
||||
} returns stakingBalanceFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
|
@ -121,7 +121,7 @@ class CryptoCurrencyStatusesFlowFactoryTest {
|
|||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
singleYieldBalanceSupplier(params = SingleYieldBalanceProducer.Params(userWalletId, stakingId))
|
||||
singleStakingBalanceSupplier(params = SingleStakingBalanceProducer.Params(userWalletId, stakingId))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal fun createStatus(currency: CryptoCurrency, fiatAmount: BigDecimal): Cry
|
|||
fiatRate = BigDecimal.ONE,
|
||||
fiatAmount = fiatAmount,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
yieldBalance = null,
|
||||
stakingBalance = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
yieldSupplyStatus = null,
|
||||
pendingTransactions = emptySet(),
|
||||
|
|
|
|||
9
domain/balance-hiding/detekt-baseline-main.xml
Normal file
9
domain/balance-hiding/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:ListenToFlipsUseCase.kt$ListenToFlipsUseCase${ send(HideBalancesError.DataError(it).left()) return@collectLatest }</ID>
|
||||
<ID>NoNameShadowing:ListenToFlipsUseCase.kt$ListenToFlipsUseCase${ send(HideBalancesError.DataError(it).left()) return@collectLatest }</ID>
|
||||
<ID>NoNameShadowing:ListenToFlipsUseCase.kt$ListenToFlipsUseCase${ send(HideBalancesError.DataError(it).left()) }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -1,14 +1,26 @@
|
|||
package com.tangem.domain.card.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
object ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
object ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
object ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
object ButtonScanCard : IntroductionProcess("Button - Scan Card")
|
||||
class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
class ButtonTokensList : IntroductionProcess("Button - Tokens List")
|
||||
class ButtonBuyCards : IntroductionProcess("Button - Buy Cards")
|
||||
class ButtonScanCardLegacy : IntroductionProcess("Button - Scan Card")
|
||||
|
||||
class CreateWalletIntroScreenOpened : IntroductionProcess("Create Wallet Intro Screen Opened")
|
||||
|
||||
class ButtonScanCard(
|
||||
val source: AnalyticsParam.ScreensSources,
|
||||
) : IntroductionProcess(
|
||||
event = "Button - Scan Card",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.SOURCE to source.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ sealed class Shop(
|
|||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
object ScreenOpened : Shop("Shop Screen Opened")
|
||||
class ScreenOpened : Shop("Shop Screen Opened")
|
||||
}
|
||||
11
domain/core/detekt-baseline-main.xml
Normal file
11
domain/core/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>IgnoredReturnValue:FlowCachingSupplier.kt$FlowCachingSupplier$put(key = key, value = flow)</ID>
|
||||
<ID>IgnoredReturnValue:FlowCachingSupplier.kt$FlowCachingSupplier$remove(key)</ID>
|
||||
<ID>MultilineLambdaItParameter:FlowCachingSupplier.kt$FlowCachingSupplier${ it.toMutableMap().apply { put(key = key, value = flow) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:FlowCachingSupplier.kt$FlowCachingSupplier${ it.toMutableMap().apply { remove(key) } }</ID>
|
||||
<ID>ObjectExtendsThrowable:DataError.kt$DataError.NetworkError$NoInternetConnection : NetworkError</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
package com.tangem.domain.demo.models
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal object DemoConfigCardIds {
|
||||
|
||||
val releaseDemoCardIds = mutableListOf(
|
||||
// === Not from the Google Sheet table ===
|
||||
"AC01000000041225",
|
||||
"AC01000000041472",
|
||||
"AB01000000046498",
|
||||
"AB01000000049608",
|
||||
"AB01000000049574",
|
||||
"AB01000000046704",
|
||||
"AB02000000051000",
|
||||
"AB02000000050911",
|
||||
|
||||
// === Mvideo ===
|
||||
// Wallet
|
||||
"AC01000000045754",
|
||||
"AC01000000041662",
|
||||
"AC01000000041647",
|
||||
"AC01000000041209",
|
||||
"AC01000000042462",
|
||||
"AC01000000041100",
|
||||
"AC01000000041621",
|
||||
"AC01000000045960",
|
||||
"AC01000000041092",
|
||||
"AC01000000041217",
|
||||
"AC01000000013489",
|
||||
"AC01000000028610",
|
||||
"AC01000000028701",
|
||||
"AC01000000028578",
|
||||
"AC01000000027281",
|
||||
"AC01000000027216",
|
||||
"AC01000000028594",
|
||||
"AC01000000028602",
|
||||
"AC01000000028636",
|
||||
"AC01000000013968",
|
||||
"AC01000000027208",
|
||||
"AC01000000013471",
|
||||
"AC01000000028586",
|
||||
"AC01000000013703",
|
||||
"AC01000000028628",
|
||||
"AC01000000028693",
|
||||
"AC01000000028685",
|
||||
"AC01000000013950",
|
||||
"AC01000000013828",
|
||||
"AC01000000013497",
|
||||
"AC01000000013836",
|
||||
"AC01000000013505",
|
||||
"AC03000000046693",
|
||||
"AC03000000046685",
|
||||
"AC03000000046677",
|
||||
"AC03000000046669",
|
||||
"AC03000000046651",
|
||||
"AC03000000046644",
|
||||
"AC03000000046636",
|
||||
"AC03000000046628",
|
||||
"AC03000000046610",
|
||||
"AC03000000046602",
|
||||
"AC03000000046594",
|
||||
"AC03000000046586",
|
||||
"AC03000000046578",
|
||||
"AC03000000046560",
|
||||
"AC03000000046552",
|
||||
"AC03000000046545",
|
||||
"AC03000000046537",
|
||||
"AC03000000046529",
|
||||
"AC03000000046511",
|
||||
"AC03000000046800",
|
||||
"AC03000000046792",
|
||||
"AC03000000046784",
|
||||
"AC03000000046776",
|
||||
"AC03000000046768",
|
||||
"AC03000000046750",
|
||||
"AC03000000046743",
|
||||
"AC03000000046735",
|
||||
"AC03000000046727",
|
||||
"AC03000000046446",
|
||||
"AC03000000046438",
|
||||
"AC03000000046412",
|
||||
"AC03000000046388",
|
||||
"AC03000000046370",
|
||||
"AC03000000046354",
|
||||
"AC03000000046347",
|
||||
"AC03000000046339",
|
||||
"AC03000000046321",
|
||||
"AC03000000046172",
|
||||
"AC03000000046396",
|
||||
"AC03000000046404",
|
||||
"AC03000000046701",
|
||||
"AC03000000046420",
|
||||
"AC03000000046719",
|
||||
"AC03000000046503",
|
||||
"AC03000000046495",
|
||||
"AC03000000046487",
|
||||
"AC03000000046362",
|
||||
"AC03000000046479",
|
||||
"AC03000000046461",
|
||||
"AC03000000046453",
|
||||
|
||||
// Note BTC
|
||||
"AB01000000059608",
|
||||
"AB01000000046647",
|
||||
"AB01000000046571",
|
||||
"AB01000000046746",
|
||||
"AB01000000059574",
|
||||
"AB01000000046753",
|
||||
"AB01000000046605",
|
||||
"AB01000000046761",
|
||||
"AB01000000046720",
|
||||
"AB01000000046530",
|
||||
"AB01000000016475",
|
||||
"AB01000000016483",
|
||||
"AB01000000016491",
|
||||
"AB01000000020709",
|
||||
"AB01000000020717",
|
||||
"AB01000000015550",
|
||||
"AB01000000015394",
|
||||
"AB01000000016079",
|
||||
"AB01000000016087",
|
||||
"AB01000000016095",
|
||||
"AB01000000020915",
|
||||
"AB01000000017184",
|
||||
"AB01000000020907",
|
||||
"AB01000000017192",
|
||||
"AB01000000016210",
|
||||
"AB01000000016111",
|
||||
"AB01000000016103",
|
||||
"AB01000000015766",
|
||||
"AB01000000015774",
|
||||
"AB01000000015782",
|
||||
"AB01000000022598",
|
||||
"AB01000000022580",
|
||||
"AB01000000005688",
|
||||
"AB07000000005696",
|
||||
"AB07000000005902",
|
||||
"AB07000000005910",
|
||||
"AB07000000005928",
|
||||
"AB07000000005936",
|
||||
"AB07000000005944",
|
||||
"AB07000000005993",
|
||||
"AB07000000005985",
|
||||
"AB07000000005977",
|
||||
"AB07000000005969",
|
||||
"AB07000000005951",
|
||||
"AB07000000005605",
|
||||
"AB07000000005803",
|
||||
"AB07000000005811",
|
||||
"AB07000000005829",
|
||||
"AB07000000005837",
|
||||
"AB07000000005845",
|
||||
"AB07000000005852",
|
||||
"AB07000000005860",
|
||||
"AB07000000005878",
|
||||
"AB07000000005886",
|
||||
"AB07000000005894",
|
||||
"AB07000000005704",
|
||||
"AB07000000005712",
|
||||
"AB07000000005720",
|
||||
"AB07000000005738",
|
||||
"AB07000000005746",
|
||||
"AB07000000005514",
|
||||
"AB07000000005522",
|
||||
"AB07000000005563",
|
||||
"AB07000000005571",
|
||||
"AB07000000005589",
|
||||
"AB07000000005597",
|
||||
"AB07000000005613",
|
||||
"AB07000000005621",
|
||||
"AB07000000005639",
|
||||
"AB07000000005647",
|
||||
"AB07000000005654",
|
||||
"AB07000000005662",
|
||||
"AB07000000005670",
|
||||
"AB07000000005530",
|
||||
"AB07000000005548",
|
||||
"AB07000000005555",
|
||||
"AB07000000005753",
|
||||
"AB07000000005761",
|
||||
"AB07000000005779",
|
||||
"AB07000000005787",
|
||||
"AB07000000005795",
|
||||
"AB07000000005506",
|
||||
|
||||
// Note ETH
|
||||
"AB02000000051083",
|
||||
"AB02000000051059",
|
||||
"AB02000000051158",
|
||||
"AB02000000050986",
|
||||
"AB02000000051026",
|
||||
"AB02000000050960",
|
||||
"AB02000000051042",
|
||||
"AB02000000051091",
|
||||
"AB02000000051034",
|
||||
"AB02000000051133",
|
||||
"AB02000000019924",
|
||||
"AB02000000019932",
|
||||
"AB02000000022092",
|
||||
"AB02000000022282",
|
||||
"AB02000000023983",
|
||||
"AB02000000023439",
|
||||
"AB02000000020328",
|
||||
"AB02000000020310",
|
||||
"AB02000000021565",
|
||||
"AB02000000022357",
|
||||
"AB02000000023355",
|
||||
"AB02000000022324",
|
||||
"AB02000000022100",
|
||||
"AB02000000019999",
|
||||
"AB02000000020013",
|
||||
"AB02000000020005",
|
||||
"AB02000000020021",
|
||||
"AB02000000020039",
|
||||
"AB02000000020278",
|
||||
"AB02000000020252",
|
||||
"AB02000000018652",
|
||||
"AB02000000018561",
|
||||
"AB08000000009481",
|
||||
"AB08000000009473",
|
||||
"AB08000000009705",
|
||||
"AB08000000009897",
|
||||
"AB08000000009689",
|
||||
"AB08000000009671",
|
||||
"AB08000000009465",
|
||||
"AB08000000009457",
|
||||
"AB08000000009440",
|
||||
"AB08000000009432",
|
||||
"AB08000000009424",
|
||||
"AB08000000009416",
|
||||
"AB08000000009408",
|
||||
"AB08000000009390",
|
||||
"AB08000000009374",
|
||||
"AB08000000009382",
|
||||
"AB08000000009267",
|
||||
"AB08000000009275",
|
||||
"AB08000000009283",
|
||||
"AB08000000009291",
|
||||
"AB08000000009309",
|
||||
"AB08000000009317",
|
||||
"AB08000000009325",
|
||||
"AB08000000009333",
|
||||
"AB08000000009341",
|
||||
"AB08000000009358",
|
||||
"AB08000000009366",
|
||||
"AB08000000009077",
|
||||
"AB08000000009143",
|
||||
"AB08000000009168",
|
||||
"AB08000000009184",
|
||||
"AB08000000009192",
|
||||
"AB08000000009200",
|
||||
"AB08000000009226",
|
||||
"AB08000000009218",
|
||||
"AB08000000009234",
|
||||
"AB08000000009242",
|
||||
"AB08000000008574",
|
||||
"AB08000000009069",
|
||||
"AB08000000008525",
|
||||
"AB08000000009051",
|
||||
"AB08000000009135",
|
||||
"AB08000000009150",
|
||||
"AB08000000009176",
|
||||
"AB08000000009085",
|
||||
"AB08000000009093",
|
||||
"AB08000000009101",
|
||||
"AB08000000009119",
|
||||
"AB08000000009127",
|
||||
"AB08000000009259",
|
||||
|
||||
// === Technopark ===
|
||||
// Wallet
|
||||
"AC01000000044120",
|
||||
"AC01000000044997",
|
||||
"AC01000000044989",
|
||||
"AC01000000043494",
|
||||
"AC01000000043486",
|
||||
"AC01000000044187",
|
||||
"AC01000000043148",
|
||||
"AC01000000044013",
|
||||
"AC01000000043973",
|
||||
"AC01000000044815",
|
||||
"AC01000000044807",
|
||||
"AC01000000043809",
|
||||
"AC01000000043833",
|
||||
"AC01000000043460",
|
||||
"AC01000000043064",
|
||||
"AC01000000044138",
|
||||
"AC01000000044500",
|
||||
"AC01000000044492",
|
||||
"AC01000000044260",
|
||||
"AC01000000044278",
|
||||
|
||||
// Note BTC
|
||||
"AB01000000049864",
|
||||
"AB01000000053239",
|
||||
"AB01000000053056",
|
||||
"AB01000000054237",
|
||||
"AB01000000054245",
|
||||
"AB01000000054211",
|
||||
"AB01000000054229",
|
||||
"AB01000000053189",
|
||||
"AB01000000054195",
|
||||
"AB01000000050797",
|
||||
"AB01000000053833",
|
||||
"AB01000000052124",
|
||||
"AB01000000051605",
|
||||
"AB01000000052223",
|
||||
"AB01000000052207",
|
||||
"AB01000000052199",
|
||||
"AB01000000047785",
|
||||
"AB01000000047850",
|
||||
"AB01000000047868",
|
||||
"AB01000000048288",
|
||||
|
||||
// Note ETH
|
||||
"AB02000000049715",
|
||||
"AB02000000049848",
|
||||
"AB02000000049814",
|
||||
"AB02000000049863",
|
||||
"AB02000000049871",
|
||||
"AB02000000049855",
|
||||
"AB02000000049285",
|
||||
"AB02000000049277",
|
||||
"AB02000000049558",
|
||||
"AB02000000049889",
|
||||
"AB02000000049988",
|
||||
"AB02000000049707",
|
||||
"AB02000000049699",
|
||||
"AB02000000049897",
|
||||
"AB02000000049905",
|
||||
"AB02000000049913",
|
||||
"AB02000000049251",
|
||||
"AB02000000049533",
|
||||
"AB02000000049541",
|
||||
"AB02000000049830",
|
||||
// === more cids ===
|
||||
"AC03000000091418",
|
||||
"AC03000000091400",
|
||||
"AC03000000099007",
|
||||
"AC03000000098991",
|
||||
"AC03000000098942",
|
||||
"AC03000000091715",
|
||||
"AC03000000091301",
|
||||
"AC03000000091343",
|
||||
"AB01000000055705",
|
||||
"AB01000000052918",
|
||||
"AB01000000047710",
|
||||
"AB01000000052306",
|
||||
"AB01000000047645",
|
||||
"AB01000000048957",
|
||||
"AB01000000052900",
|
||||
"AB01000000050391",
|
||||
"AB01000000047363",
|
||||
"AB02000000053998",
|
||||
"AB02000000019809",
|
||||
"AB02000000020872",
|
||||
"AB02000000022027",
|
||||
"AB02000000058955",
|
||||
"AB02000000053253",
|
||||
"AB02000000048063",
|
||||
"AB02000000023736",
|
||||
"AB02000000058187",
|
||||
"AB02000000000007",
|
||||
"AC03000000076229",
|
||||
"AF04000000000118",
|
||||
|
||||
// Wallet 2
|
||||
"AF04000000012006",
|
||||
"AF04000000012014",
|
||||
"AF04000000012022",
|
||||
"AF04000000012030",
|
||||
"AF15000000257889",
|
||||
"AF15000000257897",
|
||||
"AF15000000637809",
|
||||
"AF15000000640282",
|
||||
"AF15000001187424",
|
||||
"AF15000001187408",
|
||||
"AF15000001187416",
|
||||
"AF15000001195781",
|
||||
"AF15000001195773",
|
||||
"AF15000001195799",
|
||||
// Wallet 2 QA
|
||||
"AF12345678912346",
|
||||
"AF12345678912361",
|
||||
"AF10100000000076",
|
||||
"AF10100000000084",
|
||||
)
|
||||
|
||||
val testDemoCardIds = listOf(
|
||||
"FB20000000000186", // Note ETH
|
||||
"FB10000000000196", // Note BTC
|
||||
"FB30000000000176", // Wallet
|
||||
"FB04000000000152", // Wallet 2
|
||||
)
|
||||
|
||||
val debugTestDemoCardIds = emptyList<String>()
|
||||
}
|
||||
7
domain/express/models/detekt-baseline-main.xml
Normal file
7
domain/express/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>ObjectExtendsThrowable:ExpressError.kt$ExpressError$UnknownError : ExpressError</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
7
domain/feedback/models/detekt-baseline-main.xml
Normal file
7
domain/feedback/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:WalletMetaInfo.kt$WalletMetaInfo$val hotWalletIsBackedUp: Boolean? = null</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
class IsAccessCodeSimpleUseCase {
|
||||
operator fun invoke(accessCode: String): Boolean {
|
||||
return isSequential(accessCode) || isRepeatedCharacters(accessCode)
|
||||
}
|
||||
|
||||
fun isSequential(code: String): Boolean = code.length > 1 &&
|
||||
(code.zipWithNext().all { it.second == it.first + 1 } ||
|
||||
code.zipWithNext().all { it.second == it.first - 1 })
|
||||
|
||||
fun isRepeatedCharacters(code: String): Boolean = code.isNotEmpty() && code.all { it == code.first() }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.hotwallet
|
||||
|
||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||
|
||||
class IsHotWalletCreationSupported(private val hotWalletRepository: HotWalletRepository) {
|
||||
operator fun invoke(): Boolean = hotWalletRepository.isWalletCreationSupported()
|
||||
|
||||
fun getLeastVersionName(): String = hotWalletRepository.getLeastSupportedAndroidVersionName()
|
||||
}
|
||||
|
|
@ -5,6 +5,10 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface HotWalletRepository {
|
||||
|
||||
fun isWalletCreationSupported(): Boolean
|
||||
|
||||
fun getLeastSupportedAndroidVersionName(): String
|
||||
|
||||
fun accessCodeSkipped(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
||||
suspend fun setAccessCodeSkipped(userWalletId: UserWalletId, skipped: Boolean)
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:LogConfig.kt$AnalyticsHandlersLogConfig$val amplitude: Boolean = BuildConfig.LOG_ENABLED</ID>
|
||||
<ID>BooleanPropertyNaming:LogConfig.kt$AnalyticsHandlersLogConfig$val appsflyer: Boolean = BuildConfig.LOG_ENABLED</ID>
|
||||
<ID>BooleanPropertyNaming:LogConfig.kt$AnalyticsHandlersLogConfig$val firebase: Boolean = BuildConfig.LOG_ENABLED</ID>
|
||||
<ID>BooleanPropertyNaming:LogConfig.kt$LogConfig$val storeAction: Boolean = BuildConfig.LOG_ENABLED</ID>
|
||||
<ID>BooleanPropertyNaming:LogConfig.kt$NetworkLogConfig$val blockchainSdkNetwork: Boolean = BuildConfig.LOG_ENABLED</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -4,17 +4,17 @@ import com.tangem.domain.features.BuildConfig
|
|||
|
||||
object LogConfig {
|
||||
const val imageLoader: Boolean = false
|
||||
val storeAction: Boolean = BuildConfig.LOG_ENABLED
|
||||
val shouldStoreAction: Boolean = BuildConfig.LOG_ENABLED
|
||||
val network: NetworkLogConfig = NetworkLogConfig
|
||||
val analyticsHandlers: AnalyticsHandlersLogConfig = AnalyticsHandlersLogConfig
|
||||
}
|
||||
|
||||
object NetworkLogConfig {
|
||||
val blockchainSdkNetwork: Boolean = BuildConfig.LOG_ENABLED
|
||||
val isBlockchainSdkNetworkLogEnabled: Boolean = BuildConfig.LOG_ENABLED
|
||||
}
|
||||
|
||||
object AnalyticsHandlersLogConfig {
|
||||
val firebase: Boolean = BuildConfig.LOG_ENABLED
|
||||
val amplitude: Boolean = BuildConfig.LOG_ENABLED
|
||||
val appsflyer: Boolean = BuildConfig.LOG_ENABLED
|
||||
val isFirebaseLogEnabled: Boolean = BuildConfig.LOG_ENABLED
|
||||
val isAmplitudeLogEnabled: Boolean = BuildConfig.LOG_ENABLED
|
||||
val isAppsflyerLogEnabled: Boolean = BuildConfig.LOG_ENABLED
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
/* Core */
|
||||
api(projects.core.pagination)
|
||||
testImplementation(projects.core.pagination)
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>NamedArguments:CheckIsCurrencyNotAddedUseCase.kt$CheckIsCurrencyNotAddedUseCase$isCurrencyNotAdded(userWalletId, networkId, derivationPath, contractAddress)</ID>
|
||||
<ID>NamedArguments:CreateCryptoCurrencyUseCase.kt$CreateCryptoCurrencyUseCase$createCustomToken(userWalletId, networkId, derivationPath, formValues)</ID>
|
||||
<ID>NamedArguments:ValidateTokenFormUseCase.kt$ValidateTokenFormUseCase$zipOrAccumulate( { ensureIsContractAddressValid(formValues.contractAddress, networkId) }, { ensureIsDecimalsValid(formValues.decimals) }, { ensure(formValues.name.isNotBlank()) { CustomTokenFormValidationException.EmptyName } }, { ensure(formValues.symbol.isNotBlank()) { CustomTokenFormValidationException.EmptySymbol } }, ) { contractAddress, decimals, _, _ -> AddCustomTokenForm.Validated.All( contractAddress = contractAddress, symbol = formValues.symbol, name = formValues.name, decimals = decimals, ) }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -15,6 +15,11 @@ class CheckIsCurrencyNotAddedUseCase(
|
|||
derivationPath: Network.DerivationPath,
|
||||
contractAddress: String?,
|
||||
): Either<Throwable, Boolean> = Either.catch {
|
||||
repository.isCurrencyNotAdded(userWalletId, networkId, derivationPath, contractAddress)
|
||||
repository.isCurrencyNotAdded(
|
||||
userWalletId = userWalletId,
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
contractAddress = contractAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -57,9 +57,18 @@ class CreateCryptoCurrencyUseCase(
|
|||
formValues: AddCustomTokenForm.Validated.All?,
|
||||
): Either<Throwable, CryptoCurrency> = Either.catch {
|
||||
if (formValues == null) {
|
||||
customTokensRepository.createCoin(userWalletId, networkId, derivationPath)
|
||||
customTokensRepository.createCoin(
|
||||
userWalletId = userWalletId,
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
} else {
|
||||
customTokensRepository.createCustomToken(userWalletId, networkId, derivationPath, formValues)
|
||||
customTokensRepository.createCustomToken(
|
||||
userWalletId = userWalletId,
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
formValues = formValues,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
|
|
@ -28,7 +28,7 @@ class SaveManagedTokensUseCase(
|
|||
private val derivationsRepository: DerivationsRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val parallelUpdatingScope: CoroutineScope,
|
||||
) {
|
||||
|
|
@ -61,6 +61,8 @@ class SaveManagedTokensUseCase(
|
|||
|
||||
parallelUpdatingScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
syncTokens(userWalletId = userWalletId, addedCurrencies = savedCurrencies)
|
||||
|
||||
launch {
|
||||
refreshUpdatedNetworks(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -68,7 +70,7 @@ class SaveManagedTokensUseCase(
|
|||
)
|
||||
}
|
||||
launch {
|
||||
refreshUpdatedYieldBalances(
|
||||
refreshUpdatedStakingBalances(
|
||||
userWalletId = userWalletId,
|
||||
addedCurrencies = savedCurrencies,
|
||||
)
|
||||
|
|
@ -96,6 +98,26 @@ class SaveManagedTokensUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||
currenciesRepository.syncTokens(userWalletId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||
*/
|
||||
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||
|
||||
for (network in networks) {
|
||||
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
|
|
@ -103,11 +125,9 @@ class SaveManagedTokensUseCase(
|
|||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
),
|
||||
)
|
||||
|
||||
currenciesRepository.syncTokens(userWalletId)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(
|
||||
private suspend fun refreshUpdatedStakingBalances(
|
||||
userWalletId: UserWalletId,
|
||||
addedCurrencies: List<CryptoCurrency>,
|
||||
) {
|
||||
|
|
@ -115,8 +135,8 @@ class SaveManagedTokensUseCase(
|
|||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull()
|
||||
}
|
||||
|
||||
multiYieldBalanceFetcher(
|
||||
params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
multiStakingBalanceFetcher(
|
||||
params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class ValidateTokenFormUseCase(
|
|||
private val repository: CustomTokensRepository,
|
||||
) {
|
||||
|
||||
@Suppress("NamedArguments")
|
||||
suspend operator fun invoke(
|
||||
networkId: Network.ID,
|
||||
formValues: AddCustomTokenForm.Raw,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
api(projects.domain.networks)
|
||||
api(projects.domain.staking)
|
||||
api(projects.domain.quotes)
|
||||
api(projects.domain.walletManager)
|
||||
api(projects.domain.wallets)
|
||||
api(projects.domain.wallets.models)
|
||||
api(projects.domain.promo)
|
||||
|
|
|
|||
7
domain/markets/models/detekt-baseline-main.xml
Normal file
7
domain/markets/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:TokenMarketInfo.kt$TokenMarketInfo.Network$val exchangeable: Boolean</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.markets
|
||||
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
|
||||
class GetTopFiveMarketTokenUseCase(
|
||||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
) {
|
||||
operator fun invoke(
|
||||
batchingContext: TokenListBatchingContext,
|
||||
order: TokenMarketListConfig.Order,
|
||||
): TokenListBatchFlow {
|
||||
return marketsTokenRepository.getTokenListFlow(
|
||||
batchingContext = batchingContext,
|
||||
firstBatchSize = DEFAULT_BATCH_SIZE,
|
||||
nextBatchSize = NEXT_BATCH_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_BATCH_SIZE = 5
|
||||
private const val NEXT_BATCH_SIZE = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,9 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
|
|
@ -31,10 +32,11 @@ import kotlinx.coroutines.withContext
|
|||
class SaveMarketTokensUseCase(
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val parallelUpdatingScope: CoroutineScope,
|
||||
) {
|
||||
|
|
@ -80,14 +82,36 @@ class SaveMarketTokensUseCase(
|
|||
|
||||
parallelUpdatingScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
syncTokens(userWalletId, savedCurrencies)
|
||||
|
||||
launch { refreshUpdatedNetworks(userWalletId, savedCurrencies) }
|
||||
launch { refreshUpdatedYieldBalances(userWalletId, savedCurrencies) }
|
||||
launch { refreshUpdatedStakingBalances(userWalletId, savedCurrencies) }
|
||||
launch { refreshUpdatedQuotes(savedCurrencies) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun syncTokens(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||
createWalletManagers(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||
currenciesRepository.syncTokens(userWalletId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates wallet managers for the given [currencies] if they do not already exist.
|
||||
* The method will generate addresses for new networks to ensure the stability of the "Push notifications" feature.
|
||||
*
|
||||
* @param userWalletId The ID of the user's wallet.
|
||||
* @param currencies The list of cryptocurrencies for which to create wallet managers.
|
||||
*/
|
||||
private suspend fun createWalletManagers(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
val networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network)
|
||||
|
||||
for (network in networks) {
|
||||
walletManagersFacade.getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||
multiNetworkStatusFetcher(
|
||||
MultiNetworkStatusFetcher.Params(
|
||||
|
|
@ -95,10 +119,9 @@ class SaveMarketTokensUseCase(
|
|||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
),
|
||||
)
|
||||
currenciesRepository.syncTokens(userWalletId)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(
|
||||
private suspend fun refreshUpdatedStakingBalances(
|
||||
userWalletId: UserWalletId,
|
||||
existingCurrencies: List<CryptoCurrency>,
|
||||
) {
|
||||
|
|
@ -106,8 +129,8 @@ class SaveMarketTokensUseCase(
|
|||
stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull()
|
||||
}
|
||||
|
||||
multiYieldBalanceFetcher(
|
||||
params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
multiStakingBalanceFetcher(
|
||||
params = MultiStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
27
domain/models/detekt-baseline-main.xml
Normal file
27
domain/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:ShortArticle.kt$ShortArticle$val viewed: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:TokenReceiveConfig.kt$TokenReceiveConfig$val showMemoDisclaimer: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:UserWallet.kt$UserWallet.Hot$val backedUp: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:YieldBalanceItem.kt$PendingAction.PendingActionArgs$val signatureVerification: Boolean?</ID>
|
||||
<ID>BooleanPropertyNaming:YieldBalanceItem.kt$PendingAction.PendingActionArgs$val validatorAddress: Boolean?</ID>
|
||||
<ID>BooleanPropertyNaming:YieldBalanceItem.kt$PendingAction.PendingActionArgs$val validatorAddresses: Boolean?</ID>
|
||||
<ID>BooleanPropertyNaming:YieldBalanceItem.kt$PendingAction.PendingActionArgs.Amount$val required: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:YieldBalanceItem.kt$PendingAction.PendingActionArgs.Duration$val required: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:YieldBalanceItem.kt$PendingAction.PendingActionArgs.TronResource$val required: Boolean</ID>
|
||||
<ID>CastNullableToNonNullableType:DerivationPathAdapterWithMigration.kt$DerivationPathAdapterWithMigration$as</ID>
|
||||
<ID>MultilineLambdaItParameter:MobileWallet.kt$MobileWallet${ ExtendedPublicKey( publicKey = publicKey, chainCode = it, ) }</ID>
|
||||
<ID>NoNameShadowing:Account.kt$Account.CryptoPortfolio.Companion$derivationIndex</ID>
|
||||
<ID>NullableBooleanCheck:CryptoCurrency.kt$CryptoCurrency$iconUrl?.isNotBlank() ?: true</ID>
|
||||
<ID>NullableToStringCall:AccountName.kt$AccountName.Error.Empty$${Empty::class.simpleName}</ID>
|
||||
<ID>NullableToStringCall:AccountName.kt$AccountName.Error.ExceedsMaxLength$${ExceedsMaxLength::class.simpleName}</ID>
|
||||
<ID>NullableToStringCall:DerivationIndex.kt$DerivationIndex.Error.NegativeDerivationIndex$${this::class.simpleName}</ID>
|
||||
<ID>UnsafeCallOnNullableType:MobileWalletAsStringSerializer.kt$MobileWalletAsStringSerializer$moshi.adapter(MobileWallet::class.java).fromJson(decoder.decodeString())!!</ID>
|
||||
<ID>UnsafeCallOnNullableType:ScanResponseAsStringSerializer.kt$ScanResponseAsStringSerializer$moshi.adapter(ScanResponse::class.java).fromJson(decoder.decodeString())!!</ID>
|
||||
<ID>UseEmptyCounterpart:ScanResponse.kt$ScanResponse$mapOf()</ID>
|
||||
<ID>UseOrEmpty:CardDTO.kt$CardDTO.FirmwareVersion$type.rawValue ?: ""</ID>
|
||||
<ID>UseOrEmpty:UserWalletId.kt$UserWalletId$value?.toHexString() ?: ""</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.models.getResultStatusSource
|
|||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -56,13 +56,10 @@ data class CryptoCurrencyStatus(
|
|||
/** The network address */
|
||||
val networkAddress: NetworkAddress? get() = null
|
||||
|
||||
/** Staking yield balance */
|
||||
val yieldBalance: YieldBalance? get() = null
|
||||
/** Staking balance */
|
||||
val stakingBalance: StakingBalance? get() = null
|
||||
|
||||
/**
|
||||
* !!! DO NOT CONFUSE with STAKING YIELD BALANCE
|
||||
* Yield supply status
|
||||
*/
|
||||
/** Yield supply status */
|
||||
val yieldSupplyStatus: YieldSupplyStatus? get() = null
|
||||
|
||||
/** Sources */
|
||||
|
|
@ -73,11 +70,11 @@ data class CryptoCurrencyStatus(
|
|||
data class Sources(
|
||||
val networkSource: StatusSource = StatusSource.ACTUAL,
|
||||
val quoteSource: StatusSource = StatusSource.ACTUAL,
|
||||
val yieldBalanceSource: StatusSource = StatusSource.ACTUAL,
|
||||
val stakingBalanceSource: StatusSource = StatusSource.ACTUAL,
|
||||
) {
|
||||
|
||||
val total: StatusSource by lazy {
|
||||
listOf(networkSource, quoteSource, yieldBalanceSource).getResultStatusSource()
|
||||
listOf(networkSource, quoteSource, stakingBalanceSource).getResultStatusSource()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +160,7 @@ data class CryptoCurrencyStatus(
|
|||
override val fiatAmount: SerializedBigDecimal,
|
||||
override val fiatRate: SerializedBigDecimal,
|
||||
override val priceChange: SerializedBigDecimal,
|
||||
override val yieldBalance: YieldBalance?,
|
||||
override val stakingBalance: StakingBalance?,
|
||||
override val yieldSupplyStatus: YieldSupplyStatus?,
|
||||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxInfo>,
|
||||
|
|
@ -191,7 +188,7 @@ data class CryptoCurrencyStatus(
|
|||
override val fiatAmount: SerializedBigDecimal?,
|
||||
override val fiatRate: SerializedBigDecimal?,
|
||||
override val priceChange: SerializedBigDecimal?,
|
||||
override val yieldBalance: YieldBalance?,
|
||||
override val stakingBalance: StakingBalance?,
|
||||
override val yieldSupplyStatus: YieldSupplyStatus?,
|
||||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxInfo>,
|
||||
|
|
@ -213,7 +210,7 @@ data class CryptoCurrencyStatus(
|
|||
@Serializable
|
||||
data class NoQuote(
|
||||
override val amount: SerializedBigDecimal,
|
||||
override val yieldBalance: YieldBalance?,
|
||||
override val stakingBalance: StakingBalance?,
|
||||
override val yieldSupplyStatus: YieldSupplyStatus?,
|
||||
override val hasCurrentNetworkTransactions: Boolean,
|
||||
override val pendingTransactions: Set<TxInfo>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class NewsError {
|
||||
abstract val message: String?
|
||||
abstract val code: Int?
|
||||
|
||||
data class ArticleNotFound(
|
||||
override val message: String?,
|
||||
override val code: Int?,
|
||||
) : NewsError()
|
||||
|
||||
data class Unknown(
|
||||
override val message: String?,
|
||||
override val code: Int?,
|
||||
) : NewsError()
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.models.news
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the result of fetching news.
|
||||
* Can contain either data or an error.
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface TrendingNews {
|
||||
@Serializable
|
||||
data class Data(val articles: List<ShortArticle>) : TrendingNews
|
||||
|
||||
@Serializable
|
||||
data class Error(val throwable: NewsError) : TrendingNews
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** P2P.org staking account */
|
||||
@Serializable
|
||||
data class P2PStakingAccount(
|
||||
val delegatorAddress: String,
|
||||
val vaultAddress: String,
|
||||
val stake: P2PStake,
|
||||
val availableToUnstake: SerializedBigDecimal,
|
||||
val availableToWithdraw: SerializedBigDecimal,
|
||||
val exitQueue: P2PExitQueue,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class P2PStake(
|
||||
val assets: SerializedBigDecimal,
|
||||
val totalEarnedAssets: SerializedBigDecimal,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class P2PExitQueue(
|
||||
val total: SerializedBigDecimal,
|
||||
val requests: List<P2PExitRequest>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class P2PExitRequest(
|
||||
val ticket: String,
|
||||
val totalAssets: SerializedBigDecimal,
|
||||
val timestamp: Instant,
|
||||
val withdrawalTimestamp: Instant,
|
||||
val isClaimable: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Staking balance facade covering StakeKit and P2P balances
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface StakingBalance {
|
||||
|
||||
val stakingId: StakingID
|
||||
val source: StatusSource
|
||||
|
||||
val totalStaked: BigDecimal
|
||||
val totalRewards: BigDecimal?
|
||||
val unstakingAmount: BigDecimal?
|
||||
val withdrawableAmount: BigDecimal?
|
||||
|
||||
@Serializable
|
||||
sealed interface Data : StakingBalance {
|
||||
|
||||
@Serializable
|
||||
data class StakeKit(
|
||||
override val stakingId: StakingID,
|
||||
override val source: StatusSource,
|
||||
val balance: YieldBalanceItem,
|
||||
) : Data {
|
||||
|
||||
override val totalStaked: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.STAKED }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val totalRewards: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val unstakingAmount: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.UNSTAKING || it.type == BalanceType.UNLOCKING }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val withdrawableAmount: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.UNSTAKED }
|
||||
.sumOf { it.amount }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class P2P(
|
||||
override val stakingId: StakingID,
|
||||
override val source: StatusSource,
|
||||
val account: P2PStakingAccount,
|
||||
) : Data {
|
||||
|
||||
override val totalStaked: BigDecimal
|
||||
get() = account.stake.assets
|
||||
|
||||
override val totalRewards: BigDecimal
|
||||
get() = account.stake.totalEarnedAssets
|
||||
|
||||
override val unstakingAmount: BigDecimal
|
||||
get() = account.exitQueue.total
|
||||
|
||||
override val withdrawableAmount: BigDecimal
|
||||
get() = account.availableToWithdraw
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Empty(
|
||||
override val stakingId: StakingID,
|
||||
override val source: StatusSource,
|
||||
) : StakingBalance {
|
||||
override val totalStaked: BigDecimal get() = BigDecimal.ZERO
|
||||
override val totalRewards: BigDecimal? get() = null
|
||||
override val unstakingAmount: BigDecimal? get() = null
|
||||
override val withdrawableAmount: BigDecimal? get() = null
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Error(override val stakingId: StakingID) : StakingBalance {
|
||||
override val source: StatusSource get() = StatusSource.ACTUAL
|
||||
override val totalStaked: BigDecimal get() = BigDecimal.ZERO
|
||||
override val totalRewards: BigDecimal? get() = null
|
||||
override val unstakingAmount: BigDecimal? get() = null
|
||||
override val withdrawableAmount: BigDecimal? get() = null
|
||||
}
|
||||
|
||||
fun copySealed(source: StatusSource): StakingBalance {
|
||||
return when (this) {
|
||||
is Data.StakeKit -> copy(source = source)
|
||||
is Data.P2P -> copy(source = source)
|
||||
is Empty -> copy(source = source)
|
||||
is Error -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,4 +3,7 @@ package com.tangem.domain.models.staking
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StakingID(val integrationId: String, val address: String)
|
||||
data class StakingID(
|
||||
val integrationId: String,
|
||||
val address: String,
|
||||
)
|
||||
8
domain/networks/detekt-baseline-main.xml
Normal file
8
domain/networks/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>UnnecessaryAbstractClass:MultiNetworkStatusSupplier.kt$MultiNetworkStatusSupplier$MultiNetworkStatusSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleNetworkStatusSupplier.kt$SingleNetworkStatusSupplier$SingleNetworkStatusSupplier</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.domain.news.repository
|
||||
|
||||
import com.tangem.domain.news.model.NewsListBatchFlow
|
||||
import com.tangem.domain.news.model.NewsListBatchingContext
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.models.news.TrendingNews
|
||||
import com.tangem.domain.news.model.NewsListBatchFlow
|
||||
import com.tangem.domain.news.model.NewsListBatchingContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
|
|
@ -38,22 +38,17 @@ interface NewsRepository {
|
|||
suspend fun fetchDetailedArticles(newsIds: Collection<Int>, language: String?)
|
||||
|
||||
/**
|
||||
* Returns list of trending news by limit and with correct locale.
|
||||
* Fetch list of trending news by limit and with correct locale and store it in runtime data store.
|
||||
*
|
||||
* @param limit
|
||||
* @param language current device locale
|
||||
*/
|
||||
suspend fun getTrendingNews(limit: Int, language: String?): List<ShortArticle>
|
||||
suspend fun fetchTrendingNews(limit: Int, language: String?)
|
||||
|
||||
/**
|
||||
* Observes trending news with runtime viewed flag support.
|
||||
*/
|
||||
fun observeTrendingNews(): Flow<List<ShortArticle>>
|
||||
|
||||
/**
|
||||
* Refreshes trending news list and updates cache without overriding viewed status.
|
||||
*/
|
||||
suspend fun refreshTrendingNews(limit: Int, language: String?)
|
||||
fun observeTrendingNews(): Flow<TrendingNews>
|
||||
|
||||
/**
|
||||
* Updates viewed flag for provided trending articles.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Fetches trending news to store it in runtime data store.
|
||||
*/
|
||||
|
||||
class FetchTrendingNewsUseCase(private val newsRepository: NewsRepository) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, Unit> = Either.catch {
|
||||
newsRepository.fetchTrendingNews(
|
||||
limit = LIMIT_FOR_TRENDING_NEWS,
|
||||
language = Locale.getDefault().language,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LIMIT_FOR_TRENDING_NEWS = 10
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.models.news.TrendingNews
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
/**
|
||||
* Exposes trending news as a cached flow and provides helpers to refresh or mark items as viewed.
|
||||
|
|
@ -12,17 +13,12 @@ import kotlinx.coroutines.flow.Flow
|
|||
class ManageTrendingNewsUseCase(private val repository: NewsRepository) {
|
||||
|
||||
/**
|
||||
* Observes the current cached list of trending articles (max 10 items).
|
||||
* Observes the current cached list of trending articles (max 10 items) or error.
|
||||
*/
|
||||
operator fun invoke(): Flow<List<ShortArticle>> {
|
||||
return repository.observeTrendingNews()
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces refresh from backend while preserving local `viewed` flags.
|
||||
*/
|
||||
suspend fun refresh(limit: Int, language: String?) {
|
||||
repository.refreshTrendingNews(limit, language)
|
||||
fun observeTrendingNews(): Flow<TrendingNews> {
|
||||
return repository
|
||||
.observeTrendingNews()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ dependencies {
|
|||
|
||||
// region Project – Domain
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.networks)
|
||||
implementation(projects.domain.nft.models)
|
||||
|
|
|
|||
9
domain/nft/models/detekt-baseline-main.xml
Normal file
9
domain/nft/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MultilineLambdaItParameter:NFTCollections.kt${ it.content is NFTCollections.Content.Error || it.content is NFTCollections.Content.Collections && it.content.source == StatusSource.ONLY_CACHE }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollections.kt${ val content = it.content content is NFTCollections.Content.Collections && content.collections.isNullOrEmpty() }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollections.kt${ val content = it.content content is NFTCollections.Content.Collections && content.source != StatusSource.CACHE }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.nft.utils.NFTCleaner
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
class DisableWalletNFTUseCase(
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
private val nftCleaner: NFTCleaner,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId) {
|
||||
|
|
@ -20,7 +21,7 @@ class DisableWalletNFTUseCase(
|
|||
)
|
||||
.orEmpty()
|
||||
|
||||
val networks = currencies.map { it.network }
|
||||
nftRepository.clearCache(userWalletId, networks)
|
||||
val networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network)
|
||||
nftCleaner(userWalletId, networks)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
|
|
@ -16,7 +15,7 @@ import kotlinx.coroutines.flow.*
|
|||
class GetNFTCollectionsUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -33,13 +32,17 @@ class GetNFTCollectionsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun invokeForAccounts(userWalletId: UserWalletId): Flow<WalletNFTCollections> {
|
||||
fun AccountStatus.flowOfNFTCollections(): Flow<Pair<Account, List<NFTCollections>>> =
|
||||
nftCollections(userWalletId, this.flattenCurrencies().map { it.currency })
|
||||
.map { nfts -> this.account to nfts }
|
||||
fun Account.flowOfNFTCollections(): Flow<Pair<Account, List<NFTCollections>>> {
|
||||
val currencies = (this as? Account.CryptoPortfolio)?.cryptoCurrencies.orEmpty()
|
||||
|
||||
return singleAccountStatusListSupplier(userWalletId)
|
||||
.mapLatest { statusList -> statusList.accountStatuses.map { it.flowOfNFTCollections() } }
|
||||
return nftCollections(userWalletId = userWalletId, cryptoCurrencies = currencies.toList())
|
||||
.map { nfts -> this to nfts }
|
||||
}
|
||||
|
||||
return singleAccountListSupplier(userWalletId)
|
||||
.mapLatest { statusList -> statusList.accounts.map { it.flowOfNFTCollections() } }
|
||||
.flatMapLatest { flows -> combine(flows) { WalletNFTCollections(it.toMap()) } }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTNetworks
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
|
|
@ -14,19 +16,25 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||
|
||||
class GetNFTNetworksUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val nftRepository: NFTRepository,
|
||||
) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(portfolioId: PortfolioId): Flow<NFTNetworks> = when (portfolioId) {
|
||||
is PortfolioId.Account -> singleAccountStatusListSupplier(portfolioId.userWalletId)
|
||||
.map { it.accountStatuses }
|
||||
.mapNotNull { accountStatuses -> accountStatuses.find { it.account.accountId == portfolioId.accountId } }
|
||||
.map { accountStatus -> accountStatus.flattenCurrencies().map { it.currency } }
|
||||
.mapLatest { it.toNFTNetworks(portfolioId.userWalletId) }
|
||||
is PortfolioId.Wallet ->
|
||||
is PortfolioId.Account -> {
|
||||
singleAccountListSupplier(portfolioId.userWalletId)
|
||||
.mapNotNull { accountList ->
|
||||
val account = accountList.accounts.find { it.accountId == portfolioId.accountId }
|
||||
|
||||
(account as? Account.CryptoPortfolio)?.cryptoCurrencies?.toList()
|
||||
}
|
||||
.mapLatest { it.toNFTNetworks(portfolioId.userWalletId) }
|
||||
}
|
||||
is PortfolioId.Wallet -> {
|
||||
currenciesRepository
|
||||
.getWalletCurrenciesUpdates(portfolioId.userWalletId)
|
||||
.map { cryptoCurrencies -> cryptoCurrencies.toNFTNetworks(portfolioId.userWalletId) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<CryptoCurrency>.toNFTNetworks(userWalletId: UserWalletId): NFTNetworks {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.nft.utils.NFTCleaner
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class ObserveAndClearNFTCacheIfNeedUseCase(
|
||||
private val nftRepository: NFTRepository,
|
||||
private val nftCleaner: NFTCleaner,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
|
|
@ -26,7 +26,7 @@ class ObserveAndClearNFTCacheIfNeedUseCase(
|
|||
.distinctUntilChanged()
|
||||
.onEach { removedNetworks ->
|
||||
if (removedNetworks.isNotEmpty()) {
|
||||
nftRepository.clearCache(userWalletId, removedNetworks.toList())
|
||||
nftCleaner(userWalletId, removedNetworks)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.nft.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.COLLECTIONS
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.NFT
|
||||
|
|
@ -18,7 +19,7 @@ sealed class NFTAnalyticsEvent(
|
|||
) {
|
||||
|
||||
data class NFTListScreenOpened(
|
||||
val state: State,
|
||||
val state: AnalyticsParam.EmptyFull,
|
||||
val collectionsCount: Int,
|
||||
val allAssetsCount: Int,
|
||||
val noCollectionAssetsCount: Int,
|
||||
|
|
@ -30,15 +31,10 @@ sealed class NFTAnalyticsEvent(
|
|||
put(NFT, allAssetsCount.toString())
|
||||
put(NO_COLLECTION, noCollectionAssetsCount.toString())
|
||||
},
|
||||
) {
|
||||
enum class State(val value: String) {
|
||||
Empty("Empty"),
|
||||
Full("Full"),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
object Receive {
|
||||
data object ScreenOpened : NFTAnalyticsEvent(event = "Receive NFT Screen Opened")
|
||||
class ScreenOpened : NFTAnalyticsEvent(event = "Receive NFT Screen Opened")
|
||||
|
||||
data class BlockchainChosen(
|
||||
private val blockchain: String,
|
||||
|
|
@ -67,9 +63,9 @@ sealed class NFTAnalyticsEvent(
|
|||
},
|
||||
)
|
||||
|
||||
data object ButtonReadMore : NFTAnalyticsEvent(event = "Button - Read More")
|
||||
data object ButtonSeeAll : NFTAnalyticsEvent(event = "Button - See All")
|
||||
data object ButtonExplore : NFTAnalyticsEvent(event = "Button - Explore")
|
||||
data object ButtonSend : NFTAnalyticsEvent(event = "Button - Send")
|
||||
class ButtonReadMore : NFTAnalyticsEvent(event = "Button - Read More")
|
||||
class ButtonSeeAll : NFTAnalyticsEvent(event = "Button - See All")
|
||||
class ButtonExplore : NFTAnalyticsEvent(event = "Button - Explore")
|
||||
class ButtonSend : NFTAnalyticsEvent(event = "Button - Send")
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ package com.tangem.domain.nft.repository
|
|||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NFTRepository {
|
||||
|
|
@ -32,6 +32,4 @@ interface NFTRepository {
|
|||
suspend fun getNFTSupportedNetworks(userWalletId: UserWalletId): List<Network>
|
||||
|
||||
suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String?
|
||||
|
||||
suspend fun clearCache(userWalletId: UserWalletId, networks: List<Network>)
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.domain.nft.utils
|
||||
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Cleans up NFT data for a given user wallet and network(s).
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface NFTCleaner {
|
||||
|
||||
/**
|
||||
* Cleans up NFT data for a given user wallet and single network.
|
||||
*
|
||||
* @param userWalletId the user wallet id
|
||||
* @param network the network to clean up
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network) {
|
||||
invoke(userWalletId = userWalletId, networks = setOf(network))
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up NFT data for a given user wallet and multiple networks.
|
||||
*
|
||||
* @param userWalletId the user wallet id
|
||||
* @param networks the set of networks to clean up
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, networks: Set<Network>)
|
||||
}
|
||||
7
domain/onboarding/detekt-baseline-main.xml
Normal file
7
domain/onboarding/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>SuspendFunSwallowedCancellation:WasTwinsOnboardingShownUseCase.kt$WasTwinsOnboardingShownUseCase$runCatching</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
12
domain/onramp/detekt-baseline-main.xml
Normal file
12
domain/onramp/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MaxChainedCallsOnSameLine:GetOnrampOffersUseCase.kt$GetOnrampOffersUseCase$offer.quote.paymentMethod.type.getProcessingSpeed().speed</ID>
|
||||
<ID>MultilineLambdaItParameter:GetOnrampQuotesUseCase.kt$GetOnrampQuotesUseCase${ when (it) { is OnrampQuote.Data -> it.toAmount.value is OnrampQuote.Error -> null // negative difference to sort both when data and unavailable is present is OnrampQuote.AmountError -> { when (val error = it.error) { is OnrampError.AmountError.TooSmallError -> it.fromAmount.value - error.requiredAmount is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.fromAmount.value } } } }</ID>
|
||||
<ID>NamedArguments:GetOnrampOffersUseCase.kt$GetOnrampOffersUseCase$determineAdvantages( recentOffer, bestRateOffer, fastestOffer, isSingleOffer, )</ID>
|
||||
<ID>UnnecessaryLet:OnrampAnalyticsEvent.kt$OnrampAnalyticsEvent.Errors$let { put(PAYMENT_METHOD, paymentMethod) }</ID>
|
||||
<ID>UnnecessaryLet:OnrampAnalyticsEvent.kt$OnrampAnalyticsEvent.Errors$let { put(PROVIDER, providerName) }</ID>
|
||||
<ID>UseEmptyCounterpart:OnrampAnalyticsEvent.kt$OnrampAnalyticsEvent$mapOf()</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
10
domain/onramp/models/detekt-baseline-main.xml
Normal file
10
domain/onramp/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:OnrampCountry.kt$OnrampCountry$val onrampAvailable: Boolean</ID>
|
||||
<ID>ObjectExtendsThrowable:OnrampPairsError.kt$OnrampPairsError$PairsNotFound : OnrampPairsError</ID>
|
||||
<ID>ObjectExtendsThrowable:OnrampRedirectError.kt$OnrampRedirectError$VerificationFailed : OnrampRedirectError</ID>
|
||||
<ID>ObjectExtendsThrowable:OnrampRedirectError.kt$OnrampRedirectError$WrongRequestId : OnrampRedirectError</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -31,4 +31,6 @@ sealed class OnrampError {
|
|||
) : OnrampError()
|
||||
|
||||
data object PairsNotFound : OnrampError()
|
||||
|
||||
data object AlreadyHandledTransaction : OnrampError()
|
||||
}
|
||||
|
|
@ -96,9 +96,9 @@ class GetOnrampOffersUseCase(
|
|||
isMoonpayPromoActive: Boolean,
|
||||
): OnrampOffer? {
|
||||
val moonpayPromoOffers = if (isMoonpayPromoActive) {
|
||||
offers.filter {
|
||||
it.quote.provider.id == MOONPAY_PROMO_PROVIDER_ID &&
|
||||
it.quote.paymentMethod.type == PaymentMethodType.GOOGLE_PAY
|
||||
offers.filter { offer ->
|
||||
offer.quote.provider.id == MOONPAY_PROMO_PROVIDER_ID &&
|
||||
offer.quote.paymentMethod.type == PaymentMethodType.GOOGLE_PAY
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
|
|
@ -12,6 +13,10 @@ class GetOnrampTransactionUseCase(
|
|||
) {
|
||||
|
||||
suspend operator fun invoke(txId: String): Either<OnrampError, OnrampTransaction> {
|
||||
if (onrampTransactionRepository.isHandledTransaction(txId)) {
|
||||
return OnrampError.AlreadyHandledTransaction.left()
|
||||
}
|
||||
|
||||
return Either.catch {
|
||||
requireNotNull(
|
||||
onrampTransactionRepository.getTransactionById(txId),
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ class OnrampRemoveTransactionUseCase(
|
|||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(txId: String?): Either<OnrampError, Unit> {
|
||||
suspend operator fun invoke(txId: String?, forceRemove: Boolean = false): Either<OnrampError, Unit> {
|
||||
if (txId == null) return OnrampError.DomainError("Transaction id not provided").left()
|
||||
|
||||
return Either.catch {
|
||||
if (!forceRemove) {
|
||||
onrampTransactionRepository.storeHandledTransaction(txId)
|
||||
}
|
||||
onrampTransactionRepository.removeTransaction(txId)
|
||||
}.mapLeft(errorResolver::resolve)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object SelectCurrencyScreenOpened : OnrampAnalyticsEvent(event = "Currency Screen Opened")
|
||||
class SelectCurrencyScreenOpened : OnrampAnalyticsEvent(event = "Currency Screen Opened")
|
||||
|
||||
data class FiatCurrencyChosen(
|
||||
private val currency: String,
|
||||
|
|
@ -39,11 +39,11 @@ sealed class OnrampAnalyticsEvent(
|
|||
params = mapOf("Currency Type" to currency),
|
||||
)
|
||||
|
||||
data object CloseOnramp : OnrampAnalyticsEvent(event = "Button - Close")
|
||||
class CloseOnramp : OnrampAnalyticsEvent(event = "Button - Close")
|
||||
|
||||
data object SettingsOpened : OnrampAnalyticsEvent(event = "Onramp Settings Screen Opened")
|
||||
class SettingsOpened : OnrampAnalyticsEvent(event = "Onramp Settings Screen Opened")
|
||||
|
||||
data object SelectResidenceOpened : OnrampAnalyticsEvent(event = "Residence Screen Opened")
|
||||
class SelectResidenceOpened : OnrampAnalyticsEvent(event = "Residence Screen Opened")
|
||||
|
||||
data class OnResidenceChosen(
|
||||
private val residence: String,
|
||||
|
|
@ -59,7 +59,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
params = mapOf(RESIDENCE to residence),
|
||||
)
|
||||
|
||||
data object OnResidenceChange : OnrampAnalyticsEvent(event = "Button - Change")
|
||||
class OnResidenceChange : OnrampAnalyticsEvent(event = "Button - Change")
|
||||
|
||||
data class OnResidenceConfirm(
|
||||
private val residence: String,
|
||||
|
|
@ -68,7 +68,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
params = mapOf(RESIDENCE to residence),
|
||||
)
|
||||
|
||||
data object ProvidersScreenOpened : OnrampAnalyticsEvent(event = "Providers Screen Opened")
|
||||
class ProvidersScreenOpened : OnrampAnalyticsEvent(event = "Providers Screen Opened")
|
||||
|
||||
data class ProviderCalculated(
|
||||
private val providerName: String,
|
||||
|
|
@ -83,7 +83,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object PaymentMethodsScreenOpened : OnrampAnalyticsEvent(event = "Payment Method Screen Opened")
|
||||
class PaymentMethodsScreenOpened : OnrampAnalyticsEvent(event = "Payment Method Screen Opened")
|
||||
|
||||
data class OnPaymentMethodChosen(
|
||||
private val paymentMethod: String,
|
||||
|
|
@ -135,8 +135,8 @@ sealed class OnrampAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object MinAmountError : OnrampAnalyticsEvent(event = "Error - Min Amount")
|
||||
data object MaxAmountError : OnrampAnalyticsEvent(event = "Error - Max Amount")
|
||||
class MinAmountError : OnrampAnalyticsEvent(event = "Error - Min Amount")
|
||||
class MaxAmountError : OnrampAnalyticsEvent(event = "Error - Max Amount")
|
||||
|
||||
data class Errors(
|
||||
private val tokenSymbol: String,
|
||||
|
|
@ -203,7 +203,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object AllOffersClicked : OnrampAnalyticsEvent(
|
||||
class AllOffersClicked : OnrampAnalyticsEvent(
|
||||
event = "Button - All Offers",
|
||||
params = emptyMap(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,4 +24,8 @@ interface OnrampTransactionRepository {
|
|||
)
|
||||
|
||||
suspend fun removeTransaction(txId: String)
|
||||
|
||||
suspend fun storeHandledTransaction(txId: String)
|
||||
|
||||
suspend fun isHandledTransaction(txId: String): Boolean
|
||||
}
|
||||
7
domain/promo/detekt-baseline-main.xml
Normal file
7
domain/promo/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>NullableBooleanCheck:GetStoryContentUseCase.kt$GetStoryContentUseCase$isFCAAllowed(id).firstOrNull() ?: false</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -16,7 +16,7 @@ interface PromoRepository {
|
|||
|
||||
suspend fun setNeverToShowTokenPromo(promoId: PromoId)
|
||||
|
||||
suspend fun isMarketsStakingNotificationHideClicked(): Flow<Boolean>
|
||||
fun isMarketsStakingNotificationHideClicked(): Flow<Boolean>
|
||||
|
||||
suspend fun setMarketsStakingNotificationHideClicked()
|
||||
|
||||
|
|
|
|||
7
domain/quotes/detekt-baseline-main.xml
Normal file
7
domain/quotes/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>UnnecessaryAbstractClass:SingleQuoteStatusSupplier.kt$SingleQuoteStatusSupplier$SingleQuoteStatusSupplier</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -2,13 +2,12 @@
|
|||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>CanBeNonNullable:StakingAnalyticsEvent.kt$StakingAnalyticsEvent$value: Any?</ID>
|
||||
<ID>MultilineLambdaItParameter:FetchStakingYieldBalanceUseCase.kt$FetchStakingYieldBalanceUseCase${ when (it) { is StakingIdFactory.Error.UnableToGetAddress -> raise(StakingError.DomainError("$it")) StakingIdFactory.Error.UnsupportedCurrency -> Unit.right() } return@either }</ID>
|
||||
<ID>MultilineLambdaItParameter:InvalidatePendingTransactionsUseCase.kt$InvalidatePendingTransactionsUseCase${ !it.isPending && action.amount < it.amount && it.type == BalanceType.STAKED && it.validatorAddress == action.validatorAddress }</ID>
|
||||
<ID>NamedArguments:GetConstructedStakingTransactionUseCase.kt$GetConstructedStakingTransactionUseCase$constructTransaction(networkId, fee, amount, transactionId)</ID>
|
||||
<ID>NullableToStringCall:StakingApyFlowUseCase.kt$StakingApyFlowUseCase$${yield.token.coinGeckoId}</ID>
|
||||
<ID>UnnecessaryAbstractClass:MultiYieldBalanceSupplier.kt$MultiYieldBalanceSupplier$MultiYieldBalanceSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleYieldBalanceSupplier.kt$SingleYieldBalanceSupplier$SingleYieldBalanceSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:MultiStakingBalanceSupplier.kt$MultiStakingBalanceSupplier$MultiStakingBalanceSupplier</ID>
|
||||
<ID>UnnecessaryAbstractClass:SingleStakingBalanceSupplier.kt$SingleStakingBalanceSupplier$SingleStakingBalanceSupplier</ID>
|
||||
<ID>UseEmptyCounterpart:StakingAnalyticsEvent.kt$StakingAnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseOrEmpty:InvalidatePendingTransactionsUseCase.kt$InvalidatePendingTransactionsUseCase$action.validatorAddress ?: action.validatorAddresses?.getOrNull(0) ?: ""</ID>
|
||||
</CurrentIssues>
|
||||
|
|
|
|||
19
domain/staking/models/detekt-baseline-main.xml
Normal file
19
domain/staking/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:P2PEthPoolStaking.kt$P2PEthPoolStaking.Metadata.Fee$val enabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:P2PEthPoolStaking.kt$P2PEthPoolStaking.Status$val enter: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:P2PEthPoolStaking.kt$P2PEthPoolStaking.Status$val exit: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:StakingActionCommonType.kt$StakingActionCommonType.Enter$val skipEnterAmount: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:StakingActionCommonType.kt$StakingActionCommonType.Exit$val partiallyUnstakeDisabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:StakingActionCommonType.kt$StakingActionCommonType.Pending.Stake$val skipEnterAmount: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$AddressArgument$val required: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$Yield$val allValidatorsFull: Boolean = validators.all { it.status == Validator.ValidatorStatus.FULL }</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$Yield.Metadata$val supportsMultipleValidators: Boolean?</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$Yield.Metadata.Enabled$val enabled: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$Yield.Status$val enter: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$Yield.Status$val exit: Boolean?</ID>
|
||||
<ID>BooleanPropertyNaming:Yield.kt$Yield.Validator$val preferred: Boolean</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -39,12 +39,11 @@ sealed interface StakingOption {
|
|||
* P2P pooled staking option
|
||||
* Wraps P2P ETH Pool vault information
|
||||
*/
|
||||
data class P2P(val vault: P2PEthPoolVault) : StakingOption {
|
||||
override val integrationId: String =
|
||||
"p2p-ethereum-pooled:${vault.vaultAddress}"
|
||||
override val apy: SerializedBigDecimal = vault.apy
|
||||
data class P2P(val vaults: List<P2PEthPoolVault>) : StakingOption {
|
||||
override val integrationId: String = "p2p-ethereum-pooled"
|
||||
override val apy: SerializedBigDecimal = vaults.maxOf { it.apy }
|
||||
override val token: YieldToken = createEthToken()
|
||||
override val isAvailable: Boolean = !vault.isPrivate
|
||||
override val isAvailable: Boolean = vaults.isNotEmpty()
|
||||
|
||||
private fun createEthToken(): YieldToken { // TODO
|
||||
return YieldToken(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ enum class P2PEthPoolNetwork(
|
|||
val value: String,
|
||||
val displayName: String,
|
||||
val chainId: Int,
|
||||
val stakingNetworkId: String,
|
||||
val isTestnet: Boolean,
|
||||
) {
|
||||
/**
|
||||
* Ethereum mainnet
|
||||
|
|
@ -20,16 +22,20 @@ enum class P2PEthPoolNetwork(
|
|||
value = "mainnet",
|
||||
displayName = "Ethereum",
|
||||
chainId = 1,
|
||||
stakingNetworkId = "ethereum",
|
||||
isTestnet = false,
|
||||
),
|
||||
|
||||
/**
|
||||
* Ethereum testnet (Holesky)
|
||||
* Ethereum testnet (Hoodi)
|
||||
* Chain ID: 17000
|
||||
*/
|
||||
TESTNET(
|
||||
value = "hoodi",
|
||||
displayName = "Holesky Testnet",
|
||||
displayName = "Hoodi Testnet",
|
||||
chainId = 17000,
|
||||
stakingNetworkId = "ethereum/test",
|
||||
isTestnet = true,
|
||||
),
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.domain.staking.model.ethpool
|
||||
|
||||
/**
|
||||
* Configuration for P2P Ethereum staking network.
|
||||
*
|
||||
* Change [USE_TESTNET] to switch between testnet and mainnet.
|
||||
*/
|
||||
object P2PStakingConfig {
|
||||
|
||||
const val USE_TESTNET: Boolean = true
|
||||
|
||||
val activeNetwork: P2PEthPoolNetwork
|
||||
get() = if (USE_TESTNET) P2PEthPoolNetwork.TESTNET else P2PEthPoolNetwork.MAINNET
|
||||
}
|
||||
|
|
@ -7,10 +7,10 @@ import arrow.core.right
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
|
||||
|
||||
class FetchStakingYieldBalanceUseCase(
|
||||
private val singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
private val singleStakingBalanceFetcher: SingleStakingBalanceFetcher,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
) {
|
||||
|
||||
|
|
@ -32,8 +32,8 @@ class FetchStakingYieldBalanceUseCase(
|
|||
return@either
|
||||
}
|
||||
|
||||
singleYieldBalanceFetcher(
|
||||
params = SingleYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
singleStakingBalanceFetcher(
|
||||
params = SingleStakingBalanceFetcher.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
.mapLeft { StakingError.DomainError("$it") }
|
||||
.bind()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.staking.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent.ButtonRewards.addIfValueIsNotNull
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.models.staking.action.StakingActionType
|
||||
|
||||
|
|
@ -24,11 +23,11 @@ sealed class StakingAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object WhatIsStaking : StakingAnalyticsEvent(
|
||||
class WhatIsStaking : StakingAnalyticsEvent(
|
||||
event = "Link - What Is Staking",
|
||||
)
|
||||
|
||||
data object AmountScreenOpened : StakingAnalyticsEvent(
|
||||
class AmountScreenOpened : StakingAnalyticsEvent(
|
||||
event = "Amount Screen Opened",
|
||||
)
|
||||
|
||||
|
|
@ -54,7 +53,7 @@ sealed class StakingAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object RewardScreenOpened : StakingAnalyticsEvent(
|
||||
class RewardScreenOpened : StakingAnalyticsEvent(
|
||||
event = "Reward Screen Opened",
|
||||
)
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ sealed class StakingAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object ButtonMax : StakingAnalyticsEvent(
|
||||
class ButtonMax : StakingAnalyticsEvent(
|
||||
event = "Button - Max",
|
||||
)
|
||||
|
||||
|
|
@ -98,7 +97,7 @@ sealed class StakingAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object ButtonRewards : StakingAnalyticsEvent(
|
||||
class ButtonRewards : StakingAnalyticsEvent(
|
||||
event = "Button - Rewards",
|
||||
)
|
||||
|
||||
|
|
@ -112,9 +111,9 @@ sealed class StakingAnalyticsEvent(
|
|||
),
|
||||
)
|
||||
|
||||
data object ButtonShare : StakingAnalyticsEvent(event = "Button - Share")
|
||||
class ButtonShare : StakingAnalyticsEvent(event = "Button - Share")
|
||||
|
||||
data object ButtonExplore : StakingAnalyticsEvent(event = "Button - Explore")
|
||||
class ButtonExplore : StakingAnalyticsEvent(event = "Button - Explore")
|
||||
|
||||
data class StakeKitApiError(
|
||||
val stakingError: StakingError.StakeKitApiError,
|
||||
|
|
@ -145,12 +144,6 @@ sealed class StakingAnalyticsEvent(
|
|||
},
|
||||
)
|
||||
|
||||
fun MutableMap<String, String>.addIfValueIsNotNull(key: String, value: Any?) {
|
||||
if (value != null) {
|
||||
put(key, value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
data class TransactionError(
|
||||
val errorCode: String,
|
||||
) : StakingAnalyticsEvent(
|
||||
|
|
@ -178,4 +171,11 @@ sealed class StakingAnalyticsEvent(
|
|||
|
||||
enum class StakeScreenSource {
|
||||
Info, Amount, Confirmation, Validators,
|
||||
}
|
||||
|
||||
@Suppress("CanBeNonNullable")
|
||||
fun MutableMap<String, String>.addIfValueIsNotNull(key: String, value: Any?) {
|
||||
if (value != null) {
|
||||
put(key, value.toString())
|
||||
}
|
||||
}
|
||||
|
|
@ -5,17 +5,17 @@ import com.tangem.domain.models.network.Network
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Params for fetchers of yield balance
|
||||
* Params for fetchers of staking balance
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface YieldBalanceFetcherParams {
|
||||
sealed interface StakingBalanceFetcherParams {
|
||||
|
||||
/** User wallet ID */
|
||||
val userWalletId: UserWalletId
|
||||
|
||||
/**
|
||||
* Params for fetching multiple yield balances
|
||||
* Params for fetching multiple staking balances
|
||||
*
|
||||
* @property userWalletId user wallet ID
|
||||
* @property currencyIdWithNetworkMap map of currency ID to network
|
||||
|
|
@ -23,10 +23,10 @@ sealed interface YieldBalanceFetcherParams {
|
|||
data class Multi(
|
||||
override val userWalletId: UserWalletId,
|
||||
val currencyIdWithNetworkMap: Map<CryptoCurrency.ID, Network>,
|
||||
) : YieldBalanceFetcherParams
|
||||
) : StakingBalanceFetcherParams
|
||||
|
||||
/**
|
||||
* Params for fetching single yield balance
|
||||
* Params for fetching single staking balance
|
||||
*
|
||||
* @property userWalletId user wallet ID
|
||||
* @property currencyId currency ID
|
||||
|
|
@ -36,5 +36,5 @@ sealed interface YieldBalanceFetcherParams {
|
|||
override val userWalletId: UserWalletId,
|
||||
val currencyId: CryptoCurrency.ID,
|
||||
val network: Network,
|
||||
) : YieldBalanceFetcherParams
|
||||
) : StakingBalanceFetcherParams
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
|||
import com.tangem.blockchainsdk.utils.toMigratedCoinId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||
|
||||
/**
|
||||
* Represents a staking integration identifier.
|
||||
|
|
@ -101,8 +102,10 @@ sealed interface StakingIntegrationID {
|
|||
enum class P2P : StakingIntegrationID {
|
||||
EthereumPooled {
|
||||
override val value: String = "p2p-ethereum-pooled"
|
||||
override val blockchain: Blockchain = Blockchain.Ethereum
|
||||
override val networkId: String = "ethereum"
|
||||
override val blockchain: Blockchain
|
||||
get() = if (P2PStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||
override val networkId: String
|
||||
get() = P2PStakingConfig.activeNetwork.stakingNetworkId
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.models.staking.StakingID
|
||||
|
||||
/**
|
||||
* Fetcher of yields balances
|
||||
* Fetcher of staking balances
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MultiYieldBalanceFetcher : FlowFetcher<MultiYieldBalanceFetcher.Params> {
|
||||
interface MultiStakingBalanceFetcher : FlowFetcher<MultiStakingBalanceFetcher.Params> {
|
||||
|
||||
/**
|
||||
* Params for fetching multiple yield balances
|
||||
* Params for fetching multiple staking balances
|
||||
*
|
||||
* @property userWalletId user wallet ID
|
||||
* @property stakingIds map of currency ID to network
|
||||
|
|
@ -24,7 +24,7 @@ interface MultiYieldBalanceFetcher : FlowFetcher<MultiYieldBalanceFetcher.Params
|
|||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
MultiYieldBalanceFetcher.Params(
|
||||
MultiStakingBalanceFetcher.Params(
|
||||
userWalletId = $userWalletId,
|
||||
stakingIds: ${stakingIds.joinToString()}
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.staking.multi
|
||||
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Producer of all staking balances for selected wallet [UserWalletId]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MultiStakingBalanceProducer : FlowProducer<Set<StakingBalance>> {
|
||||
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
|
||||
interface Factory : FlowProducer.Factory<Params, MultiStakingBalanceProducer>
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.staking.multi
|
||||
|
||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
|
||||
/**
|
||||
* Supplier of all staking balances for selected wallet [MultiStakingBalanceProducer.Params]
|
||||
*
|
||||
* @property factory factory for creating [MultiStakingBalanceProducer]
|
||||
* @property keyCreator key creator
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class MultiStakingBalanceSupplier(
|
||||
override val factory: FlowProducer.Factory<MultiStakingBalanceProducer.Params, MultiStakingBalanceProducer>,
|
||||
override val keyCreator: (MultiStakingBalanceProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<MultiStakingBalanceProducer, MultiStakingBalanceProducer.Params, Set<StakingBalance>>()
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.domain.staking.multi
|
||||
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Producer of all yield balances for selected wallet [UserWalletId]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MultiYieldBalanceProducer : FlowProducer<Set<YieldBalance>> {
|
||||
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
|
||||
interface Factory : FlowProducer.Factory<Params, MultiYieldBalanceProducer>
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.domain.staking.multi
|
||||
|
||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
|
||||
/**
|
||||
* Supplier of all yield balances for selected wallet [MultiYieldBalanceProducer.Params]
|
||||
*
|
||||
* @property factory factory for creating [MultiYieldBalanceProducer]
|
||||
* @property keyCreator key creator
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class MultiYieldBalanceSupplier(
|
||||
override val factory: FlowProducer.Factory<MultiYieldBalanceProducer.Params, MultiYieldBalanceProducer>,
|
||||
override val keyCreator: (MultiYieldBalanceProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<MultiYieldBalanceProducer, MultiYieldBalanceProducer.Params, Set<YieldBalance>>()
|
||||
|
|
@ -2,7 +2,13 @@ package com.tangem.domain.staking.repositories
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.ethpool.*
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolAccount
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolBroadcastResult
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
@ -13,7 +19,7 @@ interface P2PEthPoolRepository {
|
|||
*
|
||||
* @param network P2P network (MAINNET or TESTNET)
|
||||
*/
|
||||
suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PEthPoolNetwork.MAINNET)
|
||||
suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork)
|
||||
|
||||
/**
|
||||
* Get list of available staking vaults
|
||||
|
|
@ -22,7 +28,7 @@ interface P2PEthPoolRepository {
|
|||
* @return Either error or list of vaults with APY, capacity, fees
|
||||
*/
|
||||
suspend fun getVaults(
|
||||
network: P2PEthPoolNetwork = P2PEthPoolNetwork.MAINNET,
|
||||
network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork,
|
||||
): Either<StakingError, List<P2PEthPoolVault>>
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.models.staking.StakingID
|
||||
|
||||
/**
|
||||
* Fetcher of yield balance
|
||||
* Fetcher of staking balance
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SingleYieldBalanceFetcher : FlowFetcher<SingleYieldBalanceFetcher.Params> {
|
||||
interface SingleStakingBalanceFetcher : FlowFetcher<SingleStakingBalanceFetcher.Params> {
|
||||
|
||||
/**
|
||||
* Params for fetching single yield balance
|
||||
* Params for fetching single staking balance
|
||||
*
|
||||
* @property userWalletId user wallet ID
|
||||
* @property stakingId staking ID
|
||||
|
|
@ -2,15 +2,15 @@ package com.tangem.domain.staking.single
|
|||
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
|
||||
/**
|
||||
* Producer of yield balance for selected wallet [UserWalletId]
|
||||
* Producer of staking balance for selected wallet [UserWalletId]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SingleYieldBalanceProducer : FlowProducer<YieldBalance> {
|
||||
interface SingleStakingBalanceProducer : FlowProducer<StakingBalance> {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
|
|
@ -19,7 +19,7 @@ interface SingleYieldBalanceProducer : FlowProducer<YieldBalance> {
|
|||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
SingleYieldBalanceProducer.Params(
|
||||
SingleStakingBalanceProducer.Params(
|
||||
userWalletId = $userWalletId,
|
||||
stakingId = $stakingId,
|
||||
)
|
||||
|
|
@ -27,5 +27,5 @@ interface SingleYieldBalanceProducer : FlowProducer<YieldBalance> {
|
|||
}
|
||||
}
|
||||
|
||||
interface Factory : FlowProducer.Factory<Params, SingleYieldBalanceProducer>
|
||||
interface Factory : FlowProducer.Factory<Params, SingleStakingBalanceProducer>
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.staking.single
|
||||
|
||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
|
||||
/**
|
||||
* Supplier of staking balance for selected wallet [SingleStakingBalanceProducer.Params]
|
||||
*
|
||||
* @property factory factory for creating [SingleStakingBalanceProducer]
|
||||
* @property keyCreator key creator
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class SingleStakingBalanceSupplier(
|
||||
override val factory: FlowProducer.Factory<SingleStakingBalanceProducer.Params, SingleStakingBalanceProducer>,
|
||||
override val keyCreator: (SingleStakingBalanceProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<SingleStakingBalanceProducer, SingleStakingBalanceProducer.Params, StakingBalance>()
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.domain.staking.single
|
||||
|
||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||
import com.tangem.domain.core.flow.FlowProducer
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
|
||||
/**
|
||||
* Supplier of yield balance for selected wallet [SingleYieldBalanceProducer.Params]
|
||||
*
|
||||
* @property factory factory for creating [SingleYieldBalanceProducer]
|
||||
* @property keyCreator key creator
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class SingleYieldBalanceSupplier(
|
||||
override val factory: FlowProducer.Factory<SingleYieldBalanceProducer.Params, SingleYieldBalanceProducer>,
|
||||
override val keyCreator: (SingleYieldBalanceProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<SingleYieldBalanceProducer, SingleYieldBalanceProducer.Params, YieldBalance>()
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.domain.staking.utils
|
||||
|
||||
import com.tangem.domain.models.staking.BalanceType
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Provider-agnostic extension to get total balance including rewards.
|
||||
* Works for both StakeKit and P2P providers.
|
||||
*
|
||||
* Returns sum of all staking-related balances including rewards
|
||||
* (staked + unstaking + withdrawable + rewards).
|
||||
*
|
||||
* When [BlockchainUtils.isIncludeStakingTotalBalance] is false, the staked balance
|
||||
* is already included in the main wallet balance, so we only return rewards.
|
||||
*/
|
||||
fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): BigDecimal {
|
||||
return when (this) {
|
||||
is StakingBalance.Data.StakeKit -> getTotalWithRewardsStakingBalanceStakeKit(blockchainId)
|
||||
is StakingBalance.Data.P2P -> {
|
||||
val rewards = totalRewards
|
||||
if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId)) {
|
||||
totalStaked + unstakingAmount + withdrawableAmount + rewards
|
||||
} else {
|
||||
rewards
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider-agnostic extension to get total staking balance excluding rewards.
|
||||
* Works for both StakeKit and P2P providers.
|
||||
*
|
||||
* Returns sum of all staking-related balances (staked + unstaking + withdrawable)
|
||||
* excluding rewards.
|
||||
*/
|
||||
fun StakingBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal {
|
||||
return when (this) {
|
||||
is StakingBalance.Data.StakeKit -> getTotalStakingBalanceStakeKit(blockchainId)
|
||||
is StakingBalance.Data.P2P -> totalStaked + unstakingAmount + withdrawableAmount
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StakeKit-specific extension to get total balance including rewards.
|
||||
*/
|
||||
private fun StakingBalance.Data.StakeKit.getTotalWithRewardsStakingBalanceStakeKit(blockchainId: String): BigDecimal {
|
||||
return if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId = blockchainId)) {
|
||||
balance.items.sumOf { it.amount }
|
||||
} else {
|
||||
getRewardStakingBalance()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StakeKit-specific extension to get total staked balance excluding rewards.
|
||||
*/
|
||||
private fun StakingBalance.Data.StakeKit.getTotalStakingBalanceStakeKit(blockchainId: String): BigDecimal {
|
||||
return if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId = blockchainId)) {
|
||||
balance.items
|
||||
.filterNot { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
} else {
|
||||
balance.items
|
||||
.filterNot { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount } - getRewardStakingBalance()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StakeKit-specific extension to get reward balance.
|
||||
*/
|
||||
fun StakingBalance.Data.StakeKit.getRewardStakingBalance(): BigDecimal {
|
||||
return balance.items
|
||||
.filter { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
}
|
||||
|
||||
/**
|
||||
* StakeKit-specific extension to get validators count.
|
||||
*/
|
||||
fun StakingBalance.Data.StakeKit.getValidatorsCount(): Int {
|
||||
return balance.items
|
||||
.filterNot { it.validatorAddress.isNullOrBlank() }
|
||||
.distinctBy { it.validatorAddress }
|
||||
.size
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.domain.staking.utils
|
||||
|
||||
import com.tangem.domain.models.staking.BalanceType
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun YieldBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): BigDecimal {
|
||||
return if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId = blockchainId)) {
|
||||
balance.items.sumOf { it.amount }
|
||||
} else {
|
||||
getRewardStakingBalance()
|
||||
}
|
||||
}
|
||||
|
||||
fun YieldBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal {
|
||||
return if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId = blockchainId)) {
|
||||
balance.items
|
||||
.filterNot { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
} else {
|
||||
balance.items
|
||||
.filterNot { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount } - getRewardStakingBalance()
|
||||
}
|
||||
}
|
||||
|
||||
fun YieldBalance.Data.getRewardStakingBalance(): BigDecimal {
|
||||
return balance.items
|
||||
.filter { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
}
|
||||
|
||||
fun YieldBalance.Data.getValidatorsCount(): Int {
|
||||
return balance.items
|
||||
.filterNot { it.validatorAddress.isNullOrBlank() }
|
||||
.distinctBy { it.validatorAddress }
|
||||
.size
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ internal class StakingIdFactoryTest {
|
|||
expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano),
|
||||
),
|
||||
CreateModel(
|
||||
currencyId = createCurrencyId(blockchain = Blockchain.Ethereum),
|
||||
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
|
||||
expected = createStakingId(integrationId = StakingIntegrationID.P2P.EthereumPooled),
|
||||
),
|
||||
CreateModel(
|
||||
|
|
|
|||
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