Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-30 15:58:51 +03:00
commit acf110dbcc
30 changed files with 171 additions and 63 deletions

View file

@ -4,30 +4,38 @@ import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import java.util.WeakHashMap import timber.log.Timber
import kotlin.reflect.KClass import kotlin.reflect.KClass
class ForegroundActivityObserver { object ForegroundActivityObserver {
private val activities = WeakHashMap<KClass<out Activity>, AppCompatActivity>() private val activities = HashMap<KClass<out Activity>, AppCompatActivity>()
val foregroundActivity: AppCompatActivity? val foregroundActivity: AppCompatActivity?
get() = activities.entries get() = activities.entries
.firstOrNull { it.value?.isDestroyed == false } .firstOrNull { entry ->
Timber.i("foregroundActivity: ${entry.key} | ${entry.value.isDestroyed}")
entry.value.isDestroyed == false
}
?.value ?.value
internal val callbacks: ActivityLifecycleCallbacks internal val callbacks: ActivityLifecycleCallbacks
get() = Callbacks() get() = Callbacks()
internal inner class Callbacks : ActivityLifecycleCallbacks { internal class Callbacks : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
} }
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
activities[activity::class] = activity as? AppCompatActivity Timber.i("onActivityResumed ${activity::class}")
if (activity is AppCompatActivity) {
Timber.i("onActivityResumed store activity")
activities[activity::class] = activity
}
} }
override fun onActivityDestroyed(activity: Activity) { override fun onActivityDestroyed(activity: Activity) {
Timber.i("onActivityDestroyed")
activities.remove(activity::class) activities.remove(activity::class)
} }

View file

@ -191,6 +191,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>() private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("onCreate")
// We need to call it before onCreate to prevent unnecessary activity recreation // We need to call it before onCreate to prevent unnecessary activity recreation
installAppTheme() installAppTheme()
@ -328,15 +329,18 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
Timber.i("onStart")
dialogManager.onStart(this) dialogManager.onStart(this)
} }
override fun onStop() { override fun onStop() {
dialogManager.onStop() dialogManager.onStop()
super.onStop() super.onStop()
Timber.i("onStop")
} }
override fun onDestroy() { override fun onDestroy() {
Timber.i("onDestroy")
// workaround: kill process when activity destroy to avoid state when lock() wallets // workaround: kill process when activity destroy to avoid state when lock() wallets
// and navigation to unlock screen was skipped because system kills activity but not process // and navigation to unlock screen was skipped because system kills activity but not process
if (BuildConfig.BUILD_TYPE != MOCKED_BUILD_TYPE) { if (BuildConfig.BUILD_TYPE != MOCKED_BUILD_TYPE) {

View file

@ -85,7 +85,7 @@ import timber.log.Timber
lateinit var store: Store<AppState> lateinit var store: Store<AppState>
lateinit var foregroundActivityObserver: ForegroundActivityObserver val foregroundActivityObserver = ForegroundActivityObserver
internal lateinit var derivationsFinder: DerivationsFinder internal lateinit var derivationsFinder: DerivationsFinder
open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider { open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider {
@ -246,6 +246,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
override fun onCreate() { override fun onCreate() {
enableStrictModeInDebug() enableStrictModeInDebug()
preInit()
super.onCreate() super.onCreate()
init() init()
} }
@ -286,22 +287,25 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
// } // }
} }
/**
* Initialize components that need to be initialized before [super.onCreate] is called
*/
private fun preInit() {
tangemAppLoggerInitializer.initialize()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
}
fun init() { fun init() {
apiConfigsManager.initialize() apiConfigsManager.initialize()
store = createReduxStore() store = createReduxStore()
tangemAppLoggerInitializer.initialize()
Timber.i("APP STARTED") Timber.i("APP STARTED")
if (BuildConfig.TESTER_MENU_ENABLED) { if (BuildConfig.TESTER_MENU_ENABLED) {
Timber.i(featureTogglesManager.toString()) Timber.i(featureTogglesManager.toString())
Timber.i(excludedBlockchainsManager.toString()) Timber.i(excludedBlockchainsManager.toString())
} }
foregroundActivityObserver = ForegroundActivityObserver()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
runBlocking { runBlocking {
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
} }

View file

@ -23,10 +23,12 @@ class AppsFlyerAnalyticsHandler(
} }
class Builder : AnalyticsHandlerBuilder { class Builder : AnalyticsHandlerBuilder {
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = null
!data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerApiKey, data.config.appsAppId) // disabled for now until analytics strategy is defined
data.isDebug && data.logConfig.appsflyer -> AppsFlyerLogClient(data.jsonConverter) // when {
else -> null // !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerApiKey, data.config.appsAppId)
}?.let { AppsFlyerAnalyticsHandler(it) } // data.isDebug && data.logConfig.appsflyer -> AppsFlyerLogClient(data.jsonConverter)
// else -> null
// }?.let { AppsFlyerAnalyticsHandler(it) }
} }
} }

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/> <background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View file

@ -19,7 +19,10 @@
<string name="account_archived_recover">回復する</string> <string name="account_archived_recover">回復する</string>
<string name="account_archived_recover_dialog_description">「 %1$s 」を回復しようとしています。</string> <string name="account_archived_recover_dialog_description">「 %1$s 」を回復しようとしています。</string>
<string name="account_archived_recover_dialog_title">アカウントを回復する</string> <string name="account_archived_recover_dialog_title">アカウントを回復する</string>
<string name="account_archived_recover_error_message">すでにアクティブアカウント数の上限20件を超えています。復元するには1件アーカイブしてください。</string>
<string name="account_archived_recover_error_title">アカウントを復元できません</string>
<string name="account_archived_title">アーカイブ済み</string> <string name="account_archived_title">アーカイブ済み</string>
<string name="account_could_not_create">アカウントを作成できませんでした。しばらくしてからもう一度お試しください。</string>
<string name="account_create_success_message">アカウントを作成しました</string> <string name="account_create_success_message">アカウントを作成しました</string>
<string name="account_details_archive">アカウントをアーカイブする</string> <string name="account_details_archive">アカウントをアーカイブする</string>
<string name="account_details_archive_action">アーカイブ</string> <string name="account_details_archive_action">アーカイブ</string>
@ -258,6 +261,7 @@
<string name="common_left">残り%1$s</string> <string name="common_left">残り%1$s</string>
<string name="common_legacy_bitcoin_address">レガシービットコイン</string> <string name="common_legacy_bitcoin_address">レガシービットコイン</string>
<string name="common_locked">ロックされています</string> <string name="common_locked">ロックされています</string>
<string name="common_locked_wallets">ロックされたウォレット</string>
<string name="common_main_network">メインネットワーク</string> <string name="common_main_network">メインネットワーク</string>
<string name="common_month"></string> <string name="common_month"></string>
<string name="common_network_fee_title">ネットワーク手数料</string> <string name="common_network_fee_title">ネットワーク手数料</string>
@ -307,6 +311,7 @@
<string name="common_sign">署名</string> <string name="common_sign">署名</string>
<string name="common_sign_and_send">署名して送信</string> <string name="common_sign_and_send">署名して送信</string>
<string name="common_skip">スキップ</string> <string name="common_skip">スキップ</string>
<string name="common_something_went_wrong">問題が発生しました</string>
<string name="common_stake">ステーキング</string> <string name="common_stake">ステーキング</string>
<string name="common_staking">ステーキング</string> <string name="common_staking">ステーキング</string>
<string name="common_start">始める</string> <string name="common_start">始める</string>
@ -1143,7 +1148,7 @@
<string name="staking_notification_restake_rewards_text">獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。</string> <string name="staking_notification_restake_rewards_text">獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。</string>
<string name="staking_notification_restake_text">再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。</string> <string name="staking_notification_restake_text">再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。</string>
<string name="staking_notification_stake_entire_balance_text">残高のすべてをステーキングしようとしています。ステーキング解除や報酬請求にかかるネットワーク手数料をカバーするために、少額を残しておくことをお勧めします。</string> <string name="staking_notification_stake_entire_balance_text">残高のすべてをステーキングしようとしています。ステーキング解除や報酬請求にかかるネットワーク手数料をカバーするために、少額を残しておくことをお勧めします。</string>
<string name="staking_notification_ton_activate_account">TONでステーキングを開始するには、まず任意の金額の送金を行ってください。これにより、ウォレットがアクティブになります。</string> <string name="staking_notification_ton_activate_account">TONのステーキングを開始するには、まず自分のアドレスに少額の取引を送信します。これによりウォレットが有効になります。</string>
<string name="staking_notification_ton_extra_reserve_info">取引を完了するには、ネットワーク手数料に加えて最大0.2TONが必要になる場合があります。未使用分は返金されます。</string> <string name="staking_notification_ton_extra_reserve_info">取引を完了するには、ネットワーク手数料に加えて最大0.2TONが必要になる場合があります。未使用分は返金されます。</string>
<string name="staking_notification_ton_extra_reserve_is_required">この操作を続行するには、ネットワーク手数料に加えて0.2 TONが必要です。残高を補充してください。</string> <string name="staking_notification_ton_extra_reserve_is_required">この操作を続行するには、ネットワーク手数料に加えて0.2 TONが必要です。残高を補充してください。</string>
<string name="staking_notification_ton_extra_reserve_title">TONの準備金が必要です</string> <string name="staking_notification_ton_extra_reserve_title">TONの準備金が必要です</string>
@ -1214,6 +1219,7 @@
<string name="story_web3_title">Web3.0対応</string> <string name="story_web3_title">Web3.0対応</string>
<string name="sui_not_enough_coin_for_fee_description">続行するには少なくとも%1$sの受信取引が必要です</string> <string name="sui_not_enough_coin_for_fee_description">続行するには少なくとも%1$sの受信取引が必要です</string>
<string name="sui_not_enough_coin_for_fee_title">残高不足</string> <string name="sui_not_enough_coin_for_fee_title">残高不足</string>
<string name="swap_approve_description">承認すると、このスマートコントラクトが今後の取引でトークンを利用できるようになります。</string>
<string name="swap_fixed_rate">固定レート</string> <string name="swap_fixed_rate">固定レート</string>
<string name="swap_give_permission_fee_footer">ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。</string> <string name="swap_give_permission_fee_footer">ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。</string>
<string name="swap_promo_text">より多くのトークンをより良いレートで、ウォレット内にて直接交換します。</string> <string name="swap_promo_text">より多くのトークンをより良いレートで、ウォレット内にて直接交換します。</string>
@ -1653,7 +1659,7 @@
<string name="yield_module_approve_sheet_title">承認を確定する</string> <string name="yield_module_approve_sheet_title">承認を確定する</string>
<string name="yield_module_balance_info_sheet_subtitle">資産残高に関するテキスト [プレースホルダー]</string> <string name="yield_module_balance_info_sheet_subtitle">資産残高に関するテキスト [プレースホルダー]</string>
<string name="yield_module_balance_info_sheet_title">あなたの%sはAaveに預けられています</string> <string name="yield_module_balance_info_sheet_title">あなたの%sはAaveに預けられています</string>
<string name="yield_module_earn_badge">%s%を獲得</string> <string name="yield_module_earn_badge">%1$s%% を獲得</string>
<string name="yield_module_earn_sheet_available_title">利用可能</string> <string name="yield_module_earn_sheet_available_title">利用可能</string>
<string name="yield_module_earn_sheet_current_apy_title">現在のAPY</string> <string name="yield_module_earn_sheet_current_apy_title">現在のAPY</string>
<string name="yield_module_earn_sheet_my_funds_title">私の資金</string> <string name="yield_module_earn_sheet_my_funds_title">私の資金</string>
@ -1670,6 +1676,8 @@
<string name="yield_module_fee_policy_sheet_title">手数料ポリシー</string> <string name="yield_module_fee_policy_sheet_title">手数料ポリシー</string>
<string name="yield_module_main_view_approve_notification_description">ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー]</string> <string name="yield_module_main_view_approve_notification_description">ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー]</string>
<string name="yield_module_main_view_approve_notification_title">トークン承認が必要</string> <string name="yield_module_main_view_approve_notification_title">トークン承認が必要</string>
<string name="yield_module_network_fee_unreachable_notification_description">ネットワーク接続を確認してください</string>
<string name="yield_module_network_fee_unreachable_notification_title">ネットワーク手数料についての情報にアクセスできません</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">アカウントへの入金はすべて自動的にAaveに貸し出されます。</string> <string name="yield_module_promo_screen_auto_balance_subtitle">アカウントへの入金はすべて自動的にAaveに貸し出されます。</string>
<string name="yield_module_promo_screen_auto_balance_title">残高は自動的に計算されます</string> <string name="yield_module_promo_screen_auto_balance_title">残高は自動的に計算されます</string>
<string name="yield_module_promo_screen_cash_out_subtitle">いつでも、即座に資金を送信、交換、売却できます。</string> <string name="yield_module_promo_screen_cash_out_subtitle">いつでも、即座に資金を送信、交換、売却できます。</string>

View file

@ -1298,6 +1298,8 @@
<string name="wallet_connect_error_wrong_card_selected">Неверная карта или кольцо выбрана в приложении Tangem</string> <string name="wallet_connect_error_wrong_card_selected">Неверная карта или кольцо выбрана в приложении Tangem</string>
<string name="wallet_connect_failed_to_build_tx">Не удалось создать транзакцию из данных Dapp. Код: %s</string> <string name="wallet_connect_failed_to_build_tx">Не удалось создать транзакцию из данных Dapp. Код: %s</string>
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать — обратитесь в службу поддержки.</string> <string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать — обратитесь в службу поддержки.</string>
<string name="wallet_connect_multiple_transactions">Транзакция в несколько шагов</string>
<string name="wallet_connect_multiple_transactions_description">Чтобы успешно обработать запрос, ваша транзакция будет разделена на несколько частей. Для завершения потребуется несколько приложений карты.</string>
<string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string> <string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string>
<string name="wallet_connect_no_sessions_title">Упс. Нет сессий.</string> <string name="wallet_connect_no_sessions_title">Упс. Нет сессий.</string>
<string name="wallet_connect_pairing_error">Не удалось создать пару WalletConnect: %1$s</string> <string name="wallet_connect_pairing_error">Не удалось создать пару WalletConnect: %1$s</string>
@ -1309,6 +1311,8 @@
<string name="wallet_connect_scanner_error_not_valid_card">Эту карту нельзя использовать с WalletConnect.</string> <string name="wallet_connect_scanner_error_not_valid_card">Эту карту нельзя использовать с WalletConnect.</string>
<string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string> <string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string>
<string name="wallet_connect_select_network">Выберите сеть</string> <string name="wallet_connect_select_network">Выберите сеть</string>
<string name="wallet_connect_sending_multiple_explanation">Транзакция в процессе отправки. Пожалуйста, приложите карту несколько раз для её завершения.</string>
<string name="wallet_connect_sending_multiple_tx">Транзакция в обработке</string>
<string name="wallet_connect_sessions_title">Сессии WalletConnect</string> <string name="wallet_connect_sessions_title">Сессии WalletConnect</string>
<string name="wallet_connect_subtitle">Подключение к dApps</string> <string name="wallet_connect_subtitle">Подключение к dApps</string>
<string name="wallet_connect_title">WalletConnect</string> <string name="wallet_connect_title">WalletConnect</string>

View file

@ -19,7 +19,10 @@
<string name="account_archived_recover">Recover</string> <string name="account_archived_recover">Recover</string>
<string name="account_archived_recover_dialog_description">Youre about to recover “%1$s”.</string> <string name="account_archived_recover_dialog_description">Youre about to recover “%1$s”.</string>
<string name="account_archived_recover_dialog_title">Recover account</string> <string name="account_archived_recover_dialog_title">Recover account</string>
<string name="account_archived_recover_error_message">You have already exceeded the limit of 20 active accounts. Archive one to recover</string>
<string name="account_archived_recover_error_title">Can\'t recover account</string>
<string name="account_archived_title">Archived</string> <string name="account_archived_title">Archived</string>
<string name="account_could_not_create">We couldnt create account. Please try again later.</string>
<string name="account_create_success_message">Account created</string> <string name="account_create_success_message">Account created</string>
<string name="account_details_archive">Archive account</string> <string name="account_details_archive">Archive account</string>
<string name="account_details_archive_action">Archive</string> <string name="account_details_archive_action">Archive</string>
@ -314,6 +317,7 @@
<string name="common_sign">Sign</string> <string name="common_sign">Sign</string>
<string name="common_sign_and_send">Sign and send</string> <string name="common_sign_and_send">Sign and send</string>
<string name="common_skip">Skip</string> <string name="common_skip">Skip</string>
<string name="common_something_went_wrong">Something went wrong</string>
<string name="common_stake">Stake</string> <string name="common_stake">Stake</string>
<string name="common_staking">Staking</string> <string name="common_staking">Staking</string>
<string name="common_start">Start</string> <string name="common_start">Start</string>
@ -927,6 +931,7 @@
<string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string> <string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string>
<string name="receive_bottom_sheet_warning_title">Send only %1$s on the %2$s network</string> <string name="receive_bottom_sheet_warning_title">Send only %1$s on the %2$s network</string>
<string name="receive_token_description">Transfer funds from any wallet or exchange</string> <string name="receive_token_description">Transfer funds from any wallet or exchange</string>
<string name="referral_address_for_rewards">Address for rewards</string>
<string name="referral_button_participate">Participate</string> <string name="referral_button_participate">Participate</string>
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string> <string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string> <string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
@ -1237,6 +1242,7 @@
<string name="story_web3_title">Web 3.0 Compatible</string> <string name="story_web3_title">Web 3.0 Compatible</string>
<string name="sui_not_enough_coin_for_fee_description">An incoming transaction of at least %1$s is required to proceed</string> <string name="sui_not_enough_coin_for_fee_description">An incoming transaction of at least %1$s is required to proceed</string>
<string name="sui_not_enough_coin_for_fee_title">Insufficient funds</string> <string name="sui_not_enough_coin_for_fee_title">Insufficient funds</string>
<string name="swap_approve_description">By approving, you allow the smart contract to use your tokens in future transactions.</string>
<string name="swap_fixed_rate">Fixed Rate</string> <string name="swap_fixed_rate">Fixed Rate</string>
<string name="swap_give_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string> <string name="swap_give_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string>
<string name="swap_promo_text">Exchange more tokens at better rates directly in your wallet.</string> <string name="swap_promo_text">Exchange more tokens at better rates directly in your wallet.</string>
@ -1455,8 +1461,8 @@
<string name="wallet_connect_error_wrong_card_selected">Wrong card or ring selected in Tangem App</string> <string name="wallet_connect_error_wrong_card_selected">Wrong card or ring selected in Tangem App</string>
<string name="wallet_connect_failed_to_build_tx">Failed to create transaction from Dapp data. Code: %s</string> <string name="wallet_connect_failed_to_build_tx">Failed to create transaction from Dapp data. Code: %s</string>
<string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string> <string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string>
<string name="wallet_connect_multiple_transactions">Multiple transactions</string> <string name="wallet_connect_multiple_transactions">Multi-Part Transaction</string>
<string name="wallet_connect_multiple_transactions_description">Youll need to tap your Tangem device a few times to complete this process.</string> <string name="wallet_connect_multiple_transactions_description">To process successfully, your transaction will be split into multiple parts. You\'ll need to tap your card several times to complete it.</string>
<string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string> <string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string>
<string name="wallet_connect_no_sessions_title">Ooops. No Sessions.</string> <string name="wallet_connect_no_sessions_title">Ooops. No Sessions.</string>
<string name="wallet_connect_pairing_error">Failed to pairing WalletConnect session: %1$s</string> <string name="wallet_connect_pairing_error">Failed to pairing WalletConnect session: %1$s</string>
@ -1468,8 +1474,8 @@
<string name="wallet_connect_scanner_error_not_valid_card">This card can\'t be used to establish WalletConnect session</string> <string name="wallet_connect_scanner_error_not_valid_card">This card can\'t be used to establish WalletConnect session</string>
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string> <string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
<string name="wallet_connect_select_network">Select network</string> <string name="wallet_connect_select_network">Select network</string>
<string name="wallet_connect_sending_multiple_explanation">We\'re processing the transaction</string> <string name="wallet_connect_sending_multiple_explanation">The transaction is being processed. Please tap your card multiple times to complete it.</string>
<string name="wallet_connect_sending_multiple_tx">Sending your funds...</string> <string name="wallet_connect_sending_multiple_tx">Transaction in progress</string>
<string name="wallet_connect_sessions_title">WalletConnect Sessions</string> <string name="wallet_connect_sessions_title">WalletConnect Sessions</string>
<string name="wallet_connect_subtitle">Connect to dApps</string> <string name="wallet_connect_subtitle">Connect to dApps</string>
<string name="wallet_connect_title">WalletConnect</string> <string name="wallet_connect_title">WalletConnect</string>
@ -1725,7 +1731,7 @@
<string name="yield_module_approve_sheet_title">Confirm approval</string> <string name="yield_module_approve_sheet_title">Confirm approval</string>
<string name="yield_module_balance_info_sheet_subtitle">Text about your money in balance [PLACEHOLDER]</string> <string name="yield_module_balance_info_sheet_subtitle">Text about your money in balance [PLACEHOLDER]</string>
<string name="yield_module_balance_info_sheet_title">Your %s is deposited in Aave</string> <string name="yield_module_balance_info_sheet_title">Your %s is deposited in Aave</string>
<string name="yield_module_earn_badge">Earn %s%</string> <string name="yield_module_earn_badge">Earn %1$s%%</string>
<string name="yield_module_earn_sheet_available_title">Available</string> <string name="yield_module_earn_sheet_available_title">Available</string>
<string name="yield_module_earn_sheet_current_apy_title">Current APY</string> <string name="yield_module_earn_sheet_current_apy_title">Current APY</string>
<string name="yield_module_earn_sheet_my_funds_title">My Funds</string> <string name="yield_module_earn_sheet_my_funds_title">My Funds</string>
@ -1742,6 +1748,8 @@
<string name="yield_module_fee_policy_sheet_title">Fee policy</string> <string name="yield_module_fee_policy_sheet_title">Fee policy</string>
<string name="yield_module_main_view_approve_notification_description">Write description here. In one, two or three lines will be awesome. [PLACEHOLDER]</string> <string name="yield_module_main_view_approve_notification_description">Write description here. In one, two or three lines will be awesome. [PLACEHOLDER]</string>
<string name="yield_module_main_view_approve_notification_title">Some token approve needed</string> <string name="yield_module_main_view_approve_notification_title">Some token approve needed</string>
<string name="yield_module_network_fee_unreachable_notification_description">Check your network connection</string>
<string name="yield_module_network_fee_unreachable_notification_title">Network fee info unreachable</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Every top-up of your account will be lended to Aave automatically.</string> <string name="yield_module_promo_screen_auto_balance_subtitle">Every top-up of your account will be lended to Aave automatically.</string>
<string name="yield_module_promo_screen_auto_balance_title">Your balance works automatically</string> <string name="yield_module_promo_screen_auto_balance_title">Your balance works automatically</string>
<string name="yield_module_promo_screen_cash_out_subtitle">Send, swap, or sell your funds instantly, anytime you want.</string> <string name="yield_module_promo_screen_cash_out_subtitle">Send, swap, or sell your funds instantly, anytime you want.</string>

View file

@ -14,6 +14,7 @@ import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase
import com.tangem.domain.transaction.usecase.SendLargeSolanaTransactionUseCase import com.tangem.domain.transaction.usecase.SendLargeSolanaTransactionUseCase
import com.tangem.domain.walletconnect.WcAnalyticEvents.SolanaLargeTransactionStatus
import com.tangem.domain.walletconnect.error.parseSendError import com.tangem.domain.walletconnect.error.parseSendError
import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
@ -59,10 +60,12 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
sendLargeSolanaTransactionUseCase(context.session.wallet as UserWallet.Cold, context.network, formattedHash) sendLargeSolanaTransactionUseCase(context.session.wallet as UserWallet.Cold, context.network, formattedHash)
.fold( .fold(
ifLeft = { ifLeft = {
analytics.send(SolanaLargeTransactionStatus(SolanaLargeTransactionStatus.Status.Failed))
Timber.e(it.toString()) Timber.e(it.toString())
emit(state.toResult(parseSendError(it).left())) emit(state.toResult(parseSendError(it).left()))
}, },
ifRight = { ifRight = {
analytics.send(SolanaLargeTransactionStatus(SolanaLargeTransactionStatus.Status.Success))
val emptyRespond = ByteArray(0).formatAsSolanaSignature() val emptyRespond = ByteArray(0).formatAsSolanaSignature()
val respondResult = respondService.respond(rawSdkRequest, emptyRespond) val respondResult = respondService.respond(rawSdkRequest, emptyRespond)
emit(state.toResult(respondResult)) emit(state.toResult(respondResult))

View file

@ -110,6 +110,10 @@ sealed interface StakingIntegrationID {
val integrationId = blockchain.integrationId ?: return null val integrationId = blockchain.integrationId ?: return null
if (integrationId is Coin && currencyId.contractAddress != null) {
return null
}
return when (integrationId) { return when (integrationId) {
is Coin -> integrationId is Coin -> integrationId
is EthereumToken -> { is EthereumToken -> {

View file

@ -139,9 +139,13 @@ class StakingIntegrationIDTest {
expected = StakingIntegrationID.Coin.Cardano, expected = StakingIntegrationID.Coin.Cardano,
), ),
CreateModel( CreateModel(
currencyId = CryptoCurrency.ID.fromValue(value = "coin⟨ETH⟩polygon-ecosystem-token⚓"), currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"),
expected = StakingIntegrationID.EthereumToken.Polygon, expected = StakingIntegrationID.EthereumToken.Polygon,
), ),
CreateModel(
currencyId = CryptoCurrency.ID.fromValue(value = "token⟨SOLANA⟩solana⚓1234567890"),
expected = null,
),
) )
} }

View file

@ -236,6 +236,29 @@ sealed class WcAnalyticEvents(
enum class Source { Domain, SmartContract } enum class Source { Domain, SmartContract }
} }
data class SolanaLargeTransaction(
val dappName: String,
) : WcAnalyticEvents(
event = "Solana Large Transaction",
params = mapOf(
AnalyticsParam.Key.DAPP_NAME to dappName,
),
)
data class SolanaLargeTransactionStatus(
val status: Status,
) : WcAnalyticEvents(
event = "Solana Large Transaction Status",
params = mapOf(
AnalyticsParam.Key.STATUS to status.value,
),
) {
enum class Status(val value: String) {
Success("Success"),
Failed("Failed"),
}
}
enum class DAppVerificationStatus(val status: String) { enum class DAppVerificationStatus(val status: String) {
Verified("Verified"), Verified("Verified"),
Risky("Risky"), Risky("Risky"),

View file

@ -12,6 +12,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency, val cryptoCurrency: CryptoCurrency,
val country: OnrampCountry, val country: OnrampCountry,
val launchSepa: Boolean,
val onDismiss: () -> Unit, val onDismiss: () -> Unit,
) )

View file

@ -9,12 +9,14 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase
import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM
import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.utils.model.EUR_CURRENCY
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -26,6 +28,7 @@ internal class ConfirmResidencyModel @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val router: Router, private val router: Router,
private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase, private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase,
private val onrampSaveDefaultCurrencyUseCase: OnrampSaveDefaultCurrencyUseCase,
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
) : Model() { ) : Model() {
@ -54,6 +57,10 @@ internal class ConfirmResidencyModel @Inject constructor(
analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name)) analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name))
modelScope.launch { modelScope.launch {
saveDefaultCountryUseCase.invoke(country) saveDefaultCountryUseCase.invoke(country)
if (params.launchSepa) {
onrampSaveDefaultCurrencyUseCase.invoke(EUR_CURRENCY)
}
params.onDismiss() params.onDismiss()
} }
}, },

View file

@ -57,6 +57,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
country = config.country, country = config.country,
launchSepa = params.launchSepa,
onDismiss = { onDismiss = {
model.bottomSheetNavigation.dismiss() model.bottomSheetNavigation.dismiss()
model.handleOnrampAvailable() model.handleOnrampAvailable()

View file

@ -3,7 +3,7 @@ package com.tangem.features.onramp.main.entity
import com.tangem.domain.onramp.model.OnrampProviderWithQuote import com.tangem.domain.onramp.model.OnrampProviderWithQuote
interface OnrampIntents { interface OnrampIntents {
fun onAmountValueChanged(value: String) fun onAmountValueChanged(value: String, isValuePasted: Boolean)
fun openSettings() fun openSettings()
fun openCurrenciesList() fun openCurrenciesList()
fun onBuyClick(quote: OnrampProviderWithQuote.Data) fun onBuyClick(quote: OnrampProviderWithQuote.Data)

View file

@ -1,8 +1,10 @@
package com.tangem.features.onramp.main.entity package com.tangem.features.onramp.main.entity
import com.tangem.domain.onramp.model.OnrampAmount import com.tangem.domain.onramp.model.OnrampAmount
import com.tangem.domain.onramp.model.OnrampPaymentMethod
data class OnrampLastUpdate( data class OnrampLastUpdate(
val lastAmount: OnrampAmount, val fromAmount: OnrampAmount,
val lastCountryString: String, val countryCode: String,
val paymentMethod: OnrampPaymentMethod,
) )

View file

@ -122,7 +122,7 @@ internal class OnrampStateFactory(
amountFieldModel = AmountFieldModel( amountFieldModel = AmountFieldModel(
value = "", value = "",
fiatValue = "", fiatValue = "",
onValueChange = onrampIntents::onAmountValueChanged, onValueChange = { onrampIntents.onAmountValueChanged(value = it, isValuePasted = false) },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
imeAction = ImeAction.None, imeAction = ImeAction.None,
keyboardType = KeyboardType.Number, keyboardType = KeyboardType.Number,

View file

@ -15,9 +15,12 @@ import java.math.BigDecimal
internal class OnrampAmountFieldChangeConverter( internal class OnrampAmountFieldChangeConverter(
private val currentStateProvider: Provider<OnrampMainComponentUM>, private val currentStateProvider: Provider<OnrampMainComponentUM>,
) : Converter<String, OnrampMainComponentUM> { ) : Converter<OnrampAmountFieldChangeConverter.Input, OnrampMainComponentUM> {
override fun convert(input: Input): OnrampMainComponentUM {
val value = input.value
val isValuePasted = input.isValuePasted
override fun convert(value: String): OnrampMainComponentUM {
val state = currentStateProvider() val state = currentStateProvider()
if (state !is OnrampMainComponentUM.Content) return state if (state !is OnrampMainComponentUM.Content) return state
@ -34,6 +37,7 @@ internal class OnrampAmountFieldChangeConverter(
imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None,
keyboardType = KeyboardType.Number, keyboardType = KeyboardType.Number,
), ),
isValuePasted = isValuePasted,
) )
return state.copy( return state.copy(
@ -66,4 +70,6 @@ internal class OnrampAmountFieldChangeConverter(
providerBlockState = OnrampProviderBlockUM.Empty, providerBlockState = OnrampProviderBlockUM.Empty,
) )
} }
data class Input(val value: String, val isValuePasted: Boolean)
} }

View file

@ -33,7 +33,8 @@ internal class OnrampAmountStateFactory(
currentStateProvider = currentStateProvider, currentStateProvider = currentStateProvider,
) )
fun getOnAmountValueChange(value: String) = onrampAmountFieldChangeConverter.convert(value) fun getOnAmountValueChange(value: String, isValuePasted: Boolean) =
onrampAmountFieldChangeConverter.convert(OnrampAmountFieldChangeConverter.Input(value, isValuePasted))
fun getUpdatedCurrencyState(currency: OnrampCurrency): OnrampMainComponentUM { fun getUpdatedCurrencyState(currency: OnrampCurrency): OnrampMainComponentUM {
val currentState = currentStateProvider() val currentState = currentStateProvider()

View file

@ -32,6 +32,7 @@ import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory.Companion.PREDEFINED_SEPA_AMOUNT import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory.Companion.PREDEFINED_SEPA_AMOUNT
import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory
import com.tangem.features.onramp.providers.entity.SelectProviderResult import com.tangem.features.onramp.providers.entity.SelectProviderResult
import com.tangem.features.onramp.utils.model.EUR_CURRENCY
import com.tangem.features.onramp.utils.sendOnrampErrorEvent import com.tangem.features.onramp.utils.sendOnrampErrorEvent
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -68,7 +69,7 @@ internal class OnrampMainComponentModel @Inject constructor(
private val params: OnrampMainComponent.Params = paramsContainer.require() private val params: OnrampMainComponent.Params = paramsContainer.require()
private var isSepaLaunched = false private var shouldForceChooseSepa = params.launchSepa
private var currencyToRestore: OnrampCurrency? = null private var currencyToRestore: OnrampCurrency? = null
val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
@ -132,6 +133,10 @@ internal class OnrampMainComponentModel @Inject constructor(
fun onProviderSelected(result: SelectProviderResult, isBestRate: Boolean) { fun onProviderSelected(result: SelectProviderResult, isBestRate: Boolean) {
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(result, isBestRate) } _state.update { amountStateFactory.getAmountSecondaryUpdatedState(result, isBestRate) }
if (result.paymentMethod.id != SEPA_METHOD_ID) {
shouldForceChooseSepa = false
}
} }
private fun checkResidenceCountry() { private fun checkResidenceCountry() {
@ -171,7 +176,7 @@ internal class OnrampMainComponentModel @Inject constructor(
updatePairsAndQuotes() updatePairsAndQuotes()
if (wasInitialLoading && params.launchSepa) { if (wasInitialLoading && params.launchSepa) {
onAmountValueChanged(PREDEFINED_SEPA_AMOUNT) onAmountValueChanged(value = PREDEFINED_SEPA_AMOUNT, isValuePasted = true)
} }
}, },
) )
@ -243,8 +248,8 @@ internal class OnrampMainComponentModel @Inject constructor(
.launchIn(modelScope) .launchIn(modelScope)
} }
override fun onAmountValueChanged(value: String) { override fun onAmountValueChanged(value: String, isValuePasted: Boolean) {
_state.update { amountStateFactory.getOnAmountValueChange(value) } _state.update { amountStateFactory.getOnAmountValueChange(value, isValuePasted) }
modelScope.launch { amountInputManager.update(value) } modelScope.launch { amountInputManager.update(value) }
} }
@ -341,9 +346,7 @@ internal class OnrampMainComponentModel @Inject constructor(
private fun selectOrUpdateQuote(quotes: List<OnrampQuote>): OnrampQuote? { private fun selectOrUpdateQuote(quotes: List<OnrampQuote>): OnrampQuote? {
val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error } val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error }
val sepaQuote = if (params.launchSepa && !isSepaLaunched) { val bestSepaQuote = if (params.launchSepa && shouldForceChooseSepa) {
isSepaLaunched = true
quotes.filterIsInstance<OnrampQuote.Data>() quotes.filterIsInstance<OnrampQuote.Data>()
.filter { it.paymentMethod.id == SEPA_METHOD_ID } .filter { it.paymentMethod.id == SEPA_METHOD_ID }
.maxByOrNull { it.toAmount.value } .maxByOrNull { it.toAmount.value }
@ -352,7 +355,7 @@ internal class OnrampMainComponentModel @Inject constructor(
} }
// Check if amount, country or currency has changed // Check if amount, country or currency has changed
val newQuote = sepaQuote ?: if (checkLastInputState(quoteToCheck)) { val newQuote = bestSepaQuote ?: if (isAmountOrCountryChanged(quoteToCheck)) {
quoteToCheck quoteToCheck
} else { } else {
val state = state.value as? OnrampMainComponentUM.Content val state = state.value as? OnrampMainComponentUM.Content
@ -371,7 +374,9 @@ internal class OnrampMainComponentModel @Inject constructor(
lastSelectedQuote lastSelectedQuote
} }
} }
newQuote?.let { updateProvider(newQuote, quotes) } if (newQuote != null) {
updateProvider(newQuote, quotes)
}
return newQuote return newQuote
} }
@ -380,8 +385,13 @@ internal class OnrampMainComponentModel @Inject constructor(
lastUpdateState.value = OnrampLastUpdate( lastUpdateState.value = OnrampLastUpdate(
quote.fromAmount, quote.fromAmount,
quote.countryCode, quote.countryCode,
quote.paymentMethod,
) )
if (quote.paymentMethod.id != SEPA_METHOD_ID) {
shouldForceChooseSepa = false
}
_state.update { _state.update {
amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes) amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes)
} }
@ -447,22 +457,14 @@ internal class OnrampMainComponentModel @Inject constructor(
} }
} }
private fun checkLastInputState(quote: OnrampQuote?): Boolean { private fun isAmountOrCountryChanged(quote: OnrampQuote?): Boolean {
return lastUpdateState.value?.lastAmount != quote?.fromAmount || return lastUpdateState.value?.fromAmount != quote?.fromAmount ||
lastUpdateState.value?.lastCountryString != quote?.countryCode lastUpdateState.value?.countryCode != quote?.countryCode
} }
private companion object { private companion object {
const val UPDATE_DELAY = 10_000L const val UPDATE_DELAY = 10_000L
const val SEPA_METHOD_ID = "sepa" const val SEPA_METHOD_ID = "sepa"
val EUR_CURRENCY = OnrampCurrency(
code = "EUR",
name = "Euro",
unit = "",
precision = 2,
image = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/Currencies/EUR.png",
)
} }
} }

View file

@ -58,6 +58,7 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor(
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency, cryptoCurrency = params.cryptoCurrency,
country = config.country, country = config.country,
launchSepa = false,
onDismiss = { model.bottomSheetNavigation.dismiss() }, onDismiss = { model.bottomSheetNavigation.dismiss() },
), ),
) )

View file

@ -348,8 +348,9 @@ internal class OnrampV2MainComponentModel @Inject constructor(
private fun updateProvider(quote: OnrampQuote) { private fun updateProvider(quote: OnrampQuote) {
lastUpdateState.value = OnrampLastUpdate( lastUpdateState.value = OnrampLastUpdate(
quote.fromAmount, fromAmount = quote.fromAmount,
quote.countryCode, countryCode = quote.countryCode,
paymentMethod = quote.paymentMethod,
) )
_state.update { _state.update {
@ -378,8 +379,8 @@ internal class OnrampV2MainComponentModel @Inject constructor(
} }
private fun checkLastInputState(quote: OnrampQuote?): Boolean { private fun checkLastInputState(quote: OnrampQuote?): Boolean {
return lastUpdateState.value?.lastAmount != quote?.fromAmount || return lastUpdateState.value?.fromAmount != quote?.fromAmount ||
lastUpdateState.value?.lastCountryString != quote?.countryCode lastUpdateState.value?.countryCode != quote?.countryCode
} }
private fun sendScreenOpenAnalytics() { private fun sendScreenOpenAnalytics() {

View file

@ -0,0 +1,11 @@
package com.tangem.features.onramp.utils.model
import com.tangem.domain.onramp.model.OnrampCurrency
internal val EUR_CURRENCY = OnrampCurrency(
code = "EUR",
name = "Euro",
unit = "",
precision = 2,
image = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/Currencies/EUR.png",
)

View file

@ -510,7 +510,7 @@ internal class SendConfirmModel @Inject constructor(
val txUrl = getExplorerTransactionUrlUseCase( val txUrl = getExplorerTransactionUrlUseCase(
txHash = txData.hash.orEmpty(), txHash = txData.hash.orEmpty(),
networkId = cryptoCurrency.network.id, networkId = cryptoCurrency.network.id,
).getOrElse { "" } ).getOrNull().orEmpty()
_uiState.update(SendConfirmSentStateTransformer(txData, txUrl)) _uiState.update(SendConfirmSentStateTransformer(txData, txUrl))
} }

View file

@ -76,7 +76,7 @@ internal class SendConfirmSuccessModel @Inject constructor(
}, },
), ),
prevButton = null, prevButton = null,
secondaryPairButtonsUM = NavigationButton( secondaryPairButtonsUM = (NavigationButton(
textReference = resourceReference(R.string.common_explore), textReference = resourceReference(R.string.common_explore),
iconRes = R.drawable.ic_web_24, iconRes = R.drawable.ic_web_24,
onClick = ::onExploreClick, onClick = ::onExploreClick,
@ -84,7 +84,7 @@ internal class SendConfirmSuccessModel @Inject constructor(
textReference = resourceReference(R.string.common_share), textReference = resourceReference(R.string.common_share),
iconRes = R.drawable.ic_share_24, iconRes = R.drawable.ic_share_24,
onClick = ::onShareClick, onClick = ::onShareClick,
), )).takeIf { params.txUrl.isNotEmpty() },
), ),
), ),
) )

View file

@ -498,7 +498,7 @@ internal class NFTSendConfirmModel @Inject constructor(
iconRes = R.drawable.ic_share_24, iconRes = R.drawable.ic_share_24,
onClick = ::onShareClick, onClick = ::onShareClick,
) )
).takeIf { confirmUM is ConfirmUM.Success }, ).takeUnless { (confirmUM as? ConfirmUM.Success)?.txUrl.isNullOrBlank() },
), ),
), ),
) )

View file

@ -52,7 +52,7 @@ internal class SendWithSwapSuccessModel @Inject constructor(
onClick = appRouter::pop, onClick = appRouter::pop,
), ),
prevButton = null, prevButton = null,
secondaryPairButtonsUM = NavigationButton( secondaryPairButtonsUM = (NavigationButton(
textReference = resourceReference(R.string.common_explore), textReference = resourceReference(R.string.common_explore),
iconRes = R.drawable.ic_web_24, iconRes = R.drawable.ic_web_24,
onClick = ::onExploreClick, onClick = ::onExploreClick,
@ -60,7 +60,7 @@ internal class SendWithSwapSuccessModel @Inject constructor(
textReference = resourceReference(R.string.common_share), textReference = resourceReference(R.string.common_share),
iconRes = R.drawable.ic_share_24, iconRes = R.drawable.ic_share_24,
onClick = ::onShareClick, onClick = ::onShareClick,
), )).takeUnless { confirmUM?.txUrl.isNullOrBlank() },
), ),
) )
} }

View file

@ -410,7 +410,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
rateType = RateType.FLOAT, rateType = RateType.FLOAT,
) )
return if (isBalanceWithoutFeeEnough) { return if (isBalanceWithoutFeeEnough && maybeQuotes.isRight()) {
provider to loadDexSwapData( provider to loadDexSwapData(
provider = provider, provider = provider,
networkId = networkId, networkId = networkId,

View file

@ -32,6 +32,7 @@ import com.tangem.domain.transaction.error.SendTransactionError.UserCancelledErr
import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcAnalyticEvents
import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus
import com.tangem.domain.walletconnect.WcAnalyticEvents.SolanaLargeTransaction
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message
@ -143,6 +144,7 @@ internal class WcSendTransactionModel @Inject constructor(
wcApproval = useCase as? WcApproval wcApproval = useCase as? WcApproval
sign = { sign = {
if (isMultipleSignRequired(useCase)) { if (isMultipleSignRequired(useCase)) {
analytics.send(SolanaLargeTransaction(useCase.rawSdkRequest.dAppMetaData.name))
openMultipleTransaction(useCase) openMultipleTransaction(useCase)
} else { } else {
useCase.sign() useCase.sign()