Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-11 14:42:09 +05:00
parent ae33c00aae
commit 85cff6b0d3
10 changed files with 169 additions and 22 deletions

View file

@ -17,6 +17,9 @@ private const val SUCCESS_STATUS = "Success"
private const val DOMAIN_CHECKED_STATUS = "hit"
private const val VALIDATION_SAFE_STATUS = "Benign"
private const val VALIDATION_WARNING_STATUS = "Warning"
private const val VALIDATION_MALICIOUS_STATUS = "Malicious"
private const val SOL_ASSET_SYMBOL = "SOL"
internal object BlockAidMapper {
@ -28,6 +31,26 @@ internal object BlockAidMapper {
}
}
fun mapToDomain(from: SolanaTransactionResponse): CheckTransactionResult {
val validation = when (from.result.validation.resultType) {
VALIDATION_SAFE_STATUS -> ValidationResult.SAFE
VALIDATION_WARNING_STATUS -> ValidationResult.WARNING
VALIDATION_MALICIOUS_STATUS -> ValidationResult.UNSAFE
else -> ValidationResult.FAILED_TO_VALIDATE
}
val simulationResponse = from.result.simulation
val simulation = if (simulationResponse == null) {
SimulationResult.FailedToSimulate
} else {
mapToSolanaAssetsDiffs(simulationResponse.accountSummary.accountAssetsDiff)
}
return CheckTransactionResult(
validation = validation,
description = from.result.validation.description,
simulation = simulation,
)
}
fun mapToDomain(from: TransactionScanResponse): CheckTransactionResult {
return CheckTransactionResult(
validation = when {
@ -68,7 +91,7 @@ internal object BlockAidMapper {
fun mapToSolanaRequest(from: TransactionData): SolanaTransactionScanRequest {
return SolanaTransactionScanRequest(
chain = from.chain.lowercase(),
blockchain = from.chain.lowercase(),
accountAddress = from.accountAddress,
metadata = TransactionMetadata(from.domainUrl),
method = from.method,
@ -89,6 +112,49 @@ internal object BlockAidMapper {
}
}
private fun mapToSolanaAssetsDiffs(assetsDiffs: List<SolanaTransactionAssetDiff>): SimulationResult {
val sendInfo = assetsDiffs.mapNotNull { assetDiff ->
val outTransfer = assetDiff.outTransfer ?: return@mapNotNull null
val amount = outTransfer.amount?.toBigDecimalOrNull() ?: return@mapNotNull null
AmountInfo.FungibleTokens(
amount = amount,
token = TokenInfo(
chainId = null,
logoUrl = assetDiff.asset.logoUrl,
symbol = assetDiff.asset.assetSymbol(),
decimals = assetDiff.asset.decimals ?: 0,
),
)
}
val receiveInfo = assetsDiffs.mapNotNull { assetDiff ->
val inTransfer = assetDiff.inTransfer ?: return@mapNotNull null
val amount = inTransfer.amount?.toBigDecimalOrNull() ?: return@mapNotNull null
AmountInfo.FungibleTokens(
amount = amount,
token = TokenInfo(
chainId = null,
logoUrl = assetDiff.asset.logoUrl,
symbol = assetDiff.asset.assetSymbol(),
decimals = assetDiff.asset.decimals ?: 0,
),
)
}
return if (sendInfo.isNotEmpty() || receiveInfo.isNotEmpty()) {
SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = receiveInfo))
} else {
SimulationResult.Success(SimulationData.NoWalletChangesDetected)
}
}
private fun SolanaTransactionAsset.assetSymbol(): String {
return if (type?.lowercase().equals(SOL_ASSET_SYMBOL, ignoreCase = true)) {
symbol ?: SOL_ASSET_SYMBOL
} else {
symbol.orEmpty()
}
}
private fun mapApproveTransaction(exposures: List<Exposure>?): SimulationResult {
val amounts = exposures?.flatMap { exposure ->
val tokenInfo = TokenInfo(

View file

@ -24,16 +24,21 @@ internal class DefaultBlockAidRepository(
}
override suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult {
val response = withContext(dispatchers.io) {
when (data.params) {
is TransactionParams.Evm -> {
api.scanJsonRpc(mapper.mapToEvmRequest(data))
}
is TransactionParams.Solana -> {
api.scanSolanaMessage(mapper.mapToSolanaRequest(data))
}
}
return when (data.params) {
is TransactionParams.Evm -> scanEvmTransaction(data = data)
is TransactionParams.Solana -> scanSolanaTransaction(data = data)
}
return mapper.mapToDomain(response)
}
private suspend fun scanEvmTransaction(data: TransactionData): CheckTransactionResult =
withContext(dispatchers.io) {
val response = api.scanJsonRpc(mapper.mapToEvmRequest(data))
mapper.mapToDomain(response)
}
private suspend fun scanSolanaTransaction(data: TransactionData): CheckTransactionResult =
withContext(dispatchers.io) {
val response = api.scanSolanaMessage(mapper.mapToSolanaRequest(data))
mapper.mapToDomain(response)
}
}

View file

@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.blockaid.models.request.DomainScanReques
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
@ -91,7 +92,7 @@ class DefaultBlockAidRepositoryTest {
)
val request = mockk<SolanaTransactionScanRequest>()
val response = mockk<TransactionScanResponse>()
val response = mockk<SolanaTransactionResponse>()
val expectedResult = mockk<CheckTransactionResult>()
every { mapper.mapToSolanaRequest(data) } returns request

View file

@ -40,6 +40,14 @@ internal class BlockAidVerificationDelegate @Inject constructor(
return@flow
}
emit(Lce.Loading(partialContent = null))
val methodName = when (method) {
is WcEthMethod -> rawSdkRequest.request.method
is WcSolanaMethod -> method.trimmedPrefixMethodName
is WcMethod.Unsupported -> {
emit(Lce.Content(failedResult))
return@flow
}
}
val params = when (method) {
is WcEthMethod -> TransactionParams.Evm(rawSdkRequest.request.params)
is WcSolanaMethod.SignAllTransaction -> TransactionParams.Solana(method.transaction)
@ -59,7 +67,7 @@ internal class BlockAidVerificationDelegate @Inject constructor(
data = TransactionData(
chain = chain,
accountAddress = accountAddress,
method = rawSdkRequest.request.method,
method = methodName,
domainUrl = session.sdkModel.appMetaData.url,
params = params,
),