diff --git a/features/walletconnect/impl/build.gradle.kts b/features/walletconnect/impl/build.gradle.kts index 9bccd9b7a3..0bf16b6415 100644 --- a/features/walletconnect/impl/build.gradle.kts +++ b/features/walletconnect/impl/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/TransactionParamsConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/TransactionParamsConverter.kt new file mode 100644 index 0000000000..bc884a209f --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/TransactionParamsConverter.kt @@ -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> { + + @Suppress("CyclomaticComplexMethod", "NestedBlockDepth") + override fun convert(value: String): List { + val result = mutableListOf() + + 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.addSingleStringValueBlock( + key: String, + stringValue: String, + ): MutableList { + add( + WcTransactionRequestBlockUM( + info = listOf( + WcTransactionRequestInfoItemUM( + title = TextReference.Str(key.capitalize()), + description = stringValue, + ), + ).toImmutableList(), + ), + ) + return this + } + + private fun MutableList.addNestedStringValueBlock( + key: String, + nestedKey: String, + stringValue: String, + ): MutableList { + add( + WcTransactionRequestBlockUM( + info = listOf( + WcTransactionRequestInfoItemUM(TextReference.Str(key.capitalize())), + WcTransactionRequestInfoItemUM( + title = TextReference.Str(nestedKey.capitalize()), + description = stringValue, + ), + ).toImmutableList(), + ), + ) + return this + } + + private fun MutableList.addMultipleObjectsBlock( + key: String, + objectValue: JSONObject, + ): MutableList { + 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 { + val items = mutableListOf() + 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" + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index 78ba6cc321..9a2275f2d4 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -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, ), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt index 333cb72fbc..a1e30fdfad 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt @@ -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, ), ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt index 6b70e21d47..df8661fc77 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt @@ -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, ), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcTransactionRequestBlockUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcTransactionRequestBlockUMConverter.kt index 241a8ba2bf..cc68b795ce 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcTransactionRequestBlockUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcTransactionRequestBlockUMConverter.kt @@ -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> { -internal class WcTransactionRequestBlockUMConverter @Inject constructor() : - Converter { - - override fun convert(value: Input): WcTransactionRequestBlockUM { + override fun convert(value: Input): List { 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( diff --git a/features/walletconnect/impl/src/test/kotlin/com/tangem/features/walletconnect/transaction/converter/TransactionParamsConverterTest.kt b/features/walletconnect/impl/src/test/kotlin/com/tangem/features/walletconnect/transaction/converter/TransactionParamsConverterTest.kt new file mode 100644 index 0000000000..7446b707fd --- /dev/null +++ b/features/walletconnect/impl/src/test/kotlin/com/tangem/features/walletconnect/transaction/converter/TransactionParamsConverterTest.kt @@ -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) + } +} \ No newline at end of file