Updated on 2026-08-14
This commit is contained in:
parent
a69cc2b5e6
commit
502d9e3b40
12 changed files with 135 additions and 65 deletions
|
|
@ -16,6 +16,7 @@ data class Asset(
|
|||
@Json(name = "chain_id") val chainId: Int? = null,
|
||||
@Json(name = "logo_url") val logoUrl: String? = null,
|
||||
@Json(name = "symbol") val symbol: String? = null,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
@Json(name = "decimals") val decimals: Int? = null,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Exposure(
|
||||
@Json(name = "asset_type") val assetType: String,
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "spenders") val spenders: Map<String, SpenderDetails>,
|
||||
)
|
||||
|
|
|
|||
17
core/ui/src/main/res/drawable/img_approvale_new_24.xml
Normal file
17
core/ui/src/main/res/drawable/img_approvale_new_24.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#0099FF"
|
||||
android:pathData="M16.539,8.682C16.769,8.338 16.677,7.872 16.332,7.642C15.988,7.412 15.522,7.505 15.292,7.849L10.88,14.452L8.677,11.614C8.423,11.287 7.952,11.228 7.625,11.482C7.297,11.736 7.238,12.207 7.492,12.534L10.333,16.194C10.481,16.384 10.712,16.492 10.953,16.484C11.193,16.475 11.416,16.351 11.55,16.151L16.539,8.682Z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#0099FF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M12,1.746C6.337,1.746 1.746,6.337 1.746,12C1.746,17.663 6.337,22.254 12,22.254C17.663,22.254 22.254,17.663 22.254,12C22.254,6.337 17.663,1.746 12,1.746ZM3.246,12C3.246,7.165 7.165,3.246 12,3.246C16.835,3.246 20.754,7.165 20.754,12C20.754,16.835 16.835,20.754 12,20.754C7.165,20.754 3.246,16.835 3.246,12Z" />
|
||||
|
||||
</vector>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.domain.blockaid.models.transaction.simultation
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class ApproveInfo {
|
||||
data class Amount(
|
||||
val approvedAmount: BigDecimal,
|
||||
val isUnlimited: Boolean,
|
||||
val tokenInfo: TokenInfo,
|
||||
) : ApproveInfo()
|
||||
|
||||
data class NonFungibleToken(val name: String, val logoUrl: String?) : ApproveInfo()
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.domain.blockaid.models.transaction.simultation
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class ApprovedAmount(
|
||||
val approvedAmount: BigDecimal,
|
||||
val isUnlimited: Boolean,
|
||||
val tokenInfo: TokenInfo,
|
||||
)
|
||||
|
|
@ -16,9 +16,7 @@ sealed class SimulationData {
|
|||
/**
|
||||
* Represents an approve operation with the specified amount (can be multiple amounts for NFT)
|
||||
*/
|
||||
data class Approve(
|
||||
val approvedAmounts: List<ApprovedAmount>,
|
||||
) : SimulationData()
|
||||
data class Approve(val items: List<ApproveInfo>) : SimulationData()
|
||||
|
||||
/**
|
||||
* Simulation was successfully performed and no changes detected
|
||||
|
|
|
|||
|
|
@ -132,8 +132,7 @@ private fun EstimatedWalletChangesPreviewMoreThanFour(
|
|||
}
|
||||
}
|
||||
|
||||
private class EstimatedWalletChangesPreviewProviderTwoItems :
|
||||
PreviewParameterProvider<WcEstimatedWalletChangesUM> {
|
||||
private class EstimatedWalletChangesPreviewProviderTwoItems : PreviewParameterProvider<WcEstimatedWalletChangesUM> {
|
||||
override val values = sequenceOf(
|
||||
WcEstimatedWalletChangesUM(
|
||||
items = persistentListOf(
|
||||
|
|
@ -149,6 +148,13 @@ private class EstimatedWalletChangesPreviewProviderTwoItems :
|
|||
description = "+ 1,131.46 MATIC",
|
||||
tokenIconUrl = "https://tangem.com",
|
||||
),
|
||||
WcEstimatedWalletChangeUM(
|
||||
iconRes = R.drawable.img_approvale_new_24,
|
||||
title = resourceReference(R.string.common_approve),
|
||||
description = "10 Collection",
|
||||
tokenIconUrl = "https://cdn.blockaid.io/nft/0x09851531816f78cF4841f1DeF22fbaB78aDD02c5/29805/" +
|
||||
"polygon?r=ed117da0-6065-4ff8-ba81-cb7395e1ec3d",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.domain.blockaid.models.transaction.CheckTransactionResult
|
|||
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.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
|
|
@ -55,7 +56,20 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor(
|
|||
},
|
||||
estimatedWalletChanges = (simulation as? SimulationResult.Success)?.data?.let { data ->
|
||||
when (data) {
|
||||
is SimulationData.Approve, SimulationData.NoWalletChangesDetected -> null
|
||||
is SimulationData.NoWalletChangesDetected -> null
|
||||
is SimulationData.Approve -> {
|
||||
val nftItems = data.items.mapNotNull { item ->
|
||||
if (item !is ApproveInfo.NonFungibleToken) return@mapNotNull null
|
||||
|
||||
WcEstimatedWalletChangeUM(
|
||||
iconRes = R.drawable.img_approvale_new_24,
|
||||
title = TextReference.Res(R.string.common_approve),
|
||||
description = item.name,
|
||||
tokenIconUrl = item.logoUrl,
|
||||
)
|
||||
}
|
||||
WcEstimatedWalletChangesUM(nftItems.toImmutableList()).takeIf { nftItems.isNotEmpty() }
|
||||
}
|
||||
is SimulationData.SendAndReceive -> {
|
||||
val items: ImmutableList<WcEstimatedWalletChangeUM> = (
|
||||
data.send.map {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi
|
|||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
|
||||
import com.tangem.core.ui.components.divider.DividerWithPadding
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -122,7 +123,7 @@ internal fun WcSendTransactionModalBottomSheet(
|
|||
modifier = Modifier.padding(top = 14.dp),
|
||||
config = state.feeErrorNotification.config,
|
||||
iconTint = TangemTheme.colors.icon.warning,
|
||||
containerColor = TangemTheme.colors.button.disabled,
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -301,8 +302,12 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide
|
|||
address = null,
|
||||
sendEnabled = false,
|
||||
feeErrorNotification = NotificationUM.Info(
|
||||
title = stringReference("Insufficient Ethereum"),
|
||||
subtitle = stringReference("Top up your balance to cover the network fee"),
|
||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue