Updated on 2026-08-14
This commit is contained in:
parent
736ef3e45e
commit
e87d946e40
10 changed files with 332 additions and 19 deletions
|
|
@ -277,20 +277,39 @@ internal class DefaultTransactionRepository(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<ByteArray> = withContext(coroutineDispatcherProvider.io) {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
val preparer = walletManager as? TransactionPreparer ?: kotlin.run {
|
||||
Timber.e("${walletManager?.wallet?.blockchain} does not support TransactionBuilder")
|
||||
error("Wallet manager does not support TransactionPreparer")
|
||||
}
|
||||
val preparer = getPreparer(network, userWalletId)
|
||||
|
||||
when (val prepareForSend = preparer.prepareForSend(transactionData, signer)) {
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> Result.failure(prepareForSend.error)
|
||||
is com.tangem.blockchain.extensions.Result.Success -> Result.success(prepareForSend.data)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun prepareForSendMultiple(
|
||||
transactionData: List<TransactionData>,
|
||||
signer: TransactionSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<List<ByteArray>> = withContext(coroutineDispatcherProvider.io) {
|
||||
val preparer = getPreparer(network, userWalletId)
|
||||
|
||||
when (val prepareForSend = preparer.prepareForSendMultiple(transactionData, signer)) {
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> Result.failure(prepareForSend.error)
|
||||
is com.tangem.blockchain.extensions.Result.Success -> Result.success(prepareForSend.data)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
val preparer = walletManager as? TransactionPreparer ?: run {
|
||||
Timber.e("${walletManager?.wallet?.blockchain} does not support TransactionBuilder")
|
||||
error("Wallet manager does not support TransactionPreparer")
|
||||
}
|
||||
return preparer
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +144,15 @@ internal object WalletConnectDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcSolanaNetwork(excludedBlockchains: ExcludedBlockchains): WcSolanaNetwork = WcSolanaNetwork(
|
||||
fun wcSolanaNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcSolanaNetwork.Factories,
|
||||
): WcSolanaNetwork = WcSolanaNetwork(
|
||||
moshi = moshi,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
|
||||
|
|
@ -175,6 +183,7 @@ internal object WalletConnectDataModule {
|
|||
fun diHelperBox(ethNetwork: WcEthNetwork, solanaNetwork: WcSolanaNetwork) = DiHelperBox(
|
||||
handlers = setOf(
|
||||
ethNetwork,
|
||||
solanaNetwork,
|
||||
),
|
||||
converters = setOf(
|
||||
ethNetwork,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.data.walletconnect.network.solana
|
||||
|
||||
import arrow.core.left
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.extensions.encodeBase64
|
||||
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.domain.transaction.usecase.PrepareForSendUseCase
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.solana.WcSolanaSignAllTransactionUseCase
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
internal class DefaultWcSolanaSignAllTransactionUseCase @AssistedInject constructor(
|
||||
override val respondService: WcRespondService,
|
||||
private val prepareForSend: PrepareForSendUseCase,
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted private val method: WcSolanaMethod.SignAllTransaction,
|
||||
) : BaseWcSignUseCase<Nothing, List<TransactionData.Compiled>>(),
|
||||
WcSolanaSignAllTransactionUseCase {
|
||||
|
||||
override suspend fun SignCollector<List<TransactionData.Compiled>>.onSign(
|
||||
state: WcSignState<List<TransactionData.Compiled>>,
|
||||
) {
|
||||
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
|
||||
.onLeft { error ->
|
||||
emit(state.toResult(error.left()))
|
||||
}
|
||||
.getOrNull()
|
||||
?: return
|
||||
val respond = getSolanaResultTxHashesString(hash)
|
||||
val respondResult = respondService.respond(rawSdkRequest, respond)
|
||||
emit(state.toResult(respondResult))
|
||||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<List<TransactionData.Compiled>>> {
|
||||
val transactionData = method.transaction
|
||||
.map { it.decodeBase64()?.toByteArray() ?: ByteArray(0) }
|
||||
.map { TransactionData.Compiled(value = TransactionData.Compiled.Data.Bytes(it)) }
|
||||
|
||||
return delegate.invoke(transactionData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build json object
|
||||
* {
|
||||
* "transactions": [
|
||||
* "signed_tx_hash"
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
private fun getSolanaResultTxHashesString(signedHashes: List<ByteArray>): String {
|
||||
val result = JSONObject()
|
||||
val transactions = JSONArray()
|
||||
signedHashes.forEach {
|
||||
transactions.put(it.encodeBase64())
|
||||
}
|
||||
result.put("transactions", transactions)
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
context: WcMethodUseCaseContext,
|
||||
method: WcSolanaMethod.SignAllTransaction,
|
||||
): DefaultWcSolanaSignAllTransactionUseCase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.data.walletconnect.network.solana
|
||||
|
||||
import arrow.core.left
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.extensions.encodeBase58
|
||||
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.domain.transaction.usecase.PrepareForSendUseCase
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.solana.WcSolanaSignTransactionUseCase
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
|
||||
internal class DefaultWcSolanaSignTransactionUseCase @AssistedInject constructor(
|
||||
override val respondService: WcRespondService,
|
||||
private val prepareForSend: PrepareForSendUseCase,
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted private val method: WcSolanaMethod.SignTransaction,
|
||||
) : BaseWcSignUseCase<Nothing, TransactionData.Compiled>(),
|
||||
WcSolanaSignTransactionUseCase {
|
||||
|
||||
override suspend fun SignCollector<TransactionData.Compiled>.onSign(state: WcSignState<TransactionData.Compiled>) {
|
||||
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
|
||||
.onLeft { error ->
|
||||
emit(state.toResult(error.left()))
|
||||
}
|
||||
.getOrNull()
|
||||
?: return
|
||||
val respond = "{ signature: \"${hash.encodeBase58()}\" }"
|
||||
val respondResult = respondService.respond(rawSdkRequest, respond)
|
||||
emit(state.toResult(respondResult))
|
||||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData.Compiled>> {
|
||||
val data = method.transaction.decodeBase64()?.toByteArray() ?: ByteArray(0)
|
||||
|
||||
val transactionData = TransactionData.Compiled(
|
||||
value = TransactionData.Compiled.Data.Bytes(data),
|
||||
)
|
||||
return delegate.invoke(transactionData)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
context: WcMethodUseCaseContext,
|
||||
method: WcSolanaMethod.SignTransaction,
|
||||
): DefaultWcSolanaSignTransactionUseCase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.data.walletconnect.network.solana
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcSolanaSignMessageRequest(
|
||||
@Json(name = "pubkey")
|
||||
val publicKey: String,
|
||||
|
||||
@Json(name = "message")
|
||||
val message: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class WcSolanaSignTransactionRequest(
|
||||
@Json(name = "transaction")
|
||||
val transaction: String,
|
||||
)
|
||||
|
|
@ -1,19 +1,45 @@
|
|||
package com.tangem.data.walletconnect.network.solana
|
||||
|
||||
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.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import jakarta.inject.Inject
|
||||
|
||||
internal class WcSolanaNetwork constructor(
|
||||
private val moshi: Moshi,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : WcNamespaceConverter {
|
||||
) : WcNamespaceConverter, WcRequestToUseCaseConverter {
|
||||
|
||||
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
|
||||
|
||||
override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? {
|
||||
val methodKey = request.request.method
|
||||
val name = Name.entries.find { it.raw == methodKey } ?: return null
|
||||
val method: WcSolanaMethod = name.toMethod(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val network = toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val context = WcMethodUseCaseContext(session = session, rawSdkRequest = request, network = network)
|
||||
return when (method) {
|
||||
is WcSolanaMethod.SignMessage -> TODO()
|
||||
is WcSolanaMethod.SignTransaction -> factories.signTransaction.create(context, method)
|
||||
is WcSolanaMethod.SignAllTransaction -> factories.signAllTransaction.create(context, method)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toBlockchain(chainId: CAIP2): Blockchain? {
|
||||
if (chainId.namespace != namespaceKey.key) return null
|
||||
return when (chainId.reference) {
|
||||
|
|
@ -41,6 +67,32 @@ internal class WcSolanaNetwork constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Name.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? {
|
||||
val rawParams = request.request.params
|
||||
return when (this) {
|
||||
Name.SignMessage -> moshi.fromJson<WcSolanaSignMessageRequest>(rawParams)?.let { request ->
|
||||
WcSolanaMethod.SignMessage(pubKey = request.publicKey, message = request.message)
|
||||
}
|
||||
Name.SignTransaction -> moshi.fromJson<WcSolanaSignTransactionRequest>(rawParams)?.let { request ->
|
||||
WcSolanaMethod.SignTransaction(request.transaction)
|
||||
}
|
||||
Name.SendAllTransaction -> moshi.fromJson<List<String>>(rawParams)?.let { list ->
|
||||
WcSolanaMethod.SignAllTransaction(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Name(val raw: String) {
|
||||
SignMessage("solana_signMessage"),
|
||||
SignTransaction("solana_signTransaction"),
|
||||
SendAllTransaction("solana_signAllTransactions"),
|
||||
}
|
||||
|
||||
internal class Factories @Inject constructor(
|
||||
val signTransaction: DefaultWcSolanaSignTransactionUseCase.Factory,
|
||||
val signAllTransaction: DefaultWcSolanaSignAllTransactionUseCase.Factory,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val MAINNET_CHAIN_ID = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
||||
private const val TESTNET_CHAIN_ID = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z"
|
||||
|
|
|
|||
|
|
@ -89,4 +89,11 @@ interface TransactionRepository {
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<ByteArray>
|
||||
|
||||
suspend fun prepareForSendMultiple(
|
||||
transactionData: List<TransactionData>,
|
||||
signer: TransactionSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<List<ByteArray>>
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.domain.card.models.TwinKey
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
|
|
@ -20,13 +21,7 @@ class PrepareForSendUseCase(
|
|||
userWallet: UserWallet,
|
||||
network: Network,
|
||||
): Either<Throwable, ByteArray> {
|
||||
val card = userWallet.scanResponse.card
|
||||
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
|
||||
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(
|
||||
cardId = card.cardId.takeIf { isCardNotBackedUp },
|
||||
twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse),
|
||||
)
|
||||
val signer = createSigner(userWallet)
|
||||
return transactionRepository.prepareForSend(
|
||||
transactionData = transactionData,
|
||||
userWalletId = userWallet.walletId,
|
||||
|
|
@ -35,4 +30,29 @@ class PrepareForSendUseCase(
|
|||
)
|
||||
.fold(onSuccess = { it.right() }, onFailure = { it.left() })
|
||||
}
|
||||
suspend operator fun invoke(
|
||||
transactionData: List<TransactionData>,
|
||||
userWallet: UserWallet,
|
||||
network: Network,
|
||||
): Either<Throwable, List<ByteArray>> {
|
||||
val signer = createSigner(userWallet)
|
||||
return transactionRepository.prepareForSendMultiple(
|
||||
transactionData = transactionData,
|
||||
userWalletId = userWallet.walletId,
|
||||
network = network,
|
||||
signer = signer,
|
||||
)
|
||||
.fold(onSuccess = { it.right() }, onFailure = { it.left() })
|
||||
}
|
||||
|
||||
private fun createSigner(userWallet: UserWallet): TransactionSigner {
|
||||
val card = userWallet.scanResponse.card
|
||||
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
|
||||
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(
|
||||
cardId = card.cardId.takeIf { isCardNotBackedUp },
|
||||
twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse),
|
||||
)
|
||||
return signer
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
sealed interface WcSolanaMethod : WcMethod {
|
||||
|
||||
data class SignMessage(
|
||||
val pubKey: String,
|
||||
val message: String,
|
||||
) : WcSolanaMethod
|
||||
|
||||
data class SignTransaction(
|
||||
val transaction: String,
|
||||
) : WcSolanaMethod
|
||||
|
||||
data class SignAllTransaction(
|
||||
val transaction: List<String>,
|
||||
) : WcSolanaMethod
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.domain.walletconnect.usecase.solana
|
||||
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WcSolanaSignTransactionUseCase :
|
||||
WcSignUseCase,
|
||||
WcSignUseCase.SimpleRun<TransactionData.Compiled> {
|
||||
|
||||
override val network: Network
|
||||
override val rawSdkRequest: WcSdkSessionRequest
|
||||
override val session: WcSession
|
||||
|
||||
override fun sign()
|
||||
override fun cancel()
|
||||
override fun invoke(): Flow<WcSignState<TransactionData.Compiled>>
|
||||
}
|
||||
|
||||
interface WcSolanaSignAllTransactionUseCase :
|
||||
WcSignUseCase,
|
||||
WcSignUseCase.SimpleRun<List<TransactionData.Compiled>> {
|
||||
|
||||
override val network: Network
|
||||
override val rawSdkRequest: WcSdkSessionRequest
|
||||
override val session: WcSession
|
||||
|
||||
override fun sign()
|
||||
override fun cancel()
|
||||
override fun invoke(): Flow<WcSignState<List<TransactionData.Compiled>>>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue