Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
175 commits
Select commit Hold shift + click to select a range
a746d3d
Merge pull request #53 from gruntsoftware/release/v4.4.1
kcw-grunt Mar 30, 2025
ba8ea45
Revert "Updated the APIManager"
kcw-grunt Mar 31, 2025
638ce3a
Reverted the Kotlin APIManager.
kcw-grunt Mar 31, 2025
5344a96
Add version and code to welcome screen
kcw-grunt Mar 31, 2025
ebc3bb9
reset padding
kcw-grunt Mar 31, 2025
03c68b8
Merge pull request #56 from gruntsoftware/feature/add-version-issue-108
kcw-grunt Apr 1, 2025
c770f01
Revert "Updated the APIManager"
kcw-grunt Mar 31, 2025
f8c1bd5
Reverted the Kotlin APIManager.
kcw-grunt Mar 31, 2025
ff58b57
fix: fix crash when FragmentSignal dismissed
andhikayuana Apr 4, 2025
d64ea93
fix: fix sync after wipe
andhikayuana Apr 7, 2025
277f387
Merge branch 'hotfix/api-manager-fix' of https://github.com/brainwall…
kcw-grunt Apr 7, 2025
9a8a20f
fix: fix wrong lifecycle to trigger callback at FragmentSignal
andhikayuana Apr 7, 2025
832fa36
Update issue templates
kcw-grunt Apr 8, 2025
b294cd6
updated core changes
kcw-grunt Apr 8, 2025
4ed8af3
Merge pull request #57 from gruntsoftware/hotfix/api-manager-fix
kcw-grunt Apr 8, 2025
089995a
Rename .java to .kt
andhikayuana Apr 11, 2025
7e85987
chore: refactor BRApiManager & APIClient
andhikayuana Apr 11, 2025
0aa41e7
chore: remove unused part at APIClient
andhikayuana Apr 14, 2025
0cc29cb
feat: wip new peer discovery
andhikayuana Apr 14, 2025
54d2d9e
feat: implement selected peer ip address from cache (fetched from API)
andhikayuana Apr 14, 2025
c6923b1
feat: implement selected peer ip address from cache (fetched from API)
andhikayuana Apr 14, 2025
ef4e6d2
feat: filter out peers with NODE_NETWORK, NODE_BLOOM
andhikayuana Apr 15, 2025
2336939
fix: race condition when clear shared prefs values after wipeAll
andhikayuana Apr 16, 2025
51a4d9a
Merge pull request #62 from gruntsoftware/hotfix/prefs-race-condition
kcw-grunt Apr 16, 2025
62afb9d
Merge pull request #61 from gruntsoftware/feat/new-peer-discovery
kcw-grunt Apr 16, 2025
391e319
Updating the core library
kcw-grunt Apr 16, 2025
836b6f0
Chore/revert pre peer discovery (Android) (#69)
andhikayuana Apr 25, 2025
3caa1a7
feat: remove unused activity (ImportActivity) at AndroidManifest.xml …
andhikayuana Apr 29, 2025
a324444
fix: fix crash IllegalStateException: cannot make a new request becau…
andhikayuana May 2, 2025
1f9c5d0
code bump
kcw-grunt May 2, 2025
7ca17d3
Feat/move tx fee (#74)
andhikayuana May 5, 2025
e7fb2b7
fix: fix dismiss allow state loss (#76)
andhikayuana May 7, 2025
3d8ef98
fix: fix typo at strings.xml (#75)
andhikayuana May 7, 2025
25be1a8
feat: new UI for receive and topup flow (moonpay integration) (#72)
andhikayuana May 12, 2025
18eda46
version and code bump
kcw-grunt May 12, 2025
dbb6a5f
Added support url (#81)
kcw-grunt May 19, 2025
c82a5e3
version and code bump
kcw-grunt May 19, 2025
2981ec6
Revert from eda0f532 & cherry pick (#86)
andhikayuana May 25, 2025
c161fed
build bump
kcw-grunt May 25, 2025
63d7494
chore: open bread activity first then open moonpay widget (#88)
andhikayuana May 26, 2025
f09eb28
Adjustment for circleci (#89)
andhikayuana May 29, 2025
47c69cd
fix: android: Footer version label is obfuscated (#92)
andhikayuana May 29, 2025
a9c4a63
fix: You saved it right screen reset button covers words (#93)
andhikayuana May 29, 2025
d8f08ad
tiny resizing (#94)
kcw-grunt May 29, 2025
ef93585
code and version bump
kcw-grunt May 29, 2025
174d612
change break (#97)
kcw-grunt May 30, 2025
a4521d7
build code number
andhikayuana May 31, 2025
6fec1e2
fix: [#137] fix: Reset fiat options in Buy / Receive modal (#99)
andhikayuana Jun 2, 2025
c8fdf8d
Chore/update device data (#100)
kcw-grunt Jun 2, 2025
3f646df
Add agent string obfuscation (#103)
kcw-grunt Jun 14, 2025
642f9e6
updated metadata (#105)
kcw-grunt Jun 24, 2025
50f44de
chore: remove screen lock detection (#106)
andhikayuana Jun 24, 2025
ea15423
🧰 Fx/seed words UI polish (#108)
kcw-grunt Jun 25, 2025
5b6dff3
❇️ Feat/add polish punjabi farsi (#109)
kcw-grunt Jun 27, 2025
4ff50b8
fix: fix YourSeedProveItState.isWordUsedCorrectly (#111)
andhikayuana Jun 29, 2025
2fd094f
Upgrade targetSdk 36 (#113)
andhikayuana Aug 5, 2025
4efd95f
feat(#195): adopt koin annotation for dependency injection (#120)
JosephSanjaya Aug 18, 2025
5900ed4
Create PULL_REQUEST_TEMPLATE.md
kcw-grunt Aug 19, 2025
3b1f628
bump for test (#121)
kcw-grunt Aug 19, 2025
f11d5a8
feat(#174): initiate games module (#119)
JosephSanjaya Aug 20, 2025
77ed7df
Force pushed the version and code bump and catch up with Prod.
kcw-grunt Aug 20, 2025
ef353e3
🚀[Release v4.7.2] Merge into Develop (#127)
kcw-grunt Aug 25, 2025
e25e9e1
Fix(#227): Crash when launching MoonPay Widget (#126)
JosephSanjaya Aug 25, 2025
e0aebb5
feat(#138): Improve Unlock Screen UI and add dark mode toggle (#122)
JosephSanjaya Aug 25, 2025
7d8dd14
Redesign welcome screen (#123)
josikie Aug 25, 2025
18df293
fix(#226): android test failure on RecoverWalletScreenGrabsTest (#130)
JosephSanjaya Aug 26, 2025
9b191b7
fix(#223): fix issue on test environment SLF4J + agent warnings in te…
JosephSanjaya Aug 26, 2025
c9b1ef0
feat(#225): Send sync duration in Firebase Analytics as a custom even…
JosephSanjaya Aug 30, 2025
58f0cd8
feat(#234): Implement language-specific Firebase messaging topics (#132)
JosephSanjaya Aug 30, 2025
4bb06cf
bump code
kcw-grunt Aug 30, 2025
9f4908b
chore(#234): normalize Indonesian language code to "id" (#133)
JosephSanjaya Aug 30, 2025
4a27ae0
Feat: Detekt and Test Plugins, (Including Create Detekt Baseline) (#136)
JosephSanjaya Sep 22, 2025
3285d8e
Chore/renamed gp module (#140)
kcw-grunt Sep 29, 2025
94459d0
create private gp submodule
kcw-grunt Sep 23, 2025
13c39a0
Update settings.gradle.kts
kcw-grunt Sep 23, 2025
c0683ad
chore: Migrate build logic to version catalog and update submodules
JosephSanjaya Sep 24, 2025
2fcb1b2
feat(deps): add kotlin-immutable dependency
JosephSanjaya Oct 5, 2025
631b4f6
refactor(app): Relocate core module imports
JosephSanjaya Oct 8, 2025
0b46614
Merge pull request #141 from gruntsoftware/js/feat/iap
JosephSanjaya Oct 9, 2025
279fa23
build bump
kcw-grunt Oct 9, 2025
5ea5fe9
Merge branch 'main' into release/v4.8.0
kcw-grunt Oct 9, 2025
779f47f
removed cruft
kcw-grunt Oct 9, 2025
05813ad
chore(submodules): remove bugged games repository
JosephSanjaya Oct 10, 2025
67000e9
bump code
kcw-grunt Oct 10, 2025
83f1b85
Release/v4.8.0 (#147)
kcw-grunt Oct 13, 2025
798b7c9
Merge branch 'develop' of https://github.com/brainwallet-co/android i…
kcw-grunt Oct 18, 2025
d5b14db
bug(crashlytics): Remove InputWordsActivity from AndroidManifest
JosephSanjaya Oct 18, 2025
a08c6f6
fix(crashlytics): resolve ReEnterPinActivity crashes and deprecation …
JosephSanjaya Oct 22, 2025
56be50f
Remove resizability and orientation restrictions in our app (#146)
josikie Oct 22, 2025
2f06f5c
build: Build secp256k1 as a static library and add ABI splits (#149)
JosephSanjaya Nov 14, 2025
aeb5ab5
version bump (#159)
kcw-grunt Feb 7, 2026
8cabf2a
Revert "build: Build secp256k1 as a static library and add ABI splits…
kcw-grunt Feb 7, 2026
0a46627
Techdebt/adjust build logic (#161)
kcw-grunt Feb 8, 2026
5738254
Added auto translate (#178)
kcw-grunt Apr 9, 2026
25fcc5d
Add auto-translate workflow for strings.xml
kcw-grunt Apr 12, 2026
34e6d65
Create PR summary workflow with GitHub Copilot
kcw-grunt Apr 12, 2026
914bcd3
Changed the AI completions
kcw-grunt Apr 13, 2026
ca4fcd9
Add more information of the README
kcw-grunt Apr 13, 2026
12a3d83
nit fix
kcw-grunt Apr 13, 2026
5d3a6c2
🦖😱 HUGE!! Bento Integration (#192)
kcw-grunt Apr 14, 2026
010be8c
Release/v4.9.0 (#201)
kcw-grunt Apr 16, 2026
ce9d121
Feat/add thai dutch (#197)
kcw-grunt Apr 16, 2026
083027f
update gitignore (#204)
kcw-grunt Apr 17, 2026
9495b47
Add send/receive transaction integration tests (#203)
kcw-grunt Apr 18, 2026
e0b116a
Designdebt/polish main screen (#205)
kcw-grunt Apr 18, 2026
902f8fb
Feat/add gamehub minigame (#206)
kcw-grunt Apr 19, 2026
879ab6f
Moved clipboard test point into event (#207)
kcw-grunt Apr 20, 2026
334ce3b
moved out of the state update (#208)
kcw-grunt Apr 20, 2026
4455c07
Version bump
kcw-grunt Apr 20, 2026
fcc51f9
fix: issue when user tries to send (#210)
kcw-grunt Apr 22, 2026
b220d0f
reformatted the test
kcw-grunt Apr 18, 2026
5d01e5a
Added More comprehensive Send Tests to cover better stability
kcw-grunt Apr 23, 2026
2ad22d0
Add WalletManager; make validateAddress instance
kcw-grunt Apr 23, 2026
f0e16f6
Updated the InstrumentedTest
kcw-grunt Apr 23, 2026
9a88ed3
adjust the config
kcw-grunt Apr 23, 2026
a11b4db
adjust config for running
kcw-grunt Apr 23, 2026
39cb5f4
Further fix
kcw-grunt Apr 23, 2026
b981834
Fixes the bug: BreadActivity.initializeViews
kcw-grunt Apr 23, 2026
7828223
Merge branch 'main' into release/v4.9.1
kcw-grunt Apr 24, 2026
4c0e7d3
updated README
kcw-grunt Apr 24, 2026
0bfe9d7
bump
kcw-grunt Apr 24, 2026
8c3f1b2
chore(i18n): auto-translate missing strings [skip ci] (#213)
kcw-grunt Apr 24, 2026
bc79335
Feat/add tutorial functionality (#214)
kcw-grunt Apr 24, 2026
2af613b
Feat/bitrefill in topsecret (#217)
kcw-grunt Apr 25, 2026
38be6d1
Feat/bitrefill in topsecret (#221)
kcw-grunt Apr 25, 2026
04e8546
added polish
kcw-grunt Apr 25, 2026
7224aff
Merge branch 'main' into develop
kcw-grunt Apr 27, 2026
c792f3e
✨ 🛍️ 🛠️ Fix/sendmodelonevent crash (#224)
kcw-grunt Apr 27, 2026
6efad7a
Fixxed the layout of the LTC fiat picker (#225)
kcw-grunt Apr 27, 2026
2025282
Refactored the analytics (#226)
kcw-grunt Apr 28, 2026
f403751
Merge branch 'main' into develop
kcw-grunt Apr 28, 2026
734b3d7
🧼 Techdebt/sync reduce rate fpf (#228)
kcw-grunt May 1, 2026
a20d678
bump
kcw-grunt May 1, 2026
d046259
Techdebt/sync reduce rate fpf (#229)
kcw-grunt May 3, 2026
9cc425d
Feat/add remote shop (#230)
kcw-grunt May 9, 2026
728f354
version bump
kcw-grunt May 10, 2026
e57563d
add part of test
kcw-grunt May 10, 2026
d0886db
Techdebt/improve shop (#232)
kcw-grunt May 28, 2026
44fcc8f
Added analytics when user shows invoice in shop (#233)
kcw-grunt May 28, 2026
89a8974
Techdebt/modules update (#235)
kcw-grunt Jun 6, 2026
9fbfeae
Fallinmoji closed branch (#241)
kcw-grunt Jul 6, 2026
143bdac
version bump
kcw-grunt Jul 6, 2026
bc615f5
Techdebt/polish interface (#242)
kcw-grunt Jul 16, 2026
28f8f56
Fix/polish clear set emojis (#243)
kcw-grunt Jul 17, 2026
81a8870
Set the clean process
kcw-grunt Jul 17, 2026
afdfcfb
Merge branch 'main' into release/v4.10.4
kcw-grunt Jul 17, 2026
b5ca771
Changed the pat
kcw-grunt Jul 17, 2026
26e5c90
polish yml
kcw-grunt Jul 17, 2026
134926c
Use typical GH TOKEN
kcw-grunt Jul 17, 2026
c45fa05
Refactor yml
kcw-grunt Jul 17, 2026
ec2a406
chore(i18n): auto-translate missing strings [skip ci] (#245)
github-actions[bot] Jul 17, 2026
f43bd4b
Unescaped the ' in the translated strings in non-exist
kcw-grunt Jul 17, 2026
3bf7a25
fix(BreadActivity): remove dead bottom-nav/toolbar cruft causing star…
grunt-claude-bot Jul 22, 2026
5eba52b
fix(submodules): remove stale modules/bw-gdlib gitlink breaking CI (#…
grunt-claude-bot Jul 22, 2026
116df6a
fix(shop-bento): eliminate flaky ShopBentoViewModelTest + version bum…
grunt-claude-bot Jul 22, 2026
cb556da
docs: real SECURITY.md + restructured README for external visitors (#…
grunt-claude-bot Jul 22, 2026
459fe19
Bump bw-gdlib submodule to pick up ghost-launcher fix (#252)
grunt-claude-bot Jul 26, 2026
01c2e60
Merge branch 'main' into develop
grunt-claude-bot Jul 29, 2026
7f6aaec
docs: standardize release headers to bold version + PR + divider form…
grunt-claude-bot Jul 29, 2026
91b91dd
🎮 Fallinmoji v1.6.0: Ready, Set, GO! (#257)
grunt-claude-bot Jul 30, 2026
9d89ff3
Add this skill
grunt-claude-bot Jul 30, 2026
0bc2053
ci: add shields.io tests-passed badge backed by a gist endpoint (#262)
grunt-claude-bot Jul 31, 2026
054ac5c
Squashed commit of the following:
grunt-claude-bot Jul 31, 2026
ba32beb
Fix in-app review prompt not firing when player skips social share (#…
grunt-claude-bot Jul 31, 2026
197c2d9
Add and fix MoonPay ipAddress param in ReceiveDialog buy flow (#264)
grunt-claude-bot Aug 1, 2026
44e420c
Fix RSA public key parsing in getEncryptedAgentString (#265)
grunt-claude-bot Aug 3, 2026
bd1c5b7
bump beta build
grunt-claude-bot Aug 4, 2026
d8c20dd
chore: bump targetSdk to 36 and update bw-gdlib (#267)
grunt-claude-bot Aug 4, 2026
0c1dc37
Update Fallinmoji (How To Play) and bento corner radius fix (#268)
grunt-claude-bot Aug 7, 2026
d7ce14d
🐛 Fix native SIGSEGV crash + report integrity warnings to Crashlytic…
grunt-claude-bot Aug 7, 2026
4b42a75
Cut release/v4.12.0: bump version to v4.12.0 (202506350)
grunt-claude-bot Aug 7, 2026
6ae66b1
update game to v1.6.2
grunt-claude-bot Aug 7, 2026
676caab
Add unit tests for BRPeerManager.onIntegrityWarning
grunt-claude-bot Aug 7, 2026
01e38fe
docs: add v4.12.0 release notes to README
grunt-claude-bot Aug 7, 2026
35f7837
Merge branch 'main' into release/v4.12.0
grunt-claude-bot Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,54 @@ jobs:
path: ~/test-results
- store_artifacts:
path: ~/test-results/junit
- run:
name: "Update tests-passed badge (shields.io gist endpoint)"
# Only the primary branch's run should own the badge state - a feature
# branch with fewer/broken tests shouldn't flap the README badge.
# Requires GIST_TOKEN (a PAT with only the `gist` scope) and GIST_ID
# to be set as CircleCI project env vars.
when: always
command: |
if [ "$CIRCLE_BRANCH" != "develop" ]; then
echo "Not on develop, skipping badge update."
exit 0
fi
if [ -z "$GIST_TOKEN" ] || [ -z "$GIST_ID" ]; then
echo "GIST_TOKEN/GIST_ID not set, skipping badge update."
exit 0
fi

total=0
failures=0
skipped=0
for f in ~/test-results/junit/*.xml; do
[ -f "$f" ] || continue
t=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
fl=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
sk=$(grep -o 'skipped="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*')
total=$((total + ${t:-0}))
failures=$((failures + ${fl:-0}))
skipped=$((skipped + ${sk:-0}))
done
passed=$((total - failures - skipped))

if [ "$failures" -gt 0 ]; then
message="${passed} passed, ${failures} failed"
color="red"
else
message="${passed} passed"
color="brightgreen"
fi

payload=$(printf '{"files":{"tests-badge.json":{"content":"{\\"schemaVersion\\":1,\\"label\\":\\"tests\\",\\"message\\":\\"%s\\",\\"color\\":\\"%s\\"}"}}}' "$message" "$color")

curl -s -X PATCH \
-H "Authorization: token ${GIST_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/gists/${GIST_ID}" \
-d "$payload" > /dev/null

echo "Badge updated: ${message}"

screengrab:
executor:
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### CircleCI status
[![Release](https://img.shields.io/github/v/release/gruntsoftware/android?style=plastic)](https://github.com/gruntsoftware/android/releases)
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/gruntsoftware/android/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/gruntsoftware/android/tree/main)
[![Tests](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/grunt-claude-bot/ae4830cb1b5da4611598d20a517fd933/raw/tests-badge.json)](https://dl.circleci.com/status-badge/redirect/gh/gruntsoftware/android/tree/develop)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Play Store
Expand Down Expand Up @@ -42,7 +43,7 @@

### Prerequisites
- Android Studio (current stable) with SDK 36 installed, NDK `25.1.8937393`, CMake `3.22.1`
- `minSdk 29`, `targetSdk 35`
- `minSdk 29`, `targetSdk 36`

## Architecture

Expand Down Expand Up @@ -73,6 +74,33 @@ For the full, up-to-date changelog see [GitHub Releases](https://github.com/grun

---

### **v4.12.0** [PR [#270](https://github.com/gruntsoftware/android/pull/270)]
---
#### ✨ In-App Review Activated
Google Play's in-app review prompt is now actually wired up after sitting dormant — `InAppReviewService` is registered in Koin and `showInAppReviewDialogIfNeeded()` is called from five placements: after a successful send, the game hub exit flow, the balance-visibility toggle, and two tutorial pages (#260). A follow-up fix decouples the prompt from the social-share flow so it fires on every game exit, not just when the player taps Twitter/Instagram share (#263).

#### 🐛 Bug Fixes
- **Native `SIGSEGV` crash in `BRPeerManager`** — several accessors (`BRPeerManagerEstimatedBlockHeight`, `LastBlockHeight`, `LastBlockTimestamp`, `SyncProgress`) dereferenced `lastBlock` without a null check, crashing the process if a checkpoint block was ever missing from the persisted block set. `core`'s `BRPeerManager.c` now guards every access and reports the recovery back to the app via a new `BRPeerManagerSetIntegrityWarningCallback`, surfaced through `BRPeerManager.onIntegrityWarning()` to Crashlytics instead of crashing (#269).
- **RSA public key parsing in `getEncryptedAgentString`** — the provisioned key is an OpenSSH `ssh-rsa <base64-blob> <comment>` line, not base64-encoded PEM; the old PEM-stripping logic corrupted the payload by merging the `ssh-rsa` prefix and comment into it, failing on every call. Now parses the OpenSSH wire format (RFC 4253) directly (#265).
- **MoonPay signed-url `ipAddress` race** — the Buy button in `ReceiveDialog` could reach MoonPay's signed-url request before the async ipify lookup resolved, sending an empty `ipAddress`. The lookup now happens fresh inside `fetchMoonpaySignedUrl` itself, awaited right before use (#264).

#### 🎮 Fallinmoji v1.6.2
"How To Play" screen update and a bento corner-radius fix, plus a bw-gdlib bump to pick it up (#268).

#### 📊 Analytics Cleanup
Removed excess/duplicate events (`app_launched`, `home_open`, a double-firing `did_skip_top_up`) and stopped logging `did_request_rating`/`user_completed_rating` as Firebase events — the Play Core API gives no guarantee a review request actually shows the dialog, so these overstated what happened. Replaced with local `Timber.d` traces (#266).

#### 🧪 Test Coverage
Added coverage for `InAppReviewService` (gating + Play Core success/failure branches), `UtilsAgentString`'s new OpenSSH parsing path, MoonPay ip-fetch success/failure, and `BRPeerManager.onIntegrityWarning`'s Crashlytics reporting.

#### 🔧 Chores
- Bumped `targetSdk` to 36 (#267)
- Version bumped: **v4.11.0 (202506346) → v4.12.0 (202506350)**

**Full Changelog**: https://github.com/gruntsoftware/android/compare/v4.11.0...v4.12.0

---

### **v4.11.0** [PR [#259](https://github.com/gruntsoftware/android/pull/259)]
---
#### 🎮 Fallinmoji v1.6.0 — Ready, Set, GO!
Expand Down
6 changes: 3 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ android {
defaultConfig {
applicationId = "ltd.grunt.brainwallet"
minSdk = 29
targetSdk = 35
versionCode = 202506347
versionName = "v4.11.0"
targetSdk = 36
versionCode = 202506350
versionName = "v4.12.0"
multiDexEnabled = true
base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Expand Down
1 change: 0 additions & 1 deletion app/src/main/java/com/brainwallet/BrainwalletApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ open class BrainwalletApp : Application() {
FirebaseCrashlytics.getInstance().setCustomKey("build_type", "debug")
FirebaseCrashlytics.getInstance().setUserId("debug_bw_devices")
}
AnalyticsManager.logCustomEvent(BWConstants._20191105_AL)

if (BuildConfig.DEBUG) Timber.plant(DebugTree())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package com.brainwallet.appreview

import android.app.Activity
import android.app.Application
import com.brainwallet.constants.BWConstants
import com.brainwallet.tools.manager.AnalyticsManager
import com.brainwallet.tools.manager.BRSharedPrefs
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
Expand All @@ -26,7 +24,6 @@ class InAppReviewService(
val request = manager.requestReviewFlow()
request.addOnCompleteListener(
OnCompleteListener { task: Task<ReviewInfo>? ->
AnalyticsManager.logCustomEvent(BWConstants._20241006_DRR)
if (task!!.isSuccessful()) {
val reviewInfo = task.getResult()
val flow = manager.launchReviewFlow(activity, reviewInfo)
Expand All @@ -41,7 +38,6 @@ class InAppReviewService(
)
if (task1.isSuccessful()) {
BRSharedPrefs.inAppReviewDone(app)
AnalyticsManager.logCustomEvent(BWConstants._20241006_UCR)
}
}
)
Expand Down
6 changes: 1 addition & 5 deletions app/src/main/java/com/brainwallet/constants/BWConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ object BWConstants {
* API Hosts
*/
const val BW_API_PROD_HOST: String = "https://api.grunt.ltd"
const val IPIFY_API_HOST: String = "https://api.ipify.org?format=text"

const val BLOCKCHAIR_EXPLORER_BASE_URL: String = "https://blockchair.com/litecoin/transaction/"
const val BLOCKCYPHER_EXPLORER_BASE_URL: String = "https://live.blockcypher.com/ltc/"

const val _20191105_AL: String = "app_launched"
const val _20191105_VSC: String = "visit_send_controller"
const val _20202116_VRC: String = "visit_receive_controller"
const val _20191105_DSL: String = "did_send_ltc"
Expand All @@ -133,11 +133,7 @@ object BWConstants {
const val _20201118_DTGS: String = "did_tap_get_support"
const val _20200217_DU: String = "did_unlock"

const val _20250303_DSTU: String = "did_skip_top_up"
const val _20250517_WCINFO: String = "wallet_callback_info"
const val _20241006_DRR: String = "did_request_rating"
const val _20241006_UCR: String = "user_completed_rating"
const val _HOME_OPEN: String = "home_open"

/**
* Analytics keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ interface LtcRepository {

suspend fun fetchMoonpaySignedUrl(params: Map<String, String>): String

suspend fun fetchUserIpAddress(): String

companion object {
const val PREF_KEY_NETWORK_FEE_PER_KB = "network_fee_per_kb"
const val PREF_KEY_NETWORK_FEE_PER_KB_CACHED_AT = "${PREF_KEY_NETWORK_FEE_PER_KB}_cached_at"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.core.net.toUri
import com.brainwallet.BuildConfig
import com.brainwallet.constants.BWConstants
import com.brainwallet.data.model.CurrencyEntity
import com.brainwallet.data.model.Fee
import com.brainwallet.data.model.LtcStats
Expand Down Expand Up @@ -33,8 +34,11 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import okhttp3.OkHttpClient
import okhttp3.Request
import org.koin.core.annotation.Single
import timber.log.Timber

Expand All @@ -47,6 +51,7 @@ class LtcRepositoryImpl(
private val peerManagerSource: PeerManagerSource,
private val repositoryScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO),
private val sharedPreferences: SharedPreferences,
private val okHttpClient: OkHttpClient,
) : LtcRepository {

// private val _rates = MutableStateFlow<List<CurrencyEntity>>(
Expand Down Expand Up @@ -170,6 +175,7 @@ class LtcRepositoryImpl(
"externalTransactionId" to externalTransactionID,
"currencyCode" to "ltc",
"themeId" to "main-v1.0.0",
"ipAddress" to fetchUserIpAddress(),
)
return remoteApiSource.getMoonpaySignedUrl(finalParams)
.signedUrl.toUri()
Expand All @@ -182,4 +188,21 @@ class LtcRepositoryImpl(
.build()
.toString()
}

override suspend fun fetchUserIpAddress(): String = withContext(Dispatchers.IO) {
runCatching {
val request = Request.Builder()
.url(BWConstants.IPIFY_API_HOST)
.build()
okHttpClient.newCall(request).execute().use { response ->
if (response.isSuccessful) {
response.body?.string()?.trim().orEmpty()
} else {
""
}
}
}.onFailure {
Timber.e(it, "fetchUserIpAddress failed")
}.getOrDefault("")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fun GdxGameView(
this.onExit = { jsonString, bytes ->
removeFragment(fm, tag)
viewModel.onEvent(MainScreenEvent.OnToggleGameHub)
gameHubViewModel.onEvent(GameHubEvent.OnGameFinished)
handleGameExit(jsonString, bytes, gameHubViewModel)
currentOnExit(jsonString, bytes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,13 @@
import com.brainwallet.presenter.customviews.BRNotificationBar;
import com.brainwallet.presenter.history.HistoryFragment;
import com.brainwallet.tools.animation.TextSizeTransition;
import com.brainwallet.tools.manager.AnalyticsManager;
import com.brainwallet.tools.manager.BRSharedPrefs;
import com.brainwallet.tools.manager.InternetManager;
import com.brainwallet.tools.manager.sync.SyncManager;
import com.brainwallet.tools.security.BitcoinUrlHandler;
import com.brainwallet.tools.security.PostAuth;
import com.brainwallet.tools.sqlite.TransactionDataSource;
import com.brainwallet.tools.threads.BRExecutor;
import com.brainwallet.constants.BWConstants;
import com.brainwallet.tools.util.BRCurrency;
import com.brainwallet.tools.util.BRExchange;
import com.brainwallet.tools.util.ExtensionKt;
Expand Down Expand Up @@ -98,7 +96,6 @@ public static BreadActivity getApp() {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bread);
AnalyticsManager.logCustomEvent(BWConstants._HOME_OPEN);
app = this;
getWindowManager().getDefaultDisplay().getSize(screenParametersPoint);

Expand Down
62 changes: 44 additions & 18 deletions app/src/main/java/com/brainwallet/tools/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import javax.crypto.BadPaddingException;
Expand Down Expand Up @@ -198,24 +200,12 @@ public static String getEncryptedAgentString(Context app) {

try {

// Convert base64 public key string to PublicKey object
byte[] keyBytes;
// Convert the base64-encoded OpenSSH "ssh-rsa <base64-blob> <comment>" public key
// string into an RSAPublicKeySpec
RSAPublicKeySpec keySpec;
try {
String pubkey = Utils.fetchServiceItem(app, ServiceItems.AGENTPUBKEY).toString();
keyBytes = Base64.getDecoder().decode(pubkey);

// Decode the base64 to get the PEM string
byte[] pemBytes = Base64.getDecoder().decode(pubkey);
String pemString = new String(pemBytes, "UTF-8");

// Extract just the key data (remove PEM headers and whitespace)
String keyData = pemString
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");

// Decode the clean base64 key data
keyBytes = Base64.getDecoder().decode(keyData);
keySpec = parseOpenSshRsaPublicKey(pubkey);
} catch (IllegalArgumentException e) {
Timber.d("Invalid base64 public key format: %s", e.toString());
return "ERROR-CANNOT-KEYBYTES-DO-CONVERSION";
Expand All @@ -232,7 +222,7 @@ public static String getEncryptedAgentString(Context app) {
// Generate PublicKey object
PublicKey publicKey;
try {
publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes));
publicKey = keyFactory.generatePublic(keySpec);
} catch (InvalidKeySpecException e) {
Timber.d("Invalid public key specification: %s", e.toString());
return "ERROR-CANNOT-INVALID-PUBLIC-KEY-SPEC";
Expand Down Expand Up @@ -275,6 +265,42 @@ public static String getEncryptedAgentString(Context app) {
}
}

// Parses a base64-encoded OpenSSH "ssh-rsa <base64-blob> <comment>" public key line
// (as provisioned in service-data.json's agent-base64-pubkey) into an RSAPublicKeySpec.
// Package-private so it can be unit-tested directly without needing a Context.
static RSAPublicKeySpec parseOpenSshRsaPublicKey(String base64EncodedOpenSshLine) {
String opensshLine = new String(Base64.getDecoder().decode(base64EncodedOpenSshLine), StandardCharsets.UTF_8);

// OpenSSH format: "ssh-rsa <base64-encoded-key-blob> [comment]"
String[] parts = opensshLine.trim().split("\\s+");
if (parts.length < 2) {
throw new IllegalArgumentException("Unexpected OpenSSH public key format");
}
byte[] keyBlob = Base64.getDecoder().decode(parts[1]);
return parseSshRsaPublicKeyBlob(keyBlob);
}

// Parses an RFC 4253 "ssh-rsa" wire-format key blob (as found in an OpenSSH
// public key line's second field) into the modulus/exponent RSA needs.
static RSAPublicKeySpec parseSshRsaPublicKeyBlob(byte[] keyBlob) {
ByteBuffer buffer = ByteBuffer.wrap(keyBlob);
String keyType = new String(readSshField(buffer), StandardCharsets.UTF_8);
if (!"ssh-rsa".equals(keyType)) {
throw new IllegalArgumentException("Unsupported SSH key type: " + keyType);
}
BigInteger exponent = new BigInteger(readSshField(buffer));
BigInteger modulus = new BigInteger(readSshField(buffer));
return new RSAPublicKeySpec(modulus, exponent);
}

// Each SSH wire-format field is a 4-byte big-endian length followed by that many bytes.
private static byte[] readSshField(ByteBuffer buffer) {
int length = buffer.getInt();
byte[] data = new byte[length];
buffer.get(data);
return data;
}

public static String reverseHex(String hex) {
if (hex == null) return null;
StringBuilder result = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import timber.log.Timber
import java.math.BigDecimal

@Composable
Expand Down Expand Up @@ -194,6 +195,7 @@ fun BalanceBentoScreen(
coroutineScope.launch {
delay(800L)
inAppReviewService.showInAppReviewDialogIfNeeded()
Timber.d("did_request_rating")
}
}
}
Expand Down Expand Up @@ -274,6 +276,7 @@ fun BalanceBentoScreen(
coroutineScope.launch {
delay(800L)
inAppReviewService.showInAppReviewDialogIfNeeded()
Timber.d("did_request_rating")
}
}
}
Expand Down
Loading