Skip to content

v7.4 — build & CI revival, HF6 wallet fixes, mainnet-only cleanup, complete localization#654

Draft
Maxnflaxl wants to merge 24 commits into
masterfrom
v7.4
Draft

v7.4 — build & CI revival, HF6 wallet fixes, mainnet-only cleanup, complete localization#654
Maxnflaxl wants to merge 24 commits into
masterfrom
v7.4

Conversation

@Maxnflaxl

@Maxnflaxl Maxnflaxl commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Gets the app building, running, and releasable again on the pinned AGP 3.5 / Gradle 6.1.1 toolchain, fixes the open wallet-safety and dApp-swap bugs, ports the first batch of platform-agnostic fixes from the iOS v7.4 PR (BeamMW/ios-wallet#594), slims the release APKs, drops the dead testnet flavor, and fills every locale's missing strings. The app version is set to 7.4. Everything is verified in the emulator (mainnet, random node) except where noted.

Dependency resolution after the JCenter sunset (#648). jcenter() replaced with mavenCentral() plus the Aliyun JCenter mirror, which still serves the six JCenter-only artifacts (anko-commons, xfetch2, loadingview, circleindicator, blurry, gradientview). LeakCanary 1.x dropped — its DisplayLeakActivity declares intent filters without android:exported, which makes installs fail outright on API 31+, and nothing referenced it.

Native libs now come from BeamMW/beam CI artifacts (currently 7.5.14510). builds.beam.mw/mainnet/latest has served stale 2021 (v5.2) libraries for years; the Kotlin side calls JNI methods that don't exist there (e.g. Wallet.enableBodyRequests), which crashes with UnsatisfiedLinkError at runtime rather than failing the build. android-wallet-mainnet.sh now downloads the libwallet-jni-<abi> artifacts from the latest successful master Build run on BeamMW/beam and strips them with llvm-strip. The .sos stay gitignored — run the script after checkout.

Release CI, signed and slim. GitHub Actions workflow runs assembleMainnetRelease on pushes to master/v7.4 and on PRs, uploading the arm64-v8a and universal release APKs as artifacts. The APKs are signed in CI (apksigner, v1+v2+v3 schemes) with a dedicated test keystore held in the repo secrets, so the artifacts install directly — without it Android rejects the unsigned output as "package appears to be invalid". The CI key is deliberately not the Play release key: CI builds update over each other, but can't update a Play-store install (uninstall first) and vice versa. Fork PRs get no secrets and fall back to unsigned artifacts. Getting a green run on the frozen toolchain required committing gradle-wrapper.jar (a fresh checkout can't run ./gradlew without it), installing an isolated minimal SDK (android-32 + build-tools 28.0.3 — AGP 3.5 can't parse the runner's newer SDK metadata), and running sdkmanager under JDK 17 while the build itself uses JDK 11. AGP's own strip task is disabled (doNotStrip + the AGP-read ANDROID_NDK_* vars blanked so it degrades to a warning); ANDROID_NDK_LATEST_HOME is left set so the fetch script can still locate the NDK's llvm-strip.

Smaller release APKs. Two sources of bloat removed: CI was shipping unstripped ~111 MB native libraries (the fetch script's strip step was disabled by an over-broad NDK-env blanking), and the universal APK carried two full byte-identical copies of the arm64 library under lib/armeabi/ and lib/armv8/ plus a standalone armeabi split APK containing 64-bit code — all dead since NDK r17 and unloadable on any minSdk-23 device. Dropping the fetch script's legacy-copy loop and armeabi from the ABI splits, and keeping the strip step alive on CI, takes the universal APK's native payload from ~625 MB to ~43 MB and the arm64 APK from ~46 MB to ~22 MB.

Masternet and beamtestnet flavors removed. Neither network target is maintained; mainnet is the only shipping flavor. Drops both product flavors, their source sets, proguard configs, lib-download scripts/gradle tasks, and every FLAVOR_MASTERNET / FLAVOR_TESTNET code path (explorer / appslist / faucet / DAO / restore URLs, the testnet BEAMX asset id, the testnet-only "Share DB" settings entries and seed long-press copy). mainnet keeps its .mainnet applicationId suffix so existing installs still update, and xml-v25/shortcuts.xml (previously pointing at the masternet package, so app shortcuts never resolved) is now correct with a single flavor to serve.

dApp swap confirm always said "not enough funds" (#653). checkAmount() balance-checked every amount entry, including receive legs — in a swap the wallet doesn't hold the target asset yet, so the check always failed. Only spend legs are checked now; the fee is still checked against BEAM, including a fee-only check for the edge case where a request carries no spend legs. Also fixes both legs of a swap showing the first asset's icon: setAmounts() used the dialog-level assetId for every row instead of the row's own.

Wallet wiped after verifying the seed (#645). The seed-verification and wallet-creation flows share the same fragments (WelcomeSeed → WelcomeConfirm → Password → WelcomeProgress), distinguished only by the volatile static App.isAuthenticated. That flag resets on process death while the navigation back-stack is restored — so if the OS killed the app on the verify screens, the restored flow read it as "create", generated a new mnemonic, and ran removeDatabase() + createWallet(), destroying the existing wallet. Every create-vs-verify decision is now gated on a durable isVerification nav-argument, which is persisted in the saved instance state and survives process death. The display-seed path is driven off the same durable arg, so "show seed phrase" always reads the wallet's real seed rather than minting a fresh mnemonic.

Automatic restore stuck the first time (#647). Automatic restore showed the wallet only from onProgress(), i.e. on the next sync-progress event after the snapshot import — when the wallet is already synced at that point, no event arrives and restore hangs on the progress screen. Retrying works because the unfinished-restore cleanup resets state and the timing differs, which matches the "stuck first time, second time it always works" report. On recovery-complete with isSynced(), the wallet is now shown directly; scoped to RESTORE_AUTOMATIC so the own-node and create/open paths are untouched. Still needs a physical-device check — the already-synced hang doesn't reproduce in the emulator.

Guard against null strings in JNI address callbacks. The native core can pass null for any WalletAddressDTO string field — token-address callbacks leave walletID/label null, which is what crashed the iOS wallet's faucet screen (BeamMW/ios-wallet#587). WalletAddress's constructor called source.walletID.replaceFirst(...) directly, so the same core behavior NPEs here. The DTO's string fields are now nullable (JNI constructor signature unchanged) and defaulted at the boundary.

"Get coins" opens the Faucet dApp. The wallet banner and the Settings entry opened faucet.beamprivacy.community, whose domain no longer resolves (NXDOMAIN). They now open the BEAM Faucet dApp in the in-app browser (live apps-list entry preferred, known plugin-faucet URL as fallback), and the dead faucet-address generation/subscription plumbing is gone from the wallet and settings screens.

Receive screen.

  • Fresh address per open — generateRegularAddress() re-tokenises the persisted default address, so opening Receive always showed the same one. Switched to generateNewAddress(), which mints a fresh SBBS address per call. Offline / max-privacy tokens still regenerate only when amount or asset changes, so editing the amount doesn't mint a new address per keystroke.
  • Online/offline copy reworded to match iOS (Better explanations on the receive screen ios-wallet#593): "online vs offline" used consistently, and the 12-hour window now means "open and connect your wallet once within 12 hours".
  • Inline "use for CEX withdrawals and mining" hint under the plain SBBS (Regular) address, so users know it's the one for exchange withdrawals and mining pools (SBBS address on main receive screen #643, SBBS address note for mining #644).

Version label. App version set to 7.4 (versionCode 513). The vX.Y label shown on the welcome/settings screens is now built from a single AppConfig.APP_VERSION_LABEL constant instead of four inline "v " + … copies (which also rendered a stray space, v 7.3).

Complete localization. Every one of the 19 locales was missing the same ~215 English string keys (added since the last translation sync, rendering as English fallback), and the reworded Receive strings existed only in English. All 19 locales now carry every string key, translated consistently with each locale's existing terminology, with format placeholders (%s, %1$s, …) and line breaks preserved and parity-checked against the source; Serbian is in Cyrillic.

Not in this PR

  • Still to port from iOS v7.4 (platform-agnostic, follow-ups): the remaining create/restore-flow fixes (stale wallet.db cleanup before init, random-node create skipping the sync UI, stuck "Restored 100%", sync listener removed before its early-return), recovery-progress throttling (the core fires ~8000 events/sec), phase-aware sync-screen messages, Edit Address expiry-to-Never, the node settings restructure + Node Peers screen, and Public Offline pinned atop the Active addresses list.
  • JNI-gated features — Messenger, Asset Swaps/DEX, the DApp store, UTXO split/consolidate, and the Receive address-type picker (SBBS address on main receive screen ios-wallet#588). Unlike iOS, whose bridge lives in the app repo, the Android bridge is beam/android/jni.cpp compiled into libwallet-jni.so, and the published Android lib is built with IPFS off and no asset-swap support — so these need core-repo work and a lib rebuild before anything can happen here.
  • Toolchain modernization to targetSdk 36 (required for Play updates by 2026-08-31) — the AGP 3.5 → 8.9+ / Kotlin / ViewBinding ladder is scoped for a later PR, not this fix release.

Before merge

Closes #648, closes #653, closes #645, closes #643, closes #644.

Maxnflaxl and others added 15 commits February 17, 2026 17:43
- Replace jcenter() with mavenCentral() plus the Aliyun JCenter mirror,
  which still serves the six JCenter-only artifacts (anko-commons, xfetch2,
  loadingview, circleindicator, blurry, gradientview)
- Drop LeakCanary 1.x: its DisplayLeakActivity has intent filters without
  android:exported, which makes installs fail on API 31+; no code usage
The previous fix read DAOInfo.sourceAssetId, but the native wallet core
never emits that key, so it always fell back to the old behavior. The
actual bug: checkAmount() balance-checked every amount entry including
receive legs - in a swap the wallet doesn't hold the target asset yet,
so 'not enough funds' always fired. Skip entries with spend == false;
fee is still checked against BEAM.

Also fix setAmounts() using the dialog-level assetId (amounts[0]) for
every row's icon instead of the row's own assetID, which made both legs
of a swap show the first asset's icon.
Fixes #653 (dapp swap confirm dialog false 'not enough funds') and
unblocks builds after the JCenter sunset (#648).
builds.beam.mw/mainnet/latest has served stale 2021 (v5.2) libraries for
years; the app calls newer JNI methods (e.g. Wallet.enableBodyRequests)
that are missing there, crashing with UnsatisfiedLinkError at runtime.
Download the libwallet-jni-<abi> artifacts from the latest successful
master Build run on BeamMW/beam instead, and strip them when llvm-strip
is available (CI artifacts are unstripped, ~111 MB each).
The masternet network no longer exists. Drop the product flavor, its
source set (res, google-services.json, gitignored jniLibs dir), proguard
config, lib download script/gradle task, and all FLAVOR_MASTERNET code
paths. Retarget the main shortcuts.xml (xml-v25) from the masternet
package to mainnet - it pointed at masternet even in mainnet builds, so
app shortcuts could never resolve.
GitHub Actions workflow that runs assembleMainnetRelease on push to
master/v7.4 and on pull requests, uploading the arm64-v8a and universal
release APKs as artifacts.

Getting a green run on the pinned AGP 3.5 / Gradle 6.1.1 toolchain
required: committing gradle-wrapper.jar (fresh checkouts could not run
./gradlew without it); installing an isolated minimal SDK (android-32 +
build-tools 28.0.3) because AGP 3.5 cannot parse the runner's newer SDK
metadata; running sdkmanager under JDK 17 while the build itself uses
JDK 11; skipping native-lib stripping via packagingOptions.doNotStrip;
and blanking the runner's ANDROID_NDK_* env vars so AGP 3.5's strip task
degrades to a warning instead of failing on the incompatible NDK.
The native core can pass null for any WalletAddressDTO string field -
token-address callbacks leave walletID/label null, which crashed the
iOS wallet on the faucet screen (ios-wallet#587). WalletAddress's
constructor called source.walletID.replaceFirst(...) directly, so the
same core behavior NPEs here. Make the DTO's string fields nullable
(JNI constructor signature is unchanged) and default them at the
boundary.

(cherry picked from commit 5085536)
The 'get coins' banner and Settings > faucet opened
faucet.beamprivacy.community, whose domain no longer resolves
(NXDOMAIN). Navigate to the BEAM Faucet dApp in the in-app browser
instead (preferring the live apps-list entry, falling back to the
known plugin-faucet URL). Removes the now-dead faucet-address
generation/subscription and external-link plumbing from the wallet
and settings screens.

(cherry picked from commit d26f59a)
generateRegularAddress(amount, assetId) re-tokenises the persisted
default address, so opening Receive always returned the same address.
Switch to generateNewAddress(), which mints and persists a fresh
address per call (delivered via subOnAddressCreated). The offline /
max-privacy tokens are still regenerated only when amount/asset change,
so editing the amount does not mint a new address per keystroke.

(cherry picked from commit f14ac51)
Reword receive_description / receive_description_2 to 'online vs
offline' consistently and make the 12-hour window mean 'open and
connect your wallet once within 12 hours', matching the iOS wallet.

(cherry picked from commit 64cd0b6)
The Regular address on a random node is the plain SBBS address. Surface
an inline italic hint under it ('use for CEX withdrawals and mining') so
users know the SBBS address is the one to use for exchange withdrawals
and mining pools, and extend sbbs_address_new to mention mining too.

(cherry picked from commit 39bd103)
The seed-verification and wallet-creation flows share the same fragments
(WelcomeSeed -> WelcomeConfirm -> Password -> WelcomeProgress) and were
distinguished only by the volatile static App.isAuthenticated. That flag
resets to false on process death, while the navigation back-stack is
restored - so if the OS killed the app while the user was on the verify
screens, the restored flow read isAuthenticated=false, generated a NEW
mnemonic, and ran the create path (removeDatabase() + createWallet()),
destroying the existing wallet.

Thread an explicit, durable isVerification nav-argument from the verify
entry point (DoubleAuthorization VerificationSeed) through WelcomeSeed
and WelcomeConfirm, and gate every create-vs-verify decision on it
instead of App.isAuthenticated. Nav-args are persisted in the saved
instance state and survive process death, and are scoped to the
navigation instance so they can't leak into a later create/restore.

Verified in the emulator: creating a new wallet still works; verifying
an existing wallet's seed shows the real seed and returns to the wallet
without wiping.

(cherry picked from commit 247fb309396e127af8429d0ec6712ce79c6e1a9d)
Keep comments focused on the code; tracker references belong in commit
messages, not the source.

(cherry picked from commit 23eb3ed214c4cc654a3fa8d53f0759bdd5d8c120)
Automatic restore shows the wallet from onProgress(), i.e. on the next
sync-progress event after the snapshot import finishes. When the wallet
is already synced at that point, no further sync event arrives, so the
restore hangs on the progress screen (retrying works because the
unfinished-restore cleanup resets state and timing differs).

When recovery import finishes in RESTORE_AUTOMATIC mode and the wallet
is already synced, show the wallet directly instead of waiting for a
sync event that will never come. Scoped to automatic restore so the
own-node and create/open paths are untouched.

Needs verification on a physical device (the already-synced hang is not
reproducible in the emulator).

(cherry picked from commit 6254c3fb462ded075a39c019587a6d5307bd92df)
Android refuses to install unsigned APKs (INSTALL_PARSE_FAILED_NO_CERTIFICATES;
'App not installed as package appears to be invalid' in the UI), so the raw
assembleMainnetRelease artifacts were uninstallable for testers. Sign them in CI
with a dedicated test keystore held in the repo secrets (CI_KEYSTORE_B64 /
CI_KEYSTORE_PASS): apksigner applies v1+v2+v3 schemes, successive CI builds can
update over each other, and the key is unrelated to the Play release key, so CI
builds and store installs stay cleanly separated. Fork PRs receive no secrets
and fall back to uploading the unsigned APKs.
The #645 nav-arg refactor made WelcomeSeed generate the existing seed only
when isVerification is set. The display-seed entry (DoubleAuthorization
DisplaySeed -> WelcomeSeed with onlyDisplay=true, isVerification=false)
therefore fell through to Api.createMnemonic() and showed a brand-new
mnemonic instead of the wallet's own seed. (The screen is currently only
reachable in code - no Settings row constructs SeedPhrase mode - but the
'Show seed phrase' title/mode are wired and clearly meant to exist.)

Drive the seed source off onlyDisplay || isVerification so both the verify
and the display paths read the persisted seed; only the create flow mints
a fresh one.
The swap-confirm fix skips balance checks on receive legs (spend == false).
A request that carries only receive legs would then run checkAmount without
validating anything, even though the transaction fee still debits BEAM. Add
a fee-only check for that case so an unaffordable fee still disables Confirm.
Two sources of bloat in the release artifacts:

- CI shipped unstripped ~111 MB native libraries. The workflow blanked
  ANDROID_NDK_LATEST_HOME along with the vars AGP reads, but that one is
  read only by android-wallet-mainnet.sh to locate the NDK's llvm-strip;
  blanking it disabled stripping on CI. Keep it set (AGP still can't see
  the NDK via ANDROID_NDK_ROOT/HOME/ANDROID_NDK) so the fetch script
  strips each lib to ~11 MB.
- The universal APK carried two extra full copies of the arm64 library
  under lib/armeabi/ and lib/armv8/, and a standalone armeabi split APK
  was built containing 64-bit code. Both trace to the fetch script's
  legacy-copy loop plus 'armeabi' in the ABI splits. armeabi has been
  dead since NDK r17 and no minSdk-23 device can load it. Drop the copy
  loop and the split entry.

Verified locally (already-stripped libs): universal native payload
625 MB -> 43 MB, arm64 APK 46 MB -> 22 MB, no armeabi/armv8 entries, no
armeabi split APK; the signed release installs and launches.
Set versionName 7.4 / versionCode 513 for the release. The 'vX.Y' label
was built inline as "v " + BuildConfig.VERSION_NAME in four screens, which
also rendered with a stray space ("v 7.3"). Add AppConfig.APP_VERSION_LABEL
as the single source ("v" + version, no space) and point Settings, Welcome
Create, Welcome Open and Welcome Progress at it.
The Receive-screen copy touched by this branch existed only in English:
receive_description carried stale wording in every locale (and was
untranslated English in 5 of them), and receive_description_2,
sbbs_address_new and sbbs_address_hint were absent everywhere (rendering
as English fallback). Update receive_description and add the three missing
keys, in de fr es it nl fi sv tr id zh ja ko th vi ru uk be sr cs, matching
each locale's existing terminology (Serbian in Cyrillic; SBBS/CEX/Beam kept
as-is).
The beamtestnet network target is no longer maintained; mainnet is the
only shipping flavor. Drop the product flavor and its proguard config,
source set (icons, colors, testnet strings, gitignored jniLibs dir), the
testnet lib-download script + gradle task, and every FLAVOR_TESTNET code
path (explorer/appslist/faucet/DAO/restore URLs, the BEAMX testnet asset
id, the testnet-only 'Share DB' settings entries and seed long-press
copy, log-share subject). mainnet keeps its .mainnet applicationId suffix
so existing installs still update. This also makes the hardcoded .mainnet
package in xml-v25/shortcuts.xml correct again (no other flavor to serve).
Every locale was missing the same ~215 English string keys (added since the
last translation sync), rendering as English fallback. Translate them for
cs de es fr id it ja ko nl ru sr sv th uk vi zh, each consistent with the
locale's existing terminology, with format placeholders and line breaks
preserved. Serbian uses Cyrillic. fi/tr/be still pending.
Complete the localization sweep for the three locales whose translation
agents were interrupted: Belarusian, Finnish and Turkish. All 19 locales
now carry every English string key; placeholders and line breaks verified
against the source.
@Maxnflaxl Maxnflaxl changed the title v7.4 v7.4 — build & CI revival, HF6 wallet fixes, mainnet-only cleanup, complete localization Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant