Updated on 2026-08-14
This commit is contained in:
parent
d68ad52a45
commit
13b1bbc486
10 changed files with 154 additions and 23 deletions
|
|
@ -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<GaslessServiceResponse<GaslessSupportedTokens>>
|
||||
|
||||
@POST("api/v1/sign")
|
||||
suspend fun signGaslessTransaction(
|
||||
@Body transaction: GaslessTransactionRequest,
|
||||
): ApiResponse<GaslessServiceResponse<GaslessSignedTransactionResult>>
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<Set<CryptoCurrency>?>(null)
|
||||
|
||||
override fun isNetworkSupported(network: Network): Boolean {
|
||||
|
|
@ -27,16 +28,27 @@ class DefaultGaslessTransactionRepository(
|
|||
return SUPPORTED_BLOCKCHAINS.contains(blockchain)
|
||||
}
|
||||
|
||||
override suspend fun getSupportedTokens(): Set<CryptoCurrency> {
|
||||
override suspend fun getSupportedTokens(network: Network): Set<CryptoCurrency> {
|
||||
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 }
|
||||
|
|
|
|||
|
|
@ -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<GaslessTokenDTO, CryptoCurrency> {
|
||||
|
||||
override fun convert(value: GaslessTokenDTO): CryptoCurrency {
|
||||
TODO("implement conversion logic here")
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ interface GaslessTransactionRepository {
|
|||
|
||||
fun isNetworkSupported(network: Network): Boolean
|
||||
|
||||
suspend fun getSupportedTokens(): Set<CryptoCurrency>
|
||||
suspend fun getSupportedTokens(network: Network): Set<CryptoCurrency>
|
||||
|
||||
fun getTokenFeeReceiverAddress(): String
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CryptoCurrencyStatus>,
|
||||
): List<CryptoCurrencyStatus> {
|
||||
val supportedGaslessTokens = gaslessTransactionRepository.getSupportedTokens()
|
||||
val supportedGaslessTokens = gaslessTransactionRepository.getSupportedTokens(network)
|
||||
return userCurrenciesStatuses
|
||||
.asSequence()
|
||||
.filter { currencyStatus ->
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue