Updated on 2026-08-14
This commit is contained in:
parent
9e0b8b3234
commit
b449775ad8
112 changed files with 979 additions and 740 deletions
|
|
@ -100,12 +100,12 @@ internal object StakingDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideFetchStakingOptionsUseCase(
|
fun provideFetchStakingOptionsUseCase(
|
||||||
stakeKitRepository: StakeKitRepository,
|
stakeKitRepository: StakeKitRepository,
|
||||||
p2pRepository: P2PEthPoolRepository,
|
p2pEthPoolRepository: P2PEthPoolRepository,
|
||||||
stakingErrorResolver: StakingErrorResolver,
|
stakingErrorResolver: StakingErrorResolver,
|
||||||
): FetchStakingOptionsUseCase {
|
): FetchStakingOptionsUseCase {
|
||||||
return FetchStakingOptionsUseCase(
|
return FetchStakingOptionsUseCase(
|
||||||
stakeKitRepository = stakeKitRepository,
|
stakeKitRepository = stakeKitRepository,
|
||||||
p2pRepository = p2pRepository,
|
p2pEthPoolRepository = p2pEthPoolRepository,
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
stakingErrorResolver = stakingErrorResolver,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -240,8 +240,9 @@ internal object StakingDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideStakingApyFlowUseCase(
|
fun provideStakingApyFlowUseCase(
|
||||||
stakeKitRepository: StakeKitRepository,
|
stakeKitRepository: StakeKitRepository,
|
||||||
|
p2pEthPoolRepository: P2PEthPoolRepository,
|
||||||
stakingFeatureToggles: StakingFeatureToggles,
|
stakingFeatureToggles: StakingFeatureToggles,
|
||||||
): StakingApyFlowUseCase {
|
): StakingApyFlowUseCase {
|
||||||
return StakingApyFlowUseCase(stakeKitRepository, stakingFeatureToggles)
|
return StakingApyFlowUseCase(stakeKitRepository, p2pEthPoolRepository, stakingFeatureToggles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.tap.network.auth
|
package com.tangem.tap.network.auth
|
||||||
|
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
|
||||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
|
|
||||||
internal class DefaultP2PEthPoolAuthProvider(
|
internal class DefaultP2PEthPoolAuthProvider(
|
||||||
|
|
@ -12,6 +12,6 @@ internal class DefaultP2PEthPoolAuthProvider(
|
||||||
val keys = environmentConfigStorage.getConfigSync().p2pApiKey
|
val keys = environmentConfigStorage.getConfigSync().p2pApiKey
|
||||||
?: error("No P2P api keys provided")
|
?: error("No P2P api keys provided")
|
||||||
|
|
||||||
return if (P2PStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet
|
return if (P2PEthPoolStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -311,7 +311,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
params = StakingComponent.Params(
|
params = StakingComponent.Params(
|
||||||
userWalletId = route.userWalletId,
|
userWalletId = route.userWalletId,
|
||||||
cryptoCurrency = route.cryptoCurrency,
|
cryptoCurrency = route.cryptoCurrency,
|
||||||
yieldId = route.yieldId,
|
integrationId = route.integrationId,
|
||||||
),
|
),
|
||||||
componentFactory = stakingComponentFactory,
|
componentFactory = stakingComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ dependencies {
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.staking.models)
|
implementation(projects.domain.staking)
|
||||||
implementation(projects.domain.markets.models)
|
implementation(projects.domain.markets.models)
|
||||||
implementation(projects.domain.onramp.models)
|
implementation(projects.domain.onramp.models)
|
||||||
implementation(projects.domain.appCurrency.models)
|
implementation(projects.domain.appCurrency.models)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.os.Bundle
|
||||||
import com.tangem.common.routing.bundle.RouteBundleParams
|
import com.tangem.common.routing.bundle.RouteBundleParams
|
||||||
import com.tangem.common.routing.bundle.bundle
|
import com.tangem.common.routing.bundle.bundle
|
||||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||||
import com.tangem.core.decompose.navigation.Route
|
import com.tangem.core.decompose.navigation.Route
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||||
|
|
@ -208,8 +209,8 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data class Staking(
|
data class Staking(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val cryptoCurrency: CryptoCurrency,
|
val cryptoCurrency: CryptoCurrency,
|
||||||
val yieldId: String,
|
val integrationId: StakingIntegrationID,
|
||||||
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId")
|
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/${integrationId.value}")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PushNotification(
|
data class PushNotification(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for creating mock P2P ETH Pool account responses for testing
|
* Factory for creating mock P2PEthPool account responses for testing
|
||||||
*/
|
*/
|
||||||
object MockP2PEthPoolAccountResponseFactory {
|
object MockP2PEthPoolAccountResponseFactory {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.domain.staking.model.isStakingSupported
|
import com.tangem.domain.staking.model.isStakingSupported
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.common.RewardInfo
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||||
import com.tangem.lib.crypto.BlockchainUtils
|
import com.tangem.lib.crypto.BlockchainUtils
|
||||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
|
|
@ -44,7 +46,7 @@ import java.math.BigDecimal
|
||||||
class TokenItemStateConverter(
|
class TokenItemStateConverter(
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
private val yieldModuleApyMap: Map<String, BigDecimal> = emptyMap(),
|
private val yieldModuleApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||||
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
private val stakingApyMap: Map<String, List<StakingTarget>> = emptyMap(),
|
||||||
private val yieldSupplyPromoBannerKey: String? = null,
|
private val yieldSupplyPromoBannerKey: String? = null,
|
||||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||||
CryptoCurrencyToIconStateConverter().convert(it)
|
CryptoCurrencyToIconStateConverter().convert(it)
|
||||||
|
|
@ -177,7 +179,7 @@ class TokenItemStateConverter(
|
||||||
private fun createTitleState(
|
private fun createTitleState(
|
||||||
currencyStatus: CryptoCurrencyStatus,
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
stakingApyMap: Map<String, List<StakingTarget>>,
|
||||||
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
||||||
): TokenItemState.TitleState {
|
): TokenItemState.TitleState {
|
||||||
return when (val value = currencyStatus.value) {
|
return when (val value = currencyStatus.value) {
|
||||||
|
|
@ -217,7 +219,7 @@ class TokenItemStateConverter(
|
||||||
private fun resolveEarnApy(
|
private fun resolveEarnApy(
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
stakingApyMap: Map<String, List<StakingTarget>>,
|
||||||
): EarnApyInfo? {
|
): EarnApyInfo? {
|
||||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
|
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
|
||||||
if (token != null && yieldModuleApyMap.isNotEmpty()) {
|
if (token != null && yieldModuleApyMap.isNotEmpty()) {
|
||||||
|
|
@ -247,9 +249,9 @@ class TokenItemStateConverter(
|
||||||
stakingApyMap = stakingApyMap,
|
stakingApyMap = stakingApyMap,
|
||||||
)
|
)
|
||||||
val rewardTypeRes = when (stakingInfo.rewardType) {
|
val rewardTypeRes = when (stakingInfo.rewardType) {
|
||||||
Yield.RewardType.APR -> R.string.staking_apr_earn_badge
|
RewardType.APR -> R.string.staking_apr_earn_badge
|
||||||
Yield.RewardType.UNKNOWN,
|
RewardType.UNKNOWN,
|
||||||
Yield.RewardType.APY,
|
RewardType.APY,
|
||||||
null,
|
null,
|
||||||
-> R.string.yield_module_earn_badge
|
-> R.string.yield_module_earn_badge
|
||||||
}
|
}
|
||||||
|
|
@ -272,49 +274,35 @@ class TokenItemStateConverter(
|
||||||
|
|
||||||
private fun findStakingRate(
|
private fun findStakingRate(
|
||||||
currencyStatus: CryptoCurrencyStatus,
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
stakingApyMap: Map<String, List<StakingTarget>>,
|
||||||
): StakingLocalInfo {
|
): StakingLocalInfo {
|
||||||
val stakingKey = currencyStatus.currency.stakingKey()
|
val stakingKey = currencyStatus.currency.stakingKey()
|
||||||
val validators = stakingApyMap[stakingKey]
|
val targets = stakingApyMap[stakingKey]
|
||||||
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
|
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
|
||||||
|
|
||||||
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
|
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
|
||||||
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
|
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
|
||||||
|
|
||||||
val rateInfo: Pair<BigDecimal, Yield.RewardType?>? = if (stakeKitBalance != null) {
|
val rewardInfo: RewardInfo? = if (stakeKitBalance != null) {
|
||||||
// StakeKit-specific: try to find rate from validator address
|
// StakeKit-specific: try to find rate from validator address
|
||||||
val validatorsByAddress = validators.associateBy { it.address }
|
val targetsByAddress = targets.associateBy { it.address }
|
||||||
stakeKitBalance.balance.items
|
stakeKitBalance.balance.items
|
||||||
.mapNotNull { it.validatorAddress }
|
.mapNotNull { it.validatorAddress }
|
||||||
.firstNotNullOfOrNull { address ->
|
.firstNotNullOfOrNull { address -> targetsByAddress[address]?.rewardInfo }
|
||||||
val validator = validatorsByAddress[address]
|
?: targets
|
||||||
validator?.rewardInfo?.rate?.let { rate ->
|
.filter { it.isPreferred }
|
||||||
rate to validator.rewardInfo?.type
|
.mapNotNull { it.rewardInfo }
|
||||||
}
|
.maxByOrNull { it.rate }
|
||||||
}
|
|
||||||
?: validators
|
|
||||||
.filter { it.preferred }
|
|
||||||
.mapNotNull { validator ->
|
|
||||||
validator.rewardInfo?.rate?.let { rate -> rate to validator.rewardInfo?.type }
|
|
||||||
}
|
|
||||||
.maxByOrNull { it.first }
|
|
||||||
} else {
|
} else {
|
||||||
// P2P or no balance: use preferred validators
|
targets
|
||||||
// TODO p2p
|
.mapNotNull { it.rewardInfo }
|
||||||
validators
|
.maxByOrNull { it.rate }
|
||||||
.filter { it.preferred }
|
|
||||||
.mapNotNull { validator ->
|
|
||||||
validator.rewardInfo?.rate?.let { rate ->
|
|
||||||
rate to validator.rewardInfo?.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.maxByOrNull { it.first }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return StakingLocalInfo(
|
return StakingLocalInfo(
|
||||||
rate = rateInfo?.first,
|
rate = rewardInfo?.rate,
|
||||||
isActive = stakingBalance != null,
|
isActive = stakingBalance != null,
|
||||||
rewardType = rateInfo?.second,
|
rewardType = rewardInfo?.type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -470,7 +458,7 @@ class TokenItemStateConverter(
|
||||||
private data class StakingLocalInfo(
|
private data class StakingLocalInfo(
|
||||||
val rate: BigDecimal?,
|
val rate: BigDecimal?,
|
||||||
val isActive: Boolean,
|
val isActive: Boolean,
|
||||||
val rewardType: Yield.RewardType?,
|
val rewardType: RewardType?,
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class EarnApyInfo(
|
private data class EarnApyInfo(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.datasource.api.common.config
|
package com.tangem.datasource.api.common.config
|
||||||
|
|
||||||
import com.tangem.datasource.BuildConfig
|
import com.tangem.datasource.BuildConfig
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
|
||||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
import com.tangem.utils.ProviderSuspend
|
import com.tangem.utils.ProviderSuspend
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ internal class P2PEthPool(
|
||||||
private fun getInitialEnvironment(): ApiEnvironment {
|
private fun getInitialEnvironment(): ApiEnvironment {
|
||||||
return when (BuildConfig.BUILD_TYPE) {
|
return when (BuildConfig.BUILD_TYPE) {
|
||||||
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
||||||
else -> if (P2PStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
|
else -> if (P2PEthPoolStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ data class P2PEthPoolBroadcastResponse(
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transaction status from P2P API
|
* Transaction status from P2PEthPool API
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = false)
|
@JsonClass(generateAdapter = false)
|
||||||
enum class P2PEthPoolTxStatusDTO {
|
enum class P2PEthPoolTxStatusDTO {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error response structure for P2P.org API
|
* Error response structure for P2P.org eth pooled API
|
||||||
*
|
*
|
||||||
* All P2P API endpoints return errors in this format
|
* All P2PEthPool API endpoints return errors in this format
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class P2PEthPoolErrorResponse(
|
data class P2PEthPoolErrorResponse(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.squareup.moshi.JsonClass
|
||||||
/**
|
/**
|
||||||
* Unified response wrapper for all P2P.org API responses
|
* Unified response wrapper for all P2P.org API responses
|
||||||
*
|
*
|
||||||
* All P2P API endpoints return responses in this format:
|
* All P2PEthPool API endpoints return responses in this format:
|
||||||
* ```json
|
* ```json
|
||||||
* {
|
* {
|
||||||
* "error": null | { code, message, name, errors },
|
* "error": null | { code, message, name, errors },
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ data class P2PEthPoolVaultsResponse(
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network identifier in P2P API
|
* Network identifier in P2PEthPool API
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = false)
|
@JsonClass(generateAdapter = false)
|
||||||
enum class P2PEthPoolNetworkDTO {
|
enum class P2PEthPoolNetworkDTO {
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ internal object StakingStoreModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideP2PBalancesPersistenceStore(
|
fun provideP2PEthPoolBalancesPersistenceStore(
|
||||||
@NetworkMoshi moshi: Moshi,
|
@NetworkMoshi moshi: Moshi,
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
|
@ -91,7 +91,7 @@ internal object StakingStoreModule {
|
||||||
types = mapWithStringKeyTypes(valueTypes = setTypes<P2PEthPoolAccountResponse>()),
|
types = mapWithStringKeyTypes(valueTypes = setTypes<P2PEthPoolAccountResponse>()),
|
||||||
defaultValue = emptyMap(),
|
defaultValue = emptyMap(),
|
||||||
),
|
),
|
||||||
produceFile = { context.dataStoreFile(fileName = "p2p_balances") },
|
produceFile = { context.dataStoreFile(fileName = "p2p_eth_pool_balances") },
|
||||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
* (similar to StakingYieldsStore for StakeKit yields)
|
* (similar to StakingYieldsStore for StakeKit yields)
|
||||||
*
|
*
|
||||||
* Vault is ETH-specific concept for pooled staking.
|
* Vault is ETH-specific concept for pooled staking.
|
||||||
* For other blockchains, P2P may use different structures.
|
* For other blockchains, P2PEthPool may use different structures.
|
||||||
*/
|
*/
|
||||||
interface P2PEthPoolVaultsStore {
|
interface P2PEthPoolVaultsStore {
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ interface P2PEthPoolVaultsStore {
|
||||||
suspend fun getSync(): List<P2PEthPoolVault>
|
suspend fun getSync(): List<P2PEthPoolVault>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store vaults from P2P API
|
* Store vaults from P2PEthPool API
|
||||||
*/
|
*/
|
||||||
suspend fun store(vaults: List<P2PEthPoolVault>)
|
suspend fun store(vaults: List<P2PEthPoolVault>)
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_
|
||||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
|
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
|
||||||
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
|
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
|
||||||
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY
|
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
|
||||||
import com.tangem.lib.auth.ExpressAuthProvider
|
import com.tangem.lib.auth.ExpressAuthProvider
|
||||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||||
|
|
@ -300,7 +300,7 @@ internal class ProdApiConfigsManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createP2PModel(): TestModel {
|
private fun createP2PModel(): TestModel {
|
||||||
val (environment, baseUrl) = if (P2PStakingConfig.USE_TESTNET) {
|
val (environment, baseUrl) = if (P2PEthPoolStakingConfig.USE_TESTNET) {
|
||||||
ApiEnvironment.DEV to "https://api-test.p2p.org/"
|
ApiEnvironment.DEV to "https://api-test.p2p.org/"
|
||||||
} else {
|
} else {
|
||||||
ApiEnvironment.PROD to "https://api.p2p.org/"
|
ApiEnvironment.PROD to "https://api.p2p.org/"
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P staking repository implementation
|
* P2PEthPool staking repository implementation
|
||||||
*/
|
*/
|
||||||
internal class DefaultP2PEthPoolRepository(
|
internal class DefaultP2PEthPoolRepository(
|
||||||
private val p2pApi: P2PEthPoolApi,
|
private val p2pEthPoolApi: P2PEthPoolApi,
|
||||||
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : P2PEthPoolRepository {
|
) : P2PEthPoolRepository {
|
||||||
|
|
@ -41,7 +41,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
|
|
||||||
override suspend fun fetchVaults(network: P2PEthPoolNetwork) {
|
override suspend fun fetchVaults(network: P2PEthPoolNetwork) {
|
||||||
val vaults = getVaults(network).getOrElse { error ->
|
val vaults = getVaults(network).getOrElse { error ->
|
||||||
Timber.e("Error fetching P2P vaults: $error")
|
Timber.e("Error fetching P2PEthPool vaults: $error")
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
p2pEthPoolVaultsStore.store(vaults)
|
p2pEthPoolVaultsStore.store(vaults)
|
||||||
|
|
@ -49,7 +49,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
|
|
||||||
override suspend fun getVaults(network: P2PEthPoolNetwork): Either<StakingError, List<P2PEthPoolVault>> = either {
|
override suspend fun getVaults(network: P2PEthPoolNetwork): Either<StakingError, List<P2PEthPoolVault>> = either {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
val response = p2pApi.getVaults(network.value)
|
val response = p2pEthPoolApi.getVaults(network.value)
|
||||||
when (response) {
|
when (response) {
|
||||||
is ApiResponse.Success -> {
|
is ApiResponse.Success -> {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
|
|
@ -76,7 +76,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
vaultAddress = vaultAddress,
|
vaultAddress = vaultAddress,
|
||||||
amount = amount.toDoubleOrNull() ?: raise(StakingError.InvalidAmount("Invalid amount format: $amount")),
|
amount = amount.toDoubleOrNull() ?: raise(StakingError.InvalidAmount("Invalid amount format: $amount")),
|
||||||
)
|
)
|
||||||
val response = p2pApi.createDepositTransaction(network.value, requestBody)
|
val response = p2pEthPoolApi.createDepositTransaction(network.value, requestBody)
|
||||||
when (response) {
|
when (response) {
|
||||||
is ApiResponse.Success -> {
|
is ApiResponse.Success -> {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
|
|
@ -101,7 +101,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
stakerPublicKey = stakerPublicKey,
|
stakerPublicKey = stakerPublicKey,
|
||||||
stakeTransactionHash = stakeTransactionHash,
|
stakeTransactionHash = stakeTransactionHash,
|
||||||
)
|
)
|
||||||
val response = p2pApi.createUnstakeTransaction(network.value, requestBody)
|
val response = p2pEthPoolApi.createUnstakeTransaction(network.value, requestBody)
|
||||||
when (response) {
|
when (response) {
|
||||||
is ApiResponse.Success -> {
|
is ApiResponse.Success -> {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
|
|
@ -133,7 +133,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
): Either<StakingError, P2PEthPoolUnsignedTx> = either {
|
): Either<StakingError, P2PEthPoolUnsignedTx> = either {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
val requestBody = P2PEthPoolWithdrawRequest(stakerAddress = stakerAddress)
|
val requestBody = P2PEthPoolWithdrawRequest(stakerAddress = stakerAddress)
|
||||||
val response = p2pApi.createWithdrawTransaction(network.value, requestBody)
|
val response = p2pEthPoolApi.createWithdrawTransaction(network.value, requestBody)
|
||||||
when (response) {
|
when (response) {
|
||||||
is ApiResponse.Success -> {
|
is ApiResponse.Success -> {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
|
|
@ -154,7 +154,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
): Either<StakingError, P2PEthPoolBroadcastResult> = either {
|
): Either<StakingError, P2PEthPoolBroadcastResult> = either {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
val requestBody = P2PEthPoolBroadcastRequest(signedTransaction = signedTransaction)
|
val requestBody = P2PEthPoolBroadcastRequest(signedTransaction = signedTransaction)
|
||||||
val response = p2pApi.broadcastTransaction(network.value, requestBody)
|
val response = p2pEthPoolApi.broadcastTransaction(network.value, requestBody)
|
||||||
when (response) {
|
when (response) {
|
||||||
is ApiResponse.Success -> {
|
is ApiResponse.Success -> {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
|
|
@ -175,7 +175,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
vaultAddress: String,
|
vaultAddress: String,
|
||||||
): Either<StakingError, P2PEthPoolAccount> = either {
|
): Either<StakingError, P2PEthPoolAccount> = either {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
val response = p2pApi.getAccountInfo(network.value, delegatorAddress, vaultAddress)
|
val response = p2pEthPoolApi.getAccountInfo(network.value, delegatorAddress, vaultAddress)
|
||||||
when (response) {
|
when (response) {
|
||||||
is ApiResponse.Success -> {
|
is ApiResponse.Success -> {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
|
|
@ -197,7 +197,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
period: Int?,
|
period: Int?,
|
||||||
): Either<StakingError, List<P2PEthPoolReward>> = either {
|
): Either<StakingError, List<P2PEthPoolReward>> = either {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
val response = p2pApi.getRewards(
|
val response = p2pEthPoolApi.getRewards(
|
||||||
network = network.value,
|
network = network.value,
|
||||||
delegatorAddress = delegatorAddress,
|
delegatorAddress = delegatorAddress,
|
||||||
vaultAddress = vaultAddress,
|
vaultAddress = vaultAddress,
|
||||||
|
|
@ -217,6 +217,10 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getVaultsFlow(): Flow<List<P2PEthPoolVault>> {
|
||||||
|
return p2pEthPoolVaultsStore.get()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getStakingAvailability(): Flow<StakingAvailability> {
|
override fun getStakingAvailability(): Flow<StakingAvailability> {
|
||||||
return getVaultsFlow()
|
return getVaultsFlow()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
@ -224,7 +228,7 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
if (vaults.isEmpty()) {
|
if (vaults.isEmpty()) {
|
||||||
return@map StakingAvailability.TemporaryUnavailable
|
return@map StakingAvailability.TemporaryUnavailable
|
||||||
} else {
|
} else {
|
||||||
StakingAvailability.Available(StakingOption.P2P(vaults))
|
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -234,15 +238,11 @@ internal class DefaultP2PEthPoolRepository(
|
||||||
return if (vaults.isEmpty()) {
|
return if (vaults.isEmpty()) {
|
||||||
StakingAvailability.TemporaryUnavailable
|
StakingAvailability.TemporaryUnavailable
|
||||||
} else {
|
} else {
|
||||||
StakingAvailability.Available(StakingOption.P2P(vaults))
|
StakingAvailability.Available(StakingOption.P2PEthPool(vaults))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getVaultsSync(): List<P2PEthPoolVault> {
|
private suspend fun getVaultsSync(): List<P2PEthPoolVault> {
|
||||||
return p2pEthPoolVaultsStore.getSync()
|
return p2pEthPoolVaultsStore.getSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVaultsFlow(): Flow<List<P2PEthPoolVault>> {
|
|
||||||
return p2pEthPoolVaultsStore.get()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -364,6 +364,7 @@ internal class DefaultStakeKitRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStakingAvailability(
|
override fun getStakingAvailability(
|
||||||
|
integrationId: StakingIntegrationID.StakeKit,
|
||||||
rawCurrencyId: CryptoCurrency.RawID,
|
rawCurrencyId: CryptoCurrency.RawID,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
): Flow<StakingAvailability> {
|
): Flow<StakingAvailability> {
|
||||||
|
|
@ -381,7 +382,7 @@ internal class DefaultStakeKitRepository(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (prefetchedYield != null) {
|
if (prefetchedYield != null) {
|
||||||
StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield))
|
StakingAvailability.Available(StakingOption.StakeKit(integrationId, prefetchedYield))
|
||||||
} else {
|
} else {
|
||||||
StakingAvailability.TemporaryUnavailable
|
StakingAvailability.TemporaryUnavailable
|
||||||
}
|
}
|
||||||
|
|
@ -389,6 +390,7 @@ internal class DefaultStakeKitRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getStakingAvailabilitySync(
|
override suspend fun getStakingAvailabilitySync(
|
||||||
|
integrationId: StakingIntegrationID.StakeKit,
|
||||||
rawCurrencyId: CryptoCurrency.RawID,
|
rawCurrencyId: CryptoCurrency.RawID,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
): StakingAvailability {
|
): StakingAvailability {
|
||||||
|
|
@ -404,7 +406,7 @@ internal class DefaultStakeKitRepository(
|
||||||
)
|
)
|
||||||
|
|
||||||
return if (prefetchedYield != null) {
|
return if (prefetchedYield != null) {
|
||||||
StakingAvailability.Available(StakingOption.StakeKit(prefetchedYield))
|
StakingAvailability.Available(StakingOption.StakeKit(integrationId, prefetchedYield))
|
||||||
} else {
|
} else {
|
||||||
StakingAvailability.TemporaryUnavailable
|
StakingAvailability.TemporaryUnavailable
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@ internal class DefaultStakingErrorResolver(
|
||||||
is StakingError.DomainError -> {
|
is StakingError.DomainError -> {
|
||||||
analyticsEventHandler.send(StakingAnalyticsEvent.DomainError(error))
|
analyticsEventHandler.send(StakingAnalyticsEvent.DomainError(error))
|
||||||
}
|
}
|
||||||
// P2P errors
|
// P2PEthPool errors
|
||||||
is StakingError.InvalidAmount,
|
is StakingError.InvalidAmount,
|
||||||
is StakingError.DataError,
|
is StakingError.DataError,
|
||||||
is StakingError.UnknownError,
|
is StakingError.UnknownError,
|
||||||
-> {
|
-> {
|
||||||
// P2P errors - no specific analytics event yet
|
// P2PEthPool errors - no specific analytics event yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ internal class DefaultStakingRepository(
|
||||||
val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id)
|
val stakingIntegration = StakingIntegrationID.create(currencyId = cryptoCurrency.id)
|
||||||
|
|
||||||
val availabilityFlow = when (stakingIntegration) {
|
val availabilityFlow = when (stakingIntegration) {
|
||||||
is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailability()
|
StakingIntegrationID.P2PEthPool -> p2pEthPoolRepository.getStakingAvailability()
|
||||||
is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailability(
|
is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailability(
|
||||||
|
stakingIntegration,
|
||||||
rawCurrencyId,
|
rawCurrencyId,
|
||||||
cryptoCurrency.symbol,
|
cryptoCurrency.symbol,
|
||||||
)
|
)
|
||||||
|
|
@ -94,8 +95,9 @@ internal class DefaultStakingRepository(
|
||||||
?: return StakingAvailability.Unavailable
|
?: return StakingAvailability.Unavailable
|
||||||
|
|
||||||
return when (stakingIntegration) {
|
return when (stakingIntegration) {
|
||||||
is StakingIntegrationID.P2P -> p2pEthPoolRepository.getStakingAvailabilitySync()
|
StakingIntegrationID.P2PEthPool -> p2pEthPoolRepository.getStakingAvailabilitySync()
|
||||||
is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailabilitySync(
|
is StakingIntegrationID.StakeKit -> stakeKitRepository.getStakingAvailabilitySync(
|
||||||
|
stakingIntegration,
|
||||||
rawCurrencyId,
|
rawCurrencyId,
|
||||||
cryptoCurrency.symbol,
|
cryptoCurrency.symbol,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO
|
||||||
import com.tangem.datasource.local.token.converter.YieldTokenConverter
|
import com.tangem.datasource.local.token.converter.YieldTokenConverter
|
||||||
|
import com.tangem.domain.staking.model.common.RewardInfo
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
import com.tangem.domain.staking.model.stakekit.AddressArgument
|
import com.tangem.domain.staking.model.stakekit.AddressArgument
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield.Metadata.RewardSchedule
|
import com.tangem.domain.staking.model.stakekit.Yield.Metadata.RewardSchedule
|
||||||
|
|
@ -124,7 +126,7 @@ internal object YieldConverter : Converter<YieldDTO, Yield> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO, rewardType: Yield.RewardType): Yield.Validator {
|
private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO, rewardType: RewardType): Yield.Validator {
|
||||||
val address = validatorDTO.address.asMandatory("address")
|
val address = validatorDTO.address.asMandatory("address")
|
||||||
|
|
||||||
return Yield.Validator(
|
return Yield.Validator(
|
||||||
|
|
@ -142,7 +144,7 @@ internal object YieldConverter : Converter<YieldDTO, Yield> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRewardInfo(validatorDTO: YieldDTO.ValidatorDTO, rewardType: Yield.RewardType): Yield.RewardInfo? {
|
private fun createRewardInfo(validatorDTO: YieldDTO.ValidatorDTO, rewardType: RewardType): RewardInfo? {
|
||||||
val aprOrApy = validatorDTO.apr
|
val aprOrApy = validatorDTO.apr
|
||||||
val commission = validatorDTO.commission
|
val commission = validatorDTO.commission
|
||||||
// gross = net / (1 - commission)
|
// gross = net / (1 - commission)
|
||||||
|
|
@ -162,17 +164,17 @@ internal object YieldConverter : Converter<YieldDTO, Yield> {
|
||||||
} else {
|
} else {
|
||||||
netApy
|
netApy
|
||||||
}
|
}
|
||||||
grossAprOrApy?.let { Yield.RewardInfo(rate = it, type = rewardType) }
|
grossAprOrApy?.let { RewardInfo(rate = it, type = rewardType) }
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
aprOrApy?.let { Yield.RewardInfo(rate = it, type = rewardType) }
|
aprOrApy?.let { RewardInfo(rate = it, type = rewardType) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): Yield.RewardType {
|
private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): RewardType {
|
||||||
return when (rewardTypeDTO) {
|
return when (rewardTypeDTO) {
|
||||||
YieldDTO.RewardTypeDTO.APY -> Yield.RewardType.APY
|
YieldDTO.RewardTypeDTO.APY -> RewardType.APY
|
||||||
YieldDTO.RewardTypeDTO.APR -> Yield.RewardType.APR
|
YieldDTO.RewardTypeDTO.APR -> RewardType.APR
|
||||||
else -> Yield.RewardType.UNKNOWN
|
else -> RewardType.UNKNOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import com.tangem.utils.converter.Converter
|
||||||
import org.joda.time.Instant
|
import org.joda.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter from P2P Account Info Response to Domain model
|
* Converter from P2PEthPool Account Info Response to Domain model
|
||||||
*/
|
*/
|
||||||
internal object P2PEthPoolAccountConverter : Converter<P2PEthPoolAccountResponse, P2PEthPoolAccount> {
|
internal object P2PEthPoolAccountConverter : Converter<P2PEthPoolAccountResponse, P2PEthPoolAccount> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import com.tangem.utils.converter.Converter
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter from P2P Broadcast Transaction Response to Domain model
|
* Converter from P2PEthPool Broadcast Transaction Response to Domain model
|
||||||
*/
|
*/
|
||||||
internal object P2PEthPoolBroadcastResultConverter : Converter<P2PEthPoolBroadcastResponse, P2PEthPoolBroadcastResult> {
|
internal object P2PEthPoolBroadcastResultConverter : Converter<P2PEthPoolBroadcastResponse, P2PEthPoolBroadcastResult> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.tangem.domain.staking.model.stakekit.StakingError
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter from P2P Error Response to Domain StakingError
|
* Converter from P2PEthPool Error Response to Domain StakingError
|
||||||
*/
|
*/
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
internal object P2PEthPoolErrorConverter : Converter<P2PEthPoolErrorResponse, StakingError> {
|
internal object P2PEthPoolErrorConverter : Converter<P2PEthPoolErrorResponse, StakingError> {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter from P2P Reward Entry DTO to Domain model
|
* Converter from P2PEthPool Reward Entry DTO to Domain model
|
||||||
*/
|
*/
|
||||||
internal object P2PEthPoolRewardConverter : Converter<P2PEthPoolRewardDTO, P2PEthPoolReward> {
|
internal object P2PEthPoolRewardConverter : Converter<P2PEthPoolRewardDTO, P2PEthPoolReward> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,16 @@ import com.tangem.domain.models.staking.*
|
||||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
/** Converts P2P ETH Pool API response to [StakingBalance.Data.P2P] */
|
/** Converts P2PEthPool API response to [StakingBalance.Data.P2PEthPool] */
|
||||||
internal object P2PStakingBalanceConverter {
|
internal object P2PEthPoolStakingBalanceConverter {
|
||||||
|
|
||||||
fun convert(response: P2PEthPoolAccountResponse, source: StatusSource): StakingBalance.Data.P2P {
|
fun convert(response: P2PEthPoolAccountResponse, source: StatusSource): StakingBalance.Data.P2PEthPool {
|
||||||
val stakingId = StakingID(
|
val stakingId = StakingID(
|
||||||
integrationId = StakingIntegrationID.P2P.EthereumPooled.value,
|
integrationId = StakingIntegrationID.P2PEthPool.value,
|
||||||
address = response.delegatorAddress,
|
address = response.delegatorAddress,
|
||||||
)
|
)
|
||||||
|
|
||||||
val account = P2PStakingAccount(
|
val account = P2PEthPoolStakingAccount(
|
||||||
delegatorAddress = response.delegatorAddress,
|
delegatorAddress = response.delegatorAddress,
|
||||||
vaultAddress = response.vaultAddress,
|
vaultAddress = response.vaultAddress,
|
||||||
stake = convertStake(response.stake),
|
stake = convertStake(response.stake),
|
||||||
|
|
@ -27,29 +27,29 @@ internal object P2PStakingBalanceConverter {
|
||||||
exitQueue = convertExitQueue(response.exitQueue),
|
exitQueue = convertExitQueue(response.exitQueue),
|
||||||
)
|
)
|
||||||
|
|
||||||
return StakingBalance.Data.P2P(
|
return StakingBalance.Data.P2PEthPool(
|
||||||
stakingId = stakingId,
|
stakingId = stakingId,
|
||||||
source = source,
|
source = source,
|
||||||
account = account,
|
account = account,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertStake(dto: P2PEthPoolStakeDTO): P2PStake {
|
private fun convertStake(dto: P2PEthPoolStakeDTO): P2PEthPoolStake {
|
||||||
return P2PStake(
|
return P2PEthPoolStake(
|
||||||
assets = dto.assets,
|
assets = dto.assets,
|
||||||
totalEarnedAssets = dto.totalEarnedAssets,
|
totalEarnedAssets = dto.totalEarnedAssets,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertExitQueue(dto: P2PEthPoolExitQueueDTO): P2PExitQueue {
|
private fun convertExitQueue(dto: P2PEthPoolExitQueueDTO): P2PEthPoolExitQueue {
|
||||||
return P2PExitQueue(
|
return P2PEthPoolExitQueue(
|
||||||
total = dto.total.toBigDecimal(),
|
total = dto.total.toBigDecimal(),
|
||||||
requests = dto.requests.map(::convertExitRequest),
|
requests = dto.requests.map(::convertExitRequest),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertExitRequest(dto: P2PEthPoolExitRequestDTO): P2PExitRequest {
|
private fun convertExitRequest(dto: P2PEthPoolExitRequestDTO): P2PEthPoolExitRequest {
|
||||||
return P2PExitRequest(
|
return P2PEthPoolExitRequest(
|
||||||
ticket = dto.ticket,
|
ticket = dto.ticket,
|
||||||
totalAssets = dto.totalAssets.toBigDecimal(),
|
totalAssets = dto.totalAssets.toBigDecimal(),
|
||||||
timestamp = Instant.fromEpochSeconds(dto.timestamp),
|
timestamp = Instant.fromEpochSeconds(dto.timestamp),
|
||||||
|
|
@ -6,7 +6,7 @@ import com.tangem.utils.converter.Converter
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter from P2P Unsigned Transaction DTO to Domain model
|
* Converter from P2PEthPool Unsigned Transaction DTO to Domain model
|
||||||
*/
|
*/
|
||||||
internal object P2PEthPoolUnsignedTxConverter : Converter<P2PEthPoolUnsignedTxDTO, P2PEthPoolUnsignedTx> {
|
internal object P2PEthPoolUnsignedTxConverter : Converter<P2PEthPoolUnsignedTxDTO, P2PEthPoolUnsignedTx> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter from P2P Vault DTO to Domain model
|
* Converter from P2PEthPool Vault DTO to Domain model
|
||||||
*/
|
*/
|
||||||
internal object P2PEthPoolVaultConverter : Converter<P2PEthPoolVaultDTO, P2PEthPoolVault> {
|
internal object P2PEthPoolVaultConverter : Converter<P2PEthPoolVaultDTO, P2PEthPoolVault> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
package com.tangem.data.staking.converters.ethpool
|
|
||||||
|
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.models.staking.*
|
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolAccount
|
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tmp solution before facade implementation
|
|
||||||
*/
|
|
||||||
internal object P2PYieldBalanceConverter {
|
|
||||||
|
|
||||||
private const val ETH_DECIMALS = 18
|
|
||||||
private const val ETH_SYMBOL = "ETH"
|
|
||||||
private const val ETH_NAME = "Ethereum"
|
|
||||||
private const val ETH_COINGECKO_ID = "ethereum"
|
|
||||||
|
|
||||||
fun convert(
|
|
||||||
account: P2PEthPoolAccount,
|
|
||||||
vault: P2PEthPoolVault,
|
|
||||||
address: String,
|
|
||||||
source: StatusSource,
|
|
||||||
): YieldBalance {
|
|
||||||
val integrationId = "p2p-ethereum-pooled"
|
|
||||||
val stakingId = StakingID(
|
|
||||||
integrationId = integrationId,
|
|
||||||
address = address,
|
|
||||||
)
|
|
||||||
|
|
||||||
val balanceItems = buildBalanceItems(account, vault)
|
|
||||||
|
|
||||||
return if (balanceItems.isEmpty()) {
|
|
||||||
YieldBalance.Empty(stakingId = stakingId, source = source)
|
|
||||||
} else {
|
|
||||||
YieldBalance.Data(
|
|
||||||
stakingId = stakingId,
|
|
||||||
source = source,
|
|
||||||
balance = YieldBalanceItem(
|
|
||||||
items = balanceItems,
|
|
||||||
integrationId = integrationId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildBalanceItems(account: P2PEthPoolAccount, vault: P2PEthPoolVault): List<BalanceItem> = buildList {
|
|
||||||
if (account.stake.assets > BigDecimal.ZERO) {
|
|
||||||
add(
|
|
||||||
createBalanceItem(
|
|
||||||
groupId = "p2p-staked",
|
|
||||||
amount = account.stake.assets,
|
|
||||||
type = BalanceType.STAKED,
|
|
||||||
validatorAddress = vault.vaultAddress,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createBalanceItem(
|
|
||||||
groupId: String,
|
|
||||||
amount: BigDecimal,
|
|
||||||
type: BalanceType,
|
|
||||||
validatorAddress: String,
|
|
||||||
): BalanceItem {
|
|
||||||
return BalanceItem(
|
|
||||||
groupId = groupId,
|
|
||||||
token = createEthToken(),
|
|
||||||
type = type,
|
|
||||||
amount = amount,
|
|
||||||
rawCurrencyId = ETH_COINGECKO_ID,
|
|
||||||
validatorAddress = validatorAddress,
|
|
||||||
date = null,
|
|
||||||
pendingActions = emptyList(),
|
|
||||||
pendingActionsConstraints = emptyList(),
|
|
||||||
isPending = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEthToken(): YieldToken {
|
|
||||||
return YieldToken(
|
|
||||||
name = ETH_NAME,
|
|
||||||
network = NetworkType.ETHEREUM,
|
|
||||||
symbol = ETH_SYMBOL,
|
|
||||||
decimals = ETH_DECIMALS,
|
|
||||||
address = null,
|
|
||||||
coinGeckoId = ETH_COINGECKO_ID,
|
|
||||||
logoURI = null,
|
|
||||||
isPoints = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package com.tangem.data.staking.di
|
package com.tangem.data.staking.di
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import com.tangem.data.staking.store.DefaultP2PBalancesStore
|
import com.tangem.data.staking.store.DefaultP2PEthPoolBalancesStore
|
||||||
import com.tangem.data.staking.store.DefaultStakingBalancesStore
|
import com.tangem.data.staking.store.DefaultStakingBalancesStore
|
||||||
import com.tangem.data.staking.store.P2PBalancesStore
|
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
|
||||||
import com.tangem.data.staking.store.StakingBalancesStore
|
import com.tangem.data.staking.store.StakingBalancesStore
|
||||||
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||||
|
|
@ -38,11 +38,11 @@ internal object StakingBalanceSupplierModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideP2PBalancesStore(
|
fun provideP2PEthPoolBalancesStore(
|
||||||
persistenceStore: DataStore<Map<String, Set<P2PEthPoolAccountResponse>>>,
|
persistenceStore: DataStore<Map<String, Set<P2PEthPoolAccountResponse>>>,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): P2PBalancesStore {
|
): P2PEthPoolBalancesStore {
|
||||||
return DefaultP2PBalancesStore(
|
return DefaultP2PEthPoolBalancesStore(
|
||||||
runtimeStore = RuntimeSharedStore(),
|
runtimeStore = RuntimeSharedStore(),
|
||||||
persistenceStore = persistenceStore,
|
persistenceStore = persistenceStore,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
|
|
|
||||||
|
|
@ -75,12 +75,12 @@ internal object StakingDataModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideP2PEthPoolRepository(
|
fun provideP2PEthPoolRepository(
|
||||||
p2pApi: P2PEthPoolApi,
|
p2pEthPoolApi: P2PEthPoolApi,
|
||||||
p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): P2PEthPoolRepository {
|
): P2PEthPoolRepository {
|
||||||
return DefaultP2PEthPoolRepository(
|
return DefaultP2PEthPoolRepository(
|
||||||
p2pApi = p2pApi,
|
p2pEthPoolApi = p2pEthPoolApi,
|
||||||
p2pEthPoolVaultsStore = p2pEthPoolVaultsStore,
|
p2pEthPoolVaultsStore = p2pEthPoolVaultsStore,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import arrow.core.left
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
import arrow.core.toOption
|
import arrow.core.toOption
|
||||||
import com.tangem.data.common.api.safeApiCall
|
import com.tangem.data.common.api.safeApiCall
|
||||||
import com.tangem.data.staking.store.P2PBalancesStore
|
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
|
||||||
import com.tangem.data.staking.store.StakingBalancesStore
|
import com.tangem.data.staking.store.StakingBalancesStore
|
||||||
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
|
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
|
||||||
import com.tangem.datasource.api.common.response.ApiResponse
|
import com.tangem.datasource.api.common.response.ApiResponse
|
||||||
|
|
@ -24,7 +24,8 @@ import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
|
||||||
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.runSuspendCatching
|
import com.tangem.utils.coroutines.runSuspendCatching
|
||||||
|
|
@ -38,16 +39,16 @@ import javax.inject.Inject
|
||||||
/**
|
/**
|
||||||
* Default implementation of [MultiStakingBalanceFetcher]
|
* Default implementation of [MultiStakingBalanceFetcher]
|
||||||
*
|
*
|
||||||
* Supports both StakeKit and P2P staking providers.
|
* Supports both StakeKit and P2PEthPool staking providers.
|
||||||
*
|
*
|
||||||
* @property userWalletsStore user wallets store
|
* @property userWalletsStore user wallets store
|
||||||
* @property stakingYieldsStore staking yields store
|
* @property stakingYieldsStore staking yields store
|
||||||
* @property stakingBalancesStore staking balances store (StakeKit)
|
* @property stakingBalancesStore staking balances store (StakeKit)
|
||||||
* @property p2pBalancesStore P2P balances store
|
* @property p2PEthPoolBalancesStore P2PEthPool balances store
|
||||||
* @property stakeKitApi stake kit API
|
* @property stakeKitApi stake kit API
|
||||||
* @property p2pApi P2P ETH Pool API
|
* @property p2pEthPoolApi P2PEthPool API
|
||||||
* @property p2pVaultsStore P2P vaults store
|
* @property p2pEthPoolVaultsStore P2PEthPool vaults store
|
||||||
* @property dispatchers dispatchers
|
* @property dispatchers dispatchers
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
|
@ -56,10 +57,10 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
private val userWalletsStore: UserWalletsStore,
|
private val userWalletsStore: UserWalletsStore,
|
||||||
private val stakingYieldsStore: StakingYieldsStore,
|
private val stakingYieldsStore: StakingYieldsStore,
|
||||||
private val stakingBalancesStore: StakingBalancesStore,
|
private val stakingBalancesStore: StakingBalancesStore,
|
||||||
private val p2pBalancesStore: P2PBalancesStore,
|
private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore,
|
||||||
private val stakeKitApi: StakeKitApi,
|
private val stakeKitApi: StakeKitApi,
|
||||||
private val p2pApi: P2PEthPoolApi,
|
private val p2pEthPoolApi: P2PEthPoolApi,
|
||||||
private val p2pVaultsStore: P2PEthPoolVaultsStore,
|
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : MultiStakingBalanceFetcher {
|
) : MultiStakingBalanceFetcher {
|
||||||
|
|
||||||
|
|
@ -75,7 +76,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
return it.left()
|
return it.left()
|
||||||
}
|
}
|
||||||
|
|
||||||
val (stakeKitIds, p2pIds) = stakingIds.partition { stakingId ->
|
val (stakeKitIds, p2pEthPoolIds) = stakingIds.partition { stakingId ->
|
||||||
val stakingIntegrationID = StakingIntegrationID.entries.find {
|
val stakingIntegrationID = StakingIntegrationID.entries.find {
|
||||||
it.value == stakingId.integrationId
|
it.value == stakingId.integrationId
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +87,7 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
"""
|
"""
|
||||||
Staking IDs to fetch:
|
Staking IDs to fetch:
|
||||||
- StakeKit: ${stakeKitIds.joinToString()}
|
- StakeKit: ${stakeKitIds.joinToString()}
|
||||||
- P2P: ${p2pIds.joinToString()}
|
- P2PEthPool: ${p2pEthPoolIds.joinToString()}
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -96,8 +97,8 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
launch { fetchStakeKitBalances(params.userWalletId, stakeKitIds.toSet()) }
|
launch { fetchStakeKitBalances(params.userWalletId, stakeKitIds.toSet()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p2pIds.isNotEmpty()) {
|
if (p2pEthPoolIds.isNotEmpty()) {
|
||||||
launch { fetchP2PBalances(params.userWalletId, p2pIds.toSet()) }
|
launch { fetchP2PBalances(params.userWalletId, p2pEthPoolIds.toSet()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,8 +111,11 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
stakingIds = stakeKitIds.toSet(),
|
stakingIds = stakeKitIds.toSet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (p2pIds.isNotEmpty()) {
|
if (p2pEthPoolIds.isNotEmpty()) {
|
||||||
p2pBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = p2pIds.toSet())
|
p2PEthPoolBalancesStore.storeError(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
stakingIds = p2pEthPoolIds.toSet(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,12 +132,12 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchP2PBalances(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
private suspend fun fetchP2PBalances(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||||
p2pBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
|
p2PEthPoolBalancesStore.refresh(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||||
|
|
||||||
val vaults = runSuspendCatching { p2pVaultsStore.getSync() }.getOrNull().orEmpty()
|
val vaults = runSuspendCatching { p2pEthPoolVaultsStore.getSync() }.getOrNull().orEmpty()
|
||||||
if (vaults.isEmpty()) {
|
if (vaults.isEmpty()) {
|
||||||
Timber.w("No P2P vaults available for $userWalletId")
|
Timber.w("No P2PEthPool vaults available for $userWalletId")
|
||||||
p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
p2PEthPoolBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,59 +147,17 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
private suspend fun fetchFromP2P(
|
private suspend fun fetchFromP2P(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
stakingIds: Set<StakingID>,
|
stakingIds: Set<StakingID>,
|
||||||
vaults: List<com.tangem.domain.staking.model.ethpool.P2PEthPoolVault>,
|
vaults: List<P2PEthPoolVault>,
|
||||||
) {
|
) {
|
||||||
safeApiCall(
|
safeApiCall(
|
||||||
call = {
|
call = {
|
||||||
val addresses = stakingIds.map { it.address }.toSet()
|
val addresses = stakingIds.map { it.address }.toSet()
|
||||||
|
val responses = fetchP2PAccountResponses(vaults = vaults, addresses = addresses)
|
||||||
|
|
||||||
val responses = mutableSetOf<P2PEthPoolAccountResponse>()
|
Timber.i("Successfully fetched ${responses.size} P2PEthPool balances for $userWalletId")
|
||||||
|
|
||||||
for (vault in vaults) {
|
|
||||||
for (address in addresses) {
|
|
||||||
runSuspendCatching {
|
|
||||||
val response = p2pApi.getAccountInfo(
|
|
||||||
network = P2PStakingConfig.activeNetwork.value,
|
|
||||||
delegatorAddress = address,
|
|
||||||
vaultAddress = vault.vaultAddress,
|
|
||||||
)
|
|
||||||
|
|
||||||
when (response) {
|
|
||||||
is ApiResponse.Success -> {
|
|
||||||
val data = response.data
|
|
||||||
if (data.error != null) {
|
|
||||||
Timber.w(
|
|
||||||
"P2P API returned error for vault ${vault.vaultAddress}, " +
|
|
||||||
"address $address: ${data.error ?: "error"}",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val result = requireNotNull(data.result) {
|
|
||||||
"Result is null in successful response"
|
|
||||||
}
|
|
||||||
responses.add(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ApiResponse.Error -> {
|
|
||||||
Timber.w(
|
|
||||||
response.cause,
|
|
||||||
"Failed to fetch P2P balance for vault ${vault.vaultAddress}, " +
|
|
||||||
"address $address",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure { error ->
|
|
||||||
Timber.w(
|
|
||||||
error,
|
|
||||||
"Failed to fetch P2P balance for vault ${vault.vaultAddress}, address $address",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.i("Successfully fetched ${responses.size} P2P balances for $userWalletId")
|
|
||||||
|
|
||||||
if (responses.isNotEmpty()) {
|
if (responses.isNotEmpty()) {
|
||||||
p2pBalancesStore.storeActual(userWalletId = userWalletId, values = responses)
|
p2PEthPoolBalancesStore.storeActual(userWalletId = userWalletId, values = responses)
|
||||||
|
|
||||||
val missingStakingIds = stakingIds.filter { stakingId ->
|
val missingStakingIds = stakingIds.filter { stakingId ->
|
||||||
responses.none { response ->
|
responses.none { response ->
|
||||||
|
|
@ -205,23 +167,76 @@ internal class DefaultMultiStakingBalanceFetcher @Inject constructor(
|
||||||
|
|
||||||
if (missingStakingIds.isNotEmpty()) {
|
if (missingStakingIds.isNotEmpty()) {
|
||||||
Timber.i("Missing responses for ${missingStakingIds.size} staking IDs: $missingStakingIds")
|
Timber.i("Missing responses for ${missingStakingIds.size} staking IDs: $missingStakingIds")
|
||||||
p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = missingStakingIds.toSet())
|
p2PEthPoolBalancesStore.storeError(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
stakingIds = missingStakingIds.toSet(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.i("No P2P responses received for $userWalletId")
|
Timber.i("No P2PEthPool responses received for $userWalletId")
|
||||||
p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
p2PEthPoolBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = { throwable ->
|
onError = { throwable ->
|
||||||
Timber.e(throwable, "Unable to fetch P2P balances $userWalletId")
|
Timber.e(throwable, "Unable to fetch P2PEthPool balances $userWalletId")
|
||||||
|
|
||||||
p2pBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
p2PEthPoolBalancesStore.storeError(userWalletId = userWalletId, stakingIds = stakingIds)
|
||||||
|
|
||||||
throw throwable
|
throw throwable
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchP2PAccountResponses(
|
||||||
|
vaults: List<P2PEthPoolVault>,
|
||||||
|
addresses: Set<String>,
|
||||||
|
): Set<P2PEthPoolAccountResponse> {
|
||||||
|
val responses = mutableSetOf<P2PEthPoolAccountResponse>()
|
||||||
|
|
||||||
|
for (vault in vaults) {
|
||||||
|
for (address in addresses) {
|
||||||
|
runSuspendCatching {
|
||||||
|
val response = p2pEthPoolApi.getAccountInfo(
|
||||||
|
network = P2PEthPoolStakingConfig.activeNetwork.value,
|
||||||
|
delegatorAddress = address,
|
||||||
|
vaultAddress = vault.vaultAddress,
|
||||||
|
)
|
||||||
|
|
||||||
|
when (response) {
|
||||||
|
is ApiResponse.Success -> {
|
||||||
|
val data = response.data
|
||||||
|
if (data.error != null) {
|
||||||
|
Timber.w(
|
||||||
|
"P2PEthPool API returned error for vault ${vault.vaultAddress}, " +
|
||||||
|
"address $address: ${data.error ?: "error"}",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val result = requireNotNull(data.result) {
|
||||||
|
"Result is null in successful response"
|
||||||
|
}
|
||||||
|
responses.add(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ApiResponse.Error -> {
|
||||||
|
Timber.w(
|
||||||
|
response.cause,
|
||||||
|
"Failed to fetch P2PEthPool balance for vault ${vault.vaultAddress}, " +
|
||||||
|
"address $address",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure { error ->
|
||||||
|
Timber.w(
|
||||||
|
error,
|
||||||
|
"Failed to fetch P2PEthPool balance for vault ${vault.vaultAddress}, address $address",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
private inline fun checkIsSupportedByWalletOrElse(userWalletId: UserWalletId, ifNotSupported: (Throwable) -> Unit) {
|
private inline fun checkIsSupportedByWalletOrElse(userWalletId: UserWalletId, ifNotSupported: (Throwable) -> Unit) {
|
||||||
val maybeUserWallet = userWalletsStore.getSyncOrNull(key = userWalletId).toOption()
|
val maybeUserWallet = userWalletsStore.getSyncOrNull(key = userWalletId).toOption()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package com.tangem.data.staking.multi
|
||||||
|
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import arrow.core.some
|
import arrow.core.some
|
||||||
import com.tangem.data.staking.store.P2PBalancesStore
|
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
|
||||||
import com.tangem.data.staking.store.StakingBalancesStore
|
import com.tangem.data.staking.store.StakingBalancesStore
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
|
import com.tangem.domain.staking.multi.MultiStakingBalanceProducer
|
||||||
|
|
@ -19,11 +19,11 @@ import kotlinx.coroutines.flow.onEmpty
|
||||||
/**
|
/**
|
||||||
* Default implementation of [MultiStakingBalanceProducer]
|
* Default implementation of [MultiStakingBalanceProducer]
|
||||||
*
|
*
|
||||||
* Combines staking balances from both StakeKit and P2P providers.
|
* Combines staking balances from both StakeKit and P2PEthPool providers.
|
||||||
*
|
*
|
||||||
* @property params params
|
* @property params params
|
||||||
* @property stakingBalancesStore StakeKit staking balances store
|
* @property stakingBalancesStore StakeKit staking balances store
|
||||||
* @property p2pBalancesStore P2P balances store
|
* @property p2PEthPoolBalancesStore P2PEthPool balances store
|
||||||
* @property dispatchers dispatchers
|
* @property dispatchers dispatchers
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.onEmpty
|
||||||
internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor(
|
internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor(
|
||||||
@Assisted val params: MultiStakingBalanceProducer.Params,
|
@Assisted val params: MultiStakingBalanceProducer.Params,
|
||||||
private val stakingBalancesStore: StakingBalancesStore,
|
private val stakingBalancesStore: StakingBalancesStore,
|
||||||
private val p2pBalancesStore: P2PBalancesStore,
|
private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : MultiStakingBalanceProducer {
|
) : MultiStakingBalanceProducer {
|
||||||
|
|
||||||
|
|
@ -39,10 +39,10 @@ internal class DefaultMultiStakingBalanceProducer @AssistedInject constructor(
|
||||||
|
|
||||||
override fun produce(): Flow<Set<StakingBalance>> {
|
override fun produce(): Flow<Set<StakingBalance>> {
|
||||||
val stakeKitFlow = stakingBalancesStore.get(userWalletId = params.userWalletId)
|
val stakeKitFlow = stakingBalancesStore.get(userWalletId = params.userWalletId)
|
||||||
val p2pFlow = p2pBalancesStore.get(userWalletId = params.userWalletId)
|
val p2pEthPoolFlow = p2PEthPoolBalancesStore.get(userWalletId = params.userWalletId)
|
||||||
|
|
||||||
return combine(stakeKitFlow, p2pFlow) { stakeKitBalances, p2pBalances ->
|
return combine(stakeKitFlow, p2pEthPoolFlow) { stakeKitBalances, p2pEthPoolBalances ->
|
||||||
stakeKitBalances + p2pBalances
|
stakeKitBalances + p2pEthPoolBalances
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEmpty { emit(value = hashSetOf()) }
|
.onEmpty { emit(value = hashSetOf()) }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.data.staking.store
|
package com.tangem.data.staking.store
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import com.tangem.data.staking.converters.ethpool.P2PStakingBalanceConverter
|
import com.tangem.data.staking.converters.ethpool.P2PEthPoolStakingBalanceConverter
|
||||||
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
|
|
@ -20,22 +20,22 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
internal typealias WalletIdWithP2PStakingBalances = Map<UserWalletId, Set<StakingBalance>>
|
internal typealias WalletIdWithP2PStakingBalances = Map<UserWalletId, Set<StakingBalance>>
|
||||||
internal typealias WalletIdWithP2PResponses = Map<String, Set<P2PEthPoolAccountResponse>>
|
internal typealias WalletIdWithP2PEthPoolResponses = Map<String, Set<P2PEthPoolAccountResponse>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of [P2PBalancesStore]
|
* Default implementation of [P2PEthPoolBalancesStore]
|
||||||
*
|
*
|
||||||
* Stores P2P ETH Pool staking balances.
|
* Stores P2PEthPool staking balances.
|
||||||
*
|
*
|
||||||
* @property runtimeStore runtime store
|
* @property runtimeStore runtime store
|
||||||
* @property persistenceStore persistence store
|
* @property persistenceStore persistence store
|
||||||
* @param dispatchers coroutine dispatchers
|
* @param dispatchers coroutine dispatchers
|
||||||
*/
|
*/
|
||||||
internal class DefaultP2PBalancesStore(
|
internal class DefaultP2PEthPoolBalancesStore(
|
||||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithP2PStakingBalances>,
|
private val runtimeStore: RuntimeSharedStore<WalletIdWithP2PStakingBalances>,
|
||||||
private val persistenceStore: DataStore<WalletIdWithP2PResponses>,
|
private val persistenceStore: DataStore<WalletIdWithP2PEthPoolResponses>,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
) : P2PBalancesStore {
|
) : P2PEthPoolBalancesStore {
|
||||||
|
|
||||||
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
|
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ internal class DefaultP2PBalancesStore(
|
||||||
value = cachedData.map { (stringWalletId, responses) ->
|
value = cachedData.map { (stringWalletId, responses) ->
|
||||||
val key = UserWalletId(stringWalletId)
|
val key = UserWalletId(stringWalletId)
|
||||||
val value = responses.map { response ->
|
val value = responses.map { response ->
|
||||||
P2PStakingBalanceConverter.convert(
|
P2PEthPoolStakingBalanceConverter.convert(
|
||||||
response = response,
|
response = response,
|
||||||
source = StatusSource.CACHE,
|
source = StatusSource.CACHE,
|
||||||
)
|
)
|
||||||
|
|
@ -108,7 +108,7 @@ internal class DefaultP2PBalancesStore(
|
||||||
|
|
||||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<P2PEthPoolAccountResponse>) {
|
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<P2PEthPoolAccountResponse>) {
|
||||||
val newBalances = values.map { response ->
|
val newBalances = values.map { response ->
|
||||||
P2PStakingBalanceConverter.convert(
|
P2PEthPoolStakingBalanceConverter.convert(
|
||||||
response = response,
|
response = response,
|
||||||
source = StatusSource.ACTUAL,
|
source = StatusSource.ACTUAL,
|
||||||
)
|
)
|
||||||
|
|
@ -152,7 +152,7 @@ internal class DefaultP2PBalancesStore(
|
||||||
current.toMutableMap().apply {
|
current.toMutableMap().apply {
|
||||||
this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty()
|
this[userWalletId.stringValue] = this[userWalletId.stringValue].orEmpty()
|
||||||
.filterNot { response ->
|
.filterNot { response ->
|
||||||
StakingIntegrationID.P2P.EthereumPooled.value in integrationIds
|
StakingIntegrationID.P2PEthPool.value in integrationIds
|
||||||
}
|
}
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
@ -7,9 +7,9 @@ import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store for P2P ETH Pool staking balances
|
* Store for P2PEthPool staking balances
|
||||||
*/
|
*/
|
||||||
interface P2PBalancesStore {
|
interface P2PEthPoolBalancesStore {
|
||||||
|
|
||||||
fun get(userWalletId: UserWalletId): Flow<Set<StakingBalance>>
|
fun get(userWalletId: UserWalletId): Flow<Set<StakingBalance>>
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.tangem.data.staking
|
package com.tangem.data.staking
|
||||||
|
|
||||||
import com.tangem.data.staking.converters.ethpool.P2PStakingBalanceConverter
|
import com.tangem.data.staking.converters.ethpool.P2PEthPoolStakingBalanceConverter
|
||||||
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||||
import com.tangem.datasource.local.token.converter.StakingBalanceConverter
|
import com.tangem.datasource.local.token.converter.StakingBalanceConverter
|
||||||
|
|
@ -11,8 +11,10 @@ internal fun YieldBalanceWrapperDTO.toDomain(source: StatusSource = StatusSource
|
||||||
return StakingBalanceConverter(isCached = source == StatusSource.CACHE).convert(this)!!
|
return StakingBalanceConverter(isCached = source == StatusSource.CACHE).convert(this)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun P2PEthPoolAccountResponse.toDomain(source: StatusSource = StatusSource.CACHE): StakingBalance.Data.P2P {
|
internal fun P2PEthPoolAccountResponse.toDomain(
|
||||||
return P2PStakingBalanceConverter.convert(
|
source: StatusSource = StatusSource.CACHE,
|
||||||
|
): StakingBalance.Data.P2PEthPool {
|
||||||
|
return P2PEthPoolStakingBalanceConverter.convert(
|
||||||
response = this,
|
response = this,
|
||||||
source = source,
|
source = source,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import arrow.core.toOption
|
||||||
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
|
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
|
||||||
import com.tangem.common.test.data.staking.MockYieldDTOFactory
|
import com.tangem.common.test.data.staking.MockYieldDTOFactory
|
||||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||||
import com.tangem.data.staking.store.P2PBalancesStore
|
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
|
||||||
import com.tangem.data.staking.store.StakingBalancesStore
|
import com.tangem.data.staking.store.StakingBalancesStore
|
||||||
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
|
import com.tangem.data.staking.utils.YieldBalanceRequestBodyFactory
|
||||||
import com.tangem.datasource.api.common.response.ApiResponse
|
import com.tangem.datasource.api.common.response.ApiResponse
|
||||||
|
|
@ -36,19 +36,19 @@ internal class DefaultMultiStakingBalanceFetcherTest {
|
||||||
private val userWalletsStore: UserWalletsStore = mockk()
|
private val userWalletsStore: UserWalletsStore = mockk()
|
||||||
private val stakingYieldsStore: StakingYieldsStore = mockk()
|
private val stakingYieldsStore: StakingYieldsStore = mockk()
|
||||||
private val stakingBalancesStore: StakingBalancesStore = mockk(relaxUnitFun = true)
|
private val stakingBalancesStore: StakingBalancesStore = mockk(relaxUnitFun = true)
|
||||||
private val p2pBalancesStore: P2PBalancesStore = mockk(relaxUnitFun = true)
|
private val p2PEthPoolBalancesStore: P2PEthPoolBalancesStore = mockk(relaxUnitFun = true)
|
||||||
private val stakeKitApi: StakeKitApi = mockk()
|
private val stakeKitApi: StakeKitApi = mockk()
|
||||||
private val p2pApi: P2PEthPoolApi = mockk()
|
private val p2pEthPoolApi: P2PEthPoolApi = mockk()
|
||||||
private val p2pVaultsStore: P2PEthPoolVaultsStore = mockk()
|
private val p2pEthPoolVaultsStore: P2PEthPoolVaultsStore = mockk()
|
||||||
|
|
||||||
private val fetcher = DefaultMultiStakingBalanceFetcher(
|
private val fetcher = DefaultMultiStakingBalanceFetcher(
|
||||||
userWalletsStore = userWalletsStore,
|
userWalletsStore = userWalletsStore,
|
||||||
stakingYieldsStore = stakingYieldsStore,
|
stakingYieldsStore = stakingYieldsStore,
|
||||||
stakingBalancesStore = stakingBalancesStore,
|
stakingBalancesStore = stakingBalancesStore,
|
||||||
p2pBalancesStore = p2pBalancesStore,
|
p2PEthPoolBalancesStore = p2PEthPoolBalancesStore,
|
||||||
stakeKitApi = stakeKitApi,
|
stakeKitApi = stakeKitApi,
|
||||||
p2pApi = p2pApi,
|
p2pEthPoolApi = p2pEthPoolApi,
|
||||||
p2pVaultsStore = p2pVaultsStore,
|
p2pEthPoolVaultsStore = p2pEthPoolVaultsStore,
|
||||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.tangem.data.staking.multi
|
||||||
import com.google.common.truth.Truth
|
import com.google.common.truth.Truth
|
||||||
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
|
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
|
||||||
import com.tangem.common.test.data.staking.MockP2PEthPoolAccountResponseFactory
|
import com.tangem.common.test.data.staking.MockP2PEthPoolAccountResponseFactory
|
||||||
import com.tangem.data.staking.store.P2PBalancesStore
|
import com.tangem.data.staking.store.P2PEthPoolBalancesStore
|
||||||
import com.tangem.data.staking.store.StakingBalancesStore
|
import com.tangem.data.staking.store.StakingBalancesStore
|
||||||
import com.tangem.data.staking.toDomain
|
import com.tangem.data.staking.toDomain
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
|
|
@ -28,13 +28,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
private val params = MultiStakingBalanceProducer.Params(userWalletId = UserWalletId("011"))
|
private val params = MultiStakingBalanceProducer.Params(userWalletId = UserWalletId("011"))
|
||||||
|
|
||||||
private val stakingBalancesStore = mockk<StakingBalancesStore>()
|
private val stakingBalancesStore = mockk<StakingBalancesStore>()
|
||||||
private val p2pBalancesStore = mockk<P2PBalancesStore>()
|
private val p2PEthPoolBalancesStore = mockk<P2PEthPoolBalancesStore>()
|
||||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||||
|
|
||||||
private val producer = DefaultMultiStakingBalanceProducer(
|
private val producer = DefaultMultiStakingBalanceProducer(
|
||||||
params = params,
|
params = params,
|
||||||
stakingBalancesStore = stakingBalancesStore,
|
stakingBalancesStore = stakingBalancesStore,
|
||||||
p2pBalancesStore = p2pBalancesStore,
|
p2PEthPoolBalancesStore = p2PEthPoolBalancesStore,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -48,13 +48,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
val networksStatusesFlow = flowOf(balances)
|
val networksStatusesFlow = flowOf(balances)
|
||||||
|
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
||||||
|
|
||||||
val actual = producer.produce()
|
val actual = producer.produce()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
val values = getEmittedValues(flow = actual)
|
val values = getEmittedValues(flow = actual)
|
||||||
|
|
||||||
|
|
@ -67,13 +67,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
|
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
|
||||||
|
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
||||||
|
|
||||||
val actual = producer.produce()
|
val actual = producer.produce()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
// first emit
|
// first emit
|
||||||
val balances = setOf(
|
val balances = setOf(
|
||||||
|
|
@ -108,13 +108,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
|
val networksStatusesFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
|
||||||
|
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
||||||
|
|
||||||
val actual = producer.produce()
|
val actual = producer.produce()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
// first emit
|
// first emit
|
||||||
val wrappers = setOf(
|
val wrappers = setOf(
|
||||||
|
|
@ -157,13 +157,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
.buffer(capacity = 5)
|
.buffer(capacity = 5)
|
||||||
|
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
every { stakingBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(emptySet())
|
||||||
|
|
||||||
val actual = producer.produceWithFallback()
|
val actual = producer.produceWithFallback()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
val values1 = getEmittedValues(flow = actual)
|
val values1 = getEmittedValues(flow = actual)
|
||||||
|
|
||||||
|
|
@ -181,13 +181,13 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test that flow is empty`() = runTest {
|
fun `test that flow is empty`() = runTest {
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns emptyFlow()
|
every { stakingBalancesStore.get(params.userWalletId) } returns emptyFlow()
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns emptyFlow()
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns emptyFlow()
|
||||||
|
|
||||||
val actual = producer.produce()
|
val actual = producer.produce()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
val values = getEmittedValues(flow = actual)
|
val values = getEmittedValues(flow = actual)
|
||||||
|
|
||||||
|
|
@ -198,53 +198,53 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test that StakeKit and P2P balances are combined`() = runTest {
|
fun `test that StakeKit and P2P balances are combined`() = runTest {
|
||||||
val stakeKitBalances = createStakeKitBalances()
|
val stakeKitBalances = createStakeKitBalances()
|
||||||
val p2pBalances = createP2PBalances()
|
val p2pEthPoolBalances = createP2PEthPoolBalances()
|
||||||
|
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
|
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns flowOf(p2pBalances)
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns flowOf(p2pEthPoolBalances)
|
||||||
|
|
||||||
val actual = producer.produce()
|
val actual = producer.produce()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
val values = getEmittedValues(flow = actual)
|
val values = getEmittedValues(flow = actual)
|
||||||
|
|
||||||
Truth.assertThat(values.size).isEqualTo(1)
|
Truth.assertThat(values.size).isEqualTo(1)
|
||||||
Truth.assertThat(values.first()).isEqualTo(stakeKitBalances + p2pBalances)
|
Truth.assertThat(values.first()).isEqualTo(stakeKitBalances + p2pEthPoolBalances)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test that P2P balances are updated independently from StakeKit`() = runTest {
|
fun `test that P2P balances are updated independently from StakeKit`() = runTest {
|
||||||
val stakeKitBalances = createStakeKitBalancesWithTonOnly()
|
val stakeKitBalances = createStakeKitBalancesWithTonOnly()
|
||||||
val p2pFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
|
val p2pEthPoolFlow = MutableSharedFlow<Set<StakingBalance>>(replay = 2)
|
||||||
|
|
||||||
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
|
every { stakingBalancesStore.get(params.userWalletId) } returns flowOf(stakeKitBalances)
|
||||||
every { p2pBalancesStore.get(params.userWalletId) } returns p2pFlow
|
every { p2PEthPoolBalancesStore.get(params.userWalletId) } returns p2pEthPoolFlow
|
||||||
|
|
||||||
val actual = producer.produce()
|
val actual = producer.produce()
|
||||||
|
|
||||||
// check after producer.produce()
|
// check after producer.produce()
|
||||||
verify { stakingBalancesStore.get(params.userWalletId) }
|
verify { stakingBalancesStore.get(params.userWalletId) }
|
||||||
verify { p2pBalancesStore.get(params.userWalletId) }
|
verify { p2PEthPoolBalancesStore.get(params.userWalletId) }
|
||||||
|
|
||||||
// first emit - empty P2P
|
// first emit - empty P2PEthPool
|
||||||
p2pFlow.emit(emptySet())
|
p2pEthPoolFlow.emit(emptySet())
|
||||||
|
|
||||||
val values1 = getEmittedValues(flow = actual)
|
val values1 = getEmittedValues(flow = actual)
|
||||||
|
|
||||||
Truth.assertThat(values1.size).isEqualTo(1)
|
Truth.assertThat(values1.size).isEqualTo(1)
|
||||||
Truth.assertThat(values1.first()).isEqualTo(stakeKitBalances)
|
Truth.assertThat(values1.first()).isEqualTo(stakeKitBalances)
|
||||||
|
|
||||||
// second emit - with P2P balance
|
// second emit - with P2PEthPool balance
|
||||||
val p2pBalances = createP2PBalances()
|
val p2pEthPoolBalances = createP2PEthPoolBalances()
|
||||||
p2pFlow.emit(p2pBalances)
|
p2pEthPoolFlow.emit(p2pEthPoolBalances)
|
||||||
|
|
||||||
val values2 = getEmittedValues(flow = actual)
|
val values2 = getEmittedValues(flow = actual)
|
||||||
|
|
||||||
Truth.assertThat(values2.size).isEqualTo(2)
|
Truth.assertThat(values2.size).isEqualTo(2)
|
||||||
Truth.assertThat(values2.last()).isEqualTo(stakeKitBalances + p2pBalances)
|
Truth.assertThat(values2.last()).isEqualTo(stakeKitBalances + p2pEthPoolBalances)
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
@ -255,7 +255,7 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
address = "0x1",
|
address = "0x1",
|
||||||
)
|
)
|
||||||
val p2pEthereumId = StakingID(
|
val p2pEthereumId = StakingID(
|
||||||
integrationId = StakingIntegrationID.P2P.EthereumPooled.value,
|
integrationId = StakingIntegrationID.P2PEthPool.value,
|
||||||
address = "0x5aa711F440Eb6d4361148bBD89d03464628ace84",
|
address = "0x5aa711F440Eb6d4361148bBD89d03464628ace84",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ internal class DefaultMultiStakingBalanceProducerTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createP2PBalances(): Set<StakingBalance> {
|
fun createP2PEthPoolBalances(): Set<StakingBalance> {
|
||||||
return setOf(
|
return setOf(
|
||||||
MockP2PEthPoolAccountResponseFactory.createWithBalance(stakingId = p2pEthereumId).toDomain(
|
MockP2PEthPoolAccountResponseFactory.createWithBalance(stakingId = p2pEthereumId).toDomain(
|
||||||
source = StatusSource.ACTUAL,
|
source = StatusSource.ACTUAL,
|
||||||
|
|
|
||||||
|
|
@ -4,31 +4,31 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/** P2P.org staking account */
|
/** P2P.org eth pooled staking account */
|
||||||
@Serializable
|
@Serializable
|
||||||
data class P2PStakingAccount(
|
data class P2PEthPoolStakingAccount(
|
||||||
val delegatorAddress: String,
|
val delegatorAddress: String,
|
||||||
val vaultAddress: String,
|
val vaultAddress: String,
|
||||||
val stake: P2PStake,
|
val stake: P2PEthPoolStake,
|
||||||
val availableToUnstake: SerializedBigDecimal,
|
val availableToUnstake: SerializedBigDecimal,
|
||||||
val availableToWithdraw: SerializedBigDecimal,
|
val availableToWithdraw: SerializedBigDecimal,
|
||||||
val exitQueue: P2PExitQueue,
|
val exitQueue: P2PEthPoolExitQueue,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class P2PStake(
|
data class P2PEthPoolStake(
|
||||||
val assets: SerializedBigDecimal,
|
val assets: SerializedBigDecimal,
|
||||||
val totalEarnedAssets: SerializedBigDecimal,
|
val totalEarnedAssets: SerializedBigDecimal,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class P2PExitQueue(
|
data class P2PEthPoolExitQueue(
|
||||||
val total: SerializedBigDecimal,
|
val total: SerializedBigDecimal,
|
||||||
val requests: List<P2PExitRequest>,
|
val requests: List<P2PEthPoolExitRequest>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class P2PExitRequest(
|
data class P2PEthPoolExitRequest(
|
||||||
val ticket: String,
|
val ticket: String,
|
||||||
val totalAssets: SerializedBigDecimal,
|
val totalAssets: SerializedBigDecimal,
|
||||||
val timestamp: Instant,
|
val timestamp: Instant,
|
||||||
|
|
@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Staking balance facade covering StakeKit and P2P balances
|
* Staking balance facade covering StakeKit and P2PEthPool balances
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface StakingBalance {
|
sealed interface StakingBalance {
|
||||||
|
|
@ -50,10 +50,10 @@ sealed interface StakingBalance {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class P2P(
|
data class P2PEthPool(
|
||||||
override val stakingId: StakingID,
|
override val stakingId: StakingID,
|
||||||
override val source: StatusSource,
|
override val source: StatusSource,
|
||||||
val account: P2PStakingAccount,
|
val account: P2PEthPoolStakingAccount,
|
||||||
) : Data {
|
) : Data {
|
||||||
|
|
||||||
override val totalStaked: BigDecimal
|
override val totalStaked: BigDecimal
|
||||||
|
|
@ -93,7 +93,7 @@ sealed interface StakingBalance {
|
||||||
fun copySealed(source: StatusSource): StakingBalance {
|
fun copySealed(source: StatusSource): StakingBalance {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is Data.StakeKit -> copy(source = source)
|
is Data.StakeKit -> copy(source = source)
|
||||||
is Data.P2P -> copy(source = source)
|
is Data.P2PEthPool -> copy(source = source)
|
||||||
is Empty -> copy(source = source)
|
is Empty -> copy(source = source)
|
||||||
is Error -> this
|
is Error -> this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,17 @@ data class YieldToken(
|
||||||
val coinGeckoId: String?,
|
val coinGeckoId: String?,
|
||||||
val logoURI: String?,
|
val logoURI: String?,
|
||||||
val isPoints: Boolean?,
|
val isPoints: Boolean?,
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
val ETH = YieldToken( // TODO p2p
|
||||||
|
name = "Ethereum",
|
||||||
|
network = NetworkType.ETHEREUM,
|
||||||
|
symbol = "ETH",
|
||||||
|
decimals = 18,
|
||||||
|
address = null,
|
||||||
|
coinGeckoId = "ethereum",
|
||||||
|
logoURI = null,
|
||||||
|
isPoints = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,8 +29,6 @@ dependencies {
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
|
||||||
implementation(projects.features.staking.api)
|
|
||||||
|
|
||||||
implementation(tangemDeps.blockchain) {
|
implementation(tangemDeps.blockchain) {
|
||||||
exclude(module = "joda-time")
|
exclude(module = "joda-time")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.tangem.domain.staking.model
|
||||||
|
|
||||||
|
import com.tangem.domain.staking.model.common.RewardInfo
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents either a StakeKit Validator or a P2P ETH Pool Vault.
|
||||||
|
*/
|
||||||
|
sealed interface StakingTarget {
|
||||||
|
|
||||||
|
/** Unique identifier (validator address or vault address) */
|
||||||
|
val address: String
|
||||||
|
|
||||||
|
/** Display name */
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
/** Reward info (rate and type) */
|
||||||
|
val rewardInfo: RewardInfo?
|
||||||
|
|
||||||
|
/** Whether this target is preferred/recommended */
|
||||||
|
val isPreferred: Boolean
|
||||||
|
|
||||||
|
/** Whether this target is active and available for staking */
|
||||||
|
val isActive: Boolean
|
||||||
|
|
||||||
|
/** Image URL for display (validator logo or vault icon) */
|
||||||
|
val image: String?
|
||||||
|
|
||||||
|
/** Whether this is a strategic partner (shows special badge in UI) */
|
||||||
|
val isStrategicPartner: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StakeKit Validator wrapper
|
||||||
|
*/
|
||||||
|
data class Validator(val delegate: Yield.Validator) : StakingTarget {
|
||||||
|
override val address: String = delegate.address
|
||||||
|
override val name: String = delegate.name
|
||||||
|
override val rewardInfo: RewardInfo? = delegate.rewardInfo
|
||||||
|
override val isPreferred: Boolean = delegate.preferred
|
||||||
|
override val isActive: Boolean = delegate.status == Yield.Validator.ValidatorStatus.ACTIVE
|
||||||
|
override val image: String? = delegate.image
|
||||||
|
override val isStrategicPartner: Boolean = delegate.isStrategicPartner
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P2P ETH Pool Vault wrapper
|
||||||
|
*/
|
||||||
|
data class Vault(val vault: P2PEthPoolVault) : StakingTarget {
|
||||||
|
override val address: String = vault.vaultAddress
|
||||||
|
override val name: String = vault.displayName
|
||||||
|
override val rewardInfo = RewardInfo(
|
||||||
|
rate = vault.apy,
|
||||||
|
type = RewardType.APY,
|
||||||
|
)
|
||||||
|
override val isPreferred: Boolean = true
|
||||||
|
override val isActive: Boolean = true
|
||||||
|
override val image: String? = null
|
||||||
|
override val isStrategicPartner: Boolean = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Yield.Validator.toStakingTarget(): StakingTarget = StakingTarget.Validator(this)
|
||||||
|
|
||||||
|
fun P2PEthPoolVault.toStakingTarget(): StakingTarget = StakingTarget.Vault(this)
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.domain.staking.model.common
|
||||||
|
|
||||||
|
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RewardInfo(
|
||||||
|
val rate: SerializedBigDecimal,
|
||||||
|
val type: RewardType,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.domain.staking.model.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class RewardType {
|
||||||
|
APY, // compound rate
|
||||||
|
APR, // simple rate
|
||||||
|
UNKNOWN,
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
import org.joda.time.Instant
|
import org.joda.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P.org account staking information
|
* Account staking information
|
||||||
* Contains detailed balance and exit queue information
|
* Contains detailed balance and exit queue information
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolAccount(
|
data class P2PEthPoolAccount(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ data class P2PEthPoolAction(
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types of P2P staking actions
|
* Types of P2PEthPool staking actions
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class P2PEthPoolActionType {
|
enum class P2PEthPoolActionType {
|
||||||
|
|
@ -33,7 +33,7 @@ enum class P2PEthPoolActionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of P2P staking action
|
* Status of P2PEthPool staking action
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class P2PEthPoolActionStatus {
|
enum class P2PEthPoolActionStatus {
|
||||||
|
|
@ -46,7 +46,7 @@ enum class P2PEthPoolActionStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transaction details for P2P staking action
|
* Transaction details for P2PEthPool staking action
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolStakingTransaction(
|
data class P2PEthPoolStakingTransaction(
|
||||||
val id: String?,
|
val id: String?,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
import org.joda.time.Instant
|
import org.joda.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P.org staking balance information (similar to StakeKit's YieldBalanceItem)
|
* Staking balance information (similar to StakeKit's YieldBalanceItem)
|
||||||
* Contains staked amounts, rewards, and pending actions
|
* Contains staked amounts, rewards, and pending actions
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolStakingBalance(
|
data class P2PEthPoolStakingBalance(
|
||||||
|
|
@ -15,7 +15,7 @@ data class P2PEthPoolStakingBalance(
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Individual balance item for P2P staking
|
* Individual balance item for P2PEthPool staking
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolBalanceItem(
|
data class P2PEthPoolBalanceItem(
|
||||||
val type: P2PEthPoolBalanceType,
|
val type: P2PEthPoolBalanceType,
|
||||||
|
|
@ -26,7 +26,7 @@ data class P2PEthPoolBalanceItem(
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types of balances in P2P staking
|
* Types of balances in P2PEthPool staking
|
||||||
*/
|
*/
|
||||||
enum class P2PEthPoolBalanceType {
|
enum class P2PEthPoolBalanceType {
|
||||||
STAKED,
|
STAKED,
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ enum class P2PEthPoolNetwork(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if chain ID is supported for P2P staking
|
* Check if chain ID is supported for P2PEthPool staking
|
||||||
*
|
*
|
||||||
* @param chainId Ethereum chain ID
|
* @param chainId Ethereum chain ID
|
||||||
* @return true if supported, false otherwise
|
* @return true if supported, false otherwise
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P.org rewards history entry
|
* Rewards history entry
|
||||||
* Historical reward information for account
|
* Historical reward information for account
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolReward(
|
data class P2PEthPoolReward(
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ data class P2PEthPoolStaking(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detailed vault information for P2P staking
|
* Detailed vault information for P2PEthPool staking
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolVaultDetails(
|
data class P2PEthPoolVaultDetails(
|
||||||
val vaultAddress: String,
|
val vaultAddress: String,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package com.tangem.domain.staking.model.ethpool
|
package com.tangem.domain.staking.model.ethpool
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for P2P Ethereum staking network.
|
* Configuration for P2PEthPool Ethereum staking network.
|
||||||
*
|
*
|
||||||
* Change [USE_TESTNET] to switch between testnet and mainnet.
|
* Change [USE_TESTNET] to switch between testnet and mainnet.
|
||||||
*/
|
*/
|
||||||
object P2PStakingConfig {
|
object P2PEthPoolStakingConfig {
|
||||||
|
|
||||||
const val USE_TESTNET: Boolean = true
|
const val USE_TESTNET: Boolean = true
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.tangem.domain.staking.model.ethpool
|
||||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P.org unsigned transaction ready for signing
|
* Unsigned transaction ready for signing
|
||||||
* Contains all necessary data for transaction signing
|
* Contains all necessary data for transaction signing
|
||||||
*/
|
*/
|
||||||
data class P2PEthPoolUnsignedTx(
|
data class P2PEthPoolUnsignedTx(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package com.tangem.domain.staking.model.stakekit
|
||||||
|
|
||||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
import com.tangem.domain.models.staking.YieldToken
|
import com.tangem.domain.models.staking.YieldToken
|
||||||
|
import com.tangem.domain.staking.model.common.RewardInfo
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -138,18 +140,6 @@ data class Yield(
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class RewardType {
|
|
||||||
APY, // compound rate
|
|
||||||
APR, // simple rate
|
|
||||||
UNKNOWN,
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class RewardInfo(
|
|
||||||
val rate: SerializedBigDecimal,
|
|
||||||
val type: RewardType,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use case for fetching all staking options from all providers
|
* Use case for fetching all staking options from all providers
|
||||||
* Fetches both StakeKit yields and P2P vaults
|
* Fetches both StakeKit yields and P2PEthPool vaults
|
||||||
*/
|
*/
|
||||||
class FetchStakingOptionsUseCase(
|
class FetchStakingOptionsUseCase(
|
||||||
private val stakeKitRepository: StakeKitRepository,
|
private val stakeKitRepository: StakeKitRepository,
|
||||||
private val p2pRepository: P2PEthPoolRepository,
|
private val p2pEthPoolRepository: P2PEthPoolRepository,
|
||||||
private val stakingErrorResolver: StakingErrorResolver,
|
private val stakingErrorResolver: StakingErrorResolver,
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(): Either<StakingError, Unit> {
|
suspend operator fun invoke(): Either<StakingError, Unit> {
|
||||||
|
|
@ -25,7 +25,7 @@ class FetchStakingOptionsUseCase(
|
||||||
block = {
|
block = {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
launch { stakeKitRepository.fetchYields() }
|
launch { stakeKitRepository.fetchYields() }
|
||||||
launch { p2pRepository.fetchVaults() }
|
launch { p2pEthPoolRepository.fetchVaults() }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
catch = { stakingErrorResolver.resolve(it) },
|
catch = { stakingErrorResolver.resolve(it) },
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class GetStakingEntryInfoUseCase(
|
||||||
symbol = symbol,
|
symbol = symbol,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is StakingOption.P2P -> {
|
is StakingOption.P2PEthPool -> {
|
||||||
StakingEntryInfo(
|
StakingEntryInfo(
|
||||||
tokenSymbol = "ETH",
|
tokenSymbol = "ETH",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.tangem.domain.staking.model
|
||||||
|
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.staking.YieldToken
|
||||||
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StakingIntegration implementation for P2PEthPool pooled staking.
|
||||||
|
* Converts P2PEthPoolVault data to the common StakingIntegration interface.
|
||||||
|
*/
|
||||||
|
class P2PEthPoolIntegration(
|
||||||
|
override val integrationId: StakingIntegrationID,
|
||||||
|
vaults: List<P2PEthPoolVault>,
|
||||||
|
) : StakingIntegration {
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
|
||||||
|
override val token: YieldToken = YieldToken.ETH
|
||||||
|
|
||||||
|
override val tokens: List<YieldToken> = listOf(token)
|
||||||
|
|
||||||
|
// Targets (vaults)
|
||||||
|
|
||||||
|
override val targets: List<StakingTarget> = vaults.map { vault ->
|
||||||
|
vault.toStakingTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val preferredTargets: List<StakingTarget> = targets
|
||||||
|
|
||||||
|
override val areAllTargetsFull: Boolean = false
|
||||||
|
|
||||||
|
// Enter/Exit Args
|
||||||
|
|
||||||
|
override val isPartialAmountDisabled: Boolean = false
|
||||||
|
|
||||||
|
override val enterMinimumAmount: BigDecimal = DEFAULT_MINIMUM_STAKE
|
||||||
|
|
||||||
|
override val exitMinimumAmount: BigDecimal? = null
|
||||||
|
|
||||||
|
override val enterArgs: Yield.Args.Enter? = null
|
||||||
|
|
||||||
|
override val exitArgs: Yield.Args.Enter? = null
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
|
||||||
|
override val warmupPeriodDays: Int = 0
|
||||||
|
|
||||||
|
override val cooldownPeriodDays: Int = DEFAULT_COOLDOWN_DAYS
|
||||||
|
|
||||||
|
override val rewardSchedule: Yield.Metadata.RewardSchedule = Yield.Metadata.RewardSchedule.DAY
|
||||||
|
|
||||||
|
override val rewardClaiming: Yield.Metadata.RewardClaiming = Yield.Metadata.RewardClaiming.AUTO
|
||||||
|
|
||||||
|
override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken = token
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEFAULT_COOLDOWN_DAYS = 7
|
||||||
|
private val DEFAULT_MINIMUM_STAKE = BigDecimal("0.01")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.tangem.domain.staking.model
|
||||||
|
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.staking.YieldToken
|
||||||
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StakingIntegration implementation for StakeKit.
|
||||||
|
* Delegates all calls to the underlying Yield object.
|
||||||
|
*/
|
||||||
|
class StakeKitIntegration(
|
||||||
|
override val integrationId: StakingIntegrationID,
|
||||||
|
private val yield: Yield,
|
||||||
|
) : StakingIntegration {
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
|
||||||
|
override val token: YieldToken = yield.token
|
||||||
|
|
||||||
|
override val tokens: List<YieldToken> = yield.tokens
|
||||||
|
|
||||||
|
// Targets (validators)
|
||||||
|
|
||||||
|
override val targets: List<StakingTarget> = yield.validators.map { it.toStakingTarget() }
|
||||||
|
|
||||||
|
override val preferredTargets: List<StakingTarget> = yield.preferredValidators.map { it.toStakingTarget() }
|
||||||
|
|
||||||
|
override val areAllTargetsFull: Boolean = yield.allValidatorsFull
|
||||||
|
|
||||||
|
// Enter/Exit Args
|
||||||
|
|
||||||
|
override val isPartialAmountDisabled: Boolean = yield.args.enter.isPartialAmountDisabled
|
||||||
|
|
||||||
|
override val enterMinimumAmount: BigDecimal? =
|
||||||
|
yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum
|
||||||
|
|
||||||
|
override val exitMinimumAmount: BigDecimal? =
|
||||||
|
yield.args.exit?.args
|
||||||
|
?.get(Yield.Args.ArgType.AMOUNT)?.minimum
|
||||||
|
|
||||||
|
override val enterArgs: Yield.Args.Enter = yield.args.enter
|
||||||
|
|
||||||
|
override val exitArgs: Yield.Args.Enter? = yield.args.exit
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
|
||||||
|
override val warmupPeriodDays: Int = yield.metadata.warmupPeriod.days
|
||||||
|
|
||||||
|
override val cooldownPeriodDays: Int? = yield.metadata.cooldownPeriod?.days
|
||||||
|
|
||||||
|
override val rewardSchedule: Yield.Metadata.RewardSchedule = yield.metadata.rewardSchedule
|
||||||
|
|
||||||
|
override val rewardClaiming: Yield.Metadata.RewardClaiming = yield.metadata.rewardClaiming
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
|
||||||
|
override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken =
|
||||||
|
tokens.firstOrNull { rawCurrencyId?.value == it.coinGeckoId } ?: token
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.tangem.domain.staking.model
|
||||||
|
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.staking.YieldToken
|
||||||
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy interface for staking integrations.
|
||||||
|
* Abstracts over StakeKit and P2PEthPool staking providers.
|
||||||
|
*/
|
||||||
|
// TODO p2p get rid of stakekit-specific models in StakingIntegration and implementors
|
||||||
|
interface StakingIntegration {
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
|
||||||
|
val integrationId: StakingIntegrationID
|
||||||
|
|
||||||
|
val token: YieldToken
|
||||||
|
|
||||||
|
val tokens: List<YieldToken>
|
||||||
|
|
||||||
|
// Targets (validators or vaults)
|
||||||
|
|
||||||
|
val targets: List<StakingTarget>
|
||||||
|
|
||||||
|
val preferredTargets: List<StakingTarget>
|
||||||
|
|
||||||
|
val areAllTargetsFull: Boolean
|
||||||
|
|
||||||
|
// Enter/Exit Args
|
||||||
|
|
||||||
|
val isPartialAmountDisabled: Boolean
|
||||||
|
|
||||||
|
val enterMinimumAmount: BigDecimal?
|
||||||
|
|
||||||
|
val exitMinimumAmount: BigDecimal?
|
||||||
|
|
||||||
|
val enterArgs: Yield.Args.Enter?
|
||||||
|
|
||||||
|
val exitArgs: Yield.Args.Enter?
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
|
||||||
|
val warmupPeriodDays: Int
|
||||||
|
|
||||||
|
val cooldownPeriodDays: Int?
|
||||||
|
|
||||||
|
val rewardSchedule: Yield.Metadata.RewardSchedule
|
||||||
|
|
||||||
|
val rewardClaiming: Yield.Metadata.RewardClaiming
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
|
||||||
|
fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
||||||
import com.tangem.blockchainsdk.utils.toMigratedCoinId
|
import com.tangem.blockchainsdk.utils.toMigratedCoinId
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a staking integration identifier.
|
* Represents a staking integration identifier.
|
||||||
|
|
@ -97,16 +97,14 @@ sealed interface StakingIntegrationID {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents P2P staking integrations
|
* Represents P2PEthPool staking integration
|
||||||
*/
|
*/
|
||||||
enum class P2P : StakingIntegrationID {
|
object P2PEthPool : StakingIntegrationID {
|
||||||
EthereumPooled {
|
override val value: String = "p2p-ethereum-pooled"
|
||||||
override val value: String = "p2p-ethereum-pooled"
|
override val blockchain: Blockchain
|
||||||
override val blockchain: Blockchain
|
get() = if (P2PEthPoolStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||||
get() = if (P2PStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
override val networkId: String
|
||||||
override val networkId: String
|
get() = P2PEthPoolStakingConfig.activeNetwork.stakingNetworkId
|
||||||
get() = P2PStakingConfig.activeNetwork.stakingNetworkId
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Polkadot {
|
// Polkadot {
|
||||||
|
|
@ -138,7 +136,7 @@ sealed interface StakingIntegrationID {
|
||||||
|
|
||||||
/** List of all native staking integration IDs */
|
/** List of all native staking integration IDs */
|
||||||
val entries: List<StakingIntegrationID> by lazy {
|
val entries: List<StakingIntegrationID> by lazy {
|
||||||
StakeKit.Coin.entries + StakeKit.EthereumToken.entries + P2P.entries
|
StakeKit.Coin.entries + StakeKit.EthereumToken.entries + listOf(P2PEthPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -152,9 +150,12 @@ sealed interface StakingIntegrationID {
|
||||||
val blockchain = Blockchain.fromId(id = currencyId.rawNetworkId)
|
val blockchain = Blockchain.fromId(id = currencyId.rawNetworkId)
|
||||||
|
|
||||||
return if (currencyId.contractAddress.isNullOrBlank()) {
|
return if (currencyId.contractAddress.isNullOrBlank()) {
|
||||||
// Order is not important — either P2P or Stakekit.Coin can be in any order
|
// Order is not important — either P2PEthPool or Stakekit.Coin can be in any order
|
||||||
P2P.entries.firstOrNull { it.blockchain == blockchain }
|
if (P2PEthPool.blockchain == blockchain) {
|
||||||
?: StakeKit.Coin.entries.firstOrNull { it.blockchain == blockchain }
|
P2PEthPool
|
||||||
|
} else {
|
||||||
|
StakeKit.Coin.entries.firstOrNull { it.blockchain == blockchain }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
StakeKit.EthereumToken.entries.firstOrNull { token ->
|
StakeKit.EthereumToken.entries.firstOrNull { token ->
|
||||||
token.blockchain == blockchain &&
|
token.blockchain == blockchain &&
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
package com.tangem.domain.staking.model
|
package com.tangem.domain.staking.model
|
||||||
|
|
||||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||||
import com.tangem.domain.models.staking.NetworkType
|
|
||||||
import com.tangem.domain.models.staking.YieldToken
|
import com.tangem.domain.models.staking.YieldToken
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a staking option from any provider
|
* Represents a staking option from any provider
|
||||||
* Unified abstraction over StakeKit and P2P staking integrations
|
* Unified abstraction over StakeKit and P2PEthPool staking integrations
|
||||||
*/
|
*/
|
||||||
sealed interface StakingOption {
|
sealed interface StakingOption {
|
||||||
|
|
||||||
/** Unique identifier for the staking option */
|
/** Unique identifier for the staking option */
|
||||||
val integrationId: String
|
val integrationId: StakingIntegrationID
|
||||||
|
|
||||||
/** Annual Percentage Yield */
|
/** Annual Percentage Yield */
|
||||||
val apy: SerializedBigDecimal
|
val apy: SerializedBigDecimal
|
||||||
|
|
@ -28,34 +27,23 @@ sealed interface StakingOption {
|
||||||
* StakeKit staking option
|
* StakeKit staking option
|
||||||
* Wraps StakeKit Yield with all validator and metadata information
|
* Wraps StakeKit Yield with all validator and metadata information
|
||||||
*/
|
*/
|
||||||
data class StakeKit(val yield: Yield) : StakingOption {
|
data class StakeKit(
|
||||||
override val integrationId: String = yield.id
|
override val integrationId: StakingIntegrationID.StakeKit,
|
||||||
|
val yield: Yield,
|
||||||
|
) : StakingOption {
|
||||||
override val apy: SerializedBigDecimal = yield.apy
|
override val apy: SerializedBigDecimal = yield.apy
|
||||||
override val token: YieldToken = yield.token
|
override val token: YieldToken = yield.token
|
||||||
override val isAvailable: Boolean = yield.isAvailable
|
override val isAvailable: Boolean = yield.isAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P2P pooled staking option
|
* P2PEthPool staking option
|
||||||
* Wraps P2P ETH Pool vault information
|
* Wraps P2PEthPool vault information
|
||||||
*/
|
*/
|
||||||
data class P2P(val vaults: List<P2PEthPoolVault>) : StakingOption {
|
data class P2PEthPool(val vaults: List<P2PEthPoolVault>) : StakingOption {
|
||||||
override val integrationId: String = "p2p-ethereum-pooled"
|
override val integrationId: StakingIntegrationID = StakingIntegrationID.P2PEthPool
|
||||||
override val apy: SerializedBigDecimal = vaults.maxOf { it.apy }
|
override val apy: SerializedBigDecimal = vaults.maxOf { it.apy }
|
||||||
override val token: YieldToken = createEthToken()
|
override val token: YieldToken = YieldToken.ETH
|
||||||
override val isAvailable: Boolean = vaults.isNotEmpty()
|
override val isAvailable: Boolean = vaults.isNotEmpty()
|
||||||
|
|
||||||
private fun createEthToken(): YieldToken { // TODO
|
|
||||||
return YieldToken(
|
|
||||||
name = "Ethereum",
|
|
||||||
network = NetworkType.ETHEREUM,
|
|
||||||
symbol = "ETH",
|
|
||||||
decimals = 18,
|
|
||||||
address = null, // Native token
|
|
||||||
coinGeckoId = "ethereum",
|
|
||||||
logoURI = null,
|
|
||||||
isPoints = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
|
||||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
@ -17,24 +17,24 @@ interface P2PEthPoolRepository {
|
||||||
/**
|
/**
|
||||||
* Fetch and store available staking vaults
|
* Fetch and store available staking vaults
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
*/
|
*/
|
||||||
suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork)
|
suspend fun fetchVaults(network: P2PEthPoolNetwork = P2PEthPoolStakingConfig.activeNetwork)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of available staking vaults
|
* Get list of available staking vaults
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @return Either error or list of vaults with APY, capacity, fees
|
* @return Either error or list of vaults with APY, capacity, fees
|
||||||
*/
|
*/
|
||||||
suspend fun getVaults(
|
suspend fun getVaults(
|
||||||
network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork,
|
network: P2PEthPoolNetwork = P2PEthPoolStakingConfig.activeNetwork,
|
||||||
): Either<StakingError, List<P2PEthPoolVault>>
|
): Either<StakingError, List<P2PEthPoolVault>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create unsigned transaction for depositing ETH into a vault
|
* Create unsigned transaction for depositing ETH into a vault
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @param delegatorAddress User's wallet address
|
* @param delegatorAddress User's wallet address
|
||||||
* @param vaultAddress Vault contract address
|
* @param vaultAddress Vault contract address
|
||||||
* @param amount Amount of ETH to deposit
|
* @param amount Amount of ETH to deposit
|
||||||
|
|
@ -53,7 +53,7 @@ interface P2PEthPoolRepository {
|
||||||
* Unstaking adds funds to exit queue. After ~1-4 days, use [createWithdrawTransaction]
|
* Unstaking adds funds to exit queue. After ~1-4 days, use [createWithdrawTransaction]
|
||||||
* to withdraw the funds.
|
* to withdraw the funds.
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @param stakerPublicKey Staker's public key (note: API doc may have Bitcoin terminology)
|
* @param stakerPublicKey Staker's public key (note: API doc may have Bitcoin terminology)
|
||||||
* @param stakeTransactionHash Original stake transaction hash
|
* @param stakeTransactionHash Original stake transaction hash
|
||||||
* @return Either error or unsigned transaction
|
* @return Either error or unsigned transaction
|
||||||
|
|
@ -69,7 +69,7 @@ interface P2PEthPoolRepository {
|
||||||
*
|
*
|
||||||
* Only works when funds are available (after exit queue wait period).
|
* Only works when funds are available (after exit queue wait period).
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @param stakerAddress User's wallet address
|
* @param stakerAddress User's wallet address
|
||||||
* @return Either error or unsigned transaction with withdrawal tickets
|
* @return Either error or unsigned transaction with withdrawal tickets
|
||||||
*/
|
*/
|
||||||
|
|
@ -81,7 +81,7 @@ interface P2PEthPoolRepository {
|
||||||
/**
|
/**
|
||||||
* Broadcast signed transaction to blockchain
|
* Broadcast signed transaction to blockchain
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @param signedTransaction Signed transaction in hex format (with 0x prefix)
|
* @param signedTransaction Signed transaction in hex format (with 0x prefix)
|
||||||
* @return Either error or broadcast result with transaction hash
|
* @return Either error or broadcast result with transaction hash
|
||||||
*/
|
*/
|
||||||
|
|
@ -95,7 +95,7 @@ interface P2PEthPoolRepository {
|
||||||
*
|
*
|
||||||
* Returns current stake, rewards, exit queue status, and available amounts
|
* Returns current stake, rewards, exit queue status, and available amounts
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @param delegatorAddress User's wallet address
|
* @param delegatorAddress User's wallet address
|
||||||
* @param vaultAddress Vault contract address
|
* @param vaultAddress Vault contract address
|
||||||
* @return Either error or account info
|
* @return Either error or account info
|
||||||
|
|
@ -109,7 +109,7 @@ interface P2PEthPoolRepository {
|
||||||
/**
|
/**
|
||||||
* Get rewards history for account and vault
|
* Get rewards history for account and vault
|
||||||
*
|
*
|
||||||
* @param network P2P network (MAINNET or TESTNET)
|
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||||
* @param delegatorAddress User's wallet address
|
* @param delegatorAddress User's wallet address
|
||||||
* @param vaultAddress Vault contract address
|
* @param vaultAddress Vault contract address
|
||||||
* @param period Optional period filter in days (30, 60, or 90)
|
* @param period Optional period filter in days (30, 60, or 90)
|
||||||
|
|
@ -123,17 +123,27 @@ interface P2PEthPoolRepository {
|
||||||
): Either<StakingError, List<P2PEthPoolReward>>
|
): Either<StakingError, List<P2PEthPoolReward>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check P2P staking availability by finding public vault
|
* Get flow of cached vaults.
|
||||||
*
|
*
|
||||||
* @return Flow of StakingAvailability - Available with StakingOption.P2P if public vault found,
|
* This returns vaults from the local cache/store.
|
||||||
|
* Call [fetchVaults] first to populate the cache from the network.
|
||||||
|
*
|
||||||
|
* @return Flow of cached vaults list
|
||||||
|
*/
|
||||||
|
fun getVaultsFlow(): Flow<List<P2PEthPoolVault>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check P2PEthPool staking availability by finding public vault
|
||||||
|
*
|
||||||
|
* @return Flow of StakingAvailability - Available with StakingOption.P2PEthPool if public vault found,
|
||||||
* TemporaryUnavailable if not found or vaults empty
|
* TemporaryUnavailable if not found or vaults empty
|
||||||
*/
|
*/
|
||||||
fun getStakingAvailability(): Flow<StakingAvailability>
|
fun getStakingAvailability(): Flow<StakingAvailability>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check P2P staking availability synchronously
|
* Check P2PEthPool staking availability synchronously
|
||||||
*
|
*
|
||||||
* @return StakingAvailability - Available with StakingOption.P2P if public vault found,
|
* @return StakingAvailability - Available with StakingOption.P2PEthPool if public vault found,
|
||||||
* TemporaryUnavailable if not found or vaults empty
|
* TemporaryUnavailable if not found or vaults empty
|
||||||
*/
|
*/
|
||||||
suspend fun getStakingAvailabilitySync(): StakingAvailability
|
suspend fun getStakingAvailabilitySync(): StakingAvailability
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.staking.model.StakingAvailability
|
import com.tangem.domain.staking.model.StakingAvailability
|
||||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||||
import com.tangem.domain.models.staking.NetworkType
|
import com.tangem.domain.models.staking.NetworkType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||||
|
|
@ -24,9 +25,17 @@ interface StakeKitRepository {
|
||||||
|
|
||||||
fun getEnabledYields(): Flow<List<Yield>>
|
fun getEnabledYields(): Flow<List<Yield>>
|
||||||
|
|
||||||
fun getStakingAvailability(rawCurrencyId: CryptoCurrency.RawID, symbol: String): Flow<StakingAvailability>
|
fun getStakingAvailability(
|
||||||
|
integrationId: StakingIntegrationID.StakeKit,
|
||||||
|
rawCurrencyId: CryptoCurrency.RawID,
|
||||||
|
symbol: String,
|
||||||
|
): Flow<StakingAvailability>
|
||||||
|
|
||||||
suspend fun getStakingAvailabilitySync(rawCurrencyId: CryptoCurrency.RawID, symbol: String): StakingAvailability
|
suspend fun getStakingAvailabilitySync(
|
||||||
|
integrationId: StakingIntegrationID.StakeKit,
|
||||||
|
rawCurrencyId: CryptoCurrency.RawID,
|
||||||
|
symbol: String,
|
||||||
|
): StakingAvailability
|
||||||
|
|
||||||
suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo
|
suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,50 @@ package com.tangem.domain.staking.usecase
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.utils.toCoinId
|
import com.tangem.blockchainsdk.utils.toCoinId
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
|
import com.tangem.domain.staking.model.toStakingTarget
|
||||||
|
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits a map of Validators values per currency for staking.
|
* Emits a map of StakingTarget values per currency for staking.
|
||||||
*
|
*
|
||||||
* Return map:
|
* Return map:
|
||||||
* - key: currency staking key (network.backendId + "_" + symbol)
|
* - key: currency staking key (coinGeckoId + "_" + symbol)
|
||||||
* - value: validators
|
* - value: list of staking targets (validators or vaults)
|
||||||
*/
|
*/
|
||||||
class StakingApyFlowUseCase(
|
class StakingApyFlowUseCase(
|
||||||
private val stakeKitRepository: StakeKitRepository,
|
private val stakeKitRepository: StakeKitRepository,
|
||||||
|
private val p2pEthPoolRepository: P2PEthPoolRepository,
|
||||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(): Flow<Map<String, List<Yield.Validator>>> {
|
operator fun invoke(): Flow<Map<String, List<StakingTarget>>> {
|
||||||
return stakeKitRepository.getEnabledYields()
|
return combine(
|
||||||
.map { yields ->
|
stakeKitRepository.getEnabledYields(),
|
||||||
yields.filterNot { yield ->
|
p2pEthPoolRepository.getVaultsFlow(),
|
||||||
val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId()
|
) { yields, p2pVaults ->
|
||||||
isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled
|
val stakeKitMap = yields.filterNot { yield ->
|
||||||
}.associate { yield ->
|
val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId()
|
||||||
val key = "${yield.token.coinGeckoId}_${yield.token.symbol}"
|
isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled
|
||||||
val apy = yield.validators
|
}.associate { yield ->
|
||||||
key to apy
|
val key = "${yield.token.coinGeckoId}_${yield.token.symbol}"
|
||||||
}
|
val targets = yield.validators.map { it.toStakingTarget() }
|
||||||
|
key to targets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val p2pMap = if (p2pVaults.isNotEmpty()) {
|
||||||
|
val ethKey = "${Blockchain.Ethereum.toCoinId()}_${Blockchain.Ethereum.currency}"
|
||||||
|
val targets = p2pVaults.map { it.toStakingTarget() }
|
||||||
|
mapOf(ethKey to targets)
|
||||||
|
} else {
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
stakeKitMap + p2pMap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider-agnostic extension to get total balance including rewards.
|
* Provider-agnostic extension to get total balance including rewards.
|
||||||
* Works for both StakeKit and P2P providers.
|
* Works for both StakeKit and P2PEthPool providers.
|
||||||
*
|
*
|
||||||
* Returns sum of all staking-related balances including rewards
|
* Returns sum of all staking-related balances including rewards
|
||||||
* (staked + unstaking + withdrawable + rewards).
|
* (staked + unstaking + withdrawable + rewards).
|
||||||
|
|
@ -18,7 +18,7 @@ import java.math.BigDecimal
|
||||||
fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): BigDecimal {
|
fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): BigDecimal {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is StakingBalance.Data.StakeKit -> getTotalWithRewardsStakingBalanceStakeKit(blockchainId)
|
is StakingBalance.Data.StakeKit -> getTotalWithRewardsStakingBalanceStakeKit(blockchainId)
|
||||||
is StakingBalance.Data.P2P -> {
|
is StakingBalance.Data.P2PEthPool -> {
|
||||||
val rewards = totalRewards
|
val rewards = totalRewards
|
||||||
if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId)) {
|
if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId)) {
|
||||||
totalStaked + unstakingAmount + withdrawableAmount + rewards
|
totalStaked + unstakingAmount + withdrawableAmount + rewards
|
||||||
|
|
@ -31,7 +31,7 @@ fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String):
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider-agnostic extension to get total staking balance excluding rewards.
|
* Provider-agnostic extension to get total staking balance excluding rewards.
|
||||||
* Works for both StakeKit and P2P providers.
|
* Works for both StakeKit and P2PEthPool providers.
|
||||||
*
|
*
|
||||||
* Returns sum of all staking-related balances (staked + unstaking + withdrawable)
|
* Returns sum of all staking-related balances (staked + unstaking + withdrawable)
|
||||||
* excluding rewards.
|
* excluding rewards.
|
||||||
|
|
@ -39,7 +39,7 @@ fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String):
|
||||||
fun StakingBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal {
|
fun StakingBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is StakingBalance.Data.StakeKit -> getTotalStakingBalanceStakeKit(blockchainId)
|
is StakingBalance.Data.StakeKit -> getTotalStakingBalanceStakeKit(blockchainId)
|
||||||
is StakingBalance.Data.P2P -> totalStaked + unstakingAmount + withdrawableAmount
|
is StakingBalance.Data.P2PEthPool -> totalStaked + unstakingAmount + withdrawableAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,8 +149,8 @@ internal class StakingIdFactoryTest {
|
||||||
expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano),
|
expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano),
|
||||||
),
|
),
|
||||||
CreateModel(
|
CreateModel(
|
||||||
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
|
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2PEthPool.blockchain),
|
||||||
expected = createStakingId(integrationId = StakingIntegrationID.P2P.EthereumPooled),
|
expected = createStakingId(integrationId = StakingIntegrationID.P2PEthPool),
|
||||||
),
|
),
|
||||||
CreateModel(
|
CreateModel(
|
||||||
currencyId = CryptoCurrency.ID.fromValue(
|
currencyId = CryptoCurrency.ID.fromValue(
|
||||||
|
|
|
||||||
|
|
@ -38,17 +38,6 @@ class StakingIntegrationIDTest {
|
||||||
Truth.assertThat(actual).hasSize(expected)
|
Truth.assertThat(actual).hasSize(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `all P2P blockchains are unique`() {
|
|
||||||
// Act
|
|
||||||
val actual = StakingIntegrationID.P2P.entries
|
|
||||||
.distinctBy(StakingIntegrationID.P2P::blockchain)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = StakingIntegrationID.P2P.entries.size
|
|
||||||
Truth.assertThat(actual).hasSize(expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `all sub blockchains are unique`() {
|
fun `all sub blockchains are unique`() {
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -151,8 +140,8 @@ class StakingIntegrationIDTest {
|
||||||
expected = StakingIntegrationID.StakeKit.Coin.Cardano,
|
expected = StakingIntegrationID.StakeKit.Coin.Cardano,
|
||||||
),
|
),
|
||||||
CreateModel(
|
CreateModel(
|
||||||
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
|
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2PEthPool.blockchain),
|
||||||
expected = StakingIntegrationID.P2P.EthereumPooled,
|
expected = StakingIntegrationID.P2PEthPool,
|
||||||
),
|
),
|
||||||
CreateModel(
|
CreateModel(
|
||||||
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"),
|
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"),
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ object CryptoCurrencyStatusFactory {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is StakingBalance.Data.P2P -> {
|
is StakingBalance.Data.P2PEthPool -> {
|
||||||
// TODO p2p
|
// TODO p2p
|
||||||
val isCurrentAddressStaking = stakingBalance.stakingId.address == address.defaultAddress.value
|
val isCurrentAddressStaking = stakingBalance.stakingId.address == address.defaultAddress.value
|
||||||
if (isCurrentAddressStaking) stakingBalance else null
|
if (isCurrentAddressStaking) stakingBalance else null
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ dependencies {
|
||||||
implementation(projects.domain.markets)
|
implementation(projects.domain.markets)
|
||||||
implementation(projects.domain.onramp.models)
|
implementation(projects.domain.onramp.models)
|
||||||
implementation(projects.domain.staking.models)
|
implementation(projects.domain.staking.models)
|
||||||
|
implementation(projects.domain.staking)
|
||||||
implementation(projects.domain.tokens)
|
implementation(projects.domain.tokens)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
AppRoute.Staking(
|
AppRoute.Staking(
|
||||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||||
cryptoCurrency = cryptoCurrencyData.status.currency,
|
cryptoCurrency = cryptoCurrencyData.status.currency,
|
||||||
yieldId = option.integrationId,
|
integrationId = option.integrationId,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ dependencies {
|
||||||
|
|
||||||
/** Domain models */
|
/** Domain models */
|
||||||
api(projects.domain.models)
|
api(projects.domain.models)
|
||||||
implementation(projects.domain.staking.models)
|
implementation(projects.domain.staking)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.staking.api
|
package com.tangem.features.staking.api
|
||||||
|
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
|
@ -10,7 +11,7 @@ interface StakingComponent : ComposableContentComponent {
|
||||||
data class Params(
|
data class Params(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val cryptoCurrency: CryptoCurrency,
|
val cryptoCurrency: CryptoCurrency,
|
||||||
val yieldId: String,
|
val integrationId: StakingIntegrationID,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, StakingComponent>
|
interface Factory : ComponentFactory<Params, StakingComponent>
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ internal class StakingAnalyticSender(
|
||||||
|
|
||||||
fun initialInfoScreen(value: StakingUiState) {
|
fun initialInfoScreen(value: StakingUiState) {
|
||||||
val initialInfoState = value.initialInfoState as? StakingStates.InitialInfoState.Data
|
val initialInfoState = value.initialInfoState as? StakingStates.InitialInfoState.Data
|
||||||
val validatorState = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data
|
val balanceState = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data
|
||||||
val validatorCount = validatorState?.balances
|
val validatorCount = balanceState?.balances
|
||||||
?.filterNot { it.validator?.address.isNullOrBlank() }
|
?.filterNot { it.target?.address.isNullOrBlank() }
|
||||||
?.distinctBy { it.validator?.address }
|
?.distinctBy { it.target?.address }
|
||||||
?.size ?: 0
|
?.size ?: 0
|
||||||
|
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
|
|
@ -34,7 +34,7 @@ internal class StakingAnalyticSender(
|
||||||
fun confirmationScreen(value: StakingUiState) {
|
fun confirmationScreen(value: StakingUiState) {
|
||||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||||
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
||||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
val validatorName = validatorState?.chosenTarget?.name ?: return
|
||||||
|
|
||||||
if (confirmationState?.innerState == InnerConfirmationStakingState.COMPLETED) return
|
if (confirmationState?.innerState == InnerConfirmationStakingState.COMPLETED) return
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ internal class StakingAnalyticSender(
|
||||||
|
|
||||||
fun sendTransactionStakingAnalytics(value: StakingUiState, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
fun sendTransactionStakingAnalytics(value: StakingUiState, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||||
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
||||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
val validatorName = validatorState?.chosenTarget?.name ?: return
|
||||||
|
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
Basic.TransactionSent(
|
Basic.TransactionSent(
|
||||||
|
|
@ -102,7 +102,7 @@ internal class StakingAnalyticSender(
|
||||||
|
|
||||||
fun sendTransactionStakingClickedAnalytics(value: StakingUiState) {
|
fun sendTransactionStakingClickedAnalytics(value: StakingUiState) {
|
||||||
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
||||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
val validatorName = validatorState?.chosenTarget?.name ?: return
|
||||||
|
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
StakingAnalyticsEvent.ButtonAction(
|
StakingAnalyticsEvent.ButtonAction(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.tangem.features.staking.impl.deeplink
|
package com.tangem.features.staking.impl.deeplink
|
||||||
|
|
||||||
import arrow.core.getOrElse
|
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.common.routing.deeplink.DeeplinkConst.NETWORK_ID_KEY
|
import com.tangem.common.routing.deeplink.DeeplinkConst.NETWORK_ID_KEY
|
||||||
|
|
@ -8,7 +7,6 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.TOKEN_ID_KEY
|
||||||
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
|
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
||||||
import com.tangem.domain.staking.GetYieldUseCase
|
|
||||||
import com.tangem.domain.staking.model.StakingAvailability
|
import com.tangem.domain.staking.model.StakingAvailability
|
||||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||||
|
|
@ -28,7 +26,6 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor(
|
||||||
private val appRouter: AppRouter,
|
private val appRouter: AppRouter,
|
||||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||||
private val getYieldUseCase: GetYieldUseCase,
|
|
||||||
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
|
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
|
||||||
) : StakingDeepLinkHandler {
|
) : StakingDeepLinkHandler {
|
||||||
|
|
||||||
|
|
@ -73,19 +70,13 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val isStakingEnabled = getStakingAvailabilityUseCase.invokeSync(
|
val availability = getStakingAvailabilityUseCase.invokeSync(
|
||||||
userWalletId = selectedUserWalletId,
|
userWalletId = selectedUserWalletId,
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
).getOrNull()
|
).getOrNull()
|
||||||
|
|
||||||
if (isStakingEnabled !is StakingAvailability.Available) {
|
val option = (availability as? StakingAvailability.Available)?.option
|
||||||
return@launch
|
if (option == null) {
|
||||||
}
|
|
||||||
|
|
||||||
val yield = getYieldUseCase.invoke(
|
|
||||||
cryptoCurrencyId = cryptoCurrency.id,
|
|
||||||
symbol = cryptoCurrency.symbol,
|
|
||||||
).getOrElse {
|
|
||||||
Timber.e("Staking is unavailable for ${cryptoCurrency.name}")
|
Timber.e("Staking is unavailable for ${cryptoCurrency.name}")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +85,7 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor(
|
||||||
AppRoute.Staking(
|
AppRoute.Staking(
|
||||||
userWalletId = selectedUserWalletId,
|
userWalletId = selectedUserWalletId,
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
yieldId = yield.id,
|
integrationId = option.integrationId,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||||
import com.tangem.common.ui.notifications.NotificationUM
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
@ -35,7 +35,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
||||||
|
|
||||||
fun openValidators()
|
fun openValidators()
|
||||||
|
|
||||||
fun onValidatorSelect(validator: Yield.Validator)
|
fun onTargetSelect(target: StakingTarget)
|
||||||
|
|
||||||
fun openRewardsValidators()
|
fun openRewardsValidators()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,15 @@ import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.staking.*
|
import com.tangem.domain.staking.*
|
||||||
import com.tangem.domain.staking.analytics.StakeScreenSource
|
import com.tangem.domain.staking.analytics.StakeScreenSource
|
||||||
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
|
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
|
||||||
|
import com.tangem.domain.staking.model.P2PEthPoolIntegration
|
||||||
|
import com.tangem.domain.staking.model.StakeKitIntegration
|
||||||
import com.tangem.domain.staking.model.StakingApproval
|
import com.tangem.domain.staking.model.StakingApproval
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||||
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
||||||
|
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||||
import com.tangem.domain.staking.utils.getValidatorsCount
|
import com.tangem.domain.staking.utils.getValidatorsCount
|
||||||
|
|
@ -134,6 +138,7 @@ internal class StakingModel @Inject constructor(
|
||||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||||
private val getActionsUseCase: GetActionsUseCase,
|
private val getActionsUseCase: GetActionsUseCase,
|
||||||
private val getYieldUseCase: GetYieldUseCase,
|
private val getYieldUseCase: GetYieldUseCase,
|
||||||
|
private val p2pEthPoolRepository: P2PEthPoolRepository,
|
||||||
private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase,
|
private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase,
|
||||||
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
|
||||||
private val getFeeUseCase: GetFeeUseCase,
|
private val getFeeUseCase: GetFeeUseCase,
|
||||||
|
|
@ -164,9 +169,18 @@ internal class StakingModel @Inject constructor(
|
||||||
|
|
||||||
private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrency.id
|
private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrency.id
|
||||||
private val userWalletId: UserWalletId = params.userWalletId
|
private val userWalletId: UserWalletId = params.userWalletId
|
||||||
private val yield: Yield = runBlocking {
|
private val integration: StakingIntegration = runBlocking {
|
||||||
getYieldUseCase(params.yieldId).getOrElse {
|
when (val integrationId = params.integrationId) {
|
||||||
error("yield must be not null")
|
is StakingIntegrationID.StakeKit -> {
|
||||||
|
val yield = getYieldUseCase(integrationId.value).getOrElse {
|
||||||
|
error("yield must be not null")
|
||||||
|
}
|
||||||
|
StakeKitIntegration(integrationId, yield)
|
||||||
|
}
|
||||||
|
StakingIntegrationID.P2PEthPool -> {
|
||||||
|
val vaults = p2pEthPoolRepository.getVaults().getOrElse { emptyList() }
|
||||||
|
P2PEthPoolIntegration(integrationId, vaults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,7 +208,7 @@ internal class StakingModel @Inject constructor(
|
||||||
return invalidatePendingTransactionsUseCase(
|
return invalidatePendingTransactionsUseCase(
|
||||||
balanceItems = stakeKitBalance?.balance?.items.orEmpty(),
|
balanceItems = stakeKitBalance?.balance?.items.orEmpty(),
|
||||||
stakingActions = stakingActions,
|
stakingActions = stakingActions,
|
||||||
token = yield.token,
|
token = integration.token,
|
||||||
).getOrElse { emptyList() }
|
).getOrElse { emptyList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +219,7 @@ internal class StakingModel @Inject constructor(
|
||||||
stakingBalanceUpdater.create(
|
stakingBalanceUpdater.create(
|
||||||
cryptoCurrencyStatus,
|
cryptoCurrencyStatus,
|
||||||
userWallet,
|
userWallet,
|
||||||
yield,
|
integration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +227,7 @@ internal class StakingModel @Inject constructor(
|
||||||
stakingFeeTransactionLoader.create(
|
stakingFeeTransactionLoader.create(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
userWallet = userWallet,
|
userWallet = userWallet,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,7 +235,7 @@ internal class StakingModel @Inject constructor(
|
||||||
stakingTransactionLoader.create(
|
stakingTransactionLoader.create(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
userWallet = userWallet,
|
userWallet = userWallet,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +294,7 @@ internal class StakingModel @Inject constructor(
|
||||||
val hasNoYieldBalanceData = cryptoCurrencyStatus.value.stakingBalance !is StakingBalance.Data.StakeKit
|
val hasNoYieldBalanceData = cryptoCurrencyStatus.value.stakingBalance !is StakingBalance.Data.StakeKit
|
||||||
|
|
||||||
when {
|
when {
|
||||||
isInitialInfoStep && noBalanceState && yield.allValidatorsFull && hasNoYieldBalanceData -> {
|
isInitialInfoStep && noBalanceState && integration.areAllTargetsFull && hasNoYieldBalanceData -> {
|
||||||
stakingEventFactory.createStakingValidatorsUnavailableAlert()
|
stakingEventFactory.createStakingValidatorsUnavailableAlert()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
@ -293,12 +307,12 @@ internal class StakingModel @Inject constructor(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
stakingApproval = stakingApproval,
|
stakingApproval = stakingApproval,
|
||||||
stakingAllowance = stakingAllowance,
|
stakingAllowance = stakingAllowance,
|
||||||
yieldArgs = yield.args,
|
integration = integration,
|
||||||
).let(::add)
|
).let(::add)
|
||||||
if (yield.args.enter.isPartialAmountDisabled) {
|
if (integration.isPartialAmountDisabled) {
|
||||||
ValidatorSelectChangeTransformer(
|
ValidatorSelectChangeTransformer(
|
||||||
selectedValidator = yield.preferredValidators.firstOrNull(),
|
selectedTarget = integration.preferredTargets.firstOrNull(),
|
||||||
yield = yield,
|
integration = integration,
|
||||||
).let(::add)
|
).let(::add)
|
||||||
SetAmountDataTransformer(
|
SetAmountDataTransformer(
|
||||||
clickIntents = this@StakingModel,
|
clickIntents = this@StakingModel,
|
||||||
|
|
@ -313,7 +327,7 @@ internal class StakingModel @Inject constructor(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
minimumTransactionAmount = minimumTransactionAmount,
|
minimumTransactionAmount = minimumTransactionAmount,
|
||||||
actionType = uiState.value.actionType,
|
actionType = uiState.value.actionType,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
).let(::add)
|
).let(::add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +341,7 @@ internal class StakingModel @Inject constructor(
|
||||||
override fun getFee() {
|
override fun getFee() {
|
||||||
stateController.update(
|
stateController.update(
|
||||||
SetConfirmationStateLoadingTransformer(
|
SetConfirmationStateLoadingTransformer(
|
||||||
yield = yield,
|
integration = integration,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
),
|
),
|
||||||
|
|
@ -479,7 +493,7 @@ internal class StakingModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAmountEnterClick() {
|
override fun onAmountEnterClick() {
|
||||||
if (yield.preferredValidators.isEmpty()) {
|
if (integration.preferredTargets.isEmpty()) {
|
||||||
stateController.updateEvent(
|
stateController.updateEvent(
|
||||||
StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators),
|
StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators),
|
||||||
)
|
)
|
||||||
|
|
@ -487,8 +501,8 @@ internal class StakingModel @Inject constructor(
|
||||||
if (uiState.value.actionType is StakingActionCommonType.Enter) {
|
if (uiState.value.actionType is StakingActionCommonType.Enter) {
|
||||||
stateController.updateAll(
|
stateController.updateAll(
|
||||||
ValidatorSelectChangeTransformer(
|
ValidatorSelectChangeTransformer(
|
||||||
selectedValidator = null,
|
selectedTarget = null,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -502,7 +516,7 @@ internal class StakingModel @Inject constructor(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
minimumTransactionAmount = minimumTransactionAmount,
|
minimumTransactionAmount = minimumTransactionAmount,
|
||||||
value = value,
|
value = value,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -518,7 +532,7 @@ internal class StakingModel @Inject constructor(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
minimumTransactionAmount = minimumTransactionAmount,
|
minimumTransactionAmount = minimumTransactionAmount,
|
||||||
actionType = uiState.value.actionType,
|
actionType = uiState.value.actionType,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -539,16 +553,16 @@ internal class StakingModel @Inject constructor(
|
||||||
stakingStateRouter.showValidators()
|
stakingStateRouter.showValidators()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onValidatorSelect(validator: Yield.Validator) {
|
override fun onTargetSelect(target: StakingTarget) {
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
StakingAnalyticsEvent.ValidatorChosen(
|
StakingAnalyticsEvent.ValidatorChosen(
|
||||||
validator = validator.name,
|
validator = target.name,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
stateController.update(
|
stateController.update(
|
||||||
ValidatorSelectChangeTransformer(
|
ValidatorSelectChangeTransformer(
|
||||||
selectedValidator = validator,
|
selectedTarget = target,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -606,7 +620,7 @@ internal class StakingModel @Inject constructor(
|
||||||
balanceType = activeStake.type,
|
balanceType = activeStake.type,
|
||||||
pendingActions = activeStake.pendingActions,
|
pendingActions = activeStake.pendingActions,
|
||||||
balanceState = activeStake,
|
balanceState = activeStake,
|
||||||
validator = activeStake.validator,
|
target = activeStake.target,
|
||||||
amountValue = activeStake.cryptoValue,
|
amountValue = activeStake.cryptoValue,
|
||||||
)
|
)
|
||||||
onNextClick(activeStake)
|
onNextClick(activeStake)
|
||||||
|
|
@ -619,7 +633,7 @@ internal class StakingModel @Inject constructor(
|
||||||
balanceType = activeStake.type,
|
balanceType = activeStake.type,
|
||||||
pendingAction = action,
|
pendingAction = action,
|
||||||
balanceState = activeStake,
|
balanceState = activeStake,
|
||||||
validator = activeStake.validator,
|
target = activeStake.target,
|
||||||
amountValue = activeStake.cryptoValue,
|
amountValue = activeStake.cryptoValue,
|
||||||
)
|
)
|
||||||
stateController.update(DismissBottomSheetStateTransformer)
|
stateController.update(DismissBottomSheetStateTransformer)
|
||||||
|
|
@ -788,7 +802,7 @@ internal class StakingModel @Inject constructor(
|
||||||
isSubtractAvailable = isAmountSubtractAvailable,
|
isSubtractAvailable = isAmountSubtractAvailable,
|
||||||
feeError = feeError,
|
feeError = feeError,
|
||||||
stakingError = stakingError,
|
stakingError = stakingError,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -911,7 +925,7 @@ internal class StakingModel @Inject constructor(
|
||||||
val validatorState = uiState.value.validatorState as? StakingStates.ValidatorState.Data
|
val validatorState = uiState.value.validatorState as? StakingStates.ValidatorState.Data
|
||||||
val feeState = confirmationState?.feeState as? FeeState.Content
|
val feeState = confirmationState?.feeState as? FeeState.Content
|
||||||
|
|
||||||
val validator = validatorState?.chosenValidator
|
val target = validatorState?.chosenTarget
|
||||||
val feeAmount = feeState?.fee?.amount
|
val feeAmount = feeState?.fee?.amount
|
||||||
val amount = amountState?.amountTextField?.cryptoAmount
|
val amount = amountState?.amountTextField?.cryptoAmount
|
||||||
saveBlockchainErrorUseCase(
|
saveBlockchainErrorUseCase(
|
||||||
|
|
@ -919,7 +933,7 @@ internal class StakingModel @Inject constructor(
|
||||||
errorMessage = errorMessage,
|
errorMessage = errorMessage,
|
||||||
blockchainId = network.rawId,
|
blockchainId = network.rawId,
|
||||||
derivationPath = network.derivationPath.value,
|
derivationPath = network.derivationPath.value,
|
||||||
destinationAddress = validator?.address.orEmpty(),
|
destinationAddress = target?.address.orEmpty(),
|
||||||
tokenSymbol = (cryptoCurrencyStatus.currency as? CryptoCurrency.Token)?.symbol,
|
tokenSymbol = (cryptoCurrencyStatus.currency as? CryptoCurrency.Token)?.symbol,
|
||||||
amount = amount?.run { value?.toPlainString() + currencySymbol }.orEmpty(),
|
amount = amount?.run { value?.toPlainString() + currencySymbol }.orEmpty(),
|
||||||
fee = feeAmount?.run { value?.toPlainString() + currencySymbol }.orEmpty(),
|
fee = feeAmount?.run { value?.toPlainString() + currencySymbol }.orEmpty(),
|
||||||
|
|
@ -928,7 +942,7 @@ internal class StakingModel @Inject constructor(
|
||||||
|
|
||||||
val email = FeedbackEmailType.StakingProblem(
|
val email = FeedbackEmailType.StakingProblem(
|
||||||
walletMetaInfo = metaInfo,
|
walletMetaInfo = metaInfo,
|
||||||
validatorName = validator?.name,
|
validatorName = target?.name,
|
||||||
transactionTypes = transactionsInProgress.map { it.type.name },
|
transactionTypes = transactionsInProgress.map { it.type.name },
|
||||||
unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction },
|
unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction },
|
||||||
)
|
)
|
||||||
|
|
@ -1229,7 +1243,7 @@ internal class StakingModel @Inject constructor(
|
||||||
stateController.updateAll(
|
stateController.updateAll(
|
||||||
SetInitialDataStateTransformer(
|
SetInitialDataStateTransformer(
|
||||||
clickIntents = this@StakingModel,
|
clickIntents = this@StakingModel,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
isAnyTokenStaked = isAnyTokenStaked,
|
isAnyTokenStaked = isAnyTokenStaked,
|
||||||
cryptoCurrencyStatus = status,
|
cryptoCurrencyStatus = status,
|
||||||
userWalletProvider = Provider { userWallet },
|
userWalletProvider = Provider { userWallet },
|
||||||
|
|
@ -1248,7 +1262,7 @@ internal class StakingModel @Inject constructor(
|
||||||
balanceState: BalanceState,
|
balanceState: BalanceState,
|
||||||
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
|
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
|
||||||
pendingAction: PendingAction? = pendingActions.firstOrNull(),
|
pendingAction: PendingAction? = pendingActions.firstOrNull(),
|
||||||
validator: Yield.Validator?,
|
target: StakingTarget?,
|
||||||
amountValue: String,
|
amountValue: String,
|
||||||
) {
|
) {
|
||||||
stateController.updateAll(
|
stateController.updateAll(
|
||||||
|
|
@ -1261,11 +1275,11 @@ internal class StakingModel @Inject constructor(
|
||||||
pendingActions = pendingActions,
|
pendingActions = pendingActions,
|
||||||
pendingAction = pendingAction,
|
pendingAction = pendingAction,
|
||||||
stakingAllowance = stakingAllowance,
|
stakingAllowance = stakingAllowance,
|
||||||
yieldArgs = yield.args,
|
integration = integration,
|
||||||
),
|
),
|
||||||
ValidatorSelectChangeTransformer(
|
ValidatorSelectChangeTransformer(
|
||||||
selectedValidator = validator,
|
selectedTarget = target,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
SetAmountDataTransformer(
|
SetAmountDataTransformer(
|
||||||
clickIntents = this,
|
clickIntents = this,
|
||||||
|
|
@ -1280,7 +1294,7 @@ internal class StakingModel @Inject constructor(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
value = amountValue,
|
value = amountValue,
|
||||||
minimumTransactionAmount = minimumTransactionAmount,
|
minimumTransactionAmount = minimumTransactionAmount,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.PendingAction
|
import com.tangem.domain.models.staking.PendingAction
|
||||||
import com.tangem.domain.models.staking.PendingActionConstraints
|
import com.tangem.domain.models.staking.PendingActionConstraints
|
||||||
import com.tangem.domain.models.staking.RewardBlockType
|
import com.tangem.domain.models.staking.RewardBlockType
|
||||||
import com.tangem.domain.staking.model.stakekit.*
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
|
@ -35,10 +35,10 @@ internal data class BalanceState(
|
||||||
val fiatAmount: BigDecimal?,
|
val fiatAmount: BigDecimal?,
|
||||||
val formattedFiatAmount: TextReference,
|
val formattedFiatAmount: TextReference,
|
||||||
val rawCurrencyId: String?,
|
val rawCurrencyId: String?,
|
||||||
val validator: Yield.Validator?,
|
val target: StakingTarget?,
|
||||||
val pendingActions: ImmutableList<PendingAction>,
|
val pendingActions: ImmutableList<PendingAction>,
|
||||||
val isPending: Boolean,
|
val isPending: Boolean,
|
||||||
val validatorAddress: String?,
|
val targetAddress: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import com.tangem.core.ui.event.StateEvent
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||||
import com.tangem.domain.models.staking.PendingAction
|
import com.tangem.domain.models.staking.PendingAction
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||||
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
|
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
|
||||||
|
|
@ -97,9 +97,9 @@ internal sealed class StakingStates {
|
||||||
override val isPrimaryButtonEnabled: Boolean,
|
override val isPrimaryButtonEnabled: Boolean,
|
||||||
override val isClickable: Boolean,
|
override val isClickable: Boolean,
|
||||||
val isVisibleOnConfirmation: Boolean,
|
val isVisibleOnConfirmation: Boolean,
|
||||||
val chosenValidator: Yield.Validator,
|
val chosenTarget: StakingTarget,
|
||||||
val activeValidator: Yield.Validator?,
|
val activeTarget: StakingTarget?,
|
||||||
val availableValidators: List<Yield.Validator>,
|
val availableTargets: List<StakingTarget>,
|
||||||
) : ValidatorState()
|
) : ValidatorState()
|
||||||
|
|
||||||
data class Empty(
|
data class Empty(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.BalanceType.Companion.isClickable
|
import com.tangem.domain.models.staking.BalanceType.Companion.isClickable
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.models.staking.action.StakingActionType
|
import com.tangem.domain.models.staking.action.StakingActionType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.utils.getRewardStakingBalance
|
import com.tangem.domain.staking.utils.getRewardStakingBalance
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
|
|
@ -29,24 +29,24 @@ import java.util.Calendar
|
||||||
internal class BalanceItemConverter(
|
internal class BalanceItemConverter(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
) : Converter<BalanceItem, BalanceState?> {
|
) : Converter<BalanceItem, BalanceState?> {
|
||||||
|
|
||||||
override fun convert(value: BalanceItem): BalanceState? {
|
override fun convert(value: BalanceItem): BalanceState? {
|
||||||
val appCurrency = appCurrencyProvider()
|
val appCurrency = appCurrencyProvider()
|
||||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||||
|
|
||||||
val validator = yield.validators.firstOrNull {
|
val target = integration.targets.firstOrNull {
|
||||||
value.validatorAddress?.contains(it.address, ignoreCase = true) == true
|
value.validatorAddress?.contains(it.address, ignoreCase = true) == true
|
||||||
}
|
}
|
||||||
val cryptoAmount = value.getBalanceValue()
|
val cryptoAmount = value.getBalanceValue()
|
||||||
val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount)
|
val fiatAmount = cryptoCurrencyStatus.value.fiatRate?.times(cryptoAmount)
|
||||||
|
|
||||||
val title = value.type.getTitle(validator?.name)
|
val title = value.type.getTitle(target?.name)
|
||||||
return title?.let {
|
return title?.let {
|
||||||
BalanceState(
|
BalanceState(
|
||||||
groupId = value.groupId,
|
groupId = value.groupId,
|
||||||
validator = validator,
|
target = target,
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = getSubtitle(value),
|
subtitle = getSubtitle(value),
|
||||||
type = value.type,
|
type = value.type,
|
||||||
|
|
@ -68,7 +68,7 @@ internal class BalanceItemConverter(
|
||||||
pendingActions = value.pendingActions.toPersistentList(),
|
pendingActions = value.pendingActions.toPersistentList(),
|
||||||
isClickable = value.isClickable(),
|
isClickable = value.isClickable(),
|
||||||
isPending = value.isPending,
|
isPending = value.isPending,
|
||||||
validatorAddress = value.validatorAddress,
|
targetAddress = value.validatorAddress,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +108,7 @@ internal class BalanceItemConverter(
|
||||||
resourceReference(R.string.staking_tap_to_unlock)
|
resourceReference(R.string.staking_tap_to_unlock)
|
||||||
}
|
}
|
||||||
BalanceType.PREPARING -> {
|
BalanceType.PREPARING -> {
|
||||||
val warmupPeriod = yield.metadata.warmupPeriod.days
|
val warmupPeriod = integration.warmupPeriodDays
|
||||||
combinedReference(
|
combinedReference(
|
||||||
resourceReference(R.string.staking_details_warmup_period),
|
resourceReference(R.string.staking_details_warmup_period),
|
||||||
stringReference(" "),
|
stringReference(" "),
|
||||||
|
|
@ -124,7 +124,7 @@ internal class BalanceItemConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUnbondingDate(date: Instant?): TextReference? {
|
private fun getUnbondingDate(date: Instant?): TextReference? {
|
||||||
val unbondingPeriod = yield.metadata.cooldownPeriod?.days ?: return null
|
val unbondingPeriod = integration.cooldownPeriodDays ?: return null
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
return combinedReference(
|
return combinedReference(
|
||||||
resourceReference(R.string.staking_details_unbonding_period),
|
resourceReference(R.string.staking_details_unbonding_period),
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.staking.BalanceItem
|
import com.tangem.domain.models.staking.BalanceItem
|
||||||
import com.tangem.domain.models.staking.BalanceType
|
import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||||
import com.tangem.utils.Provider
|
import com.tangem.utils.Provider
|
||||||
|
|
@ -21,7 +22,7 @@ import java.math.BigDecimal
|
||||||
internal class RewardsValidatorStateConverter(
|
internal class RewardsValidatorStateConverter(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
) : Converter<Unit, StakingStates.RewardsValidatorsState> {
|
) : Converter<Unit, StakingStates.RewardsValidatorsState> {
|
||||||
override fun convert(value: Unit): StakingStates.RewardsValidatorsState {
|
override fun convert(value: Unit): StakingStates.RewardsValidatorsState {
|
||||||
val stakingBalance = cryptoCurrencyStatus.value.stakingBalance
|
val stakingBalance = cryptoCurrencyStatus.value.stakingBalance
|
||||||
|
|
@ -42,13 +43,13 @@ internal class RewardsValidatorStateConverter(
|
||||||
|
|
||||||
private fun List<BalanceItem>.mapRewardBalances(cryptoCurrencyStatus: CryptoCurrencyStatus) =
|
private fun List<BalanceItem>.mapRewardBalances(cryptoCurrencyStatus: CryptoCurrencyStatus) =
|
||||||
this.mapNotNull { balance ->
|
this.mapNotNull { balance ->
|
||||||
val validator = yield.validators.firstOrNull {
|
val target = integration.targets.firstOrNull {
|
||||||
it.address.contains(balance.validatorAddress.orEmpty(), ignoreCase = true)
|
it.address.contains(balance.validatorAddress.orEmpty(), ignoreCase = true)
|
||||||
}
|
}
|
||||||
val cryptoValue = balance.amount
|
val cryptoValue = balance.amount
|
||||||
val fiatValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoValue)
|
val fiatValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoValue)
|
||||||
|
|
||||||
validator?.toBalanceState(
|
target?.toBalanceState(
|
||||||
balance = balance,
|
balance = balance,
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
cryptoValue = cryptoValue,
|
cryptoValue = cryptoValue,
|
||||||
|
|
@ -56,7 +57,7 @@ internal class RewardsValidatorStateConverter(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Yield.Validator.toBalanceState(
|
private fun StakingTarget.toBalanceState(
|
||||||
balance: BalanceItem,
|
balance: BalanceItem,
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
cryptoValue: BigDecimal,
|
cryptoValue: BigDecimal,
|
||||||
|
|
@ -80,7 +81,7 @@ internal class RewardsValidatorStateConverter(
|
||||||
|
|
||||||
return BalanceState(
|
return BalanceState(
|
||||||
groupId = balance.groupId,
|
groupId = balance.groupId,
|
||||||
validator = this,
|
target = this,
|
||||||
title = stringReference(this.name),
|
title = stringReference(this.name),
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals),
|
cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals),
|
||||||
|
|
@ -93,7 +94,7 @@ internal class RewardsValidatorStateConverter(
|
||||||
isClickable = true,
|
isClickable = true,
|
||||||
type = balance.type,
|
type = balance.type,
|
||||||
isPending = balance.isPending,
|
isPending = balance.isPending,
|
||||||
validatorAddress = balance.validatorAddress,
|
targetAddress = balance.validatorAddress,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.RewardBlockType
|
import com.tangem.domain.models.staking.RewardBlockType
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.models.staking.action.StakingActionType
|
import com.tangem.domain.models.staking.action.StakingActionType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.utils.getRewardStakingBalance
|
import com.tangem.domain.staking.utils.getRewardStakingBalance
|
||||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||||
import com.tangem.features.staking.impl.presentation.state.YieldReward
|
import com.tangem.features.staking.impl.presentation.state.YieldReward
|
||||||
|
|
@ -25,11 +25,11 @@ internal class YieldBalancesConverter(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||||
private val balancesToShowProvider: Provider<List<BalanceItem>>,
|
private val balancesToShowProvider: Provider<List<BalanceItem>>,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
) : Converter<Unit, InnerYieldBalanceState> {
|
) : Converter<Unit, InnerYieldBalanceState> {
|
||||||
|
|
||||||
private val balanceItemConverter by lazy(LazyThreadSafetyMode.NONE) {
|
private val balanceItemConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
BalanceItemConverter(cryptoCurrencyStatus, appCurrencyProvider, yield)
|
BalanceItemConverter(cryptoCurrencyStatus, appCurrencyProvider, integration)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun convert(value: Unit): InnerYieldBalanceState {
|
override fun convert(value: Unit): InnerYieldBalanceState {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.staking.FetchActionsUseCase
|
import com.tangem.domain.staking.FetchActionsUseCase
|
||||||
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
|
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||||
|
|
@ -27,7 +27,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
||||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||||
@Assisted private val userWallet: UserWallet,
|
@Assisted private val userWallet: UserWallet,
|
||||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
@Assisted private val yield: Yield,
|
@Assisted private val integration: StakingIntegration,
|
||||||
) {
|
) {
|
||||||
fun updateAfterTransaction() {
|
fun updateAfterTransaction() {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
|
|
@ -105,7 +105,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
||||||
fetchActionsUseCase(
|
fetchActionsUseCase(
|
||||||
userWalletId = userWallet.walletId,
|
userWalletId = userWallet.walletId,
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
networkType = yield.token.network,
|
networkType = integration.token.network,
|
||||||
stakingActionStatus = StakingActionStatus.PROCESSING,
|
stakingActionStatus = StakingActionStatus.PROCESSING,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +115,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
||||||
fun create(
|
fun create(
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
yield: Yield,
|
integration: StakingIntegration,
|
||||||
): StakingBalanceUpdater
|
): StakingBalanceUpdater
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.staking.PendingAction
|
import com.tangem.domain.models.staking.PendingAction
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.staking.EstimateGasUseCase
|
import com.tangem.domain.staking.EstimateGasUseCase
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||||
import com.tangem.domain.tokens.model.staking.getCurrentToken
|
|
||||||
import com.tangem.domain.transaction.error.GetFeeError
|
import com.tangem.domain.transaction.error.GetFeeError
|
||||||
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
||||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||||
|
|
@ -45,7 +44,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
||||||
private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase,
|
private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase,
|
||||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
@Assisted private val userWallet: UserWallet,
|
@Assisted private val userWallet: UserWallet,
|
||||||
@Assisted private val yield: Yield,
|
@Assisted private val integration: StakingIntegration,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getFee(
|
suspend fun getFee(
|
||||||
|
|
@ -58,9 +57,9 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
||||||
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
|
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
|
||||||
?: error("Illegal state")
|
?: error("Illegal state")
|
||||||
|
|
||||||
val validatorAddress = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator?.address
|
val validatorAddress = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenTarget?.address
|
||||||
?: state.balanceState?.validatorAddress
|
?: state.balanceState?.targetAddress
|
||||||
?: error("No validator address provided")
|
?: error("No target address provided")
|
||||||
|
|
||||||
val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||||
?: error("No amount provided")
|
?: error("No amount provided")
|
||||||
|
|
@ -170,11 +169,11 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
||||||
network = cryptoCurrencyStatus.currency.network,
|
network = cryptoCurrencyStatus.currency.network,
|
||||||
params = ActionParams(
|
params = ActionParams(
|
||||||
actionCommonType = stateController.value.actionType,
|
actionCommonType = stateController.value.actionType,
|
||||||
integrationId = yield.id,
|
integrationId = integration.integrationId.value,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
address = sourceAddress,
|
address = sourceAddress,
|
||||||
validatorAddress = validatorAddress,
|
validatorAddress = validatorAddress,
|
||||||
token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId),
|
token = integration.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId),
|
||||||
passthrough = action?.passthrough,
|
passthrough = action?.passthrough,
|
||||||
type = action?.type,
|
type = action?.type,
|
||||||
),
|
),
|
||||||
|
|
@ -234,7 +233,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
||||||
fun create(
|
fun create(
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
yield: Yield,
|
integration: StakingIntegration,
|
||||||
): StakingFeeTransactionLoader
|
): StakingFeeTransactionLoader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,15 +13,14 @@ import com.tangem.domain.staking.GetConstructedStakingTransactionUseCase
|
||||||
import com.tangem.domain.staking.GetStakingTransactionsUseCase
|
import com.tangem.domain.staking.GetStakingTransactionsUseCase
|
||||||
import com.tangem.domain.staking.SaveUnsubmittedHashUseCase
|
import com.tangem.domain.staking.SaveUnsubmittedHashUseCase
|
||||||
import com.tangem.domain.staking.SubmitHashUseCase
|
import com.tangem.domain.staking.SubmitHashUseCase
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.SubmitHashData
|
import com.tangem.domain.staking.model.SubmitHashData
|
||||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionStatus
|
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionStatus
|
||||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
|
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
|
||||||
import com.tangem.domain.tokens.model.staking.getCurrentToken
|
|
||||||
import com.tangem.domain.transaction.error.SendTransactionError
|
import com.tangem.domain.transaction.error.SendTransactionError
|
||||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||||
|
|
@ -56,12 +55,12 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
||||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
@Assisted private val userWallet: UserWallet,
|
@Assisted private val userWallet: UserWallet,
|
||||||
@Assisted private val yield: Yield,
|
@Assisted private val integration: StakingIntegration,
|
||||||
@Assisted private val isAmountSubtractAvailable: Boolean,
|
@Assisted private val isAmountSubtractAvailable: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val balanceUpdater: StakingBalanceUpdater
|
private val balanceUpdater: StakingBalanceUpdater
|
||||||
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield)
|
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, integration)
|
||||||
|
|
||||||
suspend fun constructAndSendTransactions(
|
suspend fun constructAndSendTransactions(
|
||||||
onConstructSuccess: (List<StakingTransaction>) -> Unit,
|
onConstructSuccess: (List<StakingTransaction>) -> Unit,
|
||||||
|
|
@ -193,7 +192,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
||||||
val amountState = state.amountState as? AmountState.Data
|
val amountState = state.amountState as? AmountState.Data
|
||||||
?: error("No amount provided")
|
?: error("No amount provided")
|
||||||
|
|
||||||
val validatorAddress = validatorState.chosenValidator.address
|
val validatorAddress = validatorState.chosenTarget.address
|
||||||
val amount = getAmount(amountState, fee, confirmationState.reduceAmountBy)
|
val amount = getAmount(amountState, fee, confirmationState.reduceAmountBy)
|
||||||
|
|
||||||
return getStakingTransactionsUseCase(
|
return getStakingTransactionsUseCase(
|
||||||
|
|
@ -201,11 +200,11 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
||||||
network = cryptoCurrencyStatus.currency.network,
|
network = cryptoCurrencyStatus.currency.network,
|
||||||
params = ActionParams(
|
params = ActionParams(
|
||||||
actionCommonType = state.actionType,
|
actionCommonType = state.actionType,
|
||||||
integrationId = yield.id,
|
integrationId = integration.integrationId.value,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
address = defaultAddress,
|
address = defaultAddress,
|
||||||
validatorAddress = validatorAddress,
|
validatorAddress = validatorAddress,
|
||||||
token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId),
|
token = integration.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId),
|
||||||
passthrough = action?.passthrough,
|
passthrough = action?.passthrough,
|
||||||
type = action?.type,
|
type = action?.type,
|
||||||
),
|
),
|
||||||
|
|
@ -306,7 +305,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
||||||
fun create(
|
fun create(
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
yield: Yield,
|
integration: StakingIntegration,
|
||||||
isAmountSubtractAvailable: Boolean,
|
isAmountSubtractAvailable: Boolean,
|
||||||
): StakingTransactionSender
|
): StakingTransactionSender
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.domain.models.staking.BalanceType
|
import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.RewardBlockType
|
import com.tangem.domain.models.staking.RewardBlockType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
|
import com.tangem.domain.staking.model.toStakingTarget
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState
|
||||||
|
|
@ -81,7 +82,7 @@ internal object InitialStakingStatePreview {
|
||||||
fiatAmount = null,
|
fiatAmount = null,
|
||||||
formattedFiatAmount = stringReference("100 $"),
|
formattedFiatAmount = stringReference("100 $"),
|
||||||
rawCurrencyId = null,
|
rawCurrencyId = null,
|
||||||
validator = Yield.Validator(
|
target = Yield.Validator(
|
||||||
address = "address",
|
address = "address",
|
||||||
status = Yield.Validator.ValidatorStatus.ACTIVE,
|
status = Yield.Validator.ValidatorStatus.ACTIVE,
|
||||||
name = "Binance",
|
name = "Binance",
|
||||||
|
|
@ -92,13 +93,13 @@ internal object InitialStakingStatePreview {
|
||||||
votingPower = null,
|
votingPower = null,
|
||||||
preferred = false,
|
preferred = false,
|
||||||
isStrategicPartner = false,
|
isStrategicPartner = false,
|
||||||
),
|
).toStakingTarget(),
|
||||||
pendingActions = persistentListOf(),
|
pendingActions = persistentListOf(),
|
||||||
isClickable = true,
|
isClickable = true,
|
||||||
type = BalanceType.STAKED,
|
type = BalanceType.STAKED,
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
isPending = false,
|
isPending = false,
|
||||||
validatorAddress = "",
|
targetAddress = "",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
package com.tangem.features.staking.impl.presentation.state.previewdata
|
package com.tangem.features.staking.impl.presentation.state.previewdata
|
||||||
|
|
||||||
|
import com.tangem.domain.staking.model.common.RewardInfo
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
|
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
|
||||||
|
import com.tangem.domain.staking.model.toStakingTarget
|
||||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
|
@ -13,9 +17,9 @@ internal object ValidatorStatePreviewData {
|
||||||
status = ValidatorStatus.ACTIVE,
|
status = ValidatorStatus.ACTIVE,
|
||||||
name = "Luganodes",
|
name = "Luganodes",
|
||||||
image = "https://assets.stakek.it/validators/luganodes.png",
|
image = "https://assets.stakek.it/validators/luganodes.png",
|
||||||
rewardInfo = Yield.RewardInfo(
|
rewardInfo = RewardInfo(
|
||||||
rate = BigDecimal("0.054823398040640445"),
|
rate = BigDecimal("0.054823398040640445"),
|
||||||
type = Yield.RewardType.APR,
|
type = RewardType.APR,
|
||||||
),
|
),
|
||||||
commission = 0.1,
|
commission = 0.1,
|
||||||
stakedBalance = "355544384.45009977",
|
stakedBalance = "355544384.45009977",
|
||||||
|
|
@ -29,9 +33,9 @@ internal object ValidatorStatePreviewData {
|
||||||
status = ValidatorStatus.ACTIVE,
|
status = ValidatorStatus.ACTIVE,
|
||||||
name = "InfStones",
|
name = "InfStones",
|
||||||
image = "https://assets.stakek.it/validators/infstones.png",
|
image = "https://assets.stakek.it/validators/infstones.png",
|
||||||
rewardInfo = Yield.RewardInfo(
|
rewardInfo = RewardInfo(
|
||||||
rate = BigDecimal("0.057786472172836965"),
|
rate = BigDecimal("0.057786472172836965"),
|
||||||
type = Yield.RewardType.APR,
|
type = RewardType.APR,
|
||||||
),
|
),
|
||||||
commission = 0.05,
|
commission = 0.05,
|
||||||
stakedBalance = "12495684.05643019",
|
stakedBalance = "12495684.05643019",
|
||||||
|
|
@ -45,9 +49,9 @@ internal object ValidatorStatePreviewData {
|
||||||
status = ValidatorStatus.ACTIVE,
|
status = ValidatorStatus.ACTIVE,
|
||||||
name = "Kiln",
|
name = "Kiln",
|
||||||
image = "https://assets.stakek.it/validators/kiln.png",
|
image = "https://assets.stakek.it/validators/kiln.png",
|
||||||
rewardInfo = Yield.RewardInfo(
|
rewardInfo = RewardInfo(
|
||||||
rate = BigDecimal("0.057786472172836965"),
|
rate = BigDecimal("0.057786472172836965"),
|
||||||
type = Yield.RewardType.APR,
|
type = RewardType.APR,
|
||||||
),
|
),
|
||||||
commission = 0.05,
|
commission = 0.05,
|
||||||
stakedBalance = "85400369.96393165",
|
stakedBalance = "85400369.96393165",
|
||||||
|
|
@ -58,11 +62,13 @@ internal object ValidatorStatePreviewData {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val targetList: List<StakingTarget> = validatorList.map { it.toStakingTarget() }
|
||||||
|
|
||||||
val validatorState = StakingStates.ValidatorState.Data(
|
val validatorState = StakingStates.ValidatorState.Data(
|
||||||
availableValidators = validatorList,
|
availableTargets = targetList,
|
||||||
chosenValidator = validatorList.first(),
|
chosenTarget = targetList.first(),
|
||||||
isPrimaryButtonEnabled = true,
|
isPrimaryButtonEnabled = true,
|
||||||
activeValidator = null,
|
activeTarget = null,
|
||||||
isClickable = true,
|
isClickable = true,
|
||||||
isVisibleOnConfirmation = true,
|
isVisibleOnConfirmation = true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.stub
|
||||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||||
import com.tangem.common.ui.notifications.NotificationUM
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||||
|
|
@ -40,7 +40,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
||||||
|
|
||||||
override fun openValidators() {}
|
override fun openValidators() {}
|
||||||
|
|
||||||
override fun onValidatorSelect(validator: Yield.Validator) {}
|
override fun onTargetSelect(target: StakingTarget) {}
|
||||||
|
|
||||||
override fun openRewardsValidators() {}
|
override fun openRewardsValidators() {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.staking.PendingAction
|
import com.tangem.domain.models.staking.PendingAction
|
||||||
import com.tangem.domain.models.staking.action.StakingActionType
|
import com.tangem.domain.models.staking.action.StakingActionType
|
||||||
import com.tangem.domain.staking.model.StakingApproval
|
import com.tangem.domain.staking.model.StakingApproval
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.features.staking.impl.presentation.state.*
|
import com.tangem.features.staking.impl.presentation.state.*
|
||||||
import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions
|
import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions
|
||||||
|
|
@ -24,7 +24,7 @@ internal class SetConfirmationStateInitTransformer(
|
||||||
private val stakingApproval: StakingApproval,
|
private val stakingApproval: StakingApproval,
|
||||||
private val stakingAllowance: BigDecimal,
|
private val stakingAllowance: BigDecimal,
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val yieldArgs: Yield.Args,
|
private val integration: StakingIntegration,
|
||||||
private val pendingActions: ImmutableList<PendingAction>? = null,
|
private val pendingActions: ImmutableList<PendingAction>? = null,
|
||||||
private val pendingAction: PendingAction? = pendingActions?.firstOrNull(),
|
private val pendingAction: PendingAction? = pendingActions?.firstOrNull(),
|
||||||
) : Transformer<StakingUiState> {
|
) : Transformer<StakingUiState> {
|
||||||
|
|
@ -66,7 +66,7 @@ internal class SetConfirmationStateInitTransformer(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getActionType(prevState: StakingUiState): StakingActionCommonType {
|
private fun getActionType(prevState: StakingUiState): StakingActionCommonType {
|
||||||
val isPartialEnterAmountDisabled = yieldArgs.enter.isPartialAmountDisabled
|
val isPartialEnterAmountDisabled = integration.isPartialAmountDisabled
|
||||||
val isPartialExitAmountDisabled = isPartiallyUnstakeDisabled(prevState)
|
val isPartialExitAmountDisabled = isPartiallyUnstakeDisabled(prevState)
|
||||||
return when {
|
return when {
|
||||||
isEnter -> StakingActionCommonType.Enter(isPartialEnterAmountDisabled)
|
isEnter -> StakingActionCommonType.Enter(isPartialEnterAmountDisabled)
|
||||||
|
|
@ -87,12 +87,12 @@ internal class SetConfirmationStateInitTransformer(
|
||||||
|
|
||||||
private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean {
|
private fun isPartiallyUnstakeDisabled(state: StakingUiState): Boolean {
|
||||||
val isSolana = BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId)
|
val isSolana = BlockchainUtils.isSolana(state.cryptoCurrencyBlockchainId)
|
||||||
val isValidatorPreferred = balanceState?.validator?.preferred == true
|
val isTargetPreferred = balanceState?.target?.isPreferred == true
|
||||||
|
|
||||||
return if (isSolana && !isValidatorPreferred) {
|
return if (isSolana && !isTargetPreferred) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
yieldArgs.exit?.isPartialAmountDisabled == true
|
integration.exitArgs?.isPartialAmountDisabled == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||||
|
|
@ -18,7 +18,7 @@ import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedu
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
|
|
||||||
internal class SetConfirmationStateLoadingTransformer(
|
internal class SetConfirmationStateLoadingTransformer(
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
private val cryptoCurrency: CryptoCurrency,
|
private val cryptoCurrency: CryptoCurrency,
|
||||||
) : Transformer<StakingUiState> {
|
) : Transformer<StakingUiState> {
|
||||||
|
|
@ -48,7 +48,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val rewardSchedule = getRewardScheduleText(
|
val rewardSchedule = getRewardScheduleText(
|
||||||
rewardSchedule = yield.metadata.rewardSchedule,
|
rewardSchedule = integration.rewardSchedule,
|
||||||
networkId = cryptoCurrency.network.rawId,
|
networkId = cryptoCurrency.network.rawId,
|
||||||
decapitalize = true,
|
decapitalize = true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.staking.BalanceItem
|
import com.tangem.domain.models.staking.BalanceItem
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||||
|
|
@ -41,7 +44,7 @@ import java.math.BigDecimal
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
internal class SetInitialDataStateTransformer(
|
internal class SetInitialDataStateTransformer(
|
||||||
private val clickIntents: StakingClickIntents,
|
private val clickIntents: StakingClickIntents,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
private val isAnyTokenStaked: Boolean,
|
private val isAnyTokenStaked: Boolean,
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val userWalletProvider: Provider<UserWallet>,
|
private val userWalletProvider: Provider<UserWallet>,
|
||||||
|
|
@ -55,7 +58,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||||
|
|
||||||
private val rewardsValidatorStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
private val rewardsValidatorStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
RewardsValidatorStateConverter(cryptoCurrencyStatus, appCurrencyProvider, yield)
|
RewardsValidatorStateConverter(cryptoCurrencyStatus, appCurrencyProvider, integration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val yieldBalancesConverter by lazy(LazyThreadSafetyMode.NONE) {
|
private val yieldBalancesConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
|
@ -63,7 +66,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
appCurrencyProvider = appCurrencyProvider,
|
||||||
balancesToShowProvider = balancesToShowProvider,
|
balancesToShowProvider = balancesToShowProvider,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,27 +119,27 @@ internal class SetInitialDataStateTransformer(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAnnualPercentageItem(): RoundedListWithDividersItemData {
|
private fun createAnnualPercentageItem(): RoundedListWithDividersItemData {
|
||||||
val validators = yield.preferredValidators
|
val targets = integration.preferredTargets
|
||||||
val rateRangeInfo = getPercentageRange(validators)
|
val rateRangeInfo = getPercentageRange(targets)
|
||||||
return RoundedListWithDividersItemData(
|
return RoundedListWithDividersItemData(
|
||||||
id = R.string.staking_details_annual_percentage_rate,
|
id = R.string.staking_details_annual_percentage_rate,
|
||||||
startText = getRateStartText(rateRangeInfo.first),
|
startText = getRateStartText(rateRangeInfo.first),
|
||||||
endText = rateRangeInfo.second,
|
endText = rateRangeInfo.second,
|
||||||
iconClick = {
|
iconClick = {
|
||||||
when (rateRangeInfo.first) {
|
when (rateRangeInfo.first) {
|
||||||
Yield.RewardType.APR -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_RATE)
|
RewardType.APR -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_RATE)
|
||||||
Yield.RewardType.APY -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_YIELD)
|
RewardType.APY -> clickIntents.onInfoClick(InfoType.ANNUAL_PERCENTAGE_YIELD)
|
||||||
Yield.RewardType.UNKNOWN -> {}
|
RewardType.UNKNOWN -> {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isEndTextHighlighted = false,
|
isEndTextHighlighted = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRateStartText(rewardType: Yield.RewardType): TextReference {
|
private fun getRateStartText(rewardType: RewardType): TextReference {
|
||||||
return when (rewardType) {
|
return when (rewardType) {
|
||||||
Yield.RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate)
|
RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate)
|
||||||
Yield.RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield)
|
RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield)
|
||||||
else -> TextReference.EMPTY
|
else -> TextReference.EMPTY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +156,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData? {
|
private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData? {
|
||||||
val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days ?: return null
|
val cooldownPeriodDays = integration.cooldownPeriodDays ?: return null
|
||||||
return RoundedListWithDividersItemData(
|
return RoundedListWithDividersItemData(
|
||||||
id = R.string.staking_details_unbonding_period,
|
id = R.string.staking_details_unbonding_period,
|
||||||
startText = TextReference.Res(R.string.staking_details_unbonding_period),
|
startText = TextReference.Res(R.string.staking_details_unbonding_period),
|
||||||
|
|
@ -169,7 +172,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
private fun createMinimumRequirementItem(
|
private fun createMinimumRequirementItem(
|
||||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
): RoundedListWithDividersItemData? {
|
): RoundedListWithDividersItemData? {
|
||||||
val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null
|
val minimumCryptoAmount = integration.enterMinimumAmount ?: return null
|
||||||
val blockchainId = cryptoCurrencyStatus.currency.network.rawId
|
val blockchainId = cryptoCurrencyStatus.currency.network.rawId
|
||||||
if (!showMinimumRequirementInfo(blockchainId)) return null
|
if (!showMinimumRequirementInfo(blockchainId)) return null
|
||||||
|
|
||||||
|
|
@ -183,7 +186,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRewardClaimingItem(): RoundedListWithDividersItemData? {
|
private fun createRewardClaimingItem(): RoundedListWithDividersItemData? {
|
||||||
val rewardClaiming = yield.metadata.rewardClaiming
|
val rewardClaiming = integration.rewardClaiming
|
||||||
val endTextId = rewardClaimingResources[rewardClaiming] ?: return null
|
val endTextId = rewardClaimingResources[rewardClaiming] ?: return null
|
||||||
|
|
||||||
return RoundedListWithDividersItemData(
|
return RoundedListWithDividersItemData(
|
||||||
|
|
@ -195,7 +198,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createWarmupPeriodItem(): RoundedListWithDividersItemData? {
|
private fun createWarmupPeriodItem(): RoundedListWithDividersItemData? {
|
||||||
val warmupPeriodDays = yield.metadata.warmupPeriod.days
|
val warmupPeriodDays = integration.warmupPeriodDays
|
||||||
if (warmupPeriodDays == 0) return null
|
if (warmupPeriodDays == 0) return null
|
||||||
|
|
||||||
return RoundedListWithDividersItemData(
|
return RoundedListWithDividersItemData(
|
||||||
|
|
@ -212,7 +215,7 @@ internal class SetInitialDataStateTransformer(
|
||||||
|
|
||||||
private fun createRewardScheduleItem(): RoundedListWithDividersItemData? {
|
private fun createRewardScheduleItem(): RoundedListWithDividersItemData? {
|
||||||
val endTextReference = getRewardScheduleText(
|
val endTextReference = getRewardScheduleText(
|
||||||
rewardSchedule = yield.metadata.rewardSchedule,
|
rewardSchedule = integration.rewardSchedule,
|
||||||
networkId = cryptoCurrencyStatus.currency.network.rawId,
|
networkId = cryptoCurrencyStatus.currency.network.rawId,
|
||||||
decapitalize = false,
|
decapitalize = false,
|
||||||
) ?: return null
|
) ?: return null
|
||||||
|
|
@ -252,15 +255,19 @@ internal class SetInitialDataStateTransformer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPercentageRange(validators: List<Yield.Validator>): Pair<Yield.RewardType, TextReference> {
|
private fun getPercentageRange(targets: List<StakingTarget>): Pair<RewardType, TextReference> {
|
||||||
if (validators.isEmpty()) {
|
if (targets.isEmpty()) {
|
||||||
return Yield.RewardType.APR to stringReference(DASH_SIGN)
|
return RewardType.APR to stringReference(DASH_SIGN)
|
||||||
}
|
}
|
||||||
val rewardInfos = validators
|
val rewardInfos = targets
|
||||||
.filter { it.preferred }
|
.filter { it.isPreferred }
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
?.mapNotNull { it.rewardInfo }
|
?.mapNotNull { it.rewardInfo }
|
||||||
?: validators.mapNotNull { it.rewardInfo }
|
?: targets.mapNotNull { it.rewardInfo }
|
||||||
|
|
||||||
|
if (rewardInfos.isEmpty()) {
|
||||||
|
return RewardType.APR to stringReference(DASH_SIGN)
|
||||||
|
}
|
||||||
|
|
||||||
val infoWithMinRate = rewardInfos.minBy { it.rate }
|
val infoWithMinRate = rewardInfos.minBy { it.rate }
|
||||||
val infoWithMaxRate = rewardInfos.maxBy { it.rate }
|
val infoWithMaxRate = rewardInfos.maxBy { it.rate }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
|
||||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
|
||||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
|
|
@ -13,7 +13,7 @@ internal class AmountChangeStateTransformer(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||||
private val value: String,
|
private val value: String,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
) : Transformer<StakingUiState> {
|
) : Transformer<StakingUiState> {
|
||||||
|
|
||||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||||
|
|
@ -41,7 +41,7 @@ internal class AmountChangeStateTransformer(
|
||||||
amountState = AmountRequirementStateTransformer(
|
amountState = AmountRequirementStateTransformer(
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
maxAmount = maxEnterAmount,
|
maxAmount = maxEnterAmount,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
actionType = prevState.actionType,
|
actionType = prevState.actionType,
|
||||||
).transform(updatedAmountState),
|
).transform(updatedAmountState),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter
|
||||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer
|
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer
|
||||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
|
|
@ -13,7 +13,7 @@ internal class AmountMaxValueStateTransformer(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||||
private val actionType: StakingActionCommonType,
|
private val actionType: StakingActionCommonType,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
) : Transformer<StakingUiState> {
|
) : Transformer<StakingUiState> {
|
||||||
|
|
||||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||||
|
|
@ -38,7 +38,7 @@ internal class AmountMaxValueStateTransformer(
|
||||||
amountState = AmountRequirementStateTransformer(
|
amountState = AmountRequirementStateTransformer(
|
||||||
maxAmount = maxEnterAmount,
|
maxAmount = maxEnterAmount,
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
actionType = prevState.actionType,
|
actionType = prevState.actionType,
|
||||||
).transform(updatedAmountState),
|
).transform(updatedAmountState),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.utils.parseBigDecimal
|
import com.tangem.core.ui.utils.parseBigDecimal
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.AddressArgument
|
import com.tangem.domain.staking.model.stakekit.AddressArgument
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
|
|
@ -26,7 +27,7 @@ import java.math.RoundingMode
|
||||||
internal class AmountRequirementStateTransformer(
|
internal class AmountRequirementStateTransformer(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val maxAmount: EnterAmountBoundary,
|
private val maxAmount: EnterAmountBoundary,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
private val actionType: StakingActionCommonType,
|
private val actionType: StakingActionCommonType,
|
||||||
) : Transformer<AmountState> {
|
) : Transformer<AmountState> {
|
||||||
override fun transform(prevState: AmountState): AmountState {
|
override fun transform(prevState: AmountState): AmountState {
|
||||||
|
|
@ -96,11 +97,11 @@ internal class AmountRequirementStateTransformer(
|
||||||
|
|
||||||
return when (actionType) {
|
return when (actionType) {
|
||||||
is StakingActionCommonType.Enter -> {
|
is StakingActionCommonType.Enter -> {
|
||||||
val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]
|
val enterRequirements = integration.enterArgs?.args?.get(Yield.Args.ArgType.AMOUNT)
|
||||||
enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error)
|
enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error)
|
||||||
}
|
}
|
||||||
is StakingActionCommonType.Exit -> {
|
is StakingActionCommonType.Exit -> {
|
||||||
val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT)
|
val exitRequirements = integration.exitArgs?.args?.get(Yield.Args.ArgType.AMOUNT)
|
||||||
exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error)
|
exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ internal class ShowApprovalBottomSheetTransformer(
|
||||||
val fee = feeState.fee ?: return prevState
|
val fee = feeState.fee ?: return prevState
|
||||||
|
|
||||||
val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty()
|
val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty()
|
||||||
val validatorAddress = validatorState.chosenValidator.address
|
val targetAddress = validatorState.chosenTarget.address
|
||||||
val feeCryptoValue = fee.amount.value.format {
|
val feeCryptoValue = fee.amount.value.format {
|
||||||
crypto(fee.amount.currencySymbol, fee.amount.decimals)
|
crypto(fee.amount.currencySymbol, fee.amount.decimals)
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ internal class ShowApprovalBottomSheetTransformer(
|
||||||
amount = amountState.amountTextField.value,
|
amount = amountState.amountTextField.value,
|
||||||
approveType = ApproveType.UNLIMITED,
|
approveType = ApproveType.UNLIMITED,
|
||||||
walletAddress = walletAddress,
|
walletAddress = walletAddress,
|
||||||
spenderAddress = validatorAddress,
|
spenderAddress = targetAddress,
|
||||||
fee = resourceReference(
|
fee = resourceReference(
|
||||||
R.string.common_crypto_fiat_format,
|
R.string.common_crypto_fiat_format,
|
||||||
wrappedList(feeCryptoValue, feeFiatValue),
|
wrappedList(feeCryptoValue, feeFiatValue),
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||||
import com.tangem.domain.staking.model.stakekit.StakingErrors
|
import com.tangem.domain.staking.model.stakekit.StakingErrors
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||||
|
|
@ -47,12 +47,12 @@ internal class AddStakingNotificationsTransformer(
|
||||||
private val stakingError: StakingError?,
|
private val stakingError: StakingError?,
|
||||||
private val currencyCheck: CryptoCurrencyCheck,
|
private val currencyCheck: CryptoCurrencyCheck,
|
||||||
private val isSubtractAvailable: Boolean,
|
private val isSubtractAvailable: Boolean,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
) : Transformer<StakingUiState> {
|
) : Transformer<StakingUiState> {
|
||||||
|
|
||||||
private val stakingInfoNotificationsFactory = StakingInfoNotificationsFactory(
|
private val stakingInfoNotificationsFactory = StakingInfoNotificationsFactory(
|
||||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||||
yield = yield,
|
integration = integration,
|
||||||
isSubtractAvailable = isSubtractAvailable,
|
isSubtractAvailable = isSubtractAvailable,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ internal class AddStakingNotificationsTransformer(
|
||||||
isSubtractAvailable = isSubtractAvailable,
|
isSubtractAvailable = isSubtractAvailable,
|
||||||
reduceAmountBy = reduceAmountBy,
|
reduceAmountBy = reduceAmountBy,
|
||||||
)
|
)
|
||||||
val minimumRequirement = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum.orZero()
|
val minimumRequirement = integration.enterMinimumAmount.orZero()
|
||||||
val sendingAmount = if (isEnterAction) {
|
val sendingAmount = if (isEnterAction) {
|
||||||
checkAndCalculateSubtractedAmount(
|
checkAndCalculateSubtractedAmount(
|
||||||
isAmountSubtractAvailable = isSubtractAvailable,
|
isAmountSubtractAvailable = isSubtractAvailable,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.staking.BalanceType
|
import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.StakingBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.models.staking.action.StakingActionType
|
import com.tangem.domain.models.staking.action.StakingActionType
|
||||||
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
|
|
@ -26,7 +27,7 @@ import java.math.BigDecimal
|
||||||
|
|
||||||
internal class StakingInfoNotificationsFactory(
|
internal class StakingInfoNotificationsFactory(
|
||||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
private val isSubtractAvailable: Boolean,
|
private val isSubtractAvailable: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -94,7 +95,7 @@ internal class StakingInfoNotificationsFactory(
|
||||||
resourceReference(R.string.staking_notification_withdraw_text)
|
resourceReference(R.string.staking_notification_withdraw_text)
|
||||||
}
|
}
|
||||||
StakingActionType.UNLOCK_LOCKED -> {
|
StakingActionType.UNLOCK_LOCKED -> {
|
||||||
val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days
|
val cooldownPeriodDays = integration.cooldownPeriodDays
|
||||||
if (cooldownPeriodDays != null) {
|
if (cooldownPeriodDays != null) {
|
||||||
resourceReference(R.string.staking_unlocked_locked) to resourceReference(
|
resourceReference(R.string.staking_unlocked_locked) to resourceReference(
|
||||||
R.string.staking_notification_unlock_text,
|
R.string.staking_notification_unlock_text,
|
||||||
|
|
@ -213,7 +214,7 @@ internal class StakingInfoNotificationsFactory(
|
||||||
if (prevState.actionType !is StakingActionCommonType.Exit) return
|
if (prevState.actionType !is StakingActionCommonType.Exit) return
|
||||||
|
|
||||||
val maxAmount = prevState.balanceState?.cryptoAmount ?: return
|
val maxAmount = prevState.balanceState?.cryptoAmount ?: return
|
||||||
val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return
|
val exitRequirements = integration.exitArgs?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return
|
||||||
|
|
||||||
val amountLeft = maxAmount - actionAmount
|
val amountLeft = maxAmount - actionAmount
|
||||||
val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum.orZero()
|
val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum.orZero()
|
||||||
|
|
@ -224,7 +225,7 @@ internal class StakingInfoNotificationsFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableList<NotificationUM>.addUnstakeInfoNotification() {
|
private fun MutableList<NotificationUM>.addUnstakeInfoNotification() {
|
||||||
val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days
|
val cooldownPeriodDays = integration.cooldownPeriodDays
|
||||||
|
|
||||||
val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId
|
val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId
|
||||||
if (cooldownPeriodDays != null) {
|
if (cooldownPeriodDays != null) {
|
||||||
|
|
@ -248,18 +249,17 @@ internal class StakingInfoNotificationsFactory(
|
||||||
val initialInfoState = prevState.initialInfoState as? StakingStates.InitialInfoState.Data
|
val initialInfoState = prevState.initialInfoState as? StakingStates.InitialInfoState.Data
|
||||||
val stakingBalances = (initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data)?.balances
|
val stakingBalances = (initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data)?.balances
|
||||||
|
|
||||||
val validatorAddress = prevState.balanceState?.validator?.address ?: return
|
val targetAddress = prevState.balanceState?.target?.address ?: return
|
||||||
|
|
||||||
val stakesCountWithCertainValidator = stakingBalances.orEmpty()
|
val stakesCountWithCertainTarget = stakingBalances.orEmpty()
|
||||||
.filter {
|
.count { state ->
|
||||||
it.type == BalanceType.STAKED ||
|
(state.type == BalanceType.STAKED ||
|
||||||
it.type == BalanceType.PREPARING ||
|
state.type == BalanceType.PREPARING ||
|
||||||
it.type == BalanceType.UNSTAKED
|
state.type == BalanceType.UNSTAKED) &&
|
||||||
|
state.target?.address == targetAddress
|
||||||
}
|
}
|
||||||
.filter { it.validator?.address == validatorAddress }
|
|
||||||
.size
|
|
||||||
|
|
||||||
if (stakesCountWithCertainValidator > 1) {
|
if (stakesCountWithCertainTarget > 1) {
|
||||||
add(
|
add(
|
||||||
StakingNotification.Info.Ordinary(
|
StakingNotification.Info.Ordinary(
|
||||||
title = resourceReference(R.string.staking_notification_ton_have_to_unstake_all_title),
|
title = resourceReference(R.string.staking_notification_ton_have_to_unstake_all_title),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.features.staking.impl.presentation.state.transformers.validator
|
package com.tangem.features.staking.impl.presentation.state.transformers.validator
|
||||||
|
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.StakingIntegration
|
||||||
|
import com.tangem.domain.staking.model.StakingTarget
|
||||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||||
import com.tangem.domain.models.staking.action.StakingActionType
|
import com.tangem.domain.models.staking.action.StakingActionType
|
||||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||||
|
|
@ -9,8 +10,8 @@ import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
|
|
||||||
internal class ValidatorSelectChangeTransformer(
|
internal class ValidatorSelectChangeTransformer(
|
||||||
private val yield: Yield,
|
private val integration: StakingIntegration,
|
||||||
private val selectedValidator: Yield.Validator?,
|
private val selectedTarget: StakingTarget?,
|
||||||
) : Transformer<StakingUiState> {
|
) : Transformer<StakingUiState> {
|
||||||
|
|
||||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||||
|
|
@ -23,27 +24,27 @@ internal class ValidatorSelectChangeTransformer(
|
||||||
val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo
|
val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo
|
||||||
val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED
|
val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED
|
||||||
|
|
||||||
val activeValidator = selectedValidator.takeIf { isFromInfoScreen && isRestake }
|
val activeTarget = selectedTarget.takeIf { isFromInfoScreen && isRestake }
|
||||||
?: validatorState?.activeValidator
|
?: validatorState?.activeTarget
|
||||||
val filteredValidators = yield.preferredValidators.filterNot { it == activeValidator }
|
val filteredTargets = integration.preferredTargets.filterNot { it == activeTarget }
|
||||||
|
|
||||||
val selectedValidator = if (isRestake && isFromInfoScreen) {
|
val selectedTarget = if (isRestake && isFromInfoScreen) {
|
||||||
filteredValidators.firstOrNull()
|
filteredTargets.firstOrNull()
|
||||||
} else {
|
} else {
|
||||||
selectedValidator
|
selectedTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedValidator == null && yield.preferredValidators.isEmpty()) {
|
if (selectedTarget == null && integration.preferredTargets.isEmpty()) {
|
||||||
return prevState
|
return prevState
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevState.copy(
|
return prevState.copy(
|
||||||
validatorState = StakingStates.ValidatorState.Data(
|
validatorState = StakingStates.ValidatorState.Data(
|
||||||
chosenValidator = selectedValidator ?: yield.preferredValidators.first(),
|
chosenTarget = selectedTarget ?: integration.preferredTargets.first(),
|
||||||
availableValidators = filteredValidators,
|
availableTargets = filteredTargets,
|
||||||
isPrimaryButtonEnabled = true,
|
isPrimaryButtonEnabled = true,
|
||||||
isClickable = yield.preferredValidators.size > 1,
|
isClickable = integration.preferredTargets.size > 1,
|
||||||
activeValidator = activeValidator,
|
activeTarget = activeTarget,
|
||||||
isVisibleOnConfirmation = isEnter || isRestake || isVoteLocked,
|
isVisibleOnConfirmation = isEnter || isRestake || isVoteLocked,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.features.staking.impl.presentation.state.utils
|
package com.tangem.features.staking.impl.presentation.state.utils
|
||||||
|
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.*
|
||||||
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
import com.tangem.features.staking.impl.presentation.state.utils.StakingRewardSchedule.COSMOS_SCHEDULE
|
import com.tangem.features.staking.impl.presentation.state.utils.StakingRewardSchedule.COSMOS_SCHEDULE
|
||||||
|
|
@ -52,18 +53,18 @@ internal fun getRewardScheduleText(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getRewardTypeShortText(rewardType: Yield.RewardType): TextReference {
|
internal fun getRewardTypeShortText(rewardType: RewardType): TextReference {
|
||||||
return when (rewardType) {
|
return when (rewardType) {
|
||||||
Yield.RewardType.APR -> TextReference.Res(R.string.staking_details_apr)
|
RewardType.APR -> TextReference.Res(R.string.staking_details_apr)
|
||||||
Yield.RewardType.APY -> TextReference.Res(R.string.staking_details_apy)
|
RewardType.APY -> TextReference.Res(R.string.staking_details_apy)
|
||||||
else -> TextReference.EMPTY
|
else -> TextReference.EMPTY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getRewardTypeLongText(rewardType: Yield.RewardType): TextReference {
|
internal fun getRewardTypeLongText(rewardType: RewardType): TextReference {
|
||||||
return when (rewardType) {
|
return when (rewardType) {
|
||||||
Yield.RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate)
|
RewardType.APR -> TextReference.Res(R.string.staking_details_annual_percentage_rate)
|
||||||
Yield.RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield)
|
RewardType.APY -> TextReference.Res(R.string.staking_details_annual_percentage_yield)
|
||||||
else -> TextReference.EMPTY
|
else -> TextReference.EMPTY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.format.bigdecimal.percent
|
import com.tangem.core.ui.format.bigdecimal.percent
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||||
|
|
@ -30,7 +30,7 @@ internal fun StakingClaimRewardsValidatorContent(
|
||||||
) {
|
) {
|
||||||
if (state !is StakingStates.RewardsValidatorsState.Data) return
|
if (state !is StakingStates.RewardsValidatorsState.Data) return
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier // Do not put fillMaxSize() in here
|
modifier = modifier // This function shouldn't itself apply fillMaxSize(); callers should avoid passing it here
|
||||||
.background(TangemTheme.colors.background.secondary)
|
.background(TangemTheme.colors.background.secondary)
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing12)
|
.padding(horizontal = TangemTheme.dimens.spacing12)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
|
|
@ -42,9 +42,9 @@ internal fun StakingClaimRewardsValidatorContent(
|
||||||
caption = item.getAprTextNeutral(),
|
caption = item.getAprTextNeutral(),
|
||||||
infoTitle = item.formattedFiatAmount,
|
infoTitle = item.formattedFiatAmount,
|
||||||
infoSubtitle = item.formattedCryptoAmount,
|
infoSubtitle = item.formattedCryptoAmount,
|
||||||
imageUrl = item.validator?.image.orEmpty(),
|
imageUrl = item.target?.image.orEmpty(),
|
||||||
onImageError = { ValidatorImagePlaceholder() },
|
onImageError = { ValidatorImagePlaceholder() },
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.roundedShapeItemDecoration(index, state.rewards.lastIndex, false)
|
.roundedShapeItemDecoration(index, state.rewards.lastIndex, false)
|
||||||
.background(TangemTheme.colors.background.action)
|
.background(TangemTheme.colors.background.action)
|
||||||
.clickable(
|
.clickable(
|
||||||
|
|
@ -65,11 +65,11 @@ internal fun StakingClaimRewardsValidatorContent(
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
@Composable
|
@Composable
|
||||||
private fun BalanceState.getAprTextColored() = combinedReference(
|
private fun BalanceState.getAprTextColored() = combinedReference(
|
||||||
getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN),
|
getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN),
|
||||||
annotatedReference {
|
annotatedReference {
|
||||||
appendSpace()
|
appendSpace()
|
||||||
appendColored(
|
appendColored(
|
||||||
text = validator?.rewardInfo?.rate?.orZero().format { percent() },
|
text = target?.rewardInfo?.rate?.orZero().format { percent() },
|
||||||
color = TangemTheme.colors.text.accent,
|
color = TangemTheme.colors.text.accent,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -77,6 +77,6 @@ private fun BalanceState.getAprTextColored() = combinedReference(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BalanceState.getAprTextNeutral() = combinedReference(
|
private fun BalanceState.getAprTextNeutral() = combinedReference(
|
||||||
getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN),
|
getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN),
|
||||||
stringReference(" " + validator?.rewardInfo?.rate?.orZero().format { percent() }),
|
stringReference(" " + target?.rewardInfo?.rate?.orZero().format { percent() }),
|
||||||
)
|
)
|
||||||
|
|
@ -46,7 +46,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.StakingDetailsScreenTestTags
|
import com.tangem.core.ui.test.StakingDetailsScreenTestTags
|
||||||
import com.tangem.domain.models.staking.BalanceType
|
import com.tangem.domain.models.staking.BalanceType
|
||||||
import com.tangem.domain.models.staking.RewardBlockType
|
import com.tangem.domain.models.staking.RewardBlockType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.common.RewardType
|
||||||
import com.tangem.features.staking.impl.R
|
import com.tangem.features.staking.impl.R
|
||||||
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
import com.tangem.features.staking.impl.presentation.model.StakingClickIntents
|
||||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||||
|
|
@ -317,11 +317,11 @@ private fun StakeButtonBlock(buttonState: NavigationButtonsState) {
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
@Composable
|
@Composable
|
||||||
private fun BalanceState.getAprTextColored() = combinedReference(
|
private fun BalanceState.getAprTextColored() = combinedReference(
|
||||||
getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN),
|
getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN),
|
||||||
annotatedReference {
|
annotatedReference {
|
||||||
appendSpace()
|
appendSpace()
|
||||||
appendColored(
|
appendColored(
|
||||||
text = validator?.rewardInfo?.rate?.orZero().format { percent() },
|
text = target?.rewardInfo?.rate?.orZero().format { percent() },
|
||||||
color = TangemTheme.colors.text.accent,
|
color = TangemTheme.colors.text.accent,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -329,8 +329,8 @@ private fun BalanceState.getAprTextColored() = combinedReference(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BalanceState.getAprTextNeutral() = combinedReference(
|
private fun BalanceState.getAprTextNeutral() = combinedReference(
|
||||||
getRewardTypeShortText(validator?.rewardInfo?.type ?: Yield.RewardType.UNKNOWN),
|
getRewardTypeShortText(target?.rewardInfo?.type ?: RewardType.UNKNOWN),
|
||||||
stringReference(" " + validator?.rewardInfo?.rate?.orZero().format { percent() }),
|
stringReference(" " + target?.rewardInfo?.rate?.orZero().format { percent() }),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -347,7 +347,7 @@ private fun BalanceState.getImage() = when (type) {
|
||||||
BalanceType.UNSTAKED,
|
BalanceType.UNSTAKED,
|
||||||
BalanceType.LOCKED,
|
BalanceType.LOCKED,
|
||||||
-> null
|
-> null
|
||||||
else -> validator?.image
|
else -> target?.image
|
||||||
}
|
}
|
||||||
|
|
||||||
private val textGradientColors = listOf(
|
private val textGradientColors = listOf(
|
||||||
|
|
|
||||||
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