Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
> **iOS counterpart:** the sibling [Quantum Coin iOS wallet](https://github.com/quantumcoinproject/quantum-coin-wallet-ios)
> is kept feature-parity with this Android client. Both share the same
> JavaScript SDK bundle byte-for-byte and the same `en_us.json`
> localization catalog (parity-tested on every build).
> localization catalog as the canonical reference; the Android-side
> `JsonInteractParityTest` keeps the key set and accessor surface in
> lockstep with the iOS catalog.

Native Android client for the [Quantum Coin](https://quantumcoin.org)
post-quantum blockchain. Quantum Coin is a Layer-1 quantum-resistant
Expand Down Expand Up @@ -918,11 +920,11 @@ the one shipped by the iOS wallet at
regenerates `GeneratedBundleHash.java` so the constant inside
`classes.dex` stays in lockstep with the shipping bundle bytes.
Wired as a `preBuild` dependency.
- **`syncIosLocale` Gradle task** — pulls a snapshot of the iOS
`en_us.json` into `app/src/test/resources/locale-snapshots/`
before unit tests run, so the
[`EnUsParityTest`](app/src/test/java/com/quantumcoinwallet/app/locale/EnUsParityTest.java)
byte-compares both files in CI.
- **`syncIosImpersonatorFilter` Gradle task** — pulls a snapshot of
the iOS `StablecoinImpersonatorFilter.swift` into
`app/src/test/resources/code-snapshots/` before unit tests run so
the Android-side parity test can byte-compare the two pattern
lists in CI. Skip-on-missing for fresh checkouts.

---

Expand Down Expand Up @@ -997,7 +999,7 @@ contract documented in
│ ├── webpack.config.js
│ └── src/ Re-export glue around the upstream SDKs
└── app/
├── build.gradle App module + embedBundleHash + syncIosLocale tasks
├── build.gradle App module + embedBundleHash + syncIosImpersonatorFilter tasks
├── proguard-rules.pro
└── src/
├── main/
Expand Down Expand Up @@ -1172,7 +1174,6 @@ It contains 29 test classes and 183 unit tests:
| `interact/JsonInteractParityTest` | Localization-key presence, accessor wiring, OS-specific divergence wording (root vs jailbreak, Play Store vs App Store, Android Auto Backup vs iCloud) |
| `keystorage/AddNetworkPersistsToStrongboxTest` | Pins that adding a custom network goes through the strongbox `customNetworks` field (not `SharedPreferences`) and survives a relock/unlock round-trip |
| `keystorage/MacUtilTest` | HMAC + HKDF primitives used by the generation-counter, brute-force-lockout binders, and the v=3 file MAC / inner checksum |
| `locale/EnUsParityTest` | Cross-file byte-comparison between `app/src/main/res/raw/en_us.json` and the iOS snapshot (synced by `:app:syncIosLocale` before tests run) |
| `networking/UrlBuilderHostInvariantTest` | Post-substitution URL host MUST equal the configured `BlockchainNetwork.blockExplorerDomain` (anti-host-pivot) |
| `networking/UrlBuilderLockdownTest` | CI grep guard — fails if a naive `replace("{address}"…)` or `replace("{txhash}"…)` call site reappears anywhere |
| `networking/UrlBuilderTest` | Strict regex acceptance + percent-encoding behavior |
Expand Down
68 changes: 13 additions & 55 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -267,63 +267,21 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
}

// =================================================================
// Cross-platform localization snapshot sync
// =================================================================
// The :app:syncIosLocale task copies the iOS en_us.json file into
// app/src/test/resources/locale-snapshots/ios_en_us.json so the
// Android EnUsParityTest can byte-compare both files at CI time.
// Source-of-truth for the iOS file is the sibling repo at
// ../quantum-coin-wallet-ios. The path is configurable via the
// -PiosRepo=... gradle property for non-default checkouts.
// Workflow:
// 1. Open the Android repo and the iOS repo side-by-side.
// 2. Make a localization change on whichever side is the
// change source.
// 3. Run `./gradlew :app:syncIosLocale` from the Android repo.
// 4. Commit BOTH the iOS edit (in the iOS repo) AND the
// regenerated snapshot (in the Android repo) so the parity
// test sees the new state.
// The task also runs implicitly before unit tests (`testDebugUnitTest`
// dependsOn) so a stale snapshot does NOT silently pass parity in CI:
// the snapshot is regenerated from the iOS sibling on every test
// run when the sibling repo is present, and the parity test fails
// loudly if it is not.
// CI hook: a CI job that mounts both repos can run
// ./gradlew :app:syncIosLocale && ./gradlew :app:testDebugUnitTest
// and fail the build on any drift.
tasks.register('syncIosLocale') {
description = 'Copy iOS en_us.json snapshot into Android test resources for parity test'
group = 'verification'

doLast {
def iosRepo = project.findProperty('iosRepo')
?: file("${rootDir}/../quantum-coin-wallet-ios").absolutePath
def src = file("${iosRepo}/QuantumCoinWallet/Resources/en_us.json")
def dst = file("${projectDir}/src/test/resources/locale-snapshots/ios_en_us.json")
if (!src.exists()) {
logger.warn("syncIosLocale: source file not found at ${src.absolutePath}; "
+ "skipping. Pass -PiosRepo=/path/to/quantum-coin-wallet-ios to override.")
return
}
dst.parentFile.mkdirs()
dst.bytes = src.bytes
logger.lifecycle("syncIosLocale: snapshot updated -> ${dst.absolutePath}")
}
}

// =================================================================
// Cross-platform impersonator-filter snapshot sync
// =================================================================
// The :app:syncIosImpersonatorFilter task copies the iOS Swift file
// for StablecoinImpersonatorFilter into
// app/src/test/resources/code-snapshots/StablecoinImpersonatorFilter.swift
// so the Android StablecoinImpersonatorFilterTest parity check can
// byte-compare the two pattern lists at CI time. Same skip-on-missing
// + auto-runs-before-unit-tests semantics as syncIosLocale; same
// reasoning. If you change the Android filter PATTERNS list, walk the
// Swift file too and run :app:syncIosImpersonatorFilter to regenerate
// the snapshot.
// byte-compare the two pattern lists at CI time. Source-of-truth for
// the iOS file is the sibling repo at ../quantum-coin-wallet-ios; the
// path is configurable via the -PiosRepo=... Gradle property for
// non-default checkouts. The task is skip-on-missing so a developer
// without the sibling repo checked out still gets a clean test run.
// If you change the Android filter PATTERNS list, walk the Swift file
// too and run :app:syncIosImpersonatorFilter to regenerate the
// snapshot.
tasks.register('syncIosImpersonatorFilter') {
description = 'Copy iOS StablecoinImpersonatorFilter.swift snapshot into Android test resources for parity test'
group = 'verification'
Expand All @@ -346,11 +304,11 @@ tasks.register('syncIosImpersonatorFilter') {

afterEvaluate {
tasks.matching { it.name.startsWith('test') && it.name.endsWith('UnitTest') }.configureEach {
// Best-effort: pull fresh iOS snapshots before unit tests
// so the parity checks see current state. The tasks are
// skip-on-missing (see above), so a developer without the
// sibling repo checked out still gets a clean test run.
dependsOn 'syncIosLocale'
// Best-effort: pull a fresh impersonator-filter snapshot
// before unit tests so the parity check sees current state.
// The task is skip-on-missing (see above), so a developer
// without the sibling repo checked out still gets a clean
// test run.
dependsOn 'syncIosImpersonatorFilter'
}
}
2 changes: 1 addition & 1 deletion app/src/main/res/raw/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
"no-tokens": "No tokens for this address",
"contract": "Contract",
"symbol": "Symbol",
"asset-to-send": "Asset to send",
"asset-to-send": "What to send?",
"what-is-being-sent": "Which item to send?",
"from-address": "From Address",
"to-address": "To Address",
Expand Down
Loading
Loading