Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-12 12:10:39 +05:00
parent a69cc2b5e6
commit 502d9e3b40
12 changed files with 135 additions and 65 deletions

View file

@ -3,7 +3,7 @@ package com.tangem.data.blockaid
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.domain.blockaid.models.transaction.*
import com.domain.blockaid.models.transaction.simultation.AmountInfo
import com.domain.blockaid.models.transaction.simultation.ApprovedAmount
import com.domain.blockaid.models.transaction.simultation.ApproveInfo
import com.domain.blockaid.models.transaction.simultation.SimulationData
import com.domain.blockaid.models.transaction.simultation.TokenInfo
import com.tangem.blockchain.extensions.hexToBigDecimal
@ -101,13 +101,8 @@ internal object BlockAidMapper {
private fun mapSimulationSuccessResult(from: AccountSummaryResponse): SimulationResult {
return when {
!from.assetsDiffs.isNullOrEmpty() -> mapSendReceiveTransaction(
from.assetsDiffs,
)
!from.exposures.isNullOrEmpty() -> mapApproveTransaction(
from.exposures,
)
!from.traces.isNullOrEmpty() -> mapNftSendReceiveTransaction(from.traces)
!from.exposures.isNullOrEmpty() -> mapApproveTransaction(from.exposures)
!from.assetsDiffs.isNullOrEmpty() -> mapSendReceiveTransaction(from.assetsDiffs)
else -> SimulationResult.Success(data = SimulationData.NoWalletChangesDetected)
}
}
@ -156,23 +151,11 @@ internal object BlockAidMapper {
}
private fun mapApproveTransaction(exposures: List<Exposure>?): SimulationResult {
val amounts = exposures?.flatMap { exposure ->
val tokenInfo = TokenInfo(
chainId = exposure.asset.chainId,
logoUrl = exposure.asset.logoUrl,
symbol = exposure.asset.symbol ?: "",
decimals = exposure.asset.decimals ?: 0,
)
exposure.spenders.flatMap { (_, spender) ->
val isUnlimited = spender.isApprovedForAll == true
val approval = spender.approval?.hexToBigDecimal()
spender.exposure.map { detail ->
ApprovedAmount(
approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(),
isUnlimited = isUnlimited,
tokenInfo = tokenInfo,
)
}
val amounts: List<ApproveInfo>? = exposures?.flatMap { exposure ->
if (exposure.assetType.isNFT()) {
listOf(mapApproveNftTransaction(exposure))
} else {
mapTransaction(exposure)
}
}
return if (!amounts.isNullOrEmpty()) {
@ -182,6 +165,34 @@ internal object BlockAidMapper {
}
}
private fun mapTransaction(exposure: Exposure): List<ApproveInfo.Amount> {
val tokenInfo = TokenInfo(
chainId = exposure.asset.chainId,
logoUrl = exposure.asset.logoUrl,
symbol = exposure.asset.symbol ?: "",
decimals = exposure.asset.decimals ?: 0,
)
return exposure.spenders.flatMap { (_, spender) ->
val isUnlimited = spender.isApprovedForAll == true
val approval = spender.approval?.hexToBigDecimal()
spender.exposure.map { detail ->
ApproveInfo.Amount(
approvedAmount = detail.value?.toBigDecimalOrNull() ?: approval ?: 1.toBigDecimal(),
isUnlimited = isUnlimited,
tokenInfo = tokenInfo,
)
}
}
}
private fun mapApproveNftTransaction(exposure: Exposure): ApproveInfo.NonFungibleToken {
return ApproveInfo.NonFungibleToken(
name = exposure.asset.name.orEmpty(),
logoUrl = exposure.spenders.values.firstOrNull()?.exposure?.firstOrNull()?.logoUrl
?: exposure.asset.logoUrl,
)
}
private fun mapSendReceiveTransaction(assetDiffs: List<AssetDiff>?): SimulationResult {
val sendInfo = arrayListOf<AmountInfo>()
val receiveInfo = arrayListOf<AmountInfo>()
@ -194,13 +205,31 @@ internal object BlockAidMapper {
decimals = diff.asset.decimals ?: 0,
)
diff.outTransfer.orEmpty().forEach { transfer ->
transfer.value?.toBigDecimalOrNull()?.let { amount ->
sendInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
if (diff.assetType.isNFT()) {
sendInfo.add(
AmountInfo.NonFungibleTokens(
name = diff.asset.name.orEmpty(),
logoUrl = token.logoUrl,
),
)
} else {
transfer.value?.toBigDecimalOrNull()?.let { amount ->
sendInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
}
}
}
diff.inTransfer.orEmpty().forEach { transfer ->
transfer.value?.toBigDecimalOrNull()?.let { amount ->
receiveInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
if (diff.assetType.isNFT()) {
receiveInfo.add(
AmountInfo.NonFungibleTokens(
name = diff.asset.name.orEmpty(),
logoUrl = token.logoUrl,
),
)
} else {
transfer.value?.toBigDecimalOrNull()?.let { amount ->
receiveInfo.add(AmountInfo.FungibleTokens(amount = amount, token = token))
}
}
}
}
@ -212,17 +241,7 @@ internal object BlockAidMapper {
}
}
private fun mapNftSendReceiveTransaction(traces: List<Trace>?): SimulationResult {
val sendInfo = traces?.mapNotNull {
it.exposed?.let { exposed ->
AmountInfo.NonFungibleTokens(name = "${it.asset.name} #${exposed.tokenId}", logoUrl = exposed.logoUrl)
}
}
return if (!sendInfo.isNullOrEmpty()) {
SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = listOf()))
} else {
SimulationResult.Success(SimulationData.NoWalletChangesDetected)
}
private fun String.isNFT(): Boolean {
return this.lowercase() == "erc721" || this.lowercase() == "erc1155" || this.lowercase() == "nft"
}
}

View file

@ -4,6 +4,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult
import com.domain.blockaid.models.transaction.SimulationResult
import com.domain.blockaid.models.transaction.ValidationResult
import com.domain.blockaid.models.transaction.simultation.AmountInfo
import com.domain.blockaid.models.transaction.simultation.ApproveInfo
import com.domain.blockaid.models.transaction.simultation.SimulationData
import com.google.common.truth.Truth
import com.tangem.datasource.api.common.blockaid.models.response.*
@ -44,6 +45,7 @@ class BlockAidMapperTest {
val exposure = Exposure(
asset = Asset(chainId = 1, logoUrl = "logo", symbol = "PEPE", decimals = 8),
spenders = mapOf("spender" to spenderDetails),
assetType = "native",
)
val response = TransactionScanResponse(
validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""),
@ -65,9 +67,10 @@ class BlockAidMapperTest {
val approve = simulation?.data as? SimulationData.Approve
Truth.assertThat(approve).isNotNull()
Truth.assertThat(approve?.approvedAmounts?.size).isEqualTo(1)
Truth.assertThat(approve?.approvedAmounts?.first()?.approvedAmount).isEqualTo(BigDecimal("1000.0"))
Truth.assertThat(approve?.approvedAmounts?.first()?.isUnlimited).isTrue()
Truth.assertThat(approve?.items?.size).isEqualTo(1)
Truth.assertThat((approve?.items?.first() as? ApproveInfo.Amount)?.approvedAmount)
.isEqualTo(BigDecimal("1000.0"))
Truth.assertThat((approve?.items?.first() as? ApproveInfo.Amount)?.isUnlimited).isTrue()
}
@Test

View file

@ -2,7 +2,7 @@ package com.tangem.data.walletconnect.network.ethereum
import com.domain.blockaid.models.transaction.CheckTransactionResult
import com.domain.blockaid.models.transaction.SimulationResult
import com.domain.blockaid.models.transaction.simultation.ApprovedAmount
import com.domain.blockaid.models.transaction.simultation.ApproveInfo
import com.domain.blockaid.models.transaction.simultation.SimulationData
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData
@ -68,13 +68,15 @@ internal class WcEthTxHelper @Inject constructor(
)
}
fun getApprovedAmount(txData: String?, result: CheckTransactionResult): ApprovedAmount? {
fun getApprovedAmount(txData: String?, result: CheckTransactionResult): ApproveInfo.Amount? {
val approvalMethodId = ApprovalERC20TokenCallData("", null).methodId
val isApprovalWcMethod = txData?.startsWith(approvalMethodId)
if (isApprovalWcMethod != true) return null
val simulation = result.simulation as? SimulationResult.Success
?: return null
val approves = (simulation.data as? SimulationData.Approve)?.approvedAmounts
val approves = (simulation.data as? SimulationData.Approve)
?.items
?.filterIsInstance<ApproveInfo.Amount>()
?: return null
if (approves.isEmpty()) return null
val amount = approves.first()