Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
128 changes: 97 additions & 31 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,37 +1,92 @@
-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.**,** { *; }
# 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,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 <fields>;
}
-keepclassmembers,allowoptimization,allowobfuscation 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.** {
<init>(...);
}

# prevent multi dex caused NoSuchProviderException
-keep class org.whispersystems.** { *; }
# JSON keys come from the annotation, so field names may be renamed.
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}

# Keep app types and members reachable while allowing optimization and obfuscation.
-keep,allowoptimization,allowobfuscation class one.mixin.android.** { *; }
# 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 { *; }

# Gson still relies on unannotated app fields in API, websocket, database, and cache models.
-keepclassmembers class one.mixin.android.** {
!transient !static <fields>;
# Java serialization stores class names and field names across app updates.
-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 <fields>;
}

# Persisted jobs and payloads must remain Java-serialization compatible across updates.
-keep class one.mixin.android.** implements java.io.Serializable { *; }
# JobQueue persists jobs by class name and serializes non-transient fields.
-keep class one.mixin.android.job.**
-keepclassmembers,allowoptimization class one.mixin.android.job.** {
static final long serialVersionUID;
!transient <fields>;
}
-keepclassmembers,allowoptimization,allowobfuscation class one.mixin.android.job.** {
<init>(...);
}
-keep class com.birbit.android.jobqueue.Job
-keepclassmembers class com.birbit.android.jobqueue.Job {
static final long serialVersionUID;
!transient <fields>;
}
-keep,allowoptimization,allowobfuscation class com.birbit.android.jobqueue.messaging.message.** {
public <init>();
}

# 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.** { *; }
# Protocol strings use Enum.name; constant names must stay.
-keepclassmembers enum one.mixin.android.** {
<fields>;
}

# webrtc
# webrtc JNI looks up classes by their original names.
-dontwarn org.webrtc.NetworkMonitorAutoDetect
-dontwarn android.net.Network
-keep class org.webrtc.** { *; }

-keep class org.jni_zero.** { *; }
-dontwarn org.jni_zero.**

Expand All @@ -50,8 +105,6 @@

-dontwarn sun.net.spi.nameservice.**

-keep class com.birbit.android.jobqueue.** { *; }

-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
Expand All @@ -63,40 +116,53 @@
-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.** { *; }
# R8 full mode drops TypeToken generic signatures unless the subclass is kept.
-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.**
-dontwarn org.java_websocket.**
-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.**

-keep public class com.reown.walletkit.** { *; }
# JJWT discovers implementations through META-INF/services.
-keep class io.jsonwebtoken.impl.** { *; }
-keep class io.jsonwebtoken.orgjson.** { *; }

-keep,allowoptimization,allowobfuscation,allowshrinking class com.reown.** { *; }

-dontwarn com.sun.jna.**
-keep class com.sun.jna.** { *; }

-dontwarn uniffi.**
-keep class uniffi.** { *; }

# Native Curve25519 providers are loaded by class name.
-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 kotlin.jvm.internal.** { *; }
-keep class com.appsflyer.** { *; }
-keep class com.android.installreferrer.** { *; }

-keep,allowoptimization,allowobfuscation,allowshrinking class com.checkout.** { *; }
13 changes: 13 additions & 0 deletions app/src/main/java/one/mixin/android/MixinApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -160,6 +161,7 @@ open class MixinApplication :

override fun onCreate() {
super.onCreate()
initMnemonicCode()
applicationScope = getAppScope()
init()
registerActivityLifecycleCallbacks(this)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
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
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
Expand Down
10 changes: 0 additions & 10 deletions app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2181,16 +2181,6 @@
<string name="delete_common_wallet_description">지갑을 제거하기 전에 복구 문구나 개인 키를 적어 두었는지 확인하세요. 그렇지 않으면 지갑을 복구할 수 없습니다.</string>
<string name="delete_watch_wallet_description">이 지갑은 보기 전용 지갑이므로 삭제해도 자산에 영향을 미치지 않습니다.</string>
<string name="words">%1$d 단어</string>
<string name="other_words">12단어 또는 24단어</string>
<string name="mnemonic_12_24_instruction_title">12단어 또는 24단어 니모닉 지갑을 사용하려면 다음 단계를 따르세요.</string>
<string name="mnemonic_12_24_step_1">Mixin 계정을 등록하세요.</string>
<string name="mnemonic_12_24_step_2">지갑 목록으로 이동 → 지갑 추가 → 니모닉 가져오기를 선택하세요.</string>
<string name="mnemonic_12_24_step_3">가져오기를 완료하려면 12~24단어 니모닉 문구 또는 개인 키를 입력하세요.</string>
<string name="mnemonic_12_24_step_4">사용을 시작하세요.</string>
<string name="mnemonic_12_24_privacy_info">Mixin 계정을 생성하면 기본적으로 개인정보 지갑이 생성됩니다. 귀하의 잔액, 이체, 거래 및 기타 기능은 기본적으로 개인 정보 보호로 보호됩니다.</string>
<string name="mnemonic_12_24_privacy_info_with_link">Mixin 계정을 생성하면 기본적으로 개인정보 지갑이 생성됩니다. 귀하의 잔액, 이체, 거래 및 기타 기능은 기본적으로 %1$s의 개인 정보 보호로 보호됩니다.</string>
<string name="create_mixin_account">Mixin 계정 만들기</string>
<string name="register_in_15_seconds">15초 안에 등록하세요</string>
<string name="import_private_key">개인 키 가져오기</string>
<string name="add_watch_address">감시 주소 추가</string>
<string name="private_key_hint">개인 키를 입력하세요</string>
Expand Down
10 changes: 0 additions & 10 deletions app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2279,16 +2279,6 @@
<string name="delete_common_wallet_description">請確保您在刪除錢包之前記下恢復短語或私鑰。否則你將無法找回錢包。</string>
<string name="delete_watch_wallet_description">這是一個觀察錢包,刪除此錢包不會影響你的資產。</string>
<string name="words">%1$d 字</string>
<string name="other_words">12 或 24 個字</string>
<string name="mnemonic_12_24_instruction_title">使用12字或24字助記詞錢包,請依照下列步驟操作:</string>
<string name="mnemonic_12_24_step_1">註冊一個Mixin帳號。</string>
<string name="mnemonic_12_24_step_2">進入錢包清單→新增錢包→選擇導入助記詞。</string>
<string name="mnemonic_12_24_step_3">輸入您的12或24字助記詞或私密金鑰即可完成匯入。</string>
<string name="mnemonic_12_24_step_4">開始使用。</string>
<string name="mnemonic_12_24_privacy_info">建立Mixin帳號後,會預設產生隱私錢包。您的餘額、轉帳、交易等功能將預設受到隱私保護,</string>
<string name="mnemonic_12_24_privacy_info_with_link">建立Mixin帳號後,會預設產生隱私錢包。您的餘額、轉帳、交易等功能將預設為隱私保護,%1$s。</string>
<string name="create_mixin_account">建立Mixin帳戶</string>
<string name="register_in_15_seconds">15 秒內註冊</string>
<string name="import_private_key">匯入私鑰</string>
<string name="add_watch_address">新增觀看地址</string>
<string name="private_key_hint">輸入您的私鑰</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand Down