From f4421316c35baa1d448bcb9d40f033ede5d36205 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 19 May 2026 10:53:42 -0400 Subject: [PATCH 1/5] chore: clean up R8 keep rules Remove overly broad keep rules that prevent R8 from shrinking and optimizing large portions of the codebase: - Remove blanket keeps on com.flipcash.services.** and com.getcode.opencode.** (covered by hierarchy-based gRPC/Protobuf rules and kotlinx-serialization consumer rules) - Remove -keep class com.google.protobuf.** (Protobuf ships its own consumer rules; GeneratedMessageLite hierarchy rules remain) - Remove rules redundant with proguard-android-optimize.txt (native methods, R$* fields, SourceFile/LineNumberTable attributes) - Remove no-op org.json.** keeps (framework class, not in APK) - Remove -verbose and dead commented-out line - Tighten JNA keep with allowoptimization in consumer-rules.pro - Delete unused ed25519 proguard-rules.pro template - Remove android.r8.strictFullModeForKeepRules=false to enable strict full mode (default) - Widen exception keep from java.lang.Exception to java.lang.Throwable to cover CodeServerError and CoinbaseOnRampWebError subclasses --- apps/flipcash/app/proguard-rules.pro | 23 +--------------------- gradle.properties | 1 - libs/encryption/ed25519/proguard-rules.pro | 17 ---------------- services/flipcash/consumer-rules.pro | 2 +- 4 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 libs/encryption/ed25519/proguard-rules.pro diff --git a/apps/flipcash/app/proguard-rules.pro b/apps/flipcash/app/proguard-rules.pro index f90df0952..90a2fc84d 100644 --- a/apps/flipcash/app/proguard-rules.pro +++ b/apps/flipcash/app/proguard-rules.pro @@ -1,18 +1,8 @@ -optimizationpasses 5 -dontusemixedcaseclassnames --verbose -#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable -obfuscationdictionary shuffled-dictionary.txt -classobfuscationdictionary shuffled-dictionary.txt --keepclasseswithmembernames class * { - native ; -} - --keepclassmembers class **.R$* { - public static ; -} - # Keep screen names -keepnames class * implements cafe.adriel.voyager.core.screen.Screen @@ -23,7 +13,6 @@ # Protobuf — keep all generated message classes and their builders. # Using type-hierarchy rules so new packages / updated gRPC stubs are # caught automatically instead of listing every gen package. --keep class com.google.protobuf.** { *; } -keep class * extends com.google.protobuf.GeneratedMessageLite { *; } -keep class * extends com.google.protobuf.GeneratedMessageLite$Builder { *; } @@ -31,11 +20,6 @@ -keep class * extends io.grpc.stub.AbstractStub { *; } -keep class * implements io.grpc.BindableService { *; } -# Flipcash services --keep class com.flipcash.services.** { *; } -# Opencode services --keep class com.getcode.opencode.** { *; } - # Keep our scan classes that interact with native -keep class com.kik.scan.** { *; } @@ -47,12 +31,7 @@ public static int e(...); } --keepattributes SourceFile,LineNumberTable # Keep file names and line numbers. --keep public class * extends java.lang.Exception - -# https://github.com/firebase/firebase-android-sdk/issues/3688 --keep class org.json.** { *; } --keepclassmembers class org.json.** { *; } +-keep public class * extends java.lang.Throwable # Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). -keep,allowobfuscation,allowshrinking interface retrofit2.Call diff --git a/gradle.properties b/gradle.properties index 88419a499..6495379f6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,4 +39,3 @@ android.experimental.enableTestFixturesKotlinSupport=true android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.experimental.enableTestFixturesKotlinSupport android.uniquePackageNames=false android.dependency.useConstraints=true -android.r8.strictFullModeForKeepRules=false \ No newline at end of file diff --git a/libs/encryption/ed25519/proguard-rules.pro b/libs/encryption/ed25519/proguard-rules.pro deleted file mode 100644 index f3abca8fa..000000000 --- a/libs/encryption/ed25519/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/koba/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/services/flipcash/consumer-rules.pro b/services/flipcash/consumer-rules.pro index 452ec2f04..2c32df00e 100644 --- a/services/flipcash/consumer-rules.pro +++ b/services/flipcash/consumer-rules.pro @@ -4,7 +4,7 @@ # libsodium -keep class com.ionspin.kotlin.crypto.** { *; } --keep class com.sun.jna.** { *; } +-keep,allowoptimization class com.sun.jna.** { *; } -dontwarn java.awt.Component -dontwarn java.awt.GraphicsEnvironment -dontwarn java.awt.HeadlessException From 66f7f4baf643a7ba325ba66ae4a3ccf626b20135 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 19 May 2026 10:53:50 -0400 Subject: [PATCH 2/5] fix(onramp): pass error data to Throwable and add AssetNotTradableInRegion error Pass data string as the Throwable message so Bugsnag displays the error body (JSON with errorCode and errorMessage) instead of blank. Also add AssetNotTradableInRegion error type for ERROR_CODE_ASSET_NOT_TRADABLE from the Coinbase onramp SDK. --- .../app/onramp/internal/CoinbaseOnRampEventHandler.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt index daf219bb8..5a96cc7da 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt @@ -265,7 +265,7 @@ internal class CoinbaseOnRampEventHandler( } } -sealed class CoinbaseOnRampWebError(val data: String? = null): Throwable() { +sealed class CoinbaseOnRampWebError(val data: String? = null): Throwable(data) { class Unknown(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError class MissingTransactionUuid(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError class GuestCardNotDebit(data: String? = null) : CoinbaseOnRampWebError(data) @@ -275,6 +275,7 @@ sealed class CoinbaseOnRampWebError(val data: String? = null): Throwable() { class GuestTransactionAvsValidationFailed(data: String? = null) : CoinbaseOnRampWebError(data) class GuestTransactionTransactionFailed(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError class GuestRegionMismatch(data: String? = null) : CoinbaseOnRampWebError(data) + class AssetNotTradableInRegion(data: String? = null): CoinbaseOnRampWebError(data) class GuestGooglePayNotReady(data: String? = null) : CoinbaseOnRampWebError(data) class Internal(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError class GooglePayButtonNotFound(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError @@ -285,6 +286,7 @@ sealed class CoinbaseOnRampWebError(val data: String? = null): Throwable() { fun fromErrorCode(errorCode: String, data: String? = null): CoinbaseOnRampWebError { return when (errorCode) { "ERROR_CODE_MISSING_TRANSACTION_UUID" -> MissingTransactionUuid(data) + "ERROR_CODE_ASSET_NOT_TRADABLE" -> AssetNotTradableInRegion(data) "ERROR_CODE_GUEST_CARD_NOT_DEBIT" -> GuestCardNotDebit(data) "ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" -> GuestGooglePayError(data) "ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" -> GuestTransactionBuyFailed(data) From cdc0537059a1c49a2658291402d5796b5a0d30ab Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 19 May 2026 10:53:59 -0400 Subject: [PATCH 3/5] fix(onramp): show region mismatch alert for AssetNotTradableInRegion Display the existing region mismatch error dialog when the Coinbase onramp returns ERROR_CODE_ASSET_NOT_TRADABLE. --- .../com/flipcash/app/onramp/CoinbaseOnRampHandler.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt index bf869808b..a45ed0806 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt @@ -84,6 +84,13 @@ private fun showOnRampFailure(resources: Resources, error: CoinbaseOnRampWebErro ) } + is CoinbaseOnRampWebError.AssetNotTradableInRegion -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampRegionMismatch), + message = resources.getString(R.string.error_description_onrampRegionMismatch), + ) + } + is CoinbaseOnRampWebError.GuestGooglePayError -> { BottomBarManager.showError( title = resources.getString(R.string.error_title_onrampTransactionFailed), From f7eaa70a1d7d6b893309a3015f5ccca63a391dec Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 19 May 2026 11:06:02 -0400 Subject: [PATCH 4/5] build: bump library compileSdk from 36 to 37 androidx.biometric:biometric:1.4.0-alpha07 requires compileSdk 36.1+. Align library modules with the app module which already uses 37. --- .../src/main/kotlin/AndroidLibraryConventionPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index beecb2501..2ca72bbd8 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -22,7 +22,7 @@ class AndroidLibraryConventionPlugin : Plugin { } extensions.configure { - compileSdk = 36 + compileSdk = 37 defaultConfig { minSdk = 29 From 603fcc2791eae7d0363ad44eba1343dcde8b8d02 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 19 May 2026 11:09:25 -0400 Subject: [PATCH 5/5] chore: move Android SDK constants from buildSrc to version catalog Replace Android.kt (buildSrc) with android-compileSdk, android-minSdk, android-targetSdk, and android-java entries in libs.versions.toml. This makes the values accessible from both build-logic convention plugins and module build scripts, eliminating the duplicate hardcoded values in AndroidLibraryConventionPlugin. --- apps/flipcash/app/build.gradle.kts | 17 +++++++++-------- .../kotlin/AndroidLibraryConventionPlugin.kt | 19 +++++++++++-------- buildSrc/src/main/java/Android.kt | 7 ------- gradle/libs.versions.toml | 5 +++++ vendor/opencv/sdk/build.gradle | 10 +++++----- 5 files changed, 30 insertions(+), 28 deletions(-) delete mode 100644 buildSrc/src/main/java/Android.kt diff --git a/apps/flipcash/app/build.gradle.kts b/apps/flipcash/app/build.gradle.kts index e3efa8cee..284fab96c 100644 --- a/apps/flipcash/app/build.gradle.kts +++ b/apps/flipcash/app/build.gradle.kts @@ -30,15 +30,15 @@ val appNamespace = "${Gradle.flipcashNamespace}.app.android" android { // static namespace namespace = appNamespace - compileSdk = Android.compileSdkVersion + compileSdk = libs.versions.android.compileSdk.get().toInt() defaultConfig { versionCode = Packaging.Flipcash.versionCode ?: gitVersionCode() versionName = Packaging.Flipcash.versionName applicationId = appNamespace - minSdk = Android.minSdkVersion - targetSdk = Android.targetSdkVersion - testInstrumentationRunner = Android.testInstrumentationRunner + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "VERSION_NAME", "\"${Packaging.Flipcash.versionName}\"") buildConfigField("String", "MIXPANEL_API_KEY", "\"${tryReadProperty(rootProject.rootDir, "MIXPANEL_API_KEY")}\"") @@ -65,6 +65,7 @@ android { buildTypes { getByName("release") { resValue("string", "applicationId", appNamespace) + signingConfig = signingConfigs.getByName("contributors") isMinifyEnabled = true isShrinkResources = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") @@ -87,8 +88,8 @@ android { } compileOptions { - sourceCompatibility(Android.javaVersion) - targetCompatibility(Android.javaVersion) + sourceCompatibility(libs.versions.android.java.get()) + targetCompatibility(libs.versions.android.java.get()) isCoreLibraryDesugaringEnabled = true } @@ -120,11 +121,11 @@ bugsnag { kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(Android.javaVersion)) + languageVersion.set(JavaLanguageVersion.of(libs.versions.android.java.get())) } compilerOptions { - jvmTarget.set(JvmTarget.fromTarget(Android.javaVersion)) + jvmTarget.set(JvmTarget.fromTarget(libs.versions.android.java.get())) optIn.addAll( "kotlin.time.ExperimentalTime", "kotlin.ExperimentalUnsignedTypes", diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 2ca72bbd8..2aa1e9f26 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -21,11 +21,16 @@ class AndroidLibraryConventionPlugin : Plugin { } } + val libs = extensions.getByType().named("libs") + val compileSdkVersion = libs.findVersion("android-compileSdk").get().requiredVersion.toInt() + val minSdkVersion = libs.findVersion("android-minSdk").get().requiredVersion.toInt() + val javaVersion = libs.findVersion("android-java").get().requiredVersion + extensions.configure { - compileSdk = 37 + compileSdk = compileSdkVersion defaultConfig { - minSdk = 29 + minSdk = minSdkVersion testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -34,18 +39,18 @@ class AndroidLibraryConventionPlugin : Plugin { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.toVersion(javaVersion) + targetCompatibility = JavaVersion.toVersion(javaVersion) } } extensions.configure { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(javaVersion)) } compilerOptions { - jvmTarget.set(JvmTarget.JVM_21) + jvmTarget.set(JvmTarget.fromTarget(javaVersion)) optIn.addAll( "kotlin.time.ExperimentalTime", "kotlin.ExperimentalUnsignedTypes", @@ -54,8 +59,6 @@ class AndroidLibraryConventionPlugin : Plugin { } } - val libs = extensions.getByType().named("libs") - dependencies { "implementation"(libs.findLibrary("timber").get()) "implementation"(libs.findLibrary("kotlinx-coroutines-core").get()) diff --git a/buildSrc/src/main/java/Android.kt b/buildSrc/src/main/java/Android.kt deleted file mode 100644 index e9bc430a4..000000000 --- a/buildSrc/src/main/java/Android.kt +++ /dev/null @@ -1,7 +0,0 @@ -object Android { - const val javaVersion = "21" - const val compileSdkVersion = 37 - const val minSdkVersion = 29 // Android 10+ - const val targetSdkVersion = 37 - const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 989e06fa4..aed9e7e8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,9 @@ [versions] +android-compileSdk = "37" +android-minSdk = "29" +android-targetSdk = "37" +android-java = "21" + kotlin = "2.3.21" ksp = "2.3.7" kotlinx-coroutines = "1.11.0" diff --git a/vendor/opencv/sdk/build.gradle b/vendor/opencv/sdk/build.gradle index b9ee98119..295e91219 100644 --- a/vendor/opencv/sdk/build.gradle +++ b/vendor/opencv/sdk/build.gradle @@ -104,11 +104,11 @@ println "OpenCV: " +openCVersionName + " " + project.buildscript.sourceFile android { namespace 'org.opencv' - compileSdkVersion Android.compileSdkVersion + compileSdkVersion libs.versions.android.compileSdk.get().toInteger() defaultConfig { - minSdkVersion Android.minSdkVersion - targetSdkVersion Android.targetSdkVersion + minSdkVersion libs.versions.android.minSdk.get().toInteger() + targetSdkVersion libs.versions.android.targetSdk.get().toInteger() externalNativeBuild { @@ -125,8 +125,8 @@ android { } } compileOptions { - sourceCompatibility(Android.javaVersion) - targetCompatibility(Android.javaVersion) + sourceCompatibility(libs.versions.android.java.get()) + targetCompatibility(libs.versions.android.java.get()) } buildTypes {