Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-29 16:41:42 +03:00
commit 0becaafc0a
74 changed files with 641 additions and 249 deletions

View file

@ -3,6 +3,7 @@ package com.tangem.tap
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
@ -119,4 +120,6 @@ interface ApplicationEntryPoint {
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
fun getCoroutineDispatcherProvider(): CoroutineDispatcherProvider
fun getExcludedBlockchains(): ExcludedBlockchains
}

View file

@ -92,6 +92,7 @@ import kotlinx.coroutines.flow.*
import timber.log.Timber
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.seconds
lateinit var tangemSdkManager: TangemSdkManager
lateinit var backupService: BackupService
@ -330,7 +331,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
private fun installAppTheme() {
appThemeModeFlow = createAppThemeModeFlow()
val mode = runBlocking { appThemeModeFlow.first() }
val mode = runBlocking {
withTimeoutOrNull(APP_THEME_LOAD_TIMEOUT.seconds) {
appThemeModeFlow.first()
} ?: AppThemeMode.DEFAULT
}
updateAppTheme(mode)
}
@ -599,4 +604,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
.onRight { Timber.d("Submitting hashes succeeded") }
}
}
companion object {
private const val APP_THEME_LOAD_TIMEOUT = 2
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.TangemSdkLogger
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.ParamsInterceptor
@ -189,6 +190,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val dispatchers: CoroutineDispatcherProvider
get() = entryPoint.getCoroutineDispatcherProvider()
private val excludedBlockchains: ExcludedBlockchains
get() = entryPoint.getExcludedBlockchains()
// endregion
override fun onCreate() {
@ -269,6 +273,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage,
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
excludedBlockchains = excludedBlockchains,
),
),
)

View file

@ -1,5 +1,6 @@
package com.tangem.tap.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
@ -49,6 +50,7 @@ internal object ActivityModule {
swapServiceLoader: SwapServiceLoader,
currenciesRepository: CurrenciesRepository,
getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
excludedBlockchains: ExcludedBlockchains,
dispatchers: CoroutineDispatcherProvider,
): RampStateManager {
return DefaultRampManager(
@ -58,6 +60,7 @@ internal object ActivityModule {
swapServiceLoader = swapServiceLoader,
currenciesRepository = currenciesRepository,
getNetworkCoinStatusUseCase = getNetworkCoinStatusUseCase,
excludedBlockchains = excludedBlockchains,
dispatchers = dispatchers,
)
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.di.data
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.sdk.api.TangemSdkManager
@ -20,8 +21,9 @@ internal object CardDataModule {
fun providesDerivationsRepository(
tangemSdkManager: TangemSdkManager,
userWalletsStore: UserWalletsStore,
excludedBlockchains: ExcludedBlockchains,
dispatchers: CoroutineDispatcherProvider,
): DerivationsRepository {
return DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, dispatchers)
return DefaultDerivationsRepository(tangemSdkManager, userWalletsStore, excludedBlockchains, dispatchers)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.card
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
@ -32,6 +33,7 @@ private typealias DerivedKeys = Map<ByteArrayKey, ExtendedPublicKeysMap>
internal class DefaultDerivationsRepository(
private val tangemSdkManager: TangemSdkManager,
private val userWalletsStore: UserWalletsStore,
private val excludedBlockchains: ExcludedBlockchains,
private val dispatchers: CoroutineDispatcherProvider,
) : DerivationsRepository {
@ -49,6 +51,7 @@ internal class DefaultDerivationsRepository(
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null,
extraDerivationPath = null,
scanResponse = userWallet.scanResponse,
excludedBlockchains = excludedBlockchains,
)
},
)
@ -85,6 +88,7 @@ internal class DefaultDerivationsRepository(
blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null,
extraDerivationPath = extraDerivationPath,
scanResponse = userWallet.scanResponse,
excludedBlockchains = excludedBlockchains,
)
},
)

View file

@ -2,6 +2,11 @@ package com.tangem.tap.domain.walletconnect2.data
import android.app.Application
import arrow.core.flatten
import com.reown.android.Core
import com.reown.android.CoreClient
import com.reown.android.relay.ConnectionType
import com.reown.walletkit.client.Wallet
import com.reown.walletkit.client.WalletKit
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
@ -9,11 +14,6 @@ import com.tangem.tap.domain.walletconnect2.domain.WcJrpcMethods
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
import com.tangem.tap.domain.walletconnect2.domain.WcRequest
import com.tangem.tap.domain.walletconnect2.domain.models.*
import com.walletconnect.android.Core
import com.walletconnect.android.CoreClient
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.web3.wallet.client.Wallet
import com.walletconnect.web3.wallet.client.Web3Wallet
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@ -72,7 +72,7 @@ internal class DefaultLegacyWalletConnectRepository(
}
}
Web3Wallet.initialize(Wallet.Params.Init(core = CoreClient)) { error ->
WalletKit.initialize(Wallet.Params.Init(core = CoreClient)) { error ->
Timber.e("Error while initializing Web3Wallet: $error")
scope.launch {
_events.emit(
@ -84,11 +84,11 @@ internal class DefaultLegacyWalletConnectRepository(
}
val walletDelegate = defineWalletDelegate()
Web3Wallet.setWalletDelegate(walletDelegate)
WalletKit.setWalletDelegate(walletDelegate)
}
private fun defineWalletDelegate(): Web3Wallet.WalletDelegate {
return object : Web3Wallet.WalletDelegate {
private fun defineWalletDelegate(): WalletKit.WalletDelegate {
return object : WalletKit.WalletDelegate {
override fun onSessionProposal(
sessionProposal: Wallet.Model.SessionProposal,
verifyContext: Wallet.Model.VerifyContext,
@ -188,14 +188,6 @@ internal class DefaultLegacyWalletConnectRepository(
}
}
override fun onAuthRequest(
authRequest: Wallet.Model.AuthRequest,
verifyContext: Wallet.Model.VerifyContext,
) {
// Triggered when Dapp / Requester makes an authorization request
Timber.i("onAuthRequest: $authRequest")
}
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
// Triggered when the session is deleted by the peer
if (sessionDelete is Wallet.Model.SessionDelete.Success) {
@ -251,7 +243,7 @@ internal class DefaultLegacyWalletConnectRepository(
}
override fun pair(uri: String) {
Web3Wallet.pair(
WalletKit.pair(
params = Wallet.Params.Pair(uri),
onSuccess = {
Timber.i("Paired successfully: $it")
@ -303,7 +295,7 @@ internal class DefaultLegacyWalletConnectRepository(
Timber.i("Session approval is prepared for sending: $sessionApproval")
Web3Wallet.approveSession(
WalletKit.approveSession(
params = sessionApproval,
onSuccess = {
Timber.i("Approved successfully: $it")
@ -364,7 +356,7 @@ internal class DefaultLegacyWalletConnectRepository(
)
}
Web3Wallet.respondSessionRequest(
WalletKit.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = requestData.topic,
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
@ -409,7 +401,7 @@ internal class DefaultLegacyWalletConnectRepository(
}
override fun cancelRequest(topic: String, id: Long, message: String) {
Web3Wallet.respondSessionRequest(
WalletKit.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = topic,
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError(
@ -424,7 +416,7 @@ internal class DefaultLegacyWalletConnectRepository(
}
override fun reject() {
Web3Wallet.rejectSession(
WalletKit.rejectSession(
params = Wallet.Params.SessionReject(
proposerPublicKey = sessionProposal?.proposerPublicKey ?: "",
reason = "",
@ -440,7 +432,7 @@ internal class DefaultLegacyWalletConnectRepository(
override fun disconnect(topic: String) {
val session = currentSessions.find { it.topic == topic }
Web3Wallet.disconnectSession(
WalletKit.disconnectSession(
params = Wallet.Params.SessionDisconnect(topic),
onSuccess = {
analyticsHandler.send(
@ -459,7 +451,7 @@ internal class DefaultLegacyWalletConnectRepository(
}
fun send(topic: String, id: Long, data: String) {
Web3Wallet.respondSessionRequest(
WalletKit.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = topic,
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
@ -477,7 +469,7 @@ internal class DefaultLegacyWalletConnectRepository(
}
private fun updateSessionsInternal(): Job = scope.launch {
val availableSessions = Web3Wallet.getListOfActiveSessions()
val availableSessions = WalletKit.getListOfActiveSessions()
.map {
WalletConnectSession(
topic = it.topic,

View file

@ -163,7 +163,9 @@ internal class WcSessionRequestConverter(
return sessionsRepository.loadSessions(userWalletId)
.firstOrNull { it.topic == sessionRequest.topic }
?.accounts?.firstOrNull {
it.chainId == sessionRequest.chainId && it.walletAddress.lowercase() == walletAddress?.lowercase()
// if walletAddress == null take first account
it.chainId == sessionRequest.chainId &&
(walletAddress == null || it.walletAddress.lowercase() == walletAddress.lowercase())
}?.derivationPath
}

View file

@ -1,30 +0,0 @@
package com.tangem.tap.domain.walletconnect2.domain.mapper
import com.tangem.blockchain.blockchains.solana.solanaj.core.SolanaTransaction
import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaTransactionRequest
import org.p2p.solanaj.core.AccountMeta
import org.p2p.solanaj.core.PublicKey
import org.p2p.solanaj.core.TransactionInstruction
internal fun SolanaTransactionRequest.mapToTransaction(): SolanaTransaction {
val from = PublicKey(feePayer)
val instructions = instructions.map(SolanaTransactionRequest.Instruction::mapToInstruction)
return SolanaTransaction(from)
.apply {
instructions.forEach(::addInstruction)
setRecentBlockHash(recentBlockhash)
}
}
private fun SolanaTransactionRequest.Instruction.mapToInstruction() = TransactionInstruction(
/* programId = */ PublicKey(programId),
/* keys = */ keys.map(SolanaTransactionRequest.Key::mapToAccountMeta),
/* data = */ data.toByteArray(),
)
private fun SolanaTransactionRequest.Key.mapToAccountMeta() = AccountMeta(
/* publicKey = */ PublicKey(publicKey),
/* isSigner = */ isSigner,
/* isWritable = */ isWritable,
)

View file

@ -7,39 +7,8 @@ import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
@JsonClass(generateAdapter = true)
data class SolanaTransactionRequest(
@Json(name = "feePayer")
val feePayer: String,
@Json(name = "recentBlockhash")
val recentBlockhash: String,
@Json(name = "instructions")
val instructions: List<Instruction>,
val feePayer: String?,
@Json(name = "transaction")
val transaction: String,
) : WcRequestData {
@JsonClass(generateAdapter = true)
data class Instruction(
@Json(name = "programId")
val programId: String,
@Json(name = "data")
val data: String,
@Json(name = "keys")
val keys: List<Key>,
)
@JsonClass(generateAdapter = true)
data class Key(
@Json(name = "isSigner")
val isSigner: Boolean,
@Json(name = "isWritable")
val isWritable: Boolean,
@Json(name = "pubkey")
val publicKey: String,
)
}
) : WcRequestData

View file

@ -53,6 +53,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
store.inject(DaggerGraphState::balanceHidingRepository)
.getBalanceHidingSettings().isHidingEnabledInSettings
},
needEnrollBiometrics = runBlocking {
runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false
},
),
)
}

View file

@ -11,7 +11,8 @@ import com.tangem.wallet.R
internal object SignTransactionDialog {
fun create(preparedData: WcPreparedRequest.SignTransaction, context: Context): AlertDialog {
val message = context.getString(R.string.wallet_connect_alert_sign_message, "")
val signMessage = context.getString(R.string.wallet_connect_alert_sign_message, "")
val message = "${preparedData.preparedRequestData.dAppName}\n$signMessage"
return AlertDialog.Builder(context).apply {
setTitle(context.getString(R.string.wallet_connect_title))
setMessage(message)

View file

@ -24,8 +24,8 @@ import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.mainScope
@ -224,7 +224,9 @@ object OnboardingHelper {
fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse, globalState: GlobalState) {
val blockchain = walletManager.wallet.blockchain
val cryptoCurrency = CryptoCurrencyFactory().createCoin(
val excludedBlockchains = store.inject(DaggerGraphState::excludedBlockchains)
val cryptoCurrency = CryptoCurrencyFactory(excludedBlockchains).createCoin(
blockchain = blockchain,
extraDerivationPath = null,
scanResponse = scanResponse,

View file

@ -2,6 +2,7 @@ package com.tangem.tap.network.exchangeServices
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.common.extensions.inject
@ -10,9 +11,11 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.converter.TwoWayConverter
internal class CryptoCurrencyConverter : TwoWayConverter<Currency, CryptoCurrency> {
internal class CryptoCurrencyConverter(
private val excludedBlockchains: ExcludedBlockchains,
) : TwoWayConverter<Currency, CryptoCurrency> {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
override fun convert(value: Currency): CryptoCurrency {
return when (value) {

View file

@ -1,5 +1,6 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
import com.tangem.domain.exchange.RampStateManager
@ -25,9 +26,10 @@ internal class DefaultRampManager(
private val currenciesRepository: CurrenciesRepository,
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
private val dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
) : RampStateManager {
private val cryptoCurrencyConverter = CryptoCurrencyConverter()
private val cryptoCurrencyConverter = CryptoCurrencyConverter(excludedBlockchains)
override fun isSellSupportedByService(cryptoCurrency: CryptoCurrency): Boolean {
return exchangeService?.availableForSell(

View file

@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
@ -71,4 +72,5 @@ data class DaggerGraphState(
val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null,
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
val excludedBlockchains: ExcludedBlockchains? = null,
) : StateType