Updated on 2026-08-14
This commit is contained in:
parent
2b383418c0
commit
1326b5bab3
41 changed files with 1381 additions and 38 deletions
|
|
@ -34,6 +34,7 @@ dependencies {
|
|||
/* Project - Core */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.analytics)
|
||||
api(projects.core.configToggles)
|
||||
|
||||
/* DI */
|
||||
implementation(deps.hilt.core)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.DefaultWalletConnectRepository
|
||||
import com.tangem.data.walletconnect.initialize.DefaultWcInitializeUseCase
|
||||
import com.tangem.data.walletconnect.network.bitcoin.WcBitcoinNetwork
|
||||
import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork
|
||||
import com.tangem.data.walletconnect.network.solana.WcSolanaNetwork
|
||||
import com.tangem.data.walletconnect.pair.*
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
|||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.WcRequestService
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.featuretoggle.WalletConnectFeatureToggles
|
||||
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase
|
||||
|
|
@ -34,6 +36,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.walletconnect.featuretoggle.DefaultWalletConnectFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -162,6 +166,22 @@ internal object WalletConnectDataModule {
|
|||
networksConverter = wcNetworksConverter,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcBitcoinNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcBitcoinNetwork.Factories,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): WcBitcoinNetwork = WcBitcoinNetwork(
|
||||
moshi = moshi,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
networksConverter = wcNetworksConverter,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun caipNamespaceDelegate(
|
||||
|
|
@ -198,11 +218,19 @@ internal object WalletConnectDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun diHelperBox(ethNetwork: WcEthNetwork, solanaNetwork: WcSolanaNetwork) = DiHelperBox(
|
||||
handlers = setOf(
|
||||
ethNetwork,
|
||||
solanaNetwork,
|
||||
),
|
||||
fun diHelperBox(
|
||||
ethNetwork: WcEthNetwork,
|
||||
solanaNetwork: WcSolanaNetwork,
|
||||
bitcoinNetwork: WcBitcoinNetwork,
|
||||
featureToggles: WalletConnectFeatureToggles,
|
||||
) = DiHelperBox(
|
||||
handlers = buildSet {
|
||||
add(ethNetwork)
|
||||
add(solanaNetwork)
|
||||
if (featureToggles.isBitcoinEnabled) {
|
||||
add(bitcoinNetwork)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -210,10 +238,15 @@ internal object WalletConnectDataModule {
|
|||
fun namespaceConverters(
|
||||
ethNamespaceConverter: WcEthNetwork.NamespaceConverter,
|
||||
solanaNamespaceConverter: WcSolanaNetwork.NamespaceConverter,
|
||||
): Set<@JvmSuppressWildcards WcNamespaceConverter> = setOf(
|
||||
ethNamespaceConverter,
|
||||
solanaNamespaceConverter,
|
||||
)
|
||||
bitcoinNamespaceConverter: WcBitcoinNetwork.NamespaceConverter,
|
||||
featureToggles: WalletConnectFeatureToggles,
|
||||
): Set<@JvmSuppressWildcards WcNamespaceConverter> = buildSet {
|
||||
add(ethNamespaceConverter)
|
||||
add(solanaNamespaceConverter)
|
||||
if (featureToggles.isBitcoinEnabled) {
|
||||
add(bitcoinNamespaceConverter)
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
@ -239,6 +272,14 @@ internal object WalletConnectDataModule {
|
|||
return WcSolanaNetwork.NamespaceConverter(excludedBlockchains)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcBitcoinNetworkNamespaceConverter(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): WcBitcoinNetwork.NamespaceConverter {
|
||||
return WcBitcoinNetwork.NamespaceConverter(excludedBlockchains)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWcDisconnectUseCase(
|
||||
|
|
@ -248,6 +289,14 @@ internal object WalletConnectDataModule {
|
|||
return WcDisconnectUseCase(sessionsManager, analytics)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletConnectFeatureToggles(
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): WalletConnectFeatureToggles {
|
||||
return DefaultWalletConnectFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
|
||||
internal class DiHelperBox(
|
||||
val handlers: Set<WcRequestToUseCaseConverter>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.data.walletconnect.featuretoggle
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.walletconnect.featuretoggle.WalletConnectFeatureToggles
|
||||
|
||||
internal class DefaultWalletConnectFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : WalletConnectFeatureToggles {
|
||||
|
||||
override val isBitcoinEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.WALLET_CONNECT_BITCOIN_ENABLED)
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import com.reown.walletkit.client.WalletKit
|
|||
import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
|
||||
import com.tangem.data.walletconnect.request.DefaultWcRequestService
|
||||
import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* JSON request model for sendTransfer method.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinSendTransferRequest(
|
||||
@Json(name = "account")
|
||||
val account: String,
|
||||
|
||||
@Json(name = "recipientAddress")
|
||||
val recipientAddress: String,
|
||||
|
||||
@Json(name = "amount")
|
||||
val amount: String,
|
||||
|
||||
@Json(name = "memo")
|
||||
val memo: String? = null,
|
||||
|
||||
@Json(name = "changeAddress")
|
||||
val changeAddress: String? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* JSON request model for getAccountAddresses method.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinGetAccountAddressesRequest(
|
||||
@Json(name = "account")
|
||||
val account: String? = null,
|
||||
|
||||
@Json(name = "intentions")
|
||||
val intentions: List<String>? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* JSON request model for signPsbt method.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinSignPsbtRequest(
|
||||
@Json(name = "psbt")
|
||||
val psbt: String,
|
||||
|
||||
@Json(name = "signInputs")
|
||||
val signInputs: List<WcBitcoinSignInput>,
|
||||
|
||||
@Json(name = "broadcast")
|
||||
val isBroadcast: Boolean? = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* JSON model for sign input specification.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinSignInput(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
|
||||
@Json(name = "index")
|
||||
val index: Int,
|
||||
|
||||
@Json(name = "sighashTypes")
|
||||
val sighashTypes: List<Int>? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* JSON request model for signMessage method.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinSignMessageRequest(
|
||||
@Json(name = "account")
|
||||
val account: String,
|
||||
|
||||
@Json(name = "message")
|
||||
val message: String,
|
||||
|
||||
@Json(name = "address")
|
||||
val address: String? = null,
|
||||
|
||||
@Json(name = "protocol")
|
||||
val protocol: String? = "ecdsa",
|
||||
)
|
||||
|
||||
/**
|
||||
* JSON response model for getAccountAddresses method.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinGetAccountAddressesResponse(
|
||||
@Json(name = "addresses")
|
||||
val addresses: List<WcBitcoinAddressInfo>,
|
||||
)
|
||||
|
||||
/**
|
||||
* Address information in getAccountAddresses response.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcBitcoinAddressInfo(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
|
||||
@Json(name = "publicKey")
|
||||
val publicKey: String? = null,
|
||||
|
||||
@Json(name = "path")
|
||||
val path: String? = null,
|
||||
|
||||
@Json(name = "intention")
|
||||
val intention: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.blockchains.bitcoin.walletconnect.models.AccountAddress
|
||||
import com.tangem.blockchain.extensions.Result as SdkResult
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.HandleMethodError
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcGetAddressesUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
/**
|
||||
* Use case for Bitcoin getAccountAddresses WalletConnect method.
|
||||
*
|
||||
* Returns wallet addresses filtered by intention (payment/ordinal).
|
||||
* This is a non-signing operation.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddressInfo(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "publicKey") val publicKey: String? = null,
|
||||
@Json(name = "path") val path: String? = null,
|
||||
@Json(name = "intention") val intention: String? = null,
|
||||
)
|
||||
|
||||
internal class WcBitcoinGetAccountAddressesUseCase @AssistedInject constructor(
|
||||
@Assisted val context: WcMethodUseCaseContext,
|
||||
@Assisted override val method: WcBitcoinMethod.GetAccountAddresses,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val respondService: WcRespondService,
|
||||
@SdkMoshi private val moshi: Moshi,
|
||||
) : WcGetAddressesUseCase {
|
||||
|
||||
override val wallet get() = context.session.wallet
|
||||
|
||||
override val session: WcSession
|
||||
get() = context.session
|
||||
override val rawSdkRequest: WcSdkSessionRequest
|
||||
get() = context.rawSdkRequest
|
||||
override val network: Network
|
||||
get() = context.network
|
||||
override val derivationState: WcNetworkDerivationState = when {
|
||||
context.networkDerivationsCount > 1 -> WcNetworkDerivationState.Multiple(walletAddress = context.accountAddress)
|
||||
else -> WcNetworkDerivationState.Single
|
||||
}
|
||||
|
||||
override suspend fun invoke(): Either<HandleMethodError, WcGetAddressesUseCase.GetAddressesResult> {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(wallet.walletId, network)
|
||||
?: return HandleMethodError.UnknownError("Failed to create wallet manager").left()
|
||||
return when (val result = walletManager.getAddresses(filterOptions = method.intentions)) {
|
||||
is SdkResult.Success -> {
|
||||
val accountAddresses = result.data.map { addressInfo ->
|
||||
AccountAddress(
|
||||
address = addressInfo.address,
|
||||
publicKey = addressInfo.publicKey,
|
||||
path = addressInfo.derivationPath,
|
||||
intention = addressInfo.metadata?.get("intention") as? String,
|
||||
)
|
||||
}
|
||||
val response = buildJsonResponse(accountAddresses)
|
||||
respondService.respond(rawSdkRequest, response)
|
||||
WcGetAddressesUseCase.GetAddressesResult(
|
||||
addresses = accountAddresses.map { addr ->
|
||||
WcGetAddressesUseCase.AddressInfo(
|
||||
address = addr.address,
|
||||
publicKey = addr.publicKey,
|
||||
path = addr.path,
|
||||
intention = addr.intention,
|
||||
)
|
||||
},
|
||||
).right()
|
||||
}
|
||||
is SdkResult.Failure -> {
|
||||
HandleMethodError.UnknownError(result.error.customMessage).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
respondService.rejectRequestNonBlock(rawSdkRequest)
|
||||
}
|
||||
|
||||
private fun buildJsonResponse(accountAddresses: List<AccountAddress>): String {
|
||||
val addresses = accountAddresses.map { addr ->
|
||||
AddressInfo(
|
||||
address = addr.address,
|
||||
publicKey = addr.publicKey,
|
||||
path = addr.path,
|
||||
intention = addr.intention,
|
||||
)
|
||||
}
|
||||
return moshi.adapter<List<AddressInfo>>(
|
||||
com.squareup.moshi.Types.newParameterizedType(List::class.java, AddressInfo::class.java),
|
||||
).toJson(addresses)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
context: WcMethodUseCaseContext,
|
||||
method: WcBitcoinMethod.GetAccountAddresses,
|
||||
): WcBitcoinGetAccountAddressesUseCase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.walletconnect.model.CAIP2
|
||||
import com.tangem.data.walletconnect.model.NamespaceKey
|
||||
import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter
|
||||
import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.walletconnect.model.HandleMethodError
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethodName
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import jakarta.inject.Inject
|
||||
|
||||
/**
|
||||
* WalletConnect request handler for Bitcoin blockchain.
|
||||
*
|
||||
* Handles Bitcoin-specific RPC methods: sendTransfer, getAccountAddresses, signPsbt, signMessage.
|
||||
*
|
||||
* @see <a href="https://docs.reown.com/advanced/multichain/rpc-reference/bitcoin-rpc">Bitcoin RPC Reference</a>
|
||||
*/
|
||||
internal class WcBitcoinNetwork(
|
||||
private val moshi: Moshi,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val networksConverter: WcNetworksConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
override fun toWcMethodName(request: WcSdkSessionRequest): WcBitcoinMethodName? {
|
||||
val methodKey = request.request.method
|
||||
return WcBitcoinMethodName.entries.find { it.raw == methodKey }
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override suspend fun toUseCase(request: WcSdkSessionRequest): Either<HandleMethodError, WcMethodUseCase> {
|
||||
fun error(message: String) = HandleMethodError.UnknownError(message).left()
|
||||
|
||||
val name = toWcMethodName(request) ?: return error("Unknown method name")
|
||||
val method: WcBitcoinMethod = name.toMethod(request)
|
||||
.getOrElse { return error(it.message.orEmpty()) }
|
||||
?: return error("Failed to parse $name")
|
||||
|
||||
val session = sessionsManager.findSessionByTopic(request.topic)
|
||||
?: return HandleMethodError.UnknownSession.left()
|
||||
|
||||
val wallet = session.wallet
|
||||
val chainId = request.chainId.orEmpty()
|
||||
|
||||
val account = session.account
|
||||
|
||||
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(
|
||||
rawChainId = chainId,
|
||||
account = account,
|
||||
)
|
||||
|
||||
suspend fun anyAddress() = anyExistNetwork()
|
||||
?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() }
|
||||
.orEmpty()
|
||||
|
||||
val accountAddress = when (method) {
|
||||
is WcBitcoinMethod.SendTransfer -> method.account
|
||||
is WcBitcoinMethod.GetAccountAddresses -> method.account
|
||||
is WcBitcoinMethod.SignPsbt -> method.signInputs.firstOrNull()?.address ?: anyAddress()
|
||||
is WcBitcoinMethod.SignMessage -> method.address ?: method.account
|
||||
}
|
||||
|
||||
val walletNetwork = networksConverter
|
||||
.findWalletNetworkForRequest(request, session, accountAddress)
|
||||
?: anyExistNetwork()
|
||||
?: return error("Failed to find walletNetwork for accountAddress $accountAddress")
|
||||
|
||||
val context = WcMethodUseCaseContext(
|
||||
session = session,
|
||||
rawSdkRequest = request,
|
||||
network = walletNetwork,
|
||||
accountAddress = accountAddress,
|
||||
networkDerivationsCount = networksConverter.filterWalletNetworkForRequest(
|
||||
rawChainId = chainId,
|
||||
account = account,
|
||||
).size,
|
||||
)
|
||||
|
||||
val useCase = when (method) {
|
||||
is WcBitcoinMethod.SendTransfer -> factories.sendTransfer.create(context, method)
|
||||
is WcBitcoinMethod.GetAccountAddresses -> factories.getAccountAddresses.create(context, method)
|
||||
is WcBitcoinMethod.SignPsbt -> factories.signPsbt.create(context, method)
|
||||
is WcBitcoinMethod.SignMessage -> factories.signMessage.create(context, method)
|
||||
}
|
||||
return useCase.right()
|
||||
}
|
||||
|
||||
private fun WcBitcoinMethodName.toMethod(request: WcSdkSessionRequest): Either<Throwable, WcBitcoinMethod?> {
|
||||
val rawParams = request.request.params
|
||||
return when (this) {
|
||||
WcBitcoinMethodName.SendTransfer -> moshi.fromJson<WcBitcoinSendTransferRequest>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.let { req ->
|
||||
WcBitcoinMethod.SendTransfer(
|
||||
account = req.account,
|
||||
recipientAddress = req.recipientAddress,
|
||||
amount = req.amount,
|
||||
memo = req.memo,
|
||||
changeAddress = req.changeAddress,
|
||||
)
|
||||
}
|
||||
WcBitcoinMethodName.GetAccountAddresses -> moshi.fromJson<WcBitcoinGetAccountAddressesRequest>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.let { req ->
|
||||
WcBitcoinMethod.GetAccountAddresses(
|
||||
account = req.account.orEmpty(),
|
||||
intentions = req.intentions,
|
||||
)
|
||||
}
|
||||
WcBitcoinMethodName.SignPsbt -> moshi.fromJson<WcBitcoinSignPsbtRequest>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.let { req ->
|
||||
WcBitcoinMethod.SignPsbt(
|
||||
psbt = req.psbt,
|
||||
signInputs = req.signInputs.map { input ->
|
||||
WcBitcoinMethod.SignInput(
|
||||
address = input.address,
|
||||
index = input.index,
|
||||
sighashTypes = input.sighashTypes,
|
||||
)
|
||||
},
|
||||
shouldBroadcast = req.isBroadcast == true,
|
||||
)
|
||||
}
|
||||
WcBitcoinMethodName.SignMessage -> moshi.fromJson<WcBitcoinSignMessageRequest>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.let { req ->
|
||||
WcBitcoinMethod.SignMessage(
|
||||
account = req.account,
|
||||
message = req.message,
|
||||
address = req.address,
|
||||
protocol = req.protocol ?: "ecdsa",
|
||||
)
|
||||
}
|
||||
}.right()
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitcoin namespace converter for CAIP-2 chain IDs.
|
||||
*
|
||||
* Bitcoin uses BIP-122 namespace with genesis block hash as reference.
|
||||
* Example: bip122:000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
||||
*/
|
||||
internal class NamespaceConverter @Inject constructor(
|
||||
override val excludedBlockchains: ExcludedBlockchains,
|
||||
) : WcNamespaceConverter {
|
||||
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey(NAMESPACE)
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when {
|
||||
isMainnetReference(chainId.reference) -> Blockchain.Bitcoin
|
||||
isTestnetReference(chainId.reference) -> Blockchain.BitcoinTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isMainnetReference(reference: String): Boolean {
|
||||
return MAINNET_GENESIS_PREFIX.any { reference.startsWith(it, ignoreCase = true) } ||
|
||||
reference.equals("mainnet", ignoreCase = true)
|
||||
}
|
||||
|
||||
private fun isTestnetReference(reference: String): Boolean {
|
||||
return TESTNET_GENESIS_PREFIX.any { reference.startsWith(it, ignoreCase = true) } ||
|
||||
reference.equals("testnet", ignoreCase = true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NAMESPACE = "bip122"
|
||||
|
||||
// Bitcoin mainnet genesis hash prefixes (supports any truncated version)
|
||||
private val MAINNET_GENESIS_PREFIX = listOf(
|
||||
"000000000019d6689c085ae165831e93", // Mainnet genesis hash prefix (min 32 chars for uniqueness)
|
||||
)
|
||||
|
||||
// Bitcoin testnet genesis hash prefixes (supports any truncated version)
|
||||
private val TESTNET_GENESIS_PREFIX = listOf(
|
||||
"000000000933ea01ad0ee984209779ba", // Standard testnet genesis hash prefix (9 leading zeros)
|
||||
"0000000000933ea01ad0ee984209779ba", // Alternative testnet prefix (10 leading zeros)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory classes for creating Bitcoin WalletConnect use cases.
|
||||
*/
|
||||
internal class Factories @Inject constructor(
|
||||
val sendTransfer: WcBitcoinSendTransferUseCase.Factory,
|
||||
val getAccountAddresses: WcBitcoinGetAccountAddressesUseCase.Factory,
|
||||
val signPsbt: WcBitcoinSignPsbtUseCase.Factory,
|
||||
val signMessage: WcBitcoinSignMessageUseCase.Factory,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val NAMESPACE = "bip122"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import arrow.core.left
|
||||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinTransactionExtras
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.Result as SdkResult
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
|
||||
import com.tangem.data.walletconnect.sign.SignCollector
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.walletconnect.WcTransactionSignerProvider
|
||||
import com.tangem.domain.walletconnect.model.HandleMethodError
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMutableFee
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Use case for Bitcoin sendTransfer WalletConnect method.
|
||||
*
|
||||
* Sends a Bitcoin transfer transaction with optional memo (OP_RETURN) and custom change address.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class WcBitcoinSendTransferUseCase @AssistedInject constructor(
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted override val method: WcBitcoinMethod.SendTransfer,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val signerProvider: WcTransactionSignerProvider,
|
||||
override val respondService: WcRespondService,
|
||||
override val analytics: AnalyticsEventHandler,
|
||||
blockAidDelegate: BlockAidVerificationDelegate,
|
||||
) : BaseWcSignUseCase<WcBitcoinTxAction, TransactionData>(),
|
||||
WcTransactionUseCase,
|
||||
WcMutableFee {
|
||||
|
||||
override val wallet get() = context.session.wallet
|
||||
|
||||
private val transferAmount: Amount by lazy {
|
||||
createAmountFromSatoshis(method.amount)
|
||||
}
|
||||
|
||||
override val securityStatus: LceFlow<Throwable, BlockAidTransactionCheck.Result> =
|
||||
blockAidDelegate.getSecurityStatus(
|
||||
network = network,
|
||||
method = method,
|
||||
rawSdkRequest = rawSdkRequest,
|
||||
session = session,
|
||||
accountAddress = context.accountAddress,
|
||||
).map { lce ->
|
||||
lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) }
|
||||
}
|
||||
|
||||
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(wallet.walletId, network)
|
||||
?: run {
|
||||
emit(state.toResult(HandleMethodError.UnknownError("Failed to create wallet manager").left()))
|
||||
return
|
||||
}
|
||||
|
||||
val signer = signerProvider.createSigner(wallet)
|
||||
when (val result = walletManager.send(state.signModel, signer)) {
|
||||
is SdkResult.Success -> {
|
||||
val response = buildJsonResponse(result.data.hash)
|
||||
val wcRespondResult = respondService.respond(rawSdkRequest, response)
|
||||
emit(state.toResult(wcRespondResult))
|
||||
}
|
||||
is SdkResult.Failure -> {
|
||||
emit(state.toResult(HandleMethodError.UnknownError(result.error.customMessage).left()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun FlowCollector<TransactionData>.onMiddleAction(
|
||||
signModel: TransactionData,
|
||||
action: WcBitcoinTxAction,
|
||||
) {
|
||||
val uncompiled = signModel as? TransactionData.Uncompiled ?: return
|
||||
val newState = when (action) {
|
||||
is WcBitcoinTxAction.UpdateFee -> uncompiled.copy(fee = action.fee)
|
||||
}
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override suspend fun dAppFee(): Fee? = null
|
||||
|
||||
override fun updateFee(fee: Fee) {
|
||||
middleAction(WcBitcoinTxAction.UpdateFee(fee))
|
||||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
|
||||
val fee = dAppFee()
|
||||
val transactionData = createTransactionData(fee)
|
||||
emitAll(delegate.invoke(transactionData))
|
||||
}
|
||||
|
||||
private fun createTransactionData(fee: Fee?): TransactionData.Uncompiled {
|
||||
return TransactionData.Uncompiled(
|
||||
amount = transferAmount,
|
||||
fee = fee,
|
||||
sourceAddress = context.accountAddress,
|
||||
destinationAddress = method.recipientAddress,
|
||||
extras = BitcoinTransactionExtras(
|
||||
memo = method.memo,
|
||||
changeAddress = method.changeAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAmountFromSatoshis(satoshis: String): Amount {
|
||||
val btcValue = BigDecimal(satoshis).divide(SATOSHI_IN_BTC)
|
||||
return Amount(
|
||||
currencySymbol = network.currencySymbol,
|
||||
value = btcValue,
|
||||
decimals = BITCOIN_DECIMALS,
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildJsonResponse(txid: String): String = "{\"txid\":\"$txid\"}"
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(context: WcMethodUseCaseContext, method: WcBitcoinMethod.SendTransfer): WcBitcoinSendTransferUseCase
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val SATOSHI_IN_BTC = BigDecimal("100000000")
|
||||
const val BITCOIN_DECIMALS = 8
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import arrow.core.left
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.extensions.Result as SdkResult
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
|
||||
import com.tangem.data.walletconnect.sign.SignCollector
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.SimulationResult
|
||||
import com.domain.blockaid.models.transaction.ValidationResult
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.walletconnect.WcTransactionSignerProvider
|
||||
import com.tangem.domain.walletconnect.model.HandleMethodError
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* Use case for Bitcoin signMessage WalletConnect method.
|
||||
*
|
||||
* Signs an arbitrary message using Bitcoin message signing format (BIP-137 ECDSA).
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class SignMessageResponse(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "signature") val signature: String,
|
||||
@Json(name = "messageHash") val messageHash: String? = null,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class WcBitcoinSignMessageUseCase @AssistedInject constructor(
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted override val method: WcBitcoinMethod.SignMessage,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val signerProvider: WcTransactionSignerProvider,
|
||||
override val respondService: WcRespondService,
|
||||
override val analytics: AnalyticsEventHandler,
|
||||
@SdkMoshi private val moshi: Moshi,
|
||||
) : BaseWcSignUseCase<Nothing, WcMessageSignUseCase.SignModel>(),
|
||||
WcMessageSignUseCase {
|
||||
|
||||
override val wallet get() = context.session.wallet
|
||||
|
||||
// BlockAid doesn't support Bitcoin message signing
|
||||
override val securityStatus: LceFlow<Throwable, CheckTransactionResult> = flowOf(
|
||||
Lce.Content(
|
||||
CheckTransactionResult(
|
||||
validation = ValidationResult.FAILED_TO_VALIDATE,
|
||||
simulation = SimulationResult.FailedToSimulate,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun SignCollector<WcMessageSignUseCase.SignModel>.onSign(
|
||||
state: WcSignState<WcMessageSignUseCase.SignModel>,
|
||||
) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(wallet.walletId, network)
|
||||
?: run {
|
||||
emit(state.toResult(HandleMethodError.UnknownError("Failed to create wallet manager").left()))
|
||||
return
|
||||
}
|
||||
|
||||
val signer = signerProvider.createSigner(wallet)
|
||||
|
||||
// Use the address from method, or fallback to account if not specified
|
||||
val addressToSign = method.address ?: method.account
|
||||
|
||||
// Use MessageSigner to sign the message
|
||||
when (val result = walletManager.signMessage(
|
||||
message = method.message,
|
||||
address = addressToSign,
|
||||
protocol = method.protocol,
|
||||
signer = signer,
|
||||
)) {
|
||||
is SdkResult.Success -> {
|
||||
val response = buildJsonResponse(result.data)
|
||||
val wcRespondResult = respondService.respond(rawSdkRequest, response)
|
||||
emit(state.toResult(wcRespondResult))
|
||||
}
|
||||
is SdkResult.Failure -> {
|
||||
emit(state.toResult(HandleMethodError.UnknownError(result.error.customMessage).left()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<WcMessageSignUseCase.SignModel>> {
|
||||
return delegate.invoke(initModel = WcMessageSignUseCase.SignModel(method.message))
|
||||
}
|
||||
|
||||
private fun buildJsonResponse(data: com.tangem.blockchain.common.messagesigning.MessageSignatureResult): String {
|
||||
val response = SignMessageResponse(
|
||||
address = data.address,
|
||||
signature = data.signature,
|
||||
messageHash = data.messageHash,
|
||||
)
|
||||
return moshi.adapter(SignMessageResponse::class.java).toJson(response)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(context: WcMethodUseCaseContext, method: WcBitcoinMethod.SignMessage): WcBitcoinSignMessageUseCase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import arrow.core.left
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.blockchains.bitcoin.walletconnect.models.SignInput
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.extensions.Result as SdkResult
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
|
||||
import com.tangem.data.walletconnect.sign.SignCollector
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.walletconnect.WcTransactionSignerProvider
|
||||
import com.tangem.domain.walletconnect.model.HandleMethodError
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Use case for Bitcoin signPsbt WalletConnect method.
|
||||
*
|
||||
* Signs a Partially Signed Bitcoin Transaction (BIP-174 PSBT) with optional broadcast.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class SignPsbtResponse(
|
||||
@Json(name = "psbt") val psbt: String,
|
||||
@Json(name = "txid") val txid: String? = null,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class WcBitcoinSignPsbtUseCase @AssistedInject constructor(
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted override val method: WcBitcoinMethod.SignPsbt,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val signerProvider: WcTransactionSignerProvider,
|
||||
override val respondService: WcRespondService,
|
||||
override val analytics: AnalyticsEventHandler,
|
||||
blockAidDelegate: BlockAidVerificationDelegate,
|
||||
@SdkMoshi private val moshi: Moshi,
|
||||
) : BaseWcSignUseCase<Nothing, TransactionData>(),
|
||||
WcTransactionUseCase {
|
||||
|
||||
override val wallet get() = context.session.wallet
|
||||
|
||||
override val securityStatus: LceFlow<Throwable, BlockAidTransactionCheck.Result> =
|
||||
blockAidDelegate.getSecurityStatus(
|
||||
network = network,
|
||||
method = method,
|
||||
rawSdkRequest = rawSdkRequest,
|
||||
session = session,
|
||||
accountAddress = context.accountAddress,
|
||||
).map { lce ->
|
||||
lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) }
|
||||
}
|
||||
|
||||
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
|
||||
// Update wallet manager to refresh UTXO data before processing Bitcoin transaction
|
||||
walletManagersFacade.update(
|
||||
userWalletId = wallet.walletId,
|
||||
network = network,
|
||||
extraTokens = emptySet(),
|
||||
)
|
||||
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(wallet.walletId, network)
|
||||
?: run {
|
||||
emit(state.toResult(HandleMethodError.UnknownError("Failed to create wallet manager").left()))
|
||||
return
|
||||
}
|
||||
|
||||
val signer = signerProvider.createSigner(wallet)
|
||||
val signInputs = method.signInputs.map { input ->
|
||||
SignInput(
|
||||
address = input.address,
|
||||
index = input.index,
|
||||
sighashTypes = input.sighashTypes,
|
||||
)
|
||||
}
|
||||
val signedPsbtResult = walletManager.signPsbt(
|
||||
psbtBase64 = method.psbt,
|
||||
signInputs = signInputs,
|
||||
signer = signer,
|
||||
)
|
||||
|
||||
when (signedPsbtResult) {
|
||||
is SdkResult.Success -> {
|
||||
val signedPsbt = signedPsbtResult.data
|
||||
val txid = if (method.shouldBroadcast) {
|
||||
when (val broadcastResult = walletManager.broadcastPsbt(signedPsbt)) {
|
||||
is SdkResult.Success -> broadcastResult.data
|
||||
is SdkResult.Failure -> {
|
||||
val error = HandleMethodError.UnknownError(broadcastResult.error.customMessage).left()
|
||||
emit(state.toResult(error))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val response = buildJsonResponse(signedPsbt, txid)
|
||||
val wcRespondResult = respondService.respond(rawSdkRequest, response)
|
||||
emit(state.toResult(wcRespondResult))
|
||||
}
|
||||
is SdkResult.Failure -> {
|
||||
emit(state.toResult(HandleMethodError.UnknownError(signedPsbtResult.error.customMessage).left()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData>> {
|
||||
val transactionData = TransactionData.Compiled(
|
||||
value = TransactionData.Compiled.Data.RawString(method.psbt),
|
||||
)
|
||||
return delegate.invoke(transactionData)
|
||||
}
|
||||
|
||||
private fun buildJsonResponse(signedPsbt: String, txid: String?): String {
|
||||
val response = SignPsbtResponse(
|
||||
psbt = signedPsbt,
|
||||
txid = txid,
|
||||
)
|
||||
return moshi.adapter(SignPsbtResponse::class.java).toJson(response)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(context: WcMethodUseCaseContext, method: WcBitcoinMethod.SignPsbt): WcBitcoinSignPsbtUseCase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.data.walletconnect.network.bitcoin
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
|
||||
sealed interface WcBitcoinTxAction {
|
||||
|
||||
data class UpdateFee(val fee: Fee) : WcBitcoinTxAction
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.data.walletconnect.network.solana
|
|||
|
||||
import com.tangem.blockchain.extensions.decodeBase58
|
||||
import com.tangem.blockchain.extensions.encodeBase64NoWrap
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult
|
|||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.getDappOriginUrl
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import arrow.core.left
|
|||
import arrow.core.right
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.getDappOriginUrl
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.data.walletconnect.request
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.data.walletconnect.BuildConfig
|
||||
import com.tangem.data.walletconnect.respond.DefaultWcRespondService
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionRequestConverter
|
||||
import com.tangem.data.walletconnect.utils.getDappOriginUrl
|
||||
|
|
@ -43,6 +44,7 @@ internal class DefaultWcRequestService(
|
|||
respondService.rejectRequestNonBlock(sr)
|
||||
if (name.raw.startsWith("wallet_")) return
|
||||
}
|
||||
|
||||
_wcRequest.trySend(name to sr)
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +64,11 @@ internal class DefaultWcRequestService(
|
|||
}
|
||||
|
||||
private fun saveRequest(request: WcSdkSessionRequest) {
|
||||
// Skip caching in debug builds since filtering is disabled
|
||||
if (BuildConfig.DEBUG) {
|
||||
return
|
||||
}
|
||||
|
||||
val hash = respondService.sessionRequestHash(request)
|
||||
val now = DateTime.now().millis
|
||||
respondService.cachedRequest.update { it + (now to hash) }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.reown.walletkit.client.Wallet
|
|||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import arrow.core.right
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ internal object BlockAidChainNameConverter : Converter<Network, String?> {
|
|||
|
||||
Blockchain.Solana -> "mainnet"
|
||||
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin-testnet"
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.blockaid.BlockAidVerifier
|
|||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.model.WcMethod
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
|
|
@ -26,10 +27,8 @@ internal class BlockAidVerificationDelegate @Inject constructor(
|
|||
session: WcSession,
|
||||
accountAddress: String?,
|
||||
): LceFlow<Throwable, CheckTransactionResult> = flow {
|
||||
val failedResult = CheckTransactionResult(
|
||||
validation = ValidationResult.FAILED_TO_VALIDATE,
|
||||
simulation = SimulationResult.FailedToSimulate,
|
||||
)
|
||||
val failedResult = createFailedResult()
|
||||
|
||||
if (accountAddress.isNullOrEmpty()) {
|
||||
emit(Lce.Content(failedResult))
|
||||
return@flow
|
||||
|
|
@ -43,18 +42,22 @@ internal class BlockAidVerificationDelegate @Inject constructor(
|
|||
val methodName = when (method) {
|
||||
is WcEthMethod -> rawSdkRequest.request.method
|
||||
is WcSolanaMethod -> method.trimmedPrefixMethodName
|
||||
is WcBitcoinMethod -> rawSdkRequest.request.method
|
||||
is WcMethod.Unsupported -> {
|
||||
emit(Lce.Content(failedResult))
|
||||
return@flow
|
||||
}
|
||||
}
|
||||
|
||||
val params = when (method) {
|
||||
is WcEthMethod -> TransactionParams.Evm(rawSdkRequest.request.params)
|
||||
is WcSolanaMethod.SignAllTransaction -> TransactionParams.Solana(method.transaction)
|
||||
is WcSolanaMethod.SignTransaction -> TransactionParams.Solana(listOf(method.transaction))
|
||||
is WcSolanaMethod.SignMessage -> {
|
||||
// BlockAid doesn't support solana_signMessage
|
||||
emit(Lce.Content(failedResult))
|
||||
is WcSolanaMethod.SignMessage,
|
||||
is WcBitcoinMethod,
|
||||
-> {
|
||||
// BlockAid doesn't support Solana message signing and Bitcoin methods
|
||||
emit(Lce.Content(createSafeResult()))
|
||||
return@flow
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -81,4 +84,14 @@ internal class BlockAidVerificationDelegate @Inject constructor(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFailedResult() = CheckTransactionResult(
|
||||
validation = ValidationResult.FAILED_TO_VALIDATE,
|
||||
simulation = SimulationResult.FailedToSimulate,
|
||||
)
|
||||
|
||||
private fun createSafeResult() = CheckTransactionResult(
|
||||
validation = ValidationResult.SAFE,
|
||||
simulation = SimulationResult.FailedToSimulate,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ package com.tangem.data.walletconnect.utils
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
|
||||
const val WC_TAG = "Wallet Connect"
|
||||
|
||||
internal interface WcSdkObserver : WalletKit.WalletDelegate {
|
||||
|
||||
override val onSessionAuthenticate: ((Wallet.Model.SessionAuthenticate, Wallet.Model.VerifyContext) -> Unit)?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue