diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 40ba3c5084..ca10e45669 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -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, ) } diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 23e161aaa8..07650632f0 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -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") } } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt index 633fd20cd2..1d8fc5ffc1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/UnknownEnumMoshiAdapter.kt @@ -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 { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt index 4aae3792c7..6f1df5b3f4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt @@ -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 + ): ApiResponse @GET("yields/balances") suspend fun getMultipleYieldBalances( @Body body: List, - ): ApiResponse> + ): ApiResponse> @GET("yields/{integrationId}/balances") suspend fun getSingleYieldBalance( @Path("integrationId") integrationId: String, @Body body: YieldBalanceRequestBody, - ): ApiResponse + ): ApiResponse @GET("tokens") - suspend fun getTokens(): ApiResponse> + suspend fun getTokens(): ApiResponse> } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnabledYieldsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnabledYieldsResponse.kt index c4a958c435..15605bc27c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnabledYieldsResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnabledYieldsResponse.kt @@ -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, + val data: List, @Json(name = "hasNextPage") val hasNextPage: Boolean, @Json(name = "limit") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/AddressArgument.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/AddressArgumentDTO.kt similarity index 92% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/AddressArgument.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/AddressArgumentDTO.kt index 80c6347e26..7a4f33b288 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/AddressArgument.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/AddressArgumentDTO.kt @@ -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") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/StakingActionType.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/StakingActionTypeDTO.kt similarity index 95% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/StakingActionType.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/StakingActionTypeDTO.kt index 2d71d2a50a..5f310f04a8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/StakingActionType.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/StakingActionTypeDTO.kt @@ -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, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/Token.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenDTO.kt similarity index 97% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/Token.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenDTO.kt index 88d5d12b59..6321b54ecb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/Token.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenDTO.kt @@ -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, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenWithYield.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenWithYieldDTO.kt similarity index 75% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenWithYield.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenWithYieldDTO.kt index 2bc30185af..15182ef389 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenWithYield.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/TokenWithYieldDTO.kt @@ -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, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapper.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt similarity index 96% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapper.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt index adb7968856..c64bdd591d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapper.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldBalanceWrapperDTO.kt @@ -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, + val balances: List, @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, @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") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/Yield.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt similarity index 81% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/Yield.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt index bdbea0868c..3e0c439e09 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/Yield.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt @@ -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, + val tokens: List, @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, + val validators: List, @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, + val args: Map, ) { @JsonClass(generateAdapter = true) data class Addresses( @Json(name = "address") - val address: AddressArgument, + val address: AddressArgumentDTO, @Json(name = "additionalAddresses") - val additionalAddresses: Map? = null, + val additionalAddresses: Map? = 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, + val tokensDTO: List, @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") diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt index 50b1bacb2b..45e280e949 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt @@ -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()) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt new file mode 100644 index 0000000000..1cdb6c381c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt @@ -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>, +) : StakingYieldsStore { + + override suspend fun getSyncOrNull(): List? { + return dataStore.getSyncOrNull(STAKING_YIELDS_KEY) + } + + override suspend fun store(items: List) { + dataStore.store(STAKING_YIELDS_KEY, items) + } + + companion object { + private const val STAKING_YIELDS_KEY = "STAKING_YIELDS_KEY" + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingYieldsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingYieldsStore.kt new file mode 100644 index 0000000000..61f46cf15d --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingYieldsStore.kt @@ -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? + + suspend fun store(items: List) +} \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 1135d892dc..67531e72a0 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -117,6 +117,7 @@ Отклонить Перезагрузить Переименовать + Сохранить Сохранить изменения Искать Поиск токенов @@ -128,6 +129,7 @@ Поделиться Подписать Подписать и отправить + Стейкинг Начать Отправить Успешно @@ -169,6 +171,7 @@ Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить Остерегайтесь мошеннических токенов, они могут ничего не стоить Токены могут быть созданы кем угодно + Купить кошелек Tangem Чат Код доступа Перед сканированием карты вам нужно будет ввести правильный код доступа. @@ -186,6 +189,7 @@ Скрывать балансы жестом переворота Эмитент Подписано + Отправить отзыв Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования @@ -538,6 +542,8 @@ Забыть кошелек Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. Имя + Доступно + Стейкинг %s Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Революционный аппаратный кошелек До трех карт с одним кошельком @@ -579,9 +585,10 @@ У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. - Продажа средств станет доступной после завершения транзакции(-ий) в сети %s + Продажа средств станет доступной после завершения транзакции(-ий) в сети %s Отправка средств станет доступной после завершения транзакции(-ий) в сети %s В данный момент продажа %s недоступна. Следите за нашими обновлениями. + В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями. Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -668,7 +675,7 @@ Используйте %s или отсканируйте карту, чтобы разблокировать доступ к вашему кошельку Пожалуйста, выведите все средства из этого кошелька, сбросьте его к заводским настройкам и создайте новый. Доступ к текущему кошельку будет утерян. Ошибка активации - По решению разработчиков сети BNB, стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять свои активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении чтобы перевести их в cеть BNB Smart Chain. + По решению разработчиков сети BNB стандарт BEP-2 перестанет поддерживаться в июне 2024 года. Чтобы не потерять активы, их необходимо преобразовать в стандарт BEP-20. Используйте функцию обмена в приложении или сторонние сервисы, чтобы перевести средства в cеть BNB Smart Chain. Отключение сети BNB Beacon Chain Можно лучше Нравится diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 8bbaa40db7..130168cb3a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -116,6 +116,7 @@ Reject Reload Rename + Save Save changes Search Search tokens @@ -127,6 +128,8 @@ Share Sign Sign and send + Stake + Staking Start Submit Success @@ -168,6 +171,7 @@ Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. Be aware of adding scam tokens, they can cost nothing Note that tokens can be created by anyone + Buy Tangem Wallet Chat Access code You will have to submit the correct access code before scanning the card @@ -185,6 +189,7 @@ Flip-to-Hide Balances Issuer Signed + Send feedback Details Check your internet connection or switch to a different network Terms of service @@ -531,6 +536,18 @@ Forget wallet This will remove the wallet from the application. The wallet itself can be added again. Name + Available + Average Reward Rate + %s est. profit + Minimum Requirement + No rewards to claim + On stake + Reward claiming + Reward schedule + Staking %s + Unbonding Period + Warmup period + Rewards Store your crypto assets secure while keeping private keys contained in your card Revolutionary Hardware Wallet Up to 3 physical cards to one wallet @@ -575,6 +592,7 @@ Selling funds will be available once the pending transaction(s) in network %s is complete Sending funds will be available once the pending transaction(s) in network %s is complete Selling %s is not available at the moment. Please check our updates. + Staking %s is not available at the moment. Please check our updates. Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -661,7 +679,7 @@ Use %s or scan a card to unlock access to your wallet 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. Activation error - 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. + 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. BNB Beacon Chain will shut down Could be better Like it diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts index 83a8cccc87..3c16b7c878 100644 --- a/data/staking/build.gradle.kts +++ b/data/staking/build.gradle.kts @@ -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") } diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 0c5b731554..be421d5a75 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -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, 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? { + 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(), ) } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt index 3fa4fe1a33..06cbb55869 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt @@ -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 { +class StakingTokenConverter : Converter { - override fun convert(value: TokenWithYield): StakingTokenWithYield { + override fun convert(value: TokenWithYieldDTO): StakingTokenWithYield { return StakingTokenWithYield( token = StakingToken( name = value.token.name, diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt new file mode 100644 index 0000000000..d7300234f0 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt @@ -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 { + + 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 + } + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index 7663df6823..092e8cfc62 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -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, ) } diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index f6a93898bb..80ff7080b9 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt index 55c8d1b3fa..b8609b2d1d 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt @@ -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 { - return Either.catch { stakingRepository.fetchEnabledTokens() } + return either { + catch( + block = { stakingRepository.fetchEnabledYields() }, + catch = { StakingTokensError.DataError(it) }, + ) + } } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt index 18717a31f7..2cc658f615 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingAvailabilityUseCase.kt @@ -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) } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/error/StakingTokensError.kt b/domain/staking/src/main/java/com/tangem/domain/staking/error/StakingTokensError.kt new file mode 100644 index 0000000000..d97a592cbd --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/error/StakingTokensError.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.staking.error + +sealed class StakingTokensError { + + data class DataError(val cause: Throwable) : StakingTokensError() +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingAvailability.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingAvailability.kt index bf40ee3b58..d7eaa306e0 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingAvailability.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingAvailability.kt @@ -5,4 +5,6 @@ sealed class StakingAvailability { data class Available(val integrationId: String) : StakingAvailability() data object Unavailable : StakingAvailability() + + data object TemporaryDisabled : StakingAvailability() } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/Yield.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/Yield.kt new file mode 100644 index 0000000000..3ef85e77dd --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/Yield.kt @@ -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, + val args: Args, + val status: Status, + val apy: BigDecimal, + val rewardRate: Double, + val rewardType: RewardType, + val metadata: Metadata, + val validators: List, + 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, + ) { + data class Addresses( + val address: AddressArgument, + val additionalAddresses: Map? = 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, + 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, +) \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 5e84852870..eafbb514fb 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -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 } \ No newline at end of file diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index de3ef835c5..56b3e441ab 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -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) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index e521d035af..ffef6ff04d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -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 + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index 54db16bf92..ead2d06c56 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -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, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt index 372f1e51d5..a3dbea0959 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenActionsState.kt @@ -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() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 470cc0ac26..4b3900f2c3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -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(), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 26f36f81ad..d1ec291d51 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -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, ) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt index c5b6078527..d1ef7792c8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt @@ -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, + ), + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt index dbf8a99f3c..afe2413ce6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt @@ -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, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index 2c29e74ce3..9cc42f3c2d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -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, ) : Converter { 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(), ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index af67c4e57d..cec107a630 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.flow.MutableStateFlow internal class TokenDetailsStateFactory( private val currentStateProvider: Provider, private val appCurrencyProvider: Provider, - private val stakingAvailabilityProvider: Provider, 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): 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( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index d9eb9ec18d..ddcb006eae 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -135,7 +135,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { ) } - if (state.isStakingAvailable) { + if (state.isStakingBlockShown) { item( key = StakingBlockState::class.java, contentType = StakingBlockState::class.java, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 751b8dcd92..5de4fa2c87 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -12,6 +12,8 @@ interface TokenDetailsClickIntents { fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) + fun onStakeClick(unavailabilityReason: ScenarioUnavailabilityReason) + fun onSendClick(unavailabilityReason: ScenarioUnavailabilityReason) fun onSwapClick(unavailabilityReason: ScenarioUnavailabilityReason) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 610f053855..9bcb6d6a55 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -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( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt index afbd667405..19da0ca6a5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt @@ -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 * diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt index 0f6d520acf..58ceb2ac53 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetRefreshStateTransformer.kt @@ -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 } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt index fa7478a428..417ff93ede 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt @@ -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 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 318fddb6c3..606a27ad3e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -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(