Conversation
There was a problem hiding this comment.
Pull request overview
Adds “gasless transfer” support to the Web3 send flow by introducing new API endpoints/models, new signing type handling, and updating the wallet confirmation UI to display gasless fees and route gasless submissions.
Changes:
- Add gasless fee/prepare/submit API requests + responses and expose them via
RouteService/Web3Repository/Web3ViewModel. - Integrate gasless fee selection + validation into
InputFragment, including Solana and EVM submission paths. - Update browser wallet bottom sheet + Compose preview to support gasless transfer confirmations (custom fee display + custom PIN action).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/java/one/mixin/android/web3/js/Web3Signer.kt | Adds gasless signing path for EVM message signing. |
| app/src/main/java/one/mixin/android/web3/js/JsSignMessage.kt | Introduces TYPE_GASLESS_TRANSFER and helper predicate. |
| app/src/main/java/one/mixin/android/ui/wallet/InputFragment.kt | Implements gasless fee selection, validation, and Solana/EVM submission flows. |
| app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt | Refresh job now scoped to non-empty wallet id. |
| app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt | Adds gasless API wrappers and centralizes web3 private key derivation. |
| app/src/main/java/one/mixin/android/ui/home/web3/BrowserWalletBottomSheetDialogFragment.kt | Adds fee args + gasless handling and introduces a custom PIN action hook. |
| app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt | Shows gasless preview + fee token/amount display adjustments. |
| app/src/main/java/one/mixin/android/ui/home/MainActivity.kt | Adds (currently unused) gasless/demo helpers and models. |
| app/src/main/java/one/mixin/android/ui/common/PendingTransactionRefreshHelper.kt | Adjusts background refresh behavior for pending tx polling. |
| app/src/main/java/one/mixin/android/repository/Web3Repository.kt | Adds repository calls for gasless endpoints. |
| app/src/main/java/one/mixin/android/api/service/RouteService.kt | Adds web3/gasless/* endpoints. |
| app/src/main/java/one/mixin/android/api/response/web3/GaslessTxResponse.kt | Adds response models for gasless prepare (incl. EVM signing payload). |
| app/src/main/java/one/mixin/android/api/response/web3/GaslessFeeResponse.kt | Adds response models for gasless fee estimates. |
| app/src/main/java/one/mixin/android/api/request/web3/GaslessFeeRequest.kt | Adds request model for fee estimates. |
| app/src/main/java/one/mixin/android/api/request/web3/GaslessTxRequest.kt | Adds request model for gasless prepare. |
| app/src/main/java/one/mixin/android/api/request/web3/SubmitGaslessTxRequest.kt | Adds request model for gasless submit. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| step = Step.Loading | ||
| errorInfo = null | ||
| customPinAction?.let { action -> | ||
| withContext(Dispatchers.Main.immediate) { | ||
| action(pin) | ||
| step = Step.Done | ||
| defaultSharedPreferences.putLong( | ||
| Constants.BIOMETRIC_PIN_CHECK, | ||
| System.currentTimeMillis(), | ||
| ) | ||
| } |
There was a problem hiding this comment.
customPinAction is invoked inside withContext(Dispatchers.Main.immediate), which forces the provided suspend callback to run on the main thread. In the gasless flow this callback performs network/signing work, so this can freeze the UI. Run the callback on the existing IO coroutine context and only switch to Main for updating step/preferences/UI state.
| step = Step.Loading | |
| errorInfo = null | |
| customPinAction?.let { action -> | |
| withContext(Dispatchers.Main.immediate) { | |
| action(pin) | |
| step = Step.Done | |
| defaultSharedPreferences.putLong( | |
| Constants.BIOMETRIC_PIN_CHECK, | |
| System.currentTimeMillis(), | |
| ) | |
| } | |
| withContext(Dispatchers.Main.immediate) { | |
| step = Step.Loading | |
| errorInfo = null | |
| } | |
| customPinAction?.let { action -> | |
| action(pin) | |
| withContext(Dispatchers.Main.immediate) { | |
| step = Step.Done | |
| } | |
| defaultSharedPreferences.putLong( | |
| Constants.BIOMETRIC_PIN_CHECK, | |
| System.currentTimeMillis(), | |
| ) |
| val ethPayload = GsonHelper.customGson.fromJson(payload, EthGaslessTxPayload::class.java) | ||
| ?: throw IllegalStateException("Failed to parse gasless EVM payload") | ||
| val userOpSignature = Web3Signer.signEthMessage( | ||
| priv = privateKey, | ||
| message = ethPayload.signing.userOperation.message, | ||
| type = JsSignMessage.TYPE_GASLESS_TRANSFER, | ||
| ) | ||
| val eip7702AuthSignature = ethPayload.signing.eip7702Auth | ||
| ?.takeIf { it.required } | ||
| ?.let { auth -> | ||
| if (!auth.address.equals(GASLESS_EIP7702_AUTHORIZED_ADDRESS, ignoreCase = true)) { | ||
| throw IllegalArgumentException("Unsupported EIP-7702 auth target") | ||
| } | ||
| Web3Signer.signEthMessage( | ||
| priv = privateKey, | ||
| message = auth.message, | ||
| type = JsSignMessage.TYPE_GASLESS_TRANSFER, | ||
| ) |
There was a problem hiding this comment.
EthGaslessTxPayload.signing.*.signType is parsed from the backend but currently ignored when producing userOpSignature / eip7702AuthSignature (both are always signed via TYPE_GASLESS_TRANSFER). If the backend varies signType (e.g., personal_sign vs typed data vs raw hash), this will generate invalid signatures. Use signType to select the appropriate signing algorithm (or remove the field if it’s truly constant).
| private fun <T> requireMixinData( | ||
| response: MixinResponse<T>, | ||
| action: String, | ||
| ): T { | ||
| if (!response.isSuccess) { | ||
| throw IllegalStateException("$action failed: ${response.errorDescription}") | ||
| } | ||
| return requireNotNull(response.data) { "$action returned empty data" } | ||
| } | ||
|
|
||
| private fun requireMixinSuccess( | ||
| response: MixinResponse<*>, | ||
| action: String, | ||
| ) { | ||
| if (!response.isSuccess) { | ||
| throw IllegalStateException("$action failed: ${response.errorDescription}") | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
requireMixinData / requireMixinSuccess are added but not used anywhere in this file. Please either wire them into the new gasless/demo flows or remove them to avoid dead code and future confusion.
| private fun <T> requireMixinData( | |
| response: MixinResponse<T>, | |
| action: String, | |
| ): T { | |
| if (!response.isSuccess) { | |
| throw IllegalStateException("$action failed: ${response.errorDescription}") | |
| } | |
| return requireNotNull(response.data) { "$action returned empty data" } | |
| } | |
| private fun requireMixinSuccess( | |
| response: MixinResponse<*>, | |
| action: String, | |
| ) { | |
| if (!response.isSuccess) { | |
| throw IllegalStateException("$action failed: ${response.errorDescription}") | |
| } | |
| } |
| private data class Eip7702GaslessDemoArgs( | ||
| val from: String?, | ||
| val to: String, | ||
| val assetId: String, | ||
| val amount: String, | ||
| val chainId: String, | ||
| val feeAssetId: String?, | ||
| ) | ||
|
|
||
| private data class Eip7702GaslessDemoResult( | ||
| val from: String, | ||
| val to: String, | ||
| val assetId: String, | ||
| val amount: String, | ||
| val chainId: String, | ||
| val feeAssetId: String, | ||
| val userOpSignType: String, | ||
| val userOpSignature: String, | ||
| val eip7702Required: Boolean, | ||
| val eip7702AuthSignature: String?, | ||
| ) | ||
|
|
There was a problem hiding this comment.
Eip7702GaslessDemoArgs and Eip7702GaslessDemoResult are currently unused. If they’re only for local testing, consider removing them or guarding them behind a debug-only flag/source set so they don’t ship as dead production code.
| private data class Eip7702GaslessDemoArgs( | |
| val from: String?, | |
| val to: String, | |
| val assetId: String, | |
| val amount: String, | |
| val chainId: String, | |
| val feeAssetId: String?, | |
| ) | |
| private data class Eip7702GaslessDemoResult( | |
| val from: String, | |
| val to: String, | |
| val assetId: String, | |
| val amount: String, | |
| val chainId: String, | |
| val feeAssetId: String, | |
| val userOpSignType: String, | |
| val userOpSignature: String, | |
| val eip7702Required: Boolean, | |
| val eip7702AuthSignature: String?, | |
| ) |
9eb30c5 to
6002c4e
Compare
No description provided.