Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-21 16:49:14 +03:00
commit fae450669c
122 changed files with 1494 additions and 339 deletions

View file

@ -99,6 +99,7 @@ dependencies {
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.transaction)
implementation(projects.domain.transaction.models)
implementation(projects.domain.analytics)
implementation(projects.domain.visa)
implementation(projects.domain.onboarding)

View file

@ -18,6 +18,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Module
@InstallIn(SingletonComponent::class)
internal object TransactionDomainModule {
@ -189,6 +190,15 @@ internal object TransactionDomainModule {
)
}
@Provides
@Singleton
fun providePrepareAndSignUseCase(
transactionRepository: TransactionRepository,
cardSdkConfigRepository: CardSdkConfigRepository,
): PrepareAndSignUseCase {
return PrepareAndSignUseCase(transactionRepository, cardSdkConfigRepository)
}
@Provides
@Singleton
fun provideSignUseCase(

View file

@ -13,9 +13,6 @@ import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.operations.sign.SignHashCommand
@ -42,7 +39,6 @@ import org.json.JSONArray
import org.json.JSONObject
import timber.log.Timber
import java.math.BigDecimal
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
@Suppress("LargeClass")
class WalletConnectSdkHelper {
@ -227,8 +223,6 @@ class WalletConnectSdkHelper {
)
return when (result) {
is Result.Success -> {
val sentFrom = CoreAnalyticsParam.TxSentFrom.WalletConnect
Analytics.send(Basic.TransactionSent(sentFrom = sentFrom, memoType = MemoType.Null))
val hash = result.data.hash
if (hash.startsWith(HEX_PREFIX)) {
hash
@ -394,7 +388,8 @@ class WalletConnectSdkHelper {
signature = signedHash,
hash = hashToSign,
publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(),
).asRSVLegacyEVM().toHexString().formatHex().lowercase() // use lowercase because some dapps cant handle UPPERCASE
).asRSVLegacyEVM().toHexString().formatHex()
.lowercase() // use lowercase because some dapps cant handle UPPERCASE
}
}
is CompletionResult.Failure -> {

View file

@ -18,6 +18,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
@ -130,6 +131,18 @@ internal class DefaultRampManager(
}
}
override fun checkAssetRequirements(requirements: AssetRequirementsCondition?): Boolean {
return when (requirements) {
AssetRequirementsCondition.PaidTransaction,
is AssetRequirementsCondition.PaidTransactionWithFee,
is AssetRequirementsCondition.RequiredTrustline,
-> false
is AssetRequirementsCondition.IncompleteTransaction,
null,
-> true
}
}
private suspend fun getExchangeableState(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,

View file

@ -411,6 +411,7 @@ internal class ChildFactory @Inject constructor(
context = context,
params = PushNotificationsParams(
modelCallbacks = PushNotificationsModelCallbacksStub(),
source = route.source,
nextRoute = AppRoute.Home(),
),
componentFactory = pushNotificationsComponentFactory,

View file

@ -1,6 +1,7 @@
package com.tangem.common.routing
import android.os.Bundle
import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.common.routing.bundle.RouteBundleParams
import com.tangem.common.routing.bundle.bundle
import com.tangem.common.routing.entity.InitScreenLaunchMode
@ -195,7 +196,15 @@ sealed class AppRoute(val path: String) : Route {
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId")
@Serializable
data object PushNotification : AppRoute(path = "/push_notification")
data class PushNotification(
val source: Source,
) : AppRoute(path = "/push_notification") {
enum class Source {
Stories,
Main,
Onboarding,
}
}
@Serializable
data class WalletSettings(

View file

@ -112,7 +112,12 @@ sealed class AnalyticsParam {
val permissionType: String,
) : TxSentFrom("Approve"), TxData
data object WalletConnect : TxSentFrom("WalletConnect")
data class WalletConnect(
override val blockchain: String,
override val token: String,
override val feeType: FeeType?,
) : TxSentFrom("WalletConnect"), TxData
data object Sell : TxSentFrom("Sell")
data class NFT(
@ -131,7 +136,7 @@ sealed class AnalyticsParam {
sealed interface TxData {
val blockchain: String
val token: String
val feeType: FeeType
val feeType: FeeType?
}
sealed class FeeType(val value: String) {

View file

@ -55,7 +55,9 @@ sealed class Basic(
if (sentFrom is AnalyticsParam.TxData) {
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
this[AnalyticsParam.TOKEN_PARAM] = sentFrom.token
this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value
sentFrom.feeType?.value?.let {
this[AnalyticsParam.FEE_TYPE] = it
}
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.api.common.blockaid.models.request.DomainScanReques
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
import retrofit2.http.Body
import retrofit2.http.POST
@ -17,5 +18,5 @@ interface BlockAidApi {
suspend fun scanJsonRpc(@Body request: EvmTransactionScanRequest): TransactionScanResponse
@POST("solana/message/scan")
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): TransactionScanResponse
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): SolanaTransactionResponse
}

View file

@ -7,7 +7,7 @@ import com.tangem.datasource.api.common.blockaid.models.response.TransactionMeta
@JsonClass(generateAdapter = true)
data class SolanaTransactionScanRequest(
@Json(name = "encoding") val encoding: String = "base64",
@Json(name = "chain") val chain: String,
@Json(name = "blockchain") val blockchain: String,
@Json(name = "method") val method: String,
@Json(name = "options") val options: List<String> = listOf("simulation", "validation"),
@Json(name = "metadata") val metadata: TransactionMetadata,

View file

@ -16,6 +16,7 @@ data class Asset(
@Json(name = "chain_id") val chainId: Int? = null,
@Json(name = "logo_url") val logoUrl: String? = null,
@Json(name = "symbol") val symbol: String? = null,
@Json(name = "name") val name: String? = null,
@Json(name = "decimals") val decimals: Int? = null,
)

View file

@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Exposure(
@Json(name = "asset_type") val assetType: String,
@Json(name = "asset") val asset: Asset,
@Json(name = "spenders") val spenders: Map<String, SpenderDetails>,
)

View file

@ -0,0 +1,58 @@
package com.tangem.datasource.api.common.blockaid.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SolanaTransactionResponse(
@Json(name = "result") val result: SolanaTransactionResult,
)
@JsonClass(generateAdapter = true)
data class SolanaTransactionResult(
@Json(name = "validation") val validation: SolanaTransactionValidation,
@Json(name = "simulation") val simulation: SolanaTransactionSimulation? = null,
)
@JsonClass(generateAdapter = true)
data class SolanaTransactionValidation(
@Json(name = "result_type") val resultType: String,
@Json(name = "description") val description: String?,
)
@JsonClass(generateAdapter = true)
data class SolanaTransactionSimulation(
@Json(name = "account_summary") val accountSummary: SolanaTransactionAccountSummary,
@Json(name = "error") val error: String? = null,
@Json(name = "error_details") val errorDetails: String? = null,
)
@JsonClass(generateAdapter = true)
data class SolanaTransactionAccountSummary(
@Json(name = "account_assets_diff")
val accountAssetsDiff: List<SolanaTransactionAssetDiff>,
)
@JsonClass(generateAdapter = true)
data class SolanaTransactionAssetDiff(
@Json(name = "asset_type") val assetType: String,
@Json(name = "asset") val asset: SolanaTransactionAsset,
@Json(name = "in") val inTransfer: SolanaTransferDetail? = null,
@Json(name = "out") val outTransfer: SolanaTransferDetail? = null,
)
@JsonClass(generateAdapter = true)
data class SolanaTransactionAsset(
@Json(name = "address") val address: String? = null,
@Json(name = "symbol") val symbol: String? = null,
@Json(name = "name") val name: String? = null,
@Json(name = "decimals") val decimals: Int? = null,
@Json(name = "type") val type: String? = null,
@Json(name = "logo") val logoUrl: String? = null,
)
@JsonClass(generateAdapter = true)
data class SolanaTransferDetail(
@Json(name = "value") val amount: String? = null,
@Json(name = "summary") val summary: String? = null,
)

View file

@ -116,6 +116,7 @@
<string name="cardano_max_amount_has_token_title">Nicht genug ADA</string>
<string name="common_accept">Akzeptieren</string>
<string name="common_access_denied">Zugang verweigert</string>
<string name="common_add">Hinzufügen</string>
<string name="common_add_to_portfolio">Zum Portfolio hinzufügen</string>
<string name="common_add_token">Token hinzufügen</string>
<string name="common_address">Vertragsadresse</string>
@ -1396,12 +1397,18 @@
<string name="warning_token_trustline_button_title">Trustline aktivieren</string>
<string name="warning_token_trustline_subtitle">Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s.</string>
<string name="warning_token_trustline_title">Trustline erforderlich</string>
<string name="wc_alert_add_network_to_portfolio_description">Das erforderliche Netzwerk %s ist nicht in Ihrem Portfolio hinzugefügt. Fügen Sie es zuerst hinzu und fahren Sie dann mit der Verbindung fort.</string>
<string name="wc_alert_add_network_to_portfolio_title">Netzwerk zum Portfolio hinzufügen</string>
<string name="wc_alert_audit_malicious_domain">Bösartige/ verdächtige Domäne</string>
<string name="wc_alert_audit_unknown_domain">Unbekannte Domäne</string>
<string name="wc_alert_connect_anyway">Trotzdem verbinden</string>
<string name="wc_alert_connection_timeout_description">Zeitüberschreitungsfehler. Bitte versuche es später erneut.</string>
<string name="wc_alert_connection_timeout_title">WalletConnect konnte nicht hergestellt werden</string>
<string name="wc_alert_domain_issues_description">Diese Domäne kann nicht verifiziert werden. Überprüfe die Anfrage sorgfältig und bestätige diese dann.</string>
<string name="wc_alert_network_not_connected_description">Um fortzufahren, verbinden Sie bitte Ihre dApp-Sitzung erneut mit dem erforderlichen Netzwerk %s.</string>
<string name="wc_alert_network_not_connected_title">Netzwerk nicht verbunden</string>
<string name="wc_alert_request_timeout_description">Überprüfen Sie Ihre Netzwerkverbindung</string>
<string name="wc_alert_request_timeout_title">Anforderungs-Zeitüberschreitung</string>
<string name="wc_alert_session_disconnected_description">Bitte kehre zu Deinem Browser zurück und stellen die Verbindung über WalletConnect erneut her.</string>
<string name="wc_alert_session_disconnected_title">WalletConnect-Sitzung wurde getrennt</string>
<string name="wc_alert_sign_anyway">Trotzdem unterschreiben</string>
@ -1411,9 +1418,11 @@
<string name="wc_alert_unsupported_dapps_description">Tangem Wallet unterstützt derzeit nicht %s</string>
<string name="wc_alert_unsupported_method_description">Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
<string name="wc_alert_unsupported_method_title">Wir haben einen unbekannten Fehler festgestellt.</string>
<string name="wc_alert_unsupported_network_description">Dieses Netzwerk %s wird von Tangem Wallet nicht unterstützt und kann nicht verbunden werden.</string>
<string name="wc_alert_unsupported_network_title">Nicht unterstütztes Netzwerk</string>
<string name="wc_alert_unsupported_networks_description">Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht.</string>
<string name="wc_alert_unsupported_networks_title">Nicht unterstützte Netzwerke</string>
<string name="wc_alert_verified_domain_description">Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. %s</string>
<string name="wc_alert_verified_domain_description">Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. </string>
<string name="wc_alert_verified_domain_title">Verifizierte Domain</string>
<string name="wc_alert_wrong_card_description">Falsche Karte oder falscher Ring in der App ausgewählt</string>
<string name="wc_alert_wrong_card_title">Wir haben eine Art Problem</string>

View file

@ -113,6 +113,7 @@
<string name="cardano_max_amount_has_token_title">ADA insuficiente</string>
<string name="common_accept">Aceptar</string>
<string name="common_access_denied">Acceso denegado</string>
<string name="common_add">Agregar</string>
<string name="common_add_to_portfolio">Añadir al portafolio</string>
<string name="common_add_token">Agregar token</string>
<string name="common_address">Dirección</string>
@ -1336,12 +1337,18 @@
<string name="warning_token_trustline_button_title">Habilitar línea de confianza</string>
<string name="warning_token_trustline_subtitle">Una línea de confianza debe estar habilitada para recibir este token. La red requiere un %1$s %2$s reserva.</string>
<string name="warning_token_trustline_title">Se requiere línea de confianza</string>
<string name="wc_alert_add_network_to_portfolio_description">La red requerida %s no está añadida a su portafolio. Añádala primero y luego continúe con la conexión.</string>
<string name="wc_alert_add_network_to_portfolio_title">Agregar red al portafolio</string>
<string name="wc_alert_audit_malicious_domain">Dominio malicioso</string>
<string name="wc_alert_audit_unknown_domain">Dominio desconocido</string>
<string name="wc_alert_connect_anyway">Conectarse de todas formas</string>
<string name="wc_alert_connection_timeout_description">Error de tiempo de espera. Por favor, inténtalo de nuevo más tarde.</string>
<string name="wc_alert_connection_timeout_title">Error al establecer WalletConnect</string>
<string name="wc_alert_domain_issues_description">Este dominio no puede ser verificado. Compruebe cuidadosamente la solicitud de aprobación.</string>
<string name="wc_alert_network_not_connected_description">Para continuar, vuelva a conectar su sesión de dApp con la red requerida %s.</string>
<string name="wc_alert_network_not_connected_title">Red no conectada</string>
<string name="wc_alert_request_timeout_description">Verifique su conexión de red</string>
<string name="wc_alert_request_timeout_title">Tiempo de espera de la solicitud agotado</string>
<string name="wc_alert_session_disconnected_description">Vuelva a su navegador y vuelva a conectarse a través de WalletConnect.</string>
<string name="wc_alert_session_disconnected_title">La sesión de Wallet Connect se desconectó</string>
<string name="wc_alert_sign_anyway">Firmar de todos modos</string>
@ -1352,9 +1359,11 @@
<string name="wc_alert_unsupported_dapps_title">dApp no compatible</string>
<string name="wc_alert_unsupported_method_description">Código de error: 8 005. Si el problema persiste, no dudes en contactar con nuestro soporte.</string>
<string name="wc_alert_unsupported_method_title">Hemos encontrado un error desconocido</string>
<string name="wc_alert_unsupported_network_description">Esta red %s no es compatible con Tangem Wallet y no puede conectarse.</string>
<string name="wc_alert_unsupported_network_title">Red no compatible</string>
<string name="wc_alert_unsupported_networks_description">Actualmente, Tangem no es compatible con una red requerida por %s.</string>
<string name="wc_alert_unsupported_networks_title">Redes no compatibles</string>
<string name="wc_alert_verified_domain_description">Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. %s</string>
<string name="wc_alert_verified_domain_description">Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. </string>
<string name="wc_alert_verified_domain_title">Dominio verificado</string>
<string name="wc_alert_wrong_card_description">Se seleccionó una tarjeta o un anillo incorrectos en la app</string>
<string name="wc_alert_wrong_card_title">Tenemos algún tipo de problema</string>

View file

@ -95,6 +95,7 @@
<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">Ajouter</string>
<string name="common_add_to_portfolio">Ajouter au portfolio</string>
<string name="common_add_token">Ajouter un jeton</string>
<string name="common_address">Adresse</string>
@ -195,6 +196,7 @@
<string name="common_reject">Rejeter</string>
<string name="common_reload">Recharger</string>
<string name="common_rename">Renommer</string>
<string name="common_required">Obligatoire</string>
<string name="common_save">Enregistrez</string>
<string name="common_save_changes">Sauvegarder les modifications</string>
<string name="common_search">Rechercher</string>
@ -1317,22 +1319,58 @@
<string name="warning_token_trustline_button_title">Activer Trustline</string>
<string name="warning_token_trustline_subtitle">Une Trustline doit être activée pour recevoir ce jeton. Le réseau requiert une réserve de %1$s %2$s</string>
<string name="warning_token_trustline_title">Trustline requise</string>
<string name="wc_alert_add_network_to_portfolio_description">Le réseau requis %s nest pas ajouté à votre portefeuille. Ajoutez-le dabord, puis poursuivez la connexion.</string>
<string name="wc_alert_add_network_to_portfolio_title">Ajouter le réseau au portefeuille</string>
<string name="wc_alert_audit_malicious_domain">Domaine malveillant</string>
<string name="wc_alert_audit_unknown_domain">Domaine inconnu</string>
<string name="wc_alert_connect_anyway">Se connecter quand même</string>
<string name="wc_alert_connection_timeout_description">Erreur de délai d\'attente. Veuillez réessayer plus tard.</string>
<string name="wc_alert_connection_timeout_title">Échec de la connexion à WalletConnect</string>
<string name="wc_alert_domain_issues_description">Ce domaine ne peut pas être vérifié. Vérifiez attentivement la demande avant de l\'approuver.</string>
<string name="wc_alert_network_not_connected_description">Pour continuer, veuillez reconnecter votre session dApp avec le réseau requis %s.</string>
<string name="wc_alert_network_not_connected_title">Réseau non connecté</string>
<string name="wc_alert_request_timeout_description">Vérifiez votre connexion réseau</string>
<string name="wc_alert_request_timeout_title">Délai dattente de la requête dépassé</string>
<string name="wc_alert_session_disconnected_description">Veuillez retourner à votre navigateur et vous reconnecter via WalletConnect.</string>
<string name="wc_alert_session_disconnected_title">La session Wallet Connect a été déconnectée.</string>
<string name="wc_alert_sign_anyway">Signer quand même</string>
<string name="wc_alert_unknown_error_description">Code d\'erreur : %s. Si le problème persiste, n\'hésitez pas à contacter notre service d\'assistance.</string>
<string name="wc_alert_unknown_error_description_no_error_code">Si le problème persiste, nhésitez pas à contacter notre support.</string>
<string name="wc_alert_unknown_error_title">Nous avons rencontré une erreur inconnue.</string>
<string name="wc_alert_unsupported_dapps_description">Le portefeuille Tangem ne prend actuellement pas en charge %ss</string>
<string name="wc_alert_unsupported_dapps_title">dApp non prise en charge</string>
<string name="wc_alert_unsupported_method_description">Code d\'erreur : 8 005. Si le problème persiste, n\'hésitez pas à contacter notre support.</string>
<string name="wc_alert_unsupported_method_title">Nous avons rencontré une erreur inconnue</string>
<string name="wc_alert_verified_domain_description">Ce domaine a passé les contrôles de vérification et est considéré comme sûr, fiable et exempt de menaces connues ou dactivités suspectes.%s</string>
<string name="wc_alert_unsupported_network_description">Le réseau %s nest pas pris en charge par Tangem Wallet et ne peut pas être connecté.</string>
<string name="wc_alert_unsupported_network_title">Réseau non pris en charge</string>
<string name="wc_alert_unsupported_networks_description">Tangem ne prend actuellement pas en charge le réseau requis par %s.</string>
<string name="wc_alert_unsupported_networks_title">Réseaux non pris en charge</string>
<string name="wc_alert_verified_domain_description">Ce domaine a passé les contrôles de vérification et est considéré comme sûr, fiable et exempt de menaces connues ou dactivités suspectes.</string>
<string name="wc_alert_verified_domain_title">Domaine vérifié</string>
<string name="wc_alert_wrong_card_description">Carte ou bague incorrecte sélectionnée dans l\'application</string>
<string name="wc_alert_wrong_card_title">Nous avons un problème.</string>
<string name="wc_all_dapps_disconnected">Toutes les dApps sont déconnectées</string>
<string name="wc_allow_to_spend">Autoriser à dépenser</string>
<string name="wc_common_address">Adresse</string>
<string name="wc_common_connect">Connecter</string>
<string name="wc_common_loading">Chargement</string>
<string name="wc_common_network">Réseau</string>
<string name="wc_common_networks">Réseaux</string>
<string name="wc_common_unlimited">Illimité</string>
<string name="wc_common_wallet">Wallet</string>
<string name="wc_connected_app_title">Application connectée</string>
<string name="wc_connected_networks">Réseaux connectés</string>
<string name="wc_connected_to">Connecté à %1$s</string>
<string name="wc_connection_reqeust_can_view_balance">Consultez le solde et l\'activité de votre portefeuille</string>
<string name="wc_connection_reqeust_cant_sign">Signer des transactions sans vous en informer</string>
<string name="wc_connection_reqeust_request_approval">Demander l\'accord pour les transactions</string>
<string name="wc_connection_reqeust_will_not">Ne pourra pas</string>
<string name="wc_connection_reqeust_would_like">Souhaite</string>
<string name="wc_connection_request">Demande de connexion</string>
<string name="wc_connections">Connexions</string>
<string name="wc_contents">Contenu</string>
<string name="wc_copy_data_button_text">Copier les données</string>
<string name="wc_dapp_disconnected">dApp déconnectée</string>
<string name="wc_disconnect_all">Déconnecter tout</string>
<string name="wc_disconnect_all_alert_desc">Toutes les sessions dApp seront déconnectées. Votre portefeuille ne sera plus lié à aucune dApp.</string>
<string name="wc_disconnect_all_alert_title">Déconnecter toutes les dApps</string>
@ -1344,13 +1382,22 @@
<string name="wc_errors_proposal_expired_title">La proposition de connexion a expiré</string>
<string name="wc_estimated_wallet_changes">Modifications estimées du portefeuille</string>
<string name="wc_estimated_wallet_changes_not_simulated">La transaction n\'a pas pu être simulée. Veuillez procéder avec prudence.</string>
<string name="wc_insufficient_warning_subtitle">Rechargez votre solde pour couvrir les frais de réseau.</string>
<string name="wc_insufficient_warning_title">%1$s Insuffisant</string>
<string name="wc_malicious_transaction">Transaction malveillante</string>
<string name="wc_missing_required_network_description">Ajoutez le réseau %s à votre portefeuille pour ce wallet</string>
<string name="wc_missing_required_network_title">Le wallet ne nécessite aucun réseaux</string>
<string name="wc_new_connection">Nouvelle connexion</string>
<string name="wc_no_sessions_desc">Connectez votre portefeuille à différentes dApps</string>
<string name="wc_no_sessions_title">Aucune séance</string>
<string name="wc_notification_security_risk_subtitle">Des risques potentiels ou un comportement malveillant ont été détectés. Se connecter ou signer des transactions peut entraîner une perte de fonds.</string>
<string name="wc_notification_security_risk_title">Risque de sécurité connu</string>
<string name="wc_qr_scan_hint">Ouvrez l\'application Web3 et sélectionnez l\'option WalletConnect.</string>
<string name="wc_request_from">Demande de</string>
<string name="wc_signature_type">Type de signature</string>
<string name="wc_specify_networks_subtitle">Au moins un réseau est requis pour la connexion à une dApp.</string>
<string name="wc_specify_networks_title">Spécifier les réseaux sélectionnés</string>
<string name="wc_successfully_signed">Signé avec succès</string>
<string name="wc_transaction_info_to_title">À</string>
<string name="wc_transaction_request">Demande de transaction</string>
<string name="wc_transaction_request_title">Demande de transaction</string>

View file

@ -71,8 +71,10 @@
<string name="app_settings_enable_biometrics_description">設定に移動して、Tangemアプリで生体認証を有効にします。</string>
<string name="app_settings_enable_biometrics_title">生体認証を有効にする</string>
<string name="app_settings_off_biometrics_alert_message">%1$sが無効になると、アプリのロックを解除してウォレットを操作するために、パスコードを入力する必要があります。</string>
<string name="app_settings_off_require_access_code_alert_message">後でウォレットのアクセスコードを入力してもらいます。それを安全に保存し、今後の利用に備えるためです。</string>
<string name="app_settings_off_saved_access_code_alert_message">これにより、保存されているウォレットアクセスコードがすべて削除されます。ウォレットでの今後の操作には、アクセスコードの送信が必要になります。</string>
<string name="app_settings_off_saved_wallet_alert_message">保存したデバイスを削除すると、保存されているすべてのウォレットとそのアクセスコードがアプリから削除されます。</string>
<string name="app_settings_on_require_access_code_alert_message">これにより、保存されているウォレットアクセスコードがすべて削除されます。今後ウォレットを操作するには、アクセスコードの送信が必要になります。</string>
<string name="app_settings_require_access_code">アクセスコードを要求する</string>
<string name="app_settings_require_access_code_footer">このオプションを選択すると、機密性の高い操作における生体認証が無効になります。取引の署名時などには、毎回アクセスコードの入力が必要になります。</string>
<string name="app_settings_saved_access_codes">アクセスコードを保存</string>
@ -147,6 +149,7 @@
<string name="cardano_max_amount_has_token_title">ADAが不足しています。</string>
<string name="common_accept">受け入れる</string>
<string name="common_access_denied">アクセスが拒否されました</string>
<string name="common_add">追加</string>
<string name="common_add_to_portfolio">ポートフォリオに追加</string>
<string name="common_add_token">トークンを追加</string>
<string name="common_address">アドレス</string>
@ -475,6 +478,8 @@
<string name="hw_backup_close_description">実行すると、最初からやり直す必要があります。</string>
<string name="hw_backup_google_drive_description">Googleドライブのバックアップに保存されている既存のウォレットを復元する</string>
<string name="hw_backup_google_drive_title">Googleドライブのバックアップ</string>
<string name="hw_backup_hardware_description">Tangemの業界最高水準のハードウェアウォレットで、今すぐセキュリティをアップグレードしましょう。</string>
<string name="hw_backup_hardware_title">ハードウェアウォレット</string>
<string name="hw_backup_need_action">バックアップへ移動</string>
<string name="hw_backup_need_description">アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。</string>
<string name="hw_backup_need_title">まずバックアップを完了する</string>
@ -486,7 +491,21 @@
<string name="hw_create_seed_description">最新の機能とニュースをお届けします</string>
<string name="hw_create_seed_title">シードフレーズのバックアップ</string>
<string name="hw_create_title">モバイルウォレットを作成する</string>
<string name="hw_import_seed_phrase_already_imported">このリカバリーフレーズはすでにインポートされています。</string>
<string name="hw_mobile_wallet">モバイルウォレット</string>
<string name="hw_upgrade_funds_access_description">手続き中も資金は安全に保管され、完全にアクセス可能です</string>
<string name="hw_upgrade_funds_access_title">資金へのアクセス</string>
<string name="hw_upgrade_general_security_description">すべてのプライベートウォレットデータはモバイルアプリから削除され、Tangemデバイスにのみ安全に保存されます。</string>
<string name="hw_upgrade_general_security_title">セキュリティ全般</string>
<string name="hw_upgrade_key_migration_description">秘密鍵は、アプリからTangemカード・リングに移動します</string>
<string name="hw_upgrade_key_migration_title">鍵の移行</string>
<string name="hw_upgrade_scan_device">デバイスをスキャン</string>
<string name="hw_upgrade_start_action">アップグレードを開始</string>
<string name="hw_upgrade_start_description">ウォレットをTangemウォレットにアップグレードします。これにより、コールドストレージで資産を安全に保管できます。</string>
<string name="hw_upgrade_start_title">Tangemウォレット</string>
<string name="hw_upgrade_title">ハードウェアウォレットにアップグレード</string>
<string name="hw_upgrade_to_cold_banner_description">Tangemの業界最高水準のハードウェアウォレットで、暗号資産を安全に保管しましょう。</string>
<string name="hw_upgrade_to_cold_banner_title">ハードウェアバックアップでウォレットをアップグレード</string>
<string name="information_generated_with_ai">この情報はAIで生成されました。 \nエラーが見つかった場合は、ここをタップしてください。</string>
<string name="initial_message_change_access_code_body">アクセスコードを変更するには、上図のようにカードまたはリングをタップし、操作が終了するまで取り外さないでください。</string>
<string name="initial_message_change_passcode_body">パスコードを変更するには、上記のようにカードをタップし、操作が終了するまで取り外さないでください。</string>
@ -1437,12 +1456,18 @@
<string name="warning_token_trustline_button_title">トラストラインを有効にする</string>
<string name="warning_token_trustline_subtitle">このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。</string>
<string name="warning_token_trustline_title">トラストラインが必要</string>
<string name="wc_alert_add_network_to_portfolio_description">必要なネットワーク %s はポートフォリオに追加されていません。まず追加してから接続を続行してください。</string>
<string name="wc_alert_add_network_to_portfolio_title">ネットワークをポートフォリオに追加</string>
<string name="wc_alert_audit_malicious_domain">悪意のあるドメイン</string>
<string name="wc_alert_audit_unknown_domain">不明なドメイン</string>
<string name="wc_alert_connect_anyway">とにかく接続する</string>
<string name="wc_alert_connection_timeout_description">タイムアウトエラーが発生しました。しばらくしてからもう一度お試しください。</string>
<string name="wc_alert_connection_timeout_title">WalletConnectを確立できませんでした</string>
<string name="wc_alert_domain_issues_description">このドメインは検証できません。承認前にリクエスト内容をよく確認してください。</string>
<string name="wc_alert_network_not_connected_description">続行するには、必要なネットワーク %s でdAppセッションを再接続してください。</string>
<string name="wc_alert_network_not_connected_title">ネットワークが接続されていません</string>
<string name="wc_alert_request_timeout_description">ネットワーク接続を確認してください</string>
<string name="wc_alert_request_timeout_title">リクエストがタイムアウトしました</string>
<string name="wc_alert_session_disconnected_description">ブラウザに戻り、WalletConnect経由で再接続してください。</string>
<string name="wc_alert_session_disconnected_title">Wallet Connectセッションが接続解除されました</string>
<string name="wc_alert_sign_anyway">とにかくサインする</string>
@ -1453,9 +1478,11 @@
<string name="wc_alert_unsupported_dapps_title">サポートされていないdApp</string>
<string name="wc_alert_unsupported_method_description">エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string>
<string name="wc_alert_unsupported_method_title">不明なエラーが発生しました</string>
<string name="wc_alert_unsupported_network_description">このネットワーク %s はTangem Walletでサポートされておらず、接続できません。</string>
<string name="wc_alert_unsupported_network_title">サポートされていないネットワーク</string>
<string name="wc_alert_unsupported_networks_description">Tangemは現在%sで必要なネットワークをサポートしていません。</string>
<string name="wc_alert_unsupported_networks_title">未対応のネットワーク</string>
<string name="wc_alert_verified_domain_description">このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。%s</string>
<string name="wc_alert_verified_domain_description">このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。</string>
<string name="wc_alert_verified_domain_title">検証済みドメイン</string>
<string name="wc_alert_wrong_card_description">アプリで間違ったカードまたはリングが選択されました</string>
<string name="wc_alert_wrong_card_title">問題が起きています</string>
@ -1483,7 +1510,7 @@
<string name="wc_custom_allowance_title">使用可能量の設定</string>
<string name="wc_dapp_disconnected">dAppが接続解除されました</string>
<string name="wc_disconnect_all">すべての接続を解除する</string>
<string name="wc_disconnect_all_alert_desc">すべてのdAppセッションが切断されます。ウォレットはどのdAppにも接続されなくなります。</string>
<string name="wc_disconnect_all_alert_desc">すべてのdAppセッションの接続が解除されます。ウォレットはどのdAppにもリンクされなくなります。</string>
<string name="wc_disconnect_all_alert_title">すべてのdAppを接続解除する</string>
<string name="wc_errors_invalid_domain_subtitle">新しいURIで、再度ペアリングを試してください</string>
<string name="wc_errors_invalid_domain_title">無効なdAppドメイン</string>

View file

@ -92,6 +92,7 @@
<string name="cardano_max_amount_has_token_title">Недостаточно ADA</string>
<string name="common_accept">Принять</string>
<string name="common_access_denied">Доступ запрещен</string>
<string name="common_add">Добавить</string>
<string name="common_add_to_portfolio">Добавить в портфель</string>
<string name="common_add_token">Добавить токен</string>
<string name="common_address">Адрес</string>
@ -1306,12 +1307,18 @@
<string name="warning_token_trustline_button_title">Открыть Trustline</string>
<string name="warning_token_trustline_subtitle">Чтобы получить этот токен, необходимо включить Trustline. Сеть требует резерв %1$s %2$s.</string>
<string name="warning_token_trustline_title">Требуется трастлайн</string>
<string name="wc_alert_add_network_to_portfolio_description">Требуемая сеть %s не добавлена в ваш портфель. Добавьте её, а затем выполните подключение.</string>
<string name="wc_alert_add_network_to_portfolio_title">Добавьте сеть в ваш портфель</string>
<string name="wc_alert_audit_malicious_domain">Вредоносный домен</string>
<string name="wc_alert_audit_unknown_domain">Неизвестный домен</string>
<string name="wc_alert_connect_anyway">Всё равно подключиться</string>
<string name="wc_alert_connection_timeout_description">Ошибка тайм-аута. Пожалуйста, попробуйте позже.</string>
<string name="wc_alert_connection_timeout_title">Не удалось подключиться через Wallet Connect</string>
<string name="wc_alert_domain_issues_description">Этот домен не может быть верифицирован. Внимательно проверьте запрос перед одобрением.</string>
<string name="wc_alert_network_not_connected_description">Чтобы продолжить, переподключите сессию dApp с требуемой сетью %s.</string>
<string name="wc_alert_network_not_connected_title">Сеть не подключена</string>
<string name="wc_alert_request_timeout_description">Проверьте подключение к сети</string>
<string name="wc_alert_request_timeout_title">Время ожидания запроса истекло</string>
<string name="wc_alert_session_disconnected_description">Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect.</string>
<string name="wc_alert_session_disconnected_title">Сессия Wallet Connect была завершена</string>
<string name="wc_alert_sign_anyway">Подписать всё равно</string>
@ -1321,9 +1328,11 @@
<string name="wc_alert_unsupported_dapps_description">Кошелек Tangem в настоящий момент не поддерживает %s</string>
<string name="wc_alert_unsupported_dapps_title">Неподдерживаемый dApp</string>
<string name="wc_alert_unsupported_method_title">Мы обнаружили неизвестную ошибку</string>
<string name="wc_alert_unsupported_network_description">Эта сеть %s не поддерживается Tangem Wallet и не может быть подключена.</string>
<string name="wc_alert_unsupported_network_title">Неподдерживаемая сеть</string>
<string name="wc_alert_unsupported_networks_description">Tangem в настоящее время не поддерживает необходимую сеть для %s</string>
<string name="wc_alert_unsupported_networks_title">Неподдерживаемые сети</string>
<string name="wc_alert_verified_domain_description">Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. %s</string>
<string name="wc_alert_verified_domain_description">Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. </string>
<string name="wc_alert_verified_domain_title">Верифицированный домен</string>
<string name="wc_alert_wrong_card_description">Выбрана не верная карта или кольцо</string>
<string name="wc_alert_wrong_card_title">Похоже, возникла проблема</string>

View file

@ -92,6 +92,7 @@
<string name="cardano_max_amount_has_token_title">Недостатньо ADA</string>
<string name="common_accept">Прийняти</string>
<string name="common_access_denied">Доступ заборонено</string>
<string name="common_add">Додати</string>
<string name="common_add_to_portfolio">Додати у портфель</string>
<string name="common_add_token">Додати токен</string>
<string name="common_address">Адреса</string>
@ -1293,19 +1294,27 @@
<string name="warning_token_trustline_button_title">Відкрити Trustline</string>
<string name="warning_token_trustline_subtitle">Щоб отримати цей токен, потрібно увімкнути Trustline. Мережа вимагає резерв %1$s %2$s.</string>
<string name="warning_token_trustline_title">Відкрийте Trustline</string>
<string name="wc_alert_add_network_to_portfolio_description">Потрібна мережа %s не додана до вашого портфеля. Спочатку додайте її, а потім виконайте підключення.</string>
<string name="wc_alert_add_network_to_portfolio_title">Додати мережу до портфеля</string>
<string name="wc_alert_audit_unknown_domain">Невідомий домен</string>
<string name="wc_alert_connect_anyway">Все одно підключитися</string>
<string name="wc_alert_connection_timeout_description">Помилка тайм-ауту. Будь ласка, спробуйте пізніше.</string>
<string name="wc_alert_connection_timeout_title">Не вдалося зʼєднатися через Wallet Connect</string>
<string name="wc_alert_domain_issues_description">Цей домен не може бути підтверджений. Уважно перевірте запит перед схваленням.</string>
<string name="wc_alert_network_not_connected_description">Щоб продовжити, перепідключіть сесію dApp із потрібною мережею %s.</string>
<string name="wc_alert_network_not_connected_title">Мережа не підключена</string>
<string name="wc_alert_request_timeout_description">Перевірте підключення до мережі</string>
<string name="wc_alert_request_timeout_title">Час очікування запиту вичерпано</string>
<string name="wc_alert_session_disconnected_description">Будь ласка, поверніться до браузеру і повторно підключіться через WalletConnect.</string>
<string name="wc_alert_session_disconnected_title">Сеанс Wallet Connect було завершено</string>
<string name="wc_alert_unknown_error_description">Код помилки: %s. Якщо проблема зберігається, зверніться до нашої служби підтримки.</string>
<string name="wc_alert_unknown_error_title">Ми зіткнулися з невідомою помилкою</string>
<string name="wc_alert_unsupported_dapps_description">Tangem Wallet наразі не підтримує %s</string>
<string name="wc_alert_unsupported_network_description">Ця мережа %s не підтримується Tangem Wallet і не може бути підключена.</string>
<string name="wc_alert_unsupported_network_title">Непідтримувана мережа</string>
<string name="wc_alert_unsupported_networks_description">Tangem наразі не підтримує необхідну мережу для %s.</string>
<string name="wc_alert_unsupported_networks_title">Непідтримувані мережі</string>
<string name="wc_alert_verified_domain_description">Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. %s</string>
<string name="wc_alert_verified_domain_description">Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. </string>
<string name="wc_alert_verified_domain_title">Верифікований домен</string>
<string name="wc_alert_wrong_card_description">Обрана не вірна картка або кільце</string>
<string name="wc_alert_wrong_card_title">Схоже, виникла проблема</string>
@ -1337,6 +1346,7 @@
<string name="wc_errors_no_blockchains_title">Немає мереж</string>
<string name="wc_errors_proposal_expired_subtitle">Будь ласка, згенеруйте новий URI та спробуйте ще раз</string>
<string name="wc_errors_proposal_expired_title">Термін для з’єднання минув</string>
<string name="wc_estimated_wallet_changes">Прогнозовані зміни</string>
<string name="wc_insufficient_warning_subtitle">Поповніть баланс, щоб покрити комісію мережі</string>
<string name="wc_insufficient_warning_title">Недостатньо %1$s</string>
<string name="wc_missing_required_network_description">Додайте %s мережі до вашого портфелю для цього гаманця</string>

View file

@ -152,6 +152,7 @@
<string name="cardano_max_amount_has_token_title">Not enough ADA</string>
<string name="common_accept">Accept</string>
<string name="common_access_denied">Access denied</string>
<string name="common_add">Add</string>
<string name="common_add_to_portfolio">Add to portfolio</string>
<string name="common_add_token">Add token</string>
<string name="common_address">Address</string>
@ -484,6 +485,8 @@
<string name="hw_backup_close_description">If you do, you\'ll need to start over.</string>
<string name="hw_backup_google_drive_description">Recover an existing wallet stored in your Google Drive backup</string>
<string name="hw_backup_google_drive_title">Google Drive Backup</string>
<string name="hw_backup_hardware_description">Upgrade your security right away with a best in class hardware wallet from Tangem.</string>
<string name="hw_backup_hardware_title">Hardware Wallet</string>
<string name="hw_backup_need_action">Go to backup</string>
<string name="hw_backup_need_description">To secure your wallet with a Access Code, complete the backup first.</string>
<string name="hw_backup_need_title">Finish Backup First</string>
@ -497,6 +500,19 @@
<string name="hw_create_title">Create Mobile Wallet</string>
<string name="hw_import_seed_phrase_already_imported">This recovery phrase has already been imported</string>
<string name="hw_mobile_wallet">Mobile Wallet</string>
<string name="hw_upgrade_funds_access_description">Your funds stay safe and fully accessible during the process</string>
<string name="hw_upgrade_funds_access_title">Funds access</string>
<string name="hw_upgrade_general_security_description">All private wallet data will be removed from the mobile app and stored securely on your Tangem device only</string>
<string name="hw_upgrade_general_security_title">General Security</string>
<string name="hw_upgrade_key_migration_description">Private keys will be moved from the app to your Tangem card or ring</string>
<string name="hw_upgrade_key_migration_title">Key Migration</string>
<string name="hw_upgrade_scan_device">Scan device</string>
<string name="hw_upgrade_start_action">Start upgrade</string>
<string name="hw_upgrade_start_description">Youre about to upgrade your wallet to Tangem Wallet. This will keep your assets safe with cold storage.</string>
<string name="hw_upgrade_start_title">Tangem Wallet</string>
<string name="hw_upgrade_title">Upgrade to Hardware Wallet</string>
<string name="hw_upgrade_to_cold_banner_description">Keep your crypto safe with Tangems best-in-class hardware wallet.</string>
<string name="hw_upgrade_to_cold_banner_title">Upgrade wallet with a hardwarebackup</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>
@ -1531,7 +1547,7 @@
<string name="wc_alert_unsupported_network_title">Unsupported network</string>
<string name="wc_alert_unsupported_networks_description">Tangem does not currently support a required network by %s.</string>
<string name="wc_alert_unsupported_networks_title">Unsupported networks</string>
<string name="wc_alert_verified_domain_description">This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s</string>
<string name="wc_alert_verified_domain_description">This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity.</string>
<string name="wc_alert_verified_domain_title">Verified domain</string>
<string name="wc_alert_wrong_card_description">Wrong card or ring selected in the App</string>
<string name="wc_alert_wrong_card_title">We\'ve got some kind of problem</string>

View file

@ -59,7 +59,7 @@ fun PinTextField(
}
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
keyboardType = KeyboardType.NumberPassword,
imeAction = ImeAction.Done,
),
singleLine = true,

View file

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#0099FF"
android:pathData="M16.539,8.682C16.769,8.338 16.677,7.872 16.332,7.642C15.988,7.412 15.522,7.505 15.292,7.849L10.88,14.452L8.677,11.614C8.423,11.287 7.952,11.228 7.625,11.482C7.297,11.736 7.238,12.207 7.492,12.534L10.333,16.194C10.481,16.384 10.712,16.492 10.953,16.484C11.193,16.475 11.416,16.351 11.55,16.151L16.539,8.682Z" />
<path
android:fillColor="#0099FF"
android:fillType="evenOdd"
android:pathData="M12,1.746C6.337,1.746 1.746,6.337 1.746,12C1.746,17.663 6.337,22.254 12,22.254C17.663,22.254 22.254,17.663 22.254,12C22.254,6.337 17.663,1.746 12,1.746ZM3.246,12C3.246,7.165 7.165,3.246 12,3.246C16.835,3.246 20.754,7.165 20.754,12C20.754,16.835 16.835,20.754 12,20.754C7.165,20.754 3.246,16.835 3.246,12Z" />
</vector>

View file

@ -3,7 +3,7 @@ package com.tangem.data.blockaid
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.domain.blockaid.models.transaction.*
import com.domain.blockaid.models.transaction.simultation.AmountInfo
import com.domain.blockaid.models.transaction.simultation.ApprovedAmount
import com.domain.blockaid.models.transaction.simultation.ApproveInfo
import com.domain.blockaid.models.transaction.simultation.SimulationData
import com.domain.blockaid.models.transaction.simultation.TokenInfo
import com.tangem.blockchain.extensions.hexToBigDecimal
@ -17,6 +17,9 @@ private const val SUCCESS_STATUS = "Success"
private const val DOMAIN_CHECKED_STATUS = "hit"
private const val VALIDATION_SAFE_STATUS = "Benign"
private const val VALIDATION_WARNING_STATUS = "Warning"
private const val VALIDATION_MALICIOUS_STATUS = "Malicious"
private const val SOL_ASSET_SYMBOL = "SOL"
internal object BlockAidMapper {
@ -28,6 +31,26 @@ internal object BlockAidMapper {
}
}
fun mapToDomain(from: SolanaTransactionResponse): CheckTransactionResult {
val validation = when (from.result.validation.resultType) {
VALIDATION_SAFE_STATUS -> ValidationResult.SAFE
VALIDATION_WARNING_STATUS -> ValidationResult.WARNING
VALIDATION_MALICIOUS_STATUS -> ValidationResult.UNSAFE
else -> ValidationResult.FAILED_TO_VALIDATE
}
val simulationResponse = from.result.simulation
val simulation = if (simulationResponse == null) {
SimulationResult.FailedToSimulate
} else {
mapToSolanaAssetsDiffs(simulationResponse.accountSummary.accountAssetsDiff)
}
return CheckTransactionResult(
validation = validation,
description = from.result.validation.description,
simulation = simulation,
)
}
fun mapToDomain(from: TransactionScanResponse): CheckTransactionResult {
return CheckTransactionResult(
validation = when {
@ -68,7 +91,7 @@ internal object BlockAidMapper {
fun mapToSolanaRequest(from: TransactionData): SolanaTransactionScanRequest {
return SolanaTransactionScanRequest(
chain = from.chain.lowercase(),
blockchain = from.chain.lowercase(),
accountAddress = from.accountAddress,
metadata = TransactionMetadata(from.domainUrl),
method = from.method,
@ -78,35 +101,61 @@ internal object BlockAidMapper {
private fun mapSimulationSuccessResult(from: AccountSummaryResponse): SimulationResult {
return when {
!from.assetsDiffs.isNullOrEmpty() -> mapSendReceiveTransaction(
from.assetsDiffs,
)
!from.exposures.isNullOrEmpty() -> mapApproveTransaction(
from.exposures,
)
!from.traces.isNullOrEmpty() -> mapNftSendReceiveTransaction(from.traces)
!from.exposures.isNullOrEmpty() -> mapApproveTransaction(from.exposures)
!from.assetsDiffs.isNullOrEmpty() -> mapSendReceiveTransaction(from.assetsDiffs)
else -> SimulationResult.Success(data = SimulationData.NoWalletChangesDetected)
}
}
private fun mapApproveTransaction(exposures: List<Exposure>?): SimulationResult {
val amounts = exposures?.flatMap { exposure ->
val tokenInfo = TokenInfo(
chainId = exposure.asset.chainId,
logoUrl = exposure.asset.logoUrl,
symbol = exposure.asset.symbol ?: "",
decimals = exposure.asset.decimals ?: 0,
private fun mapToSolanaAssetsDiffs(assetsDiffs: List<SolanaTransactionAssetDiff>): SimulationResult {
val sendInfo = assetsDiffs.mapNotNull { assetDiff ->
val outTransfer = assetDiff.outTransfer ?: return@mapNotNull null
val amount = outTransfer.amount?.toBigDecimalOrNull() ?: return@mapNotNull null
AmountInfo.FungibleTokens(
amount = amount,
token = TokenInfo(
chainId = null,
logoUrl = assetDiff.asset.logoUrl,
symbol = assetDiff.asset.assetSymbol(),
decimals = assetDiff.asset.decimals ?: 0,
),
)
exposure.spenders.flatMap { (_, spender) ->
val isUnlimited = spender.isApprovedForAll == true
val approval = spender.approval?.hexToBigDecimal()
spender.exposure.map { detail ->
ApprovedAmount(
approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(),
isUnlimited = isUnlimited,
tokenInfo = tokenInfo,
)
}
}
val receiveInfo = assetsDiffs.mapNotNull { assetDiff ->
val inTransfer = assetDiff.inTransfer ?: return@mapNotNull null
val amount = inTransfer.amount?.toBigDecimalOrNull() ?: return@mapNotNull null
AmountInfo.FungibleTokens(
amount = amount,
token = TokenInfo(
chainId = null,
logoUrl = assetDiff.asset.logoUrl,
symbol = assetDiff.asset.assetSymbol(),
decimals = assetDiff.asset.decimals ?: 0,
),
)
}
return if (sendInfo.isNotEmpty() || receiveInfo.isNotEmpty()) {
SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = receiveInfo))
} else {
SimulationResult.Success(SimulationData.NoWalletChangesDetected)
}
}
private fun SolanaTransactionAsset.assetSymbol(): String {
return if (type?.lowercase().equals(SOL_ASSET_SYMBOL, ignoreCase = true)) {
symbol ?: SOL_ASSET_SYMBOL
} else {
symbol.orEmpty()
}
}
private fun mapApproveTransaction(exposures: List<Exposure>?): SimulationResult {
val amounts: List<ApproveInfo>? = exposures?.flatMap { exposure ->
if (exposure.assetType.isNFT()) {
listOf(mapApproveNftTransaction(exposure))
} else {
mapTransaction(exposure)
}
}
return if (!amounts.isNullOrEmpty()) {
@ -116,6 +165,34 @@ internal object BlockAidMapper {
}
}
private fun mapTransaction(exposure: Exposure): List<ApproveInfo.Amount> {
val tokenInfo = TokenInfo(
chainId = exposure.asset.chainId,
logoUrl = exposure.asset.logoUrl,
symbol = exposure.asset.symbol ?: "",
decimals = exposure.asset.decimals ?: 0,
)
return exposure.spenders.flatMap { (_, spender) ->
val isUnlimited = spender.isApprovedForAll == true
val approval = spender.approval?.hexToBigDecimal()
spender.exposure.map { detail ->
ApproveInfo.Amount(
approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(),
isUnlimited = isUnlimited,
tokenInfo = tokenInfo,
)
}
}
}
private fun mapApproveNftTransaction(exposure: Exposure): ApproveInfo.NonFungibleToken {
return ApproveInfo.NonFungibleToken(
name = exposure.asset.name.orEmpty(),
logoUrl = exposure.spenders.values.firstOrNull()?.exposure?.firstOrNull()?.logoUrl
?: exposure.asset.logoUrl,
)
}
private fun mapSendReceiveTransaction(assetDiffs: List<AssetDiff>?): SimulationResult {
val sendInfo = arrayListOf<AmountInfo>()
val receiveInfo = arrayListOf<AmountInfo>()
@ -128,13 +205,31 @@ internal object BlockAidMapper {
decimals = diff.asset.decimals ?: 0,
)
diff.outTransfer.orEmpty().forEach { transfer ->
transfer.value?.toBigDecimalOrNull()?.let { amount ->
sendInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
if (diff.assetType.isNFT()) {
sendInfo.add(
AmountInfo.NonFungibleTokens(
name = diff.asset.name.orEmpty(),
logoUrl = token.logoUrl,
),
)
} else {
transfer.value?.toBigDecimalOrNull()?.let { amount ->
sendInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
}
}
}
diff.inTransfer.orEmpty().forEach { transfer ->
transfer.value?.toBigDecimalOrNull()?.let { amount ->
receiveInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
if (diff.assetType.isNFT()) {
receiveInfo.add(
AmountInfo.NonFungibleTokens(
name = diff.asset.name.orEmpty(),
logoUrl = token.logoUrl,
),
)
} else {
transfer.value?.toBigDecimalOrNull()?.let { amount ->
receiveInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
}
}
}
}
@ -146,17 +241,7 @@ internal object BlockAidMapper {
}
}
private fun mapNftSendReceiveTransaction(traces: List<Trace>?): SimulationResult {
val sendInfo = traces?.mapNotNull {
it.exposed?.let { exposed ->
AmountInfo.NonFungibleTokens(name = "${it.asset.name} #${exposed.tokenId}", logoUrl = exposed.logoUrl)
}
}
return if (!sendInfo.isNullOrEmpty()) {
SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = listOf()))
} else {
SimulationResult.Success(SimulationData.NoWalletChangesDetected)
}
private fun String.isNFT(): Boolean {
return this.lowercase() == "erc721" || this.lowercase() == "erc1155" || this.lowercase() == "nft"
}
}

View file

@ -24,16 +24,21 @@ internal class DefaultBlockAidRepository(
}
override suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult {
val response = withContext(dispatchers.io) {
when (data.params) {
is TransactionParams.Evm -> {
api.scanJsonRpc(mapper.mapToEvmRequest(data))
}
is TransactionParams.Solana -> {
api.scanSolanaMessage(mapper.mapToSolanaRequest(data))
}
}
return when (data.params) {
is TransactionParams.Evm -> scanEvmTransaction(data = data)
is TransactionParams.Solana -> scanSolanaTransaction(data = data)
}
return mapper.mapToDomain(response)
}
private suspend fun scanEvmTransaction(data: TransactionData): CheckTransactionResult =
withContext(dispatchers.io) {
val response = api.scanJsonRpc(mapper.mapToEvmRequest(data))
mapper.mapToDomain(response)
}
private suspend fun scanSolanaTransaction(data: TransactionData): CheckTransactionResult =
withContext(dispatchers.io) {
val response = api.scanSolanaMessage(mapper.mapToSolanaRequest(data))
mapper.mapToDomain(response)
}
}

View file

@ -4,6 +4,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult
import com.domain.blockaid.models.transaction.SimulationResult
import com.domain.blockaid.models.transaction.ValidationResult
import com.domain.blockaid.models.transaction.simultation.AmountInfo
import com.domain.blockaid.models.transaction.simultation.ApproveInfo
import com.domain.blockaid.models.transaction.simultation.SimulationData
import com.google.common.truth.Truth
import com.tangem.datasource.api.common.blockaid.models.response.*
@ -44,6 +45,7 @@ class BlockAidMapperTest {
val exposure = Exposure(
asset = Asset(chainId = 1, logoUrl = "logo", symbol = "PEPE", decimals = 8),
spenders = mapOf("spender" to spenderDetails),
assetType = "native",
)
val response = TransactionScanResponse(
validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""),
@ -65,9 +67,10 @@ class BlockAidMapperTest {
val approve = simulation?.data as? SimulationData.Approve
Truth.assertThat(approve).isNotNull()
Truth.assertThat(approve?.approvedAmounts?.size).isEqualTo(1)
Truth.assertThat(approve?.approvedAmounts?.first()?.approvedAmount).isEqualTo(BigDecimal("1000.0"))
Truth.assertThat(approve?.approvedAmounts?.first()?.isUnlimited).isTrue()
Truth.assertThat(approve?.items?.size).isEqualTo(1)
Truth.assertThat((approve?.items?.first() as? ApproveInfo.Amount)?.approvedAmount)
.isEqualTo(BigDecimal("1000.0"))
Truth.assertThat((approve?.items?.first() as? ApproveInfo.Amount)?.isUnlimited).isTrue()
}
@Test

View file

@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.blockaid.models.request.DomainScanReques
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
@ -91,7 +92,7 @@ class DefaultBlockAidRepositoryTest {
)
val request = mockk<SolanaTransactionScanRequest>()
val response = mockk<TransactionScanResponse>()
val response = mockk<SolanaTransactionResponse>()
val expectedResult = mockk<CheckTransactionResult>()
every { mapper.mapToSolanaRequest(data) } returns request

View file

@ -9,6 +9,7 @@ import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.common.utils.retryOnError
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
@ -42,6 +43,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
internal class DefaultManageTokensRepository(
private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore,
private val userTokenSaver: UserTokensSaver,
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
private val userTokensResponseStore: UserTokensResponseStore,
private val testnetTokensStorage: TestnetTokensStorage,
@ -127,7 +129,8 @@ internal class DefaultManageTokensRepository(
val tokensResponse = request.params.userWalletId?.let { userWalletId ->
if (loadUserTokensFromRemote && userWallet != null) {
safeApiCall({ tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }) {
createDefaultUserTokensResponse(userWallet)
// save tokens response only if loadUserTokensFromRemote is true and it means onboarding call
createAndSaveDefaultUserTokensResponse(userWallet = userWallet)
}
} else {
getSavedUserTokensResponseSync(userWalletId)
@ -158,6 +161,12 @@ internal class DefaultManageTokensRepository(
)
}
private suspend fun createAndSaveDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse {
val userTokensResponse = createDefaultUserTokensResponse(userWallet)
userTokenSaver.store(userWallet.walletId, userTokensResponse, useEnricher = false)
return userTokensResponse
}
private suspend fun fetchTestnetCurrencies(
userWallet: UserWallet,
request: Request<ManageTokensListConfig>,

View file

@ -32,6 +32,7 @@ internal object ManageTokensDataModule {
userWalletsStore: UserWalletsStore,
manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
userTokensResponseStore: UserTokensResponseStore,
userTokensSaver: UserTokensSaver,
testnetTokensStorage: TestnetTokensStorage,
dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
@ -43,6 +44,7 @@ internal object ManageTokensDataModule {
userWalletsStore = userWalletsStore,
manageTokensUpdateFetcher = manageTokensUpdateFetcher,
userTokensResponseStore = userTokensResponseStore,
userTokenSaver = userTokensSaver,
testnetTokensStorage = testnetTokensStorage,
excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,

View file

@ -381,6 +381,26 @@ internal class DefaultTransactionRepository(
preparer.prepareForSendMultiple(transactionData, signer)
}
override suspend fun prepareAndSign(
transactionData: TransactionData,
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
) = withContext(dispatchers.io) {
val preparer = getPreparer(network, userWalletId)
preparer.prepareAndSign(transactionData, signer)
}
override suspend fun prepareAndSignMultiple(
transactionData: List<TransactionData>,
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
) = withContext(dispatchers.io) {
val preparer = getPreparer(network, userWalletId)
preparer.prepareAndSignMultiple(transactionData, signer)
}
private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(

View file

@ -1,9 +1,21 @@
package com.tangem.data.walletconnect.network.ethereum
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.reown.walletkit.client.Wallet
import com.reown.walletkit.client.Wallet.Model
import com.reown.walletkit.client.WalletKit
import com.tangem.blockchain.extensions.hexToInt
import com.tangem.data.walletconnect.model.CAIP10
import com.tangem.data.walletconnect.model.CAIP2
import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork.NamespaceConverter.Companion.ETH_NAMESPACE_KEY
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.WcNetworksConverter
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.walletconnect.model.HandleMethodError
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.WcSession
@ -13,9 +25,13 @@ import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
internal class WcEthAddNetworkUseCase @AssistedInject constructor(
private val respondService: WcRespondService,
private val networksConverter: WcNetworksConverter,
addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory,
@Assisted val context: WcMethodUseCaseContext,
@Assisted override val method: WcEthMethod.AddEthereumChain,
) : WcAddNetworkUseCase {
@ -31,10 +47,63 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor(
else -> WcNetworkDerivationState.Single
}
private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context)
override suspend fun invoke(): Either<HandleMethodError, WcAddNetworkUseCase.AddNetwork> {
return addSwitchCommonDelegate
.commonChecks(method.rawChain.chainId)
.map { addedNetwork ->
WcAddNetworkUseCase.AddNetwork(
network = addedNetwork,
isExistInWcSession = addSwitchCommonDelegate.existInWcSession(addedNetwork),
)
}
}
override suspend fun approve(): Either<WcRequestError, String> {
fun illegalState() = WcRequestError.UnknownError(IllegalStateException("IllegalStateException")).left()
val requestedNetworkCAIP2 = CAIP2.fromRaw(rawSdkRequest.chainId.orEmpty()) ?: return illegalState()
val networkToAddCAIP2 = addSwitchCommonDelegate.hexChainIdToCAIP2(method.rawChain.chainId)
?: return illegalState()
val namespaces = session.sdkModel.namespaces[requestedNetworkCAIP2.namespace]
?: return illegalState()
// find and add all derivation
val networkToAddCAIP10 = networksConverter
.allAddressForChain(networkToAddCAIP2.raw, wallet)
.map { address -> CAIP10(networkToAddCAIP2, address).raw }
val newNamespaces = namespaces.copy(
chains = namespaces.chains.plus(networkToAddCAIP2.raw),
accounts = namespaces.accounts.plus(networkToAddCAIP10),
)
val sdkNewNamespaces = session.sdkModel.namespaces
.plus(requestedNetworkCAIP2.namespace to newNamespaces)
.mapValues { (_, session) ->
Model.Namespace.Session(
chains = session.chains,
accounts = session.accounts,
methods = session.methods,
events = session.events,
)
}
val sessionUpdate = Wallet.Params.SessionUpdate(
sessionTopic = context.session.sdkModel.topic,
namespaces = sdkNewNamespaces,
)
sdkUpdateSession(sessionUpdate) // ignore result for now
return respondService.respond(rawSdkRequest, "")
}
private suspend fun sdkUpdateSession(sessionUpdate: Wallet.Params.SessionUpdate): Either<Throwable, Unit> {
return suspendCancellableCoroutine { continuation ->
WalletKit.updateSession(
params = sessionUpdate,
onSuccess = { if (continuation.isActive) continuation.resume(Unit.right()) },
onError = { if (continuation.isActive) continuation.resume(it.throwable.left()) },
)
}
}
override fun reject() {
respondService.rejectRequestNonBlock(rawSdkRequest)
}
@ -43,4 +112,37 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor(
interface Factory {
fun create(context: WcMethodUseCaseContext, method: WcEthMethod.AddEthereumChain): WcEthAddNetworkUseCase
}
}
internal class WcEthAddSwitchCommonDelegate @AssistedInject constructor(
private val networksConverter: WcNetworksConverter,
@Assisted val context: WcMethodUseCaseContext,
) {
private val wallet: UserWallet get() = context.session.wallet
fun hexChainIdToCAIP2(hexChainId: String): CAIP2? = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${hexChainId.hexToInt()}")
fun existInWcSession(network: Network): Boolean {
return context.session.networks.any { it.rawId == network.rawId }
}
suspend fun commonChecks(hexChainId: String): Either<HandleMethodError, Network> {
val caip2 = hexChainIdToCAIP2(hexChainId)
?: return HandleMethodError.UnknownError("Failed to parse CAIP2").left()
val generalNetwork = networksConverter.createNetwork(caip2.raw, wallet)
if (generalNetwork == null) {
return HandleMethodError.TangemUnsupportedNetwork(caip2.raw).left()
}
val addedNetwork = networksConverter.mainOrAnyWalletNetworkForRequest(caip2.raw, wallet)
if (addedNetwork == null) {
return HandleMethodError.NotAddedNetwork(generalNetwork.name).left()
}
return addedNetwork.right()
}
@AssistedFactory
interface Factory {
fun create(context: WcMethodUseCaseContext): WcEthAddSwitchCommonDelegate
}
}

View file

@ -14,7 +14,6 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Compani
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
import com.tangem.data.walletconnect.utils.WcNetworksConverter
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.walletconnect.model.*
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.repository.WcSessionsManager
@ -44,7 +43,7 @@ internal class WcEthNetwork(
?: return HandleMethodError.UnknownSession.left()
val wallet = session.wallet
val chainId = request.chainId.orEmpty()
val method: WcEthMethod = name.toMethod(request, wallet)
val method: WcEthMethod = name.toMethod(request)
.getOrElse { return error(it.message.orEmpty()) }
?: return error("Failed to parse $name")
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet)
@ -54,7 +53,9 @@ internal class WcEthNetwork(
is WcEthMethod.SendTransaction -> method.transaction.from
is WcEthMethod.SignTransaction -> method.transaction.from
is WcEthMethod.SignTypedData -> method.account
is WcEthMethod.AddEthereumChain ->
is WcEthMethod.AddEthereumChain,
is WcEthMethod.SwitchEthereumChain,
->
anyExistNetwork()
?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() }
.orEmpty()
@ -65,7 +66,9 @@ internal class WcEthNetwork(
is WcEthMethod.SendTransaction,
is WcEthMethod.SignTransaction,
-> networksConverter.findWalletNetworkForRequest(request, session, accountAddress)
is WcEthMethod.AddEthereumChain -> anyExistNetwork()
is WcEthMethod.AddEthereumChain,
is WcEthMethod.SwitchEthereumChain,
-> anyExistNetwork()
} ?: return error("Failed to find walletNetwork for accountAddress $accountAddress")
val context = WcMethodUseCaseContext(
@ -81,13 +84,11 @@ internal class WcEthNetwork(
is WcEthMethod.SignTransaction -> factories.signTransaction.create(context, method)
is WcEthMethod.SignTypedData -> factories.signTypedData.create(context, method)
is WcEthMethod.AddEthereumChain -> factories.addNetwork.create(context, method)
is WcEthMethod.SwitchEthereumChain -> factories.switchNetwork.create(context, method)
}.right()
}
private suspend fun WcEthMethodName.toMethod(
request: WcSdkSessionRequest,
wallet: UserWallet,
): Either<Throwable, WcEthMethod?> {
private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest): Either<Throwable, WcEthMethod?> {
val rawParams = request.request.params
return when (this) {
WcEthMethodName.EthSign,
@ -109,14 +110,17 @@ internal class WcEthNetwork(
}
}
?: return null.right()
WcEthMethodName.AddEthereumChain -> moshi.fromJson<List<WcEthAddChain>>(rawParams)
WcEthMethodName.AddEthereumChain,
WcEthMethodName.SwitchEthereumChain,
-> moshi.fromJson<List<WcEthAddChain>>(rawParams)
.getOrElse { return it.left() }
?.firstOrNull()
?.let {
val newNetwork = networksConverter
.mainOrAnyWalletNetworkForRequest(it.chainId, wallet)
?: return null.right()
WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork).right()
if (this == WcEthMethodName.AddEthereumChain) {
WcEthMethod.AddEthereumChain(rawChain = it).right()
} else {
WcEthMethod.SwitchEthereumChain(rawChain = it).right()
}
}
?: null.right()
}
@ -147,13 +151,17 @@ internal class WcEthNetwork(
override val excludedBlockchains: ExcludedBlockchains,
) : WcNamespaceConverter {
override val namespaceKey: NamespaceKey = NamespaceKey("eip155")
override val namespaceKey: NamespaceKey = NamespaceKey(ETH_NAMESPACE_KEY)
override fun toBlockchain(chainId: CAIP2): Blockchain? {
if (chainId.namespace != namespaceKey.key) return null
val ethChainId = chainId.reference.toIntOrNull() ?: return null
return Blockchain.fromChainId(ethChainId)
}
companion object {
const val ETH_NAMESPACE_KEY = "eip155"
}
}
internal class Factories @Inject constructor(
@ -162,5 +170,6 @@ internal class WcEthNetwork(
val sendTransaction: WcEthSendTransactionUseCase.Factory,
val signTransaction: WcEthSignTransactionUseCase.Factory,
val addNetwork: WcEthAddNetworkUseCase.Factory,
val switchNetwork: WcEthSwitchNetworkUseCase.Factory,
)
}

View file

@ -7,6 +7,9 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.formatHex
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam.TxSentFrom
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.SignCollector
@ -86,6 +89,16 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
emit(state.toResult(parseSendError(error).left()))
}
.getOrNull() ?: return
analytics.send(
Basic.TransactionSent(
sentFrom = TxSentFrom.WalletConnect(
blockchain = network.name,
token = network.currencySymbol,
feeType = null,
),
memoType = MemoType.Null,
),
)
val respondResult = respondService.respond(rawSdkRequest, hash.formatHex())
emit(state.toResult(respondResult))
}

View file

@ -30,7 +30,7 @@ import com.tangem.blockchain.common.Amount as BlockchainAmount
internal class WcEthSignTransactionUseCase @AssistedInject constructor(
override val respondService: WcRespondService,
override val analytics: AnalyticsEventHandler,
private val prepareForSend: PrepareForSendUseCase,
private val prepareForSend: PrepareForSendUseCase, // TODO: TODO("[REDACTED_JIRA]")
private val ethTxHelper: WcEthTxHelper,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted override val method: WcEthMethod.SignTransaction,

View file

@ -0,0 +1,56 @@
package com.tangem.data.walletconnect.network.ethereum
import arrow.core.Either
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.HandleMethodError
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState
import com.tangem.domain.walletconnect.usecase.method.WcSwitchNetworkUseCase
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class WcEthSwitchNetworkUseCase @AssistedInject constructor(
private val respondService: WcRespondService,
@Assisted val context: WcMethodUseCaseContext,
@Assisted override val method: WcEthMethod.SwitchEthereumChain,
addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory,
) : WcSwitchNetworkUseCase {
override val session: WcSession
get() = context.session
override val rawSdkRequest: WcSdkSessionRequest
get() = context.rawSdkRequest
override val network: Network
get() = context.network
override val derivationState: WcNetworkDerivationState = when {
context.networkDerivationsCount > 1 -> WcNetworkDerivationState.Multiple(walletAddress = context.accountAddress)
else -> WcNetworkDerivationState.Single
}
private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context)
override suspend fun invoke(): Either<HandleMethodError, WcSwitchNetworkUseCase.SwitchNetwork> {
return addSwitchCommonDelegate
.commonChecks(method.rawChain.chainId)
.map { addedNetwork ->
WcSwitchNetworkUseCase.SwitchNetwork(
network = addedNetwork,
isExistInWcSession = addSwitchCommonDelegate.existInWcSession(addedNetwork),
)
}
}
override fun reject() {
respondService.rejectRequestNonBlock(rawSdkRequest)
}
@AssistedFactory
interface Factory {
fun create(context: WcMethodUseCaseContext, method: WcEthMethod.SwitchEthereumChain): WcEthSwitchNetworkUseCase
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.data.walletconnect.network.ethereum
import com.domain.blockaid.models.transaction.CheckTransactionResult
import com.domain.blockaid.models.transaction.SimulationResult
import com.domain.blockaid.models.transaction.simultation.ApprovedAmount
import com.domain.blockaid.models.transaction.simultation.ApproveInfo
import com.domain.blockaid.models.transaction.simultation.SimulationData
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData
@ -68,13 +68,15 @@ internal class WcEthTxHelper @Inject constructor(
)
}
fun getApprovedAmount(txData: String?, result: CheckTransactionResult): ApprovedAmount? {
fun getApprovedAmount(txData: String?, result: CheckTransactionResult): ApproveInfo.Amount? {
val approvalMethodId = ApprovalERC20TokenCallData("", null).methodId
val isApprovalWcMethod = txData?.startsWith(approvalMethodId)
if (isApprovalWcMethod != true) return null
val simulation = result.simulation as? SimulationResult.Success
?: return null
val approves = (simulation.data as? SimulationData.Approve)?.approvedAmounts
val approves = (simulation.data as? SimulationData.Approve)
?.items
?.filterIsInstance<ApproveInfo.Amount>()
?: return null
if (approves.isEmpty()) return null
val amount = approves.first()

View file

@ -10,7 +10,7 @@ import com.tangem.data.walletconnect.sign.SignCollector
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase
import com.tangem.domain.walletconnect.error.parseSendError
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
@ -29,7 +29,7 @@ import org.json.JSONObject
internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor(
override val respondService: WcRespondService,
override val analytics: AnalyticsEventHandler,
private val prepareForSend: PrepareForSendUseCase,
private val prepareAndSign: PrepareAndSignUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted override val method: WcSolanaMethod.SignAllTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
@ -46,7 +46,7 @@ internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor(
).map { lce -> lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) } }
override suspend fun SignCollector<List<TransactionData>>.onSign(state: WcSignState<List<TransactionData>>) {
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
val hash = prepareAndSign.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
.onLeft { error ->
emit(state.toResult(parseSendError(error).left()))
}

View file

@ -10,7 +10,7 @@ import com.tangem.data.walletconnect.sign.SignCollector
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase
import com.tangem.domain.walletconnect.error.parseSendError
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
@ -27,7 +27,7 @@ import okio.ByteString.Companion.decodeBase64
internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
override val respondService: WcRespondService,
override val analytics: AnalyticsEventHandler,
private val prepareForSend: PrepareForSendUseCase,
private val prepareAndSign: PrepareAndSignUseCase,
@Assisted override val context: WcMethodUseCaseContext,
@Assisted override val method: WcSolanaMethod.SignTransaction,
blockAidDelegate: BlockAidVerificationDelegate,
@ -44,7 +44,7 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
).map { lce -> lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) } }
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
val hash = prepareAndSign.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
.onLeft { error ->
emit(state.toResult(parseSendError(error).left()))
}

View file

@ -20,10 +20,12 @@ import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import org.joda.time.DateTime
import timber.log.Timber
import java.net.URI
@Suppress("LongParameterList")
internal class DefaultWcPairUseCase @AssistedInject constructor(
@ -63,6 +65,12 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
return@flow
}
val dAppUri = URI(sdkSessionProposal.url)
if (dAppUri.host.isNullOrEmpty()) {
emit(WcPairState.Error(WcPairError.InvalidDomainURL))
return@flow
}
val proposalState = buildProposalState(sdkSessionProposal, sdkVerifyContext)
.onLeft {
analytics.send(WcAnalyticEvents.PairFailed(it.code))
@ -112,14 +120,21 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
Timber.tag(WC_TAG).e(it, "Failed to approve session ${sdkSessionProposal.name}")
}
emit(WcPairState.Approving.Result(sessionForApprove, either))
}.onCompletion {
if (it != null) {
Timber.tag(WC_TAG).e(it, "Completed with error $pairRequest")
emit(WcPairState.Error(WcPairError.Unknown(it.message.orEmpty())))
} else {
Timber.tag(WC_TAG).i("Completed successfully $pairRequest")
}
}
.catch {
val pairError: WcPairError = when (it) {
is TimeoutCancellationException -> WcPairError.TimeoutException(it.message.orEmpty())
else -> WcPairError.Unknown(it.message.orEmpty())
}
emit(WcPairState.Error(pairError))
}
.onCompletion {
if (it != null) {
Timber.tag(WC_TAG).e(it, "Completed with error $pairRequest")
} else {
Timber.tag(WC_TAG).i("Completed successfully $pairRequest")
}
}
}
override fun approve(sessionForApprove: WcSessionApprove) {

View file

@ -150,7 +150,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
private fun Throwable.toApproveError() = WcPairError.ApprovalFailed(this.localizedMessage.orEmpty()).left()
companion object {
private const val CALLBACK_TIMEOUT = 60
private const val CALLBACK_TIMEOUT = 15
// com.reown.android.pairing.engine.domain.PairingEngine.pair
private val pairingExpiredMessages = listOf(
"Pairing URI expired",

View file

@ -33,6 +33,7 @@ internal class DefaultWcRequestService(
Timber.tag(WC_TAG).i("handle request name $name")
if (name is WcMethodName.Unsupported) {
respondService.rejectRequestNonBlock(sr)
if (name.raw.startsWith("wallet_")) return
}
_wcRequest.trySend(name to sr)
}

View file

@ -4,31 +4,32 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.domain.models.network.Network
import com.tangem.utils.converter.Converter
import javax.inject.Inject
internal class BlockAidChainNameConverter @Inject constructor() : Converter<Network, String> {
internal object BlockAidChainNameConverter : Converter<Network, String?> {
@Suppress("CyclomaticComplexMethod")
override fun convert(value: Network): String {
override fun convert(value: Network): String? {
return when (Blockchain.fromNetworkId(value.backendId)) {
Blockchain.Arbitrum -> "arbitrum"
Blockchain.Avalanche -> "avalanche"
Blockchain.AvalancheTestnet -> "avalanche-fuji"
Blockchain.Binance, Blockchain.BSC -> "bsc"
Blockchain.Ethereum -> "ethereum"
Blockchain.EthereumTestnet -> "ethereum-sepolia"
Blockchain.Polygon -> "polygon"
Blockchain.Solana -> "mainnet"
Blockchain.Gnosis -> "gnosis"
Blockchain.Optimism -> "optimism"
Blockchain.ZkSyncEra -> "zksync"
Blockchain.ZkSyncEraTestnet -> "zksync-sepolia"
Blockchain.Base -> "base"
Blockchain.BaseTestnet -> "base-sepolia"
Blockchain.Binance, Blockchain.BSC -> "bsc"
Blockchain.Ethereum -> "ethereum"
Blockchain.Optimism -> "optimism"
Blockchain.Polygon -> "polygon"
Blockchain.ZkSyncEra -> "zksync"
Blockchain.ZkSyncEraTestnet -> "zksync-sepolia"
Blockchain.Blast, Blockchain.BlastTestnet -> "blast"
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apechain"
Blockchain.Scroll -> "scroll"
else -> value.name
Blockchain.EthereumTestnet -> "ethereum-sepolia"
Blockchain.Gnosis -> "gnosis"
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apechain"
Blockchain.Solana -> "mainnet"
else -> null
}
}
}

View file

@ -17,7 +17,6 @@ import javax.inject.Inject
internal class BlockAidVerificationDelegate @Inject constructor(
private val blockAidVerifier: BlockAidVerifier,
private val blockAidChainNameConverter: BlockAidChainNameConverter,
) {
fun getSecurityStatus(
@ -27,7 +26,6 @@ internal class BlockAidVerificationDelegate @Inject constructor(
session: WcSession,
accountAddress: String?,
): LceFlow<Throwable, CheckTransactionResult> = flow {
emit(Lce.Loading(partialContent = null))
val failedResult = CheckTransactionResult(
validation = ValidationResult.FAILED_TO_VALIDATE,
simulation = SimulationResult.FailedToSimulate,
@ -36,7 +34,21 @@ internal class BlockAidVerificationDelegate @Inject constructor(
emit(Lce.Content(failedResult))
return@flow
}
when (method) {
val chain = BlockAidChainNameConverter.convert(network)
if (chain == null) {
emit(Lce.Content(failedResult))
return@flow
}
emit(Lce.Loading(partialContent = null))
val methodName = when (method) {
is WcEthMethod -> rawSdkRequest.request.method
is WcSolanaMethod -> method.trimmedPrefixMethodName
is WcMethod.Unsupported -> {
emit(Lce.Content(failedResult))
return@flow
}
}
val params = when (method) {
is WcEthMethod -> TransactionParams.Evm(rawSdkRequest.request.params)
is WcSolanaMethod.SignAllTransaction -> TransactionParams.Solana(method.transaction)
is WcSolanaMethod.SignTransaction -> TransactionParams.Solana(listOf(method.transaction))
@ -45,25 +57,28 @@ internal class BlockAidVerificationDelegate @Inject constructor(
emit(Lce.Content(failedResult))
return@flow
}
else -> null
}?.let { params ->
blockAidVerifier.verifyTransaction(
TransactionData(
chain = blockAidChainNameConverter.convert(network),
accountAddress = accountAddress,
method = rawSdkRequest.request.method,
domainUrl = session.sdkModel.appMetaData.url,
params = params,
),
).fold(
ifLeft = {
Timber.e("Failed to verify transaction: ${it.localizedMessage}")
emit(Lce.Error(it))
},
ifRight = {
emit(Lce.Content(it))
},
)
} ?: emit(Lce.Content(failedResult))
else -> {
emit(Lce.Content(failedResult))
return@flow
}
}
blockAidVerifier.verifyTransaction(
data = TransactionData(
chain = chain,
accountAddress = accountAddress,
method = methodName,
domainUrl = session.sdkModel.appMetaData.url,
params = params,
),
).fold(
ifLeft = {
Timber.e("Failed to verify transaction: ${it.localizedMessage}")
emit(Lce.Error(it))
},
ifRight = {
emit(Lce.Content(it))
},
)
}
}

View file

@ -25,6 +25,11 @@ internal class WcNetworksConverter @Inject constructor(
private val tokensFeatureToggles: TokensFeatureToggles,
) {
fun createNetwork(chainId: String, wallet: UserWallet): Network? {
return namespaceConverters
.firstNotNullOfOrNull { it.toNetwork(chainId, wallet) }
}
suspend fun findWalletNetworkForRequest(
request: WcSdkSessionRequest,
session: WcSession,
@ -48,6 +53,11 @@ internal class WcNetworksConverter @Inject constructor(
return networks.firstOrNull { !isCustomCoin(it) } ?: networks.firstOrNull()
}
suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet): List<String> {
return filterWalletNetworkForRequest(rawChainId, wallet)
.mapNotNull { walletManagersFacade.getDefaultAddress(wallet.walletId, it)?.lowercase() }
}
/**
* return all exist derivation networks
*/

View file

@ -10,6 +10,14 @@ internal object WcSdkSessionConverter : Converter<Wallet.Model.Session, WcSdkSes
return WcSdkSession(
topic = value.topic,
appMetaData = value.metaData?.let { WcAppMetaDataConverter.convert(it) } ?: WcAppMetaDataConverter.empty,
namespaces = value.namespaces.mapValues { (_, session) ->
WcSdkSession.Session(
chains = session.chains ?: listOf(),
accounts = session.accounts,
methods = session.methods,
events = session.events,
)
},
)
}
}

View file

@ -47,7 +47,7 @@ internal class DefaultWcPairUseCaseTest {
pairingTopic = "",
name = "",
description = "",
url = "",
url = "https://react-app.walletconnect.com/",
icons = listOf(),
redirect = "",
requiredNamespaces = mapOf(),

View file

@ -62,6 +62,7 @@ internal class WcSignUseCaseDelegateTest {
connectingTime = 0L,
sdkModel = WcSdkSession(
topic = "",
namespaces = mapOf(),
appMetaData = WcAppMetaData(
name = "",
description = "",

View file

@ -0,0 +1,13 @@
package com.domain.blockaid.models.transaction.simultation
import java.math.BigDecimal
sealed class ApproveInfo {
data class Amount(
val approvedAmount: BigDecimal,
val isUnlimited: Boolean,
val tokenInfo: TokenInfo,
) : ApproveInfo()
data class NonFungibleToken(val name: String, val logoUrl: String?) : ApproveInfo()
}

View file

@ -1,9 +0,0 @@
package com.domain.blockaid.models.transaction.simultation
import java.math.BigDecimal
data class ApprovedAmount(
val approvedAmount: BigDecimal,
val isUnlimited: Boolean,
val tokenInfo: TokenInfo,
)

View file

@ -16,9 +16,7 @@ sealed class SimulationData {
/**
* Represents an approve operation with the specified amount (can be multiple amounts for NFT)
*/
data class Approve(
val approvedAmounts: List<ApprovedAmount>,
) : SimulationData()
data class Approve(val items: List<ApproveInfo>) : SimulationData()
/**
* Simulation was successfully performed and no changes detected

View file

@ -7,11 +7,13 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import kotlinx.coroutines.flow.Flow
/**
* Manager that holds info about available actions as Sell and Buy
*/
@Deprecated("Move to express domain layer")
interface RampStateManager {
suspend fun availableForBuy(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): ScenarioUnavailabilityReason
@ -50,4 +52,9 @@ interface RampStateManager {
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): ScenarioUnavailabilityReason
/**
* Returns whether asset requirements are full filled to be able use express services
*/
fun checkAssetRequirements(requirements: AssetRequirementsCondition?): Boolean
}

View file

@ -10,6 +10,7 @@ class GetManagedTokensUseCase(
operator fun invoke(
context: ManageTokensListBatchingContext,
// only for onboarding case, change carefully and check repository implementation
loadUserTokensFromRemote: Boolean,
batchSize: Int = 40,
): ManageTokensListBatchFlow {

View file

@ -386,8 +386,9 @@ abstract class BaseCurrencyStatusOperations(
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId),
)
?.firstOrNull { it.network.id == networkId && it.network.derivationPath == derivationPath }
?: error("Unable to create network coin with ID: $networkId and derivation path: $derivationPath")
?.filterIsInstance<CryptoCurrency.Coin>()
?.firstOrNull { it.network.id == networkId }
?: error("Unable to create network coin with ID: $networkId")
} else {
currenciesRepository.getNetworkCoin(userWalletId, networkId, derivationPath)
}

View file

@ -109,4 +109,18 @@ interface TransactionRepository {
userWalletId: UserWalletId,
network: Network,
): com.tangem.blockchain.extensions.Result<List<ByteArray>>
suspend fun prepareAndSign(
transactionData: TransactionData,
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
): com.tangem.blockchain.extensions.Result<ByteArray>
suspend fun prepareAndSignMultiple(
transactionData: List<TransactionData>,
signer: TransactionSigner,
userWalletId: UserWalletId,
network: Network,
): com.tangem.blockchain.extensions.Result<List<ByteArray>>
}

View file

@ -0,0 +1,70 @@
package com.tangem.domain.transaction.usecase
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.error.SendTransactionError
class PrepareAndSignUseCase(
private val transactionRepository: TransactionRepository,
private val cardSdkConfigRepository: CardSdkConfigRepository,
) {
suspend operator fun invoke(
transactionData: TransactionData,
userWallet: UserWallet,
network: Network,
): Either<SendTransactionError, ByteArray> {
val signer = createSigner(userWallet)
val result = transactionRepository.prepareAndSign(
transactionData = transactionData,
userWalletId = userWallet.walletId,
network = network,
signer = signer,
)
return when (result) {
is Result.Failure -> SendTransactionUseCase.handleError(result).left()
is Result.Success -> result.data.right()
}
}
suspend operator fun invoke(
transactionData: List<TransactionData>,
userWallet: UserWallet,
network: Network,
): Either<SendTransactionError, List<ByteArray>> {
val signer = createSigner(userWallet)
val result = transactionRepository.prepareAndSignMultiple(
transactionData = transactionData,
userWalletId = userWallet.walletId,
network = network,
signer = signer,
)
return when (result) {
is Result.Failure -> SendTransactionUseCase.handleError(result).left()
is Result.Success -> result.data.right()
}
}
private fun createSigner(userWallet: UserWallet): TransactionSigner {
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
val card = userWallet.scanResponse.card
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
val signer = cardSdkConfigRepository.getCommonSigner(
cardId = card.cardId.takeIf { isCardNotBackedUp },
twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse),
)
return signer
}
}

View file

@ -5,6 +5,11 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class WcEthAddChain(
/**
* chainId are identified by EIP-155 integers expressed in hexadecimal notation,
* with 0x prefix and no leading zeroes for the chainId value.
* For more information https://eips.ethereum.org/EIPS/eip-5792#atomicbatch-capability
*/
@Json(name = "chainId")
val chainId: String,
)

View file

@ -1,7 +1,5 @@
package com.tangem.domain.walletconnect.model
import com.tangem.domain.models.network.Network
sealed interface WcEthMethod : WcMethod {
data class MessageSign(
@ -15,7 +13,7 @@ sealed interface WcEthMethod : WcMethod {
val account: String,
val dataForSign: String,
) : WcEthMethod {
val humanMsg: String = params.message.contents.orEmpty()
val humanMsg: String = params.message?.contents.orEmpty()
}
data class SendTransaction(
@ -28,6 +26,9 @@ sealed interface WcEthMethod : WcMethod {
data class AddEthereumChain(
val rawChain: WcEthAddChain,
val network: Network,
) : WcEthMethod
data class SwitchEthereumChain(
val rawChain: WcEthAddChain,
) : WcEthMethod
}

View file

@ -6,24 +6,24 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class WcEthSignTypedDataParams(
@Json(name = "domain")
val domain: Domain,
val domain: Domain?,
@Json(name = "message")
val message: Message,
val message: Message?,
@Json(name = "primaryType")
val primaryType: String,
val primaryType: String?,
@Json(name = "types")
val types: Map<String, List<Types.Type>>,
) {
@JsonClass(generateAdapter = true)
data class Domain(
@Json(name = "chainId")
val chainId: Int,
val chainId: Int?,
@Json(name = "name")
val name: String,
val name: String?,
@Json(name = "verifyingContract")
val verifyingContract: String,
val verifyingContract: String?,
@Json(name = "version")
val version: String,
val version: String?,
)
@JsonClass(generateAdapter = true)

View file

@ -14,6 +14,7 @@ enum class WcEthMethodName(override val raw: String) : WcMethodName {
SignTransaction("eth_signTransaction"),
SendTransaction("eth_sendTransaction"),
AddEthereumChain("wallet_addEthereumChain"),
SwitchEthereumChain("wallet_switchEthereumChain"),
}
enum class WcSolanaMethodName(override val raw: String) : WcMethodName {

View file

@ -16,4 +16,5 @@ sealed class WcPairError(
data class ApprovalFailed(override val message: String) : WcPairError("107 002 003")
data object RejectionFailed : WcPairError("107 002 004")
data class Unknown(override val message: String) : WcPairError(message)
data class TimeoutException(override val message: String) : WcPairError(message)
}

View file

@ -69,4 +69,9 @@ sealed class HandleMethodError(
data object UnknownSession : HandleMethodError(message = "WalletConnect session was disconnected")
data class UnknownError(override val message: String) : HandleMethodError(message)
data class TangemUnsupportedNetwork(val unsupportedNetwork: String) :
HandleMethodError("TangemUnsupportedNetwork $unsupportedNetwork")
data class NotAddedNetwork(val networkName: String) : HandleMethodError("NotAddedNetwork $networkName")
data class RequiredNetwork(val networkName: String) : HandleMethodError("RequiredNetwork $networkName")
}

View file

@ -2,18 +2,27 @@ package com.tangem.domain.walletconnect.model
sealed interface WcSolanaMethod : WcMethod {
val methodName: String
val trimmedPrefixMethodName: String get() = methodName.substringAfter("_")
data class SignMessage(
val pubKey: String,
val rawMessage: String,
val humanMsg: String,
) : WcSolanaMethod
) : WcSolanaMethod {
override val methodName: String = WcSolanaMethodName.SignMessage.raw
}
data class SignTransaction(
val transaction: String,
val address: String?,
) : WcSolanaMethod
) : WcSolanaMethod {
override val methodName: String = WcSolanaMethodName.SignTransaction.raw
}
data class SignAllTransaction(
val transaction: List<String>,
) : WcSolanaMethod
) : WcSolanaMethod {
override val methodName: String = WcSolanaMethodName.SendAllTransaction.raw
}
}

View file

@ -6,4 +6,12 @@ package com.tangem.domain.walletconnect.model.sdkcopy
data class WcSdkSession(
val topic: String,
val appMetaData: WcAppMetaData,
)
val namespaces: Map<String, Session>,
) {
data class Session(
val chains: List<String>,
val accounts: List<String>,
val methods: List<String>,
val events: List<String>,
)
}

View file

@ -11,6 +11,7 @@ import com.tangem.domain.walletconnect.model.WcSessionApprove
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.utils.extensions.mapNotNullValues
sealed class WcAnalyticEvents(
event: String,
@ -103,7 +104,7 @@ sealed class WcAnalyticEvents(
class SignatureRequestReceived(
rawRequest: WcSdkSessionRequest,
network: Network,
emulationStatus: EmulationStatus,
emulationStatus: EmulationStatus?,
) : WcAnalyticEvents(
event = "Signature Request Received",
params = mapOf(
@ -111,8 +112,8 @@ sealed class WcAnalyticEvents(
AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url,
AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method,
AnalyticsParam.Key.BLOCKCHAIN to network.name,
AnalyticsParam.Key.EMULATION_STATUS to emulationStatus.status,
),
AnalyticsParam.Key.EMULATION_STATUS to emulationStatus?.status,
).mapNotNullValues { it.value },
) {
enum class EmulationStatus(val status: String) {
Emulated("Emulated"),

View file

@ -1,12 +1,20 @@
package com.tangem.domain.walletconnect.usecase.method
import arrow.core.Either
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.HandleMethodError
import com.tangem.domain.walletconnect.model.WcRequestError
interface WcAddNetworkUseCase :
WcMethodUseCase,
WcMethodContext {
suspend operator fun invoke(): Either<HandleMethodError, AddNetwork>
suspend fun approve(): Either<WcRequestError, String>
fun reject()
data class AddNetwork(
val network: Network,
val isExistInWcSession: Boolean,
)
}

View file

@ -0,0 +1,18 @@
package com.tangem.domain.walletconnect.usecase.method
import arrow.core.Either
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.HandleMethodError
interface WcSwitchNetworkUseCase :
WcMethodUseCase,
WcMethodContext {
suspend operator fun invoke(): Either<HandleMethodError, SwitchNetwork>
fun reject()
data class SwitchNetwork(
val network: Network,
val isExistInWcSession: Boolean,
)
}

View file

@ -1,15 +0,0 @@
package com.tangem.domain.wallets.usecase
import com.tangem.sdk.api.TangemSdkManager
import javax.inject.Inject
class GetIsBiometricsEnabledUseCase @Inject constructor(
private val tangemSdkManager: TangemSdkManager,
) {
operator fun invoke(): Boolean = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false
fun canUseBiometry(): Boolean {
return tangemSdkManager.canUseBiometry
}
}

View file

@ -53,7 +53,7 @@ internal class DisclaimerModel @Inject constructor(
val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
if (shouldAskPushPermission && !isHuaweiDevice) {
router.push(AppRoute.PushNotification)
router.push(AppRoute.PushNotification(AppRoute.PushNotification.Source.Stories))
} else {
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)

View file

@ -1,5 +1,6 @@
package com.tangem.features.hotwallet.addexistingwallet.entry.routing
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.hotwallet.addexistingwallet.entry.AddExistingWalletModel
@ -63,6 +64,7 @@ internal class AddExistingWalletChildFactory @Inject constructor(
context = childContext,
params = PushNotificationsParams(
modelCallbacks = model.pushNotificationsCallbacks,
source = AppRoute.PushNotification.Source.Onboarding,
),
)
is AddExistingWalletRoute.SetupFinished -> MobileWalletSetupFinishedComponent(

View file

@ -1,12 +1,13 @@
package com.tangem.features.hotwallet.walletactivation.entry.routing
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.hotwallet.accesscode.AccessCodeComponent
import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent
import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartComponent
import com.tangem.features.hotwallet.accesscode.AccessCodeComponent
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
import com.tangem.features.hotwallet.walletactivation.entry.WalletActivationModel
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
@ -72,6 +73,7 @@ internal class WalletActivationChildFactory @Inject constructor(
context = childContext,
params = PushNotificationsParams(
modelCallbacks = model.pushNotificationsCallbacks,
source = AppRoute.PushNotification.Source.Onboarding,
),
)
is WalletActivationRoute.SetupFinished -> MobileWalletSetupFinishedComponent(

View file

@ -93,6 +93,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
actionsFlow = actionsFlow,
coroutineScope = this,
),
// only for onboarding case, change carefully and check repository implementation
loadUserTokensFromRemote = userWalletId != null && source == ManageTokensSource.ONBOARDING,
)

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -12,6 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -73,6 +75,7 @@ internal fun MultiWalletAccessCodeEnter(
label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third),
isError = state.codesNotMatchError,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
caption = when {
state.codesNotMatchError && reEnterAccessCodeState ->
stringResourceSafe(R.string.onboarding_access_codes_doesnt_match)

View file

@ -153,7 +153,7 @@ private fun PinCode(
}
},
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
keyboardType = KeyboardType.NumberPassword,
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(

View file

@ -21,7 +21,6 @@ import com.tangem.domain.tokens.GetAssetRequirementsUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
@ -210,21 +209,22 @@ internal class OnrampTokenListModel @Inject constructor(
val isOperationAvailable = checkAvailabilityByOperation(status = status)
val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation
val isNotLoading = status.value !is CryptoCurrencyStatus.Loading
val requirements = getAssetRequirementsUseCase(
userWalletId = userWallet.walletId,
currency = status.currency,
).getOrNull()
val isNotTrustlineRequired = requirements !is AssetRequirementsCondition.RequiredTrustline
val isAvailableForBuy = rampStateManager.checkAssetRequirements(requirements)
val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable
val isAvailable = when (params.filterOperation) {
OnrampOperation.BUY -> {
isNotTrustlineRequired
isAvailableForBuy
} // unreachable state is available for Buy operation
OnrampOperation.SELL -> isNotUnreachable
OnrampOperation.SWAP -> {
isNotUnreachable && isNotTrustlineRequired
isNotUnreachable && isAvailableForBuy
}
}

View file

@ -6,4 +6,5 @@ data class PushNotificationsParams(
val isBottomSheet: Boolean = false,
val nextRoute: AppRoute? = null,
val modelCallbacks: PushNotificationsModelCallbacks,
val source: AppRoute.PushNotification.Source,
)

View file

@ -53,6 +53,15 @@ sealed class PushNotificationAnalyticEvents(
),
)
data class NotificationsScreenOpened(
val source: AnalyticsParam.ScreensSources,
) : PushNotificationAnalyticEvents(
event = "Push Notification Screen Opened",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
),
)
data class NotificationsEnabled(
val isEnabled: Boolean,
) : PushNotificationAnalyticEvents(

View file

@ -1,6 +1,7 @@
package com.tangem.features.pushnotifications.impl.model
import androidx.compose.runtime.Stable
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
@ -36,6 +37,11 @@ internal class PushNotificationsModel @Inject constructor(
) : Model(), PushNotificationsClickIntents {
val params: PushNotificationsParams = paramsContainer.require()
val source = when (params.source) {
AppRoute.PushNotification.Source.Stories -> AnalyticsParam.ScreensSources.Stories
AppRoute.PushNotification.Source.Main -> AnalyticsParam.ScreensSources.Main
AppRoute.PushNotification.Source.Onboarding -> AnalyticsParam.ScreensSources.Onboarding
}
private val _state = MutableStateFlow(
PushNotificationsUM(
@ -43,6 +49,10 @@ internal class PushNotificationsModel @Inject constructor(
),
)
init {
analyticHandler.send(PushNotificationAnalyticEvents.NotificationsScreenOpened(source))
}
val state = _state.asStateFlow()
override fun onAllowClick() {
@ -51,9 +61,7 @@ internal class PushNotificationsModel @Inject constructor(
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(true)
}
}
analyticHandler.send(
PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories),
)
analyticHandler.send(PushNotificationAnalyticEvents.ButtonAllow(source))
}
override fun onLaterClick() {
@ -62,9 +70,7 @@ internal class PushNotificationsModel @Inject constructor(
notificationsRepository.setUserAllowToSubscribeOnPushNotifications(false)
}
}
analyticHandler.send(
PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories),
)
analyticHandler.send(PushNotificationAnalyticEvents.ButtonLater(source))
modelScope.launch {
neverRequestPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)

View file

@ -408,7 +408,10 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<
isPrimaryButtonEnabled = false,
feeItems = persistentListOf(
FeeItem.Suggested(
title = stringReference("Suggested by Tangem"),
title = resourceReference(
id = R.string.wc_fee_suggested,
formatArgs = wrappedList("Tangem"),
),
fee = Fee.Common(Amount(value = BigDecimal("0.1"), blockchain = Blockchain.Ethereum)),
),
FeeItem.Slow(fee = Fee.Common(Amount(value = BigDecimal("0.01"), blockchain = Blockchain.Ethereum))),

View file

@ -5,9 +5,10 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.Companion.NFT_SEND_CATEGORY
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import javax.inject.Inject
@ -34,7 +35,7 @@ internal class NFTSendAnalyticHelper @Inject constructor(
Basic.TransactionSent(
sentFrom = AnalyticsParam.TxSentFrom.NFT(
blockchain = cryptoCurrency.network.name,
token = cryptoCurrency.symbol,
token = NFT_SEND_CATEGORY, // should send "NFT" in token param
feeType = feeType,
),
memoType = getSendTransactionMemoType(destinationUM?.memoTextField),

View file

@ -38,6 +38,7 @@ internal data class BalanceState(
val validator: Yield.Validator?,
val pendingActions: ImmutableList<PendingAction>,
val isPending: Boolean,
val validatorAddress: String?,
)
@Immutable

View file

@ -68,6 +68,7 @@ internal class BalanceItemConverter(
pendingActions = value.pendingActions.toPersistentList(),
isClickable = value.isClickable(),
isPending = value.isPending,
validatorAddress = value.validatorAddress,
)
}
}

View file

@ -92,6 +92,7 @@ internal class RewardsValidatorStateConverter(
isClickable = true,
type = balance.type,
isPending = balance.isPending,
validatorAddress = balance.validatorAddress,
)
}
}

View file

@ -57,8 +57,10 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
val state = stateController.value
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
?: error("Illegal state")
val validatorState = state.validatorState as? StakingStates.ValidatorState.Data
?: error("No validator provided")
val validatorAddress = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator?.address
?: state.balanceState?.validatorAddress
?: error("No validator address provided")
val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided")
@ -66,8 +68,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
val pendingAction = confirmationState.pendingAction
val pendingActions = confirmationState.pendingActions
val validatorAddress = validatorState.chosenValidator.address
val isEnter = state.actionType is StakingActionCommonType.Enter
val isApprovalNeeded = confirmationState.isApprovalNeeded
val isAllowanceNotEnough = confirmationState.allowance < amount

View file

@ -100,6 +100,7 @@ internal object InitialStakingStatePreview {
type = BalanceType.STAKED,
subtitle = null,
isPending = false,
validatorAddress = "",
),
),
),

View file

@ -29,6 +29,7 @@ import com.tangem.features.staking.impl.presentation.state.converters.RewardsVal
import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter
import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText
import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.isNullOrZero
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.PersistentList
@ -227,6 +228,9 @@ internal class SetInitialDataStateTransformer(
}
private fun getAprRange(validators: List<Yield.Validator>): TextReference {
if (validators.isEmpty()) {
return stringReference(DASH_SIGN)
}
val aprValues = validators
.filter { it.preferred }
.takeIf { it.isNotEmpty() }

View file

@ -33,6 +33,10 @@ internal class ValidatorSelectChangeTransformer(
selectedValidator
}
if (selectedValidator == null && yield.preferredValidators.isEmpty()) {
return prevState
}
return prevState.copy(
validatorState = StakingStates.ValidatorState.Data(
chosenValidator = selectedValidator ?: yield.preferredValidators.first(),

View file

@ -26,11 +26,11 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.feature.swap.converters.*
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.ExpressDataError
@ -55,6 +55,7 @@ internal class DefaultSwapRepository(
private val errorsDataConverter: ErrorsDataConverter,
private val dataSignatureVerifier: DataSignatureVerifier,
private val appPreferencesStore: AppPreferencesStore,
private val rampStateManager: RampStateManager,
moshi: Moshi,
excludedBlockchains: ExcludedBlockchains,
) : SwapRepository {
@ -138,7 +139,8 @@ internal class DefaultSwapRepository(
val currenciesList = currencyList
.filter {
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it)
requirements !is AssetRequirementsCondition.RequiredTrustline
val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements)
isAvailableForSwap
}
.map { leastTokenInfoConverter.convert(it) }

View file

@ -9,6 +9,7 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.DefaultSwapRepository
import com.tangem.feature.swap.DefaultSwapTransactionRepository
@ -38,6 +39,7 @@ internal class SwapDataModule {
@NetworkMoshi moshi: Moshi,
excludedBlockchains: ExcludedBlockchains,
appPreferencesStore: AppPreferencesStore,
rampStateManager: RampStateManager,
): SwapRepository {
return DefaultSwapRepository(
tangemExpressApi = tangemExpressApi,
@ -49,6 +51,7 @@ internal class SwapDataModule {
moshi = moshi,
excludedBlockchains = excludedBlockchains,
appPreferencesStore = appPreferencesStore,
rampStateManager = rampStateManager,
)
}

View file

@ -17,6 +17,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
@ -31,7 +32,6 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -82,6 +82,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase,
private val amountFormatter: AmountFormatter,
private val rampStateManager: RampStateManager,
@Assisted private val userWalletId: UserWalletId,
) : SwapInteractor {
@ -179,12 +180,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo,
): List<SwapProvider>? {
val requirements = getAssetRequirementsUseCase.invoke(userWalletId, cryptoCurrencyStatuses.currency).getOrNull()
val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements)
return swapPairsLeastList.firstNotNullOfOrNull {
val listTokenInfo = tokenInfoForAvailable(it)
if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network &&
cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress &&
requirements !is AssetRequirementsCondition.RequiredTrustline
isAvailableForSwap
) {
it.providers
} else {

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
@ -74,6 +75,7 @@ internal class WalletComponent @AssistedInject constructor(
params = PushNotificationsParams(
isBottomSheet = true,
modelCallbacks = model.askForPushNotificationsModelCallbacks,
source = AppRoute.PushNotification.Source.Main,
),
)
}

View file

@ -13,6 +13,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
@ -84,7 +85,7 @@ internal class WalletModel @Inject constructor(
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
private val notificationsFeatureToggles: NotificationsFeatureToggles,
private val getIsBiometryIsEnabledUseCase: GetIsBiometricsEnabledUseCase,
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
@ -212,9 +213,15 @@ internal class WalletModel @Inject constructor(
modelScope.launch {
val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION)
val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
val isBiometricsEnabled = getIsBiometryIsEnabledUseCase()
val isBiometricsEnabled = shouldSaveUserWalletsSyncUseCase()
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
val shouldShowBottomSheet = shouldAskPermission || afterUpdate
Timber.d(
"push BS afterUpdate: $afterUpdate," +
"shouldAskPermission $shouldAskPermission," +
"isBiometricsEnabled $isBiometricsEnabled," +
"isHuaweiDevice $isHuaweiDevice",
)
if (!isBiometricsEnabled) return@launch
if (isHuaweiDevice) return@launch
if (!shouldShowBottomSheet) return@launch
@ -383,9 +390,11 @@ internal class WalletModel @Inject constructor(
val otherWallets = action.wallets.minus(action.selectedWallet)
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
otherWallets.onEach { userWallet ->
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
}
otherWallets
.filterNot(UserWallet::isLocked)
.onEach { userWallet ->
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
}
}
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
@ -500,6 +509,10 @@ internal class WalletModel @Inject constructor(
clickIntents = clickIntents,
coroutineScope = modelScope,
)
action.unlockedWallets.onEach { userWallet ->
modelScope.launch { fetchWalletContent(userWallet = userWallet) }
}
}
private fun demonstrateWalletsScrollPreview(direction: Direction) {
@ -541,6 +554,8 @@ internal class WalletModel @Inject constructor(
private suspend fun fetchWalletContent(userWallet: UserWallet) {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
if (userWallet.isLocked) return
/*
* Updating the balance of the current wallet is an essential part of InitializationWallets,
* so the coroutine is launched in the current context

View file

@ -5,13 +5,14 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.onramp.FetchHotCryptoUseCase
import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
import com.tangem.domain.tokens.FetchCardTokenListUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
@ -87,7 +88,7 @@ internal class WalletClickIntents @Inject constructor(
stateHolder.update { it.copy(selectedWalletIndex = index) }
maybeUserWallet.onRight {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled && !it.isLocked) {
launch { walletContentFetcher(userWalletId = it.walletId) }
}

View file

@ -38,5 +38,23 @@ internal class AlertsComponent(
@Serializable
data class WcDisconnected(override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class TangemUnsupportedNetwork(
val network: String,
override val onDismiss: () -> Unit,
) : AlertType()
@Serializable
data class RequiredAddNetwork(
val network: String,
override val onDismiss: () -> Unit,
) : AlertType()
@Serializable
data class RequiredReconnectWithNetwork(
val network: String,
override val onDismiss: () -> Unit,
) : AlertType()
}
}

View file

@ -57,7 +57,7 @@ internal class WcPairComponent(
private fun onChildBack() {
when (val config = contentStack.value.active.configuration) {
is WcAppInfoRoutes.AppInfo -> dismiss()
is Alert -> when (config.type) {
is Alert -> when (config.alertType) {
is Alert.Type.UnsupportedDApp,
is Alert.Type.UnsupportedNetwork,
-> dismiss()
@ -85,7 +85,7 @@ internal class WcPairComponent(
)
is Alert -> AlertsComponentV2(
appComponentContext = appComponentContext,
messageUM = createBottomSheetMessageUM(config.type),
messageUM = createBottomSheetMessageUM(config.alertType),
)
is WcAppInfoRoutes.SelectNetworks -> WcSelectNetworksComponent(
appComponentContext = appComponentContext,
@ -114,12 +114,14 @@ internal class WcPairComponent(
return when (alertType) {
is Alert.Type.Verified -> WcAlertsFactory.createVerifiedDomainAlert(alertType.appName)
is Alert.Type.UnknownDomain -> WcAlertsFactory.createUnknownDomainAlert(model::connectFromAlert)
is Alert.Type.InvalidDomain -> WcAlertsFactory.createInvalidDomainAlert(model::errorAlertOnDismiss)
is Alert.Type.UnsafeDomain -> WcAlertsFactory.createUnsafeDomainAlert(model::connectFromAlert)
is Alert.Type.UnsupportedDApp ->
WcAlertsFactory.createUnsupportedDomainAlert(alertType.appName, model::errorAlertOnDismiss)
is Alert.Type.UnsupportedNetwork ->
WcAlertsFactory.createUnsupportedChainAlert(alertType.appName, model::errorAlertOnDismiss)
is Alert.Type.UriAlreadyUsed -> WcAlertsFactory.createUriAlreadyUsedAlert(model::errorAlertOnDismiss)
is Alert.Type.TimeoutException -> WcAlertsFactory.createTimeoutExceptionAlert(model::errorAlertOnDismiss)
}
}

View file

@ -17,6 +17,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
@ -69,8 +70,9 @@ internal class WcPairModel @Inject constructor(
val stackNavigation = StackNavigation<WcAppInfoRoutes>()
private val selectedUserWalletFlow =
private val selectedUserWalletFlow: MutableStateFlow<UserWallet> by lazy {
MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId })
}
private var proposalNetwork by Delegates.notNull<WcSessionProposal.ProposalNetwork>()
private var sessionProposal by Delegates.notNull<WcSessionProposal>()
private var additionallyEnabledNetworks = setOf<Network>()
@ -214,15 +216,11 @@ internal class WcPairModel @Inject constructor(
private fun processError(error: WcPairError) {
val alert = when (error) {
is WcPairError.UnsupportedDApp -> {
WcAppInfoRoutes.Alert.Type.UnsupportedDApp(error.appName)
}
is WcPairError.UnsupportedBlockchains -> {
WcAppInfoRoutes.Alert.Type.UnsupportedNetwork(error.appName)
}
is WcPairError.UriAlreadyUsed -> {
WcAppInfoRoutes.Alert.Type.UriAlreadyUsed
}
is WcPairError.InvalidDomainURL -> WcAppInfoRoutes.Alert.Type.InvalidDomain
is WcPairError.UnsupportedDApp -> WcAppInfoRoutes.Alert.Type.UnsupportedDApp(error.appName)
is WcPairError.UnsupportedBlockchains -> WcAppInfoRoutes.Alert.Type.UnsupportedNetwork(error.appName)
is WcPairError.UriAlreadyUsed -> WcAppInfoRoutes.Alert.Type.UriAlreadyUsed
is WcPairError.TimeoutException -> WcAppInfoRoutes.Alert.Type.TimeoutException
else -> {
messageSender.send(ToastMessage(message = stringReference(error.message)))
router.pop()

View file

@ -26,15 +26,17 @@ internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent, Route {
) : WcAppInfoRoutes()
@Serializable
data class Alert(val type: Type) : WcAppInfoRoutes() {
data class Alert(val alertType: Type) : WcAppInfoRoutes() {
@Serializable
sealed class Type {
data class Verified(val appName: String) : Type()
data object UnknownDomain : Type()
data object UnsafeDomain : Type()
data object InvalidDomain : Type()
data class UnsupportedDApp(val appName: String) : Type()
data class UnsupportedNetwork(val appName: String) : Type()
data object UriAlreadyUsed : Type()
data object TimeoutException : Type()
}
}
}

View file

@ -18,8 +18,10 @@ import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.FeeSelectorComponent
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.features.walletconnect.connections.components.AlertsComponent
import com.tangem.features.walletconnect.connections.components.AlertsComponent.AlertType.*
import com.tangem.features.walletconnect.connections.components.WcPairComponent
import com.tangem.features.walletconnect.transaction.components.chain.WcAddNetworkContainerComponent
import com.tangem.features.walletconnect.transaction.components.chain.WcSwitchNetworkComponent
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.components.send.WcSendTransactionContainerComponent
import com.tangem.features.walletconnect.transaction.components.sign.WcSignTransactionContainerComponent
@ -71,6 +73,10 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
appComponentContext = childContext,
params = WcTransactionModelParams(config.rawRequest),
)
is WcInnerRoute.SwitchNetwork -> WcSwitchNetworkComponent(
appComponentContext = childContext,
params = WcTransactionModelParams(config.rawRequest),
)
is WcInnerRoute.Send -> WcSendTransactionContainerComponent(
appComponentContext = childContext,
params = WcTransactionModelParams(config.rawRequest),
@ -88,13 +94,31 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
is WcInnerRoute.UnsupportedMethodAlert -> AlertsComponent(
childContext,
AlertsComponent.Params(
alertType = AlertsComponent.AlertType.UnsupportedMethod { model.innerRouter.pop() },
alertType = UnsupportedMethod { model.innerRouter.pop() },
),
)
is WcInnerRoute.WcDappDisconnected -> AlertsComponent(
childContext,
AlertsComponent.Params(
alertType = AlertsComponent.AlertType.WcDisconnected { model.innerRouter.pop() },
alertType = WcDisconnected { model.innerRouter.pop() },
),
)
is WcInnerRoute.TangemUnsupportedNetwork -> AlertsComponent(
childContext,
AlertsComponent.Params(
alertType = TangemUnsupportedNetwork(config.networkName) { model.innerRouter.pop() },
),
)
is WcInnerRoute.RequiredAddNetwork -> AlertsComponent(
childContext,
AlertsComponent.Params(
alertType = RequiredAddNetwork(config.networkName) { model.innerRouter.pop() },
),
)
is WcInnerRoute.RequiredReconnectWithNetwork -> AlertsComponent(
childContext,
AlertsComponent.Params(
alertType = RequiredReconnectWithNetwork(config.networkName) { model.innerRouter.pop() },
),
)
}

View file

@ -22,6 +22,9 @@ internal sealed interface WcInnerRoute : Route {
@Serializable
data class AddNetwork(override val rawRequest: WcSdkSessionRequest) : Method
@Serializable
data class SwitchNetwork(override val rawRequest: WcSdkSessionRequest) : Method
@Serializable
data class Pair(val request: WcPairRequest) : WcInnerRoute
@ -30,4 +33,13 @@ internal sealed interface WcInnerRoute : Route {
@Serializable
data object WcDappDisconnected : WcInnerRoute
@Serializable
data class TangemUnsupportedNetwork(val networkName: String) : WcInnerRoute
@Serializable
data class RequiredAddNetwork(val networkName: String) : WcInnerRoute
@Serializable
data class RequiredReconnectWithNetwork(val networkName: String) : WcInnerRoute
}

View file

@ -51,6 +51,8 @@ internal class WcRoutingModel @Inject constructor(
-> WcInnerRoute.SignMessage(rawRequest)
WcEthMethodName.AddEthereumChain,
-> WcInnerRoute.AddNetwork(rawRequest)
WcEthMethodName.SwitchEthereumChain,
-> WcInnerRoute.SwitchNetwork(rawRequest)
WcEthMethodName.SignTransaction,
WcEthMethodName.SendTransaction,
WcSolanaMethodName.SignTransaction,

View file

@ -87,7 +87,11 @@ private fun ButtonsContainer(alert: AlertsComponent.AlertType, modifier: Modifie
) {
val buttonsModifier = Modifier.fillMaxWidth()
when (alert) {
is AlertsComponent.AlertType.UnsupportedMethod -> SecondaryButton(
is AlertsComponent.AlertType.UnsupportedMethod,
is AlertsComponent.AlertType.RequiredAddNetwork,
is AlertsComponent.AlertType.RequiredReconnectWithNetwork,
is AlertsComponent.AlertType.TangemUnsupportedNetwork,
-> SecondaryButton(
modifier = buttonsModifier,
onClick = alert.onDismiss,
text = stringResourceSafe(R.string.balance_hidden_got_it_button),
@ -105,16 +109,28 @@ private fun ButtonsContainer(alert: AlertsComponent.AlertType, modifier: Modifie
private fun AlertIcon(alert: AlertsComponent.AlertType, modifier: Modifier = Modifier) {
val color = when (alert) {
is AlertsComponent.AlertType.WcDisconnected -> TangemTheme.colors.icon.informative
is AlertsComponent.AlertType.UnsupportedMethod -> TangemTheme.colors.icon.attention
is AlertsComponent.AlertType.UnsupportedMethod,
is AlertsComponent.AlertType.RequiredAddNetwork,
is AlertsComponent.AlertType.RequiredReconnectWithNetwork,
is AlertsComponent.AlertType.TangemUnsupportedNetwork,
-> TangemTheme.colors.icon.attention
}
@DrawableRes val drawableId = when (alert) {
is AlertsComponent.AlertType.WcDisconnected -> R.drawable.ic_wallet_connect_24
is AlertsComponent.AlertType.UnsupportedMethod -> R.drawable.img_attention_20
is AlertsComponent.AlertType.UnsupportedMethod,
is AlertsComponent.AlertType.RequiredAddNetwork,
is AlertsComponent.AlertType.RequiredReconnectWithNetwork,
is AlertsComponent.AlertType.TangemUnsupportedNetwork,
-> R.drawable.img_attention_20
}
val iconTint = when (alert) {
is AlertsComponent.AlertType.WcDisconnected -> color
is AlertsComponent.AlertType.UnsupportedMethod -> Color.Unspecified
is AlertsComponent.AlertType.UnsupportedMethod,
is AlertsComponent.AlertType.RequiredAddNetwork,
is AlertsComponent.AlertType.RequiredReconnectWithNetwork,
is AlertsComponent.AlertType.TangemUnsupportedNetwork,
-> Color.Unspecified
}
Box(
modifier = modifier
@ -138,6 +154,9 @@ private fun AlertContentTitle(alert: AlertsComponent.AlertType, modifier: Modifi
@StringRes val titleRes: Int = when (alert) {
is AlertsComponent.AlertType.WcDisconnected -> R.string.wc_alert_session_disconnected_title
is AlertsComponent.AlertType.UnsupportedMethod -> R.string.wc_alert_unsupported_method_title
is AlertsComponent.AlertType.RequiredAddNetwork -> R.string.wc_alert_add_network_to_portfolio_title
is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> R.string.wc_alert_network_not_connected_title
is AlertsComponent.AlertType.TangemUnsupportedNetwork -> R.string.wc_alert_unsupported_network_title
}
Text(
modifier = modifier,
@ -157,6 +176,18 @@ private fun AlertContentDescription(alert: AlertsComponent.AlertType, modifier:
is AlertsComponent.AlertType.UnsupportedMethod -> stringResourceSafe(
R.string.wc_alert_unsupported_method_description,
)
is AlertsComponent.AlertType.RequiredAddNetwork -> stringResourceSafe(
R.string.wc_alert_add_network_to_portfolio_description,
alert.network,
)
is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> stringResourceSafe(
R.string.wc_alert_network_not_connected_description,
alert.network,
)
is AlertsComponent.AlertType.TangemUnsupportedNetwork -> stringResourceSafe(
R.string.wc_alert_unsupported_network_description,
alert.network,
)
}
Text(
modifier = modifier,
@ -188,5 +219,8 @@ private class AlertTypesProvider : CollectionPreviewParameterProvider<AlertsComp
collection = listOf(
AlertsComponent.AlertType.WcDisconnected(onDismiss = {}),
AlertsComponent.AlertType.UnsupportedMethod(onDismiss = {}),
AlertsComponent.AlertType.TangemUnsupportedNetwork(network = "Solana", onDismiss = {}),
AlertsComponent.AlertType.RequiredAddNetwork(network = "Solana", onDismiss = {}),
AlertsComponent.AlertType.RequiredReconnectWithNetwork(network = "Solana", onDismiss = {}),
),
)

View file

@ -48,6 +48,24 @@ internal object WcAlertsFactory {
}
}
fun createInvalidDomainAlert(onDismiss: () -> Unit): MessageBottomSheetUMV2 {
return messageBottomSheetUM {
infoBlock {
icon(R.drawable.ic_wallet_connect_24) {
type = Type.Informative
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.wc_errors_invalid_domain_title)
body = resourceReference(R.string.wc_errors_invalid_domain_subtitle)
}
primaryButton {
text = resourceReference(R.string.common_got_it)
onClick { onDismiss() }
}
onDismissRequest = onDismiss
}
}
fun createUnsafeDomainAlert(activeButtonOnClick: (() -> Unit)? = null): MessageBottomSheetUMV2 {
return messageBottomSheetUM {
infoBlock {
@ -109,6 +127,23 @@ internal object WcAlertsFactory {
}
}
fun createTimeoutExceptionAlert(onDismiss: () -> Unit): MessageBottomSheetUMV2 {
return messageBottomSheetUM {
infoBlock {
icon(R.drawable.ic_wallet_connect_24) {
type = Type.Informative
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.wc_alert_request_timeout_title)
body = resourceReference(R.string.wc_alert_request_timeout_description)
}
primaryButton {
text = resourceReference(R.string.common_got_it)
onClick { onDismiss() }
}
}
}
fun createUnsupportedChainAlert(appName: String, onDismiss: () -> Unit): MessageBottomSheetUMV2 {
return messageBottomSheetUM {
infoBlock {

Some files were not shown because too many files have changed in this diff Show more