Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-06 13:15:40 +03:00
parent 7c92156d19
commit 680794d447
12 changed files with 357 additions and 5 deletions

View file

@ -4,11 +4,16 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.transaction.convertes.GaslessSignedTransactionResultConverter
import com.tangem.data.transaction.convertes.GaslessTransactionRequestBuilder
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.gasless.GaslessTxServiceApi
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.transaction.GaslessTransactionRepository
import com.tangem.domain.transaction.models.Eip7702Authorization
import com.tangem.domain.transaction.models.GaslessSignedTransactionResult
import com.tangem.domain.transaction.models.GaslessTransactionData
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@ -22,6 +27,8 @@ class DefaultGaslessTransactionRepository(
) : GaslessTransactionRepository {
private val supportedTokensState = MutableStateFlow<Set<CryptoCurrency>?>(null)
private val gaslessTransactionRequestBuilder = GaslessTransactionRequestBuilder()
private val signedTransactionResultConverter = GaslessSignedTransactionResultConverter()
override fun isNetworkSupported(network: Network): Boolean {
val blockchain = Blockchain.fromNetworkId(network.backendId) ?: return false
@ -63,6 +70,34 @@ class DefaultGaslessTransactionRepository(
return TOKEN_RECEIVER_ADDRESS
}
override suspend fun sendGaslessTransaction(
gaslessTransactionData: GaslessTransactionData,
signature: String,
userAddress: String,
network: Network,
eip7702Auth: Eip7702Authorization?,
): GaslessSignedTransactionResult = withContext(coroutineDispatcherProvider.io) {
val blockchain = Blockchain.fromNetworkId(network.backendId)
?: error("Cannot determine blockchain for network id: ${network.backendId}")
val transactionRequest = gaslessTransactionRequestBuilder.build(
gaslessTransaction = gaslessTransactionData,
signature = signature,
userAddress = userAddress,
chainId = blockchain.getChainId() ?: error("ChainId is null for blockchain: $blockchain"),
eip7702Auth = eip7702Auth,
)
val response = gaslessTxServiceApi.signGaslessTransaction(transactionRequest).getOrThrow()
if (!response.isSuccess) {
error("Gasless service returned unsuccessful response")
}
// Convert DTO to domain model
signedTransactionResultConverter.convert(response.result)
}
override fun getBaseGasForTransaction(): BigInteger {
return BASE_GAS_FOR_TRANSACTION
}

View file

@ -0,0 +1,22 @@
package com.tangem.data.transaction.convertes
import com.tangem.datasource.api.gasless.models.GaslessSignedTransactionResultDTO as GaslessSignedTransactionResultDTO
import com.tangem.domain.transaction.models.GaslessSignedTransactionResult
import com.tangem.utils.converter.Converter
/**
* Converts DTO GaslessSignedTransactionResult from API to domain model.
* Transforms string representations of gas parameters to BigInteger for type safety.
*/
class GaslessSignedTransactionResultConverter :
Converter<GaslessSignedTransactionResultDTO, GaslessSignedTransactionResult> {
override fun convert(value: GaslessSignedTransactionResultDTO): GaslessSignedTransactionResult {
return GaslessSignedTransactionResult(
signedTransaction = value.signedTransaction,
gasLimit = value.gasLimit.toBigInteger(),
maxFeePerGas = value.maxFeePerGas.toBigInteger(),
maxPriorityFeePerGas = value.maxPriorityFeePerGas.toBigInteger(),
)
}
}

View file

@ -0,0 +1,55 @@
package com.tangem.data.transaction.convertes
import com.tangem.datasource.api.gasless.models.GaslessTransactionRequest
import com.tangem.domain.transaction.models.Eip7702Authorization
import com.tangem.domain.transaction.models.GaslessTransactionData
import com.tangem.datasource.api.gasless.models.Eip7702AuthorizationDTO
/**
* Builder for creating complete GaslessTransactionRequest from domain model.
* Combines transaction data with signature and user information.
*/
class GaslessTransactionRequestBuilder(
private val converter: GaslessTxDataToGaslessRequestConverter = GaslessTxDataToGaslessRequestConverter(),
) {
/**
* Creates complete gasless transaction request.
*
* @param gaslessTransaction domain model of transaction
* @param signature transaction signature in hex format (with 0x prefix)
* @param userAddress user's Ethereum address
* @param chainId blockchain network chain ID
* @param eip7702Auth optional EIP-7702 authorization for account abstraction
* @return complete request ready for API submission
*/
fun build(
gaslessTransaction: GaslessTransactionData,
signature: String,
userAddress: String,
chainId: Int,
eip7702Auth: Eip7702Authorization? = null,
): GaslessTransactionRequest {
return GaslessTransactionRequest(
gaslessTransaction = converter.convert(gaslessTransaction),
signature = signature,
userAddress = userAddress,
chainId = chainId,
eip7702Auth = eip7702Auth?.toDTO(),
)
}
/**
* Converts domain Eip7702Authorization to DTO.
*/
private fun Eip7702Authorization.toDTO(): Eip7702AuthorizationDTO {
return Eip7702AuthorizationDTO(
chainId = chainId,
address = address,
nonce = nonce.toString(),
yParity = yParity,
r = r,
s = s,
)
}
}

View file

@ -0,0 +1,43 @@
package com.tangem.data.transaction.convertes
import com.tangem.datasource.api.gasless.models.FeeData
import com.tangem.datasource.api.gasless.models.TransactionData
import com.tangem.domain.transaction.models.GaslessTransactionData
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.toHexString
import com.tangem.datasource.api.gasless.models.GaslessTransactionData as GaslessTransactionDataDTO
/**
* Converts domain GaslessTransactionData to DTO for API requests.
* Note: This converter only handles the transaction data conversion.
* Additional fields (signature, userAddress, chainId) must be added separately
* to create complete GaslessTransactionRequest.
*/
class GaslessTxDataToGaslessRequestConverter : Converter<GaslessTransactionData, GaslessTransactionDataDTO> {
override fun convert(value: GaslessTransactionData): GaslessTransactionDataDTO {
return GaslessTransactionDataDTO(
transaction = convertTransaction(value.transaction),
fee = convertFee(value.fee),
nonce = value.nonce.toString(),
)
}
private fun convertTransaction(transaction: com.tangem.domain.transaction.models.Transaction): TransactionData {
return TransactionData(
to = transaction.to,
value = transaction.value.toString(),
data = transaction.data.toHexString(),
)
}
private fun convertFee(fee: com.tangem.domain.transaction.models.Fee): FeeData {
return FeeData(
feeToken = fee.feeToken,
maxTokenFee = fee.maxTokenFee.toString(),
coinPriceInToken = fee.coinPriceInToken.toString(),
feeTransferGasLimit = fee.feeTransferGasLimit.toString(),
baseGas = fee.baseGas.toString(),
)
}
}