Updated on 2026-08-14
This commit is contained in:
parent
017eece8b7
commit
d96dd19f01
23 changed files with 456 additions and 8 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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