From 13b1bbc486d269d5b803b1cf8862b9b39b10e552 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Jan 2026 17:52:37 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../api/gasless/GaslessTxServiceApi.kt | 9 ++ .../models/GaslessSignedTransactionResult.kt | 23 +++++ .../models/GaslessTransactionRequest.kt | 93 +++++++++++++++++++ .../ResponseCryptoCurrenciesFactory.kt | 2 +- data/transaction/build.gradle.kts | 3 + .../DefaultGaslessTransactionRepository.kt | 24 +++-- ...aslessTokenDtoToCryptoCurrencyConverter.kt | 12 --- .../GaslessTransactionRepository.kt | 2 +- .../gasless/GetAvailableFeeTokensUseCase.kt | 5 +- .../gasless/GetFeeForGaslessUseCase.kt | 4 +- 10 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResult.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt delete mode 100644 data/transaction/src/main/java/com/tangem/data/transaction/converters/GaslessTokenDtoToCryptoCurrencyConverter.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt index 0413f4ac56..fcb9fe8c20 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt @@ -2,11 +2,20 @@ package com.tangem.datasource.api.gasless import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.gasless.models.GaslessServiceResponse +import com.tangem.datasource.api.gasless.models.GaslessSignedTransactionResult import com.tangem.datasource.api.gasless.models.GaslessSupportedTokens +import com.tangem.datasource.api.gasless.models.GaslessTransactionRequest +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST interface GaslessTxServiceApi { @GET("api/v1/tokens") suspend fun getSupportedTokens(): ApiResponse> + + @POST("api/v1/sign") + suspend fun signGaslessTransaction( + @Body transaction: GaslessTransactionRequest, + ): ApiResponse> } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResult.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResult.kt new file mode 100644 index 0000000000..0a2fc092de --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResult.kt @@ -0,0 +1,23 @@ +package com.tangem.datasource.api.gasless.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Result of gasless transaction signing. + * Contains the signed transaction data and gas parameters. + */ +@JsonClass(generateAdapter = true) +data class GaslessSignedTransactionResult( + @Json(name = "signedTransaction") + val signedTransaction: String, + + @Json(name = "gasLimit") + val gasLimit: String, + + @Json(name = "maxFeePerGas") + val maxFeePerGas: String, + + @Json(name = "maxPriorityFeePerGas") + val maxPriorityFeePerGas: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt new file mode 100644 index 0000000000..f5f40bd9df --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt @@ -0,0 +1,93 @@ +package com.tangem.datasource.api.gasless.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Request body for gasless transaction submission. + * Represents complete transaction with fee delegation metadata. + */ +@JsonClass(generateAdapter = true) +data class GaslessTransactionRequest( + @Json(name = "gaslessTransaction") + val gaslessTransaction: GaslessTransactionData, + + @Json(name = "signature") + val signature: String, + + @Json(name = "userAddress") + val userAddress: String, + + @Json(name = "chainId") + val chainId: Int, + + @Json(name = "eip7702auth") + val eip7702Auth: Eip7702Authorization? = null, +) + +@JsonClass(generateAdapter = true) +data class GaslessTransactionData( + @Json(name = "transaction") + val transaction: TransactionData, + + @Json(name = "fee") + val fee: FeeData, + + @Json(name = "nonce") + val nonce: String, +) + +@JsonClass(generateAdapter = true) +data class TransactionData( + @Json(name = "to") + val to: String, + + @Json(name = "value") + val value: String, + + @Json(name = "data") + val data: String, +) + +@JsonClass(generateAdapter = true) +data class FeeData( + @Json(name = "feeToken") + val feeToken: String, + + @Json(name = "maxTokenFee") + val maxTokenFee: String, + + @Json(name = "coinPriceInToken") + val coinPriceInToken: String, + + @Json(name = "feeTransferGasLimit") + val feeTransferGasLimit: String, + + @Json(name = "baseGas") + val baseGas: String, +) + +/** + * EIP-7702 authorization for account abstraction. + * Optional field, used only when EOA delegation is required. + */ +@JsonClass(generateAdapter = true) +data class Eip7702Authorization( + @Json(name = "chainId") + val chainId: Int, + + @Json(name = "address") + val address: String, + + @Json(name = "nonce") + val nonce: String, + + @Json(name = "yParity") + val yParity: Int, + + @Json(name = "r") + val r: String, + + @Json(name = "s") + val s: String, +) \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt index eccf6e4952..06bc2f1e85 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt @@ -127,7 +127,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor( } } - private fun createToken(blockchain: Blockchain, sdkToken: Token, network: Network): CryptoCurrency.Token { + fun createToken(blockchain: Blockchain, sdkToken: Token, network: Network): CryptoCurrency.Token { val id = getTokenId(network, sdkToken) return CryptoCurrency.Token( diff --git a/data/transaction/build.gradle.kts b/data/transaction/build.gradle.kts index fa74ec6c43..cb26912c17 100644 --- a/data/transaction/build.gradle.kts +++ b/data/transaction/build.gradle.kts @@ -24,6 +24,9 @@ dependencies { implementation(projects.core.datasource) implementation(projects.core.utils) + /** Common */ + implementation(projects.data.common) + /** Domain */ implementation(projects.libs.blockchainSdk) implementation(projects.domain.legacy) diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt index ae2209f795..19822060a2 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt @@ -1,8 +1,9 @@ package com.tangem.data.transaction import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.data.transaction.converters.GaslessTokenDtoToCryptoCurrencyConverter +import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.gasless.GaslessTxServiceApi import com.tangem.domain.models.currency.CryptoCurrency @@ -17,9 +18,9 @@ import java.math.BigInteger class DefaultGaslessTransactionRepository( private val gaslessTxServiceApi: GaslessTxServiceApi, private val coroutineDispatcherProvider: CoroutineDispatcherProvider, + private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory, ) : GaslessTransactionRepository { - private val gaslessTokenDtoToCryptoCurrency = GaslessTokenDtoToCryptoCurrencyConverter() private val supportedTokensState = MutableStateFlow?>(null) override fun isNetworkSupported(network: Network): Boolean { @@ -27,16 +28,27 @@ class DefaultGaslessTransactionRepository( return SUPPORTED_BLOCKCHAINS.contains(blockchain) } - override suspend fun getSupportedTokens(): Set { + override suspend fun getSupportedTokens(network: Network): Set { return withContext(coroutineDispatcherProvider.io) { val storedTokens = supportedTokensState.value - if (storedTokens != null) { + if (storedTokens != null && storedTokens.isNotEmpty()) { return@withContext storedTokens } + val supportedTokensData = gaslessTxServiceApi.getSupportedTokens().getOrThrow() if (supportedTokensData.isSuccess) { - val supportedTokens = supportedTokensData.result.tokens.map { - gaslessTokenDtoToCryptoCurrency.convert(it) + val supportedTokens = supportedTokensData.result.tokens.mapNotNull { token -> + val blockchain = Blockchain.fromChainId(token.chainId) ?: return@mapNotNull null + responseCryptoCurrenciesFactory.createToken( + blockchain = blockchain, + sdkToken = Token( + contractAddress = token.tokenAddress, + name = token.tokenName, + symbol = token.tokenSymbol, + decimals = token.decimals, + ), + network = network, + ) }.toSet() // update local cache supportedTokensState.update { supportedTokens } diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/converters/GaslessTokenDtoToCryptoCurrencyConverter.kt b/data/transaction/src/main/java/com/tangem/data/transaction/converters/GaslessTokenDtoToCryptoCurrencyConverter.kt deleted file mode 100644 index e6bff10c5f..0000000000 --- a/data/transaction/src/main/java/com/tangem/data/transaction/converters/GaslessTokenDtoToCryptoCurrencyConverter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.data.transaction.converters - -import com.tangem.datasource.api.gasless.models.GaslessTokenDTO -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.utils.converter.Converter - -internal class GaslessTokenDtoToCryptoCurrencyConverter : Converter { - - override fun convert(value: GaslessTokenDTO): CryptoCurrency { - TODO("implement conversion logic here") - } -} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/GaslessTransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/GaslessTransactionRepository.kt index 2f066e08f2..728a81f317 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/GaslessTransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/GaslessTransactionRepository.kt @@ -8,7 +8,7 @@ interface GaslessTransactionRepository { fun isNetworkSupported(network: Network): Boolean - suspend fun getSupportedTokens(): Set + suspend fun getSupportedTokens(network: Network): Set fun getTokenFeeReceiverAddress(): String diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetAvailableFeeTokensUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetAvailableFeeTokensUseCase.kt index c170b631c8..1515c315e8 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetAvailableFeeTokensUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetAvailableFeeTokensUseCase.kt @@ -44,7 +44,7 @@ class GetAvailableFeeTokensUseCase( return@either listOf(nativeCurrencyStatus) } - val gaslessTokens = getGaslessTokens(userCurrenciesStatuses) + val gaslessTokens = getGaslessTokens(network, userCurrenciesStatuses) buildList { add(nativeCurrencyStatus) addAll(gaslessTokens) @@ -73,9 +73,10 @@ class GetAvailableFeeTokensUseCase( } private suspend fun getGaslessTokens( + network: Network, userCurrenciesStatuses: List, ): List { - val supportedGaslessTokens = gaslessTransactionRepository.getSupportedTokens() + val supportedGaslessTokens = gaslessTransactionRepository.getSupportedTokens(network) return userCurrenciesStatuses .asSequence() .filter { currencyStatus -> diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt index cb48fefecd..84be8c0fde 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/GetFeeForGaslessUseCase.kt @@ -149,7 +149,9 @@ class GetFeeForGaslessUseCase( error = "only Fee.Ethereum supported, but was ${initialTxFee.normal::class.qualifiedName}", ) - val supportedGaslessTokens = gaslessTransactionRepository.getSupportedTokens() + val supportedGaslessTokens = gaslessTransactionRepository.getSupportedTokens( + network = nativeCurrencyStatus.currency.network, + ) val supportedGaslessTokensStatusesSortedByBalanceDesc = networkCurrenciesStatuses .filterNot { it.value.amount == BigDecimal.ZERO || it.currency !is CryptoCurrency.Token } .sortedByDescending { it.value.amount }