Files
pezkuwi-wallet-android/app/proguard-rules.pro
T
SatoshiQaziMuhammed 35c71062f0 Strip native libraries; XCM by genesis; CodeQL for Kotlin; bridge credits in history (#31)
* Strip native libraries in the app and keep light client line tables

The app module named no NDK, so AGP expected its default 27.0 against the 26.1 that CI installs,
could not strip any native library and made no native-debug-symbols.zip for Play. Pin 26.1 in the
app. The light client now keeps line tables for that symbol file; the packaged library is still
stripped to 8.6 MB.

Also adds an XCM encoding test for PezkuwiChain networks. This commit points WESTEND at Zagros
to check that the test fails against that change.

* Fix the encoding test's read of the interior field

* Encode PezkuwiChain networks by genesis in XCM

WESTEND held a stale genesis labelled Zagros. The runtimes declare ThisNetwork as ByGenesis and
the XCM v5 NetworkId has no Westend or Zagros variant, so the named mapping is removed and every
PezkuwiChain network is encoded as ByGenesis.

* Run CodeQL for every language on hosted runners, Kotlin with a build

Default setup analyses Java without a build and leaves out every Kotlin file, and the Java job on the
self-hosted runner stopped at the NDK install because sudo asks for a password, so Kotlin had not been
analysed since February. One matrix job covers actions, JavaScript/TypeScript, Rust and Java/Kotlin.

* Use CodeQL Action v4, v3 is deprecated in December 2026

* Build for CodeQL without Gradle and Kotlin daemons so the analysis has the memory

* Show incoming USDT bridge credits in history without a refresh

The realtime transfer extractor for assets pallets knew only transfer and transfer_keep_alive. The
bridge credits with transfer_approved, spending the multisig's allowance, so a credit appeared only
after the indexer caught up and the user refreshed. transfer_approved is now a transfer from owner to
destination; the signing delegate is not the sender.

* Stop the light client five minutes after RPC is back

A chain registered with the light client kept syncing for as long as the process lived. Measured on
the emulator over 5-minute windows, that was about 14 MB of data an hour and 2% of a core while the
wallet read nothing from it.

Once RPC has stayed connected for five minutes after a fallback, the connection releases the light
client: LightClients unregisters the chain, a relay chain only after its teyrchains. The engine gains
remove_chain. Its database saver now checks, under the registry lock, that the chain is still
registered: smoldot panics on a request to a removed chain. The next fallback starts cold from the
saved database.

* Refresh light client specifications from the signed config while RPC works

The light client started from the checkpoint the APK was built with, so a first fallback months after
a release warp-synced from months back, and on a phone that never fell back there is no saved database
to help. The fallback is wanted when RPC is blocked, and then the config repository may be blocked too.

On the first RPC connection of the process, each chain's specification is fetched from the config
repository through the app's HTTP client, which checks it against the signed targets and falls back to
the mirrors. It is kept when it is the same chain (id and genesis state root) with a later checkpoint;
an older file never rolls the checkpoint back. Registration uses the kept file when it is later than the
APK's own.

* Close the light client's peer connections once its last chain is removed

smoldot keeps its peer-to-peer network service, with the connections in it, for as long as the client
lives, so removing every chain left them open: on the emulator, four libp2p connections were still up
eleven minutes after the release, and data use stayed above the RPC-only level. The engine now
replaces the client once no chain and no loopback connection is left; the connections close about 20 s
later, when the attempts under way finish.

The database saver now checks a per-registration number instead of the smoldot chain id, which smoldot
reuses after a removal: a saver of a removed chain could otherwise keep running for the next chain
that got the same id. A connection whose chain is removed between the handshake and its setup now
ends instead of panicking.

* Obfuscate the app's own classes and build the R8 variant in pull requests

-keep class io.novafoundation.nova.** { *; } with -keepnames left 2.6% of the app's 26,602 classes
renamed in the 1.3.0 Play build (15.6% of all classes). It is replaced by keeping only the field names
of the app's classes, which Gson reads by name where a class has no @SerializedName, so class and method
names are obfuscated. No persisted value or reflective lookup in the app depends on a class or method
name: class names appear only in logs and in an in-memory cache key.

@JavascriptInterface methods had no rule of their own; the blanket keep covered the DApp browser's web3
bridge, its integrity check and the Transak bridge. They are kept explicitly.

Pull requests built only the unminified develop variant, so an R8 break would first show in the Play
build. A second job builds releaseTest (the release rules, debug-signed) and uploads its APK and mapping.

* Give the releaseTest build the debug package, which the CI Firebase config knows

* Build releaseTest under the production package, the one its Firebase config has

* Stop keeping every app class under the JNI rule

-keep class io.novafoundation.nova.** { native <methods>; } keeps every class the pattern matches,
whether or not it has a native method, so the app's class names stayed unobfuscated after the blanket
keep went: 2.6% of its classes were renamed in the R8 build. The rule under it already keeps the names
of classes with native methods and of those methods, which is what JNI binds by name; the Rust
bindings look up no other app class.

* Keep TypeToken and Retrofit generic signatures under R8 full mode

The R8 build failed to map every chain from the local database: R8 full mode keeps generic signatures
only on kept classes, and the app's anonymous TypeToken subclasses had been kept by the blanket rule.
These are the rules Gson 2.11 and Retrofit 2.10 ship; the app is on Gson 2.10.1 and Retrofit 2.9.0.

* Keep constructors of the app's classes so Gson can instantiate its models

The R8 build failed to read the DApp list: R8 full mode had made DappMetadataResponse abstract because
nothing in the code instantiates it, and Gson does so reflectively. It matches none of the name
patterns the rules keep models by. Keeping constructors costs no obfuscation, since they are all named
<init>.

* Keep the type argument of web3j TypeReference subclasses under R8 full mode

* Run R8 in compatibility mode

After the app's own classes became obfuscatable, the full-mode R8 build kept breaking reflective
decoding in new places: TypeToken and TypeReference subclasses lost their type argument, a Gson model
was made abstract, and SubQueryResponse<T> lost its class signature so a proxy query decoded to a
LinkedTreeMap. Full mode keeps generic signatures only on explicitly kept classes, and this code base
has many generic models outside any name pattern. Compatibility mode keeps them everywhere under
-keepattributes Signature and does not make uninstantiated classes abstract; obfuscation is unchanged.

* Allow the java.beans classes Jackson's Java 7 support refers to, missing on Android
2026-11-03 18:07:55 +04:00

307 lines
13 KiB
Prolog

# ============================================================
# Pezkuwi Wallet ProGuard Rules
# ============================================================
# Keep line numbers for debugging crash reports
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
# ============================================================
# Kotlin
# ============================================================
-dontwarn kotlin.**
-keep class kotlin.Metadata { *; }
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
# Kotlin Coroutines
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
}
-dontwarn kotlinx.coroutines.**
# ============================================================
# Retrofit & OkHttp (Strict rules for generic type preservation)
# ============================================================
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Keep Retrofit library
-keep class retrofit2.** { *; }
-keepclassmembers class retrofit2.** { *; }
# Essential attributes for reflection
-keepattributes Signature
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes EnclosingMethod
-keepattributes RuntimeVisibleAnnotations
-keepattributes RuntimeInvisibleAnnotations
-keepattributes RuntimeVisibleParameterAnnotations
-keepattributes RuntimeInvisibleParameterAnnotations
-keepattributes AnnotationDefault
# Keep ALL interfaces with Retrofit annotations - NO allowshrinking/allowobfuscation
-keep interface * {
@retrofit2.http.* <methods>;
}
# Keep the method signatures including generic types
-keepclasseswithmembers interface * {
@retrofit2.http.* <methods>;
}
# ============================================================
# Gson
# ============================================================
-keepattributes Signature
-keepattributes *Annotation*
-dontwarn sun.misc.**
-keep class com.google.gson.** { *; }
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# R8 full mode keeps generic signatures only on kept classes, so an anonymous `object : TypeToken<T>() {}`
# lost its T: chains failed to load from the local DB ("TypeToken must be created with a type argument").
# Gson 2.11 ships these rules; this app is on 2.10.1.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
# web3j's TypeReference reads its type argument the same way; without it the EVM token balance flow
# failed with "Missing type parameter".
-keep,allowobfuscation,allowshrinking class * extends org.web3j.abi.TypeReference
# Retrofit reads a suspend function's return type from Continuation's generic signature (rules from
# Retrofit 2.10; this app is on 2.9.0).
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# ============================================================
# BouncyCastle Crypto
# ============================================================
-keep class org.bouncycastle.** { *; }
-dontwarn org.bouncycastle.**
# ============================================================
# EdDSA (net.i2p.crypto:eddsa, pulled in transitively by substrate-sdk-android)
# ============================================================
# Signer.kt/SignatureVerifier.kt look up this JCA provider by name at runtime
# (Signature.getInstance(..., "EdDSA")) - every Ed25519 signature (all Solana
# transactions, and any Substrate account using CryptoType.ED25519) goes
# through this provider, so its classes must survive obfuscation intact.
-keep class net.i2p.crypto.eddsa.** { *; }
-dontwarn net.i2p.crypto.eddsa.**
# ============================================================
# Native JNI Bindings (Rust)
# ============================================================
# SR25519 signing - keep the native methods and the class
-keep class io.novafoundation.nova.sr25519.BizinikiwSr25519 { *; }
# Names of classes with native methods and of those methods, which JNI binds by name. (A
# `-keep class io.novafoundation.nova.** { native <methods>; }` here kept every class of the app,
# native methods or not.)
-keepclasseswithmembernames class * {
native <methods>;
}
# Keep all JNI related classes
-keep class io.parity.** { *; }
# Runtime signers
-keep class io.novafoundation.nova.runtime.extrinsic.signer.** { *; }
# ============================================================
# Substrate SDK
# ============================================================
-keep class jp.co.soramitsu.** { *; }
-dontwarn jp.co.soramitsu.**
# Nova Substrate SDK (io.novasama)
-keep class io.novasama.substrate_sdk_android.** { *; }
-keepclassmembers class io.novasama.substrate_sdk_android.** { *; }
-dontwarn io.novasama.substrate_sdk_android.**
# XXHash library (used by Substrate SDK hashing)
-keep class net.jpountz.** { *; }
-keepclassmembers class net.jpountz.** { *; }
-dontwarn net.jpountz.**
# Keep Schema objects and their delegated properties
-keep class * extends io.novasama.substrate_sdk_android.scale.Schema { *; }
-keepclassmembers class * extends io.novasama.substrate_sdk_android.scale.Schema {
<fields>;
<methods>;
}
# ============================================================
# Secrets & Crypto Classes
# ============================================================
-keep class io.novafoundation.nova.common.data.secrets.** { *; }
-keepclassmembers class io.novafoundation.nova.common.data.secrets.** { *; }
-keep class io.novafoundation.nova.feature_account_impl.data.secrets.** { *; }
-keep class io.novafoundation.nova.feature_account_impl.data.repository.datasource.** { *; }
-keep class io.novafoundation.nova.feature_account_impl.data.repository.addAccount.** { *; }
# ============================================================
# Firebase
# ============================================================
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**
-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.**
# ============================================================
# Branch.io Deep Linking
# ============================================================
-keep class io.branch.** { *; }
-dontwarn io.branch.**
# ============================================================
# Web3j (Ethereum)
# ============================================================
-keep class org.web3j.** { *; }
-dontwarn org.web3j.**
# ============================================================
# SQLCipher
# ============================================================
-keep class net.sqlcipher.** { *; }
-dontwarn net.sqlcipher.**
# ============================================================
# Room Database
# ============================================================
-keep class * extends androidx.room.RoomDatabase
-keep @androidx.room.Entity class *
-dontwarn androidx.room.paging.**
# ============================================================
# Data Classes & Models (Keep for serialization)
# ============================================================
-keep class io.novafoundation.nova.**.model.** { *; }
-keep class io.novafoundation.nova.**.response.** { *; }
-keep class io.novafoundation.nova.**.request.** { *; }
-keep class io.novafoundation.nova.**.dto.** { *; }
-keep class io.novafoundation.nova.**.*Remote { *; }
-keep class io.novafoundation.nova.**.*Remote$* { *; }
# ============================================================
# Parcelable
# ============================================================
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
# ============================================================
# Enums
# ============================================================
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# ============================================================
# Serializable
# ============================================================
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# ============================================================
# Ledger USB/Bluetooth
# ============================================================
-keep class io.novafoundation.nova.feature_ledger_impl.** { *; }
# ============================================================
# WalletConnect
# ============================================================
-keep class com.walletconnect.** { *; }
-keepclassmembers class com.walletconnect.** { *; }
-dontwarn com.walletconnect.**
# ============================================================
# Google API Client (Google Drive)
# ============================================================
-keep class com.google.api.** { *; }
-keepclassmembers class com.google.api.** { *; }
-keep class com.google.api.client.** { *; }
-keepclassmembers class com.google.api.client.** { *; }
-keep class com.google.api.services.** { *; }
-keepclassmembers class com.google.api.services.** { *; }
-dontwarn com.google.api.**
# ============================================================
# Navigation Component
# ============================================================
-keep class * extends androidx.navigation.Navigator { *; }
-keep @androidx.navigation.Navigator.Name class * { *; }
-keepnames class * extends androidx.navigation.Navigator
-keepattributes *Annotation*
-keep class androidx.navigation.** { *; }
-keep class * implements androidx.navigation.NavArgs { *; }
-keep class androidx.navigation.fragment.** { *; }
-keep class io.novafoundation.nova.**.navigation.** { *; }
-keep class * extends androidx.navigation.NavDestination { *; }
# Field names of the app's own classes, so Gson reads and writes JSON by the declared names even where a
# class has no @SerializedName and sits outside the model/response/request/dto packages kept above.
# Class and method names are obfuscated.
-keepclassmembers class io.novafoundation.nova.** { <fields>; }
# Constructors of the app's classes. R8 full mode makes a class it never sees instantiated abstract, and
# Gson instantiates its models reflectively: DappMetadataResponse, outside every name pattern above,
# failed with "Abstract classes can't be instantiated". Constructors are named <init> either way, so
# this costs no obfuscation, and it applies only to classes that are otherwise kept.
-keepclassmembers class io.novafoundation.nova.** { <init>(...); }
# Methods a WebView page calls by name (DApp browser web3 bridge, integrity check, Transak events).
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
# ============================================================
# Optimization settings
# ============================================================
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*,!method/inlining/*
-optimizationpasses 5
-allowaccessmodification
# Don't optimize or obfuscate Retrofit interfaces - critical for type reflection
-keepnames,includedescriptorclasses interface * {
@retrofit2.http.* <methods>;
}
# ============================================================
# Don't warn about missing classes that we don't use
# ============================================================
-dontwarn org.conscrypt.**
-dontwarn org.slf4j.**
-dontwarn javax.naming.**
-dontwarn org.w3c.dom.traversal.**
-dontwarn org.apache.xerces.**
-dontwarn org.apache.xml.**
-dontwarn org.apache.xalan.**
-dontwarn org.ietf.jgss.**
-dontwarn org.apache.http.**
# Referenced only by Jackson's Java 7 support class, which Jackson loads defensively; Android has no
# java.beans. R8 compatibility mode reports these as missing.
-dontwarn java.beans.ConstructorProperties
-dontwarn java.beans.Transient
# ByteBuddy (test dependency)
-dontwarn net.bytebuddy.**
-dontwarn com.sun.jna.**
-dontwarn edu.umd.cs.findbugs.annotations.**