Skip to content

🚀 [Release v4.12.0] - #270

Merged
kcw-grunt merged 175 commits into
mainfrom
release/v4.12.0
Aug 8, 2026
Merged

🚀 [Release v4.12.0]#270
kcw-grunt merged 175 commits into
mainfrom
release/v4.12.0

Conversation

@grunt-claude-bot

@grunt-claude-bot grunt-claude-bot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

📱 Description

Cuts release/v4.12.0, bumping versionName/versionCode to v4.12.0 (202506350). This release accumulates everything merged to develop since v4.11.0 (#259) was last released to main — most notably the native SIGSEGV crash fix + integrity-warning reporting in BRPeerManager (#269), In-App Review activation (#260, #263), the Fallinmoji "How To Play" screen and bento corner-radius fix (#268), the targetSdk 36 bump (#267), the analytics event cleanup (#266), and the MoonPay/RSA-agent-string fixes (#264, #265). Also merges in main's own did_play_game analytics/review-prompt addition to GameHubViewModel, which had shipped directly to main in v4.11.0 but was missing from develop.

Platform

  • Android

🎯 Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🔧 Refactoring (code change that neither fixes a bug nor adds a feature)
  • 📚 Documentation update
  • 🎨 UI/UX improvement
  • ⚡ Performance improvement
  • 🧪 Test addition or improvement

📋 Changes

New Components Added

  • app/src/test/java/com/brainwallet/wallet/BRPeerManagerTest.kt — 4 tests covering BRPeerManager.onIntegrityWarning's Crashlytics reporting

Modifications

  • app/build.gradle.ktsversionCode 202506349 → 202506350, versionName "v4.11.1" → "v4.12.0"
  • README.md — added the v4.12.0 release-notes section
  • Resolved merge conflicts against main (README, app/build.gradle.kts, and five files where main's pre-in-app-review-cleanup code diverged from develop's newer did_request_rating/user_completed_ratingTimber.d downgrade) — kept develop's newer state throughout; verified InAppReviewServiceTest (7), BRPeerManagerTest (4), and SendViewModelTest (30) all still pass post-merge

Removals

None

📊 Statistics

  • Additions: 783 lines
  • Deletions: 191 lines
  • Files Changed: 35
  • Commits: 13 (accumulated since v4.11.0 (🚀 [Release v4.11.0] #259) was last released to main)

🧪 Tests Status

  • Tests ran successfully locally?
  • Added more tests? How many? 4 (BRPeerManagerTest)
  • Code coverage percentage of the codebase: __%

🎯 Reviewers

@kcw-grunt, @josikie

kcw-grunt and others added 30 commits March 30, 2025 10:50
🚀[Release v4.4.1] Merge into Develop
This reverts commit 4e252ac.

# Conflicts:
#	app/src/main/java/com/brainwallet/tools/manager/APIManager.kt
This reverts commit 4e252ac.

# Conflicts:
#	app/src/main/java/com/brainwallet/tools/manager/APIManager.kt
fix: race condition when clear shared prefs values after wipeAll
from the new peer discovery (v4.2.0)
* chore: update core submodule

* chore: resolve conflict

* Update build.gradle.kts

version and code bump

* chore: for now at BRPeerManager.wrapConnectV2 only using connect, since the core using hardcoded peers

* fix: fix write down confirm screen (#63)

* fix: fix write down confirm screen

* fix: fix allow seed word item not unique

* chore: cherry picked and adjust from
- f2fa8e1
- 98644c4
- 9396927
- 7ad4b98
- 8e57d2a

* fix: fix crash can't parse response inside LtcRepository.fetchRates

* fix: fix crash (failed parse) when SelectedPeersRepository.fetchSelectedPeers got unsuccessful response

* code bump

* fix: ConcurrentModificationException at BRPeerManager.txStatusUpdate

* feat: implement new API

* code bump

---------

Co-authored-by: Kerry Washington <mrkerrywashington@icloud.com>
kcw-grunt and others added 20 commits July 17, 2026 21:54
…tup NPE (#248)

The "Techdebt/improve shop" refactor (d0886db) deleted initializeViews(),
which was the only place binding bottomNav, menuBut, drawerLayout,
primaryPrice, and secondaryPrice via findViewById. The refactor left
onCreate/setListeners still dereferencing those never-assigned fields,
so BreadActivity crashed with a guaranteed NullPointerException on
BottomNavigationView.setOnNavigationItemSelectedListener on every launch.

These views were already non-functional before the crash: the bottom
nav's menu was only ever wired up via a design-time-only tools:menu
attribute (never inflated at runtime), and primaryPrice/secondaryPrice
had no code left to populate their text. Rather than resurrect dead
legacy UI, remove it outright: drop the associated fields, listeners,
and R.id.bottomNav/menuBut/drawerLayout/primary_price/secondary_price
views plus the now-unreferenced R.menu.bottom_nav_menu resource, and
fix the layout refs that pointed at the removed views.

Co-authored-by: Claude <noreply@anthropic.com>
)

Commit 1b353ae (release/v4.9.4) renamed the bw-gdlib submodule from
modules/bw-gdlib to bw-gdlib in .gitmodules, but never removed the old
gitlink entry from the tree. That left a tracked gitlink at
modules/bw-gdlib with no corresponding .gitmodules mapping, so
`git submodule sync --recursive && git submodule update --init --recursive`
(CircleCI's setup_environment step) fails on every build with:

  fatal: no submodule mapping found in .gitmodules for path 'modules/bw-gdlib'

Removing the dangling gitlink brings the tree's 5 submodule entries back
in sync with .gitmodules' 5 declared paths.

Co-authored-by: Claude <noreply@anthropic.com>
#250)

* Bump version to v4.10.5 (202506343)

* fix(shop-bento): eliminate flaky ShopBentoViewModelTest failure

ShopBentoViewModelTest had no MainDispatcherRule, unlike every other
ViewModel test in this codebase, so Dispatchers.Main was never
explicitly installed for it. ShopBentoViewModel.init launches its
work via viewModelScope (backed by Dispatchers.Main), so this test was
actually running against whatever Dispatchers.Main happened to be left
as by other test classes sharing the same Gradle test JVM fork —
explaining why `testBrainwalletReleaseUnitTest` failed intermittently
with `TurbineAssertionError: Unconsumed events found` only under the
full suite (order/load-dependent) and never in isolation. Added
MainDispatcherRule to match the established pattern (see
UnLockViewModelTest, BWSenderIntegrationTest, TxRepositoryImplIntegrationTest).

Also collapsed ShopBentoViewModel's two independent viewModelScope.launch
blocks (one collecting settingRepository.settings, one collecting
shopProxyRepository.shopProxy) into a single combine()-driven collector.
Previously each block called _state.update independently, so real
observers of `state` (not just the test) could see transient,
incomplete intermediate states depending on collector interleaving.

Swapped the fixed-duration advanceTimeBy(100) for advanceUntilIdle()
in the affected test, which deterministically drains all pending
coroutine work instead of guessing a virtual-time delay.

Verified with repeated full `testBrainwalletReleaseUnitTest` runs
(previously failed roughly 1 in 3-5 runs under the full 415-test suite).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Removed the auto summary in place of Claude summary

* fix(ci): lower Gradle daemon memory footprint to stop OOM-kill

The unit-test job's Gradle daemon was being killed mid-build
("The message received from the daemon indicates that the daemon has
disappeared... it may have been killed or may have crashed"),
consistently right as testBrainwalletDebugUnitTest started after a
heavy multi-module build (KSP across 5 modules, detekt across 4,
dataBinding, bw-gdlib texture packing, 415 unit tests).

-Xmx4g for the Gradle daemon plus AGP-forked unit-test worker JVMs
plus OS/container overhead was exceeding the resource_class: large
executor's available RAM. Lowered the daemon heap to -Xmx3g and
org.gradle.workers.max/--max-workers from 2 to 1 to leave more
headroom for the forked test JVMs, without changing the resource
class (and its higher CircleCI billing tier).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* perf(ci): skip detekt in the unit-test job

android-build-logic's DetektSetup.attachDetektTask() wires `detekt`
as a dependency of every compile*/assemble* task project-wide
(tasks.whenTaskAdded { if (name.startsWith("compile") || ...) dependsOn(detekt) }),
so a plain `./gradlew testBrainwalletDebugUnitTest` was also running
3-4 full detekt passes (autoCorrect=true, parallel=true, HTML/XML/TXT/
SARIF/Markdown reports across app/core/iap/general-purpose-app) that
have nothing to do with running tests.

That's real CPU/memory work competing with compilation and the forked
unit-test JVMs on the resource-constrained `large` executor, on top of
the daemon heap/worker tuning from the previous commit. Verified
locally: `-x detekt` removes all detekt tasks from the graph (confirmed
via --dry-run) with no other effect, and the full unit test run still
passes.

Scoped to this CI job's command line only, not the shared build-logic,
so local dev/lint workflows are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: kcw-grunt <mrkerrywashington@icloud.com>
Co-authored-by: Claude <noreply@anthropic.com>
)

* docs: add SECURITY.md with a real vulnerability disclosure process

PR #246 (kcw-grunt-patch-1) added SECURITY.md but left it as GitHub's
unedited default template — placeholder version numbers (5.1.x/5.0.x)
that don't match any real Brainwallet release, and instructional
placeholder text ("Use this section to tell people...") never filled
in with actual content.

Replaced with a real policy: reporting instructions pointing at the
verified support channel (brainwallet.co/support, from
BWConstants.SUPPORT_WEB_LINK — there's no dedicated security@ email
anywhere in this repo, and GitHub's private vulnerability reporting
is not enabled for this repo, confirmed via the API), explicit scope
(wallet/key-management code in scope, third-party deps and social
engineering out of scope), a supported-versions policy matching how
this app actually ships (continuous release, latest version only),
and a safe harbor statement — all relevant for a wallet app handling
real user funds and seed phrases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: restructure README with a real front page for new visitors/contributors

The README opened straight into CI badges followed by a raw dump of
release notes in inconsistent order (v4.9.0, then v4.8.3, then a jump
back to v3.9.0-v3.9.1, v3.6.0, v3.3.1) — no description of what the
app actually does beyond the title, no download links, no build/setup
instructions, no contributing/security/license pointers. For an
actively-developed open-source wallet repo, that's a poor front door
for anyone landing here from GitHub search or the Play Store listing.

Added, all verified against the actual repo/app rather than assumed:
- A real description and feature list
- Download links (Play Store id ltd.grunt.brainwallet, verified live;
  the gruntsoftware/ios sibling repo; brainwallet.co)
- Clone/build instructions grounded in the actual submodule setup and
  local.properties/google-services.json requirements from
  .circleci/config.yml
- Architecture summary (Koin, Compose, Coroutines, native wallet core)
  matching the real module layout
- Testing, Security (linking the new SECURITY.md), Contributing
  (linking the existing PR template), and License sections

Preserved the full existing release-notes content underneath a
"Release Notes" heading rather than deleting project history, with a
pointer to GitHub Releases/compare views for the current changelog
going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* updated the README.md

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: kcw-grunt <mrkerrywashington@icloud.com>
* Bump bw-gdlib submodule to 5c2faab

Picks up the fix removing the exported launcher intent-filter from
AndroidLauncher, which caused a duplicate ghost home-screen icon in
release builds and crashed on launch (Crashlytics
0cdfcd994445922abf96c307663673c8). See bw-gdlib PR #14.

* Update build.gradle.kts

---------

Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
Syncs develop with everything already shipped: Release v4.10.4 (#244)
and Release v4.10.6 (#253), which develop was missing entirely (develop
had been stale since before v4.10.4). Brings in the ReLinker native-lib
crash fix, the BWSender flaky-test fix, the ghost-launcher-icon crash
fix, and the BreadActivity NPE fix.

Conflicts resolved:
- app/build.gradle.kts: kept main's v4.10.6 (202506345) over develop's
  older v4.10.5
- gradle/libs.versions.toml: kept the ReLinker dependency addition

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…at (#254)

Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
* update Fallinmoji v1.6.0

bump version

* Update .gitignore

---------

Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
Adds a step to the CircleCI unit-test job that aggregates JUnit XML
results and PATCHes a public gist with a shields.io endpoint-schema
JSON payload, then wires that gist's raw URL into the README as a
new badge. Only runs on develop, and no-ops if GIST_TOKEN/GIST_ID
aren't configured as CircleCI project env vars.
commit f701d2d
Author: grunt-claude-bot <admin@brainwallet.co>
Date:   Fri Jul 31 08:55:58 2026 +0100

    Activate InAppReviewService and wire up review prompts (#260)

    * di: register InAppReviewService in AppModule

    Wires the previously-dormant InAppReviewService into Koin using the
    existing BrainwalletApp.breadContext activity tracker. No call sites
    added yet — showInAppReviewDialogIfNeeded() is not invoked anywhere,
    so behavior is unchanged until it's called from the app.

    * feat: activate InAppReviewService and wire up review prompts

    Registers InAppReviewService in Koin DI and calls
    showInAppReviewDialogIfNeeded() from four placements: after a
    successful send, the game hub exit flow, the balance-visibility
    toggle, and two tutorial pages. Fixes several placement bugs found
    along the way (broken suspend-context usage, unresolved DI
    references, malformed syntax, and an unreachable branch in
    GameHubViewModel), and adds unit test coverage for the service's
    gating and Play Core success/failure branches.

commit 1a71dad
Author: grunt-claude-bot <admin@brainwallet.co>
Date:   Thu Jul 30 16:42:24 2026 +0100

    docs: add v4.11.0 release notes to README

commit faac661
Merge: 07224b9 3f2db9d
Author: Kerry Washington <mrkerrywashington@icloud.com>
Date:   Thu Jul 30 16:14:18 2026 +0100

    Merge branch 'main' into release/v4.11.0

commit 07224b9
Author: grunt-claude-bot <admin@brainwallet.co>
Date:   Thu Jul 30 16:10:21 2026 +0100

    test: add unit test coverage for pure utility helpers and gameinterface logic

    Covers previously-untested Base58, TypesConverter, BytesUtil, BRCompressor,
    and BRDateUtil classes, plus the GameKoinModule DI wiring and GdxGameView's
    game-exit JSON parsing/social-share dispatch logic (GameExitData and
    handleGameExit widened from private to internal to make them testable).

commit 3f2db9d
Author: grunt-claude-bot <admin@brainwallet.co>
Date:   Wed Jul 29 14:17:53 2026 +0100

    docs: standardize release headers to bold version + PR + divider format (#254) (#256)

    Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
)

Decouple the review-prompt scheduling from the social-share flow so it
runs on every game exit, not just when the player taps Twitter/Instagram
share. Adds GameHubEvent.OnGameFinished, dispatched unconditionally from
GdxGameView's onExit callback, and bumps the post-exit delay before
checking showInAppReviewDialogIfNeeded() from 800ms to 3s.
* Pass user IP address to MoonPay signed-url request in ReceiveDialog

Fetches the device's public IP via ipify and includes it as the
ipAddress param when requesting the MoonPay signed buy URL, per
MoonPay's recommendation for KYC/risk checks.

* Fix empty ipAddress in MoonPay signed-url requests

The Buy button in ReceiveDialog read a userIPAddress fetched
asynchronously on screen load, racing the ipify lookup and often
reaching MoonPay's signed-url request before it resolved. Move the ip
lookup into fetchMoonpaySignedUrl itself so it's always fetched fresh
and awaited right before use, removing the racy state field and its
lookup from ReceiveDialogViewModel/State/Dialog.

Add tests covering fetchMoonpaySignedUrl's ip inclusion/fallback and
LtcRepositoryImpl.fetchUserIpAddress's success/failure paths.
The key in service-data.json's agent-base64-pubkey is a base64-encoded
OpenSSH "ssh-rsa <base64-blob> <comment>" line, not base64-encoded PEM.
The previous PEM-header-stripping logic stripped whitespace before
re-decoding, merging the "ssh-rsa" prefix and email comment into the
base64 payload and corrupting it, which surfaced as
ERROR-CANNOT-KEYBYTES-DO-CONVERSION on every call.

Parse the OpenSSH line properly instead: split out the key blob field,
then decode the RFC 4253 wire format to get the RSA modulus/exponent
and build an RSAPublicKeySpec (the previous X509EncodedKeySpec approach
would have failed regardless, since OpenSSH's wire format isn't X.509
DER).

Add UtilsAgentStringTest covering the real provisioned key, a full
generated-keypair encrypt/decrypt round trip through the new parsing
path, and malformed-input error cases.
Clean up excess/misleading analytics events (#266)

* Remove excess/duplicate analytics events

Removes app_launched, home_open, and did_skip_top_up events. The
did_skip_top_up event was being fired twice, once each from
TopUpScreen and YourSeedProveItScreen. Also cleans up now-unused
imports left behind in BreadActivity.java and an unused
RSAPublicKeySpec import in UtilsAgentStringTest.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>

* Remove empty DatabaseTests class

The class body was empty aside from dead commented-out code left
over from a previous Java test suite that was never reactivated.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>

* Stop firing did_request_rating/user_completed_rating analytics events

The Play Core in-app review API gives no guarantee that requesting a
review actually shows the dialog to the user, so logging these as
Firebase events overstated what happened. Replace the calls with
local Timber debug traces at each showInAppReviewDialogIfNeeded()
call site, and drop the now-unused BWConstants entries.

Also cleans up InAppReviewServiceTest, which still referenced the
removed constant and mocked AnalyticsManager for calls that no
longer happen.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>

* Add informational audit test for AnalyticsManager.log* call sites

Scans app/src/main at test-run time and prints every call site
(file:line, method, event name), grouped by event name and by
method, so the analytics inventory stays visible and self-updating
instead of living only in an external doc. Purely diagnostic — never
fails on content, only if the scan itself finds nothing.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>

---------

Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>
Co-Authored-By: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
* Updated the bw-gdlib

* bump

---------

Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
* updated game

* Update TutorialSendBentoScreen.kt

---------

Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
…#269)

* Update modules

* Wire BRPeerManagerSetIntegrityWarningCallback into Firebase Crashlytics

Registers core's new optional integrity-warning callback (see
gruntsoftware/core PR #17/#18) so the previously-silent defensive
fallback for a NULL manager->lastBlock in BRPeerManager.c is now
monitored instead of going unnoticed.

- PeerManager.c: new static integrityWarning() JNI callback, following
  the same getEnv()/CallStaticVoidMethod pattern as txStatusUpdate()
  etc., registered via BRPeerManagerSetIntegrityWarningCallback()
  right after BRPeerManagerSetCallbacks() in create().
- BRPeerManager.java: new onIntegrityWarning(String) static method,
  called from native. Reports via
  FirebaseCrashlytics.getInstance().recordException(...), matching
  the existing defensive-fallback-reporting precedent in
  BRKeyStore.java, so this shows up as a monitored non-fatal issue in
  the Crashlytics console rather than silently.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>

---------

Co-authored-by: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com>
Co-authored-by: kcw-grunt <mrkerrywashington@icloud.com>
Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>
grunt-claude-bot and others added 4 commits August 7, 2026 21:05
Covers the new Java-side half of the native integrity-warning path:
recording a RuntimeException to Crashlytics with the native warning
text, once per distinct call site, and not throwing on a null warning.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>
Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>
Resolves conflicts from main's independent v4.11.0-era state diverging
from develop: keeps our newer targetSdk 36/v4.12.0 version bump, our
v4.12.0 release notes section, and the analytics-cleanup commit's
did_request_rating/user_completed_rating -> Timber.d downgrade (both
in the affected call sites and their test coverage) over main's older
AnalyticsManager-based versions. Also pulls in a GameHubViewModel
did_play_game tracking + review-prompt trigger that shipped directly
to main in v4.11.0 but was missing from develop.

Co-Authored-By: grunt-claude-bot <admin@brainwallet.co>
Co-Authored-By: kcw-grunt <mrkerrywashington@icloud.com>

@kcw-grunt kcw-grunt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kcw-grunt
kcw-grunt merged commit 2dfb6b6 into main Aug 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants