Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-03 10:31:39 +03:00
commit ee56b2f716
46 changed files with 492 additions and 233 deletions

@ -1 +1 @@
Subproject commit 328c13826f7e52ba783db1ba39de5b17b1b79bc5
Subproject commit d52c8382e9e94b2b44ceecd7cb7bd897e091073c

View file

@ -152,8 +152,8 @@ internal object StakingDomainModule {
fun provideGetPendingTransactionsUseCase(
stakingPendingTransactionRepository: StakingPendingTransactionRepository,
stakingErrorResolver: StakingErrorResolver,
): GetPendingTransactionsUseCase {
return GetPendingTransactionsUseCase(
): GetStakingPendingTransactionsUseCase {
return GetStakingPendingTransactionsUseCase(
stakingPendingTransactionRepository = stakingPendingTransactionRepository,
stakingErrorResolver = stakingErrorResolver,
)

View file

@ -128,7 +128,7 @@
<string name="common_no_address">Keine Adresse</string>
<string name="common_now">Jetzt</string>
<string name="common_ok">OK</string>
<string name="common_origin_card">Primär Karte</string>
<string name="common_origin_card">Hauptkarte</string>
<string name="common_passphrase">Passphrase</string>
<string name="common_paste">Einfügen</string>
<string name="common_privacy_policy">Datenschutzbestimmungen</string>
@ -275,7 +275,7 @@
<string name="express_token_list_empty_search">Keine Token gefunden. Bitte versuche eine andere Anfrage</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaktions-ID kopiert</string>
<string name="exсhange_token_description">Wechsel eine Ihrer Währungen in eine andere</string>
<string name="exсhange_token_description">Tauschen jeden Vermögenswert in Deinem Portfolio gegen diesen Token</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 du hast</string>
@ -345,6 +345,7 @@
<string name="manage_tokens_wallet_support_only_one_network_title">Das Wallet unterstützt nicht mehr als ein Netzwerk</string>
<string name="markets_add_to_my_portfolio_description">Um mit dem Kauf, Tausch oder Erhalt dieses Vermögenswerts zu beginnen, füge diesen Token zu mindestens 1 Netzwerk hinzu</string>
<string name="markets_add_to_my_portfolio_unavailable_description">Dieses Asset ist nicht verfügbar</string>
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">Dieses Asset ist für dieses Wallet nicht verfügbar</string>
<string name="markets_add_to_portfolio_button">Zum Portfolio hinzufügen</string>
<string name="markets_add_token">Hinzufügen</string>
<string name="markets_available_networks">Verfügbare Netzwerke</string>
@ -561,7 +562,7 @@
<item quantity="one">%d Wallet</item>
<item quantity="other">%d Wallets</item>
</plurals>
<string name="reset_card_to_factory_button_title">Zurücksetzen der Karte</string>
<string name="reset_card_to_factory_button_title">Karte zurücksetzen</string>
<string name="reset_card_to_factory_condition_1">Mir ist bewusst, dass ich nach der Durchführung dieser Aktion keinen Zugriff mehr auf die aktuelle Wallet habe.</string>
<string name="reset_card_to_factory_condition_2">Mir ist klar, dass ich diese Karte nicht verwenden kann, um meinen Zugangscode auf den anderen Karten der aktuellen Wallet wiederherzustellen</string>
<string name="reset_card_with_backup_to_factory_message">Durch das Zurücksetzen auf Werkseinstellungen wird die Wallet vollständig von der ausgewählten Karte gelöscht. Du kannst die aktuelle Wallet nicht wiederherstellen oder die Karte verwenden, um den Zugangscode wiederherzustellen.</string>
@ -686,7 +687,7 @@
<string name="staking_details_unbonding_period_info">Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden.</string>
<string name="staking_details_warmup_period">Aufwärmphase</string>
<string name="staking_details_warmup_period_info">Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking.</string>
<string name="staking_legal">Indem Du die Staking-Funktionalität nutzen, stimmst Du den %1$s und %2$s des Anbieters zu.</string>
<string name="staking_legal">Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu.</string>
<string name="staking_locked">Gesperrt</string>
<string name="staking_migrate">Migrieren</string>
<string name="staking_native">Natives Staking</string>
@ -718,14 +719,14 @@
<string name="staking_revote">Neuwahl</string>
<string name="staking_reward_claiming_auto">Automatisch</string>
<string name="staking_reward_claiming_manual">Händisch</string>
<string name="staking_reward_schedule_block">Block</string>
<string name="staking_reward_schedule_day">Tag</string>
<string name="staking_reward_schedule_block">Pro Block</string>
<string name="staking_reward_schedule_day">Täglich</string>
<string name="staking_reward_schedule_each_day">Täglich</string>
<string name="staking_reward_schedule_epoch">Epoche</string>
<string name="staking_reward_schedule_era">Ära</string>
<string name="staking_reward_schedule_hour">Stunde</string>
<string name="staking_reward_schedule_month">Monat</string>
<string name="staking_reward_schedule_week">Woche</string>
<string name="staking_reward_schedule_epoch">Pro Epoche</string>
<string name="staking_reward_schedule_era">Pro Ära</string>
<string name="staking_reward_schedule_hour">Stündlich</string>
<string name="staking_reward_schedule_month">Monatlich</string>
<string name="staking_reward_schedule_week">Wöchentlich</string>
<string name="staking_rewards">Belohnungen</string>
<string name="staking_stake_locked">Stake gesperrt</string>
<string name="staking_stake_more">Mehr staken</string>

View file

@ -342,6 +342,7 @@
<string name="manage_tokens_wallet_support_only_one_network_title">ウォレットは複数のネットワークをサポートしていません。</string>
<string name="markets_add_to_my_portfolio_description">このアセットの購入、交換、受け取りを開始するには、ポートフォリオに追加してください。</string>
<string name="markets_add_to_my_portfolio_unavailable_description">このアセットは利用できません</string>
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">このアセットはこのウォレットでは使用できません。</string>
<string name="markets_add_to_portfolio_button">ポートフォリオに追加</string>
<string name="markets_add_token">追加</string>
<string name="markets_available_networks">利用可能なネットワーク</string>
@ -799,6 +800,7 @@
<string name="transaction_history_operation">オペレーション</string>
<string name="transaction_history_transaction_from_address">送金元: %s</string>
<string name="transaction_history_transaction_to_address">送金先: %s</string>
<string name="transaction_history_transaction_validator">バリデーター: %s</string>
<string name="try_to_load_data_again_button_title">もう一度やり直してください</string>
<string name="twin_error_same_card">同じカードをスキャンしました。ツインウォレットを作成するには、番号%dのカードをスキャンする必要があります。</string>
<string name="twin_error_wrong_twin">間違ったツインカードをスキャンしました。別のカードをお試しください。</string>

View file

@ -434,7 +434,7 @@
<string name="markets_token_details_trading_volume">Объем торгов (24ч)</string>
<string name="markets_token_details_trading_volume_24h_description">Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке.</string>
<string name="markets_token_details_trading_volume_full">Объем торгов (24ч)</string>
<string name="markets_tooltip_message">Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из маркета</string>
<string name="markets_tooltip_message">Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из рынка</string>
<string name="markets_tooltip_title">Добавить токены</string>
<string name="nfc_error_unavailable">Функция NFC недоступна на вашем устройстве</string>
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>
@ -744,6 +744,7 @@
<string name="staking_stake_more">Застейкать еще</string>
<string name="staking_summary_description_text">Вы стейкаете %1$s и будете получать %2$s ежегодно</string>
<string name="staking_tap_to_unlock">Нажмите для разблокировки</string>
<string name="staking_tap_to_unlock_or_vote">Нажмите для разблокировки или голосования</string>
<string name="staking_tap_to_withdraw">Нажмите для вывода</string>
<string name="staking_title_stake">Застейкать %s</string>
<string name="staking_title_unstake">Вывести %s</string>

View file

@ -141,7 +141,7 @@
<string name="common_read_more">Детальніше</string>
<string name="common_receive">Отримати</string>
<string name="common_reject">Відхилити</string>
<string name="common_reload">Перезавантажити</string>
<string name="common_reload">Оновити</string>
<string name="common_rename">Перейменувати</string>
<string name="common_save">Зберегти</string>
<string name="common_save_changes">Зберегти зміни</string>
@ -351,6 +351,7 @@
<string name="manage_tokens_wallet_support_only_one_network_title">Гаманець не підтримує більше однієї мережі</string>
<string name="markets_add_to_my_portfolio_description">Щоб почати купувати, обмінювати або отримувати цей актив, додайте цей токен принаймні в 1 мережу</string>
<string name="markets_add_to_my_portfolio_unavailable_description">Цей актив недоступний</string>
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">Цей токен недоступний для цього гаманця</string>
<string name="markets_add_to_portfolio_button">Додати в портфоліо</string>
<string name="markets_add_token">Додати</string>
<string name="markets_available_networks">Доступні мережі</string>
@ -401,6 +402,7 @@
<string name="markets_token_details_experienced_buyers_full">Досвідчені покупці</string>
<string name="markets_token_details_fully_diluted_valuation">Повн. розведена кап.</string>
<string name="markets_token_details_fully_diluted_valuation_description">Загальна теоретична вартість криптовалюти, якщо всі монети, які могли б існувати, перебувають в обігу, включаючи ті, що не перебувають в обігу в даний час</string>
<string name="markets_token_details_fully_diluted_valuation_full">Повністю розведена капіталізація</string>
<string name="markets_token_details_genesis_date">Дата створення</string>
<string name="markets_token_details_high">Високий</string>
<string name="markets_token_details_holders">Тримачі</string>
@ -415,9 +417,11 @@
<string name="markets_token_details_low">Низький</string>
<string name="markets_token_details_market_capitalization">Рин. кап.</string>
<string name="markets_token_details_market_capitalization_description">Загальна ринкова вартість криптовалюти, що розраховується шляхом множення поточної ціни монети на загальну кількість монет в обігу</string>
<string name="markets_token_details_market_capitalization_full">Ринкова капіталізація</string>
<string name="markets_token_details_market_rating">Рейтинг</string>
<string name="markets_token_details_market_rating_description">Позиція в крипторейтингу між усіма монетами на основі ринкової капіталізації</string>
<string name="markets_token_details_market_rating_full">Рейтинг</string>
<string name="markets_token_details_max_supply">Макс. обсяг</string>
<string name="markets_token_details_max_supply_description">Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти</string>
<string name="markets_token_details_max_supply_full">Максимальна пропозиція</string>
<string name="markets_token_details_metrics">Метрики</string>
@ -713,6 +717,7 @@
<string name="staking_notification_earn_rewards_text_period_week">Стейкінг дозволяє заробляти %1$s. Ваші винагороди за стейкінг зараховуються щотиждня.</string>
<string name="staking_notification_earn_rewards_text_weekly">Стейкайте безпечно та почніть отримувати винагороди щотижня</string>
<string name="staking_notification_earn_rewards_title">Отримуйте винагороду за стейкінг</string>
<string name="staking_notification_network_error_text">Стейкінг тимчасово недоступний через проблеми в мережі. Будь ласка, спробуйте пізніше.</string>
<string name="staking_notification_new_validator_funds_transfer">Стейкінг в мережі %1$s з новим валідатором автоматично переведе всі раніше застейкані кошти до цього валідатора</string>
<string name="staking_notification_restake_rewards_text">Реінвестуйте зароблені винагороди у суму стейкінгу, щоб збільшити потенційний прибуток.</string>
<string name="staking_notification_unlock_text">Розблокуйте свої кошти, щоб вивести їх зі стейкінгу. Розблокування займе %s.</string>
@ -813,12 +818,13 @@
<string name="token_swap_promotion_button">Обміняти</string>
<string name="transaction_history_contract_address">контракт: %s</string>
<string name="transaction_history_empty_transactions">У вас ще немає транзакцій</string>
<string name="transaction_history_error_failed_to_load">Не вдалося завантажити історію транзакцій.\nНатисніть кнопку перезавантаження, щоб оновити інформацію.</string>
<string name="transaction_history_error_failed_to_load">Не вдалося завантажити історію транзакцій.\nНатисніть кнопку нижче, щоб оновити інформацію</string>
<string name="transaction_history_multiple_addresses">Кілька адрес</string>
<string name="transaction_history_not_supported_description">Історія транзакцій наразі не підтримується для цього блокчейну. Але не хвилюйтеся, ми працюємо над цим! А поки можете перевірити це в оглядачі.</string>
<string name="transaction_history_operation">Операція</string>
<string name="transaction_history_transaction_from_address">від: %s</string>
<string name="transaction_history_transaction_to_address">до: %s</string>
<string name="transaction_history_transaction_validator">валідатор: %s</string>
<string name="try_to_load_data_again_button_title">Спробуйте знову</string>
<string name="twin_error_same_card">Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d</string>
<string name="twin_error_wrong_twin">Ви відсканували не ту twin-картку. Будь ласка, спробуйте відсканувати іншу</string>

View file

@ -128,7 +128,7 @@
<string name="common_no_address">No address</string>
<string name="common_now">Now</string>
<string name="common_ok">OK</string>
<string name="common_origin_card">Primary Card</string>
<string name="common_origin_card">Primary card</string>
<string name="common_passphrase">Passphrase</string>
<string name="common_paste">Paste</string>
<string name="common_privacy_policy">Privacy Policy</string>
@ -561,7 +561,7 @@
<item quantity="one">%d wallet</item>
<item quantity="other">%d wallets</item>
</plurals>
<string name="reset_card_to_factory_button_title">Reset the Card</string>
<string name="reset_card_to_factory_button_title">Reset the card</string>
<string name="reset_card_to_factory_condition_1">I understand that after performing this action, I will no longer have access to the current wallet</string>
<string name="reset_card_to_factory_condition_2">I realize that I can\'t use this card to recover my access code on the other cards of the current wallet</string>
<string name="reset_card_with_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
@ -577,7 +577,7 @@
<string name="save_user_wallet_agreement_enroll_biometrics_title">Enable biometric authorization</string>
<string name="save_user_wallet_agreement_header_biometrics">Would you like to use biometrics?</string>
<string name="save_user_wallet_agreement_notice">Note that making a transaction with your funds will still require your card</string>
<string name="scan_card_settings_button">Scan Card</string>
<string name="scan_card_settings_button">Scan card</string>
<string name="scan_card_settings_message">Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet.</string>
<string name="scan_card_settings_title">Get your card ready!</string>
<string name="send_additional_field_already_included">Already included in the entered address</string>
@ -731,6 +731,7 @@
<string name="staking_stake_more">Stake more</string>
<string name="staking_summary_description_text">You stake %1$s and will be receiving %2$s yearly</string>
<string name="staking_tap_to_unlock">Tap to unlock</string>
<string name="staking_tap_to_unlock_or_vote">Tap to unlock or vote</string>
<string name="staking_tap_to_withdraw">Tap to withdraw</string>
<string name="staking_title_stake">Stake %s</string>
<string name="staking_title_unstake">Unstake %s</string>
@ -831,7 +832,7 @@
<string name="user_wallet_list_error_wallet_already_saved">This wallet has already been saved, you can add another one</string>
<string name="user_wallet_list_rename_popup_error_already_exists">The wallet with name %s already exists</string>
<string name="user_wallet_list_rename_popup_placeholder">Wallet name</string>
<string name="user_wallet_list_rename_popup_title">Rename Wallet</string>
<string name="user_wallet_list_rename_popup_title">Rename wallet</string>
<string name="user_wallet_list_unlock_all">Unlock all</string>
<string name="user_wallet_list_unlock_all_with">Unlock all with %s</string>
<string name="wallet_balance_blockchain_unreachable_try_later">Blockchain is unreachable. Try later</string>

View file

@ -75,6 +75,7 @@ fun getActiveIconRes(blockchainId: String): Int {
"cyber", "cyber/test" -> R.drawable.img_cyber_22
"sei", "sei/test" -> R.drawable.img_sei_22
"internet-computer" -> R.drawable.img_icp_22
"sui", "sui/test" -> R.drawable.img_sui_22
else -> R.drawable.ic_alert_24
}
}
@ -151,6 +152,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int {
"cyber", "cyber/test" -> R.drawable.img_cyber_22
"sei", "sei/test" -> R.drawable.img_sei_22
"internet-computer" -> R.drawable.img_icp_22
"sui", "sui/test" -> R.drawable.img_sui_22
else -> R.drawable.ic_alert_24
}
}
@ -224,6 +226,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
"cyber", "cyber/test" -> R.drawable.img_cyber_22
"sei", "sei/test" -> R.drawable.img_sei_22
"internet-computer" -> R.drawable.img_icp_22
"sui", "sui/test" -> R.drawable.img_sui_22
else -> R.drawable.ic_alert_24
}
}
@ -300,6 +303,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
"sei", "sei/test" -> R.drawable.ic_sei_22
"internet-computer" -> R.drawable.ic_icp_22
"sui", "sui/test" -> R.drawable.ic_sui_22
else -> R.drawable.ic_alert_24
}
}
@ -376,6 +380,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int {
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
"sei", "sei/test" -> R.drawable.ic_sei_22
"internet-computer" -> R.drawable.ic_icp_22
"sui", "sui/test" -> R.drawable.ic_sui_22
else -> R.drawable.ic_alert_24
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M14.8,9.7C15.4,10.5 15.8,11.5 15.8,12.7C15.8,13.8 15.4,14.9 14.7,15.7L14.7,15.8L14.7,15.7C14.6,15.6 14.6,15.5 14.6,15.4C14.3,14 13.2,12.7 11.4,11.7C10.2,11 9.5,10.1 9.3,9.2C9.2,8.6 9.3,8 9.4,7.5C9.6,6.9 9.9,6.5 10.1,6.2L10.8,5.4C10.9,5.2 11.1,5.2 11.2,5.4L14.8,9.7H14.8ZM15.9,8.8L11.2,3.1C11.1,3 10.9,3 10.8,3.1L6.1,8.8L6.1,8.8C5.3,9.9 4.8,11.3 4.8,12.8C4.8,16.2 7.6,19 11,19C14.5,19 17.3,16.2 17.3,12.8C17.3,11.3 16.7,9.9 15.9,8.8L15.9,8.8L15.9,8.8ZM7.3,9.7L7.7,9.1L7.7,9.2C7.7,9.3 7.7,9.4 7.7,9.5C8,10.9 9,12.1 10.6,13C12,13.8 12.8,14.7 13.1,15.7C13.2,16.2 13.2,16.6 13.1,16.9L13.1,17L13.1,17C12.5,17.3 11.8,17.5 11,17.5C8.4,17.5 6.2,15.3 6.2,12.7C6.2,11.5 6.6,10.5 7.3,9.7L7.3,9.7Z"
android:fillColor="#000" />
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<clip-path
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"/>
<path
android:pathData="M0,0h22v22h-22z"
android:fillColor="#4CA3FF"/>
<path
android:pathData="M14.8,9.7C15.4,10.5 15.8,11.5 15.8,12.7C15.8,13.8 15.4,14.9 14.7,15.7L14.7,15.8L14.7,15.7C14.6,15.6 14.6,15.5 14.6,15.4C14.3,14 13.2,12.7 11.4,11.7C10.2,11 9.5,10.1 9.3,9.2C9.2,8.6 9.3,8 9.4,7.5C9.6,6.9 9.9,6.5 10.1,6.2L10.8,5.4C10.9,5.2 11.1,5.2 11.2,5.4L14.8,9.7H14.8ZM15.9,8.8L11.2,3.1C11.1,3 10.9,3 10.8,3.1L6.1,8.8L6.1,8.8C5.3,9.9 4.8,11.3 4.8,12.8C4.8,16.2 7.6,19 11,19C14.5,19 17.3,16.2 17.3,12.8C17.3,11.3 16.7,9.9 15.9,8.8L15.9,8.8L15.9,8.8ZM7.3,9.7L7.7,9.1L7.7,9.2C7.7,9.3 7.7,9.4 7.7,9.5C8,10.9 9,12.1 10.6,13C12,13.8 12.8,14.7 13.1,15.7C13.2,16.2 13.2,16.6 13.1,16.9L13.1,17L13.1,17C12.5,17.3 11.8,17.5 11,17.5C8.4,17.5 6.2,15.3 6.2,12.7C6.2,11.5 6.6,10.5 7.3,9.7L7.3,9.7Z"
android:fillColor="#ffffff" />
</vector>

View file

@ -1,8 +1,8 @@
package com.tangem.data.managetokens
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.isSupportedInApp
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.currency.getBlockchain
@ -15,7 +15,9 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.testnet.TestnetTokensStorage
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.extensions.canHandleBlockchain
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.supportedBlockchains
@ -31,6 +33,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.BatchListSource
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request
import com.tangem.pagination.toBatchFlow
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,6 +43,7 @@ internal class DefaultManageTokensRepository(
private val userWalletsStore: UserWalletsStore,
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
private val appPreferencesStore: AppPreferencesStore,
private val testnetTokensStorage: TestnetTokensStorage,
private val dispatchers: CoroutineDispatcherProvider,
) : ManageTokensRepository {
@ -59,7 +63,6 @@ internal class DefaultManageTokensRepository(
).toBatchFlow()
}
@Suppress("ComplexCondition")
private fun createFetcher(
batchSize: Int,
): LimitOffsetBatchFetcher<ManageTokensListConfig, List<ManagedCryptoCurrency>> = LimitOffsetBatchFetcher(
@ -67,59 +70,101 @@ internal class DefaultManageTokensRepository(
batchSize = batchSize,
subFetcher = { request, _, isFirstBatchFetching ->
val userWallet = request.params.userWalletId?.let { getUserWallet(it) }
val supportedBlockchains = getSupportedBlockchains(userWallet)
val searchText = request.params.searchText?.takeIf { it.isNotBlank() }
val call = suspend {
tangemTechApi.getCoins(
networkIds = supportedBlockchains.joinToString(
separator = ",",
transform = Blockchain::toNetworkId,
),
active = true,
searchText = searchText,
offset = request.offset * request.limit,
limit = request.limit,
).getOrThrow()
}
val coinsResponse = if (isFirstBatchFetching) {
call()
if (userWallet?.scanResponse?.card?.isTestCard == true) {
fetchTestnetCurrencies(userWallet, request)
} else {
retryOnError(call = call)
fetchCurrencies(userWallet, request, isFirstBatchFetching)
}
// filter l2 coins
val updatedCoinsResponse = coinsResponse.copy(
coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) },
)
val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) }
val items = if (isFirstBatchFetching &&
tokensResponse != null &&
userWallet != null &&
request.params.searchText.isNullOrBlank()
) {
managedCryptoCurrencyFactory.createWithCustomTokens(
coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse,
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
)
} else {
managedCryptoCurrencyFactory.create(
coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse,
derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider,
)
}
BatchFetchResult.Success(
data = items,
empty = items.isEmpty(),
last = items.size < request.limit,
)
},
)
@Suppress("ComplexCondition")
private suspend fun fetchCurrencies(
userWallet: UserWallet?,
request: Request<ManageTokensListConfig>,
isFirstBatchFetching: Boolean,
): BatchFetchResult.Success<List<ManagedCryptoCurrency>> {
val supportedBlockchains = getSupportedBlockchains(userWallet)
val call = suspend {
tangemTechApi.getCoins(
networkIds = supportedBlockchains.joinToString(
separator = ",",
transform = Blockchain::toNetworkId,
),
active = true,
searchText = request.params.searchText,
offset = request.offset * request.limit,
limit = request.limit,
).getOrThrow()
}
val coinsResponse = if (isFirstBatchFetching) {
call()
} else {
retryOnError(call = call)
}
// filter l2 coins
val updatedCoinsResponse = coinsResponse.copy(
coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) },
)
val tokensResponse = request.params.userWalletId?.let { getSavedUserTokensResponseSync(it) }
val items = if (isFirstBatchFetching &&
tokensResponse != null &&
userWallet != null &&
request.params.searchText.isNullOrBlank()
) {
managedCryptoCurrencyFactory.createWithCustomTokens(
coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse,
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
)
} else {
managedCryptoCurrencyFactory.create(
coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse,
derivationStyleProvider = userWallet?.scanResponse?.derivationStyleProvider,
)
}
return BatchFetchResult.Success(
data = items,
empty = items.isEmpty(),
last = items.size < request.limit,
)
}
private suspend fun fetchTestnetCurrencies(
userWallet: UserWallet,
request: Request<ManageTokensListConfig>,
): BatchFetchResult.Success<List<ManagedCryptoCurrency>> {
val searchText = request.params.searchText
val testnetTokensConfig = testnetTokensStorage.getConfig()
val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens(
testnetTokensConfig = if (!searchText.isNullOrBlank()) {
testnetTokensConfig.copy(
tokens = testnetTokensConfig.tokens.filter { token ->
token.symbol.contains(other = searchText, ignoreCase = true) ||
token.name.contains(other = searchText, ignoreCase = true)
},
)
} else {
testnetTokensConfig
},
tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId),
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
)
return BatchFetchResult.Success(
data = items,
empty = items.isEmpty(),
last = true,
)
}
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"

View file

@ -5,6 +5,7 @@ import com.tangem.data.managetokens.DefaultManageTokensRepository
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.testnet.TestnetTokensStorage
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository
@ -27,6 +28,7 @@ internal object ManageTokensDataModule {
userWalletsStore: UserWalletsStore,
manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
appPreferencesStore: AppPreferencesStore,
testnetTokensStorage: TestnetTokensStorage,
dispatchers: CoroutineDispatcherProvider,
): ManageTokensRepository {
return DefaultManageTokensRepository(
@ -34,6 +36,7 @@ internal object ManageTokensDataModule {
userWalletsStore,
manageTokensUpdateFetcher,
appPreferencesStore,
testnetTokensStorage,
dispatchers,
)
}

View file

@ -12,6 +12,7 @@ import com.tangem.data.common.currency.getNetwork
import com.tangem.data.common.currency.getTokenId
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
@ -34,16 +35,49 @@ internal class ManagedCryptoCurrencyFactory {
tokensResponse: UserTokensResponse,
derivationStyleProvider: DerivationStyleProvider,
): List<ManagedCryptoCurrency> {
val customTokens = tokensResponse.tokens
.mapNotNull { token ->
maybeCreateCustomToken(token, coinsResponse.imageHost, derivationStyleProvider)
}
val customTokens = createCustomTokens(tokensResponse, derivationStyleProvider)
val tokens = create(coinsResponse, tokensResponse, derivationStyleProvider)
return customTokens + tokens
}
fun createTestnetWithCustomTokens(
testnetTokensConfig: TestnetTokensConfig,
tokensResponse: UserTokensResponse?,
derivationStyleProvider: DerivationStyleProvider,
): List<ManagedCryptoCurrency> {
val customTokens = tokensResponse
?.let { createCustomTokens(it, derivationStyleProvider) }
?: emptyList()
val testnetTokens = testnetTokensConfig.tokens.map { testnetToken ->
ManagedCryptoCurrency.Token(
id = ManagedCryptoCurrency.ID(testnetToken.id),
name = testnetToken.name,
symbol = testnetToken.symbol,
iconUrl = getIconUrl(testnetToken.id),
availableNetworks = testnetToken.networks?.mapNotNull { network ->
createSource(
networkId = network.id,
contractAddress = network.address,
decimals = network.decimalCount ?: return@mapNotNull null,
derivationStyleProvider = derivationStyleProvider,
)
} ?: emptyList(),
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, derivationStyleProvider),
)
}
return customTokens + testnetTokens
}
private fun createCustomTokens(
tokensResponse: UserTokensResponse,
derivationStyleProvider: DerivationStyleProvider,
): List<ManagedCryptoCurrency> = tokensResponse.tokens
.mapNotNull { token ->
maybeCreateCustomToken(token, null, derivationStyleProvider)
}
private fun maybeCreateCustomToken(
token: UserTokensResponse.Token,
imageHost: String?,
@ -100,23 +134,29 @@ internal class ManagedCryptoCurrencyFactory {
symbol = coinResponse.symbol,
iconUrl = getIconUrl(coinResponse.id, imageHost),
availableNetworks = updatedNetworks.mapNotNull { network ->
createSource(network, derivationStyleProvider)
createSource(
networkId = network.networkId,
contractAddress = network.contractAddress,
decimals = network.decimalCount?.toInt() ?: return@mapNotNull null,
derivationStyleProvider = derivationStyleProvider,
)
},
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, derivationStyleProvider),
)
}
private fun createSource(
networkResponse: CoinsResponse.Coin.Network,
networkId: String,
contractAddress: String?,
decimals: Int,
derivationStyleProvider: DerivationStyleProvider?,
extraDerivationPath: String? = null,
): SourceNetwork? {
val blockchain = Blockchain.fromNetworkId(networkResponse.networkId)
val blockchain = Blockchain.fromNetworkId(networkId)
?.takeIf { it.isSupportedInApp() }
?: return null
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
val contractAddress = networkResponse.contractAddress
return if (contractAddress.isNullOrBlank()) {
SourceNetwork.Main(
@ -127,7 +167,7 @@ internal class ManagedCryptoCurrencyFactory {
} else {
SourceNetwork.Default(
network = network,
decimals = requireNotNull(networkResponse.decimalCount?.toInt()),
decimals = decimals,
contractAddress = contractAddress,
)
}
@ -157,7 +197,7 @@ internal class ManagedCryptoCurrencyFactory {
}
}
private fun getIconUrl(id: String, imageHost: String?): String {
private fun getIconUrl(id: String, imageHost: String? = DEFAULT_IMAGE_HOST): String {
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
}

View file

@ -3,25 +3,30 @@ package com.tangem.data.staking
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
internal class DefaultStakingPendingTransactionRepository : StakingPendingTransactionRepository {
private val pendingTransactions = CopyOnWriteArrayList<PendingTransaction>()
private val pendingTransactionsMap = ConcurrentHashMap<UserWalletId, CopyOnWriteArrayList<PendingTransaction>>()
override fun saveTransaction(transaction: PendingTransaction) {
pendingTransactions.add(transaction)
override fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction) {
val transactions = pendingTransactionsMap.computeIfAbsent(userWalletId) { CopyOnWriteArrayList() }
transactions.add(transaction)
}
override fun removeTransactions(transactions: Set<PendingTransaction>) {
pendingTransactions.removeAll(transactions)
override fun removeTransactions(userWalletId: UserWalletId, transactions: Set<PendingTransaction>) {
pendingTransactionsMap[userWalletId]?.removeAll(transactions)
}
override fun getTransactionsWithBalanceItems(): List<Pair<PendingTransaction, BalanceItem>> {
return pendingTransactions.mapNotNull { pendingTransaction ->
override fun getTransactionsWithBalanceItems(
userWalletId: UserWalletId,
): List<Pair<PendingTransaction, BalanceItem>> {
return pendingTransactionsMap[userWalletId]?.mapNotNull { pendingTransaction: PendingTransaction ->
PendingTransactionItemConverter.convert(pendingTransaction)?.let { balanceItem ->
pendingTransaction to balanceItem
}
}
} ?: emptyList()
}
}

View file

@ -120,7 +120,7 @@ internal class DefaultStakingRepository(
key = YIELDS_STORE_KEY,
skipCache = refresh,
block = {
val stakingTokensWithYields = stakeKitApi.getMultipleYields(preferredValidatorsOnly = true)
val stakingTokensWithYields = stakeKitApi.getMultipleYields(preferredValidatorsOnly = false)
.getOrThrow()
stakingYieldsStore.store(stakingTokensWithYields.data)

View file

@ -10,8 +10,8 @@ internal object PendingTransactionItemConverter : Converter<PendingTransaction,
override fun convert(value: PendingTransaction): BalanceItem? {
return BalanceItem(
groupId = value.groupId ?: return null,
type = value.type ?: return null,
amount = value.amount ?: return null,
type = value.type,
amount = value.amount,
rawCurrencyId = value.rawCurrencyId,
validatorAddress = value.validator?.address,
date = DateTime.now(),

View file

@ -3,7 +3,6 @@ package com.tangem.data.staking.converters
import com.tangem.data.staking.converters.action.PendingActionConverter
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.utils.converter.Converter
internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBalance> {
@ -27,9 +26,7 @@ internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBa
validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0),
date = item.date?.toDateTime(),
pendingActions = pendingActionConverter
.convertList(item.pendingActions)
// temporarily exclude VOTE_LOCKED, it will be implemented in future iterations
.filterNot { it.type == StakingActionType.VOTE_LOCKED },
.convertList(item.pendingActions),
isPending = false,
)
},

View file

@ -28,7 +28,7 @@ class YieldConverter(
metadata = convertMetadata(value.metadata),
validators = value.validators
.asSequence()
.filter { it.preferred && it.status == ValidatorStatusDTO.ACTIVE }
.filter { it.status == ValidatorStatusDTO.ACTIVE }
.sortedByDescending { it.apr }
.map { convertValidator(it) }
.toImmutableList(),

View file

@ -11,9 +11,10 @@ android {
dependencies {
api(projects.domain.staking.models)
api(projects.domain.core)
implementation(deps.kotlin.serialization)
implementation(deps.jodatime)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)

View file

@ -6,8 +6,9 @@ import java.math.BigDecimal
data class PendingTransaction(
val groupId: String?,
val type: BalanceType?,
val amount: BigDecimal?,
val type: BalanceType,
val amount: BigDecimal,
val rawCurrencyId: String?,
val validator: Yield.Validator?,
val balancesId: Int,
)

View file

@ -1,14 +1,7 @@
package com.tangem.domain.staking.model
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.Yield
import java.math.BigDecimal
data class SubmitHashData(
val transactionHash: String,
val transactionId: String,
val validator: Yield.Validator?,
val amount: BigDecimal?,
val balanceType: BalanceType?,
val rawCurrencyId: String?,
val pendingTransaction: PendingTransaction,
)

View file

@ -32,6 +32,14 @@ sealed class YieldBalance {
.distinctBy { it.validatorAddress }
.size
}
fun getBalancesUniqueId(): Int {
// need to exclude rewards because their amount may change frequently
return balance.items
.filter { it.type != BalanceType.REWARDS }
.map { it.amount.toString() + it.type.toString() + it.groupId }
.hashCode()
}
}
data object Empty : YieldBalance()

View file

@ -5,18 +5,19 @@ import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
/**
* Use case for getting saved staking pending transactions.
*/
class GetPendingTransactionsUseCase(
class GetStakingPendingTransactionsUseCase(
private val stakingPendingTransactionRepository: StakingPendingTransactionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(): Either<StakingError, List<BalanceItem>> {
operator fun invoke(userWalletId: UserWalletId): Either<StakingError, List<BalanceItem>> {
return Either.catch {
stakingPendingTransactionRepository.getTransactionsWithBalanceItems().map { it.second }
stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId).map { it.second }
}.mapLeft {
stakingErrorResolver.resolve(it)
}

View file

@ -3,52 +3,74 @@ package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
import java.util.UUID
class InvalidatePendingTransactionsUseCase(
private val stakingPendingTransactionRepository: StakingPendingTransactionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(yieldBalance: YieldBalance): Either<StakingError, List<BalanceItem>> {
operator fun invoke(
userWalletId: UserWalletId,
balanceItems: List<BalanceItem>,
balancesId: Int,
): Either<StakingError, List<BalanceItem>> {
return Either.catch {
if (yieldBalance is YieldBalance.Data) {
val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions(
real = yieldBalance.balance.items,
pending = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(),
)
val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions(
realData = balanceItems,
newBalancesId = balancesId,
pendingData = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId),
)
stakingPendingTransactionRepository.removeTransactions(transactionsToRemove.toSet())
stakingPendingTransactionRepository.removeTransactions(userWalletId, transactionsToRemove.toSet())
balancesToDisplay
} else {
emptyList()
}
balancesToDisplay
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
private fun mergeRealAndPendingTransactions(
real: List<BalanceItem>,
pending: List<Pair<PendingTransaction, BalanceItem>>,
realData: List<BalanceItem>,
newBalancesId: Int,
pendingData: List<Pair<PendingTransaction, BalanceItem>>,
): Pair<List<BalanceItem>, List<PendingTransaction>> {
val map = real.associateBy { Triple(it.groupId, it.type, it.amount) }.toMutableMap()
val balances = realData.associateBy { Triple(it.groupId, it.type, it.amount) }.toMutableMap()
val toRemove = mutableListOf<PendingTransaction>()
val transactionsToRemove = mutableListOf<PendingTransaction>()
pending.forEach { (pendingTransaction, balanceItem) ->
pendingData.forEach { (pendingTransaction, balanceItem) ->
val key = Triple(balanceItem.groupId, balanceItem.type, balanceItem.amount)
if (map.containsKey(key)) {
map[key] = balanceItem
} else {
toRemove.add(pendingTransaction)
val oldBalancesId = pendingTransaction.balancesId
when {
newBalancesId != oldBalancesId -> {
transactionsToRemove.add(pendingTransaction)
}
balances.containsKey(key) -> {
balances[key] = balanceItem
}
else -> {
val groupId = UUID.randomUUID().toString()
balances[Triple(groupId, BalanceType.STAKED, pendingTransaction.amount)] = BalanceItem(
groupId = groupId,
type = pendingTransaction.type,
amount = pendingTransaction.amount,
rawCurrencyId = pendingTransaction.rawCurrencyId,
validatorAddress = pendingTransaction.validator?.address,
date = null,
pendingActions = emptyList(),
isPending = true,
)
}
}
}
return map.values.toList() to toRemove
return balances.values.toList() to transactionsToRemove
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
/**
* Use case for saving hash that failed to submit during staking confirmation
@ -14,9 +15,12 @@ class SavePendingTransactionUseCase(
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(pendingTransaction: PendingTransaction): Either<StakingError, Unit> {
operator fun invoke(userWalletId: UserWalletId, transaction: PendingTransaction): Either<StakingError, Unit> {
return Either.catch {
stakingPendingTransactionRepository.saveTransaction(pendingTransaction)
stakingPendingTransactionRepository.saveTransaction(
userWalletId = userWalletId,
transaction = transaction,
)
}.mapLeft {
stakingErrorResolver.resolve(it)
}

View file

@ -2,12 +2,13 @@ package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.wallets.models.UserWalletId
interface StakingPendingTransactionRepository {
fun getTransactionsWithBalanceItems(): List<Pair<PendingTransaction, BalanceItem>>
fun getTransactionsWithBalanceItems(userWalletId: UserWalletId): List<Pair<PendingTransaction, BalanceItem>>
fun saveTransaction(transaction: PendingTransaction)
fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction)
fun removeTransactions(transactions: Set<PendingTransaction>)
fun removeTransactions(userWalletId: UserWalletId, transactions: Set<PendingTransaction>)
}

View file

@ -85,17 +85,12 @@ internal object StakingNotification {
),
)
data class PendingAction(
data class Ordinary(
val title: TextReference,
val text: TextReference,
) : StakingNotification.Info(
title = title,
subtitle = text,
)
data object TronRevote : StakingNotification.Info(
title = resourceReference(R.string.staking_revote),
subtitle = resourceReference(R.string.staking_notifications_revote_tron_text),
)
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
@ -98,7 +99,7 @@ internal sealed class StakingStates {
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val reduceAmountBy: BigDecimal?,
val balanceState: BalanceState?,
val possiblePendingTransaction: PendingTransaction?,
) : ConfirmationState()
data class Empty(

View file

@ -8,6 +8,7 @@ import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.BalanceType.Companion.isClickable
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.BalanceState
@ -83,7 +84,11 @@ internal class BalanceItemConverter(
private fun getSubtitle(balance: BalanceItem) = when (balance.type) {
BalanceType.UNSTAKING -> getUnbondingDate(balance.date)
BalanceType.UNSTAKED -> resourceReference(R.string.staking_tap_to_withdraw)
BalanceType.LOCKED -> resourceReference(R.string.staking_tap_to_unlock)
BalanceType.LOCKED -> if (balance.pendingActions.any { it.type == StakingActionType.VOTE_LOCKED }) {
resourceReference(R.string.staking_tap_to_unlock_or_vote)
} else {
resourceReference(R.string.staking_tap_to_unlock)
}
BalanceType.PREPARING -> {
val warmupPeriod = yield.metadata.warmupPeriod.days
combinedReference(

View file

@ -28,12 +28,17 @@ internal class YieldBalancesConverter(
val cryptoCurrency = cryptoCurrencyStatus.currency
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance
val balanceToShowItems = balancesToShowProvider()
return if (yieldBalance is YieldBalance.Data) {
val cryptoRewardsValue = yieldBalance.getRewardStakingBalance()
val fiatRewardsValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoRewardsValue)
return if (yieldBalance is YieldBalance.Data || balanceToShowItems.any { it.isPending }) {
val cryptoRewardsValue = (yieldBalance as? YieldBalance.Data)?.getRewardStakingBalance()
val balanceToShowItems = balancesToShowProvider()
val fiatRate = cryptoCurrencyStatus.value.fiatRate
val fiatRewardsValue = if (fiatRate != null && cryptoRewardsValue != null) {
fiatRate.times(cryptoRewardsValue)
} else {
null
}
InnerYieldBalanceState.Data(
rewardsCrypto = BigDecimalFormatter.formatCryptoAmount(

View file

@ -8,10 +8,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.SubmitHashData
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
@ -35,6 +32,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import timber.log.Timber
import java.math.BigDecimal
import java.util.UUID
@Suppress("LongParameterList")
internal class StakingTransactionSender @AssistedInject constructor(
@ -91,12 +89,16 @@ internal class StakingTransactionSender @AssistedInject constructor(
sendStakingTransaction(
fullTransactionsData = fullTransactionsData,
balanceState = confirmationState.balanceState,
possiblePendingTransaction = composePendingTransaction(confirmationState),
onSendSuccess = onSendSuccess,
onSendError = onSendError,
)
}
private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
return confirmationState.possiblePendingTransaction ?: composeStakeTransaction(confirmationState)
}
private suspend fun getStakingTransactions(
state: StakingUiState,
confirmationState: StakingStates.ConfirmationState.Data,
@ -204,7 +206,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
private suspend fun sendStakingTransaction(
fullTransactionsData: List<FullTransactionData>,
balanceState: BalanceState?,
possiblePendingTransaction: PendingTransaction,
onSendSuccess: (txUrl: String) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
) {
@ -218,13 +220,9 @@ internal class StakingTransactionSender @AssistedInject constructor(
},
ifRight = { transactionHashes ->
submitHash(
transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id },
transactions = fullTransactionsData.map { it.stakeKitTransaction },
transactionHashes = transactionHashes,
groupId = balanceState?.groupId,
validator = balanceState?.validator,
amount = balanceState?.cryptoDecimal,
balanceType = balanceState?.type,
rawCurrencyId = balanceState?.rawCurrencyId,
pendingTransaction = possiblePendingTransaction,
)
val txUrl = getExplorerTransactionUrlUseCase(
txHash = transactionHashes.last(),
@ -238,25 +236,18 @@ internal class StakingTransactionSender @AssistedInject constructor(
}
private suspend fun submitHash(
transactionIds: List<String>,
transactions: List<StakingTransaction>,
transactionHashes: List<String>,
groupId: String?,
validator: Yield.Validator?,
amount: BigDecimal?,
balanceType: BalanceType?,
rawCurrencyId: String?,
pendingTransaction: PendingTransaction,
) {
transactionIds
transactions
.zip(transactionHashes)
.forEach { (transactionId, transactionHash) ->
.forEach { (transaction, transactionHash) ->
submitHashUseCase(
SubmitHashData(
transactionId = transactionId,
transactionId = transaction.id,
transactionHash = transactionHash,
validator = validator,
amount = amount,
balanceType = balanceType,
rawCurrencyId = rawCurrencyId,
pendingTransaction = pendingTransaction,
),
)
.onLeft { error ->
@ -267,20 +258,14 @@ internal class StakingTransactionSender @AssistedInject constructor(
),
)
saveUnsubmittedHashUseCase.invoke(
transactionId = transactionId,
transactionId = transaction.id,
transactionHash = transactionHash,
)
}.onRight {
Timber.d("Successful hash submission")
savePendingTransactionUseCase.invoke(
PendingTransaction(
groupId = groupId,
type = balanceType,
amount = amount,
rawCurrencyId = rawCurrencyId,
validator = validator,
),
)
if (transaction.type != StakingTransactionType.FREEZE_ENERGY) {
savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction)
}
}
}
}
@ -299,6 +284,19 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}
private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
val state = stateController.value
return PendingTransaction(
groupId = UUID.randomUUID().toString(),
type = BalanceType.STAKED,
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
validator = (confirmationState.validatorState as? ValidatorState.Content)?.chosenValidator,
balancesId = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.getBalancesUniqueId() ?: 0,
)
}
private data class FullTransactionData(
val stakeKitTransaction: StakingTransaction,
val tangemTransaction: TransactionData.Compiled,

View file

@ -93,6 +93,6 @@ internal object ConfirmationStatePreviewData {
isApprovalNeeded = false,
reduceAmountBy = null,
pendingActions = null,
balanceState = null,
possiblePendingTransaction = null,
)
}

View file

@ -273,17 +273,7 @@ internal class AddStakingNotificationsTransformer(
}
private fun MutableList<NotificationUM>.addEnterInfoNotifications() {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val hasStakedBalance = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.balance
?.items?.any {
it.type == BalanceType.PREPARING ||
it.type == BalanceType.STAKED ||
it.type == BalanceType.LOCKED
} == true
if (hasStakedBalance && isTron) {
add(StakingNotification.Info.TronRevote)
}
addTronRevoteNotification()
}
private fun MutableList<NotificationUM>.addPendingInfoNotifications(prevState: StakingUiState) {
@ -319,16 +309,39 @@ internal class AddStakingNotificationsTransformer(
null to null
}
}
StakingActionType.VOTE_LOCKED -> {
resourceReference(R.string.staking_revote) to
resourceReference(R.string.staking_notifications_revote_tron_text)
}
else -> null to null
}
if (titleReference != null && textReference != null) {
add(
StakingNotification.Info.PendingAction(
StakingNotification.Info.Ordinary(
title = titleReference,
text = textReference,
),
)
}
}
private fun MutableList<NotificationUM>.addTronRevoteNotification() {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value)
val hasStakedBalance = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.balance
?.items?.any {
it.type == BalanceType.PREPARING ||
it.type == BalanceType.STAKED ||
it.type == BalanceType.LOCKED
} == true
if (isTron && hasStakedBalance) {
add(
StakingNotification.Info.Ordinary(
title = resourceReference(R.string.staking_revote),
text = resourceReference(R.string.staking_notifications_revote_tron_text),
),
)
}
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
internal class SetBalanceStateTransformer(
private val balanceState: BalanceState? = null,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
?: return prevState
return prevState.copy(
confirmationState = possibleConfirmationState.copy(balanceState = balanceState),
)
}
}

View file

@ -144,9 +144,19 @@ internal class SetButtonsStateTransformer(
clickIntents.onAmountValueChange("") // reset amount state
clickIntents.onNextClick(StakingActionCommonType.ENTER)
}
StakingStep.Validators,
StakingStep.Amount,
-> clickIntents.onNextClick()
StakingStep.Validators -> {
if (confirmationState is StakingStates.ConfirmationState.Data) {
clickIntents.onNextClick(
pendingActions = confirmationState.pendingActions,
pendingAction = confirmationState.pendingAction,
)
} else {
clickIntents.onNextClick()
}
}
StakingStep.Amount -> {
clickIntents.onNextClick()
}
StakingStep.Confirmation -> onConfirmationClick()
StakingStep.RewardsValidators -> Unit
}

View file

@ -20,9 +20,12 @@ internal class SetConfirmationStateLoadingTransformer(
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val filteredValidators = yield.validators.filter {
it.preferred
}
val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val possibleValidatorState = possibleConfirmationState?.validatorState as? ValidatorState.Content
val chosenValidator = possibleValidatorState?.chosenValidator ?: yield.validators[0]
val chosenValidator = possibleValidatorState?.chosenValidator ?: filteredValidators[0]
return prevState.copy(
confirmationState = StakingStates.ConfirmationState.Data(
@ -32,7 +35,7 @@ internal class SetConfirmationStateLoadingTransformer(
validatorState = ValidatorState.Content(
isClickable = true,
chosenValidator = chosenValidator,
availableValidators = yield.validators,
availableValidators = filteredValidators,
),
notifications = persistentListOf(),
footerText = getFooter(prevState, chosenValidator),
@ -41,7 +44,7 @@ internal class SetConfirmationStateLoadingTransformer(
pendingActions = possibleConfirmationState?.pendingActions,
isApprovalNeeded = false,
reduceAmountBy = null,
balanceState = null,
possiblePendingTransaction = null,
),
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.ValidatorState
import com.tangem.utils.transformer.Transformer
internal object SetConfirmationStateResetAssentTransformer : Transformer<StakingUiState> {
@ -13,7 +14,7 @@ internal object SetConfirmationStateResetAssentTransformer : Transformer<Staking
confirmationState.copy(
isPrimaryButtonEnabled = true,
innerState = InnerConfirmationStakingState.ASSENT,
validatorState = confirmationState.validatorState.copySealed(isClickable = true),
validatorState = ValidatorState.Loading,
)
} else {
confirmationState

View file

@ -232,7 +232,7 @@ internal class SetInitialDataStateTransformer(
pendingActions = null,
isApprovalNeeded = isApprovalNeeded,
reduceAmountBy = null,
balanceState = null,
possiblePendingTransaction = null,
)
}

View file

@ -0,0 +1,33 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.utils.transformer.Transformer
internal class SetPossiblePendingTransactionTransformer(
private val balanceState: BalanceState,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState =
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val balancesId = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.getBalancesUniqueId() ?: 0
return prevState.copy(
confirmationState = possibleConfirmationState.copy(
possiblePendingTransaction = PendingTransaction(
groupId = balanceState.groupId,
type = balanceState.type,
amount = balanceState.cryptoDecimal,
rawCurrencyId = balanceState.rawCurrencyId,
validator = balanceState.validator,
balancesId = balancesId,
),
),
)
}
}

View file

@ -19,6 +19,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
import com.tangem.features.staking.impl.presentation.state.StakingStates
@ -39,6 +40,7 @@ internal fun StakingConfirmationContent(
) {
if (state !is StakingStates.ConfirmationState.Data) return
val isEnterAction = type == StakingActionCommonType.ENTER
val showValidatorBlock = isEnterAction || state.pendingAction?.type == StakingActionType.VOTE_LOCKED
val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED
Column(
modifier = Modifier
@ -63,7 +65,7 @@ internal fun StakingConfirmationContent(
isEditingDisabled = !isEnterAction && state.innerState != InnerConfirmationStakingState.COMPLETED,
onClick = clickIntents::onPrevClick,
)
if (isEnterAction) {
if (showValidatorBlock) {
ValidatorBlock(validatorState = state.validatorState, onClick = clickIntents::openValidators)
}
StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent)

View file

@ -136,9 +136,12 @@ internal class StakingViewModel @Inject constructor(
private val balancesToShow: List<BalanceItem>
get() {
return cryptoCurrencyStatus.value.yieldBalance?.let {
invalidatePendingTransactionsUseCase(it).getOrElse { emptyList() }
} ?: emptyList()
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
return invalidatePendingTransactionsUseCase(
userWalletId = userWallet.walletId,
balanceItems = yieldBalance?.balance?.items ?: emptyList(),
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
).getOrElse { emptyList() }
}
private var isInitialInfoAnalyticSent: Boolean = false
@ -218,13 +221,31 @@ internal class StakingViewModel @Inject constructor(
stakingStateRouter.onNextClick()
when {
isInitState() -> {
stateController.update(SetConfirmationStateResetAssentTransformer)
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
stateController.update(SetBalanceStateTransformer(balanceState))
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
}
stateController.update(
transformer = SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = true,
isApprovalNeeded = stakingApproval is StakingApproval.Needed,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
)
onRefreshSwipe(isRefreshing = false)
}
isAssentState() -> {
getFee(pendingAction, pendingActions)
stateController.update(SetBalanceStateTransformer(balanceState))
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
}
val amountState = value.amountState as? AmountState.Data
if (amountState?.amountTextField?.isWarning == true) {
stateController.update(
@ -328,6 +349,7 @@ internal class StakingViewModel @Inject constructor(
if (value.currentStep == StakingStep.Confirmation) {
when ((value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState) {
InnerConfirmationStakingState.ASSENT -> {
stateController.update(SetConfirmationStateResetAssentTransformer)
stakingStateRouter.onPrevClick()
}
null,

View file

@ -13,6 +13,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.RewardBlockType
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.error.CurrencyStatusError
@ -37,6 +38,7 @@ internal class TokenDetailsLoadedBalanceConverter(
private val currentStateProvider: Provider<TokenDetailsState>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
private val pendingBalancesProvider: Provider<List<BalanceItem>>,
private val symbol: String,
private val decimals: Int,
private val clickIntents: TokenDetailsClickIntents,
@ -143,16 +145,28 @@ internal class TokenDetailsLoadedBalanceConverter(
val stakingEntryInfo = stakingEntryInfoProvider.invoke()
val iconState = state.tokenInfoBlockState.iconState
val pendingBalances = pendingBalancesProvider.invoke()
val fiatRate = status.value.fiatRate
return when {
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> {
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null && pendingBalances.isEmpty() -> {
getStakeAvailableState(stakingEntryInfo, iconState)
}
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null && pendingBalances.isNotEmpty() -> {
val pendingBalancesCryptoAmount = pendingBalances.sumOf { it.amount }
val stakingFiatAmount = fiatRate?.multiply(pendingBalancesCryptoAmount)
getStakedState(
status = status,
stakingCryptoAmount = pendingBalancesCryptoAmount,
stakingFiatAmount = stakingFiatAmount,
stakingRewardAmount = null,
)
}
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo == null -> {
StakingBlockUM.Error(iconState = iconState)
}
else -> {
val fiatRate = status.value.fiatRate
val stakingRewardAmount = yieldBalance?.getRewardStakingBalance()?.let { fiatRate?.multiply(it) }
val stakingFiatAmount = stakingCryptoAmount?.let { fiatRate?.multiply(it) }

View file

@ -19,6 +19,7 @@ import com.tangem.domain.card.NetworkHasDerivationUseCase
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.*
@ -52,6 +53,7 @@ internal class TokenDetailsStateFactory(
private val appCurrencyProvider: Provider<AppCurrency>,
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val pendingBalancesProvider: Provider<List<BalanceItem>>,
private val clickIntents: TokenDetailsClickIntents,
private val featureToggles: TokenDetailsFeatureToggles,
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
@ -81,6 +83,7 @@ internal class TokenDetailsStateFactory(
currentStateProvider = currentStateProvider,
appCurrencyProvider = appCurrencyProvider,
stakingEntryInfoProvider = stakingEntryInfoProvider,
pendingBalancesProvider = pendingBalancesProvider,
symbol = symbol,
decimals = decimals,
clickIntents = clickIntents,

View file

@ -32,11 +32,13 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.staking.GetStakingPendingTransactionsUseCase
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
import com.tangem.domain.staking.GetYieldUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo
@ -119,6 +121,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val associateAssetUseCase: AssociateAssetUseCase,
private val getStakingPendingTransactionsUseCase: GetStakingPendingTransactionsUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val vibratorHapticManager: VibratorHapticManager,
@ -148,6 +151,8 @@ internal class TokenDetailsViewModel @Inject constructor(
private val warningsJobHolder = JobHolder()
private val swapTxJobHolder = JobHolder()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val stakingPendingBalances: List<BalanceItem>
get() = getStakingPendingTransactionsUseCase(userWalletId).getOrElse { emptyList() }
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var stakingEntryInfo: StakingEntryInfo? = null
@ -158,6 +163,7 @@ internal class TokenDetailsViewModel @Inject constructor(
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
stakingEntryInfoProvider = Provider { stakingEntryInfo },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
pendingBalancesProvider = Provider { stakingPendingBalances },
clickIntents = this,
symbol = cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,

View file

@ -89,7 +89,7 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "release-app_5.16-803"
tangemBlockchainSdk = "release-app_5.16-807"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.16-386"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^

View file

@ -126,6 +126,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"sei-network" -> Blockchain.Sei
"sei-network/test" -> Blockchain.SeiTestnet
"internet-computer" -> Blockchain.InternetComputer
"sui" -> Blockchain.Sui
"sui/test" -> Blockchain.SuiTestnet
else -> null
}
}
@ -253,6 +255,8 @@ fun Blockchain.toNetworkId(): String {
Blockchain.Sei -> "sei-network"
Blockchain.SeiTestnet -> "sei-network/test"
Blockchain.InternetComputer -> "internet-computer"
Blockchain.Sui -> "sui"
Blockchain.SuiTestnet -> "sui/test"
}
}
@ -337,6 +341,7 @@ fun Blockchain.toCoinId(): String {
Blockchain.Cyber, Blockchain.CyberTestnet -> "cyber-ethereum"
Blockchain.Sei, Blockchain.SeiTestnet -> "sei-network"
Blockchain.InternetComputer -> "internet-computer"
Blockchain.Sui, Blockchain.SuiTestnet -> "sui"
}
}