Updated on 2026-08-14
This commit is contained in:
parent
7c92156d19
commit
680794d447
12 changed files with 357 additions and 5 deletions
|
|
@ -2,6 +2,9 @@ package com.tangem.domain.transaction
|
|||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.transaction.models.Eip7702Authorization
|
||||
import com.tangem.domain.transaction.models.GaslessSignedTransactionResult
|
||||
import com.tangem.domain.transaction.models.GaslessTransactionData
|
||||
import java.math.BigInteger
|
||||
|
||||
interface GaslessTransactionRepository {
|
||||
|
|
@ -12,6 +15,50 @@ interface GaslessTransactionRepository {
|
|||
|
||||
fun getTokenFeeReceiverAddress(): String
|
||||
|
||||
/**
|
||||
* Sends gasless transaction to the gasless service for signing and returns the signed result.
|
||||
*
|
||||
* Flow:
|
||||
* 1. User signs the gasless transaction data locally with their private key
|
||||
* 2. This method submits the transaction + user signature to the gasless service
|
||||
* 3. Service validates the signature and transaction data
|
||||
* 4. Service adds its own signature for fee delegation (pays gas in tokens)
|
||||
* 5. Service constructs the final EIP-1559 transaction with all signatures
|
||||
* 6. Service returns the fully signed transaction ready for broadcasting
|
||||
*
|
||||
* The gasless service acts as a relayer that:
|
||||
* - Pays network gas fees on behalf of the user
|
||||
* - Receives payment in the specified token from the user's wallet
|
||||
* - Ensures atomic execution (either both transfers succeed or both fail)
|
||||
*
|
||||
* @param gaslessTransactionData domain model containing:
|
||||
* - transaction: target contract call data (to, value, data)
|
||||
* - fee: token payment configuration (feeToken, maxTokenFee, etc.)
|
||||
* - nonce: user's contract nonce to prevent replay attacks
|
||||
* @param signature user's ECDSA signature of the gasless transaction in hex format (0x...)
|
||||
* Signs keccak256 hash of the transaction data
|
||||
* @param userAddress user's Ethereum address (EOA or contract wallet)
|
||||
* @param network blockchain network (Ethereum, Polygon, BSC, etc.)
|
||||
* Used to determine chainId for the request
|
||||
* @param eip7702Auth optional EIP-7702 authorization for EOA delegation to smart contract
|
||||
* Required only when user's EOA needs to temporarily act as contract wallet
|
||||
* Contains signature authorizing delegation to entry point contract
|
||||
* @return [GaslessSignedTransactionResult] containing:
|
||||
* - signedTransaction: complete RLP-encoded transaction ready to broadcast
|
||||
* - gasLimit: actual gas limit allocated by the service
|
||||
* - maxFeePerGas: maximum fee per gas (base + priority) in wei
|
||||
* - maxPriorityFeePerGas: tip for validators in wei (EIP-1559)
|
||||
* @throws IllegalStateException if network is not supported or chainId cannot be determined
|
||||
* @throws Exception if service returns error or network request fails
|
||||
*/
|
||||
suspend fun sendGaslessTransaction(
|
||||
gaslessTransactionData: GaslessTransactionData,
|
||||
signature: String,
|
||||
userAddress: String,
|
||||
network: Network,
|
||||
eip7702Auth: Eip7702Authorization? = null,
|
||||
): GaslessSignedTransactionResult
|
||||
|
||||
/**
|
||||
* Hardcoded value as baseGas
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.domain.transaction.models
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
/**
|
||||
* EIP-7702 authorization for account abstraction.
|
||||
* Used for delegating EOA (Externally Owned Account) control to a smart contract.
|
||||
*
|
||||
* EIP-7702 allows EOAs to temporarily act as smart contract wallets by delegating
|
||||
* their authority to a contract address for a specific transaction.
|
||||
*
|
||||
* @property chainId blockchain network chain ID
|
||||
* @property address contract address to delegate authority to (entry point contract)
|
||||
* @property nonce authorization nonce to prevent replay attacks
|
||||
* @property yParity recovery ID for signature (0 or 1)
|
||||
* @property r ECDSA signature component R
|
||||
* @property s ECDSA signature component S
|
||||
*
|
||||
* @see <a href="https://eips.ethereum.org/EIPS/eip-7702">EIP-7702 Specification</a>
|
||||
*/
|
||||
data class Eip7702Authorization(
|
||||
val chainId: Int,
|
||||
val address: String,
|
||||
val nonce: BigInteger,
|
||||
val yParity: Int,
|
||||
val r: String,
|
||||
val s: String,
|
||||
) {
|
||||
init {
|
||||
require(chainId > 0) { "Chain ID must be positive" }
|
||||
require(address.isNotBlank()) { "Address must not be blank" }
|
||||
require(yParity in 0..1) { "yParity must be 0 or 1" }
|
||||
require(r.isNotBlank()) { "Signature component R must not be blank" }
|
||||
require(s.isNotBlank()) { "Signature component S must not be blank" }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.transaction.models
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
/**
|
||||
* Result of gasless transaction signing from the gasless service.
|
||||
* Contains the fully signed transaction ready for broadcasting and gas parameters.
|
||||
*
|
||||
* After the user signs the gasless transaction data locally, the service:
|
||||
* 1. Validates the signature
|
||||
* 2. Adds its own signature for fee delegation
|
||||
* 3. Constructs the final transaction
|
||||
* 4. Returns this signed transaction with gas parameters
|
||||
*
|
||||
* @property signedTransaction complete signed transaction in RLP-encoded hex format (0x...)
|
||||
* ready to be broadcast to the blockchain network
|
||||
* @property gasLimit maximum amount of gas units allocated for the transaction
|
||||
* @property maxFeePerGas maximum total fee per gas unit (base fee + priority fee) in wei
|
||||
* @property maxPriorityFeePerGas maximum priority fee (tip) per gas unit in wei for EIP-1559
|
||||
*/
|
||||
data class GaslessSignedTransactionResult(
|
||||
val signedTransaction: String,
|
||||
val gasLimit: BigInteger,
|
||||
val maxFeePerGas: BigInteger,
|
||||
val maxPriorityFeePerGas: BigInteger,
|
||||
) {
|
||||
init {
|
||||
require(signedTransaction.isNotBlank()) { "Signed transaction must not be blank" }
|
||||
require(signedTransaction.startsWith("0x")) { "Signed transaction must be in hex format with 0x prefix" }
|
||||
require(gasLimit > BigInteger.ZERO) { "Gas limit must be positive" }
|
||||
require(maxFeePerGas > BigInteger.ZERO) { "Max fee per gas must be positive" }
|
||||
require(maxPriorityFeePerGas >= BigInteger.ZERO) { "Max priority fee per gas must be non-negative" }
|
||||
require(maxPriorityFeePerGas <= maxFeePerGas) {
|
||||
"Max priority fee per gas cannot exceed max fee per gas"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.domain.transaction.models
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
/**
|
||||
* Domain model for gasless transaction.
|
||||
* Represents complete transaction data with fee delegation metadata.
|
||||
*/
|
||||
data class GaslessTransactionData(
|
||||
/** Transaction details */
|
||||
val transaction: Transaction,
|
||||
/** Fee payment configuration */
|
||||
val fee: Fee,
|
||||
/** Nonce from user's contract */
|
||||
val nonce: BigInteger,
|
||||
)
|
||||
|
||||
/**
|
||||
* Core transaction data.
|
||||
*
|
||||
* @property to destination address
|
||||
* @property value transaction value in wei (currently always 0 for gasless)
|
||||
* @property data encoded transaction data (contract call)
|
||||
*/
|
||||
data class Transaction(
|
||||
val to: String,
|
||||
val value: BigInteger,
|
||||
val data: ByteArray,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Transaction
|
||||
|
||||
if (to != other.to) return false
|
||||
if (value != other.value) return false
|
||||
if (!data.contentEquals(other.data)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = to.hashCode()
|
||||
result = 31 * result + value.hashCode()
|
||||
result = 31 * result + data.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fee configuration for gasless transaction.
|
||||
*
|
||||
* @property feeToken token address used for fee payment
|
||||
* @property maxTokenFee maximum fee amount in fee token units
|
||||
* @property coinPriceInToken price of native coin in fee token units
|
||||
* @property feeTransferGasLimit gas limit for fee token transfer
|
||||
* @property baseGas base gas cost (currently constant BASE_GAS, may vary in future)
|
||||
*/
|
||||
data class Fee(
|
||||
val feeToken: String,
|
||||
val maxTokenFee: BigInteger,
|
||||
val coinPriceInToken: BigInteger,
|
||||
val feeTransferGasLimit: BigInteger,
|
||||
val baseGas: BigInteger,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.transaction.usecase.gasless
|
||||
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.domain.transaction.models.TransactionFeeExtended
|
||||
|
||||
class CreateAndSendGaslessTransactionUseCase {
|
||||
|
||||
operator fun invoke(transactionData: TransactionData, fee: TransactionFeeExtended) {
|
||||
// Implementation goes here
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue