From 069ff2f4e1287ace294f6c4cb27cc5b54f2c2d2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Aug 2025 11:39:32 +0500 Subject: [PATCH 01/40] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../network/exchangeServices/DefaultRampManager.kt | 13 +++++++++++++ .../com/tangem/domain/exchange/RampStateManager.kt | 7 +++++++ .../onramp/tokenlist/model/OnrampTokenListModel.kt | 8 ++++---- .../tangem/feature/swap/DefaultSwapRepository.kt | 6 ++++-- .../com/tangem/feature/swap/di/SwapDataModule.kt | 3 +++ .../feature/swap/domain/SwapInteractorImpl.kt | 6 ++++-- 7 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 040b46b637..68ed0730c5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -98,6 +98,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) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index be5a5a9705..6b10fa0d44 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -18,6 +18,7 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.model.CryptoCurrencyStatus 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, diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index dd4d2ae6d7..f48f6aadaf 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -7,11 +7,13 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.model.CryptoCurrencyStatus 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 } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 8a3bd36ae7..6a5103bcfa 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -22,7 +22,6 @@ import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenList -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 @@ -212,21 +211,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 } } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index a23bc00f4b..a7faad5bca 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -25,10 +25,10 @@ import com.tangem.datasource.api.express.models.response.TxDetails import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore +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.transaction.models.AssetRequirementsCondition import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.swap.converters.* @@ -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) } diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index 679015d755..8dbd0c2597 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -8,6 +8,7 @@ import com.tangem.datasource.api.express.models.response.ExpressErrorResponse import com.tangem.datasource.crypto.DataSignatureVerifier import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.swap.DefaultSwapRepository @@ -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, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 712d322bd4..1e0209b1e3 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -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.network.Network import com.tangem.domain.models.quote.QuoteStatus @@ -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? { 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 { From 25086c03af6316ea404029713ecd7dee298d41b3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Aug 2025 17:11:11 +0400 Subject: [PATCH 02/40] Updated on 2026-08-14 --- .../domain/tokens/operations/BaseCurrencyStatusOperations.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 80744461c7..10a27ab90d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -369,7 +369,8 @@ abstract class BaseCurrencyStatusOperations( multiWalletCryptoCurrenciesSupplier.getSyncOrNull( params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId), ) - ?.firstOrNull { it.network.id == networkId && it.network.derivationPath == derivationPath } + ?.filterIsInstance() + ?.firstOrNull { it.network.id == networkId } ?: error("Unable to create network coin with ID: $networkId and derivation path: $derivationPath") } else { currenciesRepository.getNetworkCoin(userWalletId, networkId, derivationPath) From bc760834966185ea94f5604a815d598501c5ba5b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Aug 2025 12:55:01 +0400 Subject: [PATCH 03/40] Updated on 2026-08-14 --- .../domain/tokens/operations/BaseCurrencyStatusOperations.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 10a27ab90d..6e242d03c6 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -371,7 +371,7 @@ abstract class BaseCurrencyStatusOperations( ) ?.filterIsInstance() ?.firstOrNull { it.network.id == networkId } - ?: error("Unable to create network coin with ID: $networkId and derivation path: $derivationPath") + ?: error("Unable to create network coin with ID: $networkId") } else { currenciesRepository.getNetworkCoin(userWalletId, networkId, derivationPath) } From e79bbf29adfa54cce5ddaa31a3030e32baeb9ca0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Aug 2025 17:27:54 +0700 Subject: [PATCH 04/40] Updated on 2026-08-14 --- .../features/walletconnect/connections/model/WcPairModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt index 9a63c9baad..c1dcb8cf95 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt @@ -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() - private val selectedUserWalletFlow = + private val selectedUserWalletFlow: MutableStateFlow by lazy { MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }) + } private var proposalNetwork by Delegates.notNull() private var sessionProposal by Delegates.notNull() private var additionallyEnabledNetworks = setOf() From ee69ff5fb31f58936366030d3f6c4f9421d8b13e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Aug 2025 16:52:39 +0500 Subject: [PATCH 05/40] Updated on 2026-08-14 --- .../sign/BlockAidChainNameConverter.kt | 29 +++++----- .../utils/BlockAidVerificationDelegate.kt | 53 +++++++++++-------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt index 3421317947..c8491f5be6 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BlockAidChainNameConverter.kt @@ -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 { +internal object BlockAidChainNameConverter : Converter { @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 } } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt index 4a774ac505..eb03530b0a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt @@ -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 = flow { - emit(Lce.Loading(partialContent = null)) val failedResult = CheckTransactionResult( validation = ValidationResult.FAILED_TO_VALIDATE, simulation = SimulationResult.FailedToSimulate, @@ -36,7 +34,13 @@ 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 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 +49,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 = 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)) + }, + ) } } \ No newline at end of file From 4f88f3a7b01c2c01f3d3b854fb8df4b4a677e6f9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 8 Aug 2025 12:02:05 +0500 Subject: [PATCH 06/40] Updated on 2026-08-14 --- .../domain/walletconnect/WcAnalyticEvents.kt | 7 ++++--- .../transaction/model/WcAddNetworkModel.kt | 16 ++++++++++++++++ .../transaction/model/WcSignTransactionModel.kt | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index d4bff77def..e3f4cf7299 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -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"), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 1684d836c3..107e983abc 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -49,12 +49,14 @@ internal class WcAddNetworkModel @Inject constructor( private val params = paramsContainer.require() private var useCase by Delegates.notNull() + private val signatureReceivedAnalyticsSendState = MutableStateFlow(false) init { modelScope.launch { useCase = useCaseFactory.createUseCase(params.rawRequest) .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch + sendSignatureReceivedAnalytics(useCase) _uiState.emit( wcAddEthereumChainUMConverter.convert( WcAddEthereumChainUMConverter.Input( @@ -114,4 +116,18 @@ internal class WcAddNetworkModel @Inject constructor( private fun copyData(text: String) { clipboardManager.setText(text = text, isSensitive = true) } + + private fun sendSignatureReceivedAnalytics(useCase: WcAddNetworkUseCase) { + if (signatureReceivedAnalyticsSendState.value) return + + analytics.send( + WcAnalyticEvents.SignatureRequestReceived( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + emulationStatus = null, + ), + ) + + signatureReceivedAnalyticsSendState.value = true + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index 1283f3e84f..ccc4413ef4 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -59,12 +59,14 @@ internal class WcSignTransactionModel @Inject constructor( val stackNavigation = StackNavigation() private var useCase by Delegates.notNull() + private val signatureReceivedAnalyticsSendState = MutableStateFlow(false) init { modelScope.launch { useCase = useCaseFactory.createUseCase(params.rawRequest) .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch + sendSignatureReceivedAnalytics(useCase) useCase.invoke() .onEach { signState -> if (signingIsDone(signState)) return@onEach @@ -145,4 +147,18 @@ internal class WcSignTransactionModel @Inject constructor( private fun copyData(text: String) { clipboardManager.setText(text = text, isSensitive = true) } + + private fun sendSignatureReceivedAnalytics(useCase: WcMessageSignUseCase) { + if (signatureReceivedAnalyticsSendState.value) return + + analytics.send( + WcAnalyticEvents.SignatureRequestReceived( + rawRequest = useCase.rawSdkRequest, + network = useCase.network, + emulationStatus = null, + ), + ) + + signatureReceivedAnalyticsSendState.value = true + } } \ No newline at end of file From 9b4ac6e5f8d99c884f64b1e500563bbdb6b730fa Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 8 Aug 2025 12:31:59 +0500 Subject: [PATCH 07/40] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-es/strings.xml | 6 +++--- core/res/src/main/res/values-fr/strings.xml | 2 ++ core/res/src/main/res/values-ja/strings.xml | 12 ++++++++++- core/res/src/main/res/values-ru/strings.xml | 20 +++++++++++++++++-- .../src/main/res/values-uk-rUA/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 15 ++++++++++---- ...AddEthereumChainModalBottomSheetContent.kt | 2 +- .../send/WcSendTransactionModalBottomSheet.kt | 2 +- ...cSignTransactionModalBottomSheetContent.kt | 2 +- 10 files changed, 51 insertions(+), 14 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index aa10a37bfa..673c284024 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1434,7 +1434,7 @@ Neue Verbindung Verbinde Deine Wallet mit einer anderen dApp Keine Sitzungen - Diese Domain wird von mehreren Sicherheitsanbietern als unsicher eingestuft. Verlasse diese umgehend, um Dein Vermögen zu schützen. + Es wurden potenzielle Risiken oder bösartiges Verhalten erkannt. Das Verbinden oder Signieren von Transaktionen kann zum Verlust von Geldern führen. Bekanntes Sicherheitsrisiko Anfrage von Art der Signatur diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 6f67b2c033..f17262fd95 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1268,8 +1268,8 @@ Actualmente, MATIC está migrando a POL. Sin embargo, no se ha fijado ninguna fecha límite y MATIC aún no está obsoleto. Puede seguir usando el token MATIC de forma segura o utilizar intercambios para cambiarlo por POL. Migración de MATIC a POL - Use su tarjeta o anillo para obtener una dirección para la red - Use su tarjeta o anillo para obtener direcciónes para la red + Use su tarjeta o anillo para obtener una dirección para la red %d + Use su tarjeta o anillo para obtener direcciónes para las redes %d Faltan algunas direcciones La red no está disponible actualmente. Por favor, inténtalo de nuevo más tarde. @@ -1341,7 +1341,7 @@ Transacción maliciosa Añada la red %s a su perfil para esta billetera La billetera no tiene las redes requeridas - Este dominio está marcado como no seguro por varios proveedores de seguridad. Salga de inmediato para proteger sus activos + Se han detectado riesgos potenciales o comportamiento malicioso. Conectarse o firmar transacciones puede resultar en la pérdida de fondos. Riesgo de seguridad conocido Solicitud de Tipo de firma diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index a4140af715..bbf154df4e 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1320,6 +1320,7 @@ Adresse Chargement Illimité + Réseaux connectés Connexions Contenu Copier les données @@ -1332,6 +1333,7 @@ Nouvelle connexion Connectez votre portefeuille à différentes dApps Aucune séance + 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. Demande de Type de signature À diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index a785951f19..cf05e40cb9 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -236,6 +236,7 @@ 拒否 リロード 名前を変更 + 必須 保存 変更内容を保存 検索 @@ -392,6 +393,7 @@ プロバイダー ベストレート FCA警告リスト + FCA警告リストに掲載されたプロバイダー 最大 %s まで使用可能 %s 以上で利用可能 このペアは利用できません @@ -1302,9 +1304,11 @@ Tangemウォレットを見る このウォレットを保護するためのシークレットコードです。ログインと署名に使用されます。 アクセスコードの設定 / 変更 + アクセスコードの変更 ウォレットの受信取引とTangemの更新について通知を受け取る。 現在、Huaweiデバイスではプッシュ通知が機能しない可能性があります。現在、解決策の検討に取り組んでおり、今後のアップデートで修正をリリースする予定です。ご理解のほどよろしくお願いいたします。 取引通知 + アクセスコードを設定する ウォレット設定 Tangem %sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください @@ -1406,7 +1410,7 @@ WalletConnectを確立できませんでした このドメインは検証できません。承認前にリクエスト内容をよく確認してください。 ブラウザに戻り、WalletConnect経由で再接続してください。 - WalletConnectセッションが接続解除されました + Wallet Connectセッションが接続解除されました とにかくサインする エラーコード: %s 。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 @@ -1421,6 +1425,7 @@ 検証済みドメイン アプリで間違ったカードまたはリングが選択されました 問題が起きています + すべてのdAppが接続解除されました 使用を許可する アドレス 接続する @@ -1431,6 +1436,7 @@ ウォレット 接続されたアプリ 接続されたネットワーク + %1$sへ接続済み ウォレットの残高とアクティビティを表示する 通知なしに取引に署名する 取引の承認をリクエストする @@ -1441,6 +1447,7 @@ 内容 データをコピー 使用可能量の設定 + dAppが接続解除されました すべての接続を解除する すべてのdAppsの接続解除に関するテキスト すべてのdAppを接続解除する @@ -1448,6 +1455,8 @@ 取引をシミュレーションできませんでした。注意して続行してください。 %sでは見積もりはサポートされていません %sによる提案 + ネットワーク手数料をカバーするために残高を補充してください + %1$sが不足しています 悪意のある取引 このウォレットのポートフォリオに%sネットワークを追加します ウォレットに必要なネットワークはありません @@ -1462,6 +1471,7 @@ 署名タイプ dApp接続には、少なくとも1つのネットワークが必要です 選択したネットワークを指定する + 署名に成功しました 宛先 取引リクエスト 取引リクエスト diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 68950a67cc..10a53f974e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1304,15 +1304,18 @@ Верифицированный домен Выбрана не верная карта или кольцо Похоже, возникла проблема + Все dapps отключены Разрешение на использование Адрес Подключение Загрузка Сеть Сети - Безлимитно + Без лимитно Кошелек + Подключенное приложение Подключенные сети + Подключено к %1$s Посмотреть баланс кошелька и его активность Подписать транзакцию без вашего участия Запрос разрешения на транзакцию @@ -1323,25 +1326,38 @@ Вложение Копировать данные Настраиваемый лимит + dApp отключен Отключить все Отключить все dApp Предварительные изменения Не удалось выполнить симуляцию транзакции. Пожалуйста, действуйте с осторожностью. + Оценка не поддерживается для %s + Предложено %s + Пополните ваш баланс, чтобы покрыть комиссию сети + Недостаточно %1$s Вредоносная транзакция Добавьте сеть %s в ваш портфель для выбранного кошелька В кошельке не добавлены необходимые сети Новое подключение Подключите свой кошелек к различным dApp Нет подключений - Этот домен помечен как небезопасный несколькими поставщиками систем безопасности. Немедленно покиньте его, чтобы защитить свои активы. + Изменения в кошельке не обнаружены. + Обнаружены потенциальные риски или вредоносное поведение. Подключение или подписание транзакций может привести к потере средств. Известный риск безопасности Запрос от + Подписать всё равно Тип подписи + Для подключения к dApp требуется как минимум одна выбранная сеть. + Укажите выбранные сети + Успешно подписано На Запрос транзакции Запрос транзакции Безлимитное количество + Убедитесь, что каждая попытка сопряжения использует новый и уникальный URI. + URI уже используется Подключение кошелька + Подозрительная транзакция Отказаться Вы не закончили резервное копирование. Хотите продолжить? Да, возобновить diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index bc736e28ba..9c820fac97 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1286,10 +1286,12 @@ Обрана не вірна картка або кільце Адреса Підключення + Підключені мережі Переглянути баланс гаманця та активність Запит на підключення Вміст Копіювати дані + Виявлено потенційні ризики або шкідливу активність. Підключення чи підпис транзакцій можуть призвести до втрати коштів. Запит від Тип підпису До diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 039b9d2cdb..4c933aff60 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -58,12 +58,15 @@ Tokens in %1$s network are not supported by this card or ring due to firmware limitation. Are you having difficulty scanning your card or ring? This card is not designed to work with this app + Use %1$s to quickly and securely unlock your wallet and authorize all sensitive actions, such as signing transactions. For hardware wallets, you will still need a card to sign. Default Fee Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. Go to settings to enable biometric authentication in the Tangem App Enable biometric authentication This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. Removing the saved devices deletes all the saved wallets and their access codes from the app. + Require Access Code + This option disables biometric authentication for sensitive actions. You will be required to enter your access code every time, such as when signing a transaction. Save Access Code Biometric authentication will be requested instead of the access code for interactions with your card or ring. Keep the wallet in the app @@ -188,6 +191,7 @@ days Delete + Disable Disabled Disconnect Done @@ -340,6 +344,7 @@ Details Check your internet connection or switch to a different network Terms of service + Receive assets Sending assets in other networks will result in permanent loss. %s network Send funds using only @@ -400,6 +405,7 @@ Provider Best rate FCA Warning List + Provider in FCA warning list Available up to %s Available from %s Unavailable for this pair @@ -1478,7 +1484,7 @@ Failed to establish WalletConnect This domain cannot be verified. Check the request carefully approving. Please return to your browser and reconnect via WalletConnect. - WalletConnect session was disconnected + Wallet Connect session was disconnected Sign anyway Error code: %s. If the problem persists — feel free to contact our support. If the problem persists — feel free to contact our support. @@ -1488,7 +1494,7 @@ Error code: 8 005. If the problem persists — feel free to contact our support. We\'ve encountered unknown error Tangem does not currently support a required network by %s. - Unsuported networks + Unsupported networks Tangem support a required network by %s Verified domain Wrong card or ring selected in the App @@ -1532,14 +1538,15 @@ Connect your wallet to a different dApps No sessions No wallet changes detected - This domain is flagged as unsafe by multiple security providers. Leave immediately to protect your assets + Potential risks or malicious behavior have been detected. Connecting or signing transactions may lead to loss of funds. Known security risk Request from - Send anyway + Sign anyway Signature Type At least one network is required for dApp connection Specify selected networks Successfully signed + Wallet connect To Transaction request Transaction request diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt index e835f5ad87..36f2195df9 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt @@ -52,7 +52,7 @@ internal fun WcAddEthereumChainModalBottomSheetContent( onBack = onBack, title = { TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = resourceReference(R.string.wc_transaction_flow_title), endIconRes = R.drawable.ic_close_24, onEndClick = onDismiss, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index cf5f3fee9f..ec1400b0df 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -71,7 +71,7 @@ internal fun WcSendTransactionModalBottomSheet( containerColor = TangemTheme.colors.background.tertiary, title = { config -> TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = resourceReference(R.string.wc_transaction_flow_title), endIconRes = R.drawable.ic_close_24, onEndClick = onDismiss, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt index 86e7fad3f4..21dd6eb5a9 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt @@ -50,7 +50,7 @@ internal fun WcSignTransactionModalBottomSheetContent( onBack = onBack, title = { TangemModalBottomSheetTitle( - title = resourceReference(R.string.wc_wallet_connect), + title = resourceReference(R.string.wc_transaction_flow_title), endIconRes = R.drawable.ic_close_24, onEndClick = onDismiss, ) From ae33c00aaeba91afe2dbf8dcc5505c29183ef725 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 8 Aug 2025 18:35:53 +0500 Subject: [PATCH 08/40] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 7ab02a4628..6a3d13307a 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.27.0-1126" +tangemBlockchainSdk = "releases-5.27.0-1132" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.27.0-510" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 85cff6b0d323533df3e52145b08137465736885f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Aug 2025 14:42:09 +0500 Subject: [PATCH 09/40] Updated on 2026-08-14 --- .../api/common/blockaid/BlockAidApi.kt | 3 +- .../request/SolanaTransactionScanRequest.kt | 2 +- .../response/SolanaTransactionResponse.kt | 58 ++++++++++++++++ .../tangem/data/blockaid/BlockAidMapper.kt | 68 ++++++++++++++++++- .../blockaid/DefaultBlockAidRepository.kt | 25 ++++--- .../blockaid/DefaultBlockAidRepositoryTest.kt | 3 +- .../utils/BlockAidVerificationDelegate.kt | 10 ++- .../walletconnect/model/WcSolanaMethod.kt | 15 +++- .../WcEstimatedWalletChangeUMConverter.kt | 5 +- .../WcSendAndReceiveBlockAidUiConverter.kt | 2 +- 10 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt index 605bc8a391..cdb6ec04e3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/BlockAidApi.kt @@ -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 } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt index 232343cf53..e89428b6a6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/SolanaTransactionScanRequest.kt @@ -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 = listOf("simulation", "validation"), @Json(name = "metadata") val metadata: TransactionMetadata, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt new file mode 100644 index 0000000000..d98f56ee5d --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/SolanaTransactionResponse.kt @@ -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, +) + +@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, +) \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt index 128d1cfd56..e61f8ddd21 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt @@ -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, @@ -89,6 +112,49 @@ internal object BlockAidMapper { } } + private fun mapToSolanaAssetsDiffs(assetsDiffs: List): 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, + ), + ) + } + 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?): SimulationResult { val amounts = exposures?.flatMap { exposure -> val tokenInfo = TokenInfo( diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt index 66483ed2df..985e8d7e2e 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt @@ -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) + } } \ No newline at end of file diff --git a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt index d125246bce..54d8331f1f 100644 --- a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt +++ b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepositoryTest.kt @@ -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() - val response = mockk() + val response = mockk() val expectedResult = mockk() every { mapper.mapToSolanaRequest(data) } returns request diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt index eb03530b0a..8bc9a0c492 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/BlockAidVerificationDelegate.kt @@ -40,6 +40,14 @@ internal class BlockAidVerificationDelegate @Inject constructor( 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) @@ -59,7 +67,7 @@ internal class BlockAidVerificationDelegate @Inject constructor( data = TransactionData( chain = chain, accountAddress = accountAddress, - method = rawSdkRequest.request.method, + method = methodName, domainUrl = session.sdkModel.appMetaData.url, params = params, ), diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt index 0042025dfb..d44d98c88f 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSolanaMethod.kt @@ -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, - ) : WcSolanaMethod + ) : WcSolanaMethod { + override val methodName: String = WcSolanaMethodName.SendAllTransaction.raw + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt index c51cc78fbb..91ab33bb5b 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangeUMConverter.kt @@ -6,7 +6,6 @@ import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangeUM import com.tangem.utils.converter.Converter -import java.math.BigDecimal import javax.inject.Inject internal class WcEstimatedWalletChangeUMConverter @Inject constructor() : @@ -17,7 +16,7 @@ internal class WcEstimatedWalletChangeUMConverter @Inject constructor() : is AmountInfo.FungibleTokens -> WcEstimatedWalletChangeUM( iconRes = value.iconRes, title = resourceReference(value.titleRes), - description = "${value.sign} ${amountInfo.amount.amountText()} ${amountInfo.token.symbol}", + description = "${value.sign} ${amountInfo.amountText()} ${amountInfo.token.symbol}", tokenIconUrl = amountInfo.token.logoUrl, ) is AmountInfo.NonFungibleTokens -> WcEstimatedWalletChangeUM( @@ -29,7 +28,7 @@ internal class WcEstimatedWalletChangeUMConverter @Inject constructor() : } } - private fun BigDecimal.amountText() = format { crypto("", DECIMALS_AMOUNT) } + private fun AmountInfo.FungibleTokens.amountText() = amount.format { crypto("", token.decimals) } data class Input( val amountInfo: AmountInfo, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt index 1eda478462..3312c609d3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt @@ -21,7 +21,7 @@ import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal import javax.inject.Inject -internal const val DECIMALS_AMOUNT = 2 +private const val DECIMALS_AMOUNT = 2 @Suppress("CyclomaticComplexMethod", "LongMethod") internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( From a69cc2b5e63a00126704bfa01b3c4fa8dc504e50 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Aug 2025 19:14:30 +0700 Subject: [PATCH 10/40] Updated on 2026-08-14 --- .../v2/feeselector/ui/FeeSelectorModalBottomSheet.kt | 5 ++++- .../transaction/model/WcSendTransactionModel.kt | 11 ++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt index f9f7905693..1c2ba7badd 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/ui/FeeSelectorModalBottomSheet.kt @@ -488,7 +488,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))), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 623125db6f..8a7315fe55 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -18,7 +18,8 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2 import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2.Icon.Type -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.core.lce.Lce import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet @@ -39,6 +40,7 @@ import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeStateConfigur import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger import com.tangem.features.send.v2.api.subcomponents.feeSelector.entity.FeeSelectorData import com.tangem.features.walletconnect.connections.routing.WcInnerRoute +import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter import com.tangem.features.walletconnect.transaction.converter.WcSendTransactionUMConverter @@ -57,7 +59,7 @@ import java.math.BigDecimal import javax.inject.Inject import kotlin.properties.Delegates -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @Stable @ModelScoped internal class WcSendTransactionModel @Inject constructor( @@ -112,7 +114,10 @@ internal class WcSendTransactionModel @Inject constructor( ?.dAppFee() ?.let { dAppFee -> feeStateConfiguration = FeeStateConfiguration.Suggestion( - title = stringReference(useCase.session.sdkModel.appMetaData.name), + title = resourceReference( + id = R.string.wc_fee_suggested, + formatArgs = wrappedList(useCase.session.sdkModel.appMetaData.name), + ), fee = dAppFee, ) } From 502d9e3b40ad07b705747e14fcb4414b36439bd0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Aug 2025 12:10:39 +0500 Subject: [PATCH 11/40] Updated on 2026-08-14 --- .../blockaid/models/response/AssetDiff.kt | 1 + .../blockaid/models/response/Exposure.kt | 1 + .../res/drawable/img_approvale_new_24.xml | 17 +++ .../tangem/data/blockaid/BlockAidMapper.kt | 101 +++++++++++------- .../data/blockaid/BlockAidMapperTest.kt | 9 +- .../network/ethereum/WcEthTxHelper.kt | 8 +- .../transaction/simultation/ApproveInfo.kt | 13 +++ .../transaction/simultation/ApprovedAmount.kt | 9 -- .../transaction/simultation/SimulationData.kt | 4 +- .../blockaid/WcEstimatedWalletChangesItem.kt | 10 +- .../WcSendAndReceiveBlockAidUiConverter.kt | 16 ++- .../send/WcSendTransactionModalBottomSheet.kt | 11 +- 12 files changed, 135 insertions(+), 65 deletions(-) create mode 100644 core/ui/src/main/res/drawable/img_approvale_new_24.xml create mode 100644 domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt delete mode 100644 domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt index be70cd4a52..a3c5d878a6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/AssetDiff.kt @@ -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, ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt index e776056d4c..cfab51b716 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/Exposure.kt @@ -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, ) diff --git a/core/ui/src/main/res/drawable/img_approvale_new_24.xml b/core/ui/src/main/res/drawable/img_approvale_new_24.xml new file mode 100644 index 0000000000..bc1f814f54 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_approvale_new_24.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt index e61f8ddd21..5334f1e066 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt @@ -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 @@ -101,13 +101,8 @@ 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) } } @@ -156,23 +151,11 @@ internal object BlockAidMapper { } private fun mapApproveTransaction(exposures: List?): 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, - ) - 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 amounts: List? = exposures?.flatMap { exposure -> + if (exposure.assetType.isNFT()) { + listOf(mapApproveNftTransaction(exposure)) + } else { + mapTransaction(exposure) } } return if (!amounts.isNullOrEmpty()) { @@ -182,6 +165,34 @@ internal object BlockAidMapper { } } + private fun mapTransaction(exposure: Exposure): List { + 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?): SimulationResult { val sendInfo = arrayListOf() val receiveInfo = arrayListOf() @@ -194,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)) + } } } } @@ -212,17 +241,7 @@ internal object BlockAidMapper { } } - private fun mapNftSendReceiveTransaction(traces: List?): 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" } } \ No newline at end of file diff --git a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt index 00dd54e198..3b24f6e0a4 100644 --- a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt +++ b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt @@ -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 diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt index 057afaa2e9..3526fb2d8c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthTxHelper.kt @@ -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() ?: return null if (approves.isEmpty()) return null val amount = approves.first() diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt new file mode 100644 index 0000000000..2325c38b32 --- /dev/null +++ b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApproveInfo.kt @@ -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() +} \ No newline at end of file diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt deleted file mode 100644 index 6fdad58ec2..0000000000 --- a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/ApprovedAmount.kt +++ /dev/null @@ -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, -) \ No newline at end of file diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt index 13c28ebba4..13937ad93e 100644 --- a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt +++ b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/simultation/SimulationData.kt @@ -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, - ) : SimulationData() + data class Approve(val items: List) : SimulationData() /** * Simulation was successfully performed and no changes detected diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt index 25e8b53eba..590b49c95e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt @@ -132,8 +132,7 @@ private fun EstimatedWalletChangesPreviewMoreThanFour( } } -private class EstimatedWalletChangesPreviewProviderTwoItems : - PreviewParameterProvider { +private class EstimatedWalletChangesPreviewProviderTwoItems : PreviewParameterProvider { override val values = sequenceOf( WcEstimatedWalletChangesUM( items = persistentListOf( @@ -149,6 +148,13 @@ private class EstimatedWalletChangesPreviewProviderTwoItems : description = "+ 1,131.46 MATIC", tokenIconUrl = "https://tangem.com", ), + WcEstimatedWalletChangeUM( + iconRes = R.drawable.img_approvale_new_24, + title = resourceReference(R.string.common_approve), + description = "10 Collection", + tokenIconUrl = "https://cdn.blockaid.io/nft/0x09851531816f78cF4841f1DeF22fbaB78aDD02c5/29805/" + + "polygon?r=ed117da0-6065-4ff8-ba81-cb7395e1ec3d", + ), ), ), ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt index 3312c609d3..3ef6afbf4c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt @@ -4,6 +4,7 @@ import com.domain.blockaid.models.transaction.CheckTransactionResult 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.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.format.bigdecimal.crypto @@ -55,7 +56,20 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( }, estimatedWalletChanges = (simulation as? SimulationResult.Success)?.data?.let { data -> when (data) { - is SimulationData.Approve, SimulationData.NoWalletChangesDetected -> null + is SimulationData.NoWalletChangesDetected -> null + is SimulationData.Approve -> { + val nftItems = data.items.mapNotNull { item -> + if (item !is ApproveInfo.NonFungibleToken) return@mapNotNull null + + WcEstimatedWalletChangeUM( + iconRes = R.drawable.img_approvale_new_24, + title = TextReference.Res(R.string.common_approve), + description = item.name, + tokenIconUrl = item.logoUrl, + ) + } + WcEstimatedWalletChangesUM(nftItems.toImmutableList()).takeIf { nftItems.isNotEmpty() } + } is SimulationData.SendAndReceive -> { val items: ImmutableList = ( data.send.map { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index ec1400b0df..8712039925 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter import com.tangem.core.ui.components.divider.DividerWithPadding import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -122,7 +123,7 @@ internal fun WcSendTransactionModalBottomSheet( modifier = Modifier.padding(top = 14.dp), config = state.feeErrorNotification.config, iconTint = TangemTheme.colors.icon.warning, - containerColor = TangemTheme.colors.button.disabled, + containerColor = TangemTheme.colors.background.action, ) } } @@ -301,8 +302,12 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide address = null, sendEnabled = false, feeErrorNotification = NotificationUM.Info( - title = stringReference("Insufficient Ethereum"), - subtitle = stringReference("Top up your balance to cover the network fee"), + title = resourceReference(R.string.send_fee_unreachable_error_title), + subtitle = resourceReference(R.string.send_fee_unreachable_error_text), + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.warning_button_refresh), + onClick = {}, + ), ), ), ), From 44a191019a337016f078272037edb0bb94762fe5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Aug 2025 15:02:46 +0500 Subject: [PATCH 12/40] Updated on 2026-08-14 --- .../converter/WcSendTransactionUMConverter.kt | 3 ++ .../entity/send/WcSendTransactionUM.kt | 2 + .../model/WcSendTransactionModel.kt | 2 + ...AddEthereumChainModalBottomSheetContent.kt | 1 + .../ui/common/WcTransactionRequestButtons.kt | 40 ++++++++++++++----- .../send/WcSendTransactionModalBottomSheet.kt | 5 +++ ...cSignTransactionModalBottomSheetContent.kt | 1 + .../utils/WcNotificationsFactory.kt | 8 +--- 8 files changed, 46 insertions(+), 16 deletions(-) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index 43c5b87851..c9f6f86182 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -3,6 +3,7 @@ package com.tangem.features.walletconnect.transaction.converter import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcSolanaMethod +import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck import com.tangem.domain.walletconnect.usecase.method.WcMethodContext import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignStep @@ -52,6 +53,7 @@ internal class WcSendTransactionUMConverter @Inject constructor( estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(), isLoading = value.signState.domainStep == WcSignStep.Signing, address = WcAddressConverter.convert(value.context.derivationState), + transactionValidationResult = value.securityCheck?.result?.validation, sendEnabled = value.feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null, feeErrorNotification = feeErrorNotification, ), @@ -78,6 +80,7 @@ internal class WcSendTransactionUMConverter @Inject constructor( val actions: WcTransactionActionsUM, val feeSelectorUM: FeeSelectorUM?, val cryptoCurrencyStatus: CryptoCurrencyStatus, + val securityCheck: BlockAidTransactionCheck.Result?, val onFeeReload: () -> Unit, ) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt index 86ef260951..e97fc79509 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.walletconnect.transaction.entity.send +import com.domain.blockaid.models.transaction.ValidationResult import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.features.send.v2.api.entity.FeeSelectorUM @@ -27,6 +28,7 @@ internal data class WcSendTransactionItemUM( val walletName: String?, val networkInfo: WcNetworkInfoUM, val address: String?, + val transactionValidationResult: ValidationResult?, val sendEnabled: Boolean, val feeErrorNotification: NotificationUM.Info?, val isLoading: Boolean = false, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 8a7315fe55..c0e035a70e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -249,6 +249,7 @@ internal class WcSendTransactionModel @Inject constructor( feeSelectorUM = uiState.value?.feeSelectorUM, cryptoCurrencyStatus = cryptoCurrencyStatus, onFeeReload = ::triggerFeeReload, + securityCheck = securityCheck.getOrNull(), ), ) transactionUM = transactionUM?.copy( @@ -280,6 +281,7 @@ internal class WcSendTransactionModel @Inject constructor( stackNavigation.pushNew(WcTransactionRoutes.SelectFee) } + // Before change, make sure you are align with WcTransactionRequestButtons private fun onSign(securityCheck: BlockAidTransactionCheck.Result?) { when (securityCheck?.result?.validation) { ValidationResult.UNSAFE -> showMaliciousAlert(securityCheck.result.description) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt index 36f2195df9..3f1a55cfe8 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt @@ -97,6 +97,7 @@ internal fun WcAddEthereumChainModalBottomSheetContent( onClickActiveButton = state.onSign, activeButtonText = resourceReference(R.string.common_sign), isLoading = state.isLoading, + validationResult = null, ) }, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt index ed0217aee9..ffd23bc142 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.domain.blockaid.models.transaction.ValidationResult +import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.extensions.TextReference @@ -17,6 +19,7 @@ import com.tangem.features.walletconnect.impl.R internal fun WcTransactionRequestButtons( activeButtonText: TextReference, isLoading: Boolean, + validationResult: ValidationResult?, onDismiss: () -> Unit, onClickActiveButton: () -> Unit, modifier: Modifier = Modifier, @@ -30,15 +33,32 @@ internal fun WcTransactionRequestButtons( text = stringResourceSafe(R.string.common_cancel), onClick = onDismiss, ) - PrimaryButtonIconEnd( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = activeButtonText.resolveReference(), - onClick = onClickActiveButton, - iconResId = R.drawable.ic_tangem_24, - showProgress = isLoading, - enabled = enabled, - ) + // Before change, make sure you are align with WcSendTransactionModel::onSign + when (validationResult) { + ValidationResult.UNSAFE, + ValidationResult.WARNING, + -> PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = stringResourceSafe(R.string.common_continue), + onClick = onClickActiveButton, + showProgress = isLoading, + enabled = enabled, + ) + ValidationResult.SAFE, + ValidationResult.FAILED_TO_VALIDATE, + null, + -> PrimaryButtonIconEnd( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = activeButtonText.resolveReference(), + onClick = onClickActiveButton, + iconResId = R.drawable.ic_tangem_24, + showProgress = isLoading, + enabled = enabled, + ) + } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index 8712039925..b647ef5abd 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp +import com.domain.blockaid.models.transaction.ValidationResult import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent @@ -137,6 +138,7 @@ internal fun WcSendTransactionModalBottomSheet( activeButtonText = resourceReference(R.string.common_send), isLoading = state.isLoading, enabled = state.sendEnabled, + validationResult = state.transactionValidationResult, ) }, ) @@ -221,6 +223,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide address = null, sendEnabled = true, feeErrorNotification = null, + transactionValidationResult = null, ), WcSendTransactionItemUM( onDismiss = {}, @@ -263,6 +266,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide title = stringReference("Insufficient Ethereum"), subtitle = stringReference("Top up your balance to cover the network fee"), ), + transactionValidationResult = ValidationResult.WARNING, ), WcSendTransactionItemUM( onDismiss = {}, @@ -309,6 +313,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide onClick = {}, ), ), + transactionValidationResult = ValidationResult.SAFE, ), ), ) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt index 21dd6eb5a9..5b0b50fe2e 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt @@ -95,6 +95,7 @@ internal fun WcSignTransactionModalBottomSheetContent( onClickActiveButton = state.onSign, activeButtonText = resourceReference(R.string.common_sign), isLoading = state.isLoading, + validationResult = null, ) }, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt index bb97456631..c628689c04 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt @@ -1,6 +1,5 @@ package com.tangem.features.walletconnect.utils -import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.resourceReference @@ -54,14 +53,11 @@ internal class WcNotificationsFactory @Inject constructor() { feeSelectorUM: FeeSelectorUM?, ): Boolean { val feeSelectorContent = feeSelectorUM as? FeeSelectorUM.Content ?: return false - val lowestFee = when (val fees = feeSelectorContent.fees) { - is TransactionFee.Choosable -> fees.minimum - is TransactionFee.Single -> fees.normal - } + val selectedFee = feeSelectorContent.selectedFeeItem.fee return FeeCalculationUtils.checkExceedBalance( feeBalance = cryptoCurrencyStatus.value.amount, - feeAmount = lowestFee.amount.value, + feeAmount = selectedFee.amount.value, ) } } \ No newline at end of file From becbc0ed5c8d229d0e8165a726faa82cab78a88d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Aug 2025 17:14:15 +0300 Subject: [PATCH 13/40] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 6a3d13307a..1018ab6255 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,13 +5,13 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.27.0-1132" +tangemBlockchainSdk = "releases-5.27.0-1142" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.27.0-510" +tangemCardSdk = "releases-5.27.0-513" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-446" +tangemHotSdk = "develop-454" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^ From 973c591ff20ecec5e61d25fa6fd951b769878cbc Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Aug 2025 16:04:59 +0500 Subject: [PATCH 14/40] Updated on 2026-08-14 --- .../state/transformers/SetInitialDataStateTransformer.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 82f48a612c..b13bae78df 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -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): TextReference { + if (validators.isEmpty()) { + return stringReference(DASH_SIGN) + } val aprValues = validators .filter { it.preferred } .takeIf { it.isNotEmpty() } From 0ffde1aedb0d6b5043aaecc5bfcb6c94b3b86cac Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Aug 2025 13:55:02 +0700 Subject: [PATCH 15/40] Updated on 2026-08-14 --- .../walletconnect/connections/components/WcPairComponent.kt | 4 ++-- .../walletconnect/connections/routes/WcAppInfoRoutes.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt index a171a0f38a..e9aa7aa21c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt @@ -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, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt index cf5b226353..27a3879932 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt @@ -26,7 +26,7 @@ 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() From 9b41230fa95aab957735d9a77cd5473414edb3da Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Aug 2025 09:19:15 +0000 Subject: [PATCH 16/40] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 7250e5eb0c..1018ab6255 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,12 +5,14 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.26.0-1123" +tangemBlockchainSdk = "releases-5.27.0-1142" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.26.0-508" +tangemCardSdk = "releases-5.27.0-513" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ +tangemHotSdk = "develop-454" +#tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^ @@ -18,6 +20,8 @@ tangemVico = "2.0.0-alpha.25-tangem12" blockchain = { module = "com.tangem:blockchain", version.ref = "tangemBlockchainSdk" } card-android = { module = "com.tangem.tangem-sdk-kotlin:android", version.ref = "tangemCardSdk" } card-core = { module = "com.tangem.tangem-sdk-kotlin:core", version.ref = "tangemCardSdk" } +hot-core = { module = "com.tangem.tangem-hot-sdk-kotlin:core", version.ref = "tangemHotSdk" } +hot-android = { module = "com.tangem.tangem-hot-sdk-kotlin:android", version.ref = "tangemHotSdk" } vico-compose = { group = "com.tangem.vico", name = "compose", version.ref = "tangemVico" } vico-compose-m3 = { group = "com.tangem.vico", name = "compose-m3", version.ref = "tangemVico" } From 77e400b400544589d8b37c57799c9d8cb1442cc4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Aug 2025 18:15:10 +0500 Subject: [PATCH 17/40] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 13 ++++ core/res/src/main/res/values-es/strings.xml | 71 ++++++++++++++++++- core/res/src/main/res/values-fr/strings.xml | 13 ++++ core/res/src/main/res/values-it/strings.xml | 14 ++++ core/res/src/main/res/values-ja/strings.xml | 49 ++++++++++--- core/res/src/main/res/values-ru/strings.xml | 31 +++++++- .../src/main/res/values-uk-rUA/strings.xml | 56 +++++++++++++++ .../src/main/res/values-zh-rTW/strings.xml | 13 ++++ core/res/src/main/res/values/strings.xml | 39 ++++++++-- .../pair/DefaultWcPairUseCase.kt | 7 ++ .../connections/components/WcPairComponent.kt | 1 + .../connections/model/WcPairModel.kt | 3 + .../connections/routes/WcAppInfoRoutes.kt | 1 + .../connections/utils/WcAlertsFactory.kt | 18 +++++ .../converter/WcSendTransactionUMConverter.kt | 12 +++- 15 files changed, 322 insertions(+), 19 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 673c284024..7bc007eb36 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -147,6 +147,7 @@ Schließen Demnächst verfügbar Bestätigen + Verbinden Kontakt zum Tangem-Support Kontakt zum Visa-Support Weiter @@ -249,6 +250,10 @@ Allgemeine Geschäftsbedingungen Nutzungsbedingungen Heute + + %d Token + %d Token + Transaktion fehlgeschlagen Transaktionsstatus Transaktionen @@ -315,6 +320,8 @@ Details Überprüfe deine Internetverbindung oder wechseln zu einem anderen Netzwerk Nutzungsbedingungen + Hallo Support-Team, ich habe einen Fehler mit dem Code %s festgestellt. + WalletConnect-Fehler Du hast eine Karte oder Ring aus einer anderen Wallet verwendet. Tippe auf die Karte oder Ring, die dieser Wallet zugeordnet ist. Nicht genug Geld für die Transaktion. Bitte lade dein Konto auf. Meine Token @@ -1426,6 +1433,12 @@ Alle trennen Text über die Trennung aller dApps Alle dApps trennen + Versuchen Sie erneut, mit einer neuen URI zu koppeln + Ungültige dApp-Domain + %s gibt keine Blockchains an — weder erforderlich noch optional.\nBitte stellen Sie sicher, dass Sie die richtige URI verwendet haben + Keine Netzwerke + Bitte generieren Sie eine neue URI und versuchen Sie erneut, eine Verbindung herzustellen + Verbindungsvorschlag abgelaufen Geschätzte Wallet-Änderungen Die Transaktion konnte nicht simuliert werden. Bitte sei vorsichtig. Böswillige/ gefährliche Transaktion diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index f17262fd95..8d38231daf 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1,5 +1,9 @@ + Archivar cuenta + Archivo + Estás archivando esta cuenta, pero siempre puedes recuperarla. + Cuenta ¿No encuentra el token en su billetera? Consulte la sección de Mercados para encontrarlo y añadirlo a la compra ¿No encuentra el token en su billetera? Consulte los mercados para encontrarlo y añadirlo a la venta Vender @@ -34,12 +38,16 @@ Esta tarjeta no admite tokens en la red %1$s debido a una limitación del firmware. ¿Tiene dificultades para escanear su tarjeta/anillo? Esta tarjeta no está diseñada para funcionar con Tangem + Utilice %1$s para desbloquear de forma rápida y segura su biletera y autorizar todas las acciones importantes, como la firma de transacciones. En el caso de las billeteras físicas, seguirá necesitando una tarjeta para firmar. Tarifa por defecto Habilite las Tarifas predeterminadas para establecer automáticamente las tarifas de transacción y omitir la página de Tarifas al enviar fondos. Siempre puede volver a esta página si es necesario. Vaya a ajustes para habilitar la autenticación biométrica en la Tangem App Habilitar autenticación biométrica + Para deshabilitar %1$s deberá ingresar su código de acceso para desbloquear la aplicación e interactuar con su billetera. Esto eliminará todos los códigos de acceso guardados de la billetera. Cualquier operación posterior con la billetera requerirá introducir el código de acceso. Eliminar la tarjeta guardada borra todos las billeteras guardadas y sus códigos de acceso de la app. + Requerir código de acceso + Esta opción desactiva la autenticación biométrica para acciones importantes. Se le pedirá que introduzca su código de acceso cada vez que, por ejemplo, deba firmar una transacción. Guardar código de acceso Se solicitará la autenticación biométrica en lugar del código de acceso para las interacciones con su tarjeta o anillo. Mantener la billetera en la app @@ -135,6 +143,7 @@ Cerrar Próximamente Confirme + Conectando Contacte con el soporte de Tangem Contacte con el soporte de Visa Continuar @@ -153,6 +162,7 @@ días Suprimir + Desactivar Desactivado Desconectar Listo @@ -191,6 +201,7 @@ No Ninguna dirección No aregada + Ahora no Ahora OK Abrir en el navegador @@ -206,6 +217,7 @@ Rechazar Recargar Renombrar + Requerido Guarde Guardar cambios Buscar @@ -235,6 +247,10 @@ términos y condiciones Condiciones de uso Hoy + + %d ficha + %d fichas + Transacción fallida Estado de la transacción Transacciones @@ -301,6 +317,9 @@ Detalles Compruebe su conexión a internet o cambia a una red diferente Condiciones de uso + Recibir activos + Hola equipo de soporte, he encontrado un error con el código: %s + Error de WalletConnect Ha usado una tarjeta de otra billetera. Toque la tarjeta asociada con esta billetera No hay fondos suficientes para la transacción. Por favor, recargue su cuenta. Mis tokens @@ -358,6 +377,7 @@ Proveedor Mejor tarifa Lista de advertencias de la FCA + Proveedor en la lista de advertencia de la FCA Disponible hasta %s Disponible desde %s No disponible para este par @@ -405,6 +425,8 @@ Escanee a %s En la red %s + ¿Está seguro de que desea salir del proceso de creación del código de acceso? + Si lo haces, tendrás que empezar de nuevo. Mantente al día con las últimas funciones y noticias Esta información fue generada con IA.\nPulse aquí si encuentra algún error. Para cambiar el código de acceso coloque la tarjeta o el anillo como se muestra arriba y no lo retire hasta el fin de la operación @@ -908,9 +930,14 @@ Cantidad no válida La tarifa excede el saldo El monto total excede el saldo + Intercambiar y enviar + ¿Continuar con la conversión? Esto borrará tus datos anteriores. + Confirmar Conversión Envía cualquier token y lo convertiremos en el camino. Su destinatario obtiene exactamente lo que necesita, sin problemas. - Se enviará un destinatario + El destinatario recibirá + Al destinatario Cantidad a recibir + ¿Seguro que desea cancelar la conversión? Se borrarán sus datos anteriores. Enviar con swap Transacción enviada Escanee la tarjeta/anillo que quiere configurar @@ -1210,8 +1237,10 @@ Consíguelo ahora con un 10 % de descuento Accede a más de 13 000 criptomonedas. Compra, vende, intercambia y realiza staking con un solo toque.\nVincula hasta tres tarjetas para hacer copias de seguridad. Descubre Tangem Wallet + Cambiar código de acceso Manténteinformado sobre las transacciones entrantes de la billetera y las actualizaciones de Tangem. Notificaciones de transacciones + Establecer código de acceso Ajustes de la wallet Tangem Use %s o escanee una tarjeta/anillo para desbloquear el acceso a su billetera @@ -1308,16 +1337,27 @@ Se requiere línea de confianza Dominio malicioso Dominio desconocido + Conectarse de todas formas + Error de tiempo de espera. Por favor, inténtalo de nuevo más tarde. + Error al establecer WalletConnect Este dominio no puede ser verificado. Compruebe cuidadosamente la solicitud de aprobación. Vuelva a su navegador y vuelva a conectarse a través de WalletConnect. + La sesión de Wallet Connect se desconectó Firmar de todos modos Código de error: %s. Si el problema persiste - no dude en ponerse en contacto con nuestro soporte. Si el problema persiste, no dudes en ponerte en contacto con nuestro soporte. + Hemos encontrado un error desconocido + Tangem Wallet actualmente no es compatible con %s. + dApp no compatible Código de error: 8 005. Si el problema persiste, no dudes en contactar con nuestro soporte. Hemos encontrado un error desconocido Actualmente, Tangem no es compatible con una red requerida por %s. + Redes no compatibles Tangem soporta una red requerida por %s + Dominio verificado Se seleccionó una tarjeta o un anillo incorrectos en la app + Tenemos algún tipo de problema + Todas las dApps desconectadas Permitir gastar Dirección Conectar @@ -1326,30 +1366,59 @@ Redes Ilimitado Billetera + Aplicación conectada Redes conectadas + Conectado a %1$s Ver el saldo y la actividad de su billetera Firmar transacciones sin previo aviso Solicitar aprobación para transacciones No podrá Me gustaría Solicitud de conexión + Conexiones Contenido Copiar datos Asignación personalizada + dApp desconectada + Desconectar todo + Texto sobre desconexión de todas las dApps + Desconectar todas las dApps + Intente emparejar nuevamente con una URI nueva + Dominio de dApp no válido + %s no especifica ninguna cadena de bloques, ni obligatoria ni opcional. Asegúrese de haber utilizado el URI correcto + Sin redes + Por favor, genere una nueva URI e intente conectarse nuevamente + Propuesta de conexión caducada Cambios estimados en la billetera La transacción no ha podido ser simulada. Por favor, proceda con precaución. + La estimación no es compatible con %s + Sugerido por %s + Recargue su saldo para cubrir la tarifa de red + Insuficiente %1$s Transacción maliciosa Añada la red %s a su perfil para esta billetera La billetera no tiene las redes requeridas + Nueva conexión + Conecta tu billetera a diferentes dApps + Sin sesiones + No se han detectado cambios en la cartera Se han detectado riesgos potenciales o comportamiento malicioso. Conectarse o firmar transacciones puede resultar en la pérdida de fondos. Riesgo de seguridad conocido + Abra la aplicación Web3 y elija la opción WalletConnect Solicitud de + Firmar de todos modos Tipo de firma + Se requiere al menos una red para la conexión dApp + Especificar las redes seleccionadas + Firmado correctamente A Solicitud de transacción Solicitud de transacción Cantidad ilimitada + Asegúrese de que cada intento de emparejamiento utiliza un URI nuevo y único + URI ya utilizado Wallet connect + Transacción sospechosa Ignorar Tiene un backup interrumpido. ¿Quiere reanudarlo? Sí, reanudar diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index bbf154df4e..00b0250a97 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -124,6 +124,7 @@ Réclamez des récompenses Fermer Confirmez + Connexion Continuer Convertir Copier @@ -222,6 +223,10 @@ termes et conditions Conditions d\'utilisation Aujourd\'hui + + %d jeton + %d jetons + La transaction a échoué Statut de la transaction Transactions @@ -288,6 +293,8 @@ Détails Vérifiez votre connexion Internet ou passez à un réseau différent Conditions d\'utilisation + Bonjour équipe de support, j’ai rencontré une erreur avec le code : %s + Erreur WalletConnect Vous avez utilisé une carte d\'un autre portefeuille. Appuyez sur la carte associée à ce portefeuille Pas assez de fonds pour la transaction. Veuillez recharger votre compte. Mes jetons @@ -1327,6 +1334,12 @@ Déconnecter tout Texte sur la déconnexion de toutes les dApps Déconnecter toutes les dApps + Essayez de jumeler à nouveau avec un nouvel URI + Domaine dApp invalide + %s ne spécifie aucune blockchain — ni requise ni optionnelle.\nVeuillez vous assurer que vous avez utilisé l’URI correct + Aucun réseau + Veuillez générer un nouvel URI et essayer de vous connecter à nouveau + La proposition de connexion a expiré Modifications estimées du portefeuille La transaction n\'a pas pu être simulée. Veuillez procéder avec prudence. Transaction malveillante diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 5caec91961..a585f2e30a 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -10,6 +10,8 @@ Impossibile creare la transazione Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy Annulla + Scegli portafoglio + Connessione in corso Rimuovere Fatto Errore @@ -20,6 +22,10 @@ Invia Impossibile inviare la transazione Con successo + + %d gettone + %d gettoni + Codice di accesso Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto Mantenimento della carta @@ -34,6 +40,8 @@ Firmato Requisiti Termini del servizio + Ciao team di supporto, ho riscontrato un errore con il codice: %s + Errore WalletConnect Not enough funds for the transaction. Please top up your account. An error occurred Notifica richiesta @@ -66,6 +74,12 @@ Nessuna connessione a Internet Tangem Ok, ho capito! + Prova a eseguire nuovamente l’associazione con un nuovo URI + Dominio dApp non valido + %s non specifica alcuna blockchain — né obbligatoria né opzionale.\nAssicurati di aver utilizzato l’URI corretto + Nessuna rete + Genera un nuovo URI e prova a connetterti di nuovo + La proposta di connessione è scaduta No, invia l\'intero importo Riduci di %s XTZ Per evitare di pagare una commissione maggiore la prossima volta che ricarichi il tuo portafoglio, riduci l\'importo di %s XTZ diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index cf05e40cb9..ff32b8bf8b 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -12,6 +12,10 @@ ウォレットのロックを解除するには、 %s桁のアクセスコードを設定します。 アクセスコードの作成 アクセスコード + アカウントをアーカイブする + アーカイブ + このアカウントをアーカイブしますが、いつでも復元できます。 + アカウント アカウントを追加 保存 アカウント名 @@ -58,12 +62,16 @@ %1$s ネットワークのトークンは、ファームウェアの制限により、このカードまたはリングではサポートされていません。 カードまたはリングのスキャンに問題がありますか? このカードはこのアプリでは使用できません。 + %1$sを使用すると、ウォレットのロックを迅速かつ安全に解除できます。また、取引の署名など、機密性の高い操作も承認できます。ハードウェアウォレットの場合は、署名にカードが必要です。 デフォルト手数料 デフォルト手数料を有効にすると、取引手数料が自動的に設定され、送金時に手数料ページを表示する必要がなくなります。必要に応じて、いつでもこのページに戻ることができます。 設定に移動して、Tangemアプリで生体認証を有効にします。 生体認証を有効にする + %1$sが無効になると、アプリのロックを解除してウォレットを操作するために、パスコードを入力する必要があります。 これにより、保存されているウォレットアクセスコードがすべて削除されます。ウォレットでの今後の操作には、アクセスコードの送信が必要になります。 保存したデバイスを削除すると、保存されているすべてのウォレットとそのアクセスコードがアプリから削除されます。 + アクセスコードを要求する + このオプションを選択すると、機密性の高い操作における生体認証が無効になります。取引の署名時などには、毎回アクセスコードの入力が必要になります。 アクセスコードを保存 カードまたはリングとのやり取りには、アクセスコードの代わりに生体認証が要求されます。 ウォレットをアプリに保存する @@ -89,6 +97,7 @@ これらの%s個の単語をパスワードマネージャーなどの安全な場所に保存し、決して他の人と共有しないでください。 復元は不可能です リカバリーフレーズ + これらの単語は、絶対に誰にも共有しないでください。もし他人がこの単語を知れば、あなたの暗号資産をすべて盗むことができます。Tangemがこれらの単語を尋ねることはありません。以下の%s単語はウォレットの復元フレーズです。これらのフレーズを使用すると、デバイスを紛失した場合でもウォレットを復元できます。 これらの%s語を順番に書き留めて、安全かつプライベートに保管してください。 ウォレットと、リカバリーフレーズのセキュリティとバックアップの全責任は、Tangemではなくユーザーにあります。 リカバリーフレーズ @@ -166,6 +175,7 @@ 閉じる 近日公開 確認 + 接続中 Tangemサポートへ問い合わせる Visaサポートへ問い合わせる 続ける @@ -182,6 +192,7 @@ 削除 + 無効にする 無効 切断 完了 @@ -221,6 +232,7 @@ いいえ アドレスがありません 未追加 + 今はしない わかりました ブラウザで開く @@ -267,6 +279,9 @@ 利用規約 利用規約 今日 + + %d トークン + 取引が失敗しました 取引状況 取引 @@ -333,9 +348,12 @@ 詳細 インターネット接続を確認するか、別のネットワークに切り替えてください。 利用規約 + 資金を受け取る 他のネットワークで資産を送金すると、永久に失われます。 %sネットワーク 下記のみを使用して資金を送金する + サポートチームの皆様、コード %s のエラーが発生しました。 + WalletConnect エラー 別のウォレットのカードまたはリングを使用しました。このウォレットにリンクしているカードまたはリングをタップしてください。 取引に必要な資金が不足しています。アカウントに入金してください。 マイトークン @@ -441,10 +459,15 @@ Tangemをスキャン %sへ %sネットワーク + アクセスコードの作成プロセスを終了してもよろしいですか? 今すぐバックアップ セットアップを完了するには、ウォレットをバックアップし、アクセスコードを使用してアプリへのアクセスを保護します。 今すぐ実施 ウォレットのアクティベーションを完了する + セットアップを完了するには、アクセスコードを使用してアプリへのアクセスを保護します。 + そうした場合は、最初からやり直す必要があります。 + アクティベーションプロセスを終了してもよろしいですか? + 実行すると、最初からやり直す必要があります。 Googleドライブのバックアップに保存されている既存のウォレットを復元する Googleドライブのバックアップ バックアップへ移動 @@ -458,6 +481,7 @@ 最新の機能とニュースをお届けします シードフレーズのバックアップ モバイルウォレットを作成する + モバイルウォレット この情報はAIで生成されました。 \nエラーが見つかった場合は、ここをタップしてください。 アクセスコードを変更するには、上図のようにカードまたはリングをタップし、操作が終了するまで取り外さないでください。 パスコードを変更するには、上記のようにカードをタップし、操作が終了するまで取り外さないでください。 @@ -954,15 +978,17 @@ 無効な金額 手数料が残高を超えています 合計金額が残高を超えています - トークンを変更してもよろしいですか? 変更後、以前のデータはリセットされます。 + 受信トークンを変更してもよろしいですか? 変更すると、以前入力したデータがリセットされます。 トークンの変更 スワップして送信 + 変換を続行しますか? これにより以前のデータは消去されます。 + 変換を確定 トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。 受信者は受け取ります - 受信者に送信されます + 受取人へ 受取金額 - 変換をキャンセルしてもよろしいですか? 変更後、以前のデータはリセットされます。 - キャンセルの確認 + 変換をキャンセルしてもよろしいですか?以前のデータは消去されます。 + 変換を削除 スワップして送信 取引が送信されました 設定したいカードまたはリングをスキャンするために準備してください。 @@ -1420,7 +1446,7 @@ エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました Tangemは現在%sで必要なネットワークをサポートしていません。 - サポートされていないネットワーク + 未対応のネットワーク Tangemは%sで必要なネットワークをサポートします 検証済みドメイン アプリで間違ったカードまたはリングが選択されました @@ -1451,6 +1477,12 @@ すべての接続を解除する すべてのdAppsの接続解除に関するテキスト すべてのdAppを接続解除する + 新しいURIで、再度ペアリングを試してください + 無効なdAppドメイン + %sはブロックチェーンを指定していません(必須でもオプションでもありません)。 \n正しいURIを使用していることを確認してください。 + ネットワークなし + 新しいURIを生成し、再度接続してください + 接続提案の期限が切れました ウォレットの変更の予測 取引をシミュレーションできませんでした。注意して続行してください。 %sでは見積もりはサポートされていません @@ -1461,13 +1493,14 @@ このウォレットのポートフォリオに%sネットワークを追加します ウォレットに必要なネットワークはありません 新しい接続 - ウォレットを別のdAppに接続する + ウォレットをさまざまなdAppに接続する セッションなし ウォレットの変更は検出されませんでした - このドメインは複数のセキュリティプロバイダーから安全でないとの警告を受けています。あなたの資産を守るため、直ちにアクセスを中止してください。 + 潜在的なリスクまたは悪意のある行為が検出されました。接続または取引への署名は資金の損失につながる可能性があります。 既知のセキュリティリスク + Web3アプリを開き、WalletConnectオプションを選択します リクエスト元 - とにかく送金 + とにかく署名する 署名タイプ dApp接続には、少なくとも1つのネットワークが必要です 選択したネットワークを指定する diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 10a53f974e..5b238275be 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -116,11 +116,12 @@ Выберите действие Выберите сеть Выберите токен - Выберите кошелек + Выберите кошелёк Получить Вывести награду Закрыть Подтвердить + Подключение Продолжить Копировать Скопировать адрес @@ -219,6 +220,12 @@ условия участия Условиями использования Сегодня + + %d токен + %d токена + %d токенов + %d токенов + Ошибка транзакции Статус транзакции Транзакции @@ -284,6 +291,8 @@ Подробности Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования + Привет, команда поддержки, у меня возникла ошибка с кодом: %s + Ошибка WalletConnect Вы использовали карту или кольцо от другого кошелька. Приложите карту или кольцо, связанную с этим кошельком. Недостаточно средств для совершения транзакции. Пожалуйста, пополните свой аккаунт. Мои токены @@ -895,6 +904,18 @@ Недопустимая сумма Комиссия превышает остаток Отправляемая сумма превышает остаток + Вы уверены, что хотите изменить токен для получения? Это действие сбросит ранее введённые данные. + Изменение токена + Обмен и отправка + Продолжить с конвертацией? Это действие удалит предыдущие данные + Подтвердить конвертацию + Отправьте любой токен, и мы конвертируем его по пути. Адресат получит именно то, что нужно — без лишних действий. + Будет получено + Получателю + Сумма к получению + Вы уверены, что хотите отменить конвертацию? Ваши предыдущие данные будут удалены. + Убрать конвертацию + Отправка с обменом Транзакция отправлена Подготовьтесь к сканированию кольца или карты, которую вы хотите настроить. Забыть кошелек @@ -1285,7 +1306,7 @@ Требуется трастлайн Вредоносный домен Неизвестный домен - Подключиться всё равно + Всё равно подключиться Ошибка тайм-аута. Пожалуйста, попробуйте позже. Не удалось подключиться через Wallet Connect Этот домен не может быть верифицирован. Внимательно проверьте запрос перед одобрением. @@ -1329,6 +1350,12 @@ dApp отключен Отключить все Отключить все dApp + Попробуйте соединиться снова, используя новый URI + Недействительный домен dApp + %s не указывает никаких блокчейнов — ни обязательных, ни опциональных.\nПожалуйста, убедитесь, что вы использовали правильный URI + Нет сетей + Пожалуйста, сгенерируйте новый URI и попробуйте подключиться снова + Предложение подключения истекло Предварительные изменения Не удалось выполнить симуляцию транзакции. Пожалуйста, действуйте с осторожностью. Оценка не поддерживается для %s diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 9c820fac97..79d1d3c092 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -120,6 +120,7 @@ Отримати винагороди Закрити Підтвердити + Підключення Продовжити Копіювати Скопіювати адресу @@ -191,6 +192,7 @@ Відхилити Оновити Перейменувати + Обов\'язково Зберегти Зберегти зміни Пошук @@ -218,6 +220,12 @@ умови участі Умовами використання Сьогодні + + %d токен + %d токени + %d токенів + %d токенів + Помилка транзакції Статус транзакції Транзакції @@ -282,6 +290,8 @@ Деталі Перевірте підключення до інтернету або змініть мережу Умови використання + Привіт, команда підтримки, я зіткнувся з помилкою з кодом: %s + Помилка WalletConnect Ви використали картку або кільце від іншого гаманця. Прикладіть картку або кільце, пов\'язану з цим гаманцем. Недостатньо коштів для здійснення транзакції. Будь ласка, поповніть свій акаунт. Мої токени @@ -1282,18 +1292,64 @@ Відкрити Trustline Щоб отримати цей токен, потрібно увімкнути Trustline. Мережа вимагає резерв %1$s %2$s. Відкрийте Trustline + Невідомий домен + Все одно підключитися + Помилка тайм-ауту. Будь ласка, спробуйте пізніше. + Не вдалося зʼєднатися через Wallet Connect + Цей домен не може бути підтверджений. Уважно перевірте запит перед схваленням. Будь ласка, поверніться до браузеру і повторно підключіться через WalletConnect. + Сеанс Wallet Connect було завершено + Код помилки: %s. Якщо проблема зберігається, зверніться до нашої служби підтримки. + Ми зіткнулися з невідомою помилкою + Tangem наразі не підтримує необхідну мережу для %s. + Непідтримувані мережі + Tangem підтримує мережу, необхідну для %s + Верифікований домен Обрана не вірна картка або кільце + Схоже, виникла проблема + Усі dApps відключені Адреса Підключення + Мережа + Мережі + Гаманець + Підключений додаток Підключені мережі + Підключений до %1$s Переглянути баланс гаманця та активність + Підписати транзакцію без вашої участі + Запит схвалення на транзакцію + Не має можливості + Хотіли б Запит на підключення + З\'єднання Вміст Копіювати дані + dApp відключено + Розʼєднати все + Відключити всі dApps + Відключити всі dApps + Спробуйте ще раз з новим URI + Недійсний домен dApp + %s не вказує жодних блокчейнів — ні обов\'язкових, ні необов\'язкових. \n Переконайтеся, що ви використали правильний URI + Немає мереж + Будь ласка, згенеруйте новий URI та спробуйте ще раз + Термін для з’єднання минув + Поповніть баланс, щоб покрити комісію мережі + Недостатньо %1$s + Додайте %s мережі до вашого портфелю для цього гаманця + В гаманці не додані необхідні мережі + Нове з\'єднання + Підключайте свій гаманець до різних dApps + Немає підключень Виявлено потенційні ризики або шкідливу активність. Підключення чи підпис транзакцій можуть призвести до втрати коштів. + Відомий ризик безпеки + Відкрийте програму Web3 та виберіть опцію WalletConnect Запит від Тип підпису + Для підключення dApp потрібна принаймні одна мережа + Вкажіть вибрані мережі + Успішно підписано До Запит транзакції Запит транзакції diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index b33588b325..713520796a 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -52,7 +52,9 @@ 您尚未授予相機訪問權限,請更改您的隱私設置 删除 選擇代幣 + 選擇錢包 關閉 + 連線中 繼續 複製 複製地址 @@ -90,6 +92,9 @@ 成功 交換 條款和條件 + + %d 代幣 + 交易 我了解 無法觸達 @@ -129,6 +134,8 @@ 更多 檢查您的網路連接或切換到其他網絡 服務條款 + 您好,支援團隊,我遇到了一個錯誤,代碼為:%s + WalletConnect 錯誤 您使用了另一個錢包中的卡。點按與此錢包關聯的卡片 沒有足夠的資金進行交易。請先入金 以下信息是可選的。如果不想共享,可以將其刪除 @@ -372,6 +379,12 @@ 認證檢查失敗 網路無法使用 Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用 + 請使用新的 URI 再次嘗試配對 + 無效的 dApp 網域 + %s 未指定任何區塊鏈——無論是必須的還是可選的。\n請確保您使用了正確的 URI + 沒有網路 + 請生成新的 URI,然後再次嘗試連線 + 連線提案已過期 捨棄 您有一個備份中斷了,您想繼續嗎? 是的,恢復 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4c933aff60..4089de8ca8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -12,6 +12,12 @@ Set a %s-digit Access Code to unlock your wallet. Create Access Code Access code + Recover + Archived + Archive account + Archive + You are archiving this account, but you can always get it back. + Account Add account Save Account name @@ -63,6 +69,7 @@ Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary. Go to settings to enable biometric authentication in the Tangem App Enable biometric authentication + Disabling %1$s will require you to enter your passcode to unlock the app and to interact with your wallet. This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. Removing the saved devices deletes all the saved wallets and their access codes from the app. Require Access Code @@ -173,6 +180,7 @@ Close Coming Soon Confirm + Connecting Contact Tangem Support Contact Visa Support Continue @@ -231,6 +239,7 @@ No No address Not Added + Not Now Now OK Open in Browser @@ -278,6 +287,10 @@ terms and conditions Terms of Use Today + + %d token + %d tokens + Transaction failed Transaction status Transactions @@ -348,6 +361,8 @@ Sending assets in other networks will result in permanent loss. %s network Send funds using only + Hi support team, I\'ve encountered an error with code: %s + WalletConnect error You have used a card or ring from another wallet. Tap the card or ring associated with this wallet Not enough funds for the transaction. Please top up your account. My tokens @@ -453,6 +468,7 @@ Scan Tangem to %s On %s network + Are you sure you want to exit the access code creation process? Backup Now To complete setup, back up your wallet and secure app access with a Access Code. Finish Now @@ -460,6 +476,7 @@ To complete setup, secure app access with Access Code. If you do, you\'ll need to start over. Are you sure you want to exit the activation process? + If you do, you\'ll need to start over. Recover an existing wallet stored in your Google Drive backup Google Drive Backup Go to backup @@ -979,15 +996,18 @@ Invalid amount Fee exceeds balance Total amount exceeds balance - Are you sure you want to change the token? After changing, previous data will be reset. + Are you sure you want to change the receiving token? This will reset your previously entered data. Changing token Swap and send + Proceed with conversion? This will clear your previous data. + Confirm Conversion Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly. Recipient will receive - Will be sent a recipient + To recipient Amount to receive - Are you sure you want to cancel the conversion? After changing, previous data will be reset. - Confirm cancelation + Recipient get %s + Are you sure you want to cancel the conversion? Your previous data will be cleared. + Remove Conversion Send with swap Transaction sent Prepare to scan card or ring you want to set up. @@ -1525,6 +1545,12 @@ Disconnect all Text about discnected all dApps Disconect All dApps + Try pairing again with a fresh URI + Invalid dApp domain + %s does not specify any blockchains — neither required nor optional.\nPlease ensure you used the correct URI + No networks + Please, generate a new URI and attempt connecting again + Connection proposal expired Estimated wallet changes The transaction couldn\'t be simulated. Please proceed with caution. Estimation is not supported for %s @@ -1535,18 +1561,19 @@ Add the %s network to your portfolio for this wallet The wallet has no required networks New connection - Connect your wallet to a different dApps + Connect your wallet to different dApps No sessions No wallet changes detected Potential risks or malicious behavior have been detected. Connecting or signing transactions may lead to loss of funds. Known security risk + Open Web3 app and chose WalletConnect option Request from Sign anyway Signature Type At least one network is required for dApp connection Specify selected networks Successfully signed - Wallet connect + WalletConnect To Transaction request Transaction request diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index bfd1192118..c9e11ec7fb 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -24,6 +24,7 @@ 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 +64,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)) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt index e9aa7aa21c..b41e493f67 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt @@ -114,6 +114,7 @@ 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) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt index c1dcb8cf95..7c566be7c6 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt @@ -216,6 +216,9 @@ internal class WcPairModel @Inject constructor( private fun processError(error: WcPairError) { val alert = when (error) { + is WcPairError.InvalidDomainURL -> { + WcAppInfoRoutes.Alert.Type.InvalidDomain + } is WcPairError.UnsupportedDApp -> { WcAppInfoRoutes.Alert.Type.UnsupportedDApp(error.appName) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt index 27a3879932..226ececbbc 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt @@ -32,6 +32,7 @@ internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent, Route { 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() diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt index 095ccd662e..a17284a34d 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt @@ -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 { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index c9f6f86182..49aadb776f 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -54,10 +54,18 @@ internal class WcSendTransactionUMConverter @Inject constructor( isLoading = value.signState.domainStep == WcSignStep.Signing, address = WcAddressConverter.convert(value.context.derivationState), transactionValidationResult = value.securityCheck?.result?.validation, - sendEnabled = value.feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null, + sendEnabled = when (value.feeState) { + WcTransactionFeeState.None -> feeErrorNotification == null + is WcTransactionFeeState.Success -> { + value.feeSelectorUM is FeeSelectorUM.Content && feeErrorNotification == null + } + }, feeErrorNotification = feeErrorNotification, ), - feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading, + feeSelectorUM = when (value.feeState) { + WcTransactionFeeState.None -> FeeSelectorUM.Loading + is WcTransactionFeeState.Success -> value.feeSelectorUM ?: FeeSelectorUM.Loading + }, transactionRequestInfo = WcTransactionRequestInfoUM( blocks = buildList { addAll( From d00064cee3e3e6047f192eee239c2e8fd513a45e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Aug 2025 18:15:10 +0500 Subject: [PATCH 18/40] Updated on 2026-08-14 --- .../com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt | 2 +- .../com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index c9e11ec7fb..0e78ed19ce 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -65,7 +65,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( } val dAppUri = URI(sdkSessionProposal.url) - if (!dAppUri.host.isNullOrEmpty()) { + if (dAppUri.host.isNullOrEmpty()) { emit(WcPairState.Error(WcPairError.InvalidDomainURL)) return@flow } diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt index df3b560b15..573d2949bc 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt @@ -47,7 +47,7 @@ internal class DefaultWcPairUseCaseTest { pairingTopic = "", name = "", description = "", - url = "", + url = "https://react-app.walletconnect.com/", icons = listOf(), redirect = "", requiredNamespaces = mapOf(), From b58df2dfb713feae2a301260695c78bf5ba76712 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Aug 2025 12:31:22 +0300 Subject: [PATCH 19/40] Updated on 2026-08-14 --- .../java/com/tangem/core/ui/components/fields/PinTextField.kt | 2 +- .../impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt | 3 +++ .../v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt index fa00fb9959..f46d5a579c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt @@ -58,7 +58,7 @@ fun PinTextField( } .focusRequester(focusRequester), keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number, + keyboardType = KeyboardType.NumberPassword, imeAction = ImeAction.Done, ), singleLine = true, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt index 6c8cb1fc1b..d4c055fcda 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt @@ -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) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt index cb2d5993c8..094a37d884 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt @@ -153,7 +153,7 @@ private fun PinCode( } }, keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number, + keyboardType = KeyboardType.NumberPassword, imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions( From e3a7ba0a777ba35f00f444e231cc412d8e80769e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Aug 2025 16:31:49 +0500 Subject: [PATCH 20/40] Updated on 2026-08-14 --- .../tap/di/domain/TransactionDomainModule.kt | 10 +++ .../DefaultTransactionRepository.kt | 20 ++++++ .../ethereum/WcEthSignTransactionUseCase.kt | 2 +- .../WcSolanaSignAllTransactionUseCase.kt | 6 +- .../solana/WcSolanaSignTransactionUseCase.kt | 6 +- .../transaction/TransactionRepository.kt | 14 ++++ .../usecase/PrepareAndSignUseCase.kt | 70 +++++++++++++++++++ gradle/tangem_dependencies.toml | 2 +- 8 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index eb56a8d16c..d9a7d2b4ad 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -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 { @@ -184,6 +185,15 @@ internal object TransactionDomainModule { return PrepareForSendUseCase(transactionRepository, cardSdkConfigRepository) } + @Provides + @Singleton + fun providePrepareAndSignUseCase( + transactionRepository: TransactionRepository, + cardSdkConfigRepository: CardSdkConfigRepository, + ): PrepareAndSignUseCase { + return PrepareAndSignUseCase(transactionRepository, cardSdkConfigRepository) + } + @Provides @Singleton fun provideSignUseCase( diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index a9d3b05d9b..7179221efb 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -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, + 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( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt index b6795bf29b..8cfe678df0 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt @@ -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, diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt index 8e91c2c8dd..d8b9e39468 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignAllTransactionUseCase.kt @@ -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>.onSign(state: WcSignState>) { - 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())) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt index 282125a721..c1d301135a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt @@ -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.onSign(state: WcSignState) { - 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())) } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index 62feb67e90..f6171137cd 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -109,4 +109,18 @@ interface TransactionRepository { userWalletId: UserWalletId, network: Network, ): com.tangem.blockchain.extensions.Result> + + suspend fun prepareAndSign( + transactionData: TransactionData, + signer: TransactionSigner, + userWalletId: UserWalletId, + network: Network, + ): com.tangem.blockchain.extensions.Result + + suspend fun prepareAndSignMultiple( + transactionData: List, + signer: TransactionSigner, + userWalletId: UserWalletId, + network: Network, + ): com.tangem.blockchain.extensions.Result> } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt new file mode 100644 index 0000000000..4fecd3147f --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/PrepareAndSignUseCase.kt @@ -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 { + 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, + userWallet: UserWallet, + network: Network, + ): Either> { + 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 + } +} \ No newline at end of file diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 1018ab6255..03c2b9d8b5 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.27.0-1142" +tangemBlockchainSdk = "releases-5.27.0-1147" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.27.0-513" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From a1b5761bf3c841ad0419768b8b1ac11f53c37c94 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Aug 2025 12:42:13 +0700 Subject: [PATCH 21/40] Updated on 2026-08-14 --- .../walletconnect/network/ethereum/WcEthNetwork.kt | 12 ++++++++++-- .../domain/walletconnect/model/WcEthAddChain.kt | 5 +++++ .../domain/walletconnect/model/WcEthMethod.kt | 2 +- .../model/WcEthSignTypedDataParams.kt | 14 +++++++------- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index 0f9e21543f..fe375b57d6 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -6,9 +6,11 @@ import arrow.core.left import arrow.core.right import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.extensions.hexToInt import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey +import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork.NamespaceConverter.Companion.ETH_NAMESPACE_KEY import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext @@ -113,8 +115,10 @@ internal class WcEthNetwork( .getOrElse { return it.left() } ?.firstOrNull() ?.let { + val caip2 = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${it.chainId.hexToInt()}") + ?: return null.right() val newNetwork = networksConverter - .mainOrAnyWalletNetworkForRequest(it.chainId, wallet) + .mainOrAnyWalletNetworkForRequest(caip2.raw, wallet) ?: return null.right() WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork).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( diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt index 9daf8154a1..33633558c4 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthAddChain.kt @@ -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, ) \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt index 24a4878ec4..afb348e77e 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt @@ -15,7 +15,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( diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt index 0054a9c68c..b14e6af6df 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt @@ -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>, ) { @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) From 26ba1b1c4cec4ae6ab2d4f23354e48f97e300728 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Aug 2025 18:29:37 +0700 Subject: [PATCH 22/40] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 03c2b9d8b5..590764ebe8 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.27.0-1147" +tangemBlockchainSdk = "releases-5.27.0-1148" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.27.0-513" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 58b345126c045af04de02bbbf88c564f1be22904 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 14 Aug 2025 20:10:29 +0400 Subject: [PATCH 23/40] Updated on 2026-08-14 --- .../wallet/child/wallet/model/WalletModel.kt | 15 ++++++++++++--- .../wallet/model/intents/WalletClickIntents.kt | 5 +++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index e62305cd29..467321629b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -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 @@ -383,9 +384,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 +503,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 +548,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 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt index a7454d94db..55fd1893e3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt @@ -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) } } From c729a406df7fff7043d5c4ad594dcb7082c6ade4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Aug 2025 12:52:02 +0300 Subject: [PATCH 24/40] Updated on 2026-08-14 --- .../tangem/plugin/configuration/model/BuildConfigField.kt | 7 ------- .../com/tangem/plugin/configuration/model/BuildType.kt | 5 ----- 2 files changed, 12 deletions(-) diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt index 17f61ecdd2..058a4ccb06 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildConfigField.kt @@ -15,13 +15,6 @@ internal sealed class BuildConfigField(val type: String, val name: String, val v value = "\"$value\"", ) - // TODO remove - class TestActionEnabled(isEnabled: Boolean) : BuildConfigField( - type = "Boolean", - name = "TEST_ACTION_ENABLED", - value = isEnabled.toString(), - ) - class LogEnabled(isEnabled: Boolean) : BuildConfigField( type = "Boolean", name = "LOG_ENABLED", diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt index bac38b3402..d7f00aa00a 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/BuildType.kt @@ -25,7 +25,6 @@ internal enum class BuildType( appIdSuffix = "debug", configFields = listOf( BuildConfigField.Environment(value = "dev"), - BuildConfigField.TestActionEnabled(isEnabled = true), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), BuildConfigField.MockDataSource(isEnabled = false), @@ -47,7 +46,6 @@ internal enum class BuildType( versionSuffix = "mocked", configFields = listOf( BuildConfigField.Environment(value = "dev"), - BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = false), BuildConfigField.MockDataSource(isEnabled = true), @@ -71,7 +69,6 @@ internal enum class BuildType( versionSuffix = "internal", configFields = listOf( BuildConfigField.Environment(value = "prod"), - BuildConfigField.TestActionEnabled(isEnabled = true), BuildConfigField.LogEnabled(isEnabled = true), BuildConfigField.TesterMenuAvailability(isEnabled = true), BuildConfigField.MockDataSource(isEnabled = false), @@ -92,7 +89,6 @@ internal enum class BuildType( versionSuffix = "external", configFields = listOf( BuildConfigField.Environment(value = "prod"), - BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), BuildConfigField.MockDataSource(isEnabled = false), @@ -111,7 +107,6 @@ internal enum class BuildType( id = "release", configFields = listOf( BuildConfigField.Environment(value = "prod"), - BuildConfigField.TestActionEnabled(isEnabled = false), BuildConfigField.LogEnabled(isEnabled = false), BuildConfigField.TesterMenuAvailability(isEnabled = false), BuildConfigField.MockDataSource(isEnabled = false), From 9278f6f6277609d692ac7046f11ac9df3298751a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Aug 2025 17:05:51 +0500 Subject: [PATCH 25/40] Updated on 2026-08-14 --- .../impl/presentation/state/InnerYieldBalanceState.kt | 1 + .../presentation/state/converters/BalanceItemConverter.kt | 1 + .../state/converters/RewardsValidatorStateConverter.kt | 1 + .../state/helpers/StakingFeeTransactionLoader.kt | 8 ++++---- .../state/previewdata/InitialStakingStatePreview.kt | 1 + .../validator/ValidatorSelectChangeTransformer.kt | 4 ++++ 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index a4b3ea96fd..7b669ddab9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -34,6 +34,7 @@ internal data class BalanceState( val validator: Yield.Validator?, val pendingActions: ImmutableList, val isPending: Boolean, + val validatorAddress: String?, ) @Immutable diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 6b747f4dbd..8c08dc5acf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -68,6 +68,7 @@ internal class BalanceItemConverter( pendingActions = value.pendingActions.toPersistentList(), isClickable = value.isClickable(), isPending = value.isPending, + validatorAddress = value.validatorAddress, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index 4fefa8ca21..1a23a879e8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -91,6 +91,7 @@ internal class RewardsValidatorStateConverter( isClickable = true, type = balance.type, isPending = balance.isPending, + validatorAddress = balance.validatorAddress, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index fe8e42f2a8..1b9528d027 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -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 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index d7af6496e2..7ad9912c1a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -100,6 +100,7 @@ internal object InitialStakingStatePreview { type = BalanceType.STAKED, subtitle = null, isPending = false, + validatorAddress = "", ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt index f9f71dd51e..9ef8d57a41 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt @@ -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(), From c39f617dd6a5bd9dd23426ea566bcc26664cf600 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Aug 2025 18:06:28 +0500 Subject: [PATCH 26/40] Updated on 2026-08-14 --- .../wallets/usecase/GetIsBiometricsEnabledUseCase.kt | 11 ----------- .../feature/wallet/child/wallet/model/WalletModel.kt | 10 ++++++++-- 2 files changed, 8 insertions(+), 13 deletions(-) delete mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt deleted file mode 100644 index b57e8d3ec8..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetIsBiometricsEnabledUseCase.kt +++ /dev/null @@ -1,11 +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 -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index 467321629b..14324845f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -85,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, @@ -213,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 From ad19b86697b4a973e249b6ea2a7652053d5eb5f9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 11:52:58 +0500 Subject: [PATCH 27/40] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 12 +++++++++--- core/res/src/main/res/values-es/strings.xml | 7 ++++--- core/res/src/main/res/values-fr/strings.xml | 5 ++++- core/res/src/main/res/values-ja/strings.xml | 14 +++++++++++--- core/res/src/main/res/values-ru/strings.xml | 11 +++++++---- core/res/src/main/res/values-uk-rUA/strings.xml | 8 +++++--- core/res/src/main/res/values/strings.xml | 14 +++++++++++--- .../transaction/model/WcSignTransactionModel.kt | 6 ++++-- 8 files changed, 55 insertions(+), 22 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 7bc007eb36..c9acb51a42 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -118,6 +118,7 @@ Zugang verweigert Zum Portfolio hinzufügen Token hinzufügen + Vertragsadresse Alle Erlauben Betrag @@ -946,9 +947,13 @@ Die Gebühr geht über die Bilanz hinaus Der Gesamtbetrag geht über die Bilanz hinaus Tauschen und senden + Mit der Konvertierung fortfahren? Dadurch werden Deine vorherigen Daten gelöscht. + Das Senden einer anderen Währung führt zu deren unwiderruflichem Verlust. + Wähle das richtige Empfängernetzwerk Sende uns ein Token, und wir konvertieren es unterwegs. Dein Empfänger erhält genau das, was er braucht – nahtlos. Wird an den Empfänger gesendet Zu erhaltender Betrag + Möchtest Du die Konvertierung wirklich abbrechen? Deine bisherigen Daten werden gelöscht. Senden mit Swap Transaktion gesendet Bereite das Scannen der Karte oder Ring vor, die du einrichten möchtest. @@ -1403,11 +1408,12 @@ Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. + Tangem Wallet unterstützt derzeit nicht %s Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht. Nicht unterstützte Netzwerke - Tangem unterstützt ein erforderliches Netzwerk um %s + Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. %s Verifizierte Domain Falsche Karte oder falscher Ring in der App ausgewählt Wir haben eine Art Problem @@ -1431,7 +1437,7 @@ Daten kopieren Benutzerdefinierter Freibetrag Alle trennen - Text über die Trennung aller dApps + Alle dApp-Sitzungen werden getrennt. Ihre Wallet wird nicht mehr mit dApps verbunden sein. Alle dApps trennen Versuchen Sie erneut, mit einer neuen URI zu koppeln Ungültige dApp-Domain @@ -1455,7 +1461,7 @@ Transaktionsanfrage Transaktionsanfrage Unbegrenzte Menge - Wallet verbinden + WalletConnect Verwerfen Du hast eine unterbrochene Sicherung. Möchtest du diese fortsetzen? Ja, fortsetzen diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 8d38231daf..3810328a13 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -115,6 +115,7 @@ Acceso denegado Añadir al portafolio Agregar token + Dirección Todos Autorizar Montante @@ -1353,7 +1354,7 @@ Hemos encontrado un error desconocido Actualmente, Tangem no es compatible con una red requerida por %s. Redes no compatibles - Tangem soporta una red requerida por %s + Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. %s Dominio verificado Se seleccionó una tarjeta o un anillo incorrectos en la app Tenemos algún tipo de problema @@ -1381,7 +1382,7 @@ Asignación personalizada dApp desconectada Desconectar todo - Texto sobre desconexión de todas las dApps + Todas las sesiones de dApp se desconectarán. Su billetera ya no estará vinculada a ninguna dApp. Desconectar todas las dApps Intente emparejar nuevamente con una URI nueva Dominio de dApp no válido @@ -1417,7 +1418,7 @@ Cantidad ilimitada Asegúrese de que cada intento de emparejamiento utiliza un URI nuevo y único URI ya utilizado - Wallet connect + WalletConnect Transacción sospechosa Ignorar Tiene un backup interrumpido. ¿Quiere reanudarlo? diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 00b0250a97..a24b04d401 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -97,6 +97,7 @@ Accès refusé Ajouter au portfolio Ajouter un jeton + Adresse Tous Permettre Montant @@ -1323,6 +1324,7 @@ dApp non prise en charge Code d\'erreur : 8 005. Si le problème persiste, n\'hésitez pas à contacter notre support. Nous avons rencontré une erreur inconnue + 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 Autoriser à dépenser Adresse Chargement @@ -1332,7 +1334,7 @@ Contenu Copier les données Déconnecter tout - Texte sur la déconnexion de toutes les dApps + Toutes les sessions dApp seront déconnectées. Votre portefeuille ne sera plus lié à aucune dApp. Déconnecter toutes les dApps Essayez de jumeler à nouveau avec un nouvel URI Domaine dApp invalide @@ -1353,6 +1355,7 @@ Demande de transaction Demande de transaction Montant illimité + WalletConnect Ignorer Vous avez une sauvegarde interrompue. Voulez-vous la reprendre ? Oui, reprendre diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index ff32b8bf8b..a66d2ba966 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -12,10 +12,13 @@ ウォレットのロックを解除するには、 %s桁のアクセスコードを設定します。 アクセスコードの作成 アクセスコード + 回復する + アーカイブ済み アカウントをアーカイブする アーカイブ このアカウントをアーカイブしますが、いつでも復元できます。 アカウント + アカウント番号%s — アドレス導出に使用されます。 アカウントを追加 保存 アカウント名 @@ -146,6 +149,7 @@ アクセスが拒否されました ポートフォリオに追加 トークンを追加 + アドレス すべて 許可する 金額 @@ -286,6 +290,7 @@ 取引状況 取引 送金 + データを読み込めません… わかりました エラーが発生しました。もう一度お試しください。 アクセスできません @@ -983,10 +988,13 @@ スワップして送信 変換を続行しますか? これにより以前のデータは消去されます。 変換を確定 + その他の通貨を送信すると、取り返しのつかない損失が発生します。 + 正しい受信者ネットワークを選択してください トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。 受信者は受け取ります 受取人へ 受取金額 + 受信者は%sを取得します 変換をキャンセルしてもよろしいですか?以前のデータは消去されます。 変換を削除 スワップして送信 @@ -1447,7 +1455,7 @@ 不明なエラーが発生しました Tangemは現在%sで必要なネットワークをサポートしていません。 未対応のネットワーク - Tangemは%sで必要なネットワークをサポートします + このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。%s 検証済みドメイン アプリで間違ったカードまたはリングが選択されました 問題が起きています @@ -1475,7 +1483,7 @@ 使用可能量の設定 dAppが接続解除されました すべての接続を解除する - すべてのdAppsの接続解除に関するテキスト + すべてのdAppセッションが切断されます。ウォレットはどのdAppにも接続されなくなります。 すべてのdAppを接続解除する 新しいURIで、再度ペアリングを試してください 無効なdAppドメイン @@ -1511,7 +1519,7 @@ 無制限 各ペアリング試行で、新しくユニークなURIが使用されていることを確認します URIはすでに使用されています - ウォレットコネクト + WalletConnect 不審な取引 破棄 バックアップが中断されました。再開しますか? diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 5b238275be..6bef7c8991 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -94,6 +94,7 @@ Доступ запрещен Добавить в портфель Добавить токен + Адрес Все Разрешить Сумма @@ -230,6 +231,7 @@ Статус транзакции Транзакции Перевод + Невозможно загрузить данные… Я понял Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно @@ -1316,12 +1318,12 @@ Код ошибки: %s. Если проблема сохраняется, обратитесь в нашу службу поддержки. Если проблема сохраняется, обратитесь в нашу службу поддержки Мы обнаружили неизвестную ошибку - Кошелек Tangem.в настоящий момент не поддерживает %s + Кошелек Tangem в настоящий момент не поддерживает %s Неподдерживаемый dApp Мы обнаружили неизвестную ошибку Tangem в настоящее время не поддерживает необходимую сеть для %s Неподдерживаемые сети - Tangem поддерживает сеть, необходимую для %s + Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. %s Верифицированный домен Выбрана не верная карта или кольцо Похоже, возникла проблема @@ -1349,6 +1351,7 @@ Настраиваемый лимит dApp отключен Отключить все + Все сессии dApp будут отключены. Ваш кошелёк больше не будет связан ни с одним dApp. Отключить все dApp Попробуйте соединиться снова, используя новый URI Недействительный домен dApp @@ -1356,7 +1359,7 @@ Нет сетей Пожалуйста, сгенерируйте новый URI и попробуйте подключиться снова Предложение подключения истекло - Предварительные изменения + Прогнозируемые изменения Не удалось выполнить симуляцию транзакции. Пожалуйста, действуйте с осторожностью. Оценка не поддерживается для %s Предложено %s @@ -1383,7 +1386,7 @@ Безлимитное количество Убедитесь, что каждая попытка сопряжения использует новый и уникальный URI. URI уже используется - Подключение кошелька + WalletConnect Подозрительная транзакция Отказаться Вы не закончили резервное копирование. Хотите продолжить? diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 79d1d3c092..e8b9b18fbc 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -94,6 +94,7 @@ Доступ заборонено Додати у портфель Додати токен + Адреса Усе Дозволити Сума @@ -1301,9 +1302,10 @@ Сеанс Wallet Connect було завершено Код помилки: %s. Якщо проблема зберігається, зверніться до нашої служби підтримки. Ми зіткнулися з невідомою помилкою + Tangem Wallet наразі не підтримує %s Tangem наразі не підтримує необхідну мережу для %s. Непідтримувані мережі - Tangem підтримує мережу, необхідну для %s + Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. %s Верифікований домен Обрана не вірна картка або кільце Схоже, виникла проблема @@ -1327,7 +1329,7 @@ Копіювати дані dApp відключено Розʼєднати все - Відключити всі dApps + Усі сесії dApp буде відключено. Ваш гаманець більше не буде пов’язаний із жодним dApp. Відключити всі dApps Спробуйте ще раз з новим URI Недійсний домен dApp @@ -1353,7 +1355,7 @@ До Запит транзакції Запит транзакції - Підключення гаманця + WalletConnect Відмовитися Ви не завершили резервне копіювання. Бажаєте продовжити? Так, поновити diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4089de8ca8..c81754117c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -18,6 +18,7 @@ Archive You are archiving this account, but you can always get it back. Account + Account #%s — used for address derivation. Add account Save Account name @@ -70,8 +71,10 @@ Go to settings to enable biometric authentication in the Tangem App Enable biometric authentication Disabling %1$s will require you to enter your passcode to unlock the app and to interact with your wallet. + You’ll be asked for your wallet’s access code later so we can securely store it for future use This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. Removing the saved devices deletes all the saved wallets and their access codes from the app. + This will delete all the saved wallet access codes. Any further interaction with the wallet will require submitting the access code. Require Access Code This option disables biometric authentication for sensitive actions. You will be required to enter your access code every time, such as when signing a transaction. Save Access Code @@ -151,6 +154,7 @@ Access denied Add to portfolio Add token + Address All Allow Amount @@ -295,6 +299,7 @@ Transaction status Transactions Transfer + Unable to load the data… I understand There was an error. Please try again. Unreachable @@ -490,6 +495,7 @@ Stay up to date with the latest features and news Seed phrase backup Create Mobile Wallet + This recovery phrase has already been imported Mobile Wallet This information was generated with AI.\nTap here, if you find any errors. To change the access code tap the card or ring as shown above and do not remove until the end of the operation @@ -1001,6 +1007,8 @@ Swap and send Proceed with conversion? This will clear your previous data. Confirm Conversion + Sending any other currency will result in its irreversible loss. + Select the correct recipient network Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly. Recipient will receive To recipient @@ -1515,7 +1523,7 @@ We\'ve encountered unknown error Tangem does not currently support a required network by %s. Unsupported networks - Tangem support a required network by %s + This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s Verified domain Wrong card or ring selected in the App We\'ve got some kind of problem @@ -1543,7 +1551,7 @@ Custom allowance dApp disconnected Disconnect all - Text about discnected all dApps + All dApp sessions will be disconnected. Your wallet will no longer be linked to any dApps. Disconect All dApps Try pairing again with a fresh URI Invalid dApp domain @@ -1580,7 +1588,7 @@ Unlimited Amount Ensure that each pairing attempt uses a fresh and unique URI URI already used - Wallet connect + WalletConnect Suspicious transaction Discard You have an interrupted backup. Do you want to resume? diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index ccc4413ef4..9f3a686357 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -131,8 +131,10 @@ internal class WcSignTransactionModel @Inject constructor( } private fun signingIsDone(signState: WcSignState<*>): Boolean { - (signState.domainStep as? WcSignStep.Result)?.result?.let { - showSuccessSignMessage() + (signState.domainStep as? WcSignStep.Result)?.result?.let { result -> + if (result.isRight()) { + showSuccessSignMessage() + } router.pop() return true } From 7d0121c40cd1c1c5781a23c9e0c45465849c9e52 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 13:33:32 +0500 Subject: [PATCH 28/40] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 1 + .../com/tangem/common/routing/AppRoute.kt | 11 ++++++++++- .../routing/AddExistingWalletChildFactory.kt | 2 ++ .../api/PushNotificationsParams.kt | 3 +++ .../analytics/PushNotificationAnalyticEvents.kt | 9 +++++++++ .../impl/model/PushNotificationsModel.kt | 17 +++++++++++------ .../wallet/child/wallet/WalletComponent.kt | 2 ++ 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index dd4108f2e0..82979df7ee 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -384,6 +384,7 @@ internal class ChildFactory @Inject constructor( context = context, params = PushNotificationsParams( modelCallbacks = PushNotificationsModelCallbacksStub(), + source = route.source, ), componentFactory = pushNotificationsComponentFactory, ) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 77a7b1f113..682378bf51 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -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.SerializableIntent @@ -185,7 +186,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( diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt index 26321ced04..a07186b664 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt @@ -1,5 +1,6 @@ package com.tangem.features.hotwallet.addexistingwallet.root.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.im.port.AddExistingWalletImportComponent @@ -51,6 +52,7 @@ internal class AddExistingWalletChildFactory @Inject constructor( context = childContext, params = PushNotificationsParams( modelCallbacks = PushNotificationsModelCallbacksStub(), + source = AppRoute.PushNotification.Source.Onboarding, ), ) AddExistingWalletRoute.SetupFinished -> MobileWalletSetupFinishedComponent( diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt index 27bc4d7131..9e347a47ba 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt @@ -1,6 +1,9 @@ package com.tangem.features.pushnotifications.api +import com.tangem.common.routing.AppRoute + data class PushNotificationsParams( val isBottomSheet: Boolean = false, val modelCallbacks: PushNotificationsModelCallbacks, + val source: AppRoute.PushNotification.Source, ) \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt index 7977e16e18..8ffec6c23c 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/analytics/PushNotificationAnalyticEvents.kt @@ -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( diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt index ebf625c1ee..dab4f52d73 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt @@ -37,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( @@ -44,6 +49,10 @@ internal class PushNotificationsModel @Inject constructor( ), ) + init { + analyticHandler.send(PushNotificationAnalyticEvents.NotificationsScreenOpened(source)) + } + val state = _state.asStateFlow() override fun onAllowClick() { @@ -52,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() { @@ -63,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) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt index 07a629a880..8ee7b2c6c2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt @@ -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, ), ) } From 5284873a3ed89f0aaa769f77b042887c780c4a40 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 14:06:58 +0500 Subject: [PATCH 29/40] Updated on 2026-08-14 --- .../tangem/features/disclaimer/impl/model/DisclaimerModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt index 2c45bfb510..a34f346733 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/model/DisclaimerModel.kt @@ -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) From c8a163fd0a0b56f6021805a14dccf2c19f9655c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 13:32:41 +0300 Subject: [PATCH 30/40] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 590764ebe8..57e50cf4ac 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -7,7 +7,7 @@ tangemBlockchainSdk = "releases-5.27.0-1148" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.27.0-513" +tangemCardSdk = "releases-5.27.0-519" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 65ace6c5f2ff48ca098d5f7918a5b32b8a6ed975 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 14:15:30 +0700 Subject: [PATCH 31/40] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 6 +++ .../ethereum/WcEthAddNetworkUseCase.kt | 44 ++++++++++++++++ .../network/ethereum/WcEthNetwork.kt | 35 ++++++------- .../ethereum/WcEthSwitchNetworkUseCase.kt | 51 +++++++++++++++++++ .../request/DefaultWcRequestService.kt | 1 + .../utils/WcNetworksConverter.kt | 5 ++ .../domain/walletconnect/model/WcEthMethod.kt | 7 +-- .../walletconnect/model/WcMethodName.kt | 1 + .../walletconnect/model/WcRequestError.kt | 5 ++ .../usecase/method/WcAddNetworkUseCase.kt | 7 +++ .../usecase/method/WcSwitchNetworkUseCase.kt | 17 +++++++ .../connections/components/AlertsComponent.kt | 18 +++++++ .../routing/DefaultWcRoutingComponent.kt | 28 +++++++++- .../connections/routing/WcInnerRoute.kt | 12 +++++ .../connections/routing/WcRoutingModel.kt | 2 + .../connections/ui/AlertsModalBottomSheet.kt | 42 +++++++++++++-- .../di/WalletConnectModelModule.kt | 6 +++ .../chain/WcSwitchNetworkComponent.kt | 21 ++++++++ .../converter/WcHandleMethodErrorConverter.kt | 4 ++ .../transaction/model/WcAddNetworkModel.kt | 31 ++++++----- .../transaction/model/WcSwitchNetworkModel.kt | 45 ++++++++++++++++ 21 files changed, 349 insertions(+), 39 deletions(-) create mode 100644 data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt create mode 100644 domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt create mode 100644 features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/chain/WcSwitchNetworkComponent.kt create mode 100644 features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index c81754117c..ec20bf2c8f 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1505,12 +1505,16 @@ Enable Trustline A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve. Trustline Required + The required network %s is not added to your portfolio. Add it first, then proceed with the connection. + Add network to portfolio Malicious domain Unknown domain Connect anyway Timeout error. Please, try again later. Failed to establish WalletConnect This domain cannot be verified. Check the request carefully approving. + To continue, please reconnect your dApp session with the required network %s. + Network not connected Please return to your browser and reconnect via WalletConnect. Wallet Connect session was disconnected Sign anyway @@ -1521,6 +1525,8 @@ Unsupported dApp Error code: 8 005. If the problem persists — feel free to contact our support. We\'ve encountered unknown error + This network %s is not supported by Tangem Wallet and cannot be connected. + Unsupported network Tangem does not currently support a required network by %s. Unsupported networks This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt index 0bc201fd7a..5c988f8a8e 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt @@ -1,9 +1,17 @@ package com.tangem.data.walletconnect.network.ethereum import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.extensions.hexToInt +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 @@ -16,6 +24,7 @@ import dagger.assisted.AssistedInject internal class WcEthAddNetworkUseCase @AssistedInject constructor( private val respondService: WcRespondService, + addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory, @Assisted val context: WcMethodUseCaseContext, @Assisted override val method: WcEthMethod.AddEthereumChain, ) : WcAddNetworkUseCase { @@ -31,6 +40,14 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor( else -> WcNetworkDerivationState.Single } + private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context) + + override suspend fun invoke(): Either { + return addSwitchCommonDelegate + .commonChecks(method.rawChain.chainId) + .map { addedNetwork -> WcAddNetworkUseCase.AddNetwork(addedNetwork) } + } + override suspend fun approve(): Either { return respondService.respond(rawSdkRequest, "") } @@ -43,4 +60,31 @@ 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 + + suspend fun commonChecks(hexChainId: String): Either { + val caip2 = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${hexChainId.hexToInt()}") + ?: 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 + } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index fe375b57d6..a14460a566 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -6,17 +6,14 @@ import arrow.core.left import arrow.core.right import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.extensions.hexToInt import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey -import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork.NamespaceConverter.Companion.ETH_NAMESPACE_KEY import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson 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 @@ -46,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) @@ -56,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() @@ -67,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( @@ -83,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 { + private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest): Either { val rawParams = request.request.params return when (this) { WcEthMethodName.EthSign, @@ -111,16 +110,17 @@ internal class WcEthNetwork( } } ?: return null.right() - WcEthMethodName.AddEthereumChain -> moshi.fromJson>(rawParams) + WcEthMethodName.AddEthereumChain, + WcEthMethodName.SwitchEthereumChain, + -> moshi.fromJson>(rawParams) .getOrElse { return it.left() } ?.firstOrNull() ?.let { - val caip2 = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${it.chainId.hexToInt()}") - ?: return null.right() - val newNetwork = networksConverter - .mainOrAnyWalletNetworkForRequest(caip2.raw, 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() } @@ -170,5 +170,6 @@ internal class WcEthNetwork( val sendTransaction: WcEthSendTransactionUseCase.Factory, val signTransaction: WcEthSignTransactionUseCase.Factory, val addNetwork: WcEthAddNetworkUseCase.Factory, + val switchNetwork: WcEthSwitchNetworkUseCase.Factory, ) } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt new file mode 100644 index 0000000000..0835594dfe --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt @@ -0,0 +1,51 @@ +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 { + return addSwitchCommonDelegate + .commonChecks(method.rawChain.chainId) + .map { addedNetwork -> WcSwitchNetworkUseCase.SwitchNetwork(addedNetwork) } + } + + override fun reject() { + respondService.rejectRequestNonBlock(rawSdkRequest) + } + + @AssistedFactory + interface Factory { + fun create(context: WcMethodUseCaseContext, method: WcEthMethod.SwitchEthereumChain): WcEthSwitchNetworkUseCase + } +} \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt index 4c50b189fd..db8788c05a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt @@ -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) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt index 6a80763cc9..4b49445c7f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -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, diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt index afb348e77e..34b43e11bb 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt @@ -1,7 +1,5 @@ package com.tangem.domain.walletconnect.model -import com.tangem.domain.models.network.Network - sealed interface WcEthMethod : WcMethod { data class MessageSign( @@ -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 } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt index 6ae4b431ac..982c1cacf1 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt @@ -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 { diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt index 3c1cfe610a..e0ad853f77 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt @@ -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") } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt index 787bea6e93..35223ba040 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt @@ -1,12 +1,19 @@ 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 suspend fun approve(): Either fun reject() + + data class AddNetwork( + val network: Network, + ) } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt new file mode 100644 index 0000000000..5c2066049a --- /dev/null +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt @@ -0,0 +1,17 @@ +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 + fun reject() + + data class SwitchNetwork( + val network: Network, + ) +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt index 6fd6fde73b..fd682083d3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt @@ -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() } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt index 42b336932f..30c6d10c14 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt @@ -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() }, ), ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt index 4cabfc0547..bc9f386e88 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt @@ -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 } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt index 3c320faab7..5524f954e3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt @@ -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, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt index 7c93a184f5..c7ff306829 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt @@ -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_unsupported_method_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 WcInnerRoute.UnsupportedMethodAlert HandleMethodError.UnknownSession -> WcInnerRoute.WcDappDisconnected + + is HandleMethodError.NotAddedNetwork -> WcInnerRoute.RequiredAddNetwork(value.networkName) + is HandleMethodError.RequiredNetwork -> WcInnerRoute.RequiredReconnectWithNetwork(value.networkName) + is HandleMethodError.TangemUnsupportedNetwork -> WcInnerRoute.TangemUnsupportedNetwork(value.unsupportedNetwork) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 107e983abc..f85c7a6003 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -57,22 +57,27 @@ internal class WcAddNetworkModel @Inject constructor( .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch sendSignatureReceivedAnalytics(useCase) - _uiState.emit( - wcAddEthereumChainUMConverter.convert( - WcAddEthereumChainUMConverter.Input( - useCase = useCase, - actions = WcTransactionActionsUM( - onShowVerifiedAlert = ::showVerifiedAlert, - onDismiss = { cancel(useCase) }, - onSign = { sign(useCase) }, - onCopy = { copyData(useCase.rawSdkRequest.request.params) }, - ), - ), - ), - ) + val either = useCase.invoke() + either + .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } + .map { showUI() } } } + private fun showUI() { + _uiState.value = wcAddEthereumChainUMConverter.convert( + WcAddEthereumChainUMConverter.Input( + useCase = useCase, + actions = WcTransactionActionsUM( + onShowVerifiedAlert = ::showVerifiedAlert, + onDismiss = { cancel(useCase) }, + onSign = { sign(useCase) }, + onCopy = { copyData(useCase.rawSdkRequest.request.params) }, + ), + ), + ) + } + override fun dismiss() { _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt new file mode 100644 index 0000000000..65b63ae8b6 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt @@ -0,0 +1,45 @@ +package com.tangem.features.walletconnect.transaction.model + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.domain.walletconnect.WcRequestUseCaseFactory +import com.tangem.domain.walletconnect.model.HandleMethodError +import com.tangem.domain.walletconnect.usecase.method.WcSwitchNetworkUseCase +import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams +import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class WcSwitchNetworkModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val useCaseFactory: WcRequestUseCaseFactory, +) : Model() { + + private val params = paramsContainer.require() + + init { + modelScope.launch { + val useCase = useCaseFactory.createUseCase(params.rawRequest) + .onLeft { showErrorDialog(it) } + .getOrNull() ?: return@launch + val either = useCase.invoke() + useCase.reject() + either + .onLeft { showErrorDialog(it) } + .map { showErrorDialog(HandleMethodError.RequiredNetwork(it.network.name)) } + } + } + + private fun showErrorDialog(error: HandleMethodError) { + router.push(WcHandleMethodErrorConverter.convert(error)) + } +} \ No newline at end of file From 99a5efd7e2deb0b017a8173005a208d1ad425aa6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 14:59:00 +0700 Subject: [PATCH 32/40] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 ++ .../pair/DefaultWcPairUseCase.kt | 22 +++++++++++++------ .../walletconnect/pair/WcPairSdkDelegate.kt | 2 +- .../domain/walletconnect/model/WcPairError.kt | 1 + .../connections/components/WcPairComponent.kt | 1 + .../connections/model/WcPairModel.kt | 17 +++++--------- .../connections/routes/WcAppInfoRoutes.kt | 1 + .../connections/utils/WcAlertsFactory.kt | 17 ++++++++++++++ 8 files changed, 43 insertions(+), 20 deletions(-) diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ec20bf2c8f..f9238788d1 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1515,6 +1515,8 @@ This domain cannot be verified. Check the request carefully approving. To continue, please reconnect your dApp session with the required network %s. Network not connected + Check your network connection + Request timeout Please return to your browser and reconnect via WalletConnect. Wallet Connect session was disconnected Sign anyway diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index 0e78ed19ce..ad67584bed 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -20,6 +20,7 @@ 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 @@ -119,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) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt index e9665c0673..35902fb41f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt @@ -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", diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt index f04de6251a..2ac1b68531 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt @@ -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) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt index b41e493f67..afa23ea711 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcPairComponent.kt @@ -121,6 +121,7 @@ internal class WcPairComponent( 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) } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt index 7c566be7c6..fe64931b39 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt @@ -216,18 +216,11 @@ internal class WcPairModel @Inject constructor( private fun processError(error: WcPairError) { val alert = when (error) { - 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.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() diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt index 226ececbbc..784e0457ed 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt @@ -36,6 +36,7 @@ internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent, Route { data class UnsupportedDApp(val appName: String) : Type() data class UnsupportedNetwork(val appName: String) : Type() data object UriAlreadyUsed : Type() + data object TimeoutException : Type() } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt index a17284a34d..5ac9d638e6 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcAlertsFactory.kt @@ -127,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 { From 00171d2276daf315c241bb65d3570ce36ddc5ae9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 18:01:55 +0700 Subject: [PATCH 33/40] Updated on 2026-08-14 --- .../ethereum/WcEthAddNetworkUseCase.kt | 62 ++++++++++++++++++- .../ethereum/WcEthSwitchNetworkUseCase.kt | 7 ++- .../utils/WcNetworksConverter.kt | 5 ++ .../utils/WcSdkSessionConverter.kt | 8 +++ .../WcSignUseCaseDelegateTest.kt | 1 + .../model/sdkcopy/WcSdkSession.kt | 10 ++- .../usecase/method/WcAddNetworkUseCase.kt | 1 + .../usecase/method/WcSwitchNetworkUseCase.kt | 1 + .../connections/ui/AlertsModalBottomSheet.kt | 2 +- .../transaction/model/WcAddNetworkModel.kt | 4 +- .../transaction/model/WcSwitchNetworkModel.kt | 8 ++- 11 files changed, 102 insertions(+), 7 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt index 5c988f8a8e..bf9f6674f9 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt @@ -3,7 +3,11 @@ 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 @@ -21,9 +25,12 @@ 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, @@ -45,13 +52,58 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor( override suspend fun invoke(): Either { return addSwitchCommonDelegate .commonChecks(method.rawChain.chainId) - .map { addedNetwork -> WcAddNetworkUseCase.AddNetwork(addedNetwork) } + .map { addedNetwork -> + WcAddNetworkUseCase.AddNetwork( + network = addedNetwork, + isExistInWcSession = addSwitchCommonDelegate.existInWcSession(addedNetwork), + ) + } } override suspend fun approve(): Either { + 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 { + 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) } @@ -69,8 +121,14 @@ internal class WcEthAddSwitchCommonDelegate @AssistedInject constructor( 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 { - val caip2 = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${hexChainId.hexToInt()}") + val caip2 = hexChainIdToCAIP2(hexChainId) ?: return HandleMethodError.UnknownError("Failed to parse CAIP2").left() val generalNetwork = networksConverter.createNetwork(caip2.raw, wallet) if (generalNetwork == null) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt index 0835594dfe..5b94908fa8 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt @@ -37,7 +37,12 @@ internal class WcEthSwitchNetworkUseCase @AssistedInject constructor( override suspend fun invoke(): Either { return addSwitchCommonDelegate .commonChecks(method.rawChain.chainId) - .map { addedNetwork -> WcSwitchNetworkUseCase.SwitchNetwork(addedNetwork) } + .map { addedNetwork -> + WcSwitchNetworkUseCase.SwitchNetwork( + network = addedNetwork, + isExistInWcSession = addSwitchCommonDelegate.existInWcSession(addedNetwork), + ) + } } override fun reject() { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt index 4b49445c7f..a7d1d285fb 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -53,6 +53,11 @@ internal class WcNetworksConverter @Inject constructor( return networks.firstOrNull { !isCustomCoin(it) } ?: networks.firstOrNull() } + suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet): List { + return filterWalletNetworkForRequest(rawChainId, wallet) + .mapNotNull { walletManagersFacade.getDefaultAddress(wallet.walletId, it)?.lowercase() } + } + /** * return all exist derivation networks */ diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt index 0f2e570acd..f588c4f171 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcSdkSessionConverter.kt @@ -10,6 +10,14 @@ internal object WcSdkSessionConverter : Converter + WcSdkSession.Session( + chains = session.chains ?: listOf(), + accounts = session.accounts, + methods = session.methods, + events = session.events, + ) + }, ) } } \ No newline at end of file diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt index e68555c7c1..15049820a7 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/WcSignUseCaseDelegateTest.kt @@ -62,6 +62,7 @@ internal class WcSignUseCaseDelegateTest { connectingTime = 0L, sdkModel = WcSdkSession( topic = "", + namespaces = mapOf(), appMetaData = WcAppMetaData( name = "", description = "", diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt index 9b2d6c45b7..63ea82098f 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/sdkcopy/WcSdkSession.kt @@ -6,4 +6,12 @@ package com.tangem.domain.walletconnect.model.sdkcopy data class WcSdkSession( val topic: String, val appMetaData: WcAppMetaData, -) \ No newline at end of file + val namespaces: Map, +) { + data class Session( + val chains: List, + val accounts: List, + val methods: List, + val events: List, + ) +} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt index 35223ba040..7573141b6d 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt @@ -15,5 +15,6 @@ interface WcAddNetworkUseCase : data class AddNetwork( val network: Network, + val isExistInWcSession: Boolean, ) } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt index 5c2066049a..b7980f5500 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt @@ -13,5 +13,6 @@ interface WcSwitchNetworkUseCase : data class SwitchNetwork( val network: Network, + val isExistInWcSession: Boolean, ) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt index c7ff306829..30370ab112 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt @@ -177,7 +177,7 @@ private fun AlertContentDescription(alert: AlertsComponent.AlertType, modifier: R.string.wc_alert_unsupported_method_description, ) is AlertsComponent.AlertType.RequiredAddNetwork -> stringResourceSafe( - R.string.wc_alert_unsupported_method_description, + R.string.wc_alert_add_network_to_portfolio_description, alert.network, ) is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> stringResourceSafe( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index f85c7a6003..ab284df37a 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -60,7 +60,9 @@ internal class WcAddNetworkModel @Inject constructor( val either = useCase.invoke() either .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } - .map { showUI() } + .map { + if (it.isExistInWcSession) cancel(useCase) else showUI() + } } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt index 65b63ae8b6..d8c49a7508 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt @@ -35,7 +35,13 @@ internal class WcSwitchNetworkModel @Inject constructor( useCase.reject() either .onLeft { showErrorDialog(it) } - .map { showErrorDialog(HandleMethodError.RequiredNetwork(it.network.name)) } + .map { + if (it.isExistInWcSession) { + router.pop() + } else { + showErrorDialog(HandleMethodError.RequiredNetwork(it.network.name)) + } + } } } From 881e5adb9d9e521b755aef15fc5907eeaf555f39 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 14:38:32 +0300 Subject: [PATCH 34/40] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 10 +++++++++- core/res/src/main/res/values-es/strings.xml | 10 +++++++++- core/res/src/main/res/values-fr/strings.xml | 10 +++++++++- core/res/src/main/res/values-ja/strings.xml | 10 +++++++++- core/res/src/main/res/values-ru/strings.xml | 10 +++++++++- core/res/src/main/res/values-uk-rUA/strings.xml | 11 ++++++++++- core/res/src/main/res/values/strings.xml | 2 +- 7 files changed, 56 insertions(+), 7 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index c9acb51a42..c62cee51c8 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1396,12 +1396,18 @@ Trustline aktivieren Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s. Trustline erforderlich + 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. + Netzwerk zum Portfolio hinzufügen Bösartige/ verdächtige Domäne Unbekannte Domäne Trotzdem verbinden Zeitüberschreitungsfehler. Bitte versuche es später erneut. WalletConnect konnte nicht hergestellt werden Diese Domäne kann nicht verifiziert werden. Überprüfe die Anfrage sorgfältig und bestätige diese dann. + Um fortzufahren, verbinden Sie bitte Ihre dApp-Sitzung erneut mit dem erforderlichen Netzwerk %s. + Netzwerk nicht verbunden + Überprüfen Sie Ihre Netzwerkverbindung + Anforderungs-Zeitüberschreitung Bitte kehre zu Deinem Browser zurück und stellen die Verbindung über WalletConnect erneut her. WalletConnect-Sitzung wurde getrennt Trotzdem unterschreiben @@ -1411,9 +1417,11 @@ Tangem Wallet unterstützt derzeit nicht %s Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. + Dieses Netzwerk %s wird von Tangem Wallet nicht unterstützt und kann nicht verbunden werden. + Nicht unterstütztes Netzwerk Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht. Nicht unterstützte Netzwerke - Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. %s + Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. Verifizierte Domain Falsche Karte oder falscher Ring in der App ausgewählt Wir haben eine Art Problem diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 3810328a13..c510d098aa 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1336,12 +1336,18 @@ Habilitar línea de confianza Una línea de confianza debe estar habilitada para recibir este token. La red requiere un %1$s %2$s reserva. Se requiere línea de confianza + La red requerida %s no está añadida a su portafolio. Añádala primero y luego continúe con la conexión. + Agregar red al portafolio Dominio malicioso Dominio desconocido Conectarse de todas formas Error de tiempo de espera. Por favor, inténtalo de nuevo más tarde. Error al establecer WalletConnect Este dominio no puede ser verificado. Compruebe cuidadosamente la solicitud de aprobación. + Para continuar, vuelva a conectar su sesión de dApp con la red requerida %s. + Red no conectada + Verifique su conexión de red + Tiempo de espera de la solicitud agotado Vuelva a su navegador y vuelva a conectarse a través de WalletConnect. La sesión de Wallet Connect se desconectó Firmar de todos modos @@ -1352,9 +1358,11 @@ dApp no compatible Código de error: 8 005. Si el problema persiste, no dudes en contactar con nuestro soporte. Hemos encontrado un error desconocido + Esta red %s no es compatible con Tangem Wallet y no puede conectarse. + Red no compatible Actualmente, Tangem no es compatible con una red requerida por %s. Redes no compatibles - Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. %s + Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. Dominio verificado Se seleccionó una tarjeta o un anillo incorrectos en la app Tenemos algún tipo de problema diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index a24b04d401..5996fd51d5 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1317,14 +1317,22 @@ Activer Trustline Une Trustline doit être activée pour recevoir ce jeton. Le réseau requiert une réserve de %1$s %2$s Trustline requise + Le réseau requis %s n’est pas ajouté à votre portefeuille. Ajoutez-le d’abord, puis poursuivez la connexion. + Ajouter le réseau au portefeuille Domaine malveillant + Pour continuer, veuillez reconnecter votre session dApp avec le réseau requis %s. + Réseau non connecté + Vérifiez votre connexion réseau + Délai d’attente de la requête dépassé Signer quand même Si le problème persiste, n’hésitez pas à contacter notre support. Le portefeuille Tangem ne prend actuellement pas en charge %ss dApp non prise en charge Code d\'erreur : 8 005. Si le problème persiste, n\'hésitez pas à contacter notre support. Nous avons rencontré une erreur inconnue - 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 + Le réseau %s n’est pas pris en charge par Tangem Wallet et ne peut pas être connecté. + Réseau non pris en charge + 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. Autoriser à dépenser Adresse Chargement diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index a66d2ba966..7e06ea9819 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1437,12 +1437,18 @@ トラストラインを有効にする このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。 トラストラインが必要 + 必要なネットワーク %s はポートフォリオに追加されていません。まず追加してから接続を続行してください。 + ネットワークをポートフォリオに追加 悪意のあるドメイン 不明なドメイン とにかく接続する タイムアウトエラーが発生しました。しばらくしてからもう一度お試しください。 WalletConnectを確立できませんでした このドメインは検証できません。承認前にリクエスト内容をよく確認してください。 + 続行するには、必要なネットワーク %s でdAppセッションを再接続してください。 + ネットワークが接続されていません + ネットワーク接続を確認してください + リクエストがタイムアウトしました ブラウザに戻り、WalletConnect経由で再接続してください。 Wallet Connectセッションが接続解除されました とにかくサインする @@ -1453,9 +1459,11 @@ サポートされていないdApp エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました + このネットワーク %s はTangem Walletでサポートされておらず、接続できません。 + サポートされていないネットワーク Tangemは現在%sで必要なネットワークをサポートしていません。 未対応のネットワーク - このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。%s + このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。 検証済みドメイン アプリで間違ったカードまたはリングが選択されました 問題が起きています diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 6bef7c8991..8de88e16ba 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1306,12 +1306,18 @@ Открыть Trustline Чтобы получить этот токен, необходимо включить Trustline. Сеть требует резерв %1$s %2$s. Требуется трастлайн + Требуемая сеть %s не добавлена в ваш портфель. Добавьте её, а затем выполните подключение. + Добавьте сеть в ваш портфель Вредоносный домен Неизвестный домен Всё равно подключиться Ошибка тайм-аута. Пожалуйста, попробуйте позже. Не удалось подключиться через Wallet Connect Этот домен не может быть верифицирован. Внимательно проверьте запрос перед одобрением. + Чтобы продолжить, переподключите сессию dApp с требуемой сетью %s. + Сеть не подключена + Проверьте подключение к сети + Время ожидания запроса истекло Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect. Сессия Wallet Connect была завершена Подписать всё равно @@ -1321,9 +1327,11 @@ Кошелек Tangem в настоящий момент не поддерживает %s Неподдерживаемый dApp Мы обнаружили неизвестную ошибку + Эта сеть %s не поддерживается Tangem Wallet и не может быть подключена. + Неподдерживаемая сеть Tangem в настоящее время не поддерживает необходимую сеть для %s Неподдерживаемые сети - Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. %s + Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. Верифицированный домен Выбрана не верная карта или кольцо Похоже, возникла проблема diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index e8b9b18fbc..ef5cb62c26 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1293,19 +1293,27 @@ Відкрити Trustline Щоб отримати цей токен, потрібно увімкнути Trustline. Мережа вимагає резерв %1$s %2$s. Відкрийте Trustline + Потрібна мережа %s не додана до вашого портфеля. Спочатку додайте її, а потім виконайте підключення. + Додати мережу до портфеля Невідомий домен Все одно підключитися Помилка тайм-ауту. Будь ласка, спробуйте пізніше. Не вдалося зʼєднатися через Wallet Connect Цей домен не може бути підтверджений. Уважно перевірте запит перед схваленням. + Щоб продовжити, перепідключіть сесію dApp із потрібною мережею %s. + Мережа не підключена + Перевірте підключення до мережі + Час очікування запиту вичерпано Будь ласка, поверніться до браузеру і повторно підключіться через WalletConnect. Сеанс Wallet Connect було завершено Код помилки: %s. Якщо проблема зберігається, зверніться до нашої служби підтримки. Ми зіткнулися з невідомою помилкою Tangem Wallet наразі не підтримує %s + Ця мережа %s не підтримується Tangem Wallet і не може бути підключена. + Непідтримувана мережа Tangem наразі не підтримує необхідну мережу для %s. Непідтримувані мережі - Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. %s + Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. Верифікований домен Обрана не вірна картка або кільце Схоже, виникла проблема @@ -1337,6 +1345,7 @@ Немає мереж Будь ласка, згенеруйте новий URI та спробуйте ще раз Термін для з’єднання минув + Прогнозовані зміни Поповніть баланс, щоб покрити комісію мережі Недостатньо %1$s Додайте %s мережі до вашого портфелю для цього гаманця diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f9238788d1..5074ca1886 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1531,7 +1531,7 @@ Unsupported network Tangem does not currently support a required network by %s. Unsupported networks - This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s + This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. Verified domain Wrong card or ring selected in the App We\'ve got some kind of problem From 0a38083fb7d91c602b401a2b66a2072d9fd653f8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 17:23:44 +0300 Subject: [PATCH 35/40] Updated on 2026-08-14 --- .../send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt index b9e53dda49..7d5cc74f54 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/analytics/NFTSendAnalyticHelper.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM 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.common.analytics.CommonSendAnalyticEvents.Companion.NFT_SEND_CATEGORY 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), From ac0302d45249d5664611b7be6c3fed22447b1966 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 21:40:52 +0500 Subject: [PATCH 36/40] Updated on 2026-08-14 --- core/res/src/main/res/values-fr/strings.xml | 38 ++++++++++++++++ core/res/src/main/res/values-ja/strings.xml | 5 ++- .../model/WcSendTransactionModel.kt | 43 ++++++++++++------- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 5996fd51d5..1b64b43e63 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -195,6 +195,7 @@ Rejeter Recharger Renommer + Obligatoire Enregistrez Sauvegarder les modifications Rechercher @@ -1320,27 +1321,55 @@ Le réseau requis %s n’est pas ajouté à votre portefeuille. Ajoutez-le d’abord, puis poursuivez la connexion. Ajouter le réseau au portefeuille Domaine malveillant + Domaine inconnu + Se connecter quand même + Erreur de délai d\'attente. Veuillez réessayer plus tard. + Échec de la connexion à WalletConnect + Ce domaine ne peut pas être vérifié. Vérifiez attentivement la demande avant de l\'approuver. Pour continuer, veuillez reconnecter votre session dApp avec le réseau requis %s. Réseau non connecté Vérifiez votre connexion réseau Délai d’attente de la requête dépassé + Veuillez retourner à votre navigateur et vous reconnecter via WalletConnect. + La session Wallet Connect a été déconnectée. Signer quand même + Code d\'erreur : %s. Si le problème persiste, n\'hésitez pas à contacter notre service d\'assistance. Si le problème persiste, n’hésitez pas à contacter notre support. + Nous avons rencontré une erreur inconnue. Le portefeuille Tangem ne prend actuellement pas en charge %ss dApp non prise en charge Code d\'erreur : 8 005. Si le problème persiste, n\'hésitez pas à contacter notre support. Nous avons rencontré une erreur inconnue Le réseau %s n’est pas pris en charge par Tangem Wallet et ne peut pas être connecté. Réseau non pris en charge + Tangem ne prend actuellement pas en charge le réseau requis par %s. + Réseaux non pris en charge 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. + Domaine vérifié + Carte ou bague incorrecte sélectionnée dans l\'application + Nous avons un problème. + Toutes les dApps sont déconnectées Autoriser à dépenser Adresse + Connecter Chargement + Réseau + Réseaux Illimité + Wallet + Application connectée Réseaux connectés + Connecté à %1$s + Consultez le solde et l\'activité de votre portefeuille + Signer des transactions sans vous en informer + Demander l\'accord pour les transactions + Ne pourra pas + Souhaite + Demande de connexion Connexions Contenu Copier les données + dApp déconnectée Déconnecter tout Toutes les sessions dApp seront déconnectées. Votre portefeuille ne sera plus lié à aucune dApp. Déconnecter toutes les dApps @@ -1352,13 +1381,22 @@ La proposition de connexion a expiré Modifications estimées du portefeuille La transaction n\'a pas pu être simulée. Veuillez procéder avec prudence. + Rechargez votre solde pour couvrir les frais de réseau. + %1$s Insuffisant Transaction malveillante + Ajoutez le réseau %s à votre portefeuille pour ce wallet + Le wallet ne nécessite aucun réseaux Nouvelle connexion Connectez votre portefeuille à différentes dApps Aucune séance 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. + Risque de sécurité connu + Ouvrez l\'application Web3 et sélectionnez l\'option WalletConnect. Demande de Type de signature + Au moins un réseau est requis pour la connexion à une dApp. + Spécifier les réseaux sélectionnés + Signé avec succès À Demande de transaction Demande de transaction diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 7e06ea9819..7f437b9d1e 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -71,8 +71,10 @@ 設定に移動して、Tangemアプリで生体認証を有効にします。 生体認証を有効にする %1$sが無効になると、アプリのロックを解除してウォレットを操作するために、パスコードを入力する必要があります。 + 後でウォレットのアクセスコードを入力してもらいます。それを安全に保存し、今後の利用に備えるためです。 これにより、保存されているウォレットアクセスコードがすべて削除されます。ウォレットでの今後の操作には、アクセスコードの送信が必要になります。 保存したデバイスを削除すると、保存されているすべてのウォレットとそのアクセスコードがアプリから削除されます。 + これにより、保存されているウォレットアクセスコードがすべて削除されます。今後ウォレットを操作するには、アクセスコードの送信が必要になります。 アクセスコードを要求する このオプションを選択すると、機密性の高い操作における生体認証が無効になります。取引の署名時などには、毎回アクセスコードの入力が必要になります。 アクセスコードを保存 @@ -486,6 +488,7 @@ 最新の機能とニュースをお届けします シードフレーズのバックアップ モバイルウォレットを作成する + このリカバリーフレーズはすでにインポートされています。 モバイルウォレット この情報はAIで生成されました。 \nエラーが見つかった場合は、ここをタップしてください。 アクセスコードを変更するには、上図のようにカードまたはリングをタップし、操作が終了するまで取り外さないでください。 @@ -1491,7 +1494,7 @@ 使用可能量の設定 dAppが接続解除されました すべての接続を解除する - すべてのdAppセッションが切断されます。ウォレットはどのdAppにも接続されなくなります。 + すべてのdAppセッションの接続が解除されます。ウォレットはどのdAppにもリンクされなくなります。 すべてのdAppを接続解除する 新しいURIで、再度ペアリングを試してください 無効なdAppドメイン diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index c0e035a70e..335160da29 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -27,6 +27,7 @@ import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.error.SendTransactionError.UserCancelledError import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus @@ -346,25 +347,35 @@ internal class WcSendTransactionModel @Inject constructor( } private fun signingIsDone(signState: WcSignState<*>, useCase: WcSignUseCase<*>): Boolean { - (signState.domainStep as? WcSignStep.Result)?.result?.let { - return handleSigningError(it, useCase) + return when (val step = signState.domainStep) { + is WcSignStep.Result -> processResultStep(result = step.result, useCase = useCase) + WcSignStep.PreSign, + WcSignStep.Signing, + -> false } - return false } - private fun handleSigningError(result: Either, useCase: WcSignUseCase<*>): Boolean { - return if (result.isLeft()) { - val error = WcTransactionRoutes.Alert.Type.UnknownError( - errorMessage = result.leftOrNull()?.message(), - onDismiss = { cancel(useCase) }, - onRetry = { signFromAlert() }, - ) - stackNavigation.pushNew(WcTransactionRoutes.Alert(error)) - false - } else { - showSuccessSignMessage() - router.pop() - true + private fun processResultStep(result: Either, useCase: WcSignUseCase<*>): Boolean { + return when (result) { + is Either.Left -> { + val error = result.value + if (error is WcRequestError.WrappedSendError && error.sendTransactionError is UserCancelledError) { + return false + } + + val alertError = WcTransactionRoutes.Alert.Type.UnknownError( + errorMessage = result.value.message(), + onDismiss = { cancel(useCase) }, + onRetry = { signFromAlert() }, + ) + stackNavigation.pushNew(WcTransactionRoutes.Alert(alertError)) + false + } + is Either.Right -> { + showSuccessSignMessage() + router.pop() + true + } } } From be07bf138cfffb3e6c52354e7da16d5f07679c5a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Aug 2025 14:20:25 +0700 Subject: [PATCH 37/40] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 1 + core/res/src/main/res/values-es/strings.xml | 1 + core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 1 + core/res/src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values-uk-rUA/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 1 + ...AddEthereumChainModalBottomSheetContent.kt | 7 +- .../ui/common/WcTransactionRequestButtons.kt | 96 +++++++++++++------ 9 files changed, 79 insertions(+), 31 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index c62cee51c8..ddcad5ed92 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -116,6 +116,7 @@ Nicht genug ADA Akzeptieren Zugang verweigert + Hinzufügen Zum Portfolio hinzufügen Token hinzufügen Vertragsadresse diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index c510d098aa..fa2411cdf5 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -113,6 +113,7 @@ ADA insuficiente Aceptar Acceso denegado + Agregar Añadir al portafolio Agregar token Dirección diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 1b64b43e63..e907d8df01 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -95,6 +95,7 @@ ADA insuffisant Accepter Accès refusé + Ajouter Ajouter au portfolio Ajouter un jeton Adresse diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 7f437b9d1e..5ffb365ef3 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -149,6 +149,7 @@ ADAが不足しています。 受け入れる アクセスが拒否されました + 追加 ポートフォリオに追加 トークンを追加 アドレス diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8de88e16ba..befe0fd2d7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -92,6 +92,7 @@ Недостаточно ADA Принять Доступ запрещен + Добавить Добавить в портфель Добавить токен Адрес diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index ef5cb62c26..5c7d9f4ef4 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -92,6 +92,7 @@ Недостатньо ADA Прийняти Доступ заборонено + Додати Додати у портфель Додати токен Адреса diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 5074ca1886..8bda8073a5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -152,6 +152,7 @@ Not enough ADA Accept Access denied + Add Add to portfolio Add token Address diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt index 3f1a55cfe8..73d7ef80e2 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/chain/WcAddEthereumChainModalBottomSheetContent.kt @@ -30,8 +30,8 @@ import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.entity.chain.WcAddEthereumChainItemUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM +import com.tangem.features.walletconnect.transaction.ui.common.WcSimpleConfirmButtons import com.tangem.features.walletconnect.transaction.ui.common.WcSmallTitleItem -import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestButtons import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestItem import com.tangem.features.walletconnect.transaction.ui.common.WcWalletItem @@ -91,13 +91,12 @@ internal fun WcAddEthereumChainModalBottomSheetContent( } }, footer = { - WcTransactionRequestButtons( + WcSimpleConfirmButtons( modifier = Modifier.padding(16.dp), onDismiss = state.onDismiss, onClickActiveButton = state.onSign, - activeButtonText = resourceReference(R.string.common_sign), + activeButtonText = resourceReference(R.string.common_add), isLoading = state.isLoading, - validationResult = null, ) }, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt index ffd23bc142..61f1cf95e7 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt @@ -2,6 +2,7 @@ package com.tangem.features.walletconnect.transaction.ui.common import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -24,6 +25,73 @@ internal fun WcTransactionRequestButtons( onClickActiveButton: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, +) { + WcCommonButtons( + onDismiss = onDismiss, + modifier = modifier, + primaryButton = { + // Before change, make sure you are align with WcSendTransactionModel::onSign + when (validationResult) { + ValidationResult.UNSAFE, + ValidationResult.WARNING, + -> PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = stringResourceSafe(R.string.common_continue), + onClick = onClickActiveButton, + showProgress = isLoading, + enabled = enabled, + ) + ValidationResult.SAFE, + ValidationResult.FAILED_TO_VALIDATE, + null, + -> PrimaryButtonIconEnd( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = activeButtonText.resolveReference(), + onClick = onClickActiveButton, + iconResId = R.drawable.ic_tangem_24, + showProgress = isLoading, + enabled = enabled, + ) + } + }, + ) +} + +@Composable +internal fun WcSimpleConfirmButtons( + activeButtonText: TextReference, + isLoading: Boolean, + onDismiss: () -> Unit, + onClickActiveButton: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + WcCommonButtons( + onDismiss = onDismiss, + modifier = modifier, + primaryButton = { + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + text = activeButtonText.resolveReference(), + onClick = onClickActiveButton, + showProgress = isLoading, + enabled = enabled, + ) + }, + ) +} + +@Composable +internal fun WcCommonButtons( + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + primaryButton: @Composable RowScope.() -> Unit, ) { Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { SecondaryButton( @@ -33,32 +101,6 @@ internal fun WcTransactionRequestButtons( text = stringResourceSafe(R.string.common_cancel), onClick = onDismiss, ) - // Before change, make sure you are align with WcSendTransactionModel::onSign - when (validationResult) { - ValidationResult.UNSAFE, - ValidationResult.WARNING, - -> PrimaryButton( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = stringResourceSafe(R.string.common_continue), - onClick = onClickActiveButton, - showProgress = isLoading, - enabled = enabled, - ) - ValidationResult.SAFE, - ValidationResult.FAILED_TO_VALIDATE, - null, - -> PrimaryButtonIconEnd( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = activeButtonText.resolveReference(), - onClick = onClickActiveButton, - iconResId = R.drawable.ic_tangem_24, - showProgress = isLoading, - enabled = enabled, - ) - } + primaryButton() } } \ No newline at end of file From a08145498ca01e495d60d11dd8e05afa8d631c27 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Aug 2025 15:57:02 +0300 Subject: [PATCH 38/40] Updated on 2026-08-14 --- .../domain/walletconnect/WalletConnectSdkHelper.kt | 9 ++------- .../tangem/core/analytics/models/AnalyticsParam.kt | 9 +++++++-- .../java/com/tangem/core/analytics/models/Basic.kt | 4 +++- .../network/ethereum/WcEthSendTransactionUseCase.kt | 13 +++++++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index ffb1dbb2b0..9df63ef08a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -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 -> { diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 8eed0785bc..08021ec348 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -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( @@ -125,7 +130,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) { diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index 110391778a..875cf24182 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -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 diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt index 5358c3a317..e82c497072 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt @@ -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)) } From 27570efa938137de58746a4f57c9f3cd51abdb1d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Aug 2025 20:57:21 +0300 Subject: [PATCH 39/40] Updated on 2026-08-14 --- .../managetokens/DefaultManageTokensRepository.kt | 11 ++++++++++- .../data/managetokens/di/ManageTokensDataModule.kt | 2 ++ .../domain/managetokens/GetManagedTokensUseCase.kt | 1 + .../utils/list/ManageTokensListManager.kt | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 20cdc81e55..ce7a1c066a 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -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, diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 87943d1e88..412155dcda 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -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, diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt index e8a4b8d205..6a9c3a47ed 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetManagedTokensUseCase.kt @@ -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 { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index b486703bd8..7664a6ce6e 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -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, ) From ddb0957f2e94deeb66f867ad67a6332123ae0bdf Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Aug 2025 18:41:42 +0000 Subject: [PATCH 40/40] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 57e50cf4ac..fb79857d96 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,13 +5,13 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.27.0-1148" +tangemBlockchainSdk = "develop-1140" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.27.0-519" +tangemCardSdk = "develop-518" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-454" +tangemHotSdk = "develop-461" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^