Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-17 12:32:52 +01:00
commit e8ccd67098
295 changed files with 4414 additions and 2971 deletions

View file

@ -1,9 +1,7 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.datasource.config.ConfigManager
@ -12,14 +10,11 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.operations.attestation.Attestation
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
@ -32,29 +27,12 @@ class TapWalletManager(
private val dispatchers: CoroutineDispatcherProvider = AppCoroutineDispatcherProvider(),
) {
private val blockchainSdkConfig by lazy {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
}
private var loadUserWalletDataJob: Job? = null
set(value) {
field?.cancel()
field = value
}
val walletManagerFactory: WalletManagerFactory by lazy {
WalletManagerFactory(
config = blockchainSdkConfig,
accountCreator = store.inject(DaggerGraphState::accountCreator),
blockchainDataStorage = store.inject(DaggerGraphState::blockchainDataStorage),
loggers = if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
listOf(store.inject(DaggerGraphState::blockchainSDKLogger))
} else {
emptyList()
},
)
}
suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
// If a previous job was running, it gets cancelled before the new one starts,
// ensuring that only one job is active at any given time.
@ -79,9 +57,7 @@ class TapWalletManager(
// Order is important
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
store.dispatch(WalletConnectAction.ResetState)
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
}
}

View file

@ -13,7 +13,7 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import timber.log.Timber

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.repository
import com.tangem.common.CompletionResult
import com.tangem.domain.card.repository.ScanCardRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
// TODO: Move to the :data:card module

View file

@ -0,0 +1,98 @@
package com.tangem.tap.domain.sdk
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.common.*
import com.tangem.common.authentication.keystore.KeystoreManager
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
interface TangemSdkManager {
val canUseBiometry: Boolean
val needEnrollBiometrics: Boolean
val keystoreManager: KeystoreManager
val secureStorage: SecureStorage
val userCodeRequestPolicy: UserCodeRequestPolicy
suspend fun scanProduct(
cardId: String? = null,
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse>
suspend fun createProductWallet(
scanResponse: ScanResponse,
shouldReset: Boolean = false,
): CompletionResult<CreateProductWalletTaskResponse>
suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse>
suspend fun derivePublicKeys(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): CompletionResult<DerivationTaskResponse>
suspend fun deriveExtendedPublicKey(
cardId: String?,
walletPublicKey: ByteArray,
derivation: DerivationPath,
): CompletionResult<ExtendedPublicKey>
suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO>
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit>
suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit>
suspend fun clearSavedUserCodes(): CompletionResult<Unit>
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse>
suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse>
suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse>
suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult<SuccessResponse>
suspend fun scanCard(
cardId: String? = null,
allowRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<CardDTO>
suspend fun <T> runTaskAsync(
runnable: CardSessionRunnable<T>,
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
@DrawableRes iconScanRes: Int? = null,
): CompletionResult<T>
@Suppress("MagicNumber")
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?)
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy)
}

View file

@ -1,4 +1,4 @@
package com.tangem.tap.domain
package com.tangem.tap.domain.sdk.impl
import android.content.res.Resources
import androidx.annotation.DrawableRes
@ -29,6 +29,7 @@ import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
@ -40,10 +41,10 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
@Suppress("TooManyFunctions")
class TangemSdkManager(
class DefaultTangemSdkManager(
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val resources: Resources,
) {
) : TangemSdkManager {
private val tangemSdk: TangemSdk
get() = cardSdkConfigRepository.sdk
@ -55,25 +56,25 @@ class TangemSdkManager(
)
}
val canUseBiometry: Boolean
override val canUseBiometry: Boolean
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
val needEnrollBiometrics: Boolean
override val needEnrollBiometrics: Boolean
get() = tangemSdk.authenticationManager.needEnrollBiometrics
val keystoreManager: KeystoreManager
override val keystoreManager: KeystoreManager
get() = tangemSdk.keystoreManager
val secureStorage: SecureStorage
override val secureStorage: SecureStorage
get() = tangemSdk.secureStorage
val userCodeRequestPolicy: UserCodeRequestPolicy
override val userCodeRequestPolicy: UserCodeRequestPolicy
get() = tangemSdk.config.userCodeRequestPolicy
suspend fun scanProduct(
cardId: String? = null,
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
override suspend fun scanProduct(
cardId: String?,
messageRes: Int?,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> {
val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
@ -87,9 +88,9 @@ class TangemSdkManager(
).also { sendScanResultsToAnalytics(it) }
}
suspend fun createProductWallet(
override suspend fun createProductWallet(
scanResponse: ScanResponse,
shouldReset: Boolean = false,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
return runTaskAsync(
runnable = CreateProductWalletTask(
@ -103,7 +104,7 @@ class TangemSdkManager(
)
}
suspend fun importWallet(
override suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
@ -135,14 +136,14 @@ class TangemSdkManager(
}
}
suspend fun derivePublicKeys(
override suspend fun derivePublicKeys(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): CompletionResult<DerivationTaskResponse> {
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
}
suspend fun deriveExtendedPublicKey(
override suspend fun deriveExtendedPublicKey(
cardId: String?,
walletPublicKey: ByteArray,
derivation: DerivationPath,
@ -153,7 +154,7 @@ class TangemSdkManager(
)
}
suspend fun resetToFactorySettings(
override suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
@ -167,7 +168,7 @@ class TangemSdkManager(
.map { CardDTO(it) }
}
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
override suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
return userCodeRepository.save(
cardsIds = cardsIds,
userCode = UserCode(
@ -177,15 +178,15 @@ class TangemSdkManager(
)
}
suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit> {
override suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit> {
return userCodeRepository.delete(cardsIds.toSet())
}
suspend fun clearSavedUserCodes(): CompletionResult<Unit> {
override suspend fun clearSavedUserCodes(): CompletionResult<Unit> {
return userCodeRepository.clear()
}
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
override suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null),
cardId,
@ -193,7 +194,7 @@ class TangemSdkManager(
)
}
suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
override suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null),
cardId,
@ -201,7 +202,7 @@ class TangemSdkManager(
)
}
suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
override suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.resetUserCodes(),
cardId,
@ -209,7 +210,10 @@ class TangemSdkManager(
)
}
suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult<SuccessResponse> {
override suspend fun setAccessCodeRecoveryEnabled(
cardId: String?,
enabled: Boolean,
): CompletionResult<SuccessResponse> {
return runTaskAsyncReturnOnMain(
SetUserCodeRecoveryAllowedTask(enabled),
cardId,
@ -217,9 +221,9 @@ class TangemSdkManager(
)
}
suspend fun scanCard(
cardId: String? = null,
allowRequestAccessCodeFromRepository: Boolean = false,
override suspend fun scanCard(
cardId: String?,
allowRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
return runTaskAsyncReturnOnMain(
runnable = ScanTask(allowRequestAccessCodeFromRepository),
@ -229,12 +233,12 @@ class TangemSdkManager(
.map { CardDTO(it) }
}
suspend fun <T> runTaskAsync(
override suspend fun <T> runTaskAsync(
runnable: CardSessionRunnable<T>,
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
@DrawableRes iconScanRes: Int? = null,
cardId: String?,
initialMessage: Message?,
accessCode: String?,
@DrawableRes iconScanRes: Int?,
): CompletionResult<T> = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result ->
@ -253,7 +257,7 @@ class TangemSdkManager(
}
@Suppress("MagicNumber")
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
tangemSdk.config.cardIdDisplayFormat = when {
scanResponse == null -> CardIdDisplayFormat.Full
scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
@ -262,11 +266,11 @@ class TangemSdkManager(
}
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return resources.getString(stringResId, *formatArgs)
}
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
tangemSdk.config.userCodeRequestPolicy = policy
}

View file

@ -0,0 +1,149 @@
package com.tangem.tap.domain.sdk.impl
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.common.*
import com.tangem.common.authentication.keystore.KeystoreManager
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.sdk.mocks.MockProvider
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Suppress("TooManyFunctions")
class MockTangemSdkManager(
private val resources: Resources,
) : TangemSdkManager {
override val canUseBiometry: Boolean
get() = false
override val needEnrollBiometrics: Boolean
get() = TODO()
override val keystoreManager: KeystoreManager
get() = TODO()
override val secureStorage: SecureStorage
get() = TODO()
override val userCodeRequestPolicy: UserCodeRequestPolicy
get() = TODO()
override suspend fun scanProduct(
cardId: String?,
messageRes: Int?,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> {
return CompletionResult.Success(MockProvider.getScanResponse())
}
override suspend fun createProductWallet(
scanResponse: ScanResponse,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
TODO()
}
override suspend fun importWallet(
scanResponse: ScanResponse,
mnemonic: String,
passphrase: String?,
shouldReset: Boolean,
): CompletionResult<CreateProductWalletTaskResponse> {
TODO()
}
override suspend fun derivePublicKeys(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): CompletionResult<DerivationTaskResponse> {
TODO()
}
override suspend fun deriveExtendedPublicKey(
cardId: String?,
walletPublicKey: ByteArray,
derivation: DerivationPath,
): CompletionResult<ExtendedPublicKey> {
TODO()
}
override suspend fun resetToFactorySettings(
cardId: String,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
TODO()
}
override suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
TODO()
}
override suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit> {
TODO()
}
override suspend fun clearSavedUserCodes(): CompletionResult<Unit> {
TODO()
}
override suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun setAccessCodeRecoveryEnabled(
cardId: String?,
enabled: Boolean,
): CompletionResult<SuccessResponse> {
TODO()
}
override suspend fun scanCard(
cardId: String?,
allowRequestAccessCodeFromRepository: Boolean,
): CompletionResult<CardDTO> {
TODO()
}
override suspend fun <T> runTaskAsync(
runnable: CardSessionRunnable<T>,
cardId: String?,
initialMessage: Message?,
accessCode: String?,
@DrawableRes iconScanRes: Int?,
): CompletionResult<T> = withContext(Dispatchers.Main) {
TODO()
}
@Suppress("MagicNumber")
override fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
}
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return resources.getString(stringResId, *formatArgs)
}
override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {
TODO()
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.domain.sdk.mocks
import com.tangem.domain.models.scan.ProductType
import com.tangem.tap.domain.sdk.mocks.wallet.WalletMocks
import com.tangem.tap.domain.sdk.mocks.wallet2.Wallet2Mocks
object MockProvider {
var productType: ProductType = ProductType.Wallet
fun getScanResponse() = getMocks(productType).scanResponse
private fun getMocks(productType: ProductType): Mocks {
return when (productType) {
ProductType.Wallet -> WalletMocks
ProductType.Wallet2 -> Wallet2Mocks
else -> TODO()
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.tap.domain.sdk.mocks
import com.tangem.domain.models.scan.ScanResponse
interface Mocks {
val scanResponse: ScanResponse
}

View file

@ -0,0 +1,127 @@
package com.tangem.tap.domain.sdk.mocks.wallet
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.EncryptionMode
import com.tangem.common.card.FirmwareVersion
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.tap.domain.sdk.mocks.Mocks
import java.util.Date
object WalletMocks : Mocks {
private val cardDto = CardDTO(
cardId = "AC05000000086747",
batchId = "AC05",
cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39),
firmwareVersion = CardDTO.FirmwareVersion(
major = 4,
minor = 52,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1649635200000),
signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66),
),
issuer = CardDTO.Issuer(
name = "TANGEM AG",
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = false,
isSettingPasscodeAllowed = false,
isResettingUserCodesAllowed = true,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = false,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = false,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Secp256r1,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = false,
derivedKeys = mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = false,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.NoBackup,
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
}

View file

@ -0,0 +1,10 @@
package com.tangem.tap.domain.sdk.mocks.wallet2
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.sdk.mocks.Mocks
object Wallet2Mocks : Mocks {
override val scanResponse: ScanResponse
get() = TODO("Not yet implemented")
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.settings
import com.tangem.domain.settings.repositories.LegacySettingsRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.sdk.TangemSdkManager
internal class DefaultLegacySettingsRepository(
private val tangemSdkManager: TangemSdkManager,

View file

@ -3,11 +3,11 @@ package com.tangem.tap.domain.tasks.product
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId

View file

@ -9,7 +9,7 @@ import com.tangem.common.KeyPair
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.wallet.CreateWalletResponse
@ -88,6 +88,7 @@ class TwinCardsManager(
)
}
@Deprecated(message = "Use AssetReader instead")
private fun getIssuers(reader: AssetReader): List<Issuer> {
val file = reader.readJson(fileName = "tangem-app-config/issuers")
return getAdapter().fromJson(file)!!

View file

@ -1,12 +0,0 @@
package com.tangem.tap.domain.userWalletList
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
internal class DefaultUserWalletsListManagerFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : UserWalletsListManagerFeatureToggles {
override val isGeneralManagerEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED")
}

View file

@ -1,23 +0,0 @@
package com.tangem.tap.domain.userWalletList.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.tap.domain.userWalletList.DefaultUserWalletsListManagerFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object UserWalletsListManagerFeatureTogglesModule {
@Provides
@Singleton
fun provideUserWalletsListManagerFeatureToggles(
featureTogglesManager: FeatureTogglesManager,
): UserWalletsListManagerFeatureToggles {
return DefaultUserWalletsListManagerFeatureToggles(featureTogglesManager)
}
}

View file

@ -66,7 +66,7 @@ internal object UserWalletsListManagerModule {
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
storageName = "user_wallets_storage",
),
)

View file

@ -1,86 +0,0 @@
package com.tangem.tap.domain.userWalletList.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.authentication.storage.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
import com.tangem.tap.domain.userWalletList.utils.json.*
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.Provider
internal const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage"
fun UserWalletsListManager.Companion.provideBiometricImplementation(
applicationContext: Context,
): UserWalletsListManager {
val moshi = Moshi.Builder()
.add(WalletDerivedKeysMapAdapter())
.add(ScanResponseDerivedKeysMapAdapter())
.add(ByteArrayKeyAdapter())
.add(ExtendedPublicKeysMapAdapter())
.add(CardBackupStatusAdapter())
.add(DerivationPathAdapterWithMigration())
.add(TangemSdkAdapter.DateAdapter())
.add(TangemSdkAdapter.DerivationNodeAdapter())
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
.add(KotlinJsonAdapterFactory())
.build()
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
),
)
val authenticatedStorage = AuthenticatedStorage(
secureStorage = UserWalletsKeysStoreDecorator(
featureStorage = secureStorage,
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
),
keystoreManager = DelegatedKeystoreManager(
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
),
)
val keysRepository = BiometricUserWalletsKeysRepository(
moshi = moshi,
secureStorage = secureStorage,
authenticatedStorage = authenticatedStorage,
)
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
moshi = moshi,
secureStorage = secureStorage,
)
val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository(
moshi = moshi,
secureStorage = secureStorage,
)
val selectedUserWalletRepository = DefaultSelectedUserWalletRepository(
secureStorage = secureStorage,
)
return BiometricUserWalletsListManager(
keysRepository = keysRepository,
publicInformationRepository = publicInformationRepository,
sensitiveInformationRepository = sensitiveInformationRepository,
selectedUserWalletRepository = selectedUserWalletRepository,
)
}
fun UserWalletsListManager.Companion.provideRuntimeImplementation(): UserWalletsListManager {
return RuntimeUserWalletsListManager()
}

View file

@ -10,13 +10,11 @@ import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTrans
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
import com.tangem.tap.features.details.redux.walletconnect.TradeData
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
import timber.log.Timber
internal object BnbHelper {
fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer {
fun createMessageData(order: WcBinanceTransferOrder): BinanceMessageData.Transfer {
val input = order.msgs.first().inputs.first()
val output = order.msgs.first().inputs.first()
@ -42,51 +40,7 @@ internal object BnbHelper {
)
}
fun WcBinanceTradeOrder.toWCBinanceTradeOrder(): WCBinanceTradeOrder {
return WCBinanceTradeOrder(
account_number = accountNumber,
chain_id = chainId,
data = data,
memo = memo,
sequence = sequence,
source = source,
msgs = msgs.map { it.toWCBinanceTradeOrderMessage() },
)
}
private fun WcBinanceTradeOrder.Message.toWCBinanceTradeOrderMessage(): WCBinanceTradeOrder.Message {
return WCBinanceTradeOrder.Message(id, orderType, price, quantity, sender, side, symbol, timeInforce)
}
fun WcBinanceTransferOrder.toWCBinanceTransferOrder(): WCBinanceTransferOrder {
return WCBinanceTransferOrder(
account_number = accountNumber,
chain_id = chainId,
data = data,
memo = memo,
sequence = sequence,
source = source,
msgs = msgs.map { it.toWCBinanceTransferOrderMessage() },
)
}
private fun WcBinanceTransferOrder.Message.toWCBinanceTransferOrderMessage(): WCBinanceTransferOrder.Message {
return WCBinanceTransferOrder.Message(
inputs.map { it.toWCBinanceItem() },
outputs.map { it.toWCBinanceItem() },
)
}
private fun WcBinanceTransferOrder.Message.Item.toWCBinanceItem(): WCBinanceTransferOrder.Message.Item {
return WCBinanceTransferOrder.Message.Item(
address,
coins.map {
WCBinanceTransferOrder.Message.Item.Coin(it.amount, it.denom)
},
)
}
fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade {
fun createMessageData(order: WcBinanceTradeOrder): BinanceMessageData.Trade {
val address = order.msgs.first().sender
val tradeData = order.msgs.map {

View file

@ -1,562 +0,0 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.walletconnect.extensions.isDappSupported
import com.tangem.tap.domain.walletconnect.extensions.toWcEthereumSignMessage
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.walletConnectRepository
import com.trustwallet.walletconnect.WCClient
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import com.trustwallet.walletconnect.models.session.WCAddNetwork
import com.trustwallet.walletconnect.models.session.WCSession
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.collections.set
@Suppress("LargeClass")
internal class WalletConnectManager {
private var cardId: String? = null
private val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(RetryInterceptor())
.build()
}
private val interceptor by lazy {
createNetworkLoggingInterceptor()
}
private var sessions: MutableMap<Topic, WalletConnectActiveData> = mutableMapOf()
fun connect(wcUri: String) {
val session = WCSession.from(wcUri).guard {
store.dispatchOnMain(
WalletConnectAction.FailureEstablishingSession(
session = null,
error = TapError.WalletConnect.UnsupportedLink,
),
)
return
}
if (sessions[session.topic] != null) {
store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession)
return
}
val client = WCClient(httpClient = okHttpClient)
setListeners(client)
val peerId = UUID.randomUUID().toString()
try {
client.connect(session, tangemPeerMeta, peerId)
} catch (exception: IllegalArgumentException) {
store.dispatchOnMain(
WalletConnectAction.FailureEstablishingSession(
session = null,
error = TapError.WalletConnect.UnsupportedLink,
),
)
return
}
sessions[session.topic] = WalletConnectActiveData(
peerId = peerId,
remotePeerId = null,
session = session,
client = client,
wallet = WalletForSession(),
)
setupConnectionTimeoutCheck(session)
}
fun updateSession(session: WalletConnectSession) {
val updatedSession = sessions[session.session.topic]?.copy(
wallet = session.wallet,
)
if (updatedSession != null) {
sessions[session.session.topic] = updatedSession
}
}
fun updateBlockchain(session: WalletConnectSession) {
sessions[session.session.topic]?.client?.updateSession(
accounts = listOfNotNull(session.getAddress()),
chainId = session.wallet.blockchain?.getChainId(),
approved = true,
)
val updatedSession = sessions[session.session.topic]?.copy(
wallet = session.wallet,
)
if (updatedSession != null) {
sessions[session.session.topic] = updatedSession
}
}
@Suppress("MagicNumber")
private fun setupConnectionTimeoutCheck(session: WCSession) {
scope.launch {
delay(20_000)
val data = sessions[session.topic]
if (data != null && data.peerMeta == null) {
disconnect(session)
store.dispatchOnMain(WalletConnectAction.OpeningSessionTimeout(session))
}
}
}
fun restoreSessions(scanResponse: ScanResponse) {
val walletPublicKey = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }?.publicKey
?: return
if (scanResponse.card.backupStatus?.isActive != true) cardId = scanResponse.card.cardId
val sessions = walletConnectRepository.loadSavedSessions()
// filter sessions for this particular card
.filter { it.wallet.walletPublicKey.contentEquals(walletPublicKey) }
this.sessions = sessions
.map { session ->
WalletConnectActiveData(
peerId = session.peerId,
remotePeerId = session.remotePeerId,
client = WCClient(httpClient = okHttpClient),
session = session.session,
peerMeta = session.peerMeta,
wallet = session.wallet,
)
.also {
setListeners(it.client)
it.client.connect(it.session, tangemPeerMeta, it.peerId, it.remotePeerId)
}
}.associateBy { it.session.topic }.toMutableMap()
store.dispatchOnMain(WalletConnectAction.SetSessionsRestored(sessions))
}
fun approve(session: WCSession) {
val activeData = sessions[session.topic] ?: return
removeSimilarSessions(activeData)
val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return
val blockchain = activeData.wallet.getBlockchainForSession()
val accounts = listOf(blockchain.makeAddresses(key).first().value)
val approved = activeData.client.approveSession(
accounts = accounts,
chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!!,
)
if (approved) {
val walletConnectSession = WalletConnectSession(
peerId = activeData.peerId,
remotePeerId = activeData.remotePeerId,
wallet = activeData.wallet,
session = session,
peerMeta = activeData.peerMeta!!,
)
walletConnectRepository.saveSession(walletConnectSession)
store.dispatchOnMain(
WalletConnectAction.ApproveSession.Success(
walletConnectSession,
),
)
}
}
private fun removeSimilarSessions(activeData: WalletConnectActiveData) {
val sessionsToRemove = sessions.filter {
it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true &&
it.value.peerMeta?.url == activeData.peerMeta?.url &&
it.value.session != activeData.session
}
Timber.d("RemoveSimilarSessions: ${sessionsToRemove.values.map { it.client.session }}")
sessionsToRemove.forEach { disconnect(it.value.session) }
}
fun rejectRequest(topic: String, id: Long) {
val activeData = sessions[topic] ?: return
activeData.client.rejectRequest(id)
}
private fun acceptRequest(topic: String, id: Long, data: String) {
val activeData = sessions[topic] ?: return
activeData.client.approveRequest(id, data)
}
fun disconnect(session: WCSession) {
val activeData = sessions[session.topic] ?: return
val disconnected = if (activeData.client.isConnected) {
activeData.client.killSession()
} else {
true
}
if (disconnected) {
onSessionClosed(session)
}
}
private fun onSessionClosed(session: WCSession) {
sessions.remove(session.topic)
walletConnectRepository.removeSession(session)
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
}
fun handleTransactionRequest(
transaction: WcEthereumTransaction,
session: WalletConnectSession,
id: Long,
type: WcEthTransactionType,
) {
val activeData = sessions[session.session.topic] ?: return
scope.launch {
val data = WalletConnectSdkHelper().prepareTransactionData(
EthTransactionData(
transaction = transaction,
networkId = session.wallet.blockchain?.toNetworkId() ?: "",
rawDerivationPath = session.wallet.derivationPath?.rawPath,
id = id,
topic = session.session.topic,
type = type,
metaName = session.peerMeta.name,
metaUrl = session.peerMeta.url,
),
).guard {
sessions[session.session.topic] = activeData.copy(transactionData = null)
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
session.session.topic,
id,
),
)
return@launch
}
sessions[session.session.topic] = activeData.copy(transactionData = data)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.RequestTransaction(
WcPreparedRequest.EthTransaction(
preparedRequestData = data,
topic = session.session.topic,
requestId = id,
derivationPath = data.walletManager.wallet.publicKey.derivationPath?.rawPath,
),
),
),
)
}
}
fun completeTransaction(topic: Topic) {
val activeData = sessions[topic]
val data = activeData?.transactionData ?: return
scope.launch {
val hash = WalletConnectSdkHelper().completeTransaction(data, cardId).guard {
sessions[topic] = activeData.copy(transactionData = null)
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
topic,
data.id,
),
)
return@launch
}
acceptRequest(topic, data.id, hash)
sessions[topic] = activeData.copy(transactionData = null)
}
}
fun signBnb(id: Long, data: ByteArray, topic: Topic) {
val activeData = sessions[topic] ?: return
scope.launch {
val hash = WalletConnectSdkHelper().signBnbTransaction(
data = data,
networkId = activeData.wallet.blockchain?.toNetworkId() ?: "",
derivationPath = activeData.wallet.derivationPath?.rawPath,
cardId = cardId,
).guard {
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
topic,
id,
),
)
return@launch
}
acceptRequest(topic, id, hash)
}
}
fun handlePersonalSignRequest(message: WCEthereumSignMessage, session: WalletConnectSession, id: Long) {
val activeData = sessions[session.session.topic] ?: return
scope.launch {
val data = WalletConnectSdkHelper().prepareDataForPersonalSign(
message = message.toWcEthereumSignMessage(),
topic = session.session.topic,
id = id,
metaName = session.peerMeta.name,
)
sessions[session.session.topic] = activeData.copy(personalSignData = data)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.PersonalSign(
WcPreparedRequest.EthSign(
preparedRequestData = data,
topic = session.session.topic,
requestId = id,
derivationPath = session.wallet.derivationPath?.rawPath,
),
),
),
)
}
}
fun sendSignedMessage(topic: Topic) {
val activeData = sessions[topic]
val data = activeData?.personalSignData ?: return
scope.launch {
val hash = WalletConnectSdkHelper().signPersonalMessage(
hashToSign = data.hash,
networkId = activeData.wallet.blockchain?.toNetworkId() ?: "",
type = data.type,
derivationPath = activeData.wallet.derivationPath?.rawPath,
cardId = cardId,
)
.guard {
sessions[topic] = activeData.copy(transactionData = null)
store.dispatchOnMain(
WalletConnectAction.RejectRequest(
topic,
data.id,
),
)
return@launch
}
sessions[topic] = activeData.copy(personalSignData = data)
acceptRequest(topic, data.id, hash)
sessions[topic] = activeData.copy(transactionData = null)
}
}
@Suppress("LongMethod", "ComplexMethod")
private fun setListeners(client: WCClient) {
client.onSessionRequest = { id: Long, peer: WCPeerMeta ->
Timber.d("OnSessionRequest: $peer")
val session = client.session
val data = sessions[session?.topic]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId)
if (data != null && session != null) {
if (!peer.isDappSupported()) {
store.dispatchOnMain(
WalletConnectAction.FailureEstablishingSession(
session = session,
error = TapError.WalletConnect.UnsupportedDapp,
),
)
} else {
sessions[session.topic] = data
val sessionData = data.toWalletConnectSession()
sessionData?.let {
store.dispatchOnMain(
WalletConnectAction.ScanCard(
session = sessionData,
chainId = client.chainId?.toIntOrNull(),
),
)
}
}
}
}
client.onSessionUpdate = { id: Long, update: WCSessionUpdate ->
Timber.d("onSessionUpdate: $update")
val session = client.session
if (session != null && !update.approved) onSessionClosed(session)
}
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSendTransaction: $transaction")
// Analytics.logWcEvent(
// AnalyticsAnOld.WcAnalyticsEvent.Action(
// AnalyticsAnOld.WcAction.SendTransaction
// )
// )
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
session = sessionData,
id = id,
type = WcEthTransactionType.EthSendTransaction,
),
)
}
}
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSignTransaction: $transaction")
// Analytics.logWcEvent(
// AnalyticsAnOld.WcAnalyticsEvent.Action(
// AnalyticsAnOld.WcAction.SignTransaction
// )
// )
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
session = sessionData,
id = id,
type = WcEthTransactionType.EthSignTransaction,
),
)
}
}
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
Timber.d("onEthSign: $message")
// Analytics.logWcEvent(
// AnalyticsAnOld.WcAnalyticsEvent.Action(
// AnalyticsAnOld.WcAction.PersonalSign
// )
// )
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.HandlePersonalSignRequest(
message,
sessionData,
id,
),
)
}
}
client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder ->
}
client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder ->
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.BinanceTransaction.Trade(
id = id,
order = order,
sessionData = sessionData,
),
)
}
}
client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder ->
sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(
WalletConnectAction.BinanceTransaction.Transfer(
id = id,
order = order,
sessionData = sessionData,
),
)
}
}
client.onBnbTxConfirm = { id: Long, order: WCBinanceTxConfirmParam ->
// send empty approve request if status is OK
if (order.ok) client.approveRequest(id, "")
}
client.onDisconnect = { code: Int, reason: String ->
val session = client.session
if (session != null) {
onSessionClosed(session)
}
}
client.onWalletChangeNetwork = { id: Long, chainId: Int ->
switchChain(chainId, client)
}
client.onWalletAddNetwork = { id: Long, network: WCAddNetwork ->
// TODO
// In fact this method is used to add a EVM network. It provides RPC url and chain ID.
// Here now it just tries to switch to a EVM network with a provided chain ID.
try {
val chainId = Integer.decode(network.chainIdHex)
switchChain(chainId, client)
} catch (exception: Exception) {
Timber.d("WC add network error: chain could not be parsed")
}
}
}
private fun switchChain(chainId: Int, client: WCClient) {
val blockchain = Blockchain.fromChainId(chainId)
Timber.d("WC switch chainID\nNew Blockchain: $blockchain")
val session = sessions[client.session?.topic]?.toWalletConnectSession()
if (session != null) {
store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session))
}
}
companion object {
const val WC_SCHEME = "wc"
private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com")
fun isCorrectWcUri(string: String): Boolean = WCSession.from(string) != null
}
}
internal typealias Topic = String
internal data class WalletConnectActiveData(
val peerId: String,
val remotePeerId: String?,
val client: WCClient,
val session: WCSession,
val peerMeta: WCPeerMeta? = null,
val wallet: WalletForSession,
val transactionData: WcTransactionData? = null,
val personalSignData: WcPersonalSignData? = null,
) {
fun toWalletConnectSession(): WalletConnectSession? {
if (peerMeta == null) return null
return WalletConnectSession(
peerId = peerId,
remotePeerId = remotePeerId,
wallet = wallet,
session = session,
peerMeta = peerMeta,
)
}
}
@Suppress("MagicNumber")
internal class RetryInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val response = chain.proceed(request)
when (response.code) {
502 -> {
return chain.proceed(request)
}
}
return response
}
}

View file

@ -1,41 +0,0 @@
package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.trustwallet.walletconnect.models.WCPeerMeta
object WalletConnectNetworkUtils {
fun parseBlockchain(chainId: Int?, peer: WCPeerMeta): Blockchain? {
return when {
peer.url.contains("pancakeswap.finance") -> {
Blockchain.BSC
}
peer.url.contains("optimism") -> {
Blockchain.Optimism
}
chainId != null -> {
Blockchain.fromChainId(chainId)
}
peer.url.contains("matic.network") || peer.name == "Polygon" -> {
Blockchain.Polygon
}
peer.url.contains("binance.org") || peer.name.contains("Binance") -> {
if (peer.icons.firstOrNull()?.contains("testnet") == true) {
Blockchain.BinanceTestnet
} else {
Blockchain.Binance
}
}
peer.name.contains("BSC") -> {
Blockchain.BSC
}
peer.url.contains("honeyswap.1hive.eth.limo") -> {
// Check if something's changed after this bug report:
// https://github.com/1Hive/honeyswap-interface/issues/83
Blockchain.Gnosis
}
else -> {
Blockchain.Ethereum
}
}
}
}

View file

@ -1,105 +0,0 @@
package com.tangem.tap.domain.walletconnect
import android.app.Application
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.session.WCSession
import timber.log.Timber
import java.io.FileNotFoundException
import java.nio.charset.Charset
class WalletConnectRepository(val context: Application) {
private val walletConnectAdapter: JsonAdapter<List<SessionDao>> = MoshiConverter.sdkMoshi.adapter(
Types.newParameterizedType(List::class.java, SessionDao::class.java),
)
fun saveSession(session: WalletConnectSession) {
val sessions = loadSavedSessions() + session
saveSessions(sessions)
}
fun removeSession(session: WCSession) {
val sessions = loadSavedSessions().filterNot { it.session == session }
saveSessions(sessions)
}
fun loadSavedSessions(): List<WalletConnectSession> {
return try {
val json = context.readFileText(FILE_NAME_PREFIX_SESSIONS)
.hexToUtf8()
walletConnectAdapter.fromJson(json)!!.map { it.toSession() }
} catch (e: FileNotFoundException) {
emptyList()
} catch (exception: Exception) {
Timber.w(exception)
emptyList()
}
}
private fun saveSessions(sessions: List<WalletConnectSession>) {
val json = walletConnectAdapter.toJson(sessions.map { SessionDao.fromSession(it) })
.utf8ToHex() // convert to hex to solve problems with saving text with emojis
Timber.e("WC sessions, saving following json: $json")
context.rewriteFile(json, FILE_NAME_PREFIX_SESSIONS)
}
private fun String.utf8ToHex(): String {
return this.toByteArray().toHexString()
}
private fun String.hexToUtf8(): String {
return this.hexToBytes().toString(Charset.defaultCharset())
}
private fun Context.readFileText(fileName: String): String =
this.openFileInput(fileName).bufferedReader().readText()
private fun Context.rewriteFile(content: String, fileName: String) {
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
it.write(content.toByteArray(), 0, content.length)
}
}
companion object {
private const val FILE_NAME_PREFIX_SESSIONS = "wc_sessions"
}
}
@JsonClass(generateAdapter = true)
data class SessionDao(
val peerId: String,
val remotePeerId: String?,
val wallet: WalletForSession,
val session: WCSession,
val peerMeta: WCPeerMeta,
) {
fun toSession(): WalletConnectSession {
return WalletConnectSession(
peerId = peerId,
remotePeerId = remotePeerId,
wallet = wallet,
session = session,
peerMeta = peerMeta,
)
}
companion object {
fun fromSession(session: WalletConnectSession): SessionDao {
return SessionDao(
peerId = session.peerId,
remotePeerId = session.remotePeerId,
wallet = session.wallet,
session = session.session,
peerMeta = session.peerMeta,
)
}
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.*
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
@ -15,13 +16,10 @@ 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.domain.common.extensions.fromNetworkId
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTransferOrder
import com.tangem.tap.domain.walletconnect2.domain.TransactionType
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
@ -227,11 +225,11 @@ class WalletConnectSdkHelper {
}
fun prepareBnbTradeOrder(data: WcBinanceTradeOrder): BinanceMessageData.Trade {
return BnbHelper.createMessageData(data.toWCBinanceTradeOrder())
return BnbHelper.createMessageData(data)
}
fun prepareBnbTransferOrder(data: WcBinanceTransferOrder): BinanceMessageData.Transfer {
return BnbHelper.createMessageData(data.toWCBinanceTransferOrder())
return BnbHelper.createMessageData(data)
}
suspend fun signBnbTransaction(

View file

@ -1,39 +0,0 @@
package com.tangem.tap.domain.walletconnect.extensions
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
import com.trustwallet.walletconnect.models.WCPeerMeta
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
internal fun WCPeerMeta.isDappSupported(): Boolean {
return !unsupportedDappsList.any { this.url.contains(it) }
}
private val unsupportedDappsList: List<String> = listOf("dydx.exchange")
internal fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction {
return WcEthereumTransaction(
from = from,
to = to,
nonce = nonce,
gasPrice = gasPrice,
maxFeePerGas = maxFeePerGas,
maxPriorityFeePerGas = maxPriorityFeePerGas,
gas = gas,
gasLimit = gasLimit,
value = value,
data = data,
)
}
internal fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcSignMessage {
return WcSignMessage(
raw = raw,
type = when (type) {
WCEthereumSignMessage.WCSignType.MESSAGE -> WcSignMessage.WCSignType.MESSAGE
WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcSignMessage.WCSignType.PERSONAL_MESSAGE
WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcSignMessage.WCSignType.TYPED_MESSAGE
},
)
}

View file

@ -1,13 +1,13 @@
package com.tangem.tap.domain.walletconnect2.app
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper
import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles
internal class TangemWcBlockchainHelper(
private val featureToggles: WalletConnectFeatureToggles,
featureToggles: WalletConnectFeatureToggles,
) : WcBlockchainHelper {
private val supportedNonEvmBlockchains = if (featureToggles.isSolanaTxSignEnabled) {

View file

@ -6,6 +6,9 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.datasource.di.SdkMoshi
import com.tangem.datasource.files.FileReader
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl
@ -35,6 +38,9 @@ internal object WalletConnectInteractorModule {
wcRepository: WalletConnectRepository,
wcSessionsRepository: WalletConnectSessionsRepository,
walletConnectFeatureToggles: WalletConnectFeatureToggles,
currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade,
walletsStateHolder: WalletsStateHolder,
): WalletConnectInteractor {
return WalletConnectInteractor(
handler = WalletConnectEventsHandlerImpl(),
@ -42,7 +48,10 @@ internal object WalletConnectInteractorModule {
sessionsRepository = wcSessionsRepository,
sdkHelper = WalletConnectSdkHelper(),
blockchainHelper = TangemWcBlockchainHelper(walletConnectFeatureToggles),
dispatcher = AppCoroutineDispatcherProvider(),
currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
walletsStateHolder = walletsStateHolder,
dispatchers = AppCoroutineDispatcherProvider(),
)
}
}

View file

@ -1,26 +1,52 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.domain.models.*
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
@Suppress("LargeClass", "LongParameterList")
class WalletConnectInteractor(
private val handler: WalletConnectEventsHandler,
private val walletConnectRepository: WalletConnectRepository,
private val sessionsRepository: WalletConnectSessionsRepository,
private val sdkHelper: WalletConnectSdkHelper,
private val dispatcher: CoroutineDispatcherProvider,
private val dispatchers: CoroutineDispatcherProvider,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val walletsStateHolder: WalletsStateHolder,
val blockchainHelper: WcBlockchainHelper,
) {
private val getSelectedWalletUseCase by lazy(LazyThreadSafetyMode.NONE) {
GetSelectedWalletUseCase(walletsStateHolder)
}
private val wcScope = CoroutineScope(
Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("wcScope"),
)
private val listenerScope = CoroutineScope(
Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("listenScope"),
)
private val events = walletConnectRepository.events
private val sessions = walletConnectRepository.activeSessions
@ -34,19 +60,56 @@ class WalletConnectInteractor(
sdkHelper = sdkHelper,
)
suspend fun startListening(userWalletId: String, cardId: String?) {
init {
getSelectedWalletUseCase().onRight { userWalletFlow ->
userWalletFlow
.conflate()
.distinctUntilChanged()
.onEach(::initWithWallet)
.flowOn(dispatchers.io)
.launchIn(wcScope)
}
}
private suspend fun initWithWallet(userWallet: UserWallet) {
if (userWallet.isMultiCurrency) {
Timber.d("WalletConnect: initialize and setup networks for ${userWallet.walletId}")
startListeningWc(userWallet.walletId.stringValue, getCardId(userWallet))
subscribeOnCurrenciesUpdates(userWallet)
}
}
private fun subscribeOnCurrenciesUpdates(userWallet: UserWallet) {
currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWallet.walletId)
.conflate()
.distinctUntilChanged()
.onEach { currencies ->
setupUserChains(userWallet, currencies)
}
.flowOn(dispatchers.io)
.launchIn(wcScope)
}
private suspend fun setupUserChains(userWallet: UserWallet, currencies: List<CryptoCurrency>) {
val accounts = getAccountsForWc(
userWallet = userWallet,
networks = currencies.map { it.network },
)
setUserChains(accounts)
}
private suspend fun startListeningWc(userWalletId: String, cardId: String?) {
this.userWalletId = userWalletId
this.cardId = cardId
coroutineScope {
listenerScope.coroutineContext.cancelChildren()
listenerScope.launch {
launch { subscribeToEvents() }
launch { subscribeToSessions() }
walletConnectRepository.updateSessions()
}
}
fun setUserChains(accounts: List<Account>) {
private fun setUserChains(accounts: List<Account>) {
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
.groupBy { account ->
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
@ -106,7 +169,7 @@ class WalletConnectInteractor(
}
}
}
.flowOn(dispatcher.io)
.flowOn(dispatchers.io)
.collect()
}
@ -117,7 +180,7 @@ class WalletConnectInteractor(
val filteredSessions = filterSessionsForUserWallet(listOfSessions, relevantTopics)
handler.onListOfSessionsUpdated(filteredSessions)
}
.flowOn(dispatcher.io)
.flowOn(dispatchers.io)
.collect()
}
@ -258,6 +321,38 @@ class WalletConnectInteractor(
return uri.lowercase().startsWith(WC_SCHEME)
}
private fun getCardId(userWallet: UserWallet): String? {
return if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
userWallet.cardId
} else { // if wallet has backup, any card from wallet can be used to sign
null
}
}
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List<Network>): List<Account> {
val walletManagers = networks.mapNotNull {
val blockchain = Blockchain.fromId(it.id.value)
walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
blockchain = blockchain,
derivationPath = it.derivationPath.value,
)
}
return walletManagers.mapNotNull {
val wallet = it.wallet
val chainId = blockchainHelper.networkIdToChainIdOrNull(
wallet.blockchain.toNetworkId(),
)
chainId?.let {
Account(
chainId,
wallet.address,
wallet.publicKey.derivationPath?.rawPath,
)
}
}
}
private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? {
return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId)
}