From 89fe0a94220e8c3b894e2fa05da78accaecb61b3 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:15:22 +0900 Subject: [PATCH 1/4] Add metadata tweak deps for fdroid --- android/app/build.gradle.kts | 76 ++++++++++++++----- android/app/proguard-rules.pro | 13 ++-- android/build.gradle.kts | 19 ++++- android/gemstone/build.gradle.kts | 13 +++- android/gradle/libs.versions.toml | 1 + android/reproducible/README.md | 2 +- android/reproducible/fdroid/build.sh | 39 ++++++++++ .../fdroid/cargo-config.toml.template | 7 ++ android/reproducible/fdroid/env.sh | 24 ++++++ android/reproducible/fdroid/init.sh | 17 +++++ .../fdroid/metadata/com.gemwallet.android.yml | 33 ++++++++ android/settings.gradle.kts | 8 +- 12 files changed, 223 insertions(+), 29 deletions(-) create mode 100755 android/reproducible/fdroid/build.sh create mode 100644 android/reproducible/fdroid/cargo-config.toml.template create mode 100755 android/reproducible/fdroid/env.sh create mode 100755 android/reproducible/fdroid/init.sh create mode 100644 android/reproducible/fdroid/metadata/com.gemwallet.android.yml diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index eb6ac95833..394e0b5ef2 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -8,7 +8,38 @@ plugins { id("kotlinx-serialization") id("com.google.devtools.ksp") id("androidx.room") - id("com.google.gms.google-services") +} + +val googleServicesExcludedFlavors = setOf("fdroid") +val googleServicesExcludedTaskPrefixes = googleServicesExcludedFlavors.map { flavor -> + "process${flavor.replaceFirstChar { it.uppercase() }}" +} +val googleServicesTasks = gradle.startParameter.taskNames + .map { it.substringAfterLast(":").lowercase() } + .filterNot { it == "clean" } +val shouldApplyGoogleServices = file("google-services.json").isFile && + ( + googleServicesTasks.isEmpty() || + googleServicesTasks.any { task -> + googleServicesExcludedFlavors.none { flavor -> task.contains(flavor) } || + task in setOf("assemble", "build", "bundle") || + task == "assemblerelease" || + task == "bundlerelease" + } + ) + +if (shouldApplyGoogleServices) { + apply(plugin = "com.google.gms.google-services") + + tasks.configureEach { + if ( + name.startsWith("process") && + name.endsWith("GoogleServices") && + googleServicesExcludedTaskPrefixes.any { prefix -> name.startsWith(prefix) } + ) { + enabled = false + } + } } repositories { @@ -19,7 +50,7 @@ repositories { android { namespace = "com.gemwallet.android" compileSdk = 37 - ndkVersion = "28.1.13356709" + ndkVersion = libs.versions.androidNdk.get() val channelDimension by extra("channel") flavorDimensions.add(channelDimension) @@ -57,6 +88,10 @@ android { create("fdroid") { dimension = channelDimension + ndk { + abiFilters.add("armeabi-v7a") + abiFilters.add("arm64-v8a") + } buildConfigField("String", "UPDATE_URL", "\"\"") } create("huawei") { @@ -138,7 +173,10 @@ android { } getByName("release") { - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) isMinifyEnabled = true isShrinkResources = true isDebuggable = false @@ -289,27 +327,29 @@ dependencies { implementation(libs.reorderable) - // Google Play - "googleImplementation"(project(":flavors:fcm")) - "googleImplementation"(project(":flavors:google-review")) - // Solana Store - "solanaImplementation"(project(":flavors:fcm")) - "solanaImplementation"(project(":flavors:review-stub")) - // Universal - "universalImplementation"(project(":flavors:fcm")) - "universalImplementation"(project(":flavors:google-review")) - // Samsung - "samsungImplementation"(project(":flavors:fcm")) - "samsungImplementation"(project(":flavors:review-stub")) + if (System.getenv("FDROID_BUILD") != "true") { + // Google Play + "googleImplementation"(project(":flavors:fcm")) + "googleImplementation"(project(":flavors:google-review")) + // Solana Store + "solanaImplementation"(project(":flavors:fcm")) + "solanaImplementation"(project(":flavors:review-stub")) + // Universal + "universalImplementation"(project(":flavors:fcm")) + "universalImplementation"(project(":flavors:google-review")) + // Samsung + "samsungImplementation"(project(":flavors:fcm")) + "samsungImplementation"(project(":flavors:review-stub")) + // emerald + "emeraldImplementation"(project(":flavors:fcm")) + "emeraldImplementation"(project(":flavors:review-stub")) + } // huawei "huaweiImplementation"(project(":flavors:pushes-stub")) "huaweiImplementation"(project(":flavors:review-stub")) // fdroid "fdroidImplementation"(project(":flavors:pushes-stub")) "fdroidImplementation"(project(":flavors:review-stub")) - // emerald - "emeraldImplementation"(project(":flavors:fcm")) - "emeraldImplementation"(project(":flavors:review-stub")) // Preview debugImplementation(libs.androidx.ui.tooling) diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index b2354d5d92..906253c79f 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -12,21 +12,24 @@ # public *; #} -# Preserve stack traces and fix R8 non-deterministic map-id for reproducible builds. --keepattributes SourceFile,LineNumberTable +# Keep SourceFile stable and avoid path-derived R8 SourceFile names. +-keepattributes SourceFile -renamesourcefileattribute SourceFile -verbose #-dontobfuscate -ignorewarnings -# These lines allow optimisation whilst preserving stack traces --optimizations !code/allocation/variable --optimizations !class/unboxing/enum +# Disable R8 optimization to keep pg-map-id and DEX output stable across clean builds. +-dontoptimize + +# Keep method names for stack traces while allowing shrinking. -keep,allowshrinking,allowoptimization class * { ; } -keepattributes Signature -dontwarn com.google.firebase.analytics.connector.AnalyticsConnector +-dontwarn com.google.firebase.** +-dontwarn com.google.android.gms.** -keep class com.gemwallet.android.** { *; } -keep class com.sun.jna.** { *; } diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 64106f0a47..d2d3cd670c 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -3,7 +3,6 @@ buildscript { gradlePluginPortal() google() mavenCentral() - mavenLocal() } dependencies { classpath(libs.gradle) @@ -20,6 +19,8 @@ plugins { alias(libs.plugins.compose.compiler) apply false } +val fdroidBuild = System.getenv("FDROID_BUILD") == "true" + allprojects { repositories { val propFile = File(rootDir.absolutePath, "local.properties") @@ -31,9 +32,19 @@ allprojects { } } } + val fdroidMaven = rootProject.file("fdroid-maven") + if (fdroidBuild && fdroidMaven.exists()) { + exclusiveContent { + forRepository { + maven { url = uri(fdroidMaven) } + } + filter { + includeModule("com.reown", "android-core") + } + } + } google() mavenCentral() - mavenLocal() maven { url = uri("https://jitpack.io") } } @@ -47,6 +58,10 @@ subprojects { lockAllConfigurations() } configurations.configureEach { + if (fdroidBuild) { + exclude(group = "com.google.firebase") + exclude(group = "com.google.android.gms") + } resolutionStrategy.activateDependencyLocking() } } diff --git a/android/gemstone/build.gradle.kts b/android/gemstone/build.gradle.kts index 426ea5fc8c..4369f56690 100644 --- a/android/gemstone/build.gradle.kts +++ b/android/gemstone/build.gradle.kts @@ -10,10 +10,21 @@ val rustSrcDir = gemstoneRoot.resolve("src") val cratesDir = rootProject.projectDir.resolve("../core/crates") val jniLibsDir = gemstoneSrc.resolve("main/jniLibs") val generatedKotlinDir = gemstoneSrc.resolve("main/java") +val defaultCargoNdkAbis = if (System.getenv("UNIT_TESTS") == "true") { + "x86_64" +} else { + "arm64-v8a,armeabi-v7a" +} +val cargoNdkTargets = (System.getenv("GEMSTONE_ANDROID_ABIS") ?: defaultCargoNdkAbis) + .split(",") + .map { it.trim() } + .filter { it.isNotEmpty() } + .joinToString(" ") { "-t $it" } android { namespace = "com.gemwallet.gemstone" compileSdk = 37 + ndkVersion = libs.versions.androidNdk.get() defaultConfig { minSdk = 28 @@ -76,7 +87,7 @@ val buildCargoNdk = tasks.register("buildCargoNdk") { inputs.dir(cratesDir) inputs.file(gemstoneRoot.resolve("Cargo.toml")) outputs.dir(jniLibsDir) - commandLine("/bin/sh", "-l", "-c", "cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -o ${jniLibsDir.absolutePath} build --lib") + commandLine("/bin/sh", "-l", "-c", "cargo ndk $cargoNdkTargets -o ${jniLibsDir.absolutePath} build --lib") } tasks.configureEach { diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index c7d4991f45..afc0a7bc70 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -31,6 +31,7 @@ google-services = "4.4.4" gradle = "9.2.1" ksp = "2.3.4" android-lib = "9.2.1" +androidNdk = "28.1.13356709" kotlin = "2.3.21" hiltAndroid = "2.59.2" hiltLifecycleViewModelCompose = "1.3.0" diff --git a/android/reproducible/README.md b/android/reproducible/README.md index b4bd19aba6..dd51ccc284 100644 --- a/android/reproducible/README.md +++ b/android/reproducible/README.md @@ -1,3 +1,3 @@ # Reproducible Builds -The previous Docker-based reproducible build tooling has been removed. A [Nix](https://nixos.org/)-based replacement build environment is being evaluated; this directory is a placeholder until that work lands. +F-Droid build-server helpers live in `fdroid/`. diff --git a/android/reproducible/fdroid/build.sh b/android/reproducible/fdroid/build.sh new file mode 100755 index 0000000000..4d9ff537ba --- /dev/null +++ b/android/reproducible/fdroid/build.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +export NDK_PATH="${NDK_PATH:?NDK_PATH must point to the Android NDK}" +source "$script_dir/env.sh" + +touch local.properties + +reown_version="1.6.13" +reown_dir="$PWD/fdroid-maven/com/reown/android-core/$reown_version" +mkdir -p "$reown_dir" + +curl -L -sS --fail --retry 3 \ + -o "$reown_dir/android-core-$reown_version.aar" \ + "https://repo1.maven.org/maven2/com/reown/android-core/$reown_version/android-core-$reown_version.aar" +curl -L -sS --fail --retry 3 \ + -o "$reown_dir/android-core-$reown_version.pom" \ + "https://repo1.maven.org/maven2/com/reown/android-core/$reown_version/android-core-$reown_version.pom" + +reown_aar="$reown_dir/android-core-$reown_version.aar" +tmp_reown="$(mktemp -d)" +trap 'rm -rf "$tmp_reown"' EXIT + +mkdir -p "$tmp_reown/aar" "$tmp_reown/jar" +(cd "$tmp_reown/aar" && jar xf "$reown_aar") +( + cd "$tmp_reown/jar" + jar xf "$tmp_reown/aar/classes.jar" + # Gem F-Droid does not use Reown's Firebase push service. + rm -f com/reown/android/push/notifications/PushMessagingService*.class + jar cf "$tmp_reown/classes.jar" . +) +cp "$tmp_reown/classes.jar" "$tmp_reown/aar/classes.jar" +(cd "$tmp_reown/aar" && jar cf "$reown_aar" .) + +rm -rf "$HOME/.gradle/caches/transforms-"* +gradle --no-daemon --console plain --no-configuration-cache :app:assembleFdroidRelease diff --git a/android/reproducible/fdroid/cargo-config.toml.template b/android/reproducible/fdroid/cargo-config.toml.template new file mode 100644 index 0000000000..6b96d417c6 --- /dev/null +++ b/android/reproducible/fdroid/cargo-config.toml.template @@ -0,0 +1,7 @@ +[target.aarch64-linux-android] +ar = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" +linker = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang" + +[target.armv7-linux-androideabi] +ar = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" +linker = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi28-clang" diff --git a/android/reproducible/fdroid/env.sh b/android/reproducible/fdroid/env.sh new file mode 100755 index 0000000000..0f28c18155 --- /dev/null +++ b/android/reproducible/fdroid/env.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$script_dir/../../.." && pwd)" + +source "$HOME/.cargo/env" + +export FDROID_BUILD=true +export SKIP_SIGN=true +export CARGO_NET_GIT_FETCH_WITH_CLI=true +export NDK_TOOLCHAIN_DIR="$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin" + +export AR_aarch64_linux_android="$NDK_TOOLCHAIN_DIR/llvm-ar" +export AR_armv7_linux_androideabi="$NDK_TOOLCHAIN_DIR/llvm-ar" + +export CC_aarch64_linux_android="$NDK_TOOLCHAIN_DIR/aarch64-linux-android28-clang" +export CC_armv7_linux_androideabi="$NDK_TOOLCHAIN_DIR/armv7a-linux-androideabi28-clang" + +export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$CC_aarch64_linux_android" +export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$CC_armv7_linux_androideabi" +export GEMSTONE_ANDROID_ABIS="arm64-v8a,armeabi-v7a" + +export RUSTFLAGS="${RUSTFLAGS:-} --remap-path-prefix=$repo_root=. --remap-path-prefix=$HOME=FDROID_HOME" diff --git a/android/reproducible/fdroid/init.sh b/android/reproducible/fdroid/init.sh new file mode 100755 index 0000000000..7e406593b3 --- /dev/null +++ b/android/reproducible/fdroid/init.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euxo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +curl -sfL https://sh.rustup.rs -o /tmp/rustup.sh +chmod +x /tmp/rustup.sh +/tmp/rustup.sh -y --profile minimal --default-toolchain 1.91.0 + +source "$HOME/.cargo/env" +rustup target add \ + aarch64-linux-android \ + armv7-linux-androideabi + +cargo install --locked cargo-ndk@4.1.2 +mkdir -p "$HOME/.cargo" +sed -e "s|{NDK_PATH}|$NDK_PATH|g" "$script_dir/cargo-config.toml.template" > "$HOME/.cargo/config.toml" diff --git a/android/reproducible/fdroid/metadata/com.gemwallet.android.yml b/android/reproducible/fdroid/metadata/com.gemwallet.android.yml new file mode 100644 index 0000000000..677033559b --- /dev/null +++ b/android/reproducible/fdroid/metadata/com.gemwallet.android.yml @@ -0,0 +1,33 @@ +Categories: + - Wallet +License: GPL-3.0-only +WebSite: https://gemwallet.com +SourceCode: https://github.com/gemwalletcom/wallet +IssueTracker: https://github.com/gemwalletcom/wallet/issues + +AutoName: Gem Wallet + +RepoType: git +Repo: https://github.com/gemwalletcom/wallet.git + +Builds: + - versionName: '2.87' + versionCode: 786 + commit: REPLACE_WITH_FULL_RELEASE_COMMIT_SHA + timeout: 10800 + subdir: android + sudo: apt-get install -y build-essential just + init: NDK_PATH="$$NDK$$" reproducible/fdroid/init.sh + output: app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk + rm: + - android/flavors/fcm + - android/flavors/google-review + - core/gemstone/android/settings.gradle.kts + - core/gemstone/tests/android/GemTest + build: NDK_PATH="$$NDK$$" reproducible/fdroid/build.sh + ndk: 28.1.13356709 + +AutoUpdateMode: Version +UpdateCheckMode: Tags ^[0-9]+\.[0-9]+$ +CurrentVersion: '2.87' +CurrentVersionCode: 786 diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 835443ab96..348e371b8c 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -6,6 +6,8 @@ pluginManagement { } } rootProject.name = "wallet" +val fdroidBuild = System.getenv("FDROID_BUILD") == "true" + include(":gemstone") include (":app") include(":blockchain") @@ -19,8 +21,10 @@ include(":data:repositories") include(":data:services:remote-gem") include(":flavors") include(":flavors:pushes-stub") -include(":flavors:fcm") -include(":flavors:google-review") +if (!fdroidBuild) { + include(":flavors:fcm") + include(":flavors:google-review") +} include(":flavors:review-stub") include(":features") include(":features:recipient") From 26e73461a7d4486868527c9c66612cdb65714f89 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:44:04 +0900 Subject: [PATCH 2/4] make WalletConnect optional for fdroid --- android/app/build.gradle.kts | 2 + .../com/gemwallet/android/MainActivity.kt | 1 + .../com/gemwallet/android/MainContent.kt | 26 ++-- .../com/gemwallet/android/MainViewModel.kt | 9 +- .../android/features/main/views/MainScreen.kt | 2 + android/build.gradle.kts | 11 -- android/data/repositories/build.gradle.kts | 16 +- .../features/bridge/presents/build.gradle.kts | 14 +- .../bridge/viewmodels/build.gradle.kts | 12 +- .../settings/presents/views/SettingsScene.kt | 15 +- .../walletconnect-stub/build.gradle.kts | 25 +++ .../src/main/kotlin/com/reown/android/Core.kt | 37 +++++ .../com/reown/android/relay/ConnectionType.kt | 5 + .../com/reown/walletkit/client/Wallet.kt | 143 ++++++++++++++++++ .../com/reown/walletkit/client/WalletKit.kt | 113 ++++++++++++++ android/reproducible/fdroid/build.sh | 27 ---- android/settings.gradle.kts | 1 + 17 files changed, 393 insertions(+), 66 deletions(-) create mode 100644 android/flavors/walletconnect-stub/build.gradle.kts create mode 100644 android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/Core.kt create mode 100644 android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/relay/ConnectionType.kt create mode 100644 android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/Wallet.kt create mode 100644 android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/WalletKit.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 394e0b5ef2..bd5e5da87f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -66,6 +66,7 @@ android { vectorDrawables { useSupportLibrary = true } + buildConfigField("boolean", "WALLET_CONNECT_ENABLED", "true") splits { abi { @@ -93,6 +94,7 @@ android { abiFilters.add("arm64-v8a") } buildConfigField("String", "UPDATE_URL", "\"\"") + buildConfigField("boolean", "WALLET_CONNECT_ENABLED", "false") } create("huawei") { dimension = channelDimension diff --git a/android/app/src/main/kotlin/com/gemwallet/android/MainActivity.kt b/android/app/src/main/kotlin/com/gemwallet/android/MainActivity.kt index ea27aaa77d..ca8115700c 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/MainActivity.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/MainActivity.kt @@ -45,6 +45,7 @@ class MainActivity : FragmentActivity(), AuthRequester { pendingNavigation = pendingNavigation, systemAuthEnrollmentMissing = systemAuthEnrollmentMissing, walletConnectViewModel = walletConnectViewModel, + walletConnectEnabled = BuildConfig.WALLET_CONNECT_ENABLED, onSystemAuthRequired = systemAuthenticator::authenticate, onIntentConsumed = viewModel::consumePendingNavigation, onOpenSystemAuthSettings = systemAuthenticator::openSettings, diff --git a/android/app/src/main/kotlin/com/gemwallet/android/MainContent.kt b/android/app/src/main/kotlin/com/gemwallet/android/MainContent.kt index 6d2fc4c7a5..90eeb28f4e 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/MainContent.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/MainContent.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.gemwallet.android.model.AuthState import com.gemwallet.android.ui.WalletApp +import com.gemwallet.android.ui.models.actions.AssetIdAction import com.gemwallet.android.ui.theme.WalletTheme @Composable @@ -19,6 +20,7 @@ internal fun MainContent( pendingNavigation: PendingNavigation?, systemAuthEnrollmentMissing: Boolean, walletConnectViewModel: WalletConnectViewModel, + walletConnectEnabled: Boolean, onSystemAuthRequired: () -> Unit, onIntentConsumed: () -> Unit, onOpenSystemAuthSettings: () -> Unit, @@ -32,7 +34,11 @@ internal fun MainContent( val isWalletUnlocked = state.initialAuth == AuthState.Success val isEnrollmentRequired = state.initialAuth == AuthState.Required && systemAuthEnrollmentMissing val unlockedPendingRoutes = if (isWalletUnlocked) pendingRoutes else emptyList() - val walletConnectOverlay = rememberWalletConnectOverlay(walletConnectViewModel, onWalletConnectError) + val walletConnectOverlay: @Composable (AssetIdAction) -> Unit = if (walletConnectEnabled) { + rememberWalletConnectOverlay(walletConnectViewModel, onWalletConnectError) + } else { + remember { { _: AssetIdAction -> } } + } var isWalletContentReady by remember { mutableStateOf(state.hasUnlockedApp) } val onWalletContentReady: () -> Unit = remember { { isWalletContentReady = true } } val shouldShowLockedSplash = !isWalletUnlocked || !isWalletContentReady @@ -62,13 +68,15 @@ internal fun MainContent( } } - WalletConnectPairingToast( - visible = state.isWalletConnectPairingToastVisible, - onShown = onWalletConnectPairingToastShown, - ) - WalletConnectErrorDialog( - error = state.walletConnectError, - onDismiss = onWalletConnectErrorDismiss, - ) + if (walletConnectEnabled) { + WalletConnectPairingToast( + visible = state.isWalletConnectPairingToastVisible, + onShown = onWalletConnectPairingToastShown, + ) + WalletConnectErrorDialog( + error = state.walletConnectError, + onDismiss = onWalletConnectErrorDismiss, + ) + } } } diff --git a/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt b/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt index 3aabe69935..eb867069be 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt @@ -50,7 +50,11 @@ class MainViewModel @Inject constructor( private val walletConnectHandler = object : PendingNavigationCoordinator.WalletConnectHandler { override fun onPairing(uri: String) = addPairing(uri) - override fun onRequest() = showWalletConnectPairingToast() + override fun onRequest() { + if (BuildConfig.WALLET_CONNECT_ENABLED) { + showWalletConnectPairingToast() + } + } } init { @@ -157,6 +161,9 @@ class MainViewModel @Inject constructor( } private fun addPairing(uri: String) { + if (!BuildConfig.WALLET_CONNECT_ENABLED) { + return + } showWalletConnectPairingToast() viewModelScope.launch(Dispatchers.IO) { bridgesRepository.addPairing( diff --git a/android/app/src/main/kotlin/com/gemwallet/android/features/main/views/MainScreen.kt b/android/app/src/main/kotlin/com/gemwallet/android/features/main/views/MainScreen.kt index 394c175135..05d34390a0 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/features/main/views/MainScreen.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/features/main/views/MainScreen.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.gemwallet.android.BuildConfig import com.gemwallet.android.features.activities.presents.list.TransactionsNavScreen import com.gemwallet.android.features.assets.viewmodels.AssetsViewModel import com.gemwallet.android.features.assets.views.AssetsScreen @@ -223,6 +224,7 @@ fun MainScreen( scrollState = settingsScrollState, onSecurity = navigator::openSecurity, onBridges = navigator::openBridgeConnections, + walletConnectAvailable = BuildConfig.WALLET_CONNECT_ENABLED, onDevelop = navigator::openDevelop, onWallets = navigator::openWallets, onNotifications = navigator::openNotifications, diff --git a/android/build.gradle.kts b/android/build.gradle.kts index d2d3cd670c..c386fb2bc3 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -32,17 +32,6 @@ allprojects { } } } - val fdroidMaven = rootProject.file("fdroid-maven") - if (fdroidBuild && fdroidMaven.exists()) { - exclusiveContent { - forRepository { - maven { url = uri(fdroidMaven) } - } - filter { - includeModule("com.reown", "android-core") - } - } - } google() mavenCentral() maven { url = uri("https://jitpack.io") } diff --git a/android/data/repositories/build.gradle.kts b/android/data/repositories/build.gradle.kts index 62a95ea197..ce2d610ddd 100644 --- a/android/data/repositories/build.gradle.kts +++ b/android/data/repositories/build.gradle.kts @@ -1,6 +1,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile +val fdroidBuild = System.getenv("FDROID_BUILD") == "true" + plugins { alias(libs.plugins.android.library) id("com.google.devtools.ksp") @@ -55,17 +57,21 @@ dependencies { implementation(project(":data:services:store")) api(project(":data:services:remote-gem")) - // Wallet Connect - api(platform(libs.walletconnect.bom)) - api(libs.walletconnect.core) { - exclude(group = "com.jakewharton.timber", module = "timber") + if (fdroidBuild) { + api(project(":flavors:walletconnect-stub")) + } else { + api(platform(libs.walletconnect.bom)) + api(libs.walletconnect.core) { + exclude(group = "com.jakewharton.timber", module = "timber") + } + api(libs.walletconnect.web3wallet) } - api(libs.walletconnect.web3wallet) implementation(libs.hilt.android) ksp(libs.hilt.compiler) implementation(libs.datastore) + implementation(libs.androidx.security.crypto) api(libs.ktor.core) api(libs.ktor.cio) diff --git a/android/features/bridge/presents/build.gradle.kts b/android/features/bridge/presents/build.gradle.kts index 5ca8b2dd46..3dc37d2c97 100644 --- a/android/features/bridge/presents/build.gradle.kts +++ b/android/features/bridge/presents/build.gradle.kts @@ -1,6 +1,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile +val fdroidBuild = System.getenv("FDROID_BUILD") == "true" + plugins { alias(libs.plugins.android.library) alias(libs.plugins.compose.compiler) @@ -56,9 +58,13 @@ dependencies { implementation(project(":features:bridge:viewmodels")) implementation(project(":features:confirm:presents")) - api(platform(libs.walletconnect.bom)) - api(libs.walletconnect.core) - api(libs.walletconnect.web3wallet) + if (fdroidBuild) { + api(project(":flavors:walletconnect-stub")) + } else { + api(platform(libs.walletconnect.bom)) + api(libs.walletconnect.core) + api(libs.walletconnect.web3wallet) + } implementation(libs.hilt.android) ksp(libs.hilt.compiler) @@ -71,4 +77,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} diff --git a/android/features/bridge/viewmodels/build.gradle.kts b/android/features/bridge/viewmodels/build.gradle.kts index 47278afa12..4fd78baa9a 100644 --- a/android/features/bridge/viewmodels/build.gradle.kts +++ b/android/features/bridge/viewmodels/build.gradle.kts @@ -1,6 +1,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile +val fdroidBuild = System.getenv("FDROID_BUILD") == "true" + plugins { alias(libs.plugins.android.library) id("com.google.devtools.ksp") @@ -52,9 +54,13 @@ dependencies { implementation(project(":ui")) implementation(project(":data:repositories")) - api(platform(libs.walletconnect.bom)) - api(libs.walletconnect.core) - api(libs.walletconnect.web3wallet) + if (fdroidBuild) { + api(project(":flavors:walletconnect-stub")) + } else { + api(platform(libs.walletconnect.bom)) + api(libs.walletconnect.core) + api(libs.walletconnect.web3wallet) + } implementation(libs.hilt.android) ksp(libs.hilt.compiler) diff --git a/android/features/settings/settings/presents/src/main/kotlin/com/gemwallet/android/features/settings/settings/presents/views/SettingsScene.kt b/android/features/settings/settings/presents/src/main/kotlin/com/gemwallet/android/features/settings/settings/presents/views/SettingsScene.kt index 44ebc04fdf..7e6344afbb 100644 --- a/android/features/settings/settings/presents/src/main/kotlin/com/gemwallet/android/features/settings/settings/presents/views/SettingsScene.kt +++ b/android/features/settings/settings/presents/src/main/kotlin/com/gemwallet/android/features/settings/settings/presents/views/SettingsScene.kt @@ -44,6 +44,7 @@ import com.gemwallet.android.ui.models.ListPosition fun SettingsScene( onSecurity: () -> Unit, onBridges: () -> Unit, + walletConnectAvailable: Boolean = true, onDevelop: () -> Unit, onWallets: () -> Unit, onAboutUs: () -> Unit, @@ -112,12 +113,14 @@ fun SettingsScene( onClick = onPreferences, ) } - LinkItem( - title = stringResource(id = R.string.wallet_connect_title), - icon = R.drawable.settings_wc, - listPosition = ListPosition.Single, - ) { - onBridges() + if (walletConnectAvailable) { + LinkItem( + title = stringResource(id = R.string.wallet_connect_title), + icon = R.drawable.settings_wc, + listPosition = ListPosition.Single, + ) { + onBridges() + } } LinkItem( diff --git a/android/flavors/walletconnect-stub/build.gradle.kts b/android/flavors/walletconnect-stub/build.gradle.kts new file mode 100644 index 0000000000..a4248d68ce --- /dev/null +++ b/android/flavors/walletconnect-stub/build.gradle.kts @@ -0,0 +1,25 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile + +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "com.gemwallet.android.flavors.walletconnect.stub" + compileSdk = 37 + + defaultConfig { + minSdk = 28 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } +} diff --git a/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/Core.kt b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/Core.kt new file mode 100644 index 0000000000..8b87a866b4 --- /dev/null +++ b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/Core.kt @@ -0,0 +1,37 @@ +package com.reown.android + +import android.app.Application +import com.reown.android.relay.ConnectionType +import com.reown.walletkit.client.Wallet + +object Core { + object Model { + data class AppMetaData( + val name: String, + val description: String, + val url: String, + val icons: List, + val redirect: String? = null, + ) + } +} + +object CoreClient { + interface CoreDelegate { + fun onConnectionStateChange(state: Wallet.Model.ConnectionState) + fun onError(error: Wallet.Model.Error) + } + + fun initialize( + application: Application, + projectId: String, + metaData: Core.Model.AppMetaData, + connectionType: ConnectionType, + telemetryEnabled: Boolean, + onSuccess: () -> Unit, + ) { + onSuccess() + } + + fun setDelegate(delegate: CoreDelegate) = Unit +} diff --git a/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/relay/ConnectionType.kt b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/relay/ConnectionType.kt new file mode 100644 index 0000000000..c0b1e98fb8 --- /dev/null +++ b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/android/relay/ConnectionType.kt @@ -0,0 +1,5 @@ +package com.reown.android.relay + +enum class ConnectionType { + AUTOMATIC, +} diff --git a/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/Wallet.kt b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/Wallet.kt new file mode 100644 index 0000000000..90e9d3ab8d --- /dev/null +++ b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/Wallet.kt @@ -0,0 +1,143 @@ +package com.reown.walletkit.client + +import com.reown.android.Core + +object Wallet { + sealed interface Model { + data class Error(val throwable: Throwable) : Model + data class ConnectionState(val isAvailable: Boolean) : Model + data class ExpiredProposal(val id: Long = 0) : Model + data class ExpiredRequest(val topic: String = "", val id: Long = 0) : Model + data class SessionUpdateResponse(val topic: String = "") : Model + + sealed interface SessionDelete : Model { + data class Success(val topic: String) : SessionDelete + } + + sealed interface SettledSessionResponse : Model { + data class Result(val session: Session) : SettledSessionResponse + } + + data class Session( + val topic: String, + val expiry: Long, + val metaData: Core.Model.AppMetaData?, + val namespaces: Map, + val redirect: String? = null, + ) : Model + + data class SessionProposal( + val name: String, + val description: String, + val url: String, + val icons: List, + val requiredNamespaces: Map, + val optionalNamespaces: Map, + val proposerPublicKey: String, + val properties: Map? = null, + ) : Model + + data class SessionRequest( + val topic: String, + val chainId: String?, + val request: JsonRpcRequest, + ) : Model + + data class SessionAuthenticate( + val id: Long, + val participant: Participant, + val payloadParams: PayloadAuthRequestParams, + ) : Model + + data class Participant( + val metadata: Core.Model.AppMetaData?, + ) + + data class VerifyContext( + val origin: String, + val validation: Validation, + val isScam: Boolean? = null, + ) + + enum class Validation { + VALID, + INVALID, + UNKNOWN, + } + + data class JsonRpcRequest( + val id: Long, + val method: String, + val params: String, + ) + + sealed interface JsonRpcResponse { + data class JsonRpcResult(val id: Long, val result: String) : JsonRpcResponse + data class JsonRpcError(val id: Long, val code: Int, val message: String) : JsonRpcResponse + } + + object Namespace { + data class Proposal( + val chains: List? = null, + ) + + data class Session( + val chains: List? = null, + val methods: List, + val events: List, + val accounts: List, + ) + } + + data class PayloadAuthRequestParams( + val chains: List, + val methods: List = emptyList(), + ) + + data class Cacao( + val signature: Signature? = null, + ) { + data class Signature( + val t: String, + val s: String, + ) + } + } + + object Params { + data class Init(val core: Any) + data class Pair(val uri: String) + data class Ping(val topic: String) + data class SessionDisconnect(val sessionTopic: String) + data class SessionRequestResponse( + val sessionTopic: String, + val jsonRpcResponse: Model.JsonRpcResponse, + ) + + data class SessionApprove( + val proposerPublicKey: String, + val namespaces: Map, + val properties: Map, + ) + + data class SessionReject( + val proposerPublicKey: String, + val reason: String, + ) + + data class ApproveSessionAuthenticate( + val id: Long, + val auths: List, + ) + + data class RejectSessionAuthenticate( + val id: Long, + val reason: String, + ) + + data class FormatAuthMessage( + val payloadParams: Model.PayloadAuthRequestParams, + val issuer: String, + ) + } +} diff --git a/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/WalletKit.kt b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/WalletKit.kt new file mode 100644 index 0000000000..ff53edce66 --- /dev/null +++ b/android/flavors/walletconnect-stub/src/main/kotlin/com/reown/walletkit/client/WalletKit.kt @@ -0,0 +1,113 @@ +package com.reown.walletkit.client + +object WalletKit { + interface WalletDelegate { + val onSessionAuthenticate: (Wallet.Model.SessionAuthenticate, Wallet.Model.VerifyContext) -> Unit + fun onProposalExpired(proposal: Wallet.Model.ExpiredProposal) + fun onRequestExpired(request: Wallet.Model.ExpiredRequest) + fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) + fun onSessionExtend(session: Wallet.Model.Session) + fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal, verifyContext: Wallet.Model.VerifyContext) + fun onSessionRequest(sessionRequest: Wallet.Model.SessionRequest, verifyContext: Wallet.Model.VerifyContext) + fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) + fun onSessionUpdateResponse(sessionUpdateResponse: Wallet.Model.SessionUpdateResponse) + } + + fun initialize( + params: Wallet.Params.Init, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onSuccess() + } + + fun setWalletDelegate(delegate: WalletDelegate) = Unit + + fun respondSessionRequest( + params: Wallet.Params.SessionRequestResponse, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onSuccess() + } + + fun getPendingListOfSessionRequests(topic: String): List = emptyList() + + fun getVerifyContext(id: Long): Wallet.Model.VerifyContext? = null + + fun pingSession(params: Wallet.Params.Ping, onError: ((Wallet.Model.Error) -> Unit)?) = Unit + + fun getListOfActiveSessions(): List = emptyList() + + fun disconnectSession( + params: Wallet.Params.SessionDisconnect, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onSuccess() + } + + fun pair( + params: Wallet.Params.Pair, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onError(Wallet.Model.Error(UnsupportedOperationException("WalletConnect is not available in this build"))) + } + + fun generateApprovedNamespaces( + sessionProposal: Wallet.Model.SessionProposal, + supportedNamespaces: Map, + ): Map = supportedNamespaces + + fun approveSession( + params: Wallet.Params.SessionApprove, + onError: (Wallet.Model.Error) -> Unit, + onSuccess: () -> Unit, + ) { + onError(Wallet.Model.Error(UnsupportedOperationException("WalletConnect is not available in this build"))) + } + + fun rejectSession( + params: Wallet.Params.SessionReject, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onSuccess() + } + + fun approveSessionAuthenticate( + params: Wallet.Params.ApproveSessionAuthenticate, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onError(Wallet.Model.Error(UnsupportedOperationException("WalletConnect is not available in this build"))) + } + + fun rejectSessionAuthenticate( + params: Wallet.Params.RejectSessionAuthenticate, + onSuccess: () -> Unit, + onError: (Wallet.Model.Error) -> Unit, + ) { + onSuccess() + } + + fun getSessionProposals(): List = emptyList() + + fun generateAuthPayloadParams( + payloadParams: Wallet.Model.PayloadAuthRequestParams, + supportedChains: List, + supportedMethods: List, + ): Wallet.Model.PayloadAuthRequestParams = Wallet.Model.PayloadAuthRequestParams( + chains = payloadParams.chains.filter { it in supportedChains }, + methods = supportedMethods, + ) + + fun formatAuthMessage(params: Wallet.Params.FormatAuthMessage): String = "" + + fun generateAuthObject( + payloadParams: Wallet.Model.PayloadAuthRequestParams, + issuer: String, + signature: Wallet.Model.Cacao.Signature, + ): Wallet.Model.Cacao = Wallet.Model.Cacao(signature) +} diff --git a/android/reproducible/fdroid/build.sh b/android/reproducible/fdroid/build.sh index 4d9ff537ba..d43914e130 100755 --- a/android/reproducible/fdroid/build.sh +++ b/android/reproducible/fdroid/build.sh @@ -8,32 +8,5 @@ source "$script_dir/env.sh" touch local.properties -reown_version="1.6.13" -reown_dir="$PWD/fdroid-maven/com/reown/android-core/$reown_version" -mkdir -p "$reown_dir" - -curl -L -sS --fail --retry 3 \ - -o "$reown_dir/android-core-$reown_version.aar" \ - "https://repo1.maven.org/maven2/com/reown/android-core/$reown_version/android-core-$reown_version.aar" -curl -L -sS --fail --retry 3 \ - -o "$reown_dir/android-core-$reown_version.pom" \ - "https://repo1.maven.org/maven2/com/reown/android-core/$reown_version/android-core-$reown_version.pom" - -reown_aar="$reown_dir/android-core-$reown_version.aar" -tmp_reown="$(mktemp -d)" -trap 'rm -rf "$tmp_reown"' EXIT - -mkdir -p "$tmp_reown/aar" "$tmp_reown/jar" -(cd "$tmp_reown/aar" && jar xf "$reown_aar") -( - cd "$tmp_reown/jar" - jar xf "$tmp_reown/aar/classes.jar" - # Gem F-Droid does not use Reown's Firebase push service. - rm -f com/reown/android/push/notifications/PushMessagingService*.class - jar cf "$tmp_reown/classes.jar" . -) -cp "$tmp_reown/classes.jar" "$tmp_reown/aar/classes.jar" -(cd "$tmp_reown/aar" && jar cf "$reown_aar" .) - rm -rf "$HOME/.gradle/caches/transforms-"* gradle --no-daemon --console plain --no-configuration-cache :app:assembleFdroidRelease diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 348e371b8c..d3818e51a4 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -21,6 +21,7 @@ include(":data:repositories") include(":data:services:remote-gem") include(":flavors") include(":flavors:pushes-stub") +include(":flavors:walletconnect-stub") if (!fdroidBuild) { include(":flavors:fcm") include(":flavors:google-review") From d5c398ea6e931c6b3e61dcf13efb84986b3ec805 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:31:55 +0900 Subject: [PATCH 3/4] Address F-Droid metadata review --- .../metadata/android/en-US/changelogs/786.txt | 1 + .../android/en-US/full_description.txt | 22 +++++++++++++ .../android/en-US/short_description.txt | 1 + .../fastlane/metadata/android/en-US/title.txt | 1 + android/justfile | 3 ++ android/reproducible/README.md | 2 +- .../fdroid/metadata/com.gemwallet.android.yml | 33 ------------------- 7 files changed, 29 insertions(+), 34 deletions(-) create mode 100644 android/fastlane/metadata/android/en-US/changelogs/786.txt create mode 100644 android/fastlane/metadata/android/en-US/full_description.txt create mode 100644 android/fastlane/metadata/android/en-US/short_description.txt create mode 100644 android/fastlane/metadata/android/en-US/title.txt delete mode 100644 android/reproducible/fdroid/metadata/com.gemwallet.android.yml diff --git a/android/fastlane/metadata/android/en-US/changelogs/786.txt b/android/fastlane/metadata/android/en-US/changelogs/786.txt new file mode 100644 index 0000000000..8fe46188a5 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/786.txt @@ -0,0 +1 @@ +Initial F-Droid release. diff --git a/android/fastlane/metadata/android/en-US/full_description.txt b/android/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000000..8204f535ad --- /dev/null +++ b/android/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,22 @@ +Gem Wallet is an open-source, non-custodial crypto wallet for managing assets across 100+ blockchains. No personal account is required: back up your recovery phrase and stay in control of your funds. + +# Wallet Features +- Send and receive Bitcoin, Ethereum, Solana, USDT, USDC, BNB, XRP, Tron, and many other assets. +- Manage multiple wallets and accounts in one app. +- Protect access with local encryption, PIN protection, and biometric authentication. +- Add custom tokens and custom RPC nodes. +- View balances, transactions, NFTs, staking, and market data. + +# DeFi and Payments +- Swap tokens through integrated decentralized exchange providers. +- Use QR codes and saved contacts for payments. +- Stake supported assets and track rewards. +- Review transaction details before signing. + +# Privacy and Control +- Non-custodial design keeps private keys on your device. +- No registration or personal account is required. +- Open-source code is available for review. + +Support: support@gemwallet.com +Privacy Policy: https://gemwallet.com/privacy diff --git a/android/fastlane/metadata/android/en-US/short_description.txt b/android/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000000..05fcb77757 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Secure crypto wallet for 100+ blockchains: Bitcoin, USDT, ETH, Solana, and more. diff --git a/android/fastlane/metadata/android/en-US/title.txt b/android/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 0000000000..f77cf65699 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Gem Wallet: Bitcoin, USDT, BNB diff --git a/android/justfile b/android/justfile index 79863eea36..1bc5dac631 100644 --- a/android/justfile +++ b/android/justfile @@ -85,6 +85,9 @@ mobsfscan: release: @./gradlew clean :app:bundleGoogleRelease assembleUniversalRelease assembleHuaweiRelease assembleSolanaRelease assembleSamsungRelease --no-configuration-cache +release-fdroid: + @FDROID_BUILD=true ./gradlew clean assembleFdroidRelease --no-configuration-cache + localize: @cd ../core && cargo run --package generate --bin generate localize android ../localization/app ../android/ui/src/main/res diff --git a/android/reproducible/README.md b/android/reproducible/README.md index dd51ccc284..482bec8151 100644 --- a/android/reproducible/README.md +++ b/android/reproducible/README.md @@ -1,3 +1,3 @@ -# Reproducible Builds +# F-Droid Builds F-Droid build-server helpers live in `fdroid/`. diff --git a/android/reproducible/fdroid/metadata/com.gemwallet.android.yml b/android/reproducible/fdroid/metadata/com.gemwallet.android.yml deleted file mode 100644 index 677033559b..0000000000 --- a/android/reproducible/fdroid/metadata/com.gemwallet.android.yml +++ /dev/null @@ -1,33 +0,0 @@ -Categories: - - Wallet -License: GPL-3.0-only -WebSite: https://gemwallet.com -SourceCode: https://github.com/gemwalletcom/wallet -IssueTracker: https://github.com/gemwalletcom/wallet/issues - -AutoName: Gem Wallet - -RepoType: git -Repo: https://github.com/gemwalletcom/wallet.git - -Builds: - - versionName: '2.87' - versionCode: 786 - commit: REPLACE_WITH_FULL_RELEASE_COMMIT_SHA - timeout: 10800 - subdir: android - sudo: apt-get install -y build-essential just - init: NDK_PATH="$$NDK$$" reproducible/fdroid/init.sh - output: app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk - rm: - - android/flavors/fcm - - android/flavors/google-review - - core/gemstone/android/settings.gradle.kts - - core/gemstone/tests/android/GemTest - build: NDK_PATH="$$NDK$$" reproducible/fdroid/build.sh - ndk: 28.1.13356709 - -AutoUpdateMode: Version -UpdateCheckMode: Tags ^[0-9]+\.[0-9]+$ -CurrentVersion: '2.87' -CurrentVersionCode: 786 From aab3789a02c91860147e47f088d5a2041c1ff144 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Fri, 12 Jun 2026 23:47:11 +0900 Subject: [PATCH 4/4] Build F-Droid APK on tag release --- .github/workflows/release_on_tag.yml | 77 +++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_on_tag.yml b/.github/workflows/release_on_tag.yml index c724b288c9..6bf82087b6 100644 --- a/.github/workflows/release_on_tag.yml +++ b/.github/workflows/release_on_tag.yml @@ -4,18 +4,91 @@ on: push: tags: - "*" + release: + types: [published] permissions: contents: write + packages: read + +env: + RELEASE_TAG: ${{ github.event.release.tag_name || github.ref_name }} jobs: create-release: + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - name: Create release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: - name: ${{ github.ref_name }} - tag_name: ${{ github.ref_name }} + name: ${{ env.RELEASE_TAG }} + tag_name: ${{ env.RELEASE_TAG }} draft: false generate_release_notes: true + + build-fdroid-apk: + name: Build F-Droid APK + needs: create-release + if: ${{ always() && (github.event_name == 'release' || needs.create-release.result == 'success') }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ env.RELEASE_TAG }} + + - name: Set up JDK + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.0.0 + with: + distribution: temurin + java-version: "17" + + - name: Set up Rust + uses: ./.github/actions/setup-rust-ci + with: + shared-key: fdroid-release + + - name: Install Linux build dependencies + run: sudo apt-get update && sudo apt-get install -y build-essential + + - name: Install Android toolchains + working-directory: android + run: | + just install-toolchains + just install-ndk + + - name: Prepare signing + env: + ANDROID_FDROID_KEYSTORE_CONTENT_BASE64: ${{ secrets.ANDROID_FDROID_KEYSTORE_CONTENT_BASE64 }} + run: | + set -euo pipefail + : "${ANDROID_FDROID_KEYSTORE_CONTENT_BASE64:?ANDROID_FDROID_KEYSTORE_CONTENT_BASE64 is required}" + touch android/local.properties + echo -n "$ANDROID_FDROID_KEYSTORE_CONTENT_BASE64" | base64 --decode > android/app/fdroid-release.keystore + + - name: Build F-Droid APK + working-directory: android + env: + ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_FDROID_KEYSTORE_ALIAS }} + ANDROID_KEYSTORE_FILENAME: fdroid-release.keystore + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_FDROID_KEYSTORE_PASSWORD }} + GPR_USERNAME: ${{ github.actor }} + GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: just release-fdroid + + - name: Prepare release asset + id: asset + run: | + set -euo pipefail + apk="android/app/build/outputs/apk/fdroid/release/app-fdroid-release.apk" + asset="android/app/build/outputs/apk/fdroid/release/gem_wallet_fdroid_${RELEASE_TAG}.apk" + test -f "$apk" + cp "$apk" "$asset" + echo "path=$asset" >> "$GITHUB_OUTPUT" + + - name: Upload F-Droid APK + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + with: + tag_name: ${{ env.RELEASE_TAG }} + files: ${{ steps.asset.outputs.path }}