Updated on 2026-08-14
This commit is contained in:
commit
59228972a9
1705 changed files with 82417 additions and 14905 deletions
|
|
@ -5,66 +5,76 @@ plugins {
|
|||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
alias(deps.plugins.ksp)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.staking"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.walletManager)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/** Feature Api modules */
|
||||
implementation(projects.features.staking.api)
|
||||
|
||||
// region DI
|
||||
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
// region Kotlin
|
||||
api(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.datetime)
|
||||
// endregion
|
||||
|
||||
// region Others dependencies
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.datetime)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
// region Other libraries
|
||||
api(deps.androidx.datastore)
|
||||
api(deps.moshi)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
// endregion
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
implementation(tangemDeps.card.core)
|
||||
// region Tangem SDK
|
||||
implementation(tangemDeps.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
implementation(tangemDeps.card.core)
|
||||
// endregion
|
||||
|
||||
testImplementation(tangemDeps.card.core)
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
// region Core modules
|
||||
api(projects.core.analytics)
|
||||
api(projects.core.configToggles)
|
||||
api(projects.core.datasource)
|
||||
api(projects.core.utils)
|
||||
implementation(projects.core.analytics.models)
|
||||
// endregion
|
||||
|
||||
// region Data
|
||||
implementation(projects.data.common)
|
||||
// endregion
|
||||
|
||||
// region Domain
|
||||
api(projects.domain.common)
|
||||
api(projects.domain.core)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.staking)
|
||||
api(projects.domain.walletManager)
|
||||
api(projects.domain.wallets)
|
||||
implementation(projects.domain.card)
|
||||
// endregion
|
||||
|
||||
// region Domain models
|
||||
api(projects.domain.staking.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
// endregion
|
||||
|
||||
// region Libs
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
// endregion
|
||||
|
||||
// region Tests
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(projects.test.core)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -32,4 +32,7 @@ interface BaseStakingBalancesStore {
|
|||
|
||||
/** Clear staking balances */
|
||||
suspend fun clear(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
|
||||
|
||||
/** Remove all staking balances for the given [userWalletIds] */
|
||||
suspend fun remove(userWalletIds: List<UserWalletId>)
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import com.tangem.data.staking.converters.ethpool.P2PEthPoolStakingBalanceConver
|
|||
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
|
|
@ -108,6 +110,25 @@ internal class DefaultP2PEthPoolBalancesStore(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(userWalletIds: List<UserWalletId>) {
|
||||
if (userWalletIds.isEmpty()) return
|
||||
|
||||
val walletIds = userWalletIds.toSet()
|
||||
val stringKeys = userWalletIds.mapTo(hashSetOf()) { it.stringValue }
|
||||
|
||||
// Best-effort: attempt both runtime and persistence removal even if one fails.
|
||||
coroutineScope {
|
||||
launch {
|
||||
runSuspendCatching { runtimeStore.update(default = emptyMap()) { stored -> stored - walletIds } }
|
||||
.onFailure { TangemLogger.e("Failed to remove P2PEthPool staking balances from runtime", it) }
|
||||
}
|
||||
launch {
|
||||
runSuspendCatching { persistenceStore.updateData { stored -> stored - stringKeys } }
|
||||
.onFailure { TangemLogger.e("Failed to remove P2PEthPool staking balances from persistence", it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<P2PEthPoolAccountResponse>) {
|
||||
val newBalances = P2PEthPoolStakingBalanceConverter.convertAll(
|
||||
responses = values,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrap
|
|||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.token.converter.StakingBalanceConverter
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.staking.StakingBalance
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
|
|
@ -99,6 +101,25 @@ internal class DefaultStakeKitBalancesStore(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(userWalletIds: List<UserWalletId>) {
|
||||
if (userWalletIds.isEmpty()) return
|
||||
|
||||
val walletIds = userWalletIds.toSet()
|
||||
val stringKeys = userWalletIds.mapTo(hashSetOf()) { it.stringValue }
|
||||
|
||||
// Best-effort: attempt both runtime and persistence removal even if one fails.
|
||||
coroutineScope {
|
||||
launch {
|
||||
runSuspendCatching { runtimeStore.update(default = emptyMap()) { stored -> stored - walletIds } }
|
||||
.onFailure { TangemLogger.e("Failed to remove StakeKit staking balances from runtime", it) }
|
||||
}
|
||||
launch {
|
||||
runSuspendCatching { persistenceStore.updateData { stored -> stored - stringKeys } }
|
||||
.onFailure { TangemLogger.e("Failed to remove StakeKit staking balances from persistence", it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearInRuntime(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
runtimeStore.update(default = emptyMap()) { stored ->
|
||||
stored.toMutableMap().apply {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ internal class DefaultStakingFeatureToggles(
|
|||
}
|
||||
|
||||
private fun StakingIntegrationID.getFeatureToggle(): FeatureToggles? = when (this) {
|
||||
is StakingIntegrationID.P2PEthPool -> FeatureToggles.STAKING_ETH_ENABLED
|
||||
is StakingIntegrationID.P2PEthPool -> null
|
||||
is StakingIntegrationID.StakeKit -> this.getStakeKitFeatureToggle()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,23 @@ internal class DefaultStakingCleaner(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingCleaner {
|
||||
|
||||
override suspend fun clear(userWalletIds: List<UserWalletId>) {
|
||||
if (userWalletIds.isEmpty()) return
|
||||
|
||||
// Best-effort: isolate failures per store so one failing store does not cancel the other.
|
||||
withContext(dispatchers.default) {
|
||||
awaitAll(
|
||||
async { removeSafely(store = "StakeKit") { stakeKitBalancesStore.remove(userWalletIds) } },
|
||||
async { removeSafely(store = "P2PEthPool") { p2pEthPoolBalancesStore.remove(userWalletIds) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun removeSafely(store: String, remove: suspend () -> Unit) {
|
||||
runSuspendCatching { remove() }
|
||||
.onFailure { TangemLogger.e("Failed to remove $store staking balances", it) }
|
||||
}
|
||||
|
||||
override suspend fun invoke(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) {
|
||||
TangemLogger.d("No currencies to clear for wallet: $userWalletId")
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.data.staking.toggles
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -24,21 +24,10 @@ internal class DefaultStakingFeatureTogglesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `P2PEthPool returns true when STAKING_ETH_ENABLED is enabled`() {
|
||||
every { featureTogglesManager.isFeatureEnabled(FeatureToggles.STAKING_ETH_ENABLED) } returns true
|
||||
|
||||
fun `P2PEthPool integration is always enabled`() {
|
||||
assertThat(toggles.isIntegrationEnabled(StakingIntegrationID.P2PEthPool)).isTrue()
|
||||
|
||||
verify(exactly = 1) { featureTogglesManager.isFeatureEnabled(FeatureToggles.STAKING_ETH_ENABLED) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `P2PEthPool returns false when STAKING_ETH_ENABLED is disabled`() {
|
||||
every { featureTogglesManager.isFeatureEnabled(FeatureToggles.STAKING_ETH_ENABLED) } returns false
|
||||
|
||||
assertThat(toggles.isIntegrationEnabled(StakingIntegrationID.P2PEthPool)).isFalse()
|
||||
|
||||
verify(exactly = 1) { featureTogglesManager.isFeatureEnabled(FeatureToggles.STAKING_ETH_ENABLED) }
|
||||
verify(exactly = 0) { featureTogglesManager.isFeatureEnabled(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -167,4 +167,36 @@ class DefaultStakingCleanerTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ClearByWalletIds {
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallets WHEN clear THEN both balances stores removed once with all ids`() = runTest {
|
||||
// Arrange
|
||||
val ids = listOf(UserWalletId("011"), UserWalletId("022"))
|
||||
|
||||
// Act
|
||||
cleaner.clear(userWalletIds = ids)
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) {
|
||||
stakeKitBalancesStore.remove(ids)
|
||||
p2pEthPoolBalancesStore.remove(ids)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty list WHEN clear THEN stores are not touched`() = runTest {
|
||||
// Act
|
||||
cleaner.clear(userWalletIds = emptyList())
|
||||
|
||||
// Assert
|
||||
coVerify(inverse = true) {
|
||||
stakeKitBalancesStore.remove(any())
|
||||
p2pEthPoolBalancesStore.remove(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue