Updated on 2026-08-14
This commit is contained in:
parent
27a864a0a9
commit
0e29df4ce9
45 changed files with 731 additions and 137 deletions
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -190,6 +191,7 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCryptoCurrencyActionsUseCase {
|
||||
return GetCryptoCurrencyActionsUseCase(
|
||||
|
|
@ -199,6 +201,7 @@ internal object TokensDomainModule {
|
|||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ internal class MainViewModel @Inject constructor(
|
|||
private fun fetchStakingTokens() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
fetchStakingTokensUseCase()
|
||||
.onLeft { Timber.e("Unable to fetch the staking tokens list") }
|
||||
.onLeft { Timber.e(it, "Unable to fetch the staking tokens list") }
|
||||
.onRight { Timber.d("Staking token list was fetched successfully") }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.tangem.datasource.api.common.adapter
|
|||
|
||||
import com.squareup.moshi.*
|
||||
import com.squareup.moshi.adapters.EnumJsonAdapter
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.StakingActionType
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Token
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Yield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapper
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.StakingActionTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
|
||||
/**
|
||||
* Object to create a adapter for enum types with support for unknown enum values.
|
||||
|
|
@ -20,10 +20,11 @@ object UnknownEnumMoshiAdapter {
|
|||
|
||||
fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
|
||||
val map = mapOf(
|
||||
Token.NetworkType::class.java to Token.NetworkType.UNKNOWN,
|
||||
StakingActionType::class.java to StakingActionType.UNKNOWN,
|
||||
Yield.RewardType::class.java to Yield.RewardType.UNKNOWN,
|
||||
YieldBalanceWrapper.Balance.BalanceType::class.java to YieldBalanceWrapper.Balance.BalanceType.UNKNOWN,
|
||||
TokenDTO.NetworkTypeDTO::class.java to TokenDTO.NetworkTypeDTO.UNKNOWN,
|
||||
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
|
||||
YieldDTO.RewardTypeDTO::class.java to YieldDTO.RewardTypeDTO.UNKNOWN,
|
||||
YieldBalanceWrapperDTO.BalanceDTO.BalanceType::class.java to
|
||||
YieldBalanceWrapperDTO.BalanceDTO.BalanceType.UNKNOWN,
|
||||
)
|
||||
|
||||
return apply {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
|
|||
import com.tangem.datasource.api.stakekit.models.request.RevenueOption
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldType
|
||||
import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Yield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapper
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
|
@ -30,19 +30,19 @@ interface StakeKitApi {
|
|||
suspend fun getSingleYield(
|
||||
@Path("integrationId") integrationId: String,
|
||||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean = false,
|
||||
): ApiResponse<Yield>
|
||||
): ApiResponse<YieldDTO>
|
||||
|
||||
@GET("yields/balances")
|
||||
suspend fun getMultipleYieldBalances(
|
||||
@Body body: List<YieldBalanceRequestBody>,
|
||||
): ApiResponse<List<YieldBalanceWrapper>>
|
||||
): ApiResponse<List<YieldBalanceWrapperDTO>>
|
||||
|
||||
@GET("yields/{integrationId}/balances")
|
||||
suspend fun getSingleYieldBalance(
|
||||
@Path("integrationId") integrationId: String,
|
||||
@Body body: YieldBalanceRequestBody,
|
||||
): ApiResponse<YieldBalanceWrapper>
|
||||
): ApiResponse<YieldBalanceWrapperDTO>
|
||||
|
||||
@GET("tokens")
|
||||
suspend fun getTokens(): ApiResponse<List<TokenWithYield>>
|
||||
suspend fun getTokens(): ApiResponse<List<TokenWithYieldDTO>>
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@ package com.tangem.datasource.api.stakekit.models.response
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Yield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EnabledYieldsResponse(
|
||||
@Json(name = "data")
|
||||
val data: List<Yield>,
|
||||
val data: List<YieldDTO>,
|
||||
@Json(name = "hasNextPage")
|
||||
val hasNextPage: Boolean,
|
||||
@Json(name = "limit")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AddressArgument(
|
||||
data class AddressArgumentDTO(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "network")
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.datasource.api.stakekit.models.response.model
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
enum class StakingActionType {
|
||||
enum class StakingActionTypeDTO {
|
||||
@Json(name = "STAKE")
|
||||
STAKE,
|
||||
|
||||
|
|
@ -4,9 +4,9 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Token(
|
||||
data class TokenDTO(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "network") val network: NetworkType,
|
||||
@Json(name = "network") val network: NetworkTypeDTO,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "decimals") val decimals: Int,
|
||||
@Json(name = "address") val address: String?,
|
||||
|
|
@ -14,7 +14,7 @@ data class Token(
|
|||
@Json(name = "logoURI") val logoURI: String?,
|
||||
@Json(name = "isPoints") val isPoints: Boolean?,
|
||||
) {
|
||||
enum class NetworkType {
|
||||
enum class NetworkTypeDTO {
|
||||
@Json(name = "avalanche-c")
|
||||
AVALANCHE_C,
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenWithYield(
|
||||
@Json(name = "token") val token: Token,
|
||||
data class TokenWithYieldDTO(
|
||||
@Json(name = "token") val token: TokenDTO,
|
||||
@Json(name = "availableYields") val availableYieldIds: List<String>,
|
||||
)
|
||||
|
|
@ -6,15 +6,15 @@ import org.joda.time.DateTime
|
|||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldBalanceWrapper(
|
||||
data class YieldBalanceWrapperDTO(
|
||||
@Json(name = "balances")
|
||||
val balances: List<Balance>,
|
||||
val balances: List<BalanceDTO>,
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String?,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Balance(
|
||||
data class BalanceDTO(
|
||||
@Json(name = "groupId")
|
||||
val groupId: String,
|
||||
@Json(name = "type")
|
||||
|
|
@ -28,7 +28,7 @@ data class YieldBalanceWrapper(
|
|||
@Json(name = "pendingActions")
|
||||
val pendingActions: List<PendingAction>,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String?,
|
||||
@Json(name = "validatorAddresses")
|
||||
|
|
@ -68,7 +68,7 @@ data class YieldBalanceWrapper(
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class PendingAction(
|
||||
@Json(name = "type")
|
||||
val type: StakingActionType,
|
||||
val type: StakingActionTypeDTO,
|
||||
@Json(name = "passthrough")
|
||||
val passthrough: String,
|
||||
@Json(name = "args")
|
||||
|
|
@ -5,33 +5,33 @@ import com.squareup.moshi.JsonClass
|
|||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Yield(
|
||||
data class YieldDTO(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
val token: TokenDTO,
|
||||
@Json(name = "tokens")
|
||||
val tokens: List<Token>,
|
||||
val tokens: List<TokenDTO>,
|
||||
@Json(name = "args")
|
||||
val args: Args,
|
||||
val args: ArgsDTO,
|
||||
@Json(name = "status")
|
||||
val status: Status,
|
||||
val status: StatusDTO,
|
||||
@Json(name = "apy")
|
||||
val apy: BigDecimal,
|
||||
@Json(name = "rewardRate")
|
||||
val rewardRate: Double,
|
||||
@Json(name = "rewardType")
|
||||
val rewardType: RewardType,
|
||||
val rewardType: RewardTypeDTO,
|
||||
@Json(name = "metadata")
|
||||
val metadata: Metadata,
|
||||
val metadata: MetadataDTO,
|
||||
@Json(name = "validators")
|
||||
val validators: List<Validator>,
|
||||
val validators: List<ValidatorDTO>,
|
||||
@Json(name = "isAvailable")
|
||||
val isAvailable: Boolean,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Status(
|
||||
data class StatusDTO(
|
||||
@Json(name = "enter")
|
||||
val enter: Boolean,
|
||||
@Json(name = "exit")
|
||||
|
|
@ -39,7 +39,7 @@ data class Yield(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Args(
|
||||
data class ArgsDTO(
|
||||
@Json(name = "enter")
|
||||
val enter: Enter,
|
||||
@Json(name = "exit")
|
||||
|
|
@ -50,20 +50,20 @@ data class Yield(
|
|||
@Json(name = "addresses")
|
||||
val addresses: Addresses,
|
||||
@Json(name = "args")
|
||||
val args: Map<String, AddressArgument>,
|
||||
val args: Map<String, AddressArgumentDTO>,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Addresses(
|
||||
@Json(name = "address")
|
||||
val address: AddressArgument,
|
||||
val address: AddressArgumentDTO,
|
||||
@Json(name = "additionalAddresses")
|
||||
val additionalAddresses: Map<String, AddressArgument>? = null,
|
||||
val additionalAddresses: Map<String, AddressArgumentDTO>? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Validator(
|
||||
data class ValidatorDTO(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
@Json(name = "status")
|
||||
|
|
@ -87,7 +87,7 @@ data class Yield(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Metadata(
|
||||
data class MetadataDTO(
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "logoURI")
|
||||
|
|
@ -97,19 +97,19 @@ data class Yield(
|
|||
@Json(name = "documentation")
|
||||
val documentation: String?,
|
||||
@Json(name = "gasFeeToken")
|
||||
val gasFeeToken: Token,
|
||||
val gasFeeTokenDTO: TokenDTO,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "tokens")
|
||||
val tokens: List<Token>,
|
||||
val tokensDTO: List<TokenDTO>,
|
||||
@Json(name = "type")
|
||||
val type: String,
|
||||
@Json(name = "rewardSchedule")
|
||||
val rewardSchedule: String,
|
||||
@Json(name = "cooldownPeriod")
|
||||
val cooldownPeriod: Period,
|
||||
val cooldownPeriod: PeriodDTO,
|
||||
@Json(name = "warmupPeriod")
|
||||
val warmupPeriod: Period,
|
||||
val warmupPeriod: PeriodDTO,
|
||||
@Json(name = "rewardClaiming")
|
||||
val rewardClaiming: String,
|
||||
@Json(name = "defaultValidator")
|
||||
|
|
@ -119,25 +119,25 @@ data class Yield(
|
|||
@Json(name = "supportsMultipleValidators")
|
||||
val supportsMultipleValidators: Boolean,
|
||||
@Json(name = "revshare")
|
||||
val revshare: Enabled,
|
||||
val revshare: EnabledDTO,
|
||||
@Json(name = "fee")
|
||||
val fee: Enabled,
|
||||
val fee: EnabledDTO,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Period(
|
||||
data class PeriodDTO(
|
||||
@Json(name = "days")
|
||||
val days: Int,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Enabled(
|
||||
data class EnabledDTO(
|
||||
@Json(name = "enabled")
|
||||
val enabled: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
enum class RewardType {
|
||||
enum class RewardTypeDTO {
|
||||
@Json(name = "apy")
|
||||
APY, // compound rate
|
||||
@Json(name = "apr")
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.token.DefaultStakingTokensStore
|
||||
import com.tangem.datasource.local.token.StakingTokensStore
|
||||
import com.tangem.datasource.local.token.DefaultStakingYieldsStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,7 +15,7 @@ internal object StakingTokensStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingTokensStore(): StakingTokensStore {
|
||||
return DefaultStakingTokensStore(dataStore = RuntimeDataStore())
|
||||
fun provideStakingTokensStore(): StakingYieldsStore {
|
||||
return DefaultStakingYieldsStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
|
||||
internal class DefaultStakingYieldsStore(
|
||||
private val dataStore: StringKeyDataStore<List<YieldDTO>>,
|
||||
) : StakingYieldsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_YIELDS_KEY)
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldDTO>) {
|
||||
dataStore.store(STAKING_YIELDS_KEY, items)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STAKING_YIELDS_KEY = "STAKING_YIELDS_KEY"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
|
||||
interface StakingYieldsStore {
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldDTO>?
|
||||
|
||||
suspend fun store(items: List<YieldDTO>)
|
||||
}
|
||||
|
|
@ -117,6 +117,7 @@
|
|||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
<string name="common_rename">Переименовать</string>
|
||||
<string name="common_save">Сохранить</string>
|
||||
<string name="common_save_changes">Сохранить изменения</string>
|
||||
<string name="common_search">Искать</string>
|
||||
<string name="common_search_tokens">Поиск токенов</string>
|
||||
|
|
@ -128,6 +129,7 @@
|
|||
<string name="common_share">Поделиться</string>
|
||||
<string name="common_sign">Подписать</string>
|
||||
<string name="common_sign_and_send">Подписать и отправить</string>
|
||||
<string name="common_staking">Стейкинг</string>
|
||||
<string name="common_start">Начать</string>
|
||||
<string name="common_submit">Отправить</string>
|
||||
<string name="common_success">Успешно</string>
|
||||
|
|
@ -169,6 +171,7 @@
|
|||
<string name="custom_token_validation_error_not_found">Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить</string>
|
||||
<string name="custom_token_validation_error_not_found_description">Остерегайтесь мошеннических токенов, они могут ничего не стоить</string>
|
||||
<string name="custom_token_validation_error_not_found_title">Токены могут быть созданы кем угодно</string>
|
||||
<string name="details_buy_wallet">Купить кошелек Tangem</string>
|
||||
<string name="details_chat">Чат</string>
|
||||
<string name="details_manage_security_access_code">Код доступа</string>
|
||||
<string name="details_manage_security_access_code_description">Перед сканированием карты вам нужно будет ввести правильный код доступа.</string>
|
||||
|
|
@ -186,6 +189,7 @@
|
|||
<string name="details_row_title_flip_to_hide">Скрывать балансы жестом переворота</string>
|
||||
<string name="details_row_title_issuer">Эмитент</string>
|
||||
<string name="details_row_title_signed_hashes">Подписано</string>
|
||||
<string name="details_send_feedback">Отправить отзыв</string>
|
||||
<string name="details_title">Подробности</string>
|
||||
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
|
||||
<string name="disclaimer_title">Условия использования</string>
|
||||
|
|
@ -538,6 +542,8 @@
|
|||
<string name="settings_forget_wallet">Забыть кошелек</string>
|
||||
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
|
||||
<string name="settings_wallet_name_title">Имя</string>
|
||||
<string name="staking_details_available">Доступно</string>
|
||||
<string name="staking_details_title">Стейкинг %s</string>
|
||||
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
|
||||
<string name="story_awe_title">Революционный аппаратный кошелек</string>
|
||||
<string name="story_backup_description">До трех карт с одним кошельком</string>
|
||||
|
|
@ -579,9 +585,10 @@
|
|||
<string name="token_button_unavailability_reason_empty_balance_sell">У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями.</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">Отправка средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">В данный момент продажа %s недоступна. Следите за нашими обновлениями.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями.</string>
|
||||
<string name="token_details_generate_xpub">Сгенерировать XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Скрыть</string>
|
||||
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
|
||||
|
|
@ -668,7 +675,7 @@
|
|||
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку</string>
|
||||
<string name="warning_backup_errors_message">Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян.</string>
|
||||
<string name="warning_backup_errors_title">Ошибка активации</string>
|
||||
<string name="warning_beacon_chain_retirement_content">По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain.</string>
|
||||
<string name="warning_beacon_chain_retirement_content">По решению разработчиков сети BNB стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении или сторонние сервисы, чтобы перевести средства в cеть BNB Smart Chain.</string>
|
||||
<string name="warning_beacon_chain_retirement_title">Отключение сети BNB Beacon Chain</string>
|
||||
<string name="warning_button_could_be_better">Можно лучше</string>
|
||||
<string name="warning_button_like_it">Нравится</string>
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@
|
|||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
<string name="common_rename">Rename</string>
|
||||
<string name="common_save">Save</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
<string name="common_search">Search</string>
|
||||
<string name="common_search_tokens">Search tokens</string>
|
||||
|
|
@ -127,6 +128,8 @@
|
|||
<string name="common_share">Share</string>
|
||||
<string name="common_sign">Sign</string>
|
||||
<string name="common_sign_and_send">Sign and send</string>
|
||||
<string name="common_stake">Stake</string>
|
||||
<string name="common_staking">Staking</string>
|
||||
<string name="common_start">Start</string>
|
||||
<string name="common_submit">Submit</string>
|
||||
<string name="common_success">Success</string>
|
||||
|
|
@ -168,6 +171,7 @@
|
|||
<string name="custom_token_validation_error_not_found">Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing.</string>
|
||||
<string name="custom_token_validation_error_not_found_description">Be aware of adding scam tokens, they can cost nothing</string>
|
||||
<string name="custom_token_validation_error_not_found_title">Note that tokens can be created by anyone</string>
|
||||
<string name="details_buy_wallet">Buy Tangem Wallet</string>
|
||||
<string name="details_chat">Chat</string>
|
||||
<string name="details_manage_security_access_code">Access code</string>
|
||||
<string name="details_manage_security_access_code_description">You will have to submit the correct access code before scanning the card</string>
|
||||
|
|
@ -185,6 +189,7 @@
|
|||
<string name="details_row_title_flip_to_hide">Flip-to-Hide Balances</string>
|
||||
<string name="details_row_title_issuer">Issuer</string>
|
||||
<string name="details_row_title_signed_hashes">Signed</string>
|
||||
<string name="details_send_feedback">Send feedback</string>
|
||||
<string name="details_title">Details</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="disclaimer_title">Terms of service</string>
|
||||
|
|
@ -531,6 +536,18 @@
|
|||
<string name="settings_forget_wallet">Forget wallet</string>
|
||||
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
|
||||
<string name="settings_wallet_name_title">Name</string>
|
||||
<string name="staking_details_available">Available</string>
|
||||
<string name="staking_details_average_reward_rate">Average Reward Rate</string>
|
||||
<string name="staking_details_estimated_profit">%s est. profit</string>
|
||||
<string name="staking_details_minimum_requirement">Minimum Requirement</string>
|
||||
<string name="staking_details_no_rewards_to_claim">No rewards to claim</string>
|
||||
<string name="staking_details_on_stake">On stake</string>
|
||||
<string name="staking_details_reward_claiming">Reward claiming</string>
|
||||
<string name="staking_details_reward_schedule">Reward schedule</string>
|
||||
<string name="staking_details_title">Staking %s</string>
|
||||
<string name="staking_details_unbonding_period">Unbonding Period</string>
|
||||
<string name="staking_details_warmup_period">Warmup period</string>
|
||||
<string name="staking_rewards">Rewards</string>
|
||||
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
|
||||
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
|
||||
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
|
||||
|
|
@ -575,6 +592,7 @@
|
|||
<string name="token_button_unavailability_reason_pending_transaction_sell">Selling funds will be available once the pending transaction(s) in network %s is complete</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not available at the moment. Please check our updates.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s is not available at the moment. Please check our updates.</string>
|
||||
<string name="token_details_generate_xpub">Generate XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Hide</string>
|
||||
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
|
||||
|
|
@ -661,7 +679,7 @@
|
|||
<string name="warning_access_denied_message">Use %s or scan a card to unlock access to your wallet</string>
|
||||
<string name="warning_backup_errors_message">Please withdraw all funds from this wallet, reset it to factory settings, and create a new one. Access to the current wallet will be lost.</string>
|
||||
<string name="warning_backup_errors_title">Activation error</string>
|
||||
<string name="warning_beacon_chain_retirement_content">According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service to transfer them to the BNB Smart Chain network.</string>
|
||||
<string name="warning_beacon_chain_retirement_content">According to BNB network developers, support for the BEP-2 standard will end in June 2024. To avoid losing assets with this standard, please convert them to the BEP-20 standard. Use our swap service or third-party services to transfer funds to the BNB Smart Chain network.</string>
|
||||
<string name="warning_beacon_chain_retirement_title">BNB Beacon Chain will shut down</string>
|
||||
<string name="warning_button_could_be_better">Could be better</string>
|
||||
<string name="warning_button_like_it">Like it</string>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ dependencies {
|
|||
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.features.staking.api)
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ dependencies {
|
|||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package com.tangem.data.staking
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.data.staking.converters.StakingTokenConverter
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.data.staking.converters.YieldConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.local.token.StakingTokensStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.Yield
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -15,65 +18,90 @@ import kotlinx.coroutines.withContext
|
|||
internal class DefaultStakingRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val stakingTokenStore: StakingTokensStore,
|
||||
private val stakingYieldsStore: StakingYieldsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : StakingRepository {
|
||||
|
||||
private val tokenWithYieldConverter = StakingTokenConverter()
|
||||
private val yieldConverter = YieldConverter()
|
||||
|
||||
override suspend fun getStakingAvailabilityForActions(
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
symbol: String,
|
||||
): StakingAvailability {
|
||||
val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: return StakingAvailability.Unavailable
|
||||
|
||||
override fun getStakingAvailability(blockchainId: String): StakingAvailability {
|
||||
if (!stakingFeatureToggles.isStakingEnabled) {
|
||||
return StakingAvailability.Unavailable
|
||||
}
|
||||
return withContext(dispatchers.io) {
|
||||
val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
|
||||
|
||||
return integrationIdMap[Blockchain.fromId(blockchainId)]?.let {
|
||||
StakingAvailability.Available(it)
|
||||
} ?: StakingAvailability.Unavailable
|
||||
}
|
||||
val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, symbol)
|
||||
val isSupported = isStakingSupported(rawCurrencyId)
|
||||
|
||||
override suspend fun getEntryInfo(integrationId: String): StakingEntryInfo {
|
||||
val yield = stakeKitApi.getSingleYield(integrationId).getOrThrow()
|
||||
|
||||
return StakingEntryInfo(
|
||||
interestRate = yield.apy,
|
||||
periodInDays = yield.metadata.cooldownPeriod.days,
|
||||
tokenSymbol = yield.token.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun fetchEnabledTokens() {
|
||||
withContext(dispatchers.io) {
|
||||
val stakingTokensWithYields = stakeKitApi.getTokens().getOrThrow()
|
||||
|
||||
stakingTokenStore.store(stakingTokensWithYields.map { tokenWithYieldConverter.convert(it) })
|
||||
when {
|
||||
prefetchedYield != null && isSupported -> {
|
||||
StakingAvailability.Available(prefetchedYield.id)
|
||||
}
|
||||
prefetchedYield == null && isSupported -> {
|
||||
StakingAvailability.TemporaryDisabled
|
||||
}
|
||||
else -> StakingAvailability.Unavailable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
|
||||
private const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
|
||||
private const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking"
|
||||
private const val ETHEREUM_INTEGRATION_ID = "ethereum-matic-native-staking"
|
||||
private const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking"
|
||||
private const val TRON_INTEGRATION_ID = "tron-trx-native-staking"
|
||||
private const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking"
|
||||
private const val BINANCE_INTEGRATION_ID = "binance-bnb-native-staking"
|
||||
private const val KAVA_INTEGRATION_ID = "kava-kava-native-staking"
|
||||
private const val NEAR_INTEGRATION_ID = "near-near-native-staking"
|
||||
private const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking"
|
||||
private fun findPrefetchedYield(yields: List<Yield>, currencyId: String, symbol: String): Yield? {
|
||||
return yields
|
||||
.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
|
||||
}
|
||||
|
||||
private val integrationIdMap = mapOf(
|
||||
Blockchain.Solana to SOLANA_INTEGRATION_ID,
|
||||
Blockchain.Cosmos to COSMOS_INTEGRATION_ID,
|
||||
Blockchain.Polkadot to POLKADOT_INTEGRATION_ID,
|
||||
Blockchain.Polygon to ETHEREUM_INTEGRATION_ID,
|
||||
Blockchain.Avalanche to AVALANCHE_INTEGRATION_ID,
|
||||
Blockchain.Tron to TRON_INTEGRATION_ID,
|
||||
Blockchain.Cronos to CRONOS_INTEGRATION_ID,
|
||||
Blockchain.Binance to BINANCE_INTEGRATION_ID,
|
||||
Blockchain.Kava to KAVA_INTEGRATION_ID,
|
||||
Blockchain.Near to NEAR_INTEGRATION_ID,
|
||||
Blockchain.Tezos to TEZOS_INTEGRATION_ID,
|
||||
override fun isStakingSupported(currencyId: String): Boolean {
|
||||
return integrationIds.contains(currencyId)
|
||||
}
|
||||
|
||||
override suspend fun getEntryInfo(integrationId: String): StakingEntryInfo {
|
||||
return withContext(dispatchers.io) {
|
||||
val yield = stakeKitApi.getSingleYield(integrationId).getOrThrow()
|
||||
|
||||
StakingEntryInfo(
|
||||
interestRate = yield.apy,
|
||||
periodInDays = yield.metadata.cooldownPeriod.days,
|
||||
tokenSymbol = yield.token.symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchEnabledYields() {
|
||||
if (!stakingFeatureToggles.isStakingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
val stakingTokensWithYields = stakeKitApi.getMultipleYields().getOrThrow()
|
||||
|
||||
stakingYieldsStore.store(stakingTokensWithYields.data)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEnabledYields(): List<Yield>? {
|
||||
val yields = stakingYieldsStore.getSyncOrNull() ?: return null
|
||||
return yields.map { yieldConverter.convert(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val integrationIds = setOf(
|
||||
Blockchain.Solana.toCoinId(),
|
||||
Blockchain.Cosmos.toCoinId(),
|
||||
Blockchain.Polkadot.toCoinId(),
|
||||
Blockchain.Polygon.toCoinId(),
|
||||
Blockchain.Avalanche.toCoinId(),
|
||||
Blockchain.Tron.toCoinId(),
|
||||
Blockchain.Cronos.toCoinId(),
|
||||
Blockchain.Binance.toCoinId(),
|
||||
Blockchain.Kava.toCoinId(),
|
||||
Blockchain.Near.toCoinId(),
|
||||
Blockchain.Tezos.toCoinId(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
|
||||
import com.tangem.domain.staking.model.StakingToken
|
||||
import com.tangem.domain.staking.model.StakingTokenWithYield
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class StakingTokenConverter : Converter<TokenWithYield, StakingTokenWithYield> {
|
||||
class StakingTokenConverter : Converter<TokenWithYieldDTO, StakingTokenWithYield> {
|
||||
|
||||
override fun convert(value: TokenWithYield): StakingTokenWithYield {
|
||||
override fun convert(value: TokenWithYieldDTO): StakingTokenWithYield {
|
||||
return StakingTokenWithYield(
|
||||
token = StakingToken(
|
||||
name = value.token.name,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
package com.tangem.data.staking.converters
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.domain.staking.model.*
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class YieldConverter : Converter<YieldDTO, Yield> {
|
||||
|
||||
override fun convert(value: YieldDTO): Yield {
|
||||
return Yield(
|
||||
id = value.id,
|
||||
token = convertToken(value.token),
|
||||
tokens = value.tokens.map { convertToken(it) },
|
||||
args = convertArgs(value.args),
|
||||
status = convertStatus(value.status),
|
||||
apy = value.apy,
|
||||
rewardRate = value.rewardRate,
|
||||
rewardType = convertRewardType(value.rewardType),
|
||||
metadata = convertMetadata(value.metadata),
|
||||
validators = value.validators.map { convertValidator(it) },
|
||||
isAvailable = value.isAvailable,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToken(tokenDTO: TokenDTO): Token {
|
||||
return Token(
|
||||
name = tokenDTO.name,
|
||||
network = convertNetworkType(tokenDTO.network),
|
||||
symbol = tokenDTO.symbol,
|
||||
decimals = tokenDTO.decimals,
|
||||
address = tokenDTO.address,
|
||||
coinGeckoId = tokenDTO.coinGeckoId,
|
||||
logoURI = tokenDTO.logoURI,
|
||||
isPoints = tokenDTO.isPoints,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun convertNetworkType(networkTypeDTO: TokenDTO.NetworkTypeDTO): Token.NetworkType {
|
||||
return when (networkTypeDTO) {
|
||||
TokenDTO.NetworkTypeDTO.AVALANCHE_C -> Token.NetworkType.AVALANCHE_C
|
||||
TokenDTO.NetworkTypeDTO.AVALANCHE_ATOMIC -> Token.NetworkType.AVALANCHE_ATOMIC
|
||||
TokenDTO.NetworkTypeDTO.AVALANCHE_P -> Token.NetworkType.AVALANCHE_P
|
||||
TokenDTO.NetworkTypeDTO.ARBITRUM -> Token.NetworkType.ARBITRUM
|
||||
TokenDTO.NetworkTypeDTO.BINANCE -> Token.NetworkType.BINANCE
|
||||
TokenDTO.NetworkTypeDTO.CELO -> Token.NetworkType.CELO
|
||||
TokenDTO.NetworkTypeDTO.ETHEREUM -> Token.NetworkType.ETHEREUM
|
||||
TokenDTO.NetworkTypeDTO.ETHEREUM_GOERLI -> Token.NetworkType.ETHEREUM_GOERLI
|
||||
TokenDTO.NetworkTypeDTO.ETHEREUM_HOLESKY -> Token.NetworkType.ETHEREUM_HOLESKY
|
||||
TokenDTO.NetworkTypeDTO.FANTOM -> Token.NetworkType.FANTOM
|
||||
TokenDTO.NetworkTypeDTO.HARMONY -> Token.NetworkType.HARMONY
|
||||
TokenDTO.NetworkTypeDTO.OPTIMISM -> Token.NetworkType.OPTIMISM
|
||||
TokenDTO.NetworkTypeDTO.POLYGON -> Token.NetworkType.POLYGON
|
||||
TokenDTO.NetworkTypeDTO.GNOSIS -> Token.NetworkType.GNOSIS
|
||||
TokenDTO.NetworkTypeDTO.MOONRIVER -> Token.NetworkType.MOONRIVER
|
||||
TokenDTO.NetworkTypeDTO.OKC -> Token.NetworkType.OKC
|
||||
TokenDTO.NetworkTypeDTO.ZKSYNC -> Token.NetworkType.ZKSYNC
|
||||
TokenDTO.NetworkTypeDTO.VICTION -> Token.NetworkType.VICTION
|
||||
TokenDTO.NetworkTypeDTO.AGORIC -> Token.NetworkType.AGORIC
|
||||
TokenDTO.NetworkTypeDTO.AKASH -> Token.NetworkType.AKASH
|
||||
TokenDTO.NetworkTypeDTO.AXELAR -> Token.NetworkType.AXELAR
|
||||
TokenDTO.NetworkTypeDTO.BAND_PROTOCOL -> Token.NetworkType.BAND_PROTOCOL
|
||||
TokenDTO.NetworkTypeDTO.BITSONG -> Token.NetworkType.BITSONG
|
||||
TokenDTO.NetworkTypeDTO.CANTO -> Token.NetworkType.CANTO
|
||||
TokenDTO.NetworkTypeDTO.CHIHUAHUA -> Token.NetworkType.CHIHUAHUA
|
||||
TokenDTO.NetworkTypeDTO.COMDEX -> Token.NetworkType.COMDEX
|
||||
TokenDTO.NetworkTypeDTO.COREUM -> Token.NetworkType.COREUM
|
||||
TokenDTO.NetworkTypeDTO.COSMOS -> Token.NetworkType.COSMOS
|
||||
TokenDTO.NetworkTypeDTO.CRESCENT -> Token.NetworkType.CRESCENT
|
||||
TokenDTO.NetworkTypeDTO.CRONOS -> Token.NetworkType.CRONOS
|
||||
TokenDTO.NetworkTypeDTO.CUDOS -> Token.NetworkType.CUDOS
|
||||
TokenDTO.NetworkTypeDTO.DESMOS -> Token.NetworkType.DESMOS
|
||||
TokenDTO.NetworkTypeDTO.DYDX -> Token.NetworkType.DYDX
|
||||
TokenDTO.NetworkTypeDTO.EVMOS -> Token.NetworkType.EVMOS
|
||||
TokenDTO.NetworkTypeDTO.FETCH_AI -> Token.NetworkType.FETCH_AI
|
||||
TokenDTO.NetworkTypeDTO.GRAVITY_BRIDGE -> Token.NetworkType.GRAVITY_BRIDGE
|
||||
TokenDTO.NetworkTypeDTO.INJECTIVE -> Token.NetworkType.INJECTIVE
|
||||
TokenDTO.NetworkTypeDTO.IRISNET -> Token.NetworkType.IRISNET
|
||||
TokenDTO.NetworkTypeDTO.JUNO -> Token.NetworkType.JUNO
|
||||
TokenDTO.NetworkTypeDTO.KAVA -> Token.NetworkType.KAVA
|
||||
TokenDTO.NetworkTypeDTO.KI_NETWORK -> Token.NetworkType.KI_NETWORK
|
||||
TokenDTO.NetworkTypeDTO.MARS_PROTOCOL -> Token.NetworkType.MARS_PROTOCOL
|
||||
TokenDTO.NetworkTypeDTO.NYM -> Token.NetworkType.NYM
|
||||
TokenDTO.NetworkTypeDTO.OKEX_CHAIN -> Token.NetworkType.OKEX_CHAIN
|
||||
TokenDTO.NetworkTypeDTO.ONOMY -> Token.NetworkType.ONOMY
|
||||
TokenDTO.NetworkTypeDTO.OSMOSIS -> Token.NetworkType.OSMOSIS
|
||||
TokenDTO.NetworkTypeDTO.PERSISTENCE -> Token.NetworkType.PERSISTENCE
|
||||
TokenDTO.NetworkTypeDTO.QUICKSILVER -> Token.NetworkType.QUICKSILVER
|
||||
TokenDTO.NetworkTypeDTO.REGEN -> Token.NetworkType.REGEN
|
||||
TokenDTO.NetworkTypeDTO.SECRET -> Token.NetworkType.SECRET
|
||||
TokenDTO.NetworkTypeDTO.SENTINEL -> Token.NetworkType.SENTINEL
|
||||
TokenDTO.NetworkTypeDTO.SOMMELIER -> Token.NetworkType.SOMMELIER
|
||||
TokenDTO.NetworkTypeDTO.STAFI -> Token.NetworkType.STAFI
|
||||
TokenDTO.NetworkTypeDTO.STARGAZE -> Token.NetworkType.STARGAZE
|
||||
TokenDTO.NetworkTypeDTO.STRIDE -> Token.NetworkType.STRIDE
|
||||
TokenDTO.NetworkTypeDTO.TERITORI -> Token.NetworkType.TERITORI
|
||||
TokenDTO.NetworkTypeDTO.TGRADE -> Token.NetworkType.TGRADE
|
||||
TokenDTO.NetworkTypeDTO.UMEE -> Token.NetworkType.UMEE
|
||||
TokenDTO.NetworkTypeDTO.POLKADOT -> Token.NetworkType.POLKADOT
|
||||
TokenDTO.NetworkTypeDTO.KUSAMA -> Token.NetworkType.KUSAMA
|
||||
TokenDTO.NetworkTypeDTO.WESTEND -> Token.NetworkType.WESTEND
|
||||
TokenDTO.NetworkTypeDTO.BINANCEBEACON -> Token.NetworkType.BINANCEBEACON
|
||||
TokenDTO.NetworkTypeDTO.NEAR -> Token.NetworkType.NEAR
|
||||
TokenDTO.NetworkTypeDTO.SOLANA -> Token.NetworkType.SOLANA
|
||||
TokenDTO.NetworkTypeDTO.TEZOS -> Token.NetworkType.TEZOS
|
||||
TokenDTO.NetworkTypeDTO.TRON -> Token.NetworkType.TRON
|
||||
else -> Token.NetworkType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertArgs(argsDTO: YieldDTO.ArgsDTO): Yield.Args {
|
||||
return Yield.Args(
|
||||
enter = convertEnter(argsDTO.enter),
|
||||
exit = argsDTO.exit?.let { convertEnter(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertEnter(enterDTO: YieldDTO.ArgsDTO.Enter): Yield.Args.Enter {
|
||||
return Yield.Args.Enter(
|
||||
addresses = convertAddresses(enterDTO.addresses),
|
||||
args = enterDTO.args.mapValues { convertAddressArgument(it.value) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertAddresses(addressesDTO: YieldDTO.ArgsDTO.Enter.Addresses): Yield.Args.Enter.Addresses {
|
||||
return Yield.Args.Enter.Addresses(
|
||||
address = convertAddressArgument(addressesDTO.address),
|
||||
additionalAddresses = addressesDTO.additionalAddresses?.mapValues { convertAddressArgument(it.value) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertAddressArgument(addressArgumentDTO: AddressArgumentDTO): AddressArgument {
|
||||
return AddressArgument(
|
||||
required = addressArgumentDTO.required,
|
||||
network = addressArgumentDTO.network,
|
||||
minimum = addressArgumentDTO.minimum,
|
||||
maximum = addressArgumentDTO.maximum,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertStatus(statusDTO: YieldDTO.StatusDTO): Yield.Status {
|
||||
return Yield.Status(
|
||||
enter = statusDTO.enter,
|
||||
exit = statusDTO.exit,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertMetadata(metadataDTO: YieldDTO.MetadataDTO): Yield.Metadata {
|
||||
return Yield.Metadata(
|
||||
name = metadataDTO.name,
|
||||
logoUri = metadataDTO.logoUri,
|
||||
description = metadataDTO.description,
|
||||
documentation = metadataDTO.documentation,
|
||||
gasFeeToken = convertToken(metadataDTO.gasFeeTokenDTO),
|
||||
token = convertToken(metadataDTO.tokenDTO),
|
||||
tokens = metadataDTO.tokensDTO.map { convertToken(it) },
|
||||
type = metadataDTO.type,
|
||||
rewardSchedule = metadataDTO.rewardSchedule,
|
||||
cooldownPeriod = convertPeriod(metadataDTO.cooldownPeriod),
|
||||
warmupPeriod = convertPeriod(metadataDTO.warmupPeriod),
|
||||
rewardClaiming = metadataDTO.rewardClaiming,
|
||||
defaultValidator = metadataDTO.defaultValidator,
|
||||
minimumStake = metadataDTO.minimumStake,
|
||||
supportsMultipleValidators = metadataDTO.supportsMultipleValidators,
|
||||
revshare = convertEnabled(metadataDTO.revshare),
|
||||
fee = convertEnabled(metadataDTO.fee),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPeriod(periodDTO: YieldDTO.MetadataDTO.PeriodDTO): Yield.Metadata.Period {
|
||||
return Yield.Metadata.Period(
|
||||
days = periodDTO.days,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertEnabled(enabledDTO: YieldDTO.MetadataDTO.EnabledDTO): Yield.Metadata.Enabled {
|
||||
return Yield.Metadata.Enabled(
|
||||
enabled = enabledDTO.enabled,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO): Yield.Validator {
|
||||
return Yield.Validator(
|
||||
address = validatorDTO.address,
|
||||
status = validatorDTO.status,
|
||||
name = validatorDTO.name,
|
||||
image = validatorDTO.image,
|
||||
website = validatorDTO.website,
|
||||
apr = validatorDTO.apr,
|
||||
commission = validatorDTO.commission,
|
||||
stakedBalance = validatorDTO.stakedBalance,
|
||||
votingPower = validatorDTO.votingPower,
|
||||
preferred = validatorDTO.preferred,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): Yield.RewardType {
|
||||
return when (rewardTypeDTO) {
|
||||
YieldDTO.RewardTypeDTO.APY -> Yield.RewardType.APY
|
||||
YieldDTO.RewardTypeDTO.APR -> Yield.RewardType.APR
|
||||
else -> Yield.RewardType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.data.staking.di
|
|||
|
||||
import com.tangem.data.staking.DefaultStakingRepository
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.local.token.StakingTokensStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -21,13 +21,13 @@ internal object StakingDataModule {
|
|||
fun provideStakingRepository(
|
||||
stakeKitApi: StakeKitApi,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
stakingTokenStore: StakingTokensStore,
|
||||
stakingTokenStore: StakingYieldsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): StakingRepository {
|
||||
return DefaultStakingRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
stakingTokenStore = stakingTokenStore,
|
||||
stakingYieldsStore = stakingTokenStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.staking"
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(projects.domain.tokens.models)
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.domain.staking
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.staking.error.StakingTokensError
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
|
||||
/**
|
||||
|
|
@ -10,6 +13,11 @@ class FetchStakingTokensUseCase(
|
|||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
suspend operator fun invoke(): Either<Throwable, Unit> {
|
||||
return Either.catch { stakingRepository.fetchEnabledTokens() }
|
||||
return either {
|
||||
catch(
|
||||
block = { stakingRepository.fetchEnabledYields() },
|
||||
catch = { StakingTokensError.DataError(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,16 @@ package com.tangem.domain.staking
|
|||
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Use case for getting info about staking availability for certain blockchain.
|
||||
* Use case for getting info about staking capability in tangem app.
|
||||
*/
|
||||
class GetStakingAvailabilityUseCase(
|
||||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(blockchainNetworkId: String): StakingAvailability {
|
||||
return stakingRepository.getStakingAvailability(blockchainNetworkId)
|
||||
suspend operator fun invoke(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingAvailability {
|
||||
return stakingRepository.getStakingAvailabilityForActions(cryptoCurrencyId, symbol)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.staking.error
|
||||
|
||||
sealed class StakingTokensError {
|
||||
|
||||
data class DataError(val cause: Throwable) : StakingTokensError()
|
||||
}
|
||||
|
|
@ -5,4 +5,6 @@ sealed class StakingAvailability {
|
|||
data class Available(val integrationId: String) : StakingAvailability()
|
||||
|
||||
data object Unavailable : StakingAvailability()
|
||||
|
||||
data object TemporaryDisabled : StakingAvailability()
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package com.tangem.domain.staking.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class Yield(
|
||||
val id: String,
|
||||
val token: Token,
|
||||
val tokens: List<Token>,
|
||||
val args: Args,
|
||||
val status: Status,
|
||||
val apy: BigDecimal,
|
||||
val rewardRate: Double,
|
||||
val rewardType: RewardType,
|
||||
val metadata: Metadata,
|
||||
val validators: List<Validator>,
|
||||
val isAvailable: Boolean,
|
||||
) {
|
||||
data class Status(
|
||||
val enter: Boolean,
|
||||
val exit: Boolean?,
|
||||
)
|
||||
|
||||
data class Args(
|
||||
val enter: Enter,
|
||||
val exit: Enter?,
|
||||
) {
|
||||
data class Enter(
|
||||
val addresses: Addresses,
|
||||
val args: Map<String, AddressArgument>,
|
||||
) {
|
||||
data class Addresses(
|
||||
val address: AddressArgument,
|
||||
val additionalAddresses: Map<String, AddressArgument>? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Validator(
|
||||
val address: String,
|
||||
val status: String,
|
||||
val name: String,
|
||||
val image: String?,
|
||||
val website: String?,
|
||||
val apr: Double?,
|
||||
val commission: Double?,
|
||||
val stakedBalance: String?,
|
||||
val votingPower: Double?,
|
||||
val preferred: Boolean,
|
||||
)
|
||||
|
||||
data class Metadata(
|
||||
val name: String,
|
||||
val logoUri: String,
|
||||
val description: String,
|
||||
val documentation: String?,
|
||||
val gasFeeToken: Token,
|
||||
val token: Token,
|
||||
val tokens: List<Token>,
|
||||
val type: String,
|
||||
val rewardSchedule: String,
|
||||
val cooldownPeriod: Period,
|
||||
val warmupPeriod: Period,
|
||||
val rewardClaiming: String,
|
||||
val defaultValidator: String?,
|
||||
val minimumStake: Int?,
|
||||
val supportsMultipleValidators: Boolean,
|
||||
val revshare: Enabled,
|
||||
val fee: Enabled,
|
||||
) {
|
||||
data class Period(
|
||||
val days: Int,
|
||||
)
|
||||
|
||||
data class Enabled(
|
||||
val enabled: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
enum class RewardType {
|
||||
APY, // compound rate
|
||||
APR, // simple rate
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
data class Token(
|
||||
val name: String,
|
||||
val network: NetworkType,
|
||||
val symbol: String,
|
||||
val decimals: Int,
|
||||
val address: String?,
|
||||
val coinGeckoId: String?,
|
||||
val logoURI: String?,
|
||||
val isPoints: Boolean?,
|
||||
) {
|
||||
enum class NetworkType {
|
||||
AVALANCHE_C,
|
||||
AVALANCHE_ATOMIC,
|
||||
AVALANCHE_P,
|
||||
ARBITRUM,
|
||||
BINANCE,
|
||||
CELO,
|
||||
ETHEREUM,
|
||||
ETHEREUM_GOERLI,
|
||||
ETHEREUM_HOLESKY,
|
||||
FANTOM,
|
||||
HARMONY,
|
||||
OPTIMISM,
|
||||
POLYGON,
|
||||
GNOSIS,
|
||||
MOONRIVER,
|
||||
OKC,
|
||||
ZKSYNC,
|
||||
VICTION,
|
||||
AGORIC,
|
||||
AKASH,
|
||||
AXELAR,
|
||||
BAND_PROTOCOL,
|
||||
BITSONG,
|
||||
CANTO,
|
||||
CHIHUAHUA,
|
||||
COMDEX,
|
||||
COREUM,
|
||||
COSMOS,
|
||||
CRESCENT,
|
||||
CRONOS,
|
||||
CUDOS,
|
||||
DESMOS,
|
||||
DYDX,
|
||||
EVMOS,
|
||||
FETCH_AI,
|
||||
GRAVITY_BRIDGE,
|
||||
INJECTIVE,
|
||||
IRISNET,
|
||||
JUNO,
|
||||
KAVA,
|
||||
KI_NETWORK,
|
||||
MARS_PROTOCOL,
|
||||
NYM,
|
||||
OKEX_CHAIN,
|
||||
ONOMY,
|
||||
OSMOSIS,
|
||||
PERSISTENCE,
|
||||
QUICKSILVER,
|
||||
REGEN,
|
||||
SECRET,
|
||||
SENTINEL,
|
||||
SOMMELIER,
|
||||
STAFI,
|
||||
STARGAZE,
|
||||
STRIDE,
|
||||
TERITORI,
|
||||
TGRADE,
|
||||
UMEE,
|
||||
POLKADOT,
|
||||
KUSAMA,
|
||||
WESTEND,
|
||||
BINANCEBEACON,
|
||||
NEAR,
|
||||
SOLANA,
|
||||
TEZOS,
|
||||
TRON,
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
data class AddressArgument(
|
||||
val required: Boolean,
|
||||
val network: String? = null,
|
||||
val minimum: Double? = null,
|
||||
val maximum: Double? = null,
|
||||
)
|
||||
|
|
@ -2,12 +2,18 @@ package com.tangem.domain.staking.repositories
|
|||
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
interface StakingRepository {
|
||||
|
||||
fun getStakingAvailability(blockchainId: String): StakingAvailability
|
||||
suspend fun getStakingAvailabilityForActions(
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
symbol: String,
|
||||
): StakingAvailability
|
||||
|
||||
suspend fun getEntryInfo(integrationId: String): StakingEntryInfo
|
||||
|
||||
suspend fun fetchEnabledTokens()
|
||||
suspend fun fetchEnabledYields()
|
||||
|
||||
fun isStakingSupported(currencyId: String): Boolean
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.domain.tokens
|
|||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -28,6 +30,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -121,6 +124,17 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
activeList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
|
||||
// staking
|
||||
if (isStakingAvailable(cryptoCurrency)) {
|
||||
activeList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.None))
|
||||
} else {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// send
|
||||
val sendUnavailabilityReason = getSendUnavailabilityReason(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
|
|
@ -234,6 +248,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable))
|
||||
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
return actionsList
|
||||
}
|
||||
|
|
@ -264,4 +279,11 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean {
|
||||
return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()
|
||||
}
|
||||
|
||||
private suspend fun isStakingAvailable(cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return stakingRepository.getStakingAvailabilityForActions(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
) is StakingAvailability.Available
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@ package com.tangem.domain.tokens.model
|
|||
sealed class ScenarioUnavailabilityReason {
|
||||
data object None : ScenarioUnavailabilityReason()
|
||||
|
||||
// staking-specific
|
||||
data class StakingUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason()
|
||||
|
||||
// send&sell-specific
|
||||
data class PendingTransaction(
|
||||
val withdrawalScenario: WithdrawalScenario,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ data class TokenActionsState(
|
|||
|
||||
data class Receive(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Stake(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Swap(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ internal object TokenDetailsPreviewData {
|
|||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
isMarketPriceAvailable = false,
|
||||
isStakingAvailable = false,
|
||||
isStakingBlockShown = false,
|
||||
event = consumedEvent(),
|
||||
)
|
||||
|
||||
|
|
@ -296,7 +296,7 @@ internal object TokenDetailsPreviewData {
|
|||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
isMarketPriceAvailable = true,
|
||||
isStakingAvailable = true,
|
||||
isStakingBlockShown = true,
|
||||
event = consumedEvent(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,6 @@ internal data class TokenDetailsState(
|
|||
val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isMarketPriceAvailable: Boolean,
|
||||
val isStakingAvailable: Boolean,
|
||||
val isStakingBlockShown: Boolean,
|
||||
val event: StateEvent<TextReference>,
|
||||
)
|
||||
|
|
@ -91,4 +91,19 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) {
|
|||
dimContent = dimContent,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Staking
|
||||
*
|
||||
* @property dimContent determines whether the button content will be dimmed
|
||||
* @property onClick lambda be invoked when Swap button is clicked
|
||||
*/
|
||||
data class Stake(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_stake),
|
||||
iconResId = R.drawable.ic_arrow_down_24, // TODO staking
|
||||
onClick = onClick,
|
||||
dimContent = dimContent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -38,6 +38,12 @@ internal class TokenDetailsActionButtonsConverter(
|
|||
onLongClick = clickIntents::onCopyAddress,
|
||||
)
|
||||
}
|
||||
is TokenActionsState.ActionState.Stake -> {
|
||||
TokenDetailsActionButton.Stake(
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = { clickIntents.onStakeClick(action.unavailabilityReason) },
|
||||
)
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
TokenDetailsActionButton.Sell(
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
|
|
@ -17,7 +16,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.Toke
|
|||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -27,7 +25,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
internal class TokenDetailsSkeletonStateConverter(
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
|
||||
) : Converter<CryptoCurrency, TokenDetailsState> {
|
||||
|
||||
private val iconStateConverter by lazy { TokenDetailsIconStateConverter() }
|
||||
|
|
@ -67,7 +64,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
bottomSheetConfig = null,
|
||||
isBalanceHidden = true,
|
||||
isMarketPriceAvailable = value.id.rawCurrencyId != null,
|
||||
isStakingAvailable = stakingAvailabilityProvider.invoke() is StakingAvailability.Available,
|
||||
isStakingBlockShown = false,
|
||||
event = consumedEvent(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
internal class TokenDetailsStateFactory(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
symbol: String,
|
||||
|
|
@ -55,7 +54,6 @@ internal class TokenDetailsStateFactory(
|
|||
TokenDetailsSkeletonStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
featureToggles = featureToggles,
|
||||
stakingAvailabilityProvider = stakingAvailabilityProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -211,6 +209,12 @@ internal class TokenDetailsStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getStateWithUpdatedStakingAvailability(stakingAvailability: StakingAvailability): TokenDetailsState {
|
||||
return currentStateProvider().copy(
|
||||
isStakingBlockShown = stakingAvailability != StakingAvailability.Unavailable,
|
||||
)
|
||||
}
|
||||
|
||||
fun getStateWithStaking(stakingEither: Either<Throwable, StakingEntryInfo>): TokenDetailsState {
|
||||
return currentStateProvider().copy(
|
||||
stakingBlockState = stakingStateConverter.convert(stakingEither),
|
||||
|
|
@ -369,6 +373,12 @@ internal class TokenDetailsStateFactory(
|
|||
|
||||
private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference {
|
||||
return when (unavailabilityReason) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
|
|||
)
|
||||
}
|
||||
|
||||
if (state.isStakingAvailable) {
|
||||
if (state.isStakingBlockShown) {
|
||||
item(
|
||||
key = StakingBlockState::class.java,
|
||||
contentType = StakingBlockState::class.java,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ interface TokenDetailsClickIntents {
|
|||
|
||||
fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
|
||||
fun onStakeClick(unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
|
||||
fun onSendClick(unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
|
||||
fun onSwapClick(unavailabilityReason: ScenarioUnavailabilityReason)
|
||||
|
|
|
|||
|
|
@ -142,9 +142,6 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val stateFactory = TokenDetailsStateFactory(
|
||||
currentStateProvider = Provider { uiState },
|
||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
stakingAvailabilityProvider = Provider {
|
||||
getStakingAvailabilityUseCase.invoke(cryptoCurrency.network.id.value)
|
||||
},
|
||||
clickIntents = this,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
|
|
@ -369,7 +366,11 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
private fun updateStakingInfo() {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val stakingAvailability = getStakingAvailabilityUseCase(cryptoCurrency.network.id.value)
|
||||
val stakingAvailability = getStakingAvailabilityUseCase(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
uiState = stateFactory.getStateWithUpdatedStakingAvailability(stakingAvailability)
|
||||
if (stakingAvailability is StakingAvailability.Available) {
|
||||
val stakingInfo = getStakingEntryInfoUseCase(stakingAvailability.integrationId)
|
||||
uiState = stateFactory.getStateWithStaking(stakingInfo)
|
||||
|
|
@ -524,6 +525,10 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onStakeClick(unavailabilityReason: ScenarioUnavailabilityReason) {
|
||||
Timber.e("Not implemented yet")
|
||||
}
|
||||
|
||||
override fun onGenerateExtendedKey() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val extendedKey = getExtendedPublicKeyForCurrencyUseCase(
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
),
|
||||
)
|
||||
|
||||
data class Stake(
|
||||
override val enabled: Boolean,
|
||||
override val dimContent: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_stake),
|
||||
iconResId = R.drawable.ic_arrow_down_24, // TODO staking
|
||||
onClick = onClick,
|
||||
dimContent = dimContent,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Sell
|
||||
*
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ internal class SetRefreshStateTransformer(
|
|||
is WalletManageButton.Send -> button.copy(enabled = isButtonsEnabled)
|
||||
is WalletManageButton.Sell -> button.copy(enabled = isButtonsEnabled)
|
||||
is WalletManageButton.Receive -> button
|
||||
is WalletManageButton.Stake -> null
|
||||
is WalletManageButton.Swap -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
icon = R.drawable.ic_arrow_down_24
|
||||
action = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Stake -> {
|
||||
title = resourceReference(R.string.common_stake)
|
||||
icon = R.drawable.ic_arrow_down_24 // TODO staking replace icon
|
||||
action = { clickIntents.onStakeClick(cryptoCurrencyStatus) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
title = resourceReference(R.string.common_sell)
|
||||
icon = R.drawable.ic_currency_24
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
interface WalletCurrencyActionsClickIntents {
|
||||
|
|
@ -58,6 +59,8 @@ interface WalletCurrencyActionsClickIntents {
|
|||
|
||||
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
|
||||
|
||||
fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
|
@ -363,6 +366,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
showErrorIfDemoModeOrElse(action = ::openExplorer)
|
||||
}
|
||||
|
||||
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
// TODO staking
|
||||
Timber.e("Not implemented yet")
|
||||
}
|
||||
|
||||
private fun openExplorer() {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
|
|
@ -465,6 +473,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference {
|
||||
return when (unavailabilityReason) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue