Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-19 15:56:27 +03:00
commit e388c14eb9
184 changed files with 4338 additions and 1298 deletions

View file

@ -29,8 +29,6 @@ dependencies {
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(projects.features.staking.api)
implementation(tangemDeps.blockchain) {
exclude(module = "joda-time")
}

View file

@ -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)

View file

@ -0,0 +1,10 @@
package com.tangem.domain.staking.model.common
import kotlinx.serialization.Serializable
@Serializable
enum class RewardClaiming {
AUTO,
MANUAL,
UNKNOWN,
}

View file

@ -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,
)

View file

@ -0,0 +1,15 @@
package com.tangem.domain.staking.model.common
import kotlinx.serialization.Serializable
@Serializable
enum class RewardSchedule {
BLOCK,
HOUR,
DAY,
WEEK,
MONTH,
ERA,
EPOCH,
UNKNOWN,
}

View file

@ -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,
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.staking.model.common
import kotlinx.serialization.Serializable
/**
* Provider-agnostic representation of staking action arguments.
* Contains amount requirements and constraints for enter/exit operations.
*
* Maps from:
* - StakeKit: Yield.Args.Enter
* - P2PEthPool: P2PEthPoolStaking.Metadata
*/
@Serializable
data class StakingActionArgs(
val amountRequirement: StakingAmountRequirement?,
val isPartialAmountDisabled: Boolean,
)

View file

@ -0,0 +1,19 @@
package com.tangem.domain.staking.model.common
import com.tangem.domain.models.serialization.SerializedBigDecimal
import kotlinx.serialization.Serializable
/**
* Provider-agnostic representation of staking amount requirements.
* Contains validation constraints for stake/unstake amounts.
*
* Maps from:
* - StakeKit: AddressArgument with ArgType.AMOUNT
* - P2PEthPool: P2PEthPoolStaking.Metadata minimumStake/maximumStake
*/
@Serializable
data class StakingAmountRequirement(
val isRequired: Boolean,
val minimum: SerializedBigDecimal? = null,
val maximum: SerializedBigDecimal? = null,
)

View file

@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
import org.joda.time.Instant
/**
* P2P.org account staking information
* Account staking information
* Contains detailed balance and exit queue information
*/
data class P2PEthPoolAccount(

View file

@ -22,7 +22,7 @@ data class P2PEthPoolAction(
)
/**
* Types of P2P staking actions
* Types of P2PEthPool staking actions
*/
@Serializable
enum class P2PEthPoolActionType {
@ -33,7 +33,7 @@ enum class P2PEthPoolActionType {
}
/**
* Status of P2P staking action
* Status of P2PEthPool staking action
*/
@Serializable
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(
val id: String?,

View file

@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
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
*/
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(
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 {
STAKED,

View file

@ -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
* @return true if supported, false otherwise

View file

@ -4,7 +4,7 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
import org.joda.time.DateTime
/**
* P2P.org rewards history entry
* Rewards history entry
* Historical reward information for account
*/
data class P2PEthPoolReward(

View file

@ -61,7 +61,7 @@ data class P2PEthPoolStaking(
}
/**
* Detailed vault information for P2P staking
* Detailed vault information for P2PEthPool staking
*/
data class P2PEthPoolVaultDetails(
val vaultAddress: String,

View file

@ -1,11 +1,11 @@
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.
*/
object P2PStakingConfig {
object P2PEthPoolStakingConfig {
const val USE_TESTNET: Boolean = true

View file

@ -3,7 +3,7 @@ package com.tangem.domain.staking.model.ethpool
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
*/
data class P2PEthPoolUnsignedTx(

View file

@ -2,6 +2,8 @@ package com.tangem.domain.staking.model.stakekit
import com.tangem.domain.models.serialization.SerializedBigDecimal
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
@Serializable
@ -138,18 +140,6 @@ data class Yield(
UNKNOWN,
}
}
enum class RewardType {
APY, // compound rate
APR, // simple rate
UNKNOWN,
}
@Serializable
data class RewardInfo(
val rate: SerializedBigDecimal,
val type: RewardType,
)
}
@Serializable

View file

@ -12,11 +12,11 @@ import kotlinx.coroutines.launch
/**
* 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(
private val stakeKitRepository: StakeKitRepository,
private val p2pRepository: P2PEthPoolRepository,
private val p2pEthPoolRepository: P2PEthPoolRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(): Either<StakingError, Unit> {
@ -25,7 +25,7 @@ class FetchStakingOptionsUseCase(
block = {
coroutineScope {
launch { stakeKitRepository.fetchYields() }
launch { p2pRepository.fetchVaults() }
launch { p2pEthPoolRepository.fetchVaults() }
}
},
catch = { stakingErrorResolver.resolve(it) },

View file

@ -30,7 +30,7 @@ class GetStakingEntryInfoUseCase(
symbol = symbol,
)
}
is StakingOption.P2P -> {
is StakingOption.P2PEthPool -> {
StakingEntryInfo(
tokenSymbol = "ETH",
)

View file

@ -0,0 +1,79 @@
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.common.RewardClaiming
import com.tangem.domain.staking.model.common.RewardSchedule
import com.tangem.domain.staking.model.common.StakingActionArgs
import com.tangem.domain.staking.model.common.StakingAmountRequirement
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
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: StakingActionArgs = StakingActionArgs(
amountRequirement = StakingAmountRequirement(
isRequired = true,
minimum = DEFAULT_MINIMUM_STAKE,
maximum = null,
),
isPartialAmountDisabled = false,
)
override val exitArgs: StakingActionArgs = StakingActionArgs(
amountRequirement = StakingAmountRequirement(
isRequired = true,
minimum = null,
maximum = null,
),
isPartialAmountDisabled = false,
)
// Metadata
override val warmupPeriodDays: Int = 0
override val cooldownPeriodDays: Int = DEFAULT_COOLDOWN_DAYS
override val rewardSchedule: RewardSchedule = RewardSchedule.DAY
override val rewardClaiming: RewardClaiming = 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")
}
}

View file

@ -0,0 +1,99 @@
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.common.RewardClaiming
import com.tangem.domain.staking.model.common.RewardSchedule
import com.tangem.domain.staking.model.common.StakingActionArgs
import com.tangem.domain.staking.model.common.StakingAmountRequirement
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: StakingActionArgs = yield.args.enter.toStakingActionArgs()
override val exitArgs: StakingActionArgs? = yield.args.exit?.toStakingActionArgs()
// Metadata
override val warmupPeriodDays: Int = yield.metadata.warmupPeriod.days
override val cooldownPeriodDays: Int? = yield.metadata.cooldownPeriod?.days
override val rewardSchedule: RewardSchedule = yield.metadata.rewardSchedule.toRewardSchedule()
override val rewardClaiming: RewardClaiming = yield.metadata.rewardClaiming.toRewardClaiming()
// Basic
override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken =
tokens.firstOrNull { rawCurrencyId?.value == it.coinGeckoId } ?: token
private fun Yield.Args.Enter.toStakingActionArgs(): StakingActionArgs {
val amountArg = args[Yield.Args.ArgType.AMOUNT]
return StakingActionArgs(
amountRequirement = amountArg?.let { arg ->
StakingAmountRequirement(
isRequired = arg.required,
minimum = arg.minimum,
maximum = arg.maximum,
)
},
isPartialAmountDisabled = isPartialAmountDisabled,
)
}
private fun Yield.Metadata.RewardSchedule.toRewardSchedule(): RewardSchedule {
return when (this) {
Yield.Metadata.RewardSchedule.BLOCK -> RewardSchedule.BLOCK
Yield.Metadata.RewardSchedule.HOUR -> RewardSchedule.HOUR
Yield.Metadata.RewardSchedule.DAY -> RewardSchedule.DAY
Yield.Metadata.RewardSchedule.WEEK -> RewardSchedule.WEEK
Yield.Metadata.RewardSchedule.MONTH -> RewardSchedule.MONTH
Yield.Metadata.RewardSchedule.ERA -> RewardSchedule.ERA
Yield.Metadata.RewardSchedule.EPOCH -> RewardSchedule.EPOCH
Yield.Metadata.RewardSchedule.UNKNOWN -> RewardSchedule.UNKNOWN
}
}
private fun Yield.Metadata.RewardClaiming.toRewardClaiming(): RewardClaiming {
return when (this) {
Yield.Metadata.RewardClaiming.AUTO -> RewardClaiming.AUTO
Yield.Metadata.RewardClaiming.MANUAL -> RewardClaiming.MANUAL
Yield.Metadata.RewardClaiming.UNKNOWN -> RewardClaiming.UNKNOWN
}
}
}

View file

@ -0,0 +1,57 @@
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.common.RewardClaiming
import com.tangem.domain.staking.model.common.RewardSchedule
import com.tangem.domain.staking.model.common.StakingActionArgs
import java.math.BigDecimal
/**
* Strategy interface for staking integrations.
* Abstracts over StakeKit and P2PEthPool staking providers.
*/
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: StakingActionArgs?
val exitArgs: StakingActionArgs?
// Metadata
val warmupPeriodDays: Int
val cooldownPeriodDays: Int?
val rewardSchedule: RewardSchedule
val rewardClaiming: RewardClaiming
// Basic
fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken
}

View file

@ -5,7 +5,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toMigratedCoinId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
/**
* Represents a staking integration identifier.
@ -97,16 +97,14 @@ sealed interface StakingIntegrationID {
}
/**
* Represents P2P staking integrations
* Represents P2PEthPool staking integration
*/
enum class P2P : StakingIntegrationID {
EthereumPooled {
override val value: String = "p2p-ethereum-pooled"
override val blockchain: Blockchain
get() = if (P2PStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum
override val networkId: String
get() = P2PStakingConfig.activeNetwork.stakingNetworkId
},
object P2PEthPool : StakingIntegrationID {
override val value: String = "p2p-ethereum-pooled"
override val blockchain: Blockchain
get() = if (P2PEthPoolStakingConfig.USE_TESTNET) Blockchain.EthereumTestnet else Blockchain.Ethereum
override val networkId: String
get() = P2PEthPoolStakingConfig.activeNetwork.stakingNetworkId
}
// Polkadot {
@ -138,7 +136,7 @@ sealed interface StakingIntegrationID {
/** List of all native staking integration IDs */
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)
return if (currencyId.contractAddress.isNullOrBlank()) {
// Order is not important — either P2P or Stakekit.Coin can be in any order
P2P.entries.firstOrNull { it.blockchain == blockchain }
?: StakeKit.Coin.entries.firstOrNull { it.blockchain == blockchain }
// Order is not important — either P2PEthPool or Stakekit.Coin can be in any order
if (P2PEthPool.blockchain == blockchain) {
P2PEthPool
} else {
StakeKit.Coin.entries.firstOrNull { it.blockchain == blockchain }
}
} else {
StakeKit.EthereumToken.entries.firstOrNull { token ->
token.blockchain == blockchain &&

View file

@ -1,19 +1,18 @@
package com.tangem.domain.staking.model
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.staking.model.ethpool.P2PEthPoolVault
import com.tangem.domain.staking.model.stakekit.Yield
/**
* 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 {
/** Unique identifier for the staking option */
val integrationId: String
val integrationId: StakingIntegrationID
/** Annual Percentage Yield */
val apy: SerializedBigDecimal
@ -28,34 +27,23 @@ sealed interface StakingOption {
* StakeKit staking option
* Wraps StakeKit Yield with all validator and metadata information
*/
data class StakeKit(val yield: Yield) : StakingOption {
override val integrationId: String = yield.id
data class StakeKit(
override val integrationId: StakingIntegrationID.StakeKit,
val yield: Yield,
) : StakingOption {
override val apy: SerializedBigDecimal = yield.apy
override val token: YieldToken = yield.token
override val isAvailable: Boolean = yield.isAvailable
}
/**
* P2P pooled staking option
* Wraps P2P ETH Pool vault information
* P2PEthPool staking option
* Wraps P2PEthPool vault information
*/
data class P2P(val vaults: List<P2PEthPoolVault>) : StakingOption {
override val integrationId: String = "p2p-ethereum-pooled"
data class P2PEthPool(val vaults: List<P2PEthPoolVault>) : StakingOption {
override val integrationId: StakingIntegrationID = StakingIntegrationID.P2PEthPool
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()
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,
)
}
}
}

View file

@ -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.P2PEthPoolUnsignedTx
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 kotlinx.coroutines.flow.Flow
@ -17,24 +17,24 @@ interface P2PEthPoolRepository {
/**
* 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
*
* @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
*/
suspend fun getVaults(
network: P2PEthPoolNetwork = P2PStakingConfig.activeNetwork,
network: P2PEthPoolNetwork = P2PEthPoolStakingConfig.activeNetwork,
): Either<StakingError, List<P2PEthPoolVault>>
/**
* 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 vaultAddress Vault contract address
* @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]
* 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 stakeTransactionHash Original stake transaction hash
* @return Either error or unsigned transaction
@ -69,7 +69,7 @@ interface P2PEthPoolRepository {
*
* 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
* @return Either error or unsigned transaction with withdrawal tickets
*/
@ -81,7 +81,7 @@ interface P2PEthPoolRepository {
/**
* 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)
* @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
*
* @param network P2P network (MAINNET or TESTNET)
* @param network P2PEthPool network (MAINNET or TESTNET)
* @param delegatorAddress User's wallet address
* @param vaultAddress Vault contract address
* @return Either error or account info
@ -109,7 +109,7 @@ interface P2PEthPoolRepository {
/**
* 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 vaultAddress Vault contract address
* @param period Optional period filter in days (30, 60, or 90)
@ -123,17 +123,27 @@ interface P2PEthPoolRepository {
): 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
*/
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
*/
suspend fun getStakingAvailabilitySync(): StakingAvailability

View file

@ -8,6 +8,7 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingAvailability
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.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingAction
@ -24,9 +25,17 @@ interface StakeKitRepository {
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

View file

@ -7,7 +7,7 @@ import java.math.BigDecimal
/**
* 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
* (staked + unstaking + withdrawable + rewards).
@ -18,7 +18,7 @@ import java.math.BigDecimal
fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String): BigDecimal {
return when (this) {
is StakingBalance.Data.StakeKit -> getTotalWithRewardsStakingBalanceStakeKit(blockchainId)
is StakingBalance.Data.P2P -> {
is StakingBalance.Data.P2PEthPool -> {
val rewards = totalRewards
if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId)) {
totalStaked + unstakingAmount + withdrawableAmount + rewards
@ -31,7 +31,7 @@ fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String):
/**
* 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)
* excluding rewards.
@ -39,7 +39,7 @@ fun StakingBalance.Data.getTotalWithRewardsStakingBalance(blockchainId: String):
fun StakingBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal {
return when (this) {
is StakingBalance.Data.StakeKit -> getTotalStakingBalanceStakeKit(blockchainId)
is StakingBalance.Data.P2P -> totalStaked + unstakingAmount + withdrawableAmount
is StakingBalance.Data.P2PEthPool -> totalStaked + unstakingAmount + withdrawableAmount
}
}

View file

@ -149,8 +149,8 @@ internal class StakingIdFactoryTest {
expected = createStakingId(integrationId = StakingIntegrationID.StakeKit.Coin.Cardano),
),
CreateModel(
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
expected = createStakingId(integrationId = StakingIntegrationID.P2P.EthereumPooled),
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2PEthPool.blockchain),
expected = createStakingId(integrationId = StakingIntegrationID.P2PEthPool),
),
CreateModel(
currencyId = CryptoCurrency.ID.fromValue(

View file

@ -38,17 +38,6 @@ class StakingIntegrationIDTest {
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
fun `all sub blockchains are unique`() {
// Act
@ -151,8 +140,8 @@ class StakingIntegrationIDTest {
expected = StakingIntegrationID.StakeKit.Coin.Cardano,
),
CreateModel(
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2P.EthereumPooled.blockchain),
expected = StakingIntegrationID.P2P.EthereumPooled,
currencyId = createCurrencyId(blockchain = StakingIntegrationID.P2PEthPool.blockchain),
expected = StakingIntegrationID.P2PEthPool,
),
CreateModel(
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"),