Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-28 13:03:33 +04:00
parent e727b40042
commit 3556c3765e
7 changed files with 331 additions and 86 deletions

View file

@ -70,4 +70,8 @@ dependencies {
implementation(deps.timber)
implementation(tangemDeps.card.core)
implementation(tangemDeps.blockchain)
/** Test libraries */
implementation(deps.test.junit)
implementation(deps.test.truth)
}

View file

@ -0,0 +1,144 @@
package com.tangem.features.walletconnect.transaction.converter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
import org.json.JSONArray
import org.json.JSONObject
import javax.inject.Inject
/**
* Parses various transaction parameters from various JSON schemas to UI format for transaction request BS
*/
internal class TransactionParamsConverter @Inject constructor() : Converter<String, List<WcTransactionRequestBlockUM>> {
@Suppress("CyclomaticComplexMethod", "NestedBlockDepth")
override fun convert(value: String): List<WcTransactionRequestBlockUM> {
val result = mutableListOf<WcTransactionRequestBlockUM>()
fun loop(node: Any?) {
when (node) {
is JSONObject -> {
for (key in node.keys()) {
val objectValue = node.get(key)
when (key) {
PRIMARY_TYPE, DATA, FROM, TO, VALUE -> if (objectValue is String) {
result.addSingleStringValueBlock(key, objectValue)
}
DOMAIN -> if (objectValue is JSONObject) {
result.addMultipleObjectsBlock(key, objectValue)
}
MESSAGE -> if (objectValue is JSONObject) {
objectValue.optString(CONTENTS).let { contents ->
result.addNestedStringValueBlock(key, nestedKey = CONTENTS, contents)
}
objectValue.optJSONObject(FROM)?.let { from ->
result.addMultipleObjectsBlock(FROM, from)
}
objectValue.optJSONObject(TO)?.let { to ->
result.addMultipleObjectsBlock(TO, to)
}
}
else -> loop(objectValue)
}
}
}
is JSONArray -> {
for (i in 0 until node.length()) {
loop(node.get(i))
}
}
}
}
loop(JSONArray(value))
return result
}
private fun MutableList<WcTransactionRequestBlockUM>.addSingleStringValueBlock(
key: String,
stringValue: String,
): MutableList<WcTransactionRequestBlockUM> {
add(
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(
title = TextReference.Str(key.capitalize()),
description = stringValue,
),
).toImmutableList(),
),
)
return this
}
private fun MutableList<WcTransactionRequestBlockUM>.addNestedStringValueBlock(
key: String,
nestedKey: String,
stringValue: String,
): MutableList<WcTransactionRequestBlockUM> {
add(
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(TextReference.Str(key.capitalize())),
WcTransactionRequestInfoItemUM(
title = TextReference.Str(nestedKey.capitalize()),
description = stringValue,
),
).toImmutableList(),
),
)
return this
}
private fun MutableList<WcTransactionRequestBlockUM>.addMultipleObjectsBlock(
key: String,
objectValue: JSONObject,
): MutableList<WcTransactionRequestBlockUM> {
add(
WcTransactionRequestBlockUM(
info = buildList {
add(WcTransactionRequestInfoItemUM(TextReference.Str(key.capitalize())))
addAll(extractObjects(objectValue))
}.toImmutableList(),
),
)
return this
}
private fun String.capitalize(): String = replaceFirstChar { it.uppercaseChar() }
private fun extractObjects(obj: JSONObject): List<WcTransactionRequestInfoItemUM> {
val items = mutableListOf<WcTransactionRequestInfoItemUM>()
for (key in obj.keys()) {
val objectValue = obj.get(key)
if (objectValue is String || objectValue is Number || objectValue is Boolean) {
items.add(
WcTransactionRequestInfoItemUM(
title = TextReference.Str(key),
description = objectValue.toString(),
),
)
}
}
return items
}
private companion object {
const val PRIMARY_TYPE = "primaryType"
const val DATA = "data"
const val FROM = "from"
const val TO = "to"
const val VALUE = "value"
const val DOMAIN = "domain"
const val MESSAGE = "message"
const val CONTENTS = "contents"
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.features.walletconnect.transaction.converter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.usecase.method.*
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.entity.common.*
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM
import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM
import com.tangem.utils.converter.Converter
@ -45,33 +45,11 @@ internal class WcSendTransactionUMConverter @Inject constructor(
feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading,
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = buildList {
add(
addAll(
requestBlockUMConverter.convert(
WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest),
),
)
(value.useCase.method as? WcEthMethod.SendTransaction)?.transaction?.let { transaction ->
add(
WcTransactionRequestBlockUM(
info = buildList {
transaction.to?.let {
add(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_transaction_info_to_title),
description = it,
),
)
}
add(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.send_from_wallet_android),
description = transaction.from,
),
)
}.toImmutableList(),
),
)
}
}.toImmutableList(),
onCopy = value.actions.onCopy,
),

View file

@ -8,7 +8,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
internal class WcSignTransactionUMConverter @Inject constructor(
@ -33,11 +33,9 @@ internal class WcSignTransactionUMConverter @Inject constructor(
address = WcAddressConverter.convert(value.useCase.derivationState),
),
transactionRequestInfo = WcTransactionRequestInfoUM(
persistentListOf(
requestBlockUMConverter.convert(
WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest, value.signModel),
),
),
requestBlockUMConverter.convert(
WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest, value.signModel),
).toImmutableList(),
onCopy = value.actions.onCopy,
),
)

View file

@ -1,19 +1,13 @@
package com.tangem.features.walletconnect.transaction.converter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
@ -40,30 +34,11 @@ internal class WcSignTypedDataUMConverter @Inject constructor(
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = buildList {
add(
addAll(
requestBlockUMConverter.convert(
WcTransactionRequestBlockUMConverter.Input(value.useCase.rawSdkRequest, value.signModel),
),
)
(value.useCase.method as? WcEthMethod.SignTypedData)?.params?.message?.to?.let { to ->
add(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_transaction_info_to_title),
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.settings_wallet_name_title),
description = to.name,
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_common_wallet),
description = to.wallet,
),
),
),
)
}
}.toImmutableList(),
onCopy = value.actions.onCopy,
),

View file

@ -7,40 +7,44 @@ import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
import org.json.JSONArray
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
import javax.inject.Inject
private const val DATA_FIELD = "data"
internal class WcTransactionRequestBlockUMConverter @Inject constructor(
private val transactionParamsConverter: TransactionParamsConverter,
) : Converter<WcTransactionRequestBlockUMConverter.Input, List<WcTransactionRequestBlockUM>> {
internal class WcTransactionRequestBlockUMConverter @Inject constructor() :
Converter<WcTransactionRequestBlockUMConverter.Input, WcTransactionRequestBlockUM> {
override fun convert(value: Input): WcTransactionRequestBlockUM {
override fun convert(value: Input): List<WcTransactionRequestBlockUM> {
val params = value.rawSdkRequest.request.params
return WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = value.rawSdkRequest.request.method,
val humanMsg = value.signModel?.humanMsg
return buildList {
add(
WcTransactionRequestBlockUM(
buildList {
add(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = value.rawSdkRequest.request.method,
),
)
if (!humanMsg.isNullOrEmpty()) {
add(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = humanMsg,
),
)
}
}.toImmutableList(),
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = value.signModel?.humanMsg ?: params.tryExtractData() ?: params,
),
),
)
}
private fun String.tryExtractData() = try {
val array = JSONArray(this)
if (array.length() > 0 && array.getJSONObject(0).has(DATA_FIELD)) {
array.getJSONObject(0).getString(DATA_FIELD)
} else {
null
)
try {
addAll(transactionParamsConverter.convert(params))
} catch (exception: Exception) {
Timber.e(exception, "Error while parsing transaction params - %s", params)
}
}
} catch (ignore: Exception) {
null
}
data class Input(

View file

@ -0,0 +1,142 @@
package com.tangem.features.walletconnect.transaction.converter
import com.google.common.truth.Truth
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import kotlinx.collections.immutable.toImmutableList
import org.junit.Test
class TransactionParamsConverterTest {
private val converter = TransactionParamsConverter()
@Test
fun `GIVEN sign typed data transaction params WHEN convert THEN return correct data for UI`() {
val value = """
[
"0xcd5F26C95e84279d0ce8E6dd9030d0b2171b6101",
{
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xcccccccccccccccccccccccccccccccccccccccc"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
},
"primaryType": "Mail"
}
]
""".trimIndent()
val expected = listOf(
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(
title = TextReference.Str("PrimaryType"),
description = "Mail",
),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(TextReference.Str("Domain")),
WcTransactionRequestInfoItemUM(TextReference.Str("chainId"), "1"),
WcTransactionRequestInfoItemUM(TextReference.Str("name"), "Ether Mail"),
WcTransactionRequestInfoItemUM(TextReference.Str("version"), "1"),
WcTransactionRequestInfoItemUM(
TextReference.Str("verifyingContract"),
"0xcccccccccccccccccccccccccccccccccccccccc",
),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(TextReference.Str("Message")),
WcTransactionRequestInfoItemUM(TextReference.Str("Contents"), "Hello, Bob!"),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(TextReference.Str("From")),
WcTransactionRequestInfoItemUM(
TextReference.Str("wallet"),
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
),
WcTransactionRequestInfoItemUM(TextReference.Str("name"), "Cow"),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(TextReference.Str("To")),
WcTransactionRequestInfoItemUM(
TextReference.Str("wallet"),
"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
),
WcTransactionRequestInfoItemUM(TextReference.Str("name"), "Bob"),
).toImmutableList(),
),
)
Truth.assertThat(converter.convert(value)).isEqualTo(expected)
}
@Test
fun `GIVEN send or approve transaction params WHEN convert THEN return correct data for UI`() {
val value = """
[
{
"data": "0x095ea7b3000000000000000000000000f0d4c12a5768d806021f80a262b4d39d26c58b8dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"from": "0xcd5F26C95e84279d0ce8E6dd9030d0b2171b6101",
"to": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
"value": "0x0"
}
]
""".trimIndent()
val expected = listOf(
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(
title = TextReference.Str("Data"),
description = "0x095ea7b3000000000000000000000000f0d4c12a5768d806021f80a262b4d39d26c58b8dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(
title = TextReference.Str("From"),
description = "0xcd5F26C95e84279d0ce8E6dd9030d0b2171b6101",
),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(
title = TextReference.Str("To"),
description = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
),
).toImmutableList(),
),
WcTransactionRequestBlockUM(
info = listOf(
WcTransactionRequestInfoItemUM(
title = TextReference.Str("Value"),
description = "0x0",
),
).toImmutableList(),
),
)
Truth.assertThat(converter.convert(value)).isEqualTo(expected)
}
}