Updated on 2026-08-14
This commit is contained in:
parent
9f070d6023
commit
b7c264b448
12 changed files with 408 additions and 13 deletions
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.features.walletconnect.transaction.converter
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
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.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Builds a transaction request details block (recipient and amount) for a Bitcoin `sendTransfer` WalletConnect request.
|
||||
*
|
||||
*/
|
||||
internal class WcBtcSendTransferRequestInfoConverter @Inject constructor() :
|
||||
Converter<WcBtcSendTransferRequestInfoConverter.Input, WcTransactionRequestBlockUM> {
|
||||
|
||||
override fun convert(value: Input): WcTransactionRequestBlockUM {
|
||||
val method = value.method
|
||||
return WcTransactionRequestBlockUM(
|
||||
info = buildList {
|
||||
add(WcTransactionRequestInfoItemUM(resourceReference(R.string.common_from), method.account))
|
||||
add(WcTransactionRequestInfoItemUM(resourceReference(R.string.common_to), method.recipientAddress))
|
||||
add(
|
||||
WcTransactionRequestInfoItemUM(
|
||||
title = resourceReference(R.string.common_amount),
|
||||
description = formatAmount(method.amount, value.decimals, value.symbol),
|
||||
),
|
||||
)
|
||||
val changeAddress = method.changeAddress
|
||||
if (!changeAddress.isNullOrEmpty()) {
|
||||
add(WcTransactionRequestInfoItemUM(resourceReference(R.string.wc_change_address), changeAddress))
|
||||
}
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatAmount(amount: String, decimals: Int, symbol: String): String {
|
||||
val value = amount.toBigDecimalOrNull()
|
||||
?.movePointLeft(decimals)
|
||||
?.stripTrailingZeros()
|
||||
?: return amount
|
||||
return "${value.toPlainString()} $symbol"
|
||||
}
|
||||
|
||||
data class Input(
|
||||
val method: WcBitcoinMethod.SendTransfer,
|
||||
val decimals: Int,
|
||||
val symbol: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.models.wallet.isHotWallet
|
|||
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.WcPsbtOutput
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMethodContext
|
||||
|
|
@ -15,11 +16,13 @@ import com.tangem.features.send.api.entity.FeeSelectorUM
|
|||
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
|
||||
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.WcTransactionRequestBlockUM
|
||||
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.features.walletconnect.utils.WcNotificationsFactory
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -27,6 +30,8 @@ internal class WcSendTransactionUMConverter @Inject constructor(
|
|||
private val appInfoContentUMConverter: WcTransactionAppInfoContentUMConverter,
|
||||
private val networkInfoUMConverter: WcNetworkInfoUMConverter,
|
||||
private val requestBlockUMConverter: WcTransactionRequestBlockUMConverter,
|
||||
private val btcSendTransferRequestInfoConverter: WcBtcSendTransferRequestInfoConverter,
|
||||
private val signPsbtRequestInfoConverter: WcSignPsbtRequestInfoConverter,
|
||||
private val notificationsFactory: WcNotificationsFactory,
|
||||
) : Converter<WcSendTransactionUMConverter.Input, WcSendTransactionUM?> {
|
||||
|
||||
|
|
@ -76,13 +81,7 @@ internal class WcSendTransactionUMConverter @Inject constructor(
|
|||
is WcTransactionFeeState.Success -> value.feeSelectorUM ?: FeeSelectorUM.Loading
|
||||
},
|
||||
transactionRequestInfo = WcTransactionRequestInfoUM(
|
||||
blocks = buildList {
|
||||
addAll(
|
||||
requestBlockUMConverter.convert(
|
||||
WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest),
|
||||
),
|
||||
)
|
||||
}.toImmutableList(),
|
||||
blocks = buildRequestInfoBlocks(value),
|
||||
onCopy = value.actions.onCopy,
|
||||
),
|
||||
)
|
||||
|
|
@ -97,6 +96,37 @@ internal class WcSendTransactionUMConverter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildRequestInfoBlocks(value: Input): ImmutableList<WcTransactionRequestBlockUM> = buildList {
|
||||
addAll(
|
||||
requestBlockUMConverter.convert(
|
||||
WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest),
|
||||
),
|
||||
)
|
||||
val method = value.context.method
|
||||
if (method is WcBitcoinMethod.SendTransfer) {
|
||||
add(
|
||||
btcSendTransferRequestInfoConverter.convert(
|
||||
WcBtcSendTransferRequestInfoConverter.Input(
|
||||
method = method,
|
||||
decimals = value.cryptoCurrencyStatus.currency.decimals,
|
||||
symbol = value.cryptoCurrencyStatus.currency.symbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (method is WcBitcoinMethod.SignPsbt && value.psbtOutputs != null) {
|
||||
addAll(
|
||||
signPsbtRequestInfoConverter.convert(
|
||||
WcSignPsbtRequestInfoConverter.Input(
|
||||
outputs = value.psbtOutputs,
|
||||
decimals = value.cryptoCurrencyStatus.currency.decimals,
|
||||
symbol = value.cryptoCurrencyStatus.currency.symbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toImmutableList()
|
||||
|
||||
data class Input(
|
||||
val context: WcMethodContext,
|
||||
val portfolioName: AccountTitleUM?,
|
||||
|
|
@ -106,6 +136,7 @@ internal class WcSendTransactionUMConverter @Inject constructor(
|
|||
val feeSelectorUM: FeeSelectorUM?,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val securityCheck: BlockAidTransactionCheck.Result?,
|
||||
val psbtOutputs: List<WcPsbtOutput>?,
|
||||
val onFeeReload: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.features.walletconnect.transaction.converter
|
||||
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.walletconnect.model.WcPsbtOutput
|
||||
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.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Builds transaction request details blocks (recipient + amount per output) for a Bitcoin `signPsbt` request.
|
||||
*
|
||||
*/
|
||||
internal class WcSignPsbtRequestInfoConverter @Inject constructor() :
|
||||
Converter<WcSignPsbtRequestInfoConverter.Input, List<WcTransactionRequestBlockUM>> {
|
||||
|
||||
override fun convert(value: Input): List<WcTransactionRequestBlockUM> {
|
||||
val outputs = value.outputs
|
||||
if (outputs.isEmpty()) return emptyList()
|
||||
val shouldShowIndex = outputs.size > 1
|
||||
return outputs.mapIndexed { index, output ->
|
||||
val recipientTitle = when {
|
||||
output.address == null -> resourceReference(R.string.common_no_address)
|
||||
shouldShowIndex -> combinedReference(
|
||||
resourceReference(R.string.common_to),
|
||||
stringReference(" ${index + 1}"),
|
||||
)
|
||||
else -> resourceReference(R.string.common_to)
|
||||
}
|
||||
WcTransactionRequestBlockUM(
|
||||
info = buildList {
|
||||
add(
|
||||
WcTransactionRequestInfoItemUM(
|
||||
title = recipientTitle,
|
||||
description = output.address.orEmpty(),
|
||||
),
|
||||
)
|
||||
add(
|
||||
WcTransactionRequestInfoItemUM(
|
||||
title = resourceReference(R.string.common_amount),
|
||||
description = formatAmount(output.amountSatoshi, value.decimals, value.symbol),
|
||||
),
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatAmount(amountSatoshi: Long, decimals: Int, symbol: String): String {
|
||||
val value = BigDecimal.valueOf(amountSatoshi)
|
||||
.movePointLeft(decimals)
|
||||
.stripTrailingZeros()
|
||||
return "${value.toPlainString()} $symbol"
|
||||
}
|
||||
|
||||
data class Input(
|
||||
val outputs: List<WcPsbtOutput>,
|
||||
val decimals: Int,
|
||||
val symbol: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import com.tangem.domain.walletconnect.WcAnalyticEvents
|
|||
import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents.SolanaLargeTransaction
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.model.WcPsbtOutput
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message
|
||||
import com.tangem.domain.walletconnect.usecase.method.*
|
||||
|
|
@ -100,6 +101,7 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
private var useCase: WcSignUseCase<*> by Delegates.notNull()
|
||||
private var signState: WcSignState<*> by Delegates.notNull()
|
||||
private var wcApproval: WcApproval? = null
|
||||
private var psbtOutputs: List<WcPsbtOutput>? = null
|
||||
private var sign: () -> Unit = {}
|
||||
private val blockAidUiConverter = WcSendAndReceiveBlockAidUiConverter()
|
||||
private val feeReloadState = MutableStateFlow(false)
|
||||
|
|
@ -126,6 +128,9 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
.onNone { unknownMethodRunnable() }
|
||||
.getOrNull() ?: return@launch
|
||||
this@WcSendTransactionModel.useCase = useCase
|
||||
if (useCase is WcPsbtUseCase) {
|
||||
psbtOutputs = useCase.parsePsbtOutputs()
|
||||
}
|
||||
(useCase as? WcMutableFee)
|
||||
?.dAppFee()
|
||||
?.let { dAppFee ->
|
||||
|
|
@ -283,6 +288,7 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
onFeeReload = ::triggerFeeReload,
|
||||
securityCheck = securityCheck.getOrNull(),
|
||||
psbtOutputs = psbtOutputs,
|
||||
portfolioName = portfolioNameDelegate.createAccountTitleUM(useCase.session),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.features.walletconnect.transaction.converter
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.walletconnect.model.WcBitcoinMethod
|
||||
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 kotlinx.collections.immutable.toImmutableList
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class WcBtcSendTransferRequestInfoConverterTest {
|
||||
|
||||
private val converter = WcBtcSendTransferRequestInfoConverter()
|
||||
|
||||
@Test
|
||||
fun `GIVEN sendTransfer with change address WHEN convert THEN block with from to amount and change`() {
|
||||
val input = WcBtcSendTransferRequestInfoConverter.Input(
|
||||
method = WcBitcoinMethod.SendTransfer(
|
||||
account = "bc1qsenderaddress",
|
||||
recipientAddress = "bc1qrecipientaddress",
|
||||
amount = "5000000",
|
||||
memo = null,
|
||||
changeAddress = "bc1qchangeaddress",
|
||||
),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val expected = WcTransactionRequestBlockUM(
|
||||
info = listOf(
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.common_from), "bc1qsenderaddress"),
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.common_to), "bc1qrecipientaddress"),
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.common_amount), "0.05 BTC"),
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.wc_change_address), "bc1qchangeaddress"),
|
||||
).toImmutableList(),
|
||||
)
|
||||
|
||||
Truth.assertThat(converter.convert(input)).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN sendTransfer without change address WHEN convert THEN block with from to amount only`() {
|
||||
val input = WcBtcSendTransferRequestInfoConverter.Input(
|
||||
method = WcBitcoinMethod.SendTransfer(
|
||||
account = "bc1qsenderaddress",
|
||||
recipientAddress = "bc1qrecipientaddress",
|
||||
amount = "100000000",
|
||||
memo = null,
|
||||
changeAddress = null,
|
||||
),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val expected = WcTransactionRequestBlockUM(
|
||||
info = listOf(
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.common_from), "bc1qsenderaddress"),
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.common_to), "bc1qrecipientaddress"),
|
||||
WcTransactionRequestInfoItemUM(resourceReference(R.string.common_amount), "1 BTC"),
|
||||
).toImmutableList(),
|
||||
)
|
||||
|
||||
Truth.assertThat(converter.convert(input)).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN blank change address WHEN convert THEN change address item is omitted`() {
|
||||
val input = WcBtcSendTransferRequestInfoConverter.Input(
|
||||
method = WcBitcoinMethod.SendTransfer(
|
||||
account = "bc1qsenderaddress",
|
||||
recipientAddress = "bc1qrecipientaddress",
|
||||
amount = "1",
|
||||
memo = null,
|
||||
changeAddress = "",
|
||||
),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val result = converter.convert(input)
|
||||
|
||||
Truth.assertThat(result.info).hasSize(3)
|
||||
Truth.assertThat(result.info.last().title).isEqualTo(resourceReference(R.string.common_amount))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-numeric amount WHEN convert THEN raw amount is shown`() {
|
||||
val input = WcBtcSendTransferRequestInfoConverter.Input(
|
||||
method = WcBitcoinMethod.SendTransfer(
|
||||
account = "bc1qsenderaddress",
|
||||
recipientAddress = "bc1qrecipientaddress",
|
||||
amount = "not-a-number",
|
||||
memo = null,
|
||||
changeAddress = null,
|
||||
),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val amountItem = converter.convert(input).info[2]
|
||||
|
||||
Truth.assertThat(amountItem.title).isEqualTo(resourceReference(R.string.common_amount))
|
||||
Truth.assertThat(amountItem.description).isEqualTo("not-a-number")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.tangem.features.walletconnect.transaction.converter
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.walletconnect.model.WcPsbtOutput
|
||||
import com.tangem.features.walletconnect.impl.R
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class WcSignPsbtRequestInfoConverterTest {
|
||||
|
||||
private val converter = WcSignPsbtRequestInfoConverter()
|
||||
|
||||
@Test
|
||||
fun `GIVEN single output WHEN convert THEN one block with to and amount`() {
|
||||
val input = WcSignPsbtRequestInfoConverter.Input(
|
||||
outputs = listOf(WcPsbtOutput(address = "bc1qrecipient", amountSatoshi = 100_000L)),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val result = converter.convert(input)
|
||||
|
||||
Truth.assertThat(result).hasSize(1)
|
||||
Truth.assertThat(result[0].info.map { it.title }).containsExactly(
|
||||
resourceReference(R.string.common_to),
|
||||
resourceReference(R.string.common_amount),
|
||||
).inOrder()
|
||||
Truth.assertThat(result[0].info[0].description).isEqualTo("bc1qrecipient")
|
||||
Truth.assertThat(result[0].info[1].description).isEqualTo("0.001 BTC")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple outputs WHEN convert THEN indexed blocks per output`() {
|
||||
val input = WcSignPsbtRequestInfoConverter.Input(
|
||||
outputs = listOf(
|
||||
WcPsbtOutput(address = "bc1qfirst", amountSatoshi = 250_000L),
|
||||
WcPsbtOutput(address = "bc1qsecond", amountSatoshi = 1_0000_0000L),
|
||||
),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val result = converter.convert(input)
|
||||
|
||||
Truth.assertThat(result).hasSize(2)
|
||||
Truth.assertThat(result[0].info[0].title)
|
||||
.isEqualTo(combinedReference(resourceReference(R.string.common_to), stringReference(" 1")))
|
||||
Truth.assertThat(result[0].info[1].description).isEqualTo("0.0025 BTC")
|
||||
Truth.assertThat(result[1].info[0].title)
|
||||
.isEqualTo(combinedReference(resourceReference(R.string.common_to), stringReference(" 2")))
|
||||
Truth.assertThat(result[1].info[0].description).isEqualTo("bc1qsecond")
|
||||
Truth.assertThat(result[1].info[1].description).isEqualTo("1 BTC")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN output with undecodable address WHEN convert THEN no address title and empty recipient`() {
|
||||
val input = WcSignPsbtRequestInfoConverter.Input(
|
||||
outputs = listOf(WcPsbtOutput(address = null, amountSatoshi = 50_000L)),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
val result = converter.convert(input)
|
||||
|
||||
Truth.assertThat(result[0].info[0].title).isEqualTo(resourceReference(R.string.common_no_address))
|
||||
Truth.assertThat(result[0].info[0].description).isEqualTo("")
|
||||
Truth.assertThat(result[0].info[1].description).isEqualTo("0.0005 BTC")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no outputs WHEN convert THEN empty list`() {
|
||||
val input = WcSignPsbtRequestInfoConverter.Input(
|
||||
outputs = emptyList(),
|
||||
decimals = 8,
|
||||
symbol = "BTC",
|
||||
)
|
||||
|
||||
Truth.assertThat(converter.convert(input)).isEmpty()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue