Updated on 2026-08-14
This commit is contained in:
commit
fae450669c
122 changed files with 1494 additions and 339 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -411,6 +411,7 @@ internal class ChildFactory @Inject constructor(
|
|||
context = context,
|
||||
params = PushNotificationsParams(
|
||||
modelCallbacks = PushNotificationsModelCallbacksStub(),
|
||||
source = route.source,
|
||||
nextRoute = AppRoute.Home(),
|
||||
),
|
||||
componentFactory = pushNotificationsComponentFactory,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 n’est pas ajouté à votre portefeuille. Ajoutez-le d’abord, 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 d’attente 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, n’hé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 d’activités suspectes.%s</string>
|
||||
<string name="wc_alert_unsupported_network_description">Le réseau %s n’est 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 d’activité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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">You’re 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 Tangem’s best-in-class hardware wallet.</string>
|
||||
<string name="hw_upgrade_to_cold_banner_title">Upgrade wallet with a hardware
backup</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>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fun PinTextField(
|
|||
}
|
||||
.focusRequester(focusRequester),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number,
|
||||
keyboardType = KeyboardType.NumberPassword,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
singleLine = true,
|
||||
|
|
|
|||
17
core/ui/src/main/res/drawable/img_approvale_new_24.xml
Normal file
17
core/ui/src/main/res/drawable/img_approvale_new_24.xml
Normal 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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
pairingTopic = "",
|
||||
name = "",
|
||||
description = "",
|
||||
url = "",
|
||||
url = "https://react-app.walletconnect.com/",
|
||||
icons = listOf(),
|
||||
redirect = "",
|
||||
requiredNamespaces = mapOf(),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ internal class WcSignUseCaseDelegateTest {
|
|||
connectingTime = 0L,
|
||||
sdkModel = WcSdkSession(
|
||||
topic = "",
|
||||
namespaces = mapOf(),
|
||||
appMetaData = WcAppMetaData(
|
||||
name = "",
|
||||
description = "",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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>,
|
||||
)
|
||||
}
|
||||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ private fun PinCode(
|
|||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number,
|
||||
keyboardType = KeyboardType.NumberPassword,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ data class PushNotificationsParams(
|
|||
val isBottomSheet: Boolean = false,
|
||||
val nextRoute: AppRoute? = null,
|
||||
val modelCallbacks: PushNotificationsModelCallbacks,
|
||||
val source: AppRoute.PushNotification.Source,
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ internal data class BalanceState(
|
|||
val validator: Yield.Validator?,
|
||||
val pendingActions: ImmutableList<PendingAction>,
|
||||
val isPending: Boolean,
|
||||
val validatorAddress: String?,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ internal class BalanceItemConverter(
|
|||
pendingActions = value.pendingActions.toPersistentList(),
|
||||
isClickable = value.isClickable(),
|
||||
isPending = value.isPending,
|
||||
validatorAddress = value.validatorAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ internal class RewardsValidatorStateConverter(
|
|||
isClickable = true,
|
||||
type = balance.type,
|
||||
isPending = balance.isPending,
|
||||
validatorAddress = balance.validatorAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ internal object InitialStakingStatePreview {
|
|||
type = BalanceType.STAKED,
|
||||
subtitle = null,
|
||||
isPending = false,
|
||||
validatorAddress = "",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = {}),
|
||||
),
|
||||
)
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue