Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-05 14:46:29 +04:00
parent 07833b1814
commit 439fb00fb2
14 changed files with 89 additions and 39 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.core.analytics.models.event
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
/**
@ -71,5 +72,15 @@ sealed class MainScreenAnalyticsEvent(
event = "Button - Close",
params = mapOf(AnalyticsParam.SOURCE to source.value),
)
data class HotTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
event = "Hot Token Clicked",
params = mapOf(TOKEN_PARAM to currencySymbol),
)
data class HotTokenError(val errorCode: String) : MainScreenAnalyticsEvent(
event = "Hot Token Error",
params = mapOf(ERROR_CODE to errorCode),
)
// endregion
}

View file

@ -37,7 +37,7 @@
},
{
"name": "ONRAMP_HOT_TOKENS_ENABLED",
"version": "undefined"
"version": "5.21.0"
},
{
"name": "STAKING_TON_ENABLED",

View file

@ -20,6 +20,7 @@ android {
dependencies {
/** Project */
implementation(projects.core.analytics)
implementation(projects.core.utils)
implementation(projects.libs.auth)
implementation(projects.domain.appTheme.models)

View file

@ -1,5 +1,8 @@
package com.tangem.datasource.exchangeservice.hotcrypto
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
@ -32,6 +35,7 @@ internal class DefaultHotCryptoLoader @Inject constructor(
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
) : HotCryptoLoader {
override suspend fun fetch(tokens: List<UserTokensResponse.Token>) {
@ -68,6 +72,12 @@ internal class DefaultHotCryptoLoader @Inject constructor(
}
.onFailure {
Timber.e(it, "Unable to fetch hot crypto")
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.HotTokenError(
errorCode = (it as? ApiResponseError.HttpException)?.code?.code?.toString().orEmpty(),
),
)
}
}

View file

@ -142,7 +142,7 @@
<string name="common_generate_addresses">Adressen synchronisieren</string>
<string name="common_go_to_provider">Zum Anbieter gehen</string>
<string name="common_go_to_token">Zum Token</string>
<string name="common_hide">Ausblenden</string>
<string name="common_hide">Ausblenden</string>
<string name="common_hour">stunde</string>
<string name="common_import">Importieren</string>
<string name="common_in_progress">In Arbeit</string>
@ -316,8 +316,8 @@
<string name="express_status_buying">Kauf %s</string>
<string name="express_status_buying_active">Kauf %s…</string>
<string name="express_status_hide_button_text">Diese Transaktion ausblenden</string>
<string name="express_status_hide_dialog_text">Sobald der Transaktionsstatus ausgeblendet ist, kann er nicht mehr angezeigt werden. Stattdessen kannst Du ihn durch einfaches Wischen schließen.</string>
<string name="express_status_hide_dialog_title">Diese Transaktion ausblenden</string>
<string name="express_status_hide_dialog_text">Sobald der Transaktionsstatus ausgeblendet ist, kann er nicht mehr angezeigt werden. Stattdessen kannst Du ihn durch einfaches Wischen schließen.</string>
<string name="express_status_hide_dialog_title">Diese Transaktion ausblenden</string>
<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>

View file

@ -87,8 +87,8 @@
<string name="cardano_max_amount_has_token_title">ADA insuffisant</string>
<string name="common_accept">Accepter</string>
<string name="common_access_denied">Accès refusé</string>
<string name="common_add_to_portfolio">Ajouter au portfolio</string>
<string name="common_add_token">Ajouter un jeton</string>
<string name="common_add_to_portfolio">Ajouter au portfolio</string>
<string name="common_add_token">Ajouter un jeton</string>
<string name="common_all">Tous</string>
<string name="common_allow">Permettre</string>
<string name="common_analytics">Analytique</string>
@ -142,7 +142,7 @@
<string name="common_generate_addresses">Synchroniser les adresses</string>
<string name="common_go_to_provider">Aller au fournisseur</string>
<string name="common_go_to_token">Aller au jeton</string>
<string name="common_hide">Cacher</string>
<string name="common_hide">Cacher</string>
<string name="common_hour">heure</string>
<string name="common_import">Importez</string>
<string name="common_in_progress">En cours</string>
@ -315,9 +315,9 @@
<string name="express_status_bought">Acheté %s</string>
<string name="express_status_buying">Achat en cours %s</string>
<string name="express_status_buying_active">Achat en cours %s...</string>
<string name="express_status_hide_button_text">Masquer cette transaction</string>
<string name="express_status_hide_dialog_text">Une fois masqué, le statut de la transaction ne peut plus être consulté. Vous pouvez simplement faire glisser votre doigt pour le fermer.</string>
<string name="express_status_hide_dialog_title">Masquer le statut de la transaction ?</string>
<string name="express_status_hide_button_text">Masquer cette transaction</string>
<string name="express_status_hide_dialog_text">Une fois masqué, le statut de la transaction ne peut plus être consulté. Vous pouvez simplement faire glisser votre doigt pour le fermer.</string>
<string name="express_status_hide_dialog_title">Masquer le statut de la transaction ?</string>
<string name="express_token_list_empty_search">Aucun jeton trouvé. Veuillez essayer une autre demande</string>
<string name="express_transaction_id">ID : %s</string>
<string name="express_transaction_id_copied">ID de transaction copié</string>
@ -344,8 +344,8 @@
<string name="give_permission_unlimited">Illimité</string>
<string name="home_button_order">Commandez</string>
<string name="home_button_scan">Scannez</string>
<string name="hot_crypto_add_token_subtitle">à %s</string>
<string name="hot_crypto_token_network">Via %s</string>
<string name="hot_crypto_add_token_subtitle">à %s</string>
<string name="hot_crypto_token_network">Via %s</string>
<string name="information_generated_with_ai">Ces informations ont été générées avec l\'IA.\nAppuyez ici si vous trouvez des erreurs.</string>
<string name="initial_message_change_access_code_body">Touchez, pour modifier le code d\'accès</string>
<string name="initial_message_change_passcode_body">Touchez, pour modifier le mot de passe</string>
@ -794,8 +794,8 @@
<string name="staking_migrate">Migrer</string>
<string name="staking_native">Native staking</string>
<string name="staking_no_validators_error_message">Aucun validateur actif n\'est disponible pour le staking pour le moment. Veuillez réessayer plus tard.</string>
<string name="staking_notification_additional_ada_deposit_text">Lors du staking sur le réseau Cardano, l\'intégralité du solde est utilisée. 2 ADA supplémentaires seront réservés et restitués après le dé-staking. Votre ADA n\'est pas verrouillé pendant le staking.</string>
<string name="staking_notification_additional_ada_deposit_title">Détails du ADA staking </string>
<string name="staking_notification_additional_ada_deposit_text">Lors du staking sur le réseau Cardano, l\'intégralité du solde est utilisée. 2 ADA supplémentaires seront réservés et restitués après le dé-staking. Votre ADA n\'est pas verrouillé pendant le staking.</string>
<string name="staking_notification_additional_ada_deposit_title">Détails du ADA staking </string>
<string name="staking_notification_claim_rewards_text">Récomp. gagnées vous seront envoyées et dispo pour utilisation immédiate</string>
<string name="staking_notification_earn_rewards_text">Stakez et commencez à gagner vos récompenses</string>
<string name="staking_notification_earn_rewards_text_daily">Stakez en toute sécurité et commencez à gagner des récompenses quotidiennes</string>
@ -809,8 +809,8 @@
<string name="staking_notification_earn_rewards_title">Gagnez des récompenses de staking</string>
<string name="staking_notification_low_staked_balance_text">Votre solde stakée sera trop faible pour annuler votre staking. Vous devrez staker davantage pour atteindre le montant minimum pour annuler le staking.</string>
<string name="staking_notification_low_staked_balance_title">Solde de staking faible</string>
<string name="staking_notification_minimum_balance_error_text">Un minimum de %1$s %2$s est requis pour le re-staking. Veuillez recharger votre solde.</string>
<string name="staking_notification_minimum_balance_error_title">Pas assez de %s</string>
<string name="staking_notification_minimum_balance_error_text">Un minimum de %1$s %2$s est requis pour le re-staking. Veuillez recharger votre solde.</string>
<string name="staking_notification_minimum_balance_error_title">Pas assez de %s</string>
<string name="staking_notification_network_error_text">L\'option de staking n\'est actuellement pas disponible en raison des conditions du réseau. Veuillez réessayer plus tard.</string>
<string name="staking_notification_new_validator_funds_transfer">Le staking dans le réseau %1$s avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur</string>
<string name="staking_notification_restake_rewards_text">Réinvestissez vos récompenses gagnées dans le montant que vous avez staké, augmentant ainsi vos gains potentiels.</string>
@ -1057,7 +1057,7 @@
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Êtes-vous sûr(e) de vouloir annuler la transaction ? Vous ne pourrez plus réessayer.</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Votre transaction d\'un montant de %1$s %2$s n\'a pas été finalisée. Vous pouvez réessayer plus tard pour la finaliser.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">Vous avez une transaction inachevée</string>
<string name="warning_last_balance_updated_time">La dernière mise à jour a été %s</string>
<string name="warning_last_balance_updated_time">La dernière mise à jour a été %s</string>
<string name="warning_low_signatures_message">Il ne reste que %s signatures sur cette carte. Vous devez retirer tous vos fonds.</string>
<string name="warning_low_signatures_title">Faible nombre de signatures</string>
<string name="warning_manage_tokens_legacy_derivation_message">Les jetons sur différents réseaux peuvent avoir des adresses différentes. Vérifiez bien que votre adresse correspond au réseau lorsque vous transférez des fonds.</string>
@ -1090,7 +1090,7 @@
<string name="warning_solana_rent_fee_message">Le réseau Solana facture un loyer de %1$s tous les 2 jours. Les comptes qui ne peuvent pas se permettre le loyer sont purgés du réseau. Déposez sur votre compte plus de %2$s pour l\'utiliser gratuitement.</string>
<string name="warning_some_networks_unreachable_message">Certains réseaux sont actuellement inaccessibles. Veuillez réessayer plus tard.</string>
<string name="warning_some_networks_unreachable_title">Certains réseaux sont inaccessibles</string>
<string name="warning_some_token_balances_not_updated">Certains soldes de jetons n\'ont pas pu être mis à jour</string>
<string name="warning_some_token_balances_not_updated">Certains soldes de jetons n\'ont pas pu être mis à jour</string>
<string name="warning_testnet_card_message">Il s\'agit d\'une carte Testnet. Elle ne peut pas traiter les transactions et ne doit être utilisée qu\'à des fins de test et de développement.</string>
<string name="warning_testnet_card_title">À des fins de test uniquement</string>
<string name="welcome_interrupted_backup_alert_discard">Ignorer</string>

View file

@ -139,7 +139,7 @@
<string name="common_generate_addresses">アドレスを同期する</string>
<string name="common_go_to_provider">プロバイダーへ移動</string>
<string name="common_go_to_token">トークンへ移動</string>
<string name="common_hide">非表示</string>
<string name="common_hide">非表示</string>
<string name="common_hour">時間</string>
<string name="common_import">インポート</string>
<string name="common_in_progress">進行中</string>
@ -312,9 +312,9 @@
<string name="express_status_bought">%sを買い付けました</string>
<string name="express_status_buying">%sを買い付けています</string>
<string name="express_status_buying_active">%sを買い付けています...</string>
<string name="express_status_hide_button_text">この取引を非表示にする</string>
<string name="express_status_hide_dialog_text">一度非表示にすると、取引状況を再度表示することはできません。代わりに、スワイプして閉じることができます。</string>
<string name="express_status_hide_dialog_title">取引状況を非表示にしますか?</string>
<string name="express_status_hide_button_text">この取引を非表示にする</string>
<string name="express_status_hide_dialog_text">一度非表示にすると、取引状況を再度表示することはできません。代わりに、スワイプして閉じることができます。</string>
<string name="express_status_hide_dialog_title">取引状況を非表示にしますか?</string>
<string name="express_token_list_empty_search">トークンが見つかりません。別のリクエストをお試しください。</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">取引IDをコピーしました</string>
@ -778,8 +778,8 @@
<string name="staking_details_unbonding_period_info">ステーキングから資金の引き出しを要求した後、トークンが利用可能になるまでの待機期間。</string>
<string name="staking_details_warmup_period">ウォームアップ期間</string>
<string name="staking_details_warmup_period_info">ステーキングへの参加を有効にするために割り当てられた時間。</string>
<string name="staking_error_no_validators_message">現在、利用可能なバリデーターは見つかりません。しばらくしてからもう一度お試しください。</string>
<string name="staking_error_no_validators_title">ステーキングは利用できません</string>
<string name="staking_error_no_validators_message">現在、利用可能なバリデーターは見つかりません。しばらくしてからもう一度お試しください。</string>
<string name="staking_error_no_validators_title">ステーキングは利用できません</string>
<string name="staking_give_permission_fee_footer">ネットワークは、ステーキングのためにトークンの使用を承認していることを確認するために、トークン承認手数料を請求します。</string>
<string name="staking_legal">ステーキング機能を使用すると、プロバイダーの%1$sと%2$sに同意したことになります</string>
<string name="staking_locked">ロック中</string>
@ -801,8 +801,8 @@
<string name="staking_notification_earn_rewards_title">ステーキング報酬を獲得</string>
<string name="staking_notification_low_staked_balance_text">残りのステーキング残高が少なすぎてステーキングを解除できません。最小のステーキング解除残高を満たすには、さらにステーキングする必要があります。</string>
<string name="staking_notification_low_staked_balance_title">ステーキング残高が低いです</string>
<string name="staking_notification_minimum_balance_error_text">再ステーキングには最低%1$s %2$sが必要です。残高を補充してください。</string>
<string name="staking_notification_minimum_balance_error_title">%sが足りません</string>
<string name="staking_notification_minimum_balance_error_text">再ステーキングには最低%1$s %2$sが必要です。残高を補充してください。</string>
<string name="staking_notification_minimum_balance_error_title">%sが足りません</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>

View file

@ -345,7 +345,7 @@
<string name="home_button_order">Order Tangem</string>
<string name="home_button_scan">Scan Tangem</string>
<string name="hot_crypto_add_token_subtitle">to %s</string>
<string name="hot_crypto_token_network">In %s network</string>
<string name="hot_crypto_token_network">On %s network</string>
<string name="information_generated_with_ai">This information was generated with AI.\nTap here, if you find any errors.</string>
<string name="initial_message_change_access_code_body">To change the access code tap the card or ring as shown above and do not remove until the end of the operation</string>
<string name="initial_message_change_passcode_body">To change the passcode tap the card as shown above and do not remove until the end of the operation</string>

View file

@ -21,6 +21,7 @@ class GetCryptoCurrencyStatusSyncUseCase(
internal val dispatchers: CoroutineDispatcherProvider,
) {
// multi-currency
suspend operator fun invoke(
userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID,
@ -38,6 +39,7 @@ class GetCryptoCurrencyStatusSyncUseCase(
.mapLeft { error -> error.mapToCurrencyError() }
}
// single-currency
suspend operator fun invoke(userWalletId: UserWalletId): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,

View file

@ -13,7 +13,7 @@ import com.tangem.features.onramp.impl.R
* @property currencyName currency name
* @property networkName network name
* @property currencyIconState currency icon state
* @property onAddClick lambda be invoked when add button is clicked
* @property addButtonUM add button UM
*
[REDACTED_AUTHOR]
*/
@ -22,7 +22,7 @@ data class OnrampAddToPortfolioUM(
val currencyName: String,
val networkName: String,
val currencyIconState: CurrencyIconState,
val onAddClick: () -> Unit,
val addButtonUM: AddButtonUM,
) {
val bsTitle: TextReference = resourceReference(id = R.string.common_add_token)
@ -37,5 +37,8 @@ data class OnrampAddToPortfolioUM(
formatArgs = wrappedList(networkName),
)
val addButtonText: TextReference = resourceReference(R.string.common_add_to_portfolio)
data class AddButtonUM(val isProgress: Boolean, val onClick: () -> Unit) {
val text: TextReference = resourceReference(R.string.common_add_to_portfolio)
}
}

View file

@ -13,6 +13,7 @@ import com.tangem.features.onramp.hottokens.portfolio.entity.OnrampAddToPortfoli
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -48,7 +49,7 @@ internal class OnrampAddToPortfolioModel @Inject constructor(
currencyName = params.cryptoCurrency.name,
networkName = params.cryptoCurrency.network.name,
currencyIconState = params.currencyIconState,
onAddClick = ::onAddClick,
addButtonUM = OnrampAddToPortfolioUM.AddButtonUM(isProgress = false, onClick = ::onAddClick),
)
}
@ -60,8 +61,12 @@ internal class OnrampAddToPortfolioModel @Inject constructor(
private fun onAddClick() {
modelScope.launch {
changeAddButtonProgressStatus(isProgress = true)
derivePublicKeysUseCase(params.userWalletId, listOfNotNull(params.cryptoCurrency)).getOrElse {
Timber.e("Failed to derive public keys: $it")
changeAddButtonProgressStatus(isProgress = false)
}
addCryptoCurrenciesUseCase(
@ -69,6 +74,13 @@ internal class OnrampAddToPortfolioModel @Inject constructor(
currencies = listOfNotNull(params.cryptoCurrency),
)
.onRight { params.onSuccessAdding(params.cryptoCurrency.id) }
.onLeft { changeAddButtonProgressStatus(isProgress = false) }
}
}
private fun changeAddButtonProgressStatus(isProgress: Boolean) {
_state.update {
it.copy(addButtonUM = it.addButtonUM.copy(isProgress = isProgress))
}
}
}

View file

@ -66,8 +66,7 @@ fun OnrampAddToPortfolioContent(state: OnrampAddToPortfolioUM) {
SpacerH24()
AddToPortfolioButton(
text = state.addButtonText,
onClick = state.onAddClick,
state = state.addButtonUM,
modifier = Modifier.padding(horizontal = 16.dp),
)
@ -109,13 +108,13 @@ private fun CurrencyNetworkName(text: TextReference, modifier: Modifier = Modifi
}
@Composable
private fun AddToPortfolioButton(text: TextReference, onClick: () -> Unit, modifier: Modifier = Modifier) {
private fun AddToPortfolioButton(state: OnrampAddToPortfolioUM.AddButtonUM, modifier: Modifier = Modifier) {
TangemButton(
text = text.resolveReference(),
text = state.text.resolveReference(),
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
onClick = onClick,
onClick = state.onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
showProgress = false,
showProgress = state.isProgress,
enabled = true,
modifier = modifier.fillMaxWidth(),
size = TangemButtonSize.WideAction,
@ -150,7 +149,7 @@ private fun PreviewOnrampAddToPortfolioContent() {
isGrayscale = false,
showCustomBadge = false,
),
onAddClick = {},
addButtonUM = OnrampAddToPortfolioUM.AddButtonUM(isProgress = false, onClick = {}),
),
)
},

View file

@ -59,7 +59,7 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
context = child(key = "hot_crypto"),
params = HotCryptoComponent.Params(
userWalletId = params.userWalletId,
onTokenClick = model::onTokenClick,
onTokenClick = model::onHotTokenClick,
),
)
} else {

View file

@ -72,6 +72,18 @@ internal class OnrampOperationModel @Inject constructor(
},
)
selectTokenIfDemoModeOff(status)
}
fun onHotTokenClick(status: CryptoCurrencyStatus) {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.HotTokenClicked(currencySymbol = status.currency.symbol),
)
selectTokenIfDemoModeOff(status)
}
private fun selectTokenIfDemoModeOff(status: CryptoCurrencyStatus) {
if (params is Params.Sell || !onrampFeatureToggles.isFeatureEnabled) {
showErrorIfDemoModeOrElse { selectToken(status) }
} else {