Updated on 2026-08-14
This commit is contained in:
parent
7a8f55b589
commit
2af08f578c
46 changed files with 1035 additions and 0 deletions
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.api.common.blockaid
|
||||
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
|
||||
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.TransactionScanResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface BlockAidApi {
|
||||
|
||||
@POST("site/scan")
|
||||
suspend fun scanDomain(@Body request: DomainScanRequest): DomainScanResponse
|
||||
|
||||
@POST("evm/json-rpc/scan")
|
||||
suspend fun scanJsonRpc(@Body request: EvmTransactionScanRequest): TransactionScanResponse
|
||||
|
||||
@POST("solana/message/scan")
|
||||
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): TransactionScanResponse
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DomainScanRequest(
|
||||
@Json(name = "url") val url: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EvmTransactionScanRequest(
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "account_address") val accountAddress: String,
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "data") val data: RpcData,
|
||||
@Json(name = "options") val options: List<String> = listOf("simulation", "validation"),
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RpcData(
|
||||
@Json(name = "jsonrpc") val jsonrpc: String = "2.0",
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "params") val params: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SolanaTransactionScanRequest(
|
||||
@Json(name = "encoding") val encoding: String = "base64",
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "options") val options: List<String> = listOf("simulation, validation"),
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
@Json(name = "account_address") val accountAddress: String,
|
||||
@Json(name = "transactions") val transactions: List<String>,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AccountSummaryResponse(
|
||||
@Json(name = "assets_diffs") val assetsDiffs: List<AssetDiff>,
|
||||
@Json(name = "exposures") val exposures: List<Exposure>,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssetDiff(
|
||||
@Json(name = "asset_type") val assetType: String,
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "in") val inTransfer: List<Transfer>? = null,
|
||||
@Json(name = "out") val outTransfer: List<Transfer>? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
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,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Transfer(
|
||||
@Json(name = "value") val value: String,
|
||||
@Json(name = "raw_value") val rawValue: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DomainScanResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "is_malicious") val isMalicious: Boolean?,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Exposure(
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "spenders") val spenders: Map<String, SpenderDetails>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SpenderDetails(
|
||||
@Json(name = "exposure") val exposure: List<ExposureDetail>,
|
||||
@Json(name = "is_approved_for_all") val isApprovedForAll: Boolean? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExposureDetail(
|
||||
@Json(name = "value") val value: String,
|
||||
@Json(name = "raw_value") val rawValue: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SimulationResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "account_summary") val accountSummary: AccountSummaryResponse,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionMetadata(
|
||||
@Json(name = "domain") val domain: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionScanResponse(
|
||||
@Json(name = "validation") val validation: ValidationResponse,
|
||||
@Json(name = "simulation") val simulation: SimulationResponse,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ValidationResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "result_type") val resultType: String,
|
||||
)
|
||||
|
|
@ -27,6 +27,7 @@ sealed class ApiConfig {
|
|||
TangemVisaAuth,
|
||||
TangemVisa,
|
||||
TangemCardSdk,
|
||||
BlockAid,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -37,6 +38,7 @@ sealed class ApiConfig {
|
|||
is TangemVisaAuth -> ID.TangemVisaAuth
|
||||
is TangemVisa -> ID.TangemVisa
|
||||
is TangemCardSdk -> ID.TangemCardSdk
|
||||
is BlockAid -> ID.BlockAid
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
internal class BlockAid(
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
headers = buildMap {
|
||||
environmentConfigStorage.getConfigSync().blockAidApiKey?.let { apiKey ->
|
||||
put("X-API-KEY", ProviderSuspend { apiKey })
|
||||
}
|
||||
put("accept", ProviderSuspend { "application/json" })
|
||||
put("content-type", ProviderSuspend { "application/json" })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -46,6 +46,12 @@ internal object ApiConfigsModule {
|
|||
@IntoSet
|
||||
fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemVisa(appVersionProvider)
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideBlockAidConfig(environmentConfigStorage: EnvironmentConfigStorage): ApiConfig {
|
||||
return BlockAid(environmentConfigStorage)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideTangemCardSdkConfig(): ApiConfig = TangemCardSdk()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
|
|
@ -282,6 +283,29 @@ internal object NetworkModule {
|
|||
.create(T::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlockAidApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): BlockAidApi {
|
||||
return createApi<BlockAidApi>(
|
||||
id = ApiConfig.ID.BlockAid,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
).applyTimeoutAnnotations()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T> createApi(
|
||||
id: ApiConfig.ID,
|
||||
moshi: Moshi,
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ data class EnvironmentConfig(
|
|||
val express: ExpressModel? = null,
|
||||
val devExpress: ExpressModel? = null,
|
||||
val stakeKitApiKey: String? = null,
|
||||
val blockAidApiKey: String? = null,
|
||||
)
|
||||
|
|
@ -23,6 +23,7 @@ internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, E
|
|||
express = value.express,
|
||||
devExpress = value.devExpress,
|
||||
stakeKitApiKey = value.stakeKitApiKey,
|
||||
blockAidApiKey = value.blockaidApiKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ class EnvironmentConfigModel(
|
|||
@Json(name = "alephiumTangemApiKey") val alephiumTangemApiKey: String?,
|
||||
@Json(name = "moralisApiKey") val moralisApiKey: String?,
|
||||
@Json(name = "nftScanApiKey") val nftScanApiKey: String?,
|
||||
@Json(name = "blockaidApiKey") val blockaidApiKey: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
is TangemVisaAuth -> createVisaAuthModel()
|
||||
is TangemVisa -> createVisaModel()
|
||||
is TangemCardSdk -> createTangemCardSdkModel()
|
||||
is BlockAid -> createBlockAidSdkModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +219,16 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createBlockAidSdkModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.BlockAid,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
|
|
|
|||
1
data/blockaid/.gitignore
vendored
Normal file
1
data/blockaid/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
41
data/blockaid/build.gradle.kts
Normal file
41
data/blockaid/build.gradle.kts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.android.library)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.blockaid"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Project - Domain */
|
||||
implementation(projects.data.common)
|
||||
implementation(projects.domain.blockaid)
|
||||
implementation(projects.domain.blockaid.models)
|
||||
|
||||
/* Project - Data */
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/* DI */
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/* Tangem libraries */
|
||||
implementation(tangemDeps.blockchain)
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
/* Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/* Tests */
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.turbine)
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
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.ApprovedAmount
|
||||
import com.domain.blockaid.models.transaction.simultation.SimulationData
|
||||
import com.domain.blockaid.models.transaction.simultation.TokenInfo
|
||||
import com.domain.blockaid.models.transaction.simultation.AmountInfo
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.RpcData
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.*
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val SUCCESS_STATUS = "Success"
|
||||
private const val DOMAIN_CHECKED_STATUS = "hit"
|
||||
private const val VALIDATION_SAFE_STATUS = "Benign"
|
||||
|
||||
internal class BlockAidMapper @Inject constructor() {
|
||||
|
||||
fun mapToDomain(from: DomainScanResponse): CheckDAppResult {
|
||||
return when {
|
||||
from.status != DOMAIN_CHECKED_STATUS -> CheckDAppResult.FAILED_TO_VERIFY
|
||||
from.isMalicious == true -> CheckDAppResult.UNSAFE
|
||||
else -> CheckDAppResult.SAFE
|
||||
}
|
||||
}
|
||||
|
||||
fun mapToDomain(from: TransactionScanResponse): CheckTransactionResult {
|
||||
return CheckTransactionResult(
|
||||
validation = when {
|
||||
from.validation.status != SUCCESS_STATUS -> ValidationResult.FAILED_TO_VALIDATE
|
||||
from.validation.resultType == VALIDATION_SAFE_STATUS -> ValidationResult.SAFE
|
||||
else -> ValidationResult.UNSAFE
|
||||
},
|
||||
simulation = if (from.simulation.status != SUCCESS_STATUS) {
|
||||
SimulationResult.FailedToSimulate
|
||||
} else {
|
||||
mapSimulationSuccessResult(from.simulation.accountSummary)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun mapToEvmRequest(from: TransactionData): EvmTransactionScanRequest {
|
||||
return EvmTransactionScanRequest(
|
||||
chain = from.chain,
|
||||
accountAddress = from.accountAddress,
|
||||
method = from.method,
|
||||
data = RpcData(
|
||||
method = from.method,
|
||||
params = (from.params as TransactionParams.Evm).params,
|
||||
),
|
||||
metadata = TransactionMetadata(from.domainUrl),
|
||||
)
|
||||
}
|
||||
|
||||
fun mapToSolanaRequest(from: TransactionData): SolanaTransactionScanRequest {
|
||||
return SolanaTransactionScanRequest(
|
||||
chain = from.chain,
|
||||
accountAddress = from.accountAddress,
|
||||
metadata = TransactionMetadata(from.domainUrl),
|
||||
method = from.method,
|
||||
transactions = (from.params as TransactionParams.Solana).transactions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapSimulationSuccessResult(from: AccountSummaryResponse): SimulationResult {
|
||||
return when {
|
||||
from.assetsDiffs.isEmpty() && from.exposures.isNotEmpty() -> mapApproveTransaction(from.exposures)
|
||||
from.assetsDiffs.isNotEmpty() && from.exposures.isEmpty() -> mapSendReceiveTransaction(from.assetsDiffs)
|
||||
else -> SimulationResult.FailedToSimulate
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
exposure.spenders.flatMap { (_, spender) ->
|
||||
val isUnlimited = spender.isApprovedForAll == true
|
||||
spender.exposure.mapNotNull { detail ->
|
||||
ApprovedAmount(
|
||||
approvedAmount = detail.value.toBigDecimalOrNull() ?: return@mapNotNull null,
|
||||
isUnlimited = isUnlimited,
|
||||
tokenInfo = tokenInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if (amounts.isNotEmpty()) {
|
||||
SimulationResult.Success(SimulationData.Approve(amounts))
|
||||
} else {
|
||||
SimulationResult.FailedToSimulate
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapSendReceiveTransaction(assetDiffs: List<AssetDiff>): SimulationResult {
|
||||
val sendInfo = arrayListOf<AmountInfo>()
|
||||
val receiveInfo = arrayListOf<AmountInfo>()
|
||||
|
||||
assetDiffs.forEach { diff ->
|
||||
val token = TokenInfo(
|
||||
chainId = diff.asset.chainId,
|
||||
logoUrl = diff.asset.logoUrl,
|
||||
symbol = diff.asset.symbol,
|
||||
)
|
||||
diff.outTransfer.orEmpty().forEach { transfer ->
|
||||
transfer.value.toBigDecimalOrNull()?.let { amount ->
|
||||
sendInfo.add(AmountInfo(amount = amount, token = token))
|
||||
}
|
||||
}
|
||||
diff.inTransfer.orEmpty().forEach { transfer ->
|
||||
transfer.value.toBigDecimalOrNull()?.let { amount ->
|
||||
receiveInfo.add(AmountInfo(amount = amount, token = token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (sendInfo.isNotEmpty() || receiveInfo.isNotEmpty()) {
|
||||
SimulationResult.Success(SimulationData.SendAndReceive(send = sendInfo, receive = receiveInfo))
|
||||
} else {
|
||||
SimulationResult.FailedToSimulate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.data.blockaid
|
||||
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.TransactionData
|
||||
|
||||
interface BlockAidRepository {
|
||||
|
||||
suspend fun verifyDAppDomain(data: DAppData): CheckDAppResult
|
||||
|
||||
suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.data.blockaid
|
||||
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.TransactionData
|
||||
import com.domain.blockaid.models.transaction.TransactionParams
|
||||
import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultBlockAidRepository @Inject constructor(
|
||||
private val api: BlockAidApi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val mapper: BlockAidMapper,
|
||||
) : BlockAidRepository {
|
||||
|
||||
override suspend fun verifyDAppDomain(data: DAppData): CheckDAppResult {
|
||||
val response = withContext(dispatcherProvider.io) {
|
||||
api.scanDomain(DomainScanRequest(data.url))
|
||||
}
|
||||
return mapper.mapToDomain(response)
|
||||
}
|
||||
|
||||
override suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult {
|
||||
val response = withContext(dispatcherProvider.io) {
|
||||
when (data.params) {
|
||||
is TransactionParams.Evm -> {
|
||||
api.scanJsonRpc(mapper.mapToEvmRequest(data))
|
||||
}
|
||||
is TransactionParams.Solana -> {
|
||||
api.scanSolanaMessage(mapper.mapToSolanaRequest(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapper.mapToDomain(response)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.data.blockaid
|
||||
|
||||
import arrow.core.Either
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.TransactionData
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Verifies the safety of DApps and WalletConnect transactions
|
||||
*/
|
||||
class DefaultBlockAidVerifier @Inject constructor(
|
||||
private val repository: BlockAidRepository,
|
||||
) : BlockAidVerifier {
|
||||
|
||||
/**
|
||||
* Checks if a DApp is safe to use
|
||||
*/
|
||||
override suspend fun verifyDApp(data: DAppData): Either<Throwable, CheckDAppResult> {
|
||||
return Either.catch { repository.verifyDAppDomain(data) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the safety of a WalletConnect transaction and provides a simulation result
|
||||
*/
|
||||
override suspend fun verifyTransaction(data: TransactionData): Either<Throwable, CheckTransactionResult> {
|
||||
return Either.catch { repository.verifyTransaction(data) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.data.blockaid.di
|
||||
|
||||
import com.tangem.data.blockaid.BlockAidRepository
|
||||
import com.tangem.data.blockaid.DefaultBlockAidRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface BlockAidDataInternalModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindRepository(repository: DefaultBlockAidRepository): BlockAidRepository
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.data.blockaid.di
|
||||
|
||||
import com.tangem.data.blockaid.DefaultBlockAidVerifier
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface BlockAidDataModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindVerifier(verifier: DefaultBlockAidVerifier): BlockAidVerifier
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.tangem.data.blockaid
|
||||
|
||||
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.SimulationData
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class BlockAidMapperTest {
|
||||
|
||||
private val mapper = BlockAidMapper()
|
||||
|
||||
@Test
|
||||
fun whenStatusHitAndIsMaliciousFalseThenMapToDomainReturnsSafe() {
|
||||
val response = DomainScanResponse(status = "hit", isMalicious = false)
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(CheckDAppResult.SAFE, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenStatusHitAndIsMaliciousTrueThenMapToDomainReturnsUnsafe() {
|
||||
val response = DomainScanResponse(status = "hit", isMalicious = true)
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(CheckDAppResult.UNSAFE, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenStatusNotHitThenMapToDomainReturnsFailedToVerify() {
|
||||
val response = DomainScanResponse(status = "miss", isMalicious = false)
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(CheckDAppResult.FAILED_TO_VERIFY, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenResponseBenignValidationThenReturnsSafeValidation() {
|
||||
val spenderDetails = SpenderDetails(
|
||||
isApprovedForAll = true,
|
||||
exposure = listOf(ExposureDetail(value = "1000.0", rawValue = "0x123")),
|
||||
)
|
||||
val exposure = Exposure(
|
||||
asset = Asset(chainId = 1, logoUrl = "logo", symbol = "PEPE"),
|
||||
spenders = mapOf("spender" to spenderDetails),
|
||||
)
|
||||
val response = TransactionScanResponse(
|
||||
validation = ValidationResponse(status = "Success", resultType = "Benign"),
|
||||
simulation = SimulationResponse(
|
||||
status = "Success",
|
||||
accountSummary = AccountSummaryResponse(assetsDiffs = emptyList(), exposures = listOf(exposure)),
|
||||
),
|
||||
)
|
||||
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(ValidationResult.SAFE, result.validation)
|
||||
|
||||
val simulation = result.simulation as? SimulationResult.Success
|
||||
assertNotNull(simulation)
|
||||
|
||||
val approve = simulation?.data as? SimulationData.Approve
|
||||
assertNotNull(approve)
|
||||
assertEquals(1, approve?.approvedAmounts?.size)
|
||||
assertEquals(BigDecimal("1000.0"), approve?.approvedAmounts?.first()?.approvedAmount)
|
||||
assertTrue(approve?.approvedAmounts?.first()?.isUnlimited == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenResponseBenignValidationAndSuccessSimulationThenReturnsSendReceiveResult() {
|
||||
val assetDiff = AssetDiff(
|
||||
assetType = "ERC20",
|
||||
asset = Asset(chainId = 1, logoUrl = "logo", symbol = "ETH"),
|
||||
inTransfer = listOf(Transfer(value = "2.0", rawValue = "0x1")),
|
||||
outTransfer = listOf(Transfer(value = "1.5", rawValue = "0x2")),
|
||||
)
|
||||
val response = TransactionScanResponse(
|
||||
validation = ValidationResponse(status = "Success", resultType = "Benign"),
|
||||
simulation = SimulationResponse(
|
||||
status = "Success",
|
||||
accountSummary = AccountSummaryResponse(exposures = emptyList(), assetsDiffs = listOf(assetDiff)),
|
||||
),
|
||||
)
|
||||
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(ValidationResult.SAFE, result.validation)
|
||||
|
||||
val simulation = result.simulation as? SimulationResult.Success
|
||||
assertNotNull(simulation)
|
||||
|
||||
val data = simulation?.data as? SimulationData.SendAndReceive
|
||||
assertNotNull(data)
|
||||
assertEquals(BigDecimal("1.5"), data?.send?.first()?.amount)
|
||||
assertEquals(BigDecimal("2.0"), data?.receive?.first()?.amount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenResponseErrorValidationThenReturnsFailedToValidate() {
|
||||
val response = TransactionScanResponse(
|
||||
validation = ValidationResponse(status = "Error", resultType = "Benign"),
|
||||
simulation = SimulationResponse(
|
||||
status = "Success",
|
||||
accountSummary = AccountSummaryResponse(emptyList(), emptyList()),
|
||||
),
|
||||
)
|
||||
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(ValidationResult.FAILED_TO_VALIDATE, result.validation)
|
||||
assertTrue(result.simulation is SimulationResult.FailedToSimulate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenResponseNotBenignThenReturnsValidationUnsafe() {
|
||||
val response = TransactionScanResponse(
|
||||
validation = ValidationResponse(status = "Success", resultType = "Phishing"),
|
||||
simulation = SimulationResponse(
|
||||
status = "Success",
|
||||
accountSummary = AccountSummaryResponse(emptyList(), emptyList()),
|
||||
),
|
||||
)
|
||||
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertEquals(ValidationResult.UNSAFE, result.validation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenResponseSimulationNotSuccessThenReturnsSimulationFailedToSimulate() {
|
||||
val response = TransactionScanResponse(
|
||||
validation = ValidationResponse(status = "Success", resultType = "Benign"),
|
||||
simulation = SimulationResponse(
|
||||
status = "Error",
|
||||
accountSummary = AccountSummaryResponse(emptyList(), emptyList()),
|
||||
),
|
||||
)
|
||||
|
||||
val result = mapper.mapToDomain(response)
|
||||
assertTrue(result.simulation is SimulationResult.FailedToSimulate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenResponseSimulationIsEmptyThenReturnsFailedToSimulate() {
|
||||
val txResponse = TransactionScanResponse(
|
||||
validation = ValidationResponse(status = "Success", resultType = "Benign"),
|
||||
simulation = SimulationResponse(
|
||||
status = "Success",
|
||||
accountSummary = AccountSummaryResponse(
|
||||
assetsDiffs = emptyList(),
|
||||
exposures = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val result = mapper.mapToDomain(txResponse)
|
||||
assertTrue(result.simulation is SimulationResult.FailedToSimulate)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package com.tangem.data.blockaid
|
||||
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.TransactionData
|
||||
import com.domain.blockaid.models.transaction.TransactionParams
|
||||
import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
|
||||
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.TransactionScanResponse
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.*
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DefaultBlockAidRepositoryTest {
|
||||
|
||||
@MockK
|
||||
private lateinit var api: BlockAidApi
|
||||
|
||||
@MockK
|
||||
private lateinit var mapper: BlockAidMapper
|
||||
|
||||
@MockK
|
||||
private lateinit var dispatcherProvider: CoroutineDispatcherProvider
|
||||
|
||||
private lateinit var repository: DefaultBlockAidRepository
|
||||
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
every { dispatcherProvider.io } returns testDispatcher
|
||||
repository = DefaultBlockAidRepository(api, dispatcherProvider, mapper)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenVerifyAppDomainThenCallsApiAndMapsResult() = runTest {
|
||||
val url = "https://example.com"
|
||||
val domainData = DAppData(url)
|
||||
val domainResponse = DomainScanResponse(status = "hit", isMalicious = false)
|
||||
val expectedResult = CheckDAppResult.SAFE
|
||||
|
||||
coEvery { api.scanDomain(DomainScanRequest(url)) } returns domainResponse
|
||||
every { mapper.mapToDomain(domainResponse) } returns expectedResult
|
||||
|
||||
val result = repository.verifyDAppDomain(domainData)
|
||||
|
||||
assertEquals(expectedResult, result)
|
||||
coVerify { api.scanDomain(DomainScanRequest(url)) }
|
||||
verify { mapper.mapToDomain(domainResponse) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenVerifyEvmTransactionThenCallsScanJsonRpcAndMaps() = runTest {
|
||||
val data = TransactionData(
|
||||
chain = "ethereum",
|
||||
accountAddress = "0xabc",
|
||||
domainUrl = "https://uniswap.org",
|
||||
method = "eth_sendTransaction",
|
||||
params = TransactionParams.Evm(params = "some-params"),
|
||||
)
|
||||
|
||||
val request = mockk<EvmTransactionScanRequest>()
|
||||
val response = mockk<TransactionScanResponse>()
|
||||
val expectedResult = mockk<CheckTransactionResult>()
|
||||
|
||||
every { mapper.mapToEvmRequest(data) } returns request
|
||||
coEvery { api.scanJsonRpc(request) } returns response
|
||||
every { mapper.mapToDomain(response) } returns expectedResult
|
||||
|
||||
val result = repository.verifyTransaction(data)
|
||||
|
||||
assertEquals(expectedResult, result)
|
||||
coVerify { api.scanJsonRpc(request) }
|
||||
verify { mapper.mapToEvmRequest(data) }
|
||||
verify { mapper.mapToDomain(response) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenVerifySolanaTransactionThenCallsScanSolanaMessageAndMapsResult() = runTest {
|
||||
val data = TransactionData(
|
||||
chain = "mainnet",
|
||||
accountAddress = "/Rd2TLl...",
|
||||
domainUrl = "https://example.com",
|
||||
method = "signTransaction",
|
||||
params = TransactionParams.Solana(transactions = listOf("TX_PAYLOAD_BASE64")),
|
||||
)
|
||||
|
||||
val request = mockk<SolanaTransactionScanRequest>()
|
||||
val response = mockk<TransactionScanResponse>()
|
||||
val expectedResult = mockk<CheckTransactionResult>()
|
||||
|
||||
every { mapper.mapToSolanaRequest(data) } returns request
|
||||
coEvery { api.scanSolanaMessage(request) } returns response
|
||||
every { mapper.mapToDomain(response) } returns expectedResult
|
||||
|
||||
val result = repository.verifyTransaction(data)
|
||||
|
||||
assertEquals(expectedResult, result)
|
||||
coVerify { api.scanSolanaMessage(request) }
|
||||
verify { mapper.mapToSolanaRequest(data) }
|
||||
verify { mapper.mapToDomain(response) }
|
||||
}
|
||||
}
|
||||
1
domain/blockaid/.gitignore
vendored
Normal file
1
domain/blockaid/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
13
domain/blockaid/build.gradle.kts
Normal file
13
domain/blockaid/build.gradle.kts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.blockaid.models)
|
||||
|
||||
/* Other */
|
||||
implementation(deps.moshi.adapters)
|
||||
}
|
||||
1
domain/blockaid/models/.gitignore
vendored
Normal file
1
domain/blockaid/models/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
10
domain/blockaid/models/build.gradle.kts
Normal file
10
domain/blockaid/models/build.gradle.kts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.ksp)
|
||||
id("configuration")
|
||||
}
|
||||
dependencies {
|
||||
/* Other */
|
||||
implementation(deps.moshi)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.domain.blockaid.models.dapp
|
||||
|
||||
/**
|
||||
* Result of BlockAid's DApp domain check
|
||||
*/
|
||||
enum class CheckDAppResult {
|
||||
|
||||
/**
|
||||
* DApp was confirmed safe
|
||||
*/
|
||||
SAFE,
|
||||
|
||||
/**
|
||||
* DApp was confirmed unsafe (known security risk)
|
||||
*/
|
||||
UNSAFE,
|
||||
|
||||
/**
|
||||
* Check wasn't performed, BlockAid cannot guarantee DApp's safety
|
||||
*/
|
||||
FAILED_TO_VERIFY,
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.domain.blockaid.models.dapp
|
||||
|
||||
/**
|
||||
* Data BlockAid needs to verify DApp domain
|
||||
*
|
||||
* @property url DApp's domain url
|
||||
*/
|
||||
@JvmInline
|
||||
value class DAppData(
|
||||
val url: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.domain.blockaid.models.transaction
|
||||
|
||||
/**
|
||||
* Result of BlockAid's transaction check
|
||||
*
|
||||
* @property validation Indicates whether the transaction is considered safe or unsafe
|
||||
* @property simulation Provides insight into the expected outcome of the transaction
|
||||
*/
|
||||
data class CheckTransactionResult(
|
||||
val validation: ValidationResult,
|
||||
val simulation: SimulationResult,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.domain.blockaid.models.transaction
|
||||
|
||||
import com.domain.blockaid.models.transaction.simultation.SimulationData
|
||||
|
||||
/**
|
||||
* Result of BlockAid's transaction simulation
|
||||
*/
|
||||
sealed class SimulationResult {
|
||||
|
||||
/**
|
||||
* Simulation was successfully performed and returned data
|
||||
*/
|
||||
data class Success(
|
||||
val data: SimulationData,
|
||||
) : SimulationResult()
|
||||
|
||||
/**
|
||||
* Simulation wasn't performed, BlockAid cannot guarantee transaction's behavior
|
||||
*/
|
||||
data object FailedToSimulate : SimulationResult()
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.domain.blockaid.models.transaction
|
||||
|
||||
/**
|
||||
* Input data for BlockAid's transaction check
|
||||
*
|
||||
* @property chain Chain name, for ex. "ethereum"
|
||||
* @property accountAddress The address of the account (wallet) received the request in hex string format
|
||||
* @property method Transaction method, for ex. "eth_signTransaction"
|
||||
* @property domainUrl Url of the DApp domain
|
||||
*/
|
||||
data class TransactionData(
|
||||
val chain: String,
|
||||
val accountAddress: String,
|
||||
val method: String,
|
||||
val domainUrl: String,
|
||||
val params: TransactionParams,
|
||||
)
|
||||
|
||||
sealed class TransactionParams {
|
||||
|
||||
/**
|
||||
* Parameters for Ethereum based transactions, can be taken from [WcSdkSessionRequest.JSONRPCRequest.params]
|
||||
*/
|
||||
data class Evm(
|
||||
val params: String,
|
||||
) : TransactionParams()
|
||||
|
||||
/**
|
||||
* Parameters for Solana transactions
|
||||
*
|
||||
* @property transactions Base64-encoded serialized list of transactions
|
||||
*/
|
||||
data class Solana(
|
||||
val transactions: List<String>,
|
||||
) : TransactionParams()
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.domain.blockaid.models.transaction
|
||||
|
||||
/**
|
||||
* Result of BlockAid's transaction validation
|
||||
*/
|
||||
enum class ValidationResult {
|
||||
|
||||
/**
|
||||
* Transaction was confirmed safe
|
||||
*/
|
||||
SAFE,
|
||||
|
||||
/**
|
||||
* Transaction was confirmed unsafe
|
||||
*/
|
||||
UNSAFE,
|
||||
|
||||
/**
|
||||
* Validation wasn't performed, BlockAid cannot guarantee transaction's safety
|
||||
*/
|
||||
FAILED_TO_VALIDATE,
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.domain.blockaid.models.transaction.simultation
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class AmountInfo(
|
||||
val amount: BigDecimal,
|
||||
val token: TokenInfo,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.domain.blockaid.models.transaction.simultation
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class ApprovedAmount(
|
||||
val approvedAmount: BigDecimal,
|
||||
val isUnlimited: Boolean,
|
||||
val tokenInfo: TokenInfo,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.domain.blockaid.models.transaction.simultation
|
||||
|
||||
/**
|
||||
* Result of a successful transaction simulation.
|
||||
*/
|
||||
sealed class SimulationData {
|
||||
|
||||
/**
|
||||
* Represents a swap/send/sell operations with specified send and receive amounts (can be multiple amounts for NFT)
|
||||
*/
|
||||
data class SendAndReceive(
|
||||
val send: List<AmountInfo>,
|
||||
val receive: List<AmountInfo>,
|
||||
) : SimulationData()
|
||||
|
||||
/**
|
||||
* Represents an approve operation with the specified amount (can be multiple amounts for NFT)
|
||||
*/
|
||||
data class Approve(
|
||||
val approvedAmounts: List<ApprovedAmount>,
|
||||
) : SimulationData()
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.domain.blockaid.models.transaction.simultation
|
||||
|
||||
data class TokenInfo(
|
||||
val chainId: Int?,
|
||||
val logoUrl: String?,
|
||||
val symbol: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.blockaid
|
||||
|
||||
import arrow.core.Either
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.domain.blockaid.models.transaction.CheckTransactionResult
|
||||
import com.domain.blockaid.models.transaction.TransactionData
|
||||
|
||||
/**
|
||||
* Verifies the safety of DApps and WalletConnect transactions
|
||||
*/
|
||||
interface BlockAidVerifier {
|
||||
|
||||
/**
|
||||
* Checks if a DApp is safe to use
|
||||
*/
|
||||
suspend fun verifyDApp(data: DAppData): Either<Throwable, CheckDAppResult>
|
||||
|
||||
/**
|
||||
* Checks the safety of a WalletConnect transaction and provides a simulation result
|
||||
*/
|
||||
suspend fun verifyTransaction(data: TransactionData): Either<Throwable, CheckTransactionResult>
|
||||
}
|
||||
|
|
@ -289,6 +289,8 @@ include(":domain:nft")
|
|||
include(":domain:nft:models")
|
||||
include(":domain:networks")
|
||||
include(":domain:quotes")
|
||||
include(":domain:blockaid")
|
||||
include(":domain:blockaid:models")
|
||||
// endregion Domain modules
|
||||
|
||||
// region Data modules
|
||||
|
|
@ -316,4 +318,5 @@ include(":data:networks")
|
|||
include(":data:nft")
|
||||
include(":data:onramp")
|
||||
include(":data:quotes")
|
||||
include(":data:blockaid")
|
||||
// endregion Data modules
|
||||
Loading…
Add table
Add a link
Reference in a new issue