Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-26 18:42:48 +07:00
commit bc30f9aa2e
19 changed files with 267 additions and 138 deletions

View file

@ -8,6 +8,7 @@ import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module
@ -237,7 +238,10 @@ internal object StakingDomainModule {
@Provides
@Singleton
fun provideStakingApyFlowUseCase(stakeKitRepository: StakeKitRepository): StakingApyFlowUseCase {
return StakingApyFlowUseCase(stakeKitRepository)
fun provideStakingApyFlowUseCase(
stakeKitRepository: StakeKitRepository,
stakingFeatureToggles: StakingFeatureToggles,
): StakingApyFlowUseCase {
return StakingApyFlowUseCase(stakeKitRepository, stakingFeatureToggles)
}
}

View file

@ -23,20 +23,22 @@ internal class DefaultStakingYieldsStore(
return withTimeoutOrNull(timeout = YIELDS_WAITING_TIMEOUT, block = { get().firstOrNull() })
}
override suspend fun store(items: List<YieldDTO>) {
dataStore.updateData { data ->
val updatedItems = data.toMutableList()
items.forEach { newItem ->
val existingItemIndex = data.indexOfFirst { it.id == newItem.id }
if (existingItemIndex != -1) {
// Update existing item
updatedItems[existingItemIndex] = newItem
} else {
// Add new item
updatedItems.add(newItem)
override suspend fun store(items: List<YieldDTO>, force: Boolean) {
if (force) {
dataStore.updateData { items }
} else {
dataStore.updateData { data ->
val updatedItems = data.toMutableList()
items.forEach { newItem ->
val existingItemIndex = data.indexOfFirst { it.id == newItem.id }
if (existingItemIndex != -1) {
updatedItems[existingItemIndex] = newItem
} else {
updatedItems.add(newItem)
}
}
updatedItems
}
updatedItems
}
}

View file

@ -11,5 +11,5 @@ interface StakingYieldsStore {
suspend fun getSyncWithTimeout(): List<YieldDTO>?
suspend fun store(items: List<YieldDTO>)
suspend fun store(items: List<YieldDTO>, force: Boolean = false)
}

View file

@ -54,6 +54,8 @@
<string name="account_unsaved_dialog_message_create">Bist Du sicher, dass Du das neue Konto verwerfen willst?</string>
<string name="account_unsaved_dialog_message_edit">Bist Du sicher, dass Du die Bearbeitungen verwerfen willst?</string>
<string name="account_unsaved_dialog_title">Nicht gespeicherte Änderungen</string>
<string name="accounts_migration_alert_message">Einige benutzerdefinierte Token wurden von „%1$s\" Zu \"%2$sda ihre Herleitung zu diesem Konto gehört.</string>
<string name="accounts_migration_alert_title">Einige benutzerdefinierte Token wurden verschoben.</string>
<string name="action_buttons_buy_empty_search_message">Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum kaufen im Portfolio hinzu.</string>
<string name="action_buttons_sell_empty_search_message">Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum verkaufen im Portfolio hinzu.</string>
<string name="action_buttons_sell_navigation_bar_title">Verkaufen</string>
@ -88,6 +90,7 @@
<string name="alert_manage_tokens_unsupported_message">Tokens im %1$s -Netzwerk werden von dieser Karte oder Ring aufgrund einer Firmware-Einschränkung nicht unterstützt.</string>
<string name="alert_troubleshooting_scan_card_title">Hast du Probleme beim Scannen deiner Karte oder Ring?</string>
<string name="alert_unsupported_card">Diese Karte oder Ring ist für die Zusammenarbeit mit Tangem nicht geeignet</string>
<string name="app_settings_access_code_warning">Lege zunächst einen Zugangscode fest, um die Biometrie zu aktivieren.</string>
<string name="app_settings_biometrics_footer">Verwende die %1$s um Deine Wallet zu entsperren und sensible Aktionen wie das Signieren von Transaktionen zu bestätigen. Bei Hardware-Wallets ist zum Signieren weiterhin eine Karte oder ein Ring erforderlich.</string>
<string name="app_settings_default_fee">Standardgebühr</string>
<string name="app_settings_default_fee_footer">Aktiviere die Option Standardgebühr, um die Transaktionsgebühren automatisch festzulegen und die Gebührenseite beim Senden von Geldern zu überspringen. Du kannst bei Bedarf jederzeit zu dieser Seite zurückkehren.</string>
@ -299,6 +302,7 @@
<item quantity="other">%1$s Netzwerke</item>
</plurals>
<string name="common_new_address">Neue Adresse</string>
<string name="common_news">Nachrichten</string>
<string name="common_next">Weiter</string>
<string name="common_nft">NFT</string>
<string name="common_no">Nein</string>
@ -364,7 +368,7 @@
<string name="common_transaction_failed">Transaktion fehlgeschlagen</string>
<string name="common_transaction_status">Transaktionsstatus</string>
<string name="common_transactions">Transaktionen</string>
<string name="common_transfer">Überweiseung</string>
<string name="common_transfer">Überweisung</string>
<string name="common_unable_to_load">Die Daten konnten nicht geladen werden…</string>
<string name="common_understand">Ich verstehe</string>
<string name="common_unknown_error">Es ist ein Fehler aufgetreten. Bitte versuche es erneut.</string>
@ -517,6 +521,8 @@
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaktions-ID kopiert</string>
<string name="exсhange_token_description">Tauschen jeden Vermögenswert in Deinem Portfolio gegen diesen Token</string>
<string name="feed_market_and_news">Markt &amp; Nachrichten</string>
<string name="feed_trending_now">Aktuelle Trends</string>
<string name="feedback_data_collection_message">Die folgenden Angaben sind freiwillig. Du kannst diese löschen, wenn du sie nicht weitergeben möchtest.</string>
<string name="feedback_preface_rate_negative">Teile uns mit, welche Funktionen du vermisst, und wir werden versuchen, dir zu helfen.</string>
<string name="feedback_preface_scan_failed">Bitte sag uns, welche Karte oder Ring du hast</string>
@ -768,6 +774,8 @@
<string name="markets_token_details_volume">Volumen</string>
<string name="markets_tooltip_message">Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen</string>
<string name="markets_tooltip_title">Token hinzufügen</string>
<string name="news_all_news">Alle Neuigkeiten</string>
<string name="news_stay_in_the_loop">Auf dem Laufenden bleiben</string>
<string name="nfc_error_unavailable">NFC ist auf deinem Gerät nicht verfügbar</string>
<string name="nft_about_title">Über NFT</string>
<string name="nft_asset">NFT-Vermögenswert</string>
@ -1370,6 +1378,7 @@
<string name="tangem_pay_status_completed">Abgeschlossen</string>
<string name="tangem_pay_status_declined">Abgelehnt</string>
<string name="tangem_pay_status_pending">Ausstehend</string>
<string name="tangem_pay_terms_limits">Bedingungen und Einschränkungen</string>
<string name="tangem_pay_transaction_declined_notification_text">Die Bank hat diese Transaktionsanfrage abgelehnt.</string>
<string name="tangem_pay_transaction_fee_notification_text">Diese Gebühr dient zur Deckung der Kosten für die Abwicklung Deiner Überweisung.</string>
<string name="tangem_pay_unfreeze_card_alert_body">Nutze Dein Geld weiterhin. Du kannst es jederzeit einfrieren.</string>
@ -1403,6 +1412,7 @@
<string name="tangempay_card_details_swap_description">Tausche beliebige Vermögenswerte in Deinem Portfolio gegen eine Karte.</string>
<string name="tangempay_card_details_title">Kartendetails</string>
<string name="tangempay_card_details_unfreeze_card">Karte entsperren</string>
<string name="tangempay_issue_card_notification_description">Es dauert in der Regel bis zu 15 Minuten.</string>
<string name="tangempay_kyc_card_ready_notification_title">Tangem Pay</string>
<string name="tangempay_kyc_in_progress_notification_button">Status anzeigen</string>
<string name="tangempay_kyc_in_progress_notification_title">KYC für Tangem Pay in Arbeit</string>
@ -1951,9 +1961,12 @@
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Ertragsmodus</string>
<string name="yield_module_token_details_earn_notification_processing">Bearbeitung Deiner Einzahlung</string>
<string name="yield_module_token_details_earn_notification_title">Ertragsmodus</string>
<string name="yield_module_transaction_enter">Ertragsmodus ein</string>
<string name="yield_module_transaction_exit">Ertragsmodus aus</string>
<string name="yield_module_transaction_topup">Aufstockung des Ertragsmodus</string>
<string name="yield_module_transaction_enter">Ertragsmodus aktivieren</string>
<string name="yield_module_transaction_enter_subtitle">%1$s geliefert an Aave</string>
<string name="yield_module_transaction_exit">Ertragsmodus deaktiviert</string>
<string name="yield_module_transaction_exit_subtitle">%1$s aus Aave zurücküberwiesen</string>
<string name="yield_module_transaction_topup">Lieferung an Aave</string>
<string name="yield_module_transaction_topup_subtitle">%1$s geliefert an Aave</string>
<string name="yield_module_transfer_mode_automatic">Automatisch</string>
<string name="yield_module_unable_to_cover_fee_description">Einzahlung von %1$s %2$s zur Deckung der Netzwerkgebühr für Transaktionen</string>
<string name="yield_module_unable_to_cover_fee_title">Die Gebühr %s kann nicht gedeckt werden</string>

View file

@ -1568,8 +1568,11 @@
<string name="yield_module_token_details_earn_notification_processing">Procesando su depósito</string>
<string name="yield_module_token_details_earn_notification_title">Modo Rendimiento</string>
<string name="yield_module_transaction_enter">Modo Rendimiento activado</string>
<string name="yield_module_transaction_enter_subtitle">%1$s suministrado a Aave</string>
<string name="yield_module_transaction_exit">Modo Rendimiento desactivado</string>
<string name="yield_module_transaction_topup">Recarga del modo Rendimiento</string>
<string name="yield_module_transaction_exit_subtitle">%1$s retirado de Aave</string>
<string name="yield_module_transaction_topup">Suministrar a Aave</string>
<string name="yield_module_transaction_topup_subtitle">%1$s suministrado a Aave</string>
<string name="yield_module_transfer_mode_automatic">Automático</string>
<string name="yield_module_unable_to_cover_fee_description">Deposite algo de %1$s %2$s para cubrir la tarifa de red para las transacciones</string>
<string name="yield_module_unable_to_cover_fee_title">No se puede cubrir la tarifa %s</string>

View file

@ -1547,8 +1547,11 @@
<string name="yield_module_token_details_earn_notification_processing">Traitement de votre dépôt</string>
<string name="yield_module_token_details_earn_notification_title">Mode Rendement</string>
<string name="yield_module_transaction_enter">Mode rendement activé</string>
<string name="yield_module_transaction_enter_subtitle">%1$s fourni à Aave</string>
<string name="yield_module_transaction_exit">Mode rendement désactivé</string>
<string name="yield_module_transaction_topup">Recharger le mode Rendement</string>
<string name="yield_module_transaction_exit_subtitle">%1$s retiré d\'Aave</string>
<string name="yield_module_transaction_topup">Approvisionnement à Aave</string>
<string name="yield_module_transaction_topup_subtitle">%1$s fourni à Aave</string>
<string name="yield_module_transfer_mode_automatic">Automatique</string>
<string name="yield_module_unable_to_cover_fee_description">Déposez %1$s %2$s pour couvrir les frais de réseau liés aux transactions.</string>
<string name="yield_module_unable_to_cover_fee_title">Impossible de couvrir les frais %s</string>

View file

@ -90,6 +90,7 @@
<string name="alert_manage_tokens_unsupported_message">%1$s ネットワークのトークンは、ファームウェアの制限により、このカードまたはリングではサポートされていません。</string>
<string name="alert_troubleshooting_scan_card_title">カードまたはリングのスキャンに問題がありますか?</string>
<string name="alert_unsupported_card">このカードはこのアプリでは使用できません。</string>
<string name="app_settings_access_code_warning">まずアクセスコードを設定してから、生体認証を有効にしてください</string>
<string name="app_settings_biometrics_footer">%1$sを使用してウォレットのロックを解除し、取引の署名などの機密性の高い操作を承認してください。ハードウェアウォレットの場合は、署名にカードまたはリングが必要です。</string>
<string name="app_settings_default_fee">デフォルト手数料</string>
<string name="app_settings_default_fee_footer">デフォルト手数料を有効にすると、取引手数料が自動的に設定され、送金時に手数料ページを表示する必要がなくなります。必要に応じて、いつでもこのページに戻ることができます。</string>
@ -1910,9 +1911,12 @@
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">利息モード</string>
<string name="yield_module_token_details_earn_notification_processing">利息モードの有効化</string>
<string name="yield_module_token_details_earn_notification_title">利息モード</string>
<string name="yield_module_transaction_enter">利息モード オン</string>
<string name="yield_module_transaction_exit">利息モード オフ</string>
<string name="yield_module_transaction_topup">利息モードへの追加入金</string>
<string name="yield_module_transaction_enter">利息モードが有効になりました</string>
<string name="yield_module_transaction_enter_subtitle">%1$sがAaveに供給されました</string>
<string name="yield_module_transaction_exit">利息モードが無効になりました</string>
<string name="yield_module_transaction_exit_subtitle">%1$sがAaveから引き出されました</string>
<string name="yield_module_transaction_topup">Aaveへの供給</string>
<string name="yield_module_transaction_topup_subtitle">%1$sがAaveに供給されました</string>
<string name="yield_module_transfer_mode_automatic">自動</string>
<string name="yield_module_unable_to_cover_fee_description">取引のネットワーク手数料をカバーするために、 %1$s %2$sを追加してください。</string>
<string name="yield_module_unable_to_cover_fee_title">%s手数料を支払えません</string>

View file

@ -1851,9 +1851,12 @@
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Режим доходности</string>
<string name="yield_module_token_details_earn_notification_processing">Включение режима доходности</string>
<string name="yield_module_token_details_earn_notification_title">Режим доходности</string>
<string name="yield_module_transaction_enter">Доход включен</string>
<string name="yield_module_transaction_exit">Доход выключен</string>
<string name="yield_module_transaction_topup">Доход - пополнение</string>
<string name="yield_module_transaction_enter">Режим доходности включен</string>
<string name="yield_module_transaction_enter_subtitle">%1$s отправлено в Aave</string>
<string name="yield_module_transaction_exit">Режим доходности выключен</string>
<string name="yield_module_transaction_exit_subtitle">%1$s выведено из Aave</string>
<string name="yield_module_transaction_topup">Перевод средств в Aave</string>
<string name="yield_module_transaction_topup_subtitle">%1$s отправлено в Aave</string>
<string name="yield_module_transfer_mode_automatic">Автоматически</string>
<string name="yield_module_unable_to_cover_fee_description">Внесите немного %1$s %2$s, чтобы покрыть комиссию сети за транзакции.</string>
<string name="yield_module_unable_to_cover_fee_title">Невозможно покрыть комиссию в %s</string>

View file

@ -1520,6 +1520,12 @@
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Режим дохідності</string>
<string name="yield_module_token_details_earn_notification_processing">Відправка ваших коштів</string>
<string name="yield_module_token_details_earn_notification_title">Режим прибутковості</string>
<string name="yield_module_transaction_enter">Режим прибутковості увімкнено</string>
<string name="yield_module_transaction_enter_subtitle">%1$s постачено в Aave</string>
<string name="yield_module_transaction_exit">Режим прибутковості вимкнено</string>
<string name="yield_module_transaction_exit_subtitle">%1$s виведено з Aave</string>
<string name="yield_module_transaction_topup">Постачити в Aave</string>
<string name="yield_module_transaction_topup_subtitle">%1$s постачено в Aave</string>
<string name="yield_module_transfer_mode_automatic">Автоматично</string>
<string name="yield_module_unable_to_cover_fee_description">Внесіть трохи %1$s %2$s, щоб покрити комісію мережі за транзакції.</string>
<string name="yield_module_unable_to_cover_fee_title">Неможливо покрити комісію в %s</string>

View file

@ -90,6 +90,7 @@
<string name="alert_manage_tokens_unsupported_message">Tokens in %1$s network are not supported by this card or ring due to firmware limitation.</string>
<string name="alert_troubleshooting_scan_card_title">Are you having difficulty scanning your card or ring?</string>
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
<string name="app_settings_access_code_warning">Set an access code first to enable biometrics</string>
<string name="app_settings_biometrics_footer">Use %1$s to unlock your wallet and approve sensitive actions, like signing transactions. For hardware wallets, a card or ring is still required to sign.</string>
<string name="app_settings_default_fee">Default Fee</string>
<string name="app_settings_default_fee_footer">Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary.</string>
@ -1444,9 +1445,14 @@
<string name="tangempay_onboarding_security_description">A separate payment account will be created without disclosing your addresses and assets</string>
<string name="tangempay_onboarding_security_title">Unrivaled privacy</string>
<string name="tangempay_onboarding_title">Get your free Tangem Pay Card in minutes</string>
<string name="tangempay_payment_account">Payment account</string>
<string name="tangempay_payment_account_sync_needed">Payment account sync needed</string>
<string name="tangempay_service_unavailable_description">Were fixing a technical issue. Please try again later.</string>
<string name="tangempay_service_unavailable_title">Service temporarily unavailable</string>
<string name="tangempay_service_unreachable_try_later">The service is currently unreachable. Please try again later.</string>
<string name="tangempay_temporarily_unavailable">Tangem Pay is temporarily unavailable</string>
<string name="tangempay_title">Tangem Pay</string>
<string name="tangempay_use_tangem_device_to_restore_payment_account">Use your card or ring to restore access to your payment account</string>
<string name="this_is_my_wallet_title">This is my wallet</string>
<string name="toast_balances_hidden">Balances hidden</string>
<string name="toast_balances_shown">Balances shown</string>
@ -1984,9 +1990,12 @@
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Yield Mode</string>
<string name="yield_module_token_details_earn_notification_processing">Enabling Yield Mode</string>
<string name="yield_module_token_details_earn_notification_title">Yield Mode</string>
<string name="yield_module_transaction_enter">Yield Mode on</string>
<string name="yield_module_transaction_exit">Yield Mode off</string>
<string name="yield_module_transaction_topup">Yield Mode top-up</string>
<string name="yield_module_transaction_enter">Yield Mode enabled</string>
<string name="yield_module_transaction_enter_subtitle">%1$s supplied to Aave</string>
<string name="yield_module_transaction_exit">Yield Mode disabled</string>
<string name="yield_module_transaction_exit_subtitle">%1$s withdrawn from Aave</string>
<string name="yield_module_transaction_topup">Supply to Aave</string>
<string name="yield_module_transaction_topup_subtitle">%1$s supplied to Aave</string>
<string name="yield_module_transfer_mode_automatic">Automatic</string>
<string name="yield_module_unable_to_cover_fee_description">Add some %1$s %2$s to cover the network fee for transactions.</string>
<string name="yield_module_unable_to_cover_fee_title">Unable to cover %s fee</string>

View file

@ -240,6 +240,8 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
textAlign = TextAlign.Start,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
is TransactionState.Loading -> {

View file

@ -45,7 +45,6 @@ import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async
@ -60,7 +59,6 @@ internal class DefaultStakeKitRepository(
private val stakingYieldsStore: StakingYieldsStore,
private val dispatchers: CoroutineDispatcherProvider,
private val walletManagersFacade: WalletManagersFacade,
private val stakingFeatureToggles: StakingFeatureToggles,
moshi: Moshi,
) : StakeKitRepository {
@ -96,7 +94,8 @@ internal class DefaultStakeKitRepository(
}
}
}
stakingYieldsStore.store(yields)
val shouldRewriteCache = yieldsResponses.all { it is ApiResponse.Success }
stakingYieldsStore.store(yields, shouldRewriteCache)
}
}
@ -161,9 +160,11 @@ internal class DefaultStakeKitRepository(
}
private fun getAvailableStakeKitIntegrationsIds(): List<StakingIntegrationID.StakeKit> {
return StakingIntegrationID.StakeKit.entries.filterNot {
it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled
}
return StakingIntegrationID.StakeKit.entries
// load all integrations for now and filter in use cases if needed
// .filterNot {
// it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled
// }
}
private fun NetworkTypeDTO.extractJsonName(): String {

View file

@ -6,7 +6,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.domain.card.common.TapWorkarounds.isWallet2
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
@ -16,11 +16,14 @@ import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.lib.crypto.BlockchainUtils.isCardano
import com.tangem.lib.crypto.BlockchainUtils.isSolana
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.withContext
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ -31,6 +34,7 @@ internal class DefaultStakingRepository(
private val dispatchers: CoroutineDispatcherProvider,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val stakingFeatureToggles: StakingFeatureToggles,
private val walletManagersFacade: WalletManagersFacade,
) : StakingRepository {
override fun getStakingAvailability(
@ -38,7 +42,7 @@ internal class DefaultStakingRepository(
cryptoCurrency: CryptoCurrency,
): Flow<StakingAvailability> {
return channelFlow {
if (!checkFeatureToggleEnabled(cryptoCurrency.network.id)) {
if (!checkFeatureToggleEnabled(userWalletId, cryptoCurrency)) {
send(StakingAvailability.Unavailable)
return@channelFlow
}
@ -73,7 +77,7 @@ internal class DefaultStakingRepository(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): StakingAvailability {
if (!checkFeatureToggleEnabled(cryptoCurrency.network.id)) {
if (!checkFeatureToggleEnabled(userWalletId, cryptoCurrency)) {
return StakingAvailability.Unavailable
}
@ -112,11 +116,26 @@ internal class DefaultStakingRepository(
}
}
private fun checkFeatureToggleEnabled(networkId: Network.ID): Boolean {
return when (networkId.toBlockchain()) {
private suspend fun checkFeatureToggleEnabled(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
return when (cryptoCurrency.network.id.toBlockchain()) {
Blockchain.TON -> stakingFeatureToggles.isTonStakingEnabled
Blockchain.Cardano -> stakingFeatureToggles.isCardanoStakingEnabled
Blockchain.Ethereum -> stakingFeatureToggles.isEthStakingEnabled
Blockchain.Cardano -> {
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
val balance = stakingBalanceStoreV2.getSyncOrNull(
userWalletId = userWalletId,
stakingId = StakingID(
integrationId = StakingIntegrationID.create(currencyId = cryptoCurrency.id)?.value
?: return false,
address = address,
),
)
if (balance != null && balance is YieldBalance.Data && balance.balance.items.isNotEmpty()) {
return true
} else {
stakingFeatureToggles.isCardanoStakingEnabled
}
}
else -> true
}
}

View file

@ -3,12 +3,7 @@ package com.tangem.data.staking.di
import com.squareup.moshi.Moshi
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.data.staking.DefaultP2PEthPoolRepository
import com.tangem.data.staking.DefaultStakeKitActionRepository
import com.tangem.data.staking.DefaultStakingErrorResolver
import com.tangem.data.staking.DefaultStakeKitRepository
import com.tangem.data.staking.DefaultStakingRepository
import com.tangem.data.staking.DefaultStakeKitTransactionHashRepository
import com.tangem.data.staking.*
import com.tangem.data.staking.converters.error.StakeKitErrorConverter
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.toggles.DefaultStakingFeatureToggles
@ -21,12 +16,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.P2PEthPoolVaultsStore
import com.tangem.datasource.local.token.StakingActionsStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.repositories.StakeKitActionRepository
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
import com.tangem.domain.staking.repositories.*
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -49,7 +39,6 @@ internal object StakingDataModule {
stakingYieldsStore: StakingYieldsStore,
dispatchers: CoroutineDispatcherProvider,
walletManagersFacade: WalletManagersFacade,
stakingFeatureToggles: StakingFeatureToggles,
@NetworkMoshi moshi: Moshi,
): StakeKitRepository {
return DefaultStakeKitRepository(
@ -57,7 +46,6 @@ internal object StakingDataModule {
stakingYieldsStore = stakingYieldsStore,
dispatchers = dispatchers,
walletManagersFacade = walletManagersFacade,
stakingFeatureToggles = stakingFeatureToggles,
moshi = moshi,
)
}
@ -71,6 +59,7 @@ internal object StakingDataModule {
dispatchers: CoroutineDispatcherProvider,
getUserWalletUseCase: GetUserWalletUseCase,
stakingFeatureToggles: StakingFeatureToggles,
walletManagersFacade: WalletManagersFacade,
): StakingRepository {
return DefaultStakingRepository(
stakeKitRepository = stakeKitRepository,
@ -78,6 +67,7 @@ internal object StakingDataModule {
stakingBalanceStoreV2 = yieldsBalancesStore,
dispatchers = dispatchers,
getUserWalletUseCase = getUserWalletUseCase,
walletManagersFacade = walletManagersFacade,
stakingFeatureToggles = stakingFeatureToggles,
)
}

View file

@ -1,7 +1,10 @@
package com.tangem.domain.staking.usecase
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -12,12 +15,18 @@ import kotlinx.coroutines.flow.map
* - key: currency staking key (network.backendId + "_" + symbol)
* - value: validators
*/
class StakingApyFlowUseCase(private val stakeKitRepository: StakeKitRepository) {
class StakingApyFlowUseCase(
private val stakeKitRepository: StakeKitRepository,
private val stakingFeatureToggles: StakingFeatureToggles,
) {
operator fun invoke(): Flow<Map<String, List<Yield.Validator>>> {
return stakeKitRepository.getEnabledYields()
.map { yields ->
yields.associate { yield ->
yields.filterNot { yield ->
val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId()
isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled
}.associate { yield ->
val key = "${yield.token.coinGeckoId}_${yield.token.symbol}"
val apy = yield.validators
key to apy

View file

@ -1106,14 +1106,15 @@ internal class TokenDetailsModel @Inject constructor(
private fun openStaking() {
modelScope.launch {
val yield = getYieldUseCase.invoke(
getYieldUseCase.invoke(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
).getOrElse {
error("Staking is unavailable for ${cryptoCurrency.name}")
).onRight { yield ->
router.openStaking(userWalletId, cryptoCurrency, yield.id)
}.onLeft {
Timber.e("Staking is unavailable for ${cryptoCurrency.name}")
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.staking_error_no_validators_title)))
}
router.openStaking(userWalletId, cryptoCurrency, yield.id)
}
}

View file

@ -27,7 +27,7 @@ internal class TxHistoryItemToTransactionStateConverter(
txHash = value.txHash,
amount = value.getAmount(),
time = value.timestampInMillis.toTimeFormat(),
status = value.status.tiUiStatus(),
status = value.status.toUiStatus(),
direction = value.extractDirection(),
iconRes = value.extractIcon(),
title = value.extractTitle(),
@ -41,21 +41,21 @@ internal class TxHistoryItemToTransactionStateConverter(
R.drawable.ic_close_24
} else {
when (type) {
is TxInfo.TransactionType.Approve -> R.drawable.ic_doc_24
is TxInfo.TransactionType.Staking.Stake,
is TxInfo.TransactionType.Staking.Vote,
is TxInfo.TransactionType.Staking.Restake,
is TransactionType.Approve -> R.drawable.ic_doc_24
is TransactionType.Staking.Stake,
is TransactionType.Staking.Vote,
is TransactionType.Staking.Restake,
-> R.drawable.ic_transaction_history_staking_24
is TxInfo.TransactionType.Staking.ClaimRewards,
is TransactionType.Staking.ClaimRewards,
-> R.drawable.ic_transaction_history_claim_rewards_24
is TxInfo.TransactionType.Staking.Unstake,
is TxInfo.TransactionType.Staking.Withdraw,
is TransactionType.Staking.Unstake,
is TransactionType.Staking.Withdraw,
-> R.drawable.ic_transaction_history_unstaking_24
is TxInfo.TransactionType.Operation,
is TxInfo.TransactionType.Swap,
is TxInfo.TransactionType.Transfer,
is TxInfo.TransactionType.YieldSupply,
is TxInfo.TransactionType.UnknownOperation,
is TransactionType.Operation,
is TransactionType.Swap,
is TransactionType.Transfer,
is TransactionType.YieldSupply,
is TransactionType.UnknownOperation,
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
}
}
@ -78,43 +78,67 @@ internal class TxHistoryItemToTransactionStateConverter(
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
}
private fun TxInfo.extractSubtitle(): TextReference = when (val interactionAddress = interactionAddressType) {
is TxInfo.InteractionAddressType.Contract -> resourceReference(
id = R.string.transaction_history_contract_address,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is TxInfo.InteractionAddressType.Multiple -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
)
is TxInfo.InteractionAddressType.User -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is TxInfo.InteractionAddressType.Validator -> resourceReference(
id = R.string.transaction_history_transaction_validator,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
null -> {
TextReference.EMPTY
private fun TxInfo.extractSubtitle(): TextReference {
return when (this.type) {
is TransactionType.YieldSupply.Enter -> {
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
}
is TransactionType.YieldSupply.Topup,
-> {
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
}
is TransactionType.YieldSupply.Exit -> {
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
}
else -> extractSubtitleByAddressType()
}
}
private fun TxInfo.extractSubtitleByAddressType(): TextReference =
when (val interactionAddress = interactionAddressType) {
is TxInfo.InteractionAddressType.Contract -> resourceReference(
id = R.string.transaction_history_contract_address,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is TxInfo.InteractionAddressType.Multiple -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
)
is TxInfo.InteractionAddressType.User -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is TxInfo.InteractionAddressType.Validator -> resourceReference(
id = R.string.transaction_history_transaction_validator,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
null -> {
TextReference.EMPTY
}
}
private fun TxInfo.extractDirection() =
if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING
@Suppress("ComplexCondition")
private fun TxInfo.getAmount(): String {
if (type is TxInfo.TransactionType.Staking.Vote ||
type == TxInfo.TransactionType.Staking.ClaimRewards ||
type == TxInfo.TransactionType.Staking.Withdraw
if (type is TransactionType.Staking.Vote ||
type == TransactionType.Staking.ClaimRewards ||
type == TransactionType.Staking.Withdraw ||
type == TransactionType.YieldSupply.Enter ||
type == TransactionType.YieldSupply.Exit ||
type == TransactionType.YieldSupply.Topup
) {
return ""
}
@ -126,7 +150,7 @@ internal class TxHistoryItemToTransactionStateConverter(
return prefix + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
}
private fun TxInfo.TransactionStatus.tiUiStatus() = when (this) {
private fun TxInfo.TransactionStatus.toUiStatus() = when (this) {
TxInfo.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed
TxInfo.TransactionStatus.Failed -> TransactionState.Content.Status.Failed
TxInfo.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed

View file

@ -10,7 +10,8 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.network.TxInfo.*
import com.tangem.domain.models.network.TxInfo.TransactionStatus
import com.tangem.domain.models.network.TxInfo.TransactionType
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.StringsSigns.MINUS
@ -85,36 +86,56 @@ internal class TxHistoryItemStateConverter(
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
}
private fun TxInfo.extractSubtitle(): TextReference = when (val interactionAddress = interactionAddressType) {
is InteractionAddressType.Contract -> resourceReference(
id = R.string.transaction_history_contract_address,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is InteractionAddressType.Multiple -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
)
is InteractionAddressType.User -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is InteractionAddressType.Validator -> resourceReference(
id = R.string.transaction_history_transaction_validator,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
null -> {
TextReference.EMPTY
private fun TxInfo.extractSubtitle(): TextReference {
return when (this.type) {
is TransactionType.YieldSupply.Enter -> {
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
}
is TransactionType.YieldSupply.Topup,
-> {
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
}
is TransactionType.YieldSupply.Exit -> {
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
}
else -> extractSubtitleByAddressType()
}
}
private fun TxInfo.extractSubtitleByAddressType(): TextReference =
when (val interactionAddress = interactionAddressType) {
is TxInfo.InteractionAddressType.Contract -> resourceReference(
id = R.string.transaction_history_contract_address,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is TxInfo.InteractionAddressType.Multiple -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
)
is TxInfo.InteractionAddressType.User -> resourceReference(
id = if (isOutgoing) {
R.string.transaction_history_transaction_to_address
} else {
R.string.transaction_history_transaction_from_address
},
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
is TxInfo.InteractionAddressType.Validator -> resourceReference(
id = R.string.transaction_history_transaction_validator,
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
null -> {
TextReference.EMPTY
}
}
private fun TxInfo.extractDirection() =
if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING
@ -124,10 +145,14 @@ internal class TxHistoryItemStateConverter(
TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed
}
@Suppress("ComplexCondition")
private fun TxInfo.getAmount(): String {
if (type is TransactionType.Staking.Vote ||
type == TransactionType.Staking.ClaimRewards ||
type == TransactionType.Staking.Withdraw
type == TransactionType.Staking.Withdraw ||
type == TransactionType.YieldSupply.Enter ||
type == TransactionType.YieldSupply.Exit ||
type == TransactionType.YieldSupply.Topup
) {
return ""
}

View file

@ -110,7 +110,18 @@ internal class YieldSupplyModel @Inject constructor(
bottomSheetNavigation.activate(Unit)
}
} else {
onStartEarningClick()
uiState
.filterIsInstance<YieldSupplyUM.Available>()
.take(1)
.onEach { state ->
appRouter.push(
YieldSupplyPromo(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency,
apy = state.apy,
),
)
}.launchIn(modelScope)
}
}
}