Updated on 2026-08-14
This commit is contained in:
parent
9c68c9ff00
commit
4f499f41c7
17 changed files with 159 additions and 179 deletions
|
|
@ -7,7 +7,6 @@ import arrow.core.right
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.walletconnect.model.CAIP2
|
||||
import com.tangem.data.walletconnect.model.NamespaceKey
|
||||
import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter
|
||||
|
|
@ -15,7 +14,6 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Compani
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
|
@ -156,16 +154,6 @@ internal class WcEthNetwork(
|
|||
val ethChainId = chainId.reference.toIntOrNull() ?: return null
|
||||
return Blockchain.fromChainId(ethChainId)
|
||||
}
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = network.toBlockchain()
|
||||
if (!blockchain.isEvm()) return null
|
||||
val chainId = blockchain.getChainId() ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class Factories @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
WcMutableFee {
|
||||
|
||||
private var approvalAmount: WcApprovedAmount? = null
|
||||
// in case of change TransactionExtras
|
||||
// change approvalAmount for example
|
||||
private var isIgnoreDAppFee: Boolean = false
|
||||
private var dAppFee: Fee? = null
|
||||
|
||||
override val securityStatus: LceFlow<Throwable, BlockAidTransactionCheck.Result> =
|
||||
|
|
@ -102,7 +105,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
},
|
||||
)
|
||||
approvalAmount = action.amount
|
||||
dAppFee = null
|
||||
isIgnoreDAppFee = true
|
||||
uncompiled.copy(extras = extras.copy(callData = callData))
|
||||
}
|
||||
is WcEthTxAction.UpdateFee -> uncompiled.copy(fee = action.fee)
|
||||
|
|
@ -111,8 +114,11 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
emit(newState)
|
||||
}
|
||||
|
||||
override fun dAppFee(): Fee? {
|
||||
return dAppFee
|
||||
override suspend fun dAppFee(): Fee? {
|
||||
if (isIgnoreDAppFee) return null
|
||||
if (dAppFee != null) return dAppFee
|
||||
return ethTxHelper.getDAppFee(method.transaction, wallet, network)
|
||||
.also { dAppFee = it }
|
||||
}
|
||||
|
||||
override fun updateFee(fee: Fee) {
|
||||
|
|
@ -120,9 +126,8 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
|
||||
dAppFee = ethTxHelper.getDAppFee(method.transaction, wallet, network)
|
||||
val transactionData = ethTxHelper.createTransactionData(
|
||||
dAppFee = dAppFee,
|
||||
dAppFee = dAppFee(),
|
||||
network = context.network,
|
||||
txParams = method.transaction,
|
||||
) ?: return@flow
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
WcMutableFee {
|
||||
|
||||
private var approvalAmount: WcApprovedAmount? = null
|
||||
// in case of change TransactionExtras
|
||||
// change approvalAmount for example
|
||||
private var isIgnoreDAppFee: Boolean = false
|
||||
private var dAppFee: Fee? = null
|
||||
|
||||
override val securityStatus = blockAidDelegate.getSecurityStatus(
|
||||
|
|
@ -101,7 +104,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
},
|
||||
)
|
||||
approvalAmount = action.amount
|
||||
dAppFee = null
|
||||
isIgnoreDAppFee = true
|
||||
uncompiled.copy(extras = extras.copy(callData = callData))
|
||||
}
|
||||
is WcEthTxAction.UpdateFee -> uncompiled.copy(fee = action.fee)
|
||||
|
|
@ -115,17 +118,19 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
|
||||
dAppFee = ethTxHelper.getDAppFee(method.transaction, wallet, network)
|
||||
val transactionData = ethTxHelper.createTransactionData(
|
||||
dAppFee = dAppFee,
|
||||
dAppFee = dAppFee(),
|
||||
network = context.network,
|
||||
txParams = method.transaction,
|
||||
) ?: return@flow
|
||||
emitAll(delegate.invoke(transactionData))
|
||||
}
|
||||
|
||||
override fun dAppFee(): Fee? {
|
||||
return dAppFee
|
||||
override suspend fun dAppFee(): Fee? {
|
||||
if (isIgnoreDAppFee) return null
|
||||
if (dAppFee != null) return dAppFee
|
||||
return ethTxHelper.getDAppFee(method.transaction, wallet, network)
|
||||
.also { dAppFee = it }
|
||||
}
|
||||
|
||||
override fun getAmount(): WcApprovedAmount? {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.squareup.moshi.Moshi
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.extensions.decodeBase58
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.data.walletconnect.model.CAIP2
|
||||
import com.tangem.data.walletconnect.model.NamespaceKey
|
||||
|
|
@ -17,7 +16,6 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Compani
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.HandleMethodError
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethodName
|
||||
|
|
@ -88,27 +86,14 @@ internal class WcSolanaNetwork(
|
|||
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
val isMainNet = MAINNET_CHAIN_ID.any { it.lowercase() == chainId.reference.lowercase() }
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when (chainId.reference) {
|
||||
MAINNET_CHAIN_ID -> Blockchain.Solana
|
||||
TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet
|
||||
return when {
|
||||
isMainNet -> Blockchain.Solana
|
||||
chainId.reference.lowercase() == TESTNET_CHAIN_ID -> Blockchain.SolanaTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toCAIP2(network: Network): CAIP2? {
|
||||
val blockchain = network.toBlockchain()
|
||||
val chainId = when (blockchain) {
|
||||
Blockchain.Solana -> MAINNET_CHAIN_ID
|
||||
Blockchain.SolanaTestnet -> TESTNET_CHAIN_ID
|
||||
else -> null
|
||||
}
|
||||
chainId ?: return null
|
||||
return CAIP2(
|
||||
namespace = namespaceKey.key,
|
||||
reference = chainId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): Either<Throwable, WcSolanaMethod?> {
|
||||
|
|
@ -140,7 +125,7 @@ internal class WcSolanaNetwork(
|
|||
)
|
||||
|
||||
companion object {
|
||||
private const val MAINNET_CHAIN_ID = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
||||
private val MAINNET_CHAIN_ID = listOf("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")
|
||||
private const val TESTNET_CHAIN_ID = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z"
|
||||
}
|
||||
}
|
||||
|
|
@ -110,8 +110,10 @@ internal class AssociateNetworksDelegate(
|
|||
.distinctBy { it.rawId }
|
||||
}
|
||||
|
||||
private fun Map<String, Namespace.Proposal>.setOfChainId(): Set<String> =
|
||||
this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
|
||||
private fun missingNetworkName(chainId: String): String = chainId.replaceFirstChar(Char::titlecase)
|
||||
|
||||
companion object {
|
||||
internal fun Map<String, Namespace.Proposal>.setOfChainId(): Set<String> =
|
||||
this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.data.walletconnect.pair
|
|||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.data.walletconnect.model.CAIP10
|
||||
import com.tangem.data.walletconnect.model.CAIP2
|
||||
import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate.Companion.setOfChainId
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -22,6 +24,26 @@ internal class CaipNamespaceDelegate(
|
|||
val userWallet = sessionForApprove.wallet
|
||||
val result = mutableMapOf<String, Session>()
|
||||
|
||||
val requiredNamespaces = sessionProposal.requiredNamespaces.setOfChainId()
|
||||
val optionalNamespaces = sessionProposal.optionalNamespaces.setOfChainId()
|
||||
val allWcNetworks = (requiredNamespaces + optionalNamespaces)
|
||||
.mapNotNull { chainId ->
|
||||
val network = namespaceConverters
|
||||
.firstNotNullOfOrNull { it.toNetwork(chainId, userWallet) }
|
||||
?: return@mapNotNull null
|
||||
val caip2 = CAIP2.fromRaw(chainId) ?: return@mapNotNull null
|
||||
network to caip2
|
||||
}
|
||||
|
||||
suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, network)
|
||||
val chainId = allWcNetworks
|
||||
.find { (wcNetwork, _) -> network.rawId == wcNetwork.rawId }
|
||||
?.second
|
||||
if (chainId == null || address == null) return null
|
||||
return CAIP10(chainId = chainId, accountAddress = address)
|
||||
}
|
||||
|
||||
wcNetworksConverter.convertNetworksForApprove(sessionForApprove)
|
||||
.mapNotNull { createCAIP10(userWallet.walletId, it) }
|
||||
.forEach { account ->
|
||||
|
|
@ -52,13 +74,6 @@ internal class CaipNamespaceDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, network)
|
||||
val chainId = namespaceConverters.firstNotNullOfOrNull { it.toCAIP2(network) }
|
||||
if (chainId == null || address == null) return null
|
||||
return CAIP10(chainId = chainId, accountAddress = address)
|
||||
}
|
||||
|
||||
private data class Session(
|
||||
val chains: MutableSet<String> = mutableSetOf(),
|
||||
val accounts: MutableSet<String> = mutableSetOf(),
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
}.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")
|
||||
}
|
||||
|
|
@ -134,7 +135,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
private suspend fun walletKitApproveSession(
|
||||
sessionForApprove: WcSessionApprove,
|
||||
sdkSessionProposal: Wallet.Model.SessionProposal,
|
||||
): Either<WcPairError, Wallet.Model.SettledSessionResponse.Result> {
|
||||
): Either<WcPairError, Wallet.Model.SettledSessionResponse.Result> = try {
|
||||
val namespaces = caipNamespaceDelegate.associate(
|
||||
sdkSessionProposal,
|
||||
sessionForApprove,
|
||||
|
|
@ -143,7 +144,10 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
proposerPublicKey = sdkSessionProposal.proposerPublicKey,
|
||||
namespaces = namespaces,
|
||||
)
|
||||
return sdkDelegate.approve(sessionApprove)
|
||||
sdkDelegate.approve(sessionApprove)
|
||||
} catch (e: Throwable) {
|
||||
Timber.tag(WC_TAG).e(e, "Failed to sdk approve session $pairRequest")
|
||||
WcPairError.ApprovalFailed(e.message.orEmpty()).left()
|
||||
}
|
||||
|
||||
private suspend fun buildProposalState(
|
||||
|
|
|
|||
|
|
@ -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 = 30
|
||||
private const val CALLBACK_TIMEOUT = 60
|
||||
// com.reown.android.pairing.engine.domain.PairingEngine.pair
|
||||
private val pairingExpiredMessages = listOf(
|
||||
"Pairing URI expired",
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ internal class DefaultWcSessionsManager(
|
|||
if (someMigrated) return@transform // ignore emit, wait next one
|
||||
}
|
||||
val associatedSessions: List<WcSession> = associate(inSdk, inStore, wallets)
|
||||
val someRemove = removeUnknownSessions(inStore, associatedSessions)
|
||||
val someRemove = removeUnknownSessions(inStore, inSdk, associatedSessions)
|
||||
if (someRemove) return@transform // ignore emit, wait next one
|
||||
emit(associatedSessions.groupBy { it.wallet })
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ internal class DefaultWcSessionsManager(
|
|||
override suspend fun removeSession(session: WcSession): Either<Throwable, Unit> {
|
||||
val topic = session.sdkModel.topic
|
||||
val sdkCall = sdkDisconnectSession(topic)
|
||||
.onRight { onSessionDelete.trySend(Wallet.Model.SessionDelete.Success(topic = topic, reason = "")) }
|
||||
onSessionDelete.trySend(Wallet.Model.SessionDelete.Success(topic = topic, reason = ""))
|
||||
analytics.send(WcAnalyticEvents.SessionDisconnected(session.sdkModel.appMetaData))
|
||||
return sdkCall
|
||||
}
|
||||
|
|
@ -142,10 +142,17 @@ internal class DefaultWcSessionsManager(
|
|||
return wcSessions
|
||||
}
|
||||
|
||||
private suspend fun removeUnknownSessions(storeSessions: Set<WcSessionDTO>, wcSessions: List<WcSession>): Boolean {
|
||||
private suspend fun removeUnknownSessions(
|
||||
storeSessions: Set<WcSessionDTO>,
|
||||
inSdkSessions: List<Wallet.Model.Session>,
|
||||
wcSessions: List<WcSession>,
|
||||
): Boolean {
|
||||
val unknownStoredSessions = storeSessions
|
||||
.filterNot { dto -> wcSessions.any { it.sdkModel.topic == dto.topic } }
|
||||
val haveSomeUnknown = unknownStoredSessions.isNotEmpty()
|
||||
val unknownSdkSessions = inSdkSessions
|
||||
.filterNot { sdkSession -> wcSessions.any { it.sdkModel.topic == sdkSession.topic } }
|
||||
val haveSomeUnknownSdkSessions = unknownSdkSessions.isNotEmpty()
|
||||
|
||||
if (haveSomeUnknown) {
|
||||
Timber.tag(WC_TAG).i("removeUnknownSessions $unknownStoredSessions")
|
||||
|
|
@ -164,7 +171,10 @@ internal class DefaultWcSessionsManager(
|
|||
store.removeSessions(emptyNetworksDto)
|
||||
}
|
||||
if (haveEmptySessions) {
|
||||
emptyNetworkSessions.map { scope.launch { sdkDisconnectSession(it.sdkModel.topic) } }
|
||||
emptyNetworkSessions.forEach { scope.launch { sdkDisconnectSession(it.sdkModel.topic) } }
|
||||
}
|
||||
if (haveSomeUnknownSdkSessions) {
|
||||
unknownSdkSessions.forEach { scope.launch { sdkDisconnectSession(it.topic) } }
|
||||
}
|
||||
return haveSomeUnknown || haveEmptyDto
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ internal interface WcNamespaceConverter {
|
|||
fun toBlockchain(chainId: CAIP2): Blockchain?
|
||||
fun toBlockchain(chainId: String): Blockchain? = toCAIP2(chainId)?.let { caip2 -> toBlockchain(caip2) }
|
||||
|
||||
fun toCAIP2(network: Network): CAIP2?
|
||||
fun toCAIP2(chainId: String): CAIP2? = CAIP2.fromRaw(chainId)
|
||||
|
||||
fun toNetwork(chainId: String, wallet: UserWallet): Network? {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue