Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-10 11:46:32 +03:00
parent af2b62dbe3
commit edb42c1ee4
11 changed files with 173 additions and 17 deletions

View file

@ -131,6 +131,10 @@ class DialogManager : StoreSubscriber<GlobalState> {
preparedData = state.dialog.data,
context = context,
)
is WalletConnectDialog.SignTransactionsDialog -> SignTransactionsDialog.create(
preparedData = state.dialog.data,
context = context,
)
is WalletConnectDialog.PairConnectErrorDialog -> SimpleAlertDialog.create(
titleRes = R.string.wallet_connect_title,
message = state.dialog.error.message,

View file

@ -36,6 +36,8 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionReque
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import org.json.JSONArray
import org.json.JSONObject
import timber.log.Timber
import java.math.BigDecimal
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
@ -427,6 +429,9 @@ class WalletConnectSdkHelper {
}
}
/**
* Returns result of signing prepared to send in WC request
*/
suspend fun signTransaction(
hashToSign: ByteArray,
networkId: String,
@ -465,8 +470,67 @@ class WalletConnectSdkHelper {
}
}
/**
* Returns result of signing prepared to send in WC request
*/
suspend fun signTransactions(
hashesToSign: List<ByteArray>,
networkId: String,
type: TransactionType,
derivationPath: String?,
cardId: String?,
): String? {
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
val sdk = store.inject(DaggerGraphState::cardSdkConfigRepository).sdk
val signer = store.inject(DaggerGraphState::transactionSignerFactory).createTransactionSigner(
cardId = cardId,
sdk = sdk,
twinKey = null,
)
return when (val signingResult = signer.sign(hashesToSign, wallet.publicKey)) {
is CompletionResult.Failure -> {
Timber.e(signingResult.error.customMessage)
null
}
is CompletionResult.Success -> {
when (type) {
TransactionType.SOLANA_TX -> {
val result = signingResult.data.mapIndexed { index, bytes ->
byteArrayOf(1) + bytes + hashesToSign[index]
}
getSolanaResultTxHashesString(result)
}
}
}
else -> {
null
}
}
}
private fun getSolanaResultString(signedHash: ByteArray) = "{ signature: \"${signedHash.encodeBase58()}\" }"
/**
* 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()
}
private fun getBnbResultString(publicKey: String, signature: String) =
"{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}"

View file

@ -315,7 +315,7 @@ class WalletConnectInteractor(
if (currentRequest == null || request.topic != currentRequest.topic) return
val networkId = blockchainHelper.chainIdToNetworkIdOrNull(currentRequest.chainId.orEmpty()) ?: return
val signedHash = when (request) {
val signingResultData = when (request) {
is WcPreparedRequest.BnbTransaction -> sdkHelper.signBnbTransaction(
data = request.preparedRequestData.data.data,
networkId = networkId,
@ -333,13 +333,20 @@ class WalletConnectInteractor(
derivationPath = request.derivationPath,
cardId = cardId,
)
is WcPreparedRequest.SignTransaction -> sdkHelper.signTransaction(
is WcPreparedRequest.SolanaSignTransaction -> sdkHelper.signTransaction(
hashToSign = request.preparedRequestData.hashToSign,
networkId = networkId,
type = request.preparedRequestData.type,
derivationPath = request.derivationPath,
cardId = cardId,
)
is WcPreparedRequest.SolanaSignMultipleTransactions -> sdkHelper.signTransactions(
hashesToSign = request.preparedRequestData.hashesToSign,
networkId = networkId,
type = request.preparedRequestData.type,
derivationPath = request.derivationPath,
cardId = cardId,
)
}
val requestData = RequestData(
@ -349,12 +356,12 @@ class WalletConnectInteractor(
method = currentRequest.method,
)
if (signedHash == null) {
if (signingResultData == null) {
walletConnectRepository.rejectRequest(requestData, WalletConnectError.SigningError)
} else {
walletConnectRepository.sendRequest(
requestData = requestData,
result = signedHash,
result = signingResultData,
)
}
}

View file

@ -28,6 +28,7 @@ internal enum class WcJrpcMethods(val code: String) {
SOLANA_SIGN_TX("solana_signTransaction"),
SOLANA_SIGN_MESSAGE("solana_signMessage"),
SOLANA_SIGN_ALL_TX("solana_signAllTransactions"),
;
companion object {
@ -130,6 +131,12 @@ data class WcEthereumTransaction(
val data: String,
) : WcRequestData
@JsonClass(generateAdapter = true)
data class SolanaTransactionsRequest(
@Json(name = "transactions")
val transactions: List<String>,
) : WcRequestData
interface WcRequestData
data class WcCustomRequestData(val data: String) : WcRequestData
@ -146,6 +153,7 @@ sealed class WcRequest(open val data: WcRequestData) {
data class AddChain(override val data: WcAddChain) : WcRequest(data)
data class CustomRequest(override val data: WcCustomRequestData) : WcRequest(data)
data class SolanaSignRequest(override val data: SolanaTransactionRequest) : WcRequest(data)
data class SolanaSignTransactions(override val data: SolanaTransactionsRequest) : WcRequest(data)
}
@Singleton
@ -242,6 +250,11 @@ internal class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private
WcRequest.EthSign(data = data)
}
WcJrpcMethods.SOLANA_SIGN_ALL_TX -> {
val transactionsToSign = moshi.adapter(SolanaTransactionsRequest::class.java)
.fromJsonOrNull(params) ?: return customRequest
WcRequest.SolanaSignTransactions(transactionsToSign)
}
}
}

View file

@ -31,19 +31,37 @@ sealed class WcPreparedRequest(
derivationPath: String?,
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
class SignTransaction(
override val preparedRequestData: WcGenericTransactionData,
class SolanaSignTransaction(
override val preparedRequestData: GenericTransactionData.SingleHash,
topic: String,
requestId: Long,
derivationPath: String?,
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
class SolanaSignMultipleTransactions(
override val preparedRequestData: GenericTransactionData.MultipleHashes,
topic: String,
requestId: Long,
derivationPath: String?,
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
}
data class WcGenericTransactionData(
val hashToSign: ByteArray,
val dAppName: String,
val type: TransactionType,
)
sealed interface GenericTransactionData {
val dAppName: String
val type: TransactionType
data class SingleHash(
val hashToSign: ByteArray,
override val dAppName: String,
override val type: TransactionType,
) : GenericTransactionData
data class MultipleHashes(
val hashesToSign: List<ByteArray>,
override val dAppName: String,
override val type: TransactionType,
) : GenericTransactionData
}
enum class TransactionType {
SOLANA_TX,

View file

@ -117,8 +117,8 @@ internal class WcSessionRequestConverter(
is WcRequest.SolanaSignRequest -> {
val transaction = request.data.transaction
WcPreparedRequest.SignTransaction(
preparedRequestData = WcGenericTransactionData(
WcPreparedRequest.SolanaSignTransaction(
preparedRequestData = GenericTransactionData.SingleHash(
hashToSign = transaction.prepareSolanaTransaction(),
dAppName = sessionRequest.metaName,
type = TransactionType.SOLANA_TX,
@ -128,6 +128,19 @@ internal class WcSessionRequestConverter(
derivationPath = derivationPath,
)
}
is WcRequest.SolanaSignTransactions -> {
val transactions = request.data.transactions
WcPreparedRequest.SolanaSignMultipleTransactions(
preparedRequestData = GenericTransactionData.MultipleHashes(
hashesToSign = transactions.map { it.prepareSolanaTransaction() }, //
dAppName = sessionRequest.metaName,
type = TransactionType.SOLANA_TX,
),
topic = sessionRequest.topic,
requestId = sessionRequest.id,
derivationPath = derivationPath,
)
}
else -> throw WalletConnectError.UnsupportedMethod
}
}

View file

@ -171,7 +171,10 @@ class WalletConnectMiddleware {
is WcPreparedRequest.BnbTransaction -> WalletConnectDialog.BnbTransactionDialog(request)
is WcPreparedRequest.EthTransaction -> WalletConnectDialog.RequestTransaction(request)
is WcPreparedRequest.EthSign -> WalletConnectDialog.PersonalSign(request)
is WcPreparedRequest.SignTransaction -> WalletConnectDialog.SignTransactionDialog(request)
is WcPreparedRequest.SolanaSignTransaction ->
WalletConnectDialog.SignTransactionDialog(request)
is WcPreparedRequest.SolanaSignMultipleTransactions ->
WalletConnectDialog.SignTransactionsDialog(request)
}
store.dispatch(GlobalAction.ShowDialog(dialog))
}

View file

@ -116,7 +116,11 @@ sealed class WalletConnectDialog : StateDialog {
) : WalletConnectDialog()
data class SignTransactionDialog(
val data: WcPreparedRequest.SignTransaction,
val data: WcPreparedRequest.SolanaSignTransaction,
) : WalletConnectDialog()
data class SignTransactionsDialog(
val data: WcPreparedRequest.SolanaSignMultipleTransactions,
) : WalletConnectDialog()
data class PairConnectErrorDialog(val error: Throwable) : WalletConnectDialog()

View file

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

View file

@ -0,0 +1,30 @@
package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.store
import com.tangem.wallet.R
internal object SignTransactionsDialog {
fun create(preparedData: WcPreparedRequest.SolanaSignMultipleTransactions, context: Context): AlertDialog {
val signMessage = context.getString(R.string.wallet_connect_alert_sign_message, "")
val message = "${preparedData.preparedRequestData.dAppName}\n$signMessage"
return AlertDialog.Builder(context).apply {
setTitle(context.getString(R.string.wallet_connect_title))
setMessage(message)
setPositiveButton(context.getText(R.string.common_sign)) { _, _ ->
store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData))
}
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId))
}
setOnDismissListener {
store.dispatch(GlobalAction.HideDialog)
}
}.create()
}
}