Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3daa9ef
add gasless apis
Tougee Mar 26, 2026
0bd9f45
basic eth gasless process
Tougee Mar 27, 2026
4982e21
fix typo
Tougee Mar 31, 2026
baec0f2
Merge remote-tracking branch 'origin/master' into feat/gasless
SeniorZhai Mar 31, 2026
a2ff3a8
Merge remote-tracking branch 'origin/master' into feat/gasless
SeniorZhai Mar 31, 2026
980126c
Add gasless fee token flow for web3 transfers
SeniorZhai Mar 31, 2026
c0bd757
Merge remote-tracking branch 'origin/master' into feat/gasless
SeniorZhai Apr 1, 2026
595b9ae
Add preview flow for gasless web3 transfers
SeniorZhai Apr 1, 2026
32ba11c
Merge remote-tracking branch 'origin/master' into feat/gasless
SeniorZhai Apr 1, 2026
89a7eb0
Refine gasless transfer signing flow
SeniorZhai Apr 1, 2026
6e4a0be
Merge remote-tracking branch 'origin/master' into feat/gasless
SeniorZhai Apr 2, 2026
e0c7948
Handle native fee selection outside gasless fees API
SeniorZhai Apr 2, 2026
d96954d
Merge remote-tracking branch 'origin/master' into feat/gasless
SeniorZhai Apr 7, 2026
4534987
Refine web3 fee flow selection
SeniorZhai Apr 8, 2026
7deac03
Format network fee display
SeniorZhai Apr 8, 2026
2fff6f6
Add pending tracking for gasless EVM transfers
SeniorZhai Apr 8, 2026
ca447b6
Use sponsor transaction id for initial gasless pending state
SeniorZhai Apr 8, 2026
3912605
Store Solana gasless transfers as local pending transactions
SeniorZhai Apr 8, 2026
59f43c1
Remove web3 gasless chain restrictions
SeniorZhai Apr 9, 2026
2c941c7
Merge branch 'master' into feat/gasless
crossle Apr 9, 2026
1d5e729
Add gasless swap transfer flow
SeniorZhai Apr 9, 2026
a017590
Add fee loading state for swap transfer
SeniorZhai Apr 10, 2026
ad3b1d1
feat: remove gas check
SeniorZhai Apr 10, 2026
6002c4e
fix(web3): improve gasless fee handling in trade flows
SeniorZhai Apr 10, 2026
4ee1864
Merge branch 'master' into feat/gasless
crossle Apr 10, 2026
12ce728
Fix web3 full-balance gas fallback
SeniorZhai Apr 10, 2026
cd17fa9
Resolve merge conflict in Web3ViewModel
SeniorZhai Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package one.mixin.android.api.request.web3

import com.google.gson.annotations.SerializedName

data class GaslessFeeRequest(
val from: String,
val to: String,
@SerializedName("asset_id")
val assetId: String,
@SerializedName("chain_id")
val chainId: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package one.mixin.android.api.request.web3

import com.google.gson.annotations.SerializedName

data class GaslessTxRequest(
val from: String,
val to: String,
@SerializedName("asset_id")
val assetId: String,
val amount: String,
@SerializedName("fee_asset_id")
val feeAssetId: String,
@SerializedName("chain_id")
val chainId: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package one.mixin.android.api.request.web3

import com.google.gson.JsonElement
import com.google.gson.annotations.SerializedName

data class SubmitGaslessTxRequest(
@SerializedName("chain_id")
val chainId: String,
val payload: JsonElement,
@SerializedName("user_op_signature")
val userOpSignature: String,
@SerializedName("eip7702_auth_signature")
val eip7702AuthSignature: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package one.mixin.android.api.response.web3

import com.google.gson.annotations.SerializedName

data class GaslessFeeResponse(
val fees: List<GaslessFeeEstimate>,
)

data class GaslessFeeEstimate(
@SerializedName("asset_id")
val assetId: String,
val amount: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package one.mixin.android.api.response.web3

import com.google.gson.annotations.SerializedName
import one.mixin.android.db.web3.vo.TransactionStatus

data class GaslessSponsorTransactionResponse(
@SerializedName("sponsor_tx_id")
val sponsorTxId: String,
@SerializedName("chain_id")
val chainId: String,
val account: String,
@SerializedName("web3_chain_id")
val web3ChainId: Int,
val state: String,
@SerializedName("broadcast_tx_hash")
val broadcastTxHash: String? = null,
val reason: String? = null,
@SerializedName("created_at")
val createdAt: String,
@SerializedName("updated_at")
val updatedAt: String,
)

fun GaslessSponsorTransactionResponse.hasBroadcastTxHash(): Boolean =
!broadcastTxHash.isNullOrBlank()

fun GaslessSponsorTransactionResponse.toPendingStatusOrNull(): String? {
if (hasBroadcastTxHash()) return TransactionStatus.PENDING.value
if (!reason.isNullOrBlank()) return TransactionStatus.FAILED.value
return when (state.lowercase()) {
"failed", "rejected", "cancelled", "canceled", "expired" -> TransactionStatus.FAILED.value
else -> null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package one.mixin.android.api.response.web3

import com.google.gson.JsonElement
import com.google.gson.annotations.SerializedName

data class GaslessTxResponse(
@SerializedName("chain_id")
val chainId: String,
val payload: JsonElement,
)

data class EthGaslessTxPayload(
val userOperation: UserOperationJson,
val signing: EthGaslessSignRequests,
)

data class UserOperationJson(
val sender: String,
val nonce: String,
val initCode: String,
val callData: String,
val callGasLimit: String,
val verificationGasLimit: String,
val preVerificationGas: String,
val maxFeePerGas: String,
val maxPriorityFeePerGas: String,
val paymasterAndData: String,
val signature: String,
)

data class EthGaslessSignRequests(
val userOperation: UserOpSignRequest,
val eip7702Auth: EIP7702SignRequest? = null,
)

data class UserOpSignRequest(
val signType: String,
val message: String,
)

data class EIP7702SignRequest(
val required: Boolean,
val signType: String,
val message: String,
@SerializedName("chainId")
val chainId: String,
val address: String,
val nonce: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package one.mixin.android.api.response.web3

import com.google.gson.annotations.SerializedName

data class SubmitGaslessTxResponse(
@SerializedName("sponsor_tx_id")
val sponsorTxId: String,
)
27 changes: 27 additions & 0 deletions app/src/main/java/one/mixin/android/api/service/RouteService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import one.mixin.android.api.request.RouteTickerRequest
import one.mixin.android.api.request.RouteTokenRequest
import one.mixin.android.api.request.web3.EstimateFeeRequest
import one.mixin.android.api.request.web3.EstimateFeeResponse
import one.mixin.android.api.request.web3.GaslessFeeRequest
import one.mixin.android.api.request.web3.GaslessTxRequest
import one.mixin.android.api.request.web3.RpcRequest
import one.mixin.android.api.request.web3.StakeRequest
import one.mixin.android.api.request.web3.SubmitGaslessTxRequest
import one.mixin.android.api.request.web3.SwapRequest
import one.mixin.android.api.request.web3.WalletRequest
import one.mixin.android.api.request.web3.Web3RawTransactionRequest
Expand All @@ -24,12 +27,16 @@ import one.mixin.android.api.response.RouteTickerResponse
import one.mixin.android.api.response.UserAddressView
import one.mixin.android.api.response.web3.ParsedTx
import one.mixin.android.api.response.web3.QuoteResult
import one.mixin.android.api.response.web3.GaslessFeeResponse
import one.mixin.android.api.response.web3.GaslessSponsorTransactionResponse
import one.mixin.android.api.response.web3.StakeAccount
import one.mixin.android.api.response.web3.StakeAccountActivation
import one.mixin.android.api.response.web3.StakeResponse
import one.mixin.android.api.response.web3.SubmitGaslessTxResponse
import one.mixin.android.api.response.web3.SwapResponse
import one.mixin.android.api.response.web3.SwapToken
import one.mixin.android.api.response.web3.Tx
import one.mixin.android.api.response.web3.GaslessTxResponse
import one.mixin.android.api.response.web3.Validator
import one.mixin.android.api.response.web3.WalletOutput
import one.mixin.android.db.web3.vo.Web3Address
Expand Down Expand Up @@ -271,6 +278,26 @@ interface RouteService {
@Body request: EstimateFeeRequest,
): MixinResponse<EstimateFeeResponse>

@POST("web3/gasless/fees")
suspend fun gaslessFee(
@Body request: GaslessFeeRequest,
): MixinResponse<GaslessFeeResponse>

@POST("web3/gasless/prepare")
suspend fun gaslessTx(
@Body request: GaslessTxRequest,
): MixinResponse<GaslessTxResponse>

@POST("web3/gasless/submit")
suspend fun submitGaslessTx(
@Body request: SubmitGaslessTxRequest,
): MixinResponse<SubmitGaslessTxResponse>

@GET("web3/gasless/transactions/{id}")
suspend fun gaslessTransaction(
@Path("id") id: String,
): MixinResponse<GaslessSponsorTransactionResponse>

@POST("web3/rpc")
suspend fun rpc(
@Query("chain_id") chainId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface Web3RawTransactionDao : BaseDao<Web3RawTransaction> {
@Query("SELECT * FROM raw_transactions WHERE hash = :hash AND chain_id = :chainId AND account IN (SELECT DISTINCT destination FROM addresses WHERE wallet_id = :walletId)")
suspend fun getRawTransactionByHashAndChain(walletId:String, hash: String, chainId: String): Web3RawTransaction?

@Query("SELECT nonce FROM raw_transactions WHERE chain_id = :chainId AND state = 'pending' AND account IN (SELECT DISTINCT destination FROM addresses WHERE wallet_id = :walletId) ORDER BY nonce DESC LIMIT 1")
@Query("DELETE FROM raw_transactions WHERE hash = :hash AND chain_id = :chainId")
suspend fun deleteByHashAndChain(hash: String, chainId: String)

@Query("SELECT nonce FROM raw_transactions WHERE chain_id = :chainId AND state = 'pending' AND raw NOT LIKE 'gasless:%' AND account IN (SELECT DISTINCT destination FROM addresses WHERE wallet_id = :walletId) ORDER BY nonce DESC LIMIT 1")
suspend fun getNonce(walletId:String, chainId: String): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,19 @@ data class Web3RawTransaction(
@Ignore
@SerializedName("simulate_tx")
var simulateTx: ParsedTx? = null
}
}

private const val GASLESS_PENDING_SPONSOR_PREFIX = "gasless:sponsor:"
private const val GASLESS_PENDING_BROADCAST_PREFIX = "gasless:broadcast:"

fun Web3RawTransaction.isGaslessPending(): Boolean =
raw.startsWith(GASLESS_PENDING_SPONSOR_PREFIX) || raw.startsWith(GASLESS_PENDING_BROADCAST_PREFIX)

fun Web3RawTransaction.isGaslessSponsorPending(): Boolean =
raw.startsWith(GASLESS_PENDING_SPONSOR_PREFIX)

fun buildGaslessSponsorPendingRawMarker(sponsorTxId: String): String =
"$GASLESS_PENDING_SPONSOR_PREFIX$sponsorTxId"

fun buildGaslessBroadcastPendingRawMarker(broadcastTxHash: String): String =
"$GASLESS_PENDING_BROADCAST_PREFIX$broadcastTxHash"
Loading
Loading