Updated on 2026-08-14
This commit is contained in:
commit
14a7ac4f5e
431 changed files with 23077 additions and 3701 deletions
|
|
@ -16,6 +16,7 @@ import java.math.BigDecimal
|
|||
* @property privacyPolicy privacy policy link
|
||||
* @property isRecommended flag that indicates if this provider is recommended
|
||||
* @property slippage provider slippage
|
||||
* @property isExchangeOnlyWithinSingleAddress flag that indicates if exchange is only allowed within a single address
|
||||
*
|
||||
* Uses to store transaction data in datastore, when extends - should always add default value
|
||||
* to support backward compatibility
|
||||
|
|
@ -40,4 +41,6 @@ data class ExpressProvider(
|
|||
val isRecommended: Boolean = false,
|
||||
@Json(name = "slippage")
|
||||
val slippage: BigDecimal?,
|
||||
@Json(name = "exchangeOnlyWithinSingleAddress")
|
||||
val isExchangeOnlyWithinSingleAddress: Boolean = false,
|
||||
)
|
||||
|
|
@ -18,6 +18,9 @@ sealed interface FeedbackEmailType {
|
|||
/** User rate the app as "can be better" */
|
||||
data class RateCanBeBetter(override val walletMetaInfo: WalletMetaInfo) : FeedbackEmailType
|
||||
|
||||
/** User has problem with backup */
|
||||
data class BackupProblem(override val walletMetaInfo: WalletMetaInfo) : FeedbackEmailType
|
||||
|
||||
/** User has problem with scanning */
|
||||
data object ScanningProblem : FeedbackEmailType {
|
||||
override val walletMetaInfo: WalletMetaInfo? = null
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ class SendFeedbackEmailUseCase(
|
|||
is FeedbackEmailType.Visa.FeatureIsBeta,
|
||||
-> this
|
||||
is FeedbackEmailType.DirectUserRequest,
|
||||
is FeedbackEmailType.BackupProblem,
|
||||
is FeedbackEmailType.RateCanBeBetter,
|
||||
is FeedbackEmailType.StakingProblem,
|
||||
is FeedbackEmailType.SwapProblem,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ internal class EmailMessageBodyResolver(
|
|||
is FeedbackEmailType.SwapProblem -> addSwapProblemBody(type)
|
||||
is FeedbackEmailType.CurrencyDescriptionError -> addTokenInfo(type)
|
||||
is FeedbackEmailType.PreActivatedWallet -> addUserRequestBody(type.walletMetaInfo)
|
||||
is FeedbackEmailType.BackupProblem -> addUserRequestBody(type.walletMetaInfo)
|
||||
is FeedbackEmailType.ScanningProblem,
|
||||
is FeedbackEmailType.CardAttestationFailed,
|
||||
-> addPhoneInfoBody()
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
|
|||
is FeedbackEmailType.Visa.Withdrawal,
|
||||
is FeedbackEmailType.Visa.FeatureIsBeta,
|
||||
is FeedbackEmailType.PreActivatedWallet,
|
||||
is FeedbackEmailType.BackupProblem,
|
||||
-> R.string.feedback_preface_support
|
||||
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
|
||||
is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ internal class EmailSubjectResolver(private val resources: Resources) {
|
|||
resources.getStringSafe(R.string.feedback_subject_support_tangem)
|
||||
}
|
||||
}
|
||||
is FeedbackEmailType.BackupProblem -> resources.getStringSafe(R.string.feedback_subject_backup_problem)
|
||||
is FeedbackEmailType.RateCanBeBetter -> resources.getStringSafe(R.string.feedback_subject_rate_negative)
|
||||
is FeedbackEmailType.ScanningProblem -> resources.getStringSafe(R.string.feedback_subject_scan_failed)
|
||||
is FeedbackEmailType.TransactionSendingProblem,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable
|
|||
[REDACTED_AUTHOR]
|
||||
* @param id - unique identifier of the article
|
||||
* @param title - article title
|
||||
* @param sourceName - name of original article source
|
||||
* @param source - object of source name and identifier
|
||||
* @param locale - language of the article
|
||||
* @param publishedAt - date of article publishing
|
||||
* @param url - link to source of original article
|
||||
|
|
@ -18,9 +18,15 @@ import kotlinx.serialization.Serializable
|
|||
data class OriginalArticle(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val sourceName: String,
|
||||
val source: Source,
|
||||
val locale: String,
|
||||
val publishedAt: String,
|
||||
val url: String,
|
||||
val imageUrl: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Source(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import com.tangem.domain.models.staking.StakingEntryType.Companion.fromBalanceType
|
||||
|
||||
fun BalanceItem.toStakingBalanceEntry(validatorName: String? = null): StakingBalanceEntry {
|
||||
return StakingBalanceEntry(
|
||||
id = groupId,
|
||||
type = fromBalanceType(type),
|
||||
amount = amount,
|
||||
validator = validatorAddress?.let {
|
||||
ValidatorInfo(address = it, name = validatorName)
|
||||
},
|
||||
date = date,
|
||||
actions = StakingEntryActions.StakeKit(
|
||||
pendingActions = pendingActions,
|
||||
pendingActionsConstraints = pendingActionsConstraints,
|
||||
),
|
||||
isPending = isPending,
|
||||
rawCurrencyId = rawCurrencyId,
|
||||
)
|
||||
}
|
||||
|
||||
fun List<BalanceItem>.toStakingBalanceEntries(
|
||||
validatorNameResolver: (String?) -> String? = { null },
|
||||
): List<StakingBalanceEntry> {
|
||||
return map { it.toStakingBalanceEntry(validatorNameResolver(it.validatorAddress)) }
|
||||
}
|
||||
|
|
@ -4,34 +4,34 @@ import com.tangem.domain.models.serialization.SerializedBigDecimal
|
|||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** P2P.org staking account */
|
||||
/** P2P.org eth pooled staking account */
|
||||
@Serializable
|
||||
data class P2PStakingAccount(
|
||||
data class P2PEthPoolStakingAccount(
|
||||
val delegatorAddress: String,
|
||||
val vaultAddress: String,
|
||||
val stake: P2PStake,
|
||||
val stake: P2PEthPoolStake,
|
||||
val availableToUnstake: SerializedBigDecimal,
|
||||
val availableToWithdraw: SerializedBigDecimal,
|
||||
val exitQueue: P2PExitQueue,
|
||||
val exitQueue: P2PEthPoolExitQueue,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class P2PStake(
|
||||
data class P2PEthPoolStake(
|
||||
val assets: SerializedBigDecimal,
|
||||
val totalEarnedAssets: SerializedBigDecimal,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class P2PExitQueue(
|
||||
data class P2PEthPoolExitQueue(
|
||||
val total: SerializedBigDecimal,
|
||||
val requests: List<P2PExitRequest>,
|
||||
val requests: List<P2PEthPoolExitRequest>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class P2PExitRequest(
|
||||
data class P2PEthPoolExitRequest(
|
||||
val ticket: String,
|
||||
val totalAssets: SerializedBigDecimal,
|
||||
val timestamp: Instant,
|
||||
val withdrawalTimestamp: Instant,
|
||||
val withdrawalTimestamp: Instant?,
|
||||
val isClaimable: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun P2PEthPoolStakingAccount.toStakingBalanceEntries(vaultName: String? = null): List<StakingBalanceEntry> {
|
||||
return buildList {
|
||||
if (stake.assets > BigDecimal.ZERO) {
|
||||
add(createStakedEntry(vaultAddress, stake.assets, vaultName))
|
||||
}
|
||||
exitQueue.requests.filter { !it.isClaimable }.forEach { add(createUnstakingEntry(vaultAddress, it, vaultName)) }
|
||||
if (availableToWithdraw > BigDecimal.ZERO) {
|
||||
add(createWithdrawableEntry(vaultAddress, availableToWithdraw, vaultName))
|
||||
}
|
||||
if (stake.totalEarnedAssets > BigDecimal.ZERO) {
|
||||
add(createRewardsEntry(vaultAddress, stake.totalEarnedAssets, vaultName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStakedEntry(vaultAddress: String, amount: BigDecimal, vaultName: String?): StakingBalanceEntry {
|
||||
return StakingBalanceEntry(
|
||||
id = vaultAddress,
|
||||
type = StakingEntryType.STAKED,
|
||||
amount = amount,
|
||||
validator = ValidatorInfo(address = vaultAddress, name = vaultName),
|
||||
date = null,
|
||||
actions = StakingEntryActions.P2PEthPool(ticket = null, estimatedWithdrawalDate = null, isClaimable = false),
|
||||
isPending = false,
|
||||
rawCurrencyId = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUnstakingEntry(
|
||||
vaultAddress: String,
|
||||
request: P2PEthPoolExitRequest,
|
||||
vaultName: String?,
|
||||
): StakingBalanceEntry {
|
||||
return StakingBalanceEntry(
|
||||
id = "${vaultAddress}_${request.ticket}",
|
||||
type = StakingEntryType.UNSTAKING,
|
||||
amount = request.totalAssets,
|
||||
validator = ValidatorInfo(address = vaultAddress, name = vaultName),
|
||||
date = request.withdrawalTimestamp,
|
||||
actions = StakingEntryActions.P2PEthPool(
|
||||
ticket = request.ticket,
|
||||
estimatedWithdrawalDate = request.withdrawalTimestamp,
|
||||
isClaimable = false,
|
||||
),
|
||||
isPending = false,
|
||||
rawCurrencyId = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createWithdrawableEntry(
|
||||
vaultAddress: String,
|
||||
amount: BigDecimal,
|
||||
vaultName: String?,
|
||||
): StakingBalanceEntry {
|
||||
return StakingBalanceEntry(
|
||||
id = "${vaultAddress}_withdrawable",
|
||||
type = StakingEntryType.WITHDRAWABLE,
|
||||
amount = amount,
|
||||
validator = ValidatorInfo(address = vaultAddress, name = vaultName),
|
||||
date = null,
|
||||
actions = StakingEntryActions.P2PEthPool(
|
||||
ticket = null,
|
||||
estimatedWithdrawalDate = null,
|
||||
isClaimable = true,
|
||||
),
|
||||
isPending = false,
|
||||
rawCurrencyId = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createRewardsEntry(vaultAddress: String, amount: BigDecimal, vaultName: String?): StakingBalanceEntry {
|
||||
return StakingBalanceEntry(
|
||||
id = "${vaultAddress}_rewards",
|
||||
type = StakingEntryType.REWARDS,
|
||||
amount = amount,
|
||||
validator = ValidatorInfo(address = vaultAddress, name = vaultName),
|
||||
date = null,
|
||||
actions = StakingEntryActions.P2PEthPool(ticket = null, estimatedWithdrawalDate = null, isClaimable = false),
|
||||
isPending = false,
|
||||
rawCurrencyId = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Staking balance facade covering StakeKit and P2P balances
|
||||
* Staking balance facade covering StakeKit and P2PEthPool balances
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface StakingBalance {
|
||||
|
|
@ -21,6 +22,9 @@ sealed interface StakingBalance {
|
|||
@Serializable
|
||||
sealed interface Data : StakingBalance {
|
||||
|
||||
/** Provider-agnostic list of balance entries for UI display */
|
||||
val entries: List<StakingBalanceEntry>
|
||||
|
||||
@Serializable
|
||||
data class StakeKit(
|
||||
override val stakingId: StakingID,
|
||||
|
|
@ -28,45 +32,41 @@ sealed interface StakingBalance {
|
|||
val balance: YieldBalanceItem,
|
||||
) : Data {
|
||||
|
||||
override val totalStaked: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.STAKED }
|
||||
.sumOf { it.amount }
|
||||
override val totalStaked: SerializedBigDecimal = balance.items
|
||||
.filter { it.type == BalanceType.STAKED }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val totalRewards: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
override val totalRewards: SerializedBigDecimal = balance.items
|
||||
.filter { it.type == BalanceType.REWARDS }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val unstakingAmount: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.UNSTAKING || it.type == BalanceType.UNLOCKING }
|
||||
.sumOf { it.amount }
|
||||
override val unstakingAmount: SerializedBigDecimal = balance.items
|
||||
.filter { it.type == BalanceType.UNSTAKING || it.type == BalanceType.UNLOCKING }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val withdrawableAmount: BigDecimal
|
||||
get() = balance.items
|
||||
.filter { it.type == BalanceType.UNSTAKED }
|
||||
.sumOf { it.amount }
|
||||
override val withdrawableAmount: SerializedBigDecimal = balance.items
|
||||
.filter { it.type == BalanceType.UNSTAKED }
|
||||
.sumOf { it.amount }
|
||||
|
||||
override val entries: List<StakingBalanceEntry> = balance.items.toStakingBalanceEntries()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class P2P(
|
||||
data class P2PEthPool(
|
||||
override val stakingId: StakingID,
|
||||
override val source: StatusSource,
|
||||
val account: P2PStakingAccount,
|
||||
val account: P2PEthPoolStakingAccount,
|
||||
) : Data {
|
||||
|
||||
override val totalStaked: BigDecimal
|
||||
get() = account.stake.assets
|
||||
override val totalStaked: SerializedBigDecimal = account.stake.assets
|
||||
|
||||
override val totalRewards: BigDecimal
|
||||
get() = account.stake.totalEarnedAssets
|
||||
override val totalRewards: SerializedBigDecimal = account.stake.totalEarnedAssets
|
||||
|
||||
override val unstakingAmount: BigDecimal
|
||||
get() = account.exitQueue.total
|
||||
override val unstakingAmount: SerializedBigDecimal = account.exitQueue.total
|
||||
|
||||
override val withdrawableAmount: BigDecimal
|
||||
get() = account.availableToWithdraw
|
||||
override val withdrawableAmount: SerializedBigDecimal = account.availableToWithdraw
|
||||
|
||||
override val entries: List<StakingBalanceEntry> = account.toStakingBalanceEntries()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ sealed interface StakingBalance {
|
|||
fun copySealed(source: StatusSource): StakingBalance {
|
||||
return when (this) {
|
||||
is Data.StakeKit -> copy(source = source)
|
||||
is Data.P2P -> copy(source = source)
|
||||
is Data.P2PEthPool -> copy(source = source)
|
||||
is Empty -> copy(source = source)
|
||||
is Error -> this
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StakingBalanceEntry(
|
||||
val id: String,
|
||||
val type: StakingEntryType,
|
||||
val amount: SerializedBigDecimal,
|
||||
val validator: ValidatorInfo?,
|
||||
val date: Instant?,
|
||||
val actions: StakingEntryActions,
|
||||
val isPending: Boolean,
|
||||
val rawCurrencyId: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ValidatorInfo(
|
||||
val address: String,
|
||||
val name: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed interface StakingEntryActions {
|
||||
|
||||
@Serializable
|
||||
data class StakeKit(
|
||||
val pendingActions: List<PendingAction>,
|
||||
val pendingActionsConstraints: List<PendingActionConstraints>,
|
||||
) : StakingEntryActions {
|
||||
val hasPendingActions: Boolean get() = pendingActions.isNotEmpty()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class P2PEthPool(
|
||||
val ticket: String?,
|
||||
val estimatedWithdrawalDate: Instant?,
|
||||
val isClaimable: Boolean,
|
||||
) : StakingEntryActions
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.domain.models.staking
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class StakingEntryType {
|
||||
AVAILABLE,
|
||||
STAKED,
|
||||
PREPARING,
|
||||
LOCKED,
|
||||
UNSTAKING,
|
||||
UNLOCKING,
|
||||
WITHDRAWABLE,
|
||||
REWARDS,
|
||||
UNKNOWN,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromBalanceType(type: BalanceType): StakingEntryType = when (type) {
|
||||
BalanceType.AVAILABLE -> AVAILABLE
|
||||
BalanceType.STAKED -> STAKED
|
||||
BalanceType.PREPARING -> PREPARING
|
||||
BalanceType.LOCKED -> LOCKED
|
||||
BalanceType.UNSTAKING -> UNSTAKING
|
||||
BalanceType.UNLOCKING -> UNLOCKING
|
||||
BalanceType.UNSTAKED -> WITHDRAWABLE // stakekit's UNSTAKED = ready to withdraw
|
||||
BalanceType.REWARDS -> REWARDS
|
||||
BalanceType.UNKNOWN -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,4 +12,17 @@ data class YieldToken(
|
|||
val coinGeckoId: String?,
|
||||
val logoURI: String?,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
data class NewsListConfig(
|
||||
val language: String,
|
||||
val snapshot: String,
|
||||
val snapshot: String?,
|
||||
val tokenIds: List<String> = emptyList(),
|
||||
val categoryIds: List<Int> = emptyList(),
|
||||
)
|
||||
|
|
@ -2,9 +2,11 @@ package com.tangem.domain.news.repository
|
|||
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.models.news.TrendingNews
|
||||
import com.tangem.domain.news.model.NewsListBatchFlow
|
||||
import com.tangem.domain.news.model.NewsListBatchingContext
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
|
|
@ -20,6 +22,13 @@ interface NewsRepository {
|
|||
*/
|
||||
fun getNewsListBatchFlow(context: NewsListBatchingContext, batchSize: Int): NewsListBatchFlow
|
||||
|
||||
/**
|
||||
* Returns list of short article by config.
|
||||
*
|
||||
* @param config config for getting news list
|
||||
*/
|
||||
suspend fun getNews(config: NewsListConfig, limit: Int): List<ShortArticle>
|
||||
|
||||
/**
|
||||
* Returns detailed article by id with locale configuration.
|
||||
* @param newsId news identification
|
||||
|
|
@ -50,13 +59,13 @@ interface NewsRepository {
|
|||
*/
|
||||
fun observeTrendingNews(): Flow<TrendingNews>
|
||||
|
||||
/**
|
||||
* Updates viewed flag for provided trending articles.
|
||||
*/
|
||||
suspend fun updateTrendingNewsViewed(articleIds: Collection<Int>, viewed: Boolean)
|
||||
|
||||
/**
|
||||
* Returns available categories.
|
||||
*/
|
||||
suspend fun getCategories(): List<ArticleCategory>
|
||||
|
||||
/**
|
||||
* Updates viewed flag for provided news articles (applies to both regular and trending news).
|
||||
*/
|
||||
suspend fun updateNewsViewed(articleIds: Collection<Int>, viewed: Boolean)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.news.ArticleCategory
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ class GetNewsCategoriesUseCase(
|
|||
/**
|
||||
* Fetches categories from the repository.
|
||||
*/
|
||||
suspend operator fun invoke(): List<ArticleCategory> {
|
||||
return repository.getCategories()
|
||||
suspend operator fun invoke(): Either<Throwable, List<ArticleCategory>> = Either.catch {
|
||||
repository.getCategories()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
|
||||
class GetNewsUseCase(private val repository: NewsRepository) {
|
||||
|
||||
suspend fun getNews(limit: Int, newsListConfig: NewsListConfig): Either<Throwable, List<ShortArticle>> =
|
||||
Either.catch {
|
||||
repository.getNews(
|
||||
config = newsListConfig,
|
||||
limit = limit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,18 +20,4 @@ class ManageTrendingNewsUseCase(private val repository: NewsRepository) {
|
|||
.observeTrendingNews()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a single article as viewed/unviewed.
|
||||
*/
|
||||
suspend fun markAsViewed(articleId: Int, viewed: Boolean = true) {
|
||||
repository.updateTrendingNewsViewed(listOf(articleId), viewed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks multiple articles at once (useful for bulk updates).
|
||||
*/
|
||||
suspend fun markAsViewed(articleIds: Collection<Int>, viewed: Boolean = true) {
|
||||
repository.updateTrendingNewsViewed(articleIds, viewed)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
|
||||
class MarkArticleAsViewedUseCase(private val repository: NewsRepository) {
|
||||
|
||||
/**
|
||||
* Marks a single article as viewed/unviewed.
|
||||
*/
|
||||
suspend fun markAsViewed(articleId: Int, viewed: Boolean = true) {
|
||||
repository.updateNewsViewed(listOf(articleId), viewed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks multiple articles at once (useful for bulk updates).
|
||||
*/
|
||||
suspend fun markAsViewed(articleIds: Collection<Int>, viewed: Boolean = true) {
|
||||
repository.updateNewsViewed(articleIds, viewed)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.news.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.news.DetailedArticle
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -23,7 +24,7 @@ class ObserveNewsDetailsUseCase(
|
|||
/**
|
||||
* Prefetches the given article ids (can be called with current + next ids for pager preloading).
|
||||
*/
|
||||
suspend fun prefetch(newsIds: Collection<Int>, language: String?) {
|
||||
suspend fun prefetch(newsIds: Collection<Int>, language: String?): Either<Throwable, Unit> = Either.catch {
|
||||
repository.fetchDetailedArticles(newsIds, language)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class RewardClaiming {
|
||||
AUTO,
|
||||
MANUAL,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -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,15 @@
|
|||
package com.tangem.domain.staking.model.common
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class RewardSchedule {
|
||||
BLOCK,
|
||||
HOUR,
|
||||
DAY,
|
||||
WEEK,
|
||||
MONTH,
|
||||
ERA,
|
||||
EPOCH,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.domain.staking.model.ethpool
|
||||
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import org.joda.time.Instant
|
||||
|
||||
/**
|
||||
* P2P.org account staking information
|
||||
* Contains detailed balance and exit queue information
|
||||
*/
|
||||
data class P2PEthPoolAccount(
|
||||
val delegatorAddress: String,
|
||||
val vaultAddress: String,
|
||||
val stake: P2PEthPoolStake,
|
||||
val availableToUnstake: SerializedBigDecimal,
|
||||
val availableToWithdraw: SerializedBigDecimal,
|
||||
val exitQueue: P2PEthPoolExitQueue,
|
||||
)
|
||||
|
||||
/**
|
||||
* Current stake information
|
||||
*/
|
||||
data class P2PEthPoolStake(
|
||||
val assets: SerializedBigDecimal,
|
||||
val totalEarnedAssets: SerializedBigDecimal,
|
||||
)
|
||||
|
||||
/**
|
||||
* Exit queue information
|
||||
*/
|
||||
data class P2PEthPoolExitQueue(
|
||||
val total: SerializedBigDecimal,
|
||||
val requests: List<P2PEthPoolExitRequest>,
|
||||
)
|
||||
|
||||
/**
|
||||
* Individual exit request in the queue
|
||||
*/
|
||||
data class P2PEthPoolExitRequest(
|
||||
val ticket: String,
|
||||
val totalAssets: SerializedBigDecimal,
|
||||
val timestamp: Instant,
|
||||
val withdrawalTimestamp: Instant,
|
||||
val isClaimable: Boolean,
|
||||
)
|
||||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class GetStakingEntryInfoUseCase(
|
|||
symbol = symbol,
|
||||
)
|
||||
}
|
||||
is StakingOption.P2P -> {
|
||||
is StakingOption.P2PEthPool -> {
|
||||
StakingEntryInfo(
|
||||
tokenSymbol = "ETH",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.staking.model
|
||||
|
||||
sealed class CooldownPeriod {
|
||||
|
||||
data class Fixed(val days: Int) : CooldownPeriod()
|
||||
|
||||
data class Range(val minDays: Int, val maxDays: Int) : CooldownPeriod()
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
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,
|
||||
private val 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 = calculateMaximumStakeAmount(),
|
||||
),
|
||||
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 cooldownPeriod: CooldownPeriod = CooldownPeriod.Range(
|
||||
minDays = MIN_COOLDOWN_DAYS,
|
||||
maxDays = MAX_COOLDOWN_DAYS,
|
||||
)
|
||||
|
||||
override val rewardSchedule: RewardSchedule = RewardSchedule.DAY
|
||||
|
||||
override val rewardClaiming: RewardClaiming = RewardClaiming.AUTO
|
||||
|
||||
override fun getCurrentToken(rawCurrencyId: CryptoCurrency.RawID?): YieldToken = token
|
||||
|
||||
private fun calculateMaximumStakeAmount(): BigDecimal? {
|
||||
return vaults
|
||||
.mapNotNull { vault ->
|
||||
val availableCapacity = vault.capacity - vault.totalAssets
|
||||
if (availableCapacity > BigDecimal.ZERO) availableCapacity else null
|
||||
}
|
||||
.maxOrNull()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MIN_COOLDOWN_DAYS = 1
|
||||
private const val MAX_COOLDOWN_DAYS = 4
|
||||
private val DEFAULT_MINIMUM_STAKE = BigDecimal("0.01")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
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 cooldownPeriod: CooldownPeriod? = yield.metadata.cooldownPeriod?.days?.let {
|
||||
CooldownPeriod.Fixed(it)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
sealed 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 cooldownPeriod: CooldownPeriod?
|
||||
|
||||
val rewardSchedule: RewardSchedule
|
||||
|
||||
val rewardClaiming: 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.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 &&
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.domain.staking.repositories
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.staking.P2PEthPoolStakingAccount
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolAccount
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolBroadcastResult
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolNetwork
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolReward
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolUnsignedTx
|
||||
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||
import com.tangem.domain.staking.model.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,15 +53,17 @@ 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 stakerPublicKey Staker's public key (note: API doc may have Bitcoin terminology)
|
||||
* @param stakeTransactionHash Original stake transaction hash
|
||||
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||
* @param delegatorAddress User's wallet address
|
||||
* @param vaultAddress Vault contract address
|
||||
* @param amount Amount of ETH to unstake
|
||||
* @return Either error or unsigned transaction
|
||||
*/
|
||||
suspend fun createUnstakeTransaction(
|
||||
network: P2PEthPoolNetwork,
|
||||
stakerPublicKey: String,
|
||||
stakeTransactionHash: String,
|
||||
delegatorAddress: String,
|
||||
vaultAddress: String,
|
||||
amount: String,
|
||||
): Either<StakingError, P2PEthPoolUnsignedTx>
|
||||
|
||||
/**
|
||||
|
|
@ -69,19 +71,23 @@ interface P2PEthPoolRepository {
|
|||
*
|
||||
* Only works when funds are available (after exit queue wait period).
|
||||
*
|
||||
* @param network P2P network (MAINNET or TESTNET)
|
||||
* @param stakerAddress User's wallet address
|
||||
* @param network P2PEthPool network (MAINNET or TESTNET)
|
||||
* @param delegatorAddress User's wallet address
|
||||
* @param vaultAddress Vault contract address
|
||||
* @param amount Amount of ETH to withdraw
|
||||
* @return Either error or unsigned transaction with withdrawal tickets
|
||||
*/
|
||||
suspend fun createWithdrawTransaction(
|
||||
network: P2PEthPoolNetwork,
|
||||
stakerAddress: String,
|
||||
delegatorAddress: String,
|
||||
vaultAddress: String,
|
||||
amount: String,
|
||||
): Either<StakingError, P2PEthPoolUnsignedTx>
|
||||
|
||||
/**
|
||||
* 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 +101,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
|
||||
|
|
@ -104,12 +110,12 @@ interface P2PEthPoolRepository {
|
|||
network: P2PEthPoolNetwork,
|
||||
delegatorAddress: String,
|
||||
vaultAddress: String,
|
||||
): Either<StakingError, P2PEthPoolAccount>
|
||||
): Either<StakingError, P2PEthPoolStakingAccount>
|
||||
|
||||
/**
|
||||
* 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 +129,37 @@ 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>>
|
||||
|
||||
/**
|
||||
* Get cached vaults synchronously from local store.
|
||||
*
|
||||
* This returns vaults from the local cache/store without network call.
|
||||
* Call [fetchVaults] first to populate the cache from the network.
|
||||
*
|
||||
* @return List of cached vaults (empty if cache is not populated)
|
||||
*/
|
||||
suspend fun getVaultsSync(): 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ object CryptoCurrencyStatusFactory {
|
|||
null
|
||||
}
|
||||
}
|
||||
is StakingBalance.Data.P2P -> {
|
||||
is StakingBalance.Data.P2PEthPool -> {
|
||||
// TODO p2p
|
||||
val isCurrentAddressStaking = stakingBalance.stakingId.address == address.defaultAddress.value
|
||||
if (isCurrentAddressStaking) stakingBalance else null
|
||||
|
|
|
|||
|
|
@ -428,10 +428,10 @@ class CryptoCurrencyStatusFactoryTest {
|
|||
source = StatusSource.ACTUAL,
|
||||
balance = YieldBalanceItem(
|
||||
items = listOf(
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.token.coinGeckoId } returns currency.id.rawCurrencyId?.value
|
||||
},
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.token.coinGeckoId } returns "unknown"
|
||||
},
|
||||
),
|
||||
|
|
@ -525,10 +525,10 @@ class CryptoCurrencyStatusFactoryTest {
|
|||
source = StatusSource.ACTUAL,
|
||||
balance = YieldBalanceItem(
|
||||
items = listOf(
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.token.coinGeckoId } returns currency.id.rawCurrencyId?.value
|
||||
},
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.token.coinGeckoId } returns "unknown"
|
||||
},
|
||||
),
|
||||
|
|
@ -614,10 +614,10 @@ class CryptoCurrencyStatusFactoryTest {
|
|||
source = StatusSource.ACTUAL,
|
||||
balance = YieldBalanceItem(
|
||||
items = listOf(
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.token.coinGeckoId } returns currency.id.rawCurrencyId?.value
|
||||
},
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.token.coinGeckoId } returns "unknown"
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -589,11 +589,11 @@ class TotalFiatBalanceCalculatorTest {
|
|||
|
||||
private fun createStakeKitBalance(amount: BigDecimal, balanceType: BalanceType): StakingBalance.Data.StakeKit {
|
||||
return StakingBalance.Data.StakeKit(
|
||||
stakingId = mockk(),
|
||||
stakingId = mockk(relaxed = true),
|
||||
source = StatusSource.ACTUAL,
|
||||
balance = YieldBalanceItem(
|
||||
items = listOf(
|
||||
mockk<BalanceItem> {
|
||||
mockk<BalanceItem>(relaxed = true) {
|
||||
every { this@mockk.amount } returns amount
|
||||
every { this@mockk.type } returns balanceType
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue