From e598b2bf016f9f78d4c5b751b68f9953dff86d54 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 3 Sep 2026 12:09:55 +0800 Subject: [PATCH 1/5] chore(r8): optimize release keep rules --- app/proguard-rules.pro | 70 +++++++++++++++-------------- app/src/main/res/values/strings.xml | 10 +++++ 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 5ec25db22a..b7f803c579 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,37 +1,43 @@ --optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* --optimizationpasses 5 -allowaccessmodification -# Preserve third-party names for reflection while allowing shrinking and optimization. --keep,allowshrinking,allowoptimization class !one.mixin.android.**,!androidx.**,!com.yalantis.ucrop.**,** { *; } - -# prevent multi dex caused NoSuchProviderException --keep class org.whispersystems.** { *; } - -# Keep app types and members reachable while allowing optimization and obfuscation. --keep,allowoptimization,allowobfuscation class one.mixin.android.** { *; } +# Gson model fields are addressed by their JSON names at runtime. +-keepclassmembers,allowoptimization class one.mixin.android.api.** { + !transient !static ; +} +-keepclassmembers,allowoptimization class one.mixin.android.vo.** { + !transient !static ; +} +-keepclassmembers,allowoptimization class one.mixin.android.websocket.** { + !transient !static ; +} +-keepclassmembers,allowoptimization class one.mixin.android.db.** { + !transient !static ; +} +-keepclassmembers,allowoptimization class one.mixin.android.ui.transfer.** { + !transient !static ; +} -keep class com.google.android.gms.internal.mlkit_entity_extraction.** extends java.util.Random { *; } -# Gson still relies on unannotated app fields in API, websocket, database, and cache models. --keepclassmembers class one.mixin.android.** { - !transient !static ; -} +# Preserve classes passed through Android Serializable extras. +-keep,allowoptimization class one.mixin.android.websocket.BlazeMessageData { *; } +-keep,allowoptimization class one.mixin.android.ui.wallet.WalletActivity$Destination { *; } +-keep,allowoptimization class one.mixin.android.ui.wallet.WalletActivity$Destination$* { *; } -# Persisted jobs and payloads must remain Java-serialization compatible across updates. --keep class one.mixin.android.** implements java.io.Serializable { *; } +# JobQueue persists these objects with Java serialization. +-keepnames class one.mixin.android.job.** +-keepclassmembers,allowshrinking,allowoptimization class one.mixin.android.job.** { + !transient ; +} # These fragment arguments persist nested type names in saved state. -keepnames class one.mixin.android.ui.wallet.ImportKeyBottomSheetDialogFragment$PopupType$* -keepnames class one.mixin.android.ui.home.reminder.ReminderBottomSheetDialogFragment$PopupType$* --keep class io.jsonwebtoken.** { *; } - # webrtc -dontwarn org.webrtc.NetworkMonitorAutoDetect -dontwarn android.net.Network -keep class org.webrtc.** { *; } - -keep class org.jni_zero.** { *; } -dontwarn org.jni_zero.** @@ -50,9 +56,7 @@ -dontwarn sun.net.spi.nameservice.** --keep class com.birbit.android.jobqueue.** { *; } - --keepclassmembers enum * { +-keepclassmembers,allowshrinking,allowoptimization,allowobfuscation enum * { public static **[] values(); public static ** valueOf(java.lang.String); } @@ -63,16 +67,9 @@ -keep,allowobfuscation,allowshrinking class io.reactivex.Observable -keep,allowobfuscation,allowshrinking class io.reactivex.Completable - -# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode - -keepattributes Signature --keep class * extends com.google.gson.reflect.TypeToken # web3j --keep class org.web3j.protocol.** { *; } --keep class org.web3j.abi.** { *; } --keep class org.web3j.crypto.** { *; } -dontwarn org.web3j.crypto.** -dontwarn jnr.unixsocket.** -dontwarn org.web3j.protocol.ipc.** @@ -80,13 +77,14 @@ -dontwarn org.web3j.protocol.websocket.** -dontwarn com.fasterxml.jackson.databind.** --keep class com.fasterxml.jackson.core.** { *; } --keep class com.fasterxml.jackson.databind.** { *; } --keep class com.fasterxml.jackson.annotation.** { *; } #-dontwarn java.lang.SafeVarargs -dontwarn org.slf4j.** --keep public class com.reown.walletkit.** { *; } +# JJWT discovers these implementations through META-INF/services. +-keep class io.jsonwebtoken.impl.compression.DeflateCompressionCodec { *; } +-keep class io.jsonwebtoken.impl.compression.GzipCompressionCodec { *; } +-keep class io.jsonwebtoken.orgjson.io.OrgJsonSerializer { *; } +-keep class io.jsonwebtoken.orgjson.io.OrgJsonDeserializer { *; } -dontwarn com.sun.jna.** -keep class com.sun.jna.** { *; } @@ -94,9 +92,13 @@ -dontwarn uniffi.** -keep class uniffi.** { *; } +-keep,allowshrinking,allowoptimization,allowobfuscation class org.whispersystems.libsignal.** { *; } +-keep class org.whispersystems.curve25519.NativeCurve25519Provider { *; } +-keep class org.whispersystems.curve25519.JavaCurve25519Provider { *; } +-keep class org.whispersystems.curve25519.OpportunisticCurve25519Provider { *; } + -dontwarn groovy.lang.GroovyShell -dontwarn com.yalantis.ucrop** -dontwarn com.appsflyer.** --keep class kotlin.jvm.internal.** { *; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fea21e623b..5952775b2a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2235,6 +2235,16 @@ Make sure you\'ve written down the recovery phrase or private key before removing the wallet. Otherwise you won\'t be able to recover the wallet. Deleting this wallet will not affect your assets, as it is a watch-only wallet. %1$d words + 12 or 24 words + To use a 12- or 24-word mnemonic wallet, follow these steps: + Register a Mixin account. + Go to Wallet list → Add wallet → Import mnemonic. + Enter your 12- or 24-word mnemonic phrase or private key to complete the import. + Start using it. + After creating a Mixin account, a privacy wallet will be created by default. Your balance, transfers, transactions, and other functions will be protected by default. + After creating a Mixin account, a privacy wallet will be created by default. Your balance, transfers, transactions, and other functions will be protected by %1$s. + Create Mixin Account + Register in 15 seconds Import Private Key Add Watch Address Type your private key From fcd7bc42141cf923e5a339274f1ca9f09fbab58d Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 3 Sep 2026 13:04:02 +0800 Subject: [PATCH 2/5] fix(release): preserve reflective runtime metadata --- app/proguard-rules.pro | 74 +++++++++++-------- .../one/mixin/android/MixinApplication.kt | 13 ++++ 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index b7f803c579..de007e637a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,34 +1,33 @@ -allowaccessmodification -# Gson model fields are addressed by their JSON names at runtime. --keepclassmembers,allowoptimization class one.mixin.android.api.** { +# Gson still reads unannotated app fields by their source names. +-keepclassmembers class one.mixin.android.** { !transient !static ; } --keepclassmembers,allowoptimization class one.mixin.android.vo.** { - !transient !static ; -} --keepclassmembers,allowoptimization class one.mixin.android.websocket.** { - !transient !static ; -} --keepclassmembers,allowoptimization class one.mixin.android.db.** { - !transient !static ; -} --keepclassmembers,allowoptimization class one.mixin.android.ui.transfer.** { - !transient !static ; + +# R8 full mode strips unused constructors that Gson instantiates reflectively. +-keepclassmembers class one.mixin.android.** { + (...); } +# Retrofit/Gson models are often only constructed reflectively. +-keep,allowoptimization,allowobfuscation class one.mixin.android.api.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.vo.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.websocket.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.crypto.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.web3.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.tip.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.media.** { *; } +-keep,allowoptimization,allowobfuscation class one.mixin.android.webrtc.** { *; } + -keep class com.google.android.gms.internal.mlkit_entity_extraction.** extends java.util.Random { *; } -# Preserve classes passed through Android Serializable extras. --keep,allowoptimization class one.mixin.android.websocket.BlazeMessageData { *; } --keep,allowoptimization class one.mixin.android.ui.wallet.WalletActivity$Destination { *; } --keep,allowoptimization class one.mixin.android.ui.wallet.WalletActivity$Destination$* { *; } +# Java serialization stores field names and class names across process/app updates. +-keep class one.mixin.android.** implements java.io.Serializable { *; } -# JobQueue persists these objects with Java serialization. --keepnames class one.mixin.android.job.** --keepclassmembers,allowshrinking,allowoptimization class one.mixin.android.job.** { - !transient ; -} +# JobQueue persists jobs with Java serialization and looks up classes by name. +-keep class one.mixin.android.job.** { *; } +-keep class com.birbit.android.jobqueue.** { *; } # These fragment arguments persist nested type names in saved state. -keepnames class one.mixin.android.ui.wallet.ImportKeyBottomSheetDialogFragment$PopupType$* @@ -56,7 +55,7 @@ -dontwarn sun.net.spi.nameservice.** --keepclassmembers,allowshrinking,allowoptimization,allowobfuscation enum * { +-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } @@ -69,7 +68,17 @@ -keepattributes Signature +# R8 full mode drops TypeToken generic signatures unless the subclass is kept. +-keep class com.google.gson.reflect.TypeToken { *; } +-keep class * extends com.google.gson.reflect.TypeToken +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + # web3j +-keep class org.web3j.protocol.** { *; } +-keep class org.web3j.abi.** { *; } +-keep class org.web3j.crypto.** { *; } -dontwarn org.web3j.crypto.** -dontwarn jnr.unixsocket.** -dontwarn org.web3j.protocol.ipc.** @@ -77,14 +86,17 @@ -dontwarn org.web3j.protocol.websocket.** -dontwarn com.fasterxml.jackson.databind.** +-keep class com.fasterxml.jackson.core.** { *; } +-keep class com.fasterxml.jackson.databind.** { *; } +-keep class com.fasterxml.jackson.annotation.** { *; } #-dontwarn java.lang.SafeVarargs -dontwarn org.slf4j.** -# JJWT discovers these implementations through META-INF/services. --keep class io.jsonwebtoken.impl.compression.DeflateCompressionCodec { *; } --keep class io.jsonwebtoken.impl.compression.GzipCompressionCodec { *; } --keep class io.jsonwebtoken.orgjson.io.OrgJsonSerializer { *; } --keep class io.jsonwebtoken.orgjson.io.OrgJsonDeserializer { *; } +# JJWT discovers implementations through META-INF/services. +-keep class io.jsonwebtoken.** { *; } + +-keep public class com.reown.walletkit.** { *; } +-keep class com.reown.android.** { *; } -dontwarn com.sun.jna.** -keep class com.sun.jna.** { *; } @@ -92,10 +104,8 @@ -dontwarn uniffi.** -keep class uniffi.** { *; } --keep,allowshrinking,allowoptimization,allowobfuscation class org.whispersystems.libsignal.** { *; } --keep class org.whispersystems.curve25519.NativeCurve25519Provider { *; } --keep class org.whispersystems.curve25519.JavaCurve25519Provider { *; } --keep class org.whispersystems.curve25519.OpportunisticCurve25519Provider { *; } +# Native Curve25519 providers are loaded by class name. +-keep class org.whispersystems.** { *; } -dontwarn groovy.lang.GroovyShell diff --git a/app/src/main/java/one/mixin/android/MixinApplication.kt b/app/src/main/java/one/mixin/android/MixinApplication.kt index 33dc41dc0f..e908a19a47 100644 --- a/app/src/main/java/one/mixin/android/MixinApplication.kt +++ b/app/src/main/java/one/mixin/android/MixinApplication.kt @@ -87,6 +87,7 @@ import one.mixin.android.vo.CallStateLiveData import one.mixin.android.webrtc.GroupCallService import one.mixin.android.webrtc.VoiceCallService import one.mixin.android.webrtc.disconnect +import org.bitcoinj.crypto.MnemonicCode import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -160,6 +161,7 @@ open class MixinApplication : override fun onCreate() { super.onCreate() + initMnemonicCode() applicationScope = getAppScope() init() registerActivityLifecycleCallbacks(this) @@ -189,6 +191,17 @@ open class MixinApplication : Session.getAccount()?.let(ThirdPartyUserIdentity::setUser) } + private fun initMnemonicCode() { + if (MnemonicCode.INSTANCE != null) return + val wordList = Thread.currentThread().contextClassLoader + ?.getResourceAsStream("en-mnemonic-word-list.txt") + ?: error("Missing BIP39 English word list") + MnemonicCode.INSTANCE = MnemonicCode( + wordList, + "ad90bf3beb7b0eb7e5acd74727dc0da96e0a280a258354e7293fb7e211ac03db", + ) + } + private fun initBugsnag() { val config = BugsnagConfiguration.load(this) config.enabledErrorTypes.anrs = true From c2b70a4a6331718f129a6f0f03198cac5e23b9e7 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 3 Sep 2026 13:25:32 +0800 Subject: [PATCH 3/5] chore(r8): tighten release keep rules --- app/proguard-rules.pro | 116 ++++++++++++++------- app/src/main/res/values-ko/strings.xml | 10 -- app/src/main/res/values-zh-rTW/strings.xml | 10 -- app/src/main/res/values/strings.xml | 10 -- 4 files changed, 79 insertions(+), 67 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index de007e637a..15214ff7b8 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,39 +1,77 @@ -allowaccessmodification -# Gson still reads unannotated app fields by their source names. --keepclassmembers class one.mixin.android.** { +# Play requires >= 25% obfuscation, optimization, and shrinking (DEX > 10 MB). +# Do not use `-keep class x.** { *; }` without allow* unless the name is a +# runtime identifier (SPI, JNI, or persisted Java serialization). + +# Gson/Room models that still use source field names as JSON/column keys. +-keepclassmembers class one.mixin.android.api.**, + one.mixin.android.vo.**, + one.mixin.android.websocket.**, + one.mixin.android.db.**, + one.mixin.android.crypto.**, + one.mixin.android.web3.**, + one.mixin.android.tip.**, + one.mixin.android.media.**, + one.mixin.android.webrtc.**, + one.mixin.android.ui.web.**, + one.mixin.android.ui.wallet.**, + one.mixin.android.ui.landing.**, + one.mixin.android.ui.transfer.** { !transient !static ; + (...); } -# R8 full mode strips unused constructors that Gson instantiates reflectively. --keepclassmembers class one.mixin.android.** { - (...); +# JSON keys come from the annotation, so field names may be renamed. +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; } -# Retrofit/Gson models are often only constructed reflectively. --keep,allowoptimization,allowobfuscation class one.mixin.android.api.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.vo.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.websocket.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.crypto.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.web3.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.tip.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.media.** { *; } --keep,allowoptimization,allowobfuscation class one.mixin.android.webrtc.** { *; } +# R8 full mode can replace reflectively constructed model types with null. +-keep,allowoptimization,allowobfuscation class one.mixin.android.api.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.vo.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.websocket.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.crypto.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.web3.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.tip.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.media.** +-keep,allowoptimization,allowobfuscation class one.mixin.android.webrtc.** -keep class com.google.android.gms.internal.mlkit_entity_extraction.** extends java.util.Random { *; } -# Java serialization stores field names and class names across process/app updates. --keep class one.mixin.android.** implements java.io.Serializable { *; } +# Java serialization stores class names and field names across app updates. +-keep class one.mixin.android.** implements java.io.Serializable +-keepclassmembers class one.mixin.android.** implements java.io.Serializable { + static final long serialVersionUID; + !transient ; +} -# JobQueue persists jobs with Java serialization and looks up classes by name. --keep class one.mixin.android.job.** { *; } --keep class com.birbit.android.jobqueue.** { *; } +# JobQueue persists jobs by class name and serializes non-transient fields. +-keep class one.mixin.android.job.** +-keepclassmembers class one.mixin.android.job.** { + static final long serialVersionUID; + !transient ; + (...); +} +-keep class com.birbit.android.jobqueue.Job +-keepclassmembers class com.birbit.android.jobqueue.Job { + static final long serialVersionUID; + !transient ; +} +-keep,allowoptimization,allowobfuscation class com.birbit.android.jobqueue.messaging.message.** { + public (); +} # These fragment arguments persist nested type names in saved state. -keepnames class one.mixin.android.ui.wallet.ImportKeyBottomSheetDialogFragment$PopupType$* -keepnames class one.mixin.android.ui.home.reminder.ReminderBottomSheetDialogFragment$PopupType$* -# webrtc +# Protocol strings use Enum.name; constant names must stay. +-keepclassmembers enum one.mixin.android.** { + ; +} + +# webrtc JNI looks up classes by their original names. -dontwarn org.webrtc.NetworkMonitorAutoDetect -dontwarn android.net.Network -keep class org.webrtc.** { *; } @@ -69,16 +107,15 @@ -keepattributes Signature # R8 full mode drops TypeToken generic signatures unless the subclass is kept. --keep class com.google.gson.reflect.TypeToken { *; } --keep class * extends com.google.gson.reflect.TypeToken --keepclassmembers,allowobfuscation class * { - @com.google.gson.annotations.SerializedName ; -} - -# web3j --keep class org.web3j.protocol.** { *; } --keep class org.web3j.abi.** { *; } --keep class org.web3j.crypto.** { *; } +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken { *; } +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken + +# web3j Type.getTypeAsString() uses the datatype class simple name. +-keep class org.web3j.abi.datatypes.** { *; } +-keep class org.web3j.abi.TypeReference { *; } +-keep class * extends org.web3j.abi.TypeReference +-keep,allowoptimization,allowobfuscation,allowshrinking class org.web3j.crypto.** { *; } +-keep,allowoptimization,allowobfuscation,allowshrinking class org.web3j.protocol.** { *; } -dontwarn org.web3j.crypto.** -dontwarn jnr.unixsocket.** -dontwarn org.web3j.protocol.ipc.** @@ -86,17 +123,15 @@ -dontwarn org.web3j.protocol.websocket.** -dontwarn com.fasterxml.jackson.databind.** --keep class com.fasterxml.jackson.core.** { *; } --keep class com.fasterxml.jackson.databind.** { *; } --keep class com.fasterxml.jackson.annotation.** { *; } +-keep,allowoptimization,allowobfuscation,allowshrinking class com.fasterxml.jackson.** { *; } #-dontwarn java.lang.SafeVarargs -dontwarn org.slf4j.** # JJWT discovers implementations through META-INF/services. --keep class io.jsonwebtoken.** { *; } +-keep class io.jsonwebtoken.impl.** { *; } +-keep class io.jsonwebtoken.orgjson.** { *; } --keep public class com.reown.walletkit.** { *; } --keep class com.reown.android.** { *; } +-keep,allowoptimization,allowobfuscation,allowshrinking class com.reown.** { *; } -dontwarn com.sun.jna.** -keep class com.sun.jna.** { *; } @@ -105,10 +140,17 @@ -keep class uniffi.** { *; } # Native Curve25519 providers are loaded by class name. --keep class org.whispersystems.** { *; } +-keep class org.whispersystems.curve25519.NativeCurve25519Provider { *; } +-keep class org.whispersystems.curve25519.JavaCurve25519Provider { *; } +-keep class org.whispersystems.curve25519.OpportunisticCurve25519Provider { *; } +-keep,allowoptimization,allowobfuscation,allowshrinking class org.whispersystems.libsignal.** { *; } -dontwarn groovy.lang.GroovyShell -dontwarn com.yalantis.ucrop** -dontwarn com.appsflyer.** +-keep class com.appsflyer.** { *; } +-keep class com.android.installreferrer.** { *; } + +-keep,allowoptimization,allowobfuscation,allowshrinking class com.checkout.** { *; } diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f46af19384..1240c4fda8 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -2181,16 +2181,6 @@ 지갑을 제거하기 전에 복구 문구나 개인 키를 적어 두었는지 확인하세요. 그렇지 않으면 지갑을 복구할 수 없습니다. 이 지갑은 보기 전용 지갑이므로 삭제해도 자산에 영향을 미치지 않습니다. %1$d 단어 - 12단어 또는 24단어 - 12단어 또는 24단어 니모닉 지갑을 사용하려면 다음 단계를 따르세요. - Mixin 계정을 등록하세요. - 지갑 목록으로 이동 → 지갑 추가 → 니모닉 가져오기를 선택하세요. - 가져오기를 완료하려면 12~24단어 니모닉 문구 또는 개인 키를 입력하세요. - 사용을 시작하세요. - Mixin 계정을 생성하면 기본적으로 개인정보 지갑이 생성됩니다. 귀하의 잔액, 이체, 거래 및 기타 기능은 기본적으로 개인 정보 보호로 보호됩니다. - Mixin 계정을 생성하면 기본적으로 개인정보 지갑이 생성됩니다. 귀하의 잔액, 이체, 거래 및 기타 기능은 기본적으로 %1$s의 개인 정보 보호로 보호됩니다. - Mixin 계정 만들기 - 15초 안에 등록하세요 개인 키 가져오기 감시 주소 추가 개인 키를 입력하세요 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 88926174a7..dcfad32764 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2279,16 +2279,6 @@ 請確保您在刪除錢包之前記下恢復短語或私鑰。否則你將無法找回錢包。 這是一個觀察錢包,刪除此錢包不會影響你的資產。 %1$d 字 - 12 或 24 個字 - 使用12字或24字助記詞錢包,請依照下列步驟操作: - 註冊一個Mixin帳號。 - 進入錢包清單→新增錢包→選擇導入助記詞。 - 輸入您的12或24字助記詞或私密金鑰即可完成匯入。 - 開始使用。 - 建立Mixin帳號後,會預設產生隱私錢包。您的餘額、轉帳、交易等功能將預設受到隱私保護, - 建立Mixin帳號後,會預設產生隱私錢包。您的餘額、轉帳、交易等功能將預設為隱私保護,%1$s。 - 建立Mixin帳戶 - 15 秒內註冊 匯入私鑰 新增觀看地址 輸入您的私鑰 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5952775b2a..fea21e623b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2235,16 +2235,6 @@ Make sure you\'ve written down the recovery phrase or private key before removing the wallet. Otherwise you won\'t be able to recover the wallet. Deleting this wallet will not affect your assets, as it is a watch-only wallet. %1$d words - 12 or 24 words - To use a 12- or 24-word mnemonic wallet, follow these steps: - Register a Mixin account. - Go to Wallet list → Add wallet → Import mnemonic. - Enter your 12- or 24-word mnemonic phrase or private key to complete the import. - Start using it. - After creating a Mixin account, a privacy wallet will be created by default. Your balance, transfers, transactions, and other functions will be protected by default. - After creating a Mixin account, a privacy wallet will be created by default. Your balance, transfers, transactions, and other functions will be protected by %1$s. - Create Mixin Account - Register in 15 seconds Import Private Key Add Watch Address Type your private key From 33481a339124b1c2bee5430be49a57c1663a42d3 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 3 Sep 2026 14:25:02 +0800 Subject: [PATCH 4/5] chore(r8): relax safe release keep constraints --- app/proguard-rules.pro | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 15214ff7b8..abee1f191e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -5,7 +5,18 @@ # runtime identifier (SPI, JNI, or persisted Java serialization). # Gson/Room models that still use source field names as JSON/column keys. --keepclassmembers class one.mixin.android.api.**, +-keepclassmembers,allowoptimization class one.mixin.android.api.**, + one.mixin.android.vo.**, + one.mixin.android.websocket.**, + one.mixin.android.db.**, + one.mixin.android.crypto.**, + one.mixin.android.web3.**, + one.mixin.android.tip.**, + one.mixin.android.media.**, + one.mixin.android.webrtc.** { + !transient !static ; +} +-keepclassmembers,allowoptimization,allowobfuscation class one.mixin.android.api.**, one.mixin.android.vo.**, one.mixin.android.websocket.**, one.mixin.android.db.**, @@ -18,7 +29,6 @@ one.mixin.android.ui.wallet.**, one.mixin.android.ui.landing.**, one.mixin.android.ui.transfer.** { - !transient !static ; (...); } @@ -40,17 +50,19 @@ -keep class com.google.android.gms.internal.mlkit_entity_extraction.** extends java.util.Random { *; } # Java serialization stores class names and field names across app updates. --keep class one.mixin.android.** implements java.io.Serializable --keepclassmembers class one.mixin.android.** implements java.io.Serializable { +-keep,allowoptimization class one.mixin.android.** implements java.io.Serializable +-keepclassmembers,allowoptimization class one.mixin.android.** implements java.io.Serializable { static final long serialVersionUID; !transient ; } # JobQueue persists jobs by class name and serializes non-transient fields. -keep class one.mixin.android.job.** --keepclassmembers class one.mixin.android.job.** { +-keepclassmembers,allowoptimization class one.mixin.android.job.** { static final long serialVersionUID; !transient ; +} +-keepclassmembers,allowoptimization,allowobfuscation class one.mixin.android.job.** { (...); } -keep class com.birbit.android.jobqueue.Job From 3ab559111bc2d817de131414b5363a7991b720fa Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 3 Sep 2026 15:01:43 +0800 Subject: [PATCH 5/5] fix(wallet): protect earn cache deserialization --- .../android/ui/wallet/home/WalletHomeCache.kt | 21 ++++++++++++++-- .../ui/wallet/home/WalletHomeEarnAccount.kt | 7 ++++++ .../wallet/home/WalletHomeCashAccountTest.kt | 24 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt index f027f31331..0d2efdfb5d 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt @@ -1,6 +1,7 @@ package one.mixin.android.ui.wallet.home import android.content.SharedPreferences +import com.google.gson.JsonParser import com.google.gson.annotations.SerializedName import one.mixin.android.db.web3.vo.Web3TokenItem import one.mixin.android.db.web3.vo.Web3TransactionItem @@ -10,6 +11,7 @@ import one.mixin.android.vo.SnapshotItem import one.mixin.android.vo.safe.TokenItem private const val PREF_WALLET_HOME_CACHE_PREFIX = "pref_wallet_home_cache" +private val EARN_ACCOUNT_CACHE_FIELDS = arrayOf("assetId", "assetSymbol", "iconUrl", "balanceUsd", "earningsUsd") data class WalletHomeCache( @SerializedName("walletType") @@ -102,9 +104,24 @@ fun SharedPreferences.getWalletHomeCacheState( fun SharedPreferences.getWalletHomeCache( key: String, ): WalletHomeCache? = + getString(key, null).parseWalletHomeCache() + +internal fun String?.parseWalletHomeCache(): WalletHomeCache? = runCatching { - getString(key, null) - ?.let { GsonHelper.customGson.fromJson(it, WalletHomeCache::class.java) } + val root = JsonParser.parseString(this).asJsonObject + root.getAsJsonArray("earnAccounts")?.let { accounts -> + for (index in accounts.size() - 1 downTo 0) { + val account = accounts[index] + if (!account.isJsonObject || EARN_ACCOUNT_CACHE_FIELDS.any { field -> + val value = account.asJsonObject.get(field) + value == null || value.isJsonNull + } + ) { + accounts.remove(index) + } + } + } + GsonHelper.customGson.fromJson(root, WalletHomeCache::class.java) }.getOrNull() fun SharedPreferences.putWalletHomeCache( diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeEarnAccount.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeEarnAccount.kt index 8b1d8985d7..28ae13822e 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeEarnAccount.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeEarnAccount.kt @@ -1,5 +1,6 @@ package one.mixin.android.ui.wallet.home +import com.google.gson.annotations.SerializedName import one.mixin.android.api.response.EarnAccountSummary import one.mixin.android.api.response.EarnProduct import one.mixin.android.extension.numberFormat2 @@ -7,11 +8,17 @@ import one.mixin.android.vo.safe.TokenItem import java.math.BigDecimal data class WalletHomeEarnAccount( + @SerializedName("assetId") val assetId: String, + @SerializedName("assetSymbol") val assetSymbol: String, + @SerializedName("iconUrl") val iconUrl: String, + @SerializedName("balanceUsd") val balanceUsd: BigDecimal, + @SerializedName("earningsUsd") val earningsUsd: BigDecimal, + @SerializedName("apyText") val apyText: String?, ) { val balanceAmountText: String diff --git a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt index 0cbf1b6082..55847caa4c 100644 --- a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt +++ b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt @@ -8,6 +8,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test import java.math.BigDecimal @@ -156,6 +157,29 @@ class WalletHomeCashAccountTest { assertFalse(state.cards.contains(WalletHomeCardType.CASH)) } + @Test + fun walletHomeCacheDropsIncompleteEarnAccounts() { + val json = """ + { + "walletType":"PRIVACY", + "fiatTotal":"0.00", + "btcTotal":"0.00", + "fiatSymbol":"USD", + "totalTokenCount":0, + "totalTransactionCount":0, + "earnAccounts":[ + {"assetId":"asset-1","assetSymbol":"USD","iconUrl":"","earningsUsd":"0"}, + {"assetId":"asset-2","assetSymbol":"USD","iconUrl":"","balanceUsd":"1.25","earningsUsd":"0"} + ] + } + """.trimIndent() + + val cache = json.parseWalletHomeCache() + + assertTrue(cache != null) + assertEquals(listOf("asset-2"), cache?.toState()?.earnAccounts?.map { it.assetId }) + } + @Test fun walletHomeStateShowsDynamicBannerWhenStillLoading() { val banner = WalletHomeBanner(