TronBalancesIntegrationTest.kt uses ScalarsConverterFactory but nothing in
app/build.gradle declared it, so the app module's androidTest compilation
failed with "Unresolved reference 'scalars'" - this was never caught before
now because the emulator balances_test.yml workflow only runs on a schedule/
manual dispatch, not on every push.
Hardcoded -e class only ran one test class, silently excluding any new
integration test added to the same package (e.g. the new full-architecture
BalancesUpdateSystem test).
BalancesUpdateSystem.launchChainUpdaters() wrapped updater.listenForUpdates() in a
try/catch that discarded synchronous exceptions with zero logging - a silent failure
point that could explain a chain's balances never syncing with no trace in logcat.
Also add a full-architecture instrumented test that persists a real watch-only
MetaAccount and polls AssetDao through the actual BalancesUpdateSystem pipeline,
instead of bypassing it like the existing BalancesIntegrationTest does.
SELECT * FROM chains had no ORDER BY - SQLite gives no ordering guarantee
for that, so registration/connection order across ~98 chains was
effectively unpredictable per run. The merged chains.json lists Pezkuwi's
own chains first (wallet-utils' merge_chains()), and CollectionDiffer's
findDiff()/Room's batch insert both preserve that order on first sync, so
ordering by rowid (insertion order) makes Pezkuwi's own chains reliably
register and start connecting first/early, instead of being interleaved
unpredictably among ~90+ Nova-inherited chains where they could end up
processed dead last - observed directly: a fresh install took 45+ seconds
without a single Pezkuwi-related registration/connection attempt.
No user should wait minutes for their own wallet's native chain to even
start syncing after install, regardless of whether the eventual outcome
is correct.
Tron is a REST API (TronGrid), not a Substrate runtime, so it never goes
through BalancesIntegrationTest's chainRegistry-based mechanism at all -
it had zero CI coverage. This exercises the exact TronGridApi the
production app uses for balance reads, via a standalone Retrofit client
(TronGridApi isn't exposed through a public feature API for tests to
reach through the DI graph).
Test account is the mainnet Founder's Tron address, verified live via
TronGrid's public API on 2026-07-09: 3,925.23 TRX native + 625,213.92
USDT-TRC20 - substantial real balances, not a guessed or empty account.
mapChainLocalToChain() runs as a single mapList{} transform over the entire
chain list. If it throws for even one malformed row (e.g. a JSON parse
failure on the chain's `additional` blob), the whole transform throws,
killing the Eagerly-shared currentChains/chainsById flows for every chain -
permanently, since Eagerly sharing never restarts once its upstream dies.
This is the same class of bug already fixed for registerChain/unregisterChain
in the diff-processing step below, but one level upstream of it: a mapping
failure here never even reaches that per-chain isolation, since it happens
before the diff is computed at all.
Device logs on a fresh install showed exactly this shape: 8 chains (including
Pezkuwi, Pezkuwi Asset Hub, Polkadot Asset Hub) successfully completed
"Constructed runtime" within the first ~2.5 seconds, then all activity for
every remaining chain stopped completely for the rest of the session - no
crash, no battery-saver kill (confirmed off), the process simply went silent,
consistent with the shared flow dying mid-registration and never recovering.
ChainSyncService.syncUp() and EvmAssetsSyncService.syncEVMAssets() both
diff a freshly-fetched remote list against everything currently stored
locally, then apply that diff unconditionally. If the remote fetch
succeeds but returns a suspiciously small or empty list (CDN hiccup,
regional network filtering, a bad publish upstream) - rather than
throwing, which retryUntilDone would catch and retry - the diff would
delete most or all of the user's chains/assets from the local DB. This
is active data loss, not a sync failure, and it self-heals on the next
good sync if we just skip applying a suspicious one instead.
Directly relevant: a user's live production install (unrelated to any
in-flight branch work) was observed with a completely empty networks
and token list after previously working fine, matching this exact
failure mode.
StatemineAssetBalance and NativeAssetBalance's startSyncingBalance()
wrapped setup and subscription failures in runCatching{}/catch{} that
returned emptyFlow()/NoCause instead of propagating. This meant
FullSyncPaymentUpdater's retryWhen (added for the Tron fix) never
actually engaged for these asset types - a transient failure silently
and permanently stopped sync for that asset (HEZ, PEZ, USDT, DOT, ...)
with only a logcat line as evidence. Let the exceptions propagate so
the existing retry boundary can do its job.
ChainRegistry.currentChains ran registerChain()/unregisterChain() per
chain, unguarded, inside a plain (non-Supervisor) CoroutineScope and an
Eagerly-shared flow that never restarts once it dies. One bad chain row
could permanently kill sync for every chain. Isolated each chain's
register/unregister in its own runCatching, and switched the scope to
a SupervisorJob as defense in depth.
ChainUpdaterGroupUpdateSystem.runUpdaters() called getRuntime() and
built per-chain updater flows with no error boundary; callers merge
several chains' results together, so one chain's failure (e.g. a
disabled chain throwing DisabledChainException) could kill
governance/staking/crowdloan sync for every other chain in the group.
Wrapped the body in flow{} + catch to isolate failures per chain.
Generalizes today's Tron balance-polling fix - the same silent-death
bug exists in the shared Substrate sync path used by every chain
(Interlay, Kintsugi, Karura, Acala, Hydration, Polkadex, and any other
orml/statemine asset), not just Tron-specific code.
FullSyncPaymentUpdater.syncAsset() previously used runCatching around
the one-time startSyncingBalance() call and a separate .catch on the
resulting flow - either path failing (a transient WSS hiccup during
setup, an orml currencyId-decode edge case, a node not supporting a
specific RPC method during round-robin, a dropped connection later)
permanently ended that asset's sync for the Updater's lifetime: no
retry, only a Log.e line, and mapNotNull silently dropped the null
result. Since no balance update flow ever emits, AssetCache never
creates a DB row for that asset, and every UI surface (main list,
multi-chain picker) reads via an INNER JOIN that requires that row -
so the asset stays invisible with zero user-visible error until app
restart, which has the same odds of failing again.
Live-tested: Interlay/Kintsugi's node URLs are all reachable (manual
WSS handshake test, 101 Switching Protocols), and none of these chains
are blacklisted - so this wasn't a connectivity or config issue, it
was this retry gap.
Wraps both the initial subscription call and the ongoing flow in one
flow{} builder with retryWhen (30s fixed interval, matching Tron's
polling interval) instead of runCatching + .catch, so a transient
failure at either point just gets retried instead of permanently
killing sync for that asset.
Real user on a fresh install reported TRX and USDT-TRC20 never
appearing anywhere in the Tokens list (not "zero balance", genuinely
absent - including from the multi-chain USDT chain picker). Traced
the full pipeline; every layer up to and including
TypeBasedAssetSourceRegistry, ChainRegistry, address derivation, and
TronGridApi wiring was already correct.
Root cause: pollingBalanceFlow() had no retry around fetch() - any
single transient failure (DNS hiccup, timeout, momentary connectivity
loss during app cold start) threw out of the `while(true)` loop,
killing the flow permanently. The collector
(FullSyncPaymentUpdater.syncAsset()) only logs and gives up on
failure, it doesn't resubscribe. Since the asset's first balance
write never happens, AssetCache never inserts a DB row for it, and
every UI surface (main list, multi-chain picker, search) reads
exclusively via an INNER JOIN that requires that row to exist - so
the asset is invisible everywhere, permanently, with zero
user-visible error.
Swallow fetch() failures and retry on the next interval instead of
letting them escape the loop - one bad poll no longer blackholes the
asset for the rest of the app session.
Also fixes a related but separate gap found while tracing this:
RealSecretsMetaAccount.multiChainEncryptionIn() had no isTronBased
branch (only isEthereumBased), unlike DefaultMetaAccount's
hasAccountIn/accountIdIn which already handle Tron correctly. This
didn't cause the missing-tokens bug, but would have broken "export
account" (JSON key backup) for Tron - fixed by routing Tron through
MultiChainEncryption.Ethereum, matching what
RealTronTransactionService already does for actual transaction
signing (same secp256k1 keypair).
Real user reported Tron stuck showing "Connecting..." forever in the
Networks screen after the ChainRegistry fix (which correctly skips
creating a ChainConnection for Tron, since TronGrid is a plain REST
API, not WSS). NetworkListAdapterItemFactory.getConnectingState()
rendered every non-Connected state (including the Disconnected
default a missing connection pool entry falls back to) as
"Connecting" with an indefinite shimmer - there was no way to
distinguish "never had a connection to begin with" from "still
negotiating one".
Tron doesn't have a meaningful WS connection state at all (its actual
balance/transfer operations poll TronGridApi directly, confirmed
independent of ChainConnection), so there's nothing correct to show
here - treat it the same as the already-existing isDisabled early
return (no status badge) rather than defaulting into the generic
"still connecting" UI.
Card now opens in a slim single-line pill showing only Trust Score.
Tapping it expands to the full card (citizen status, world Kurdish
count, referral actions); a chevron in the expanded header collapses
it back. Expand state persists across scroll/recycling within the
session but resets to collapsed on a fresh app launch.
main now has the fix for a serious bug that also affects this branch:
ChainRegistry unconditionally attempted a WSS connection for every
chain including Tron (whose node is a plain REST API, not WSS),
hanging indefinitely and blocking all subsequent chain registration
in the sequential registration loop - a real freeze risk for any user
with the Tron chain enabled, found while diagnosing balances_test.yml.
Also pulls in the various CI-only fixes from PR #11 (unrelated to
app behavior).
* fix: use arm64-v8a emulator image on macos-14 runners
The "Run balances tests" job has been failing on every scheduled run
for weeks: the emulator requested an x86 system image while
macos-14 GitHub-hosted runners are Apple Silicon (aarch64) - QEMU2
refuses to boot a mismatched-architecture image ("Avd's CPU
Architecture 'x86' is not supported by the QEMU2 emulator on
aarch64 host"), so every adb command against the never-booted
emulator failed with "device not found" for the full 10-minute
boot timeout on every run.
* fix(ci): run balances-test emulator on ubuntu-latest with KVM instead of macos-14
macos-14 (Apple Silicon) GitHub-hosted runners don't expose nested
virtualization to guests, so the Android emulator's HVF backend fails
with HV_UNSUPPORTED and the job times out waiting for boot - this
affects both x86 and arm64-v8a system images, so the arch: arm64-v8a
change alone wasn't sufficient. macos-13 (which previously supported
this via HAXM) was retired by GitHub.
Linux hosted runners support KVM-accelerated emulators once the kvm
device's udev permissions are relaxed, which is the documented setup
for reactivecircus/android-emulator-runner and also faster/cheaper
than macOS runners.
* fix(ci): build instrumentialTest androidTest APK to match AllureAndroidJUnitRunner
run_balances_test.sh has always targeted
io.novafoundation.nova.debug.test/io.qameta.allure.android.runners.AllureAndroidJUnitRunner,
but android_build.yml's "Build debug tests" step built the plain
debug variant's androidTest APK, whose manifest declares the module
default runner (androidx.test.runner.AndroidJUnitRunner, from
allmodules.gradle) - AllureAndroidJUnitRunner is only wired via
testInstrumentationRunner on the dedicated instrumentialTest build
type (app/build.gradle), which was apparently created for exactly
this purpose but never actually wired into CI. Result:
INSTRUMENTATION_FAILED as soon as the emulator boot issue was fixed
and this step was reached for the first time.
instrumentialTest initWith buildTypes.debug (same applicationIdSuffix
'.debug', same default debug signing), so it installs under the same
package next to the already-built debug app APK - only the
androidTest APK's build type needs to change, not the main app build.
Only balances_test.yml sets build-debug-tests: true, so this doesn't
affect any other android_build.yml caller (pull_request.yml has it
explicitly disabled with its own separate pre-existing TODO).
* test: temporarily point android_build.yml ref at this branch for verification
TEMPORARY - reusable workflow calls (uses: ...@ref) pin to that ref's
copy regardless of the caller's branch, so android_build.yml's fix
never actually ran in the previous verification attempts even though
it was committed to this branch. Must revert to @main before merging
this PR, once android_build.yml's changes are on main.
* fix(ci): declare AllureAndroidJUnitRunner on debug build type, not instrumentialTest
AGP only ever compiles one androidTest APK per module, tied to
android.testBuildType (default "debug", never overridden in this
project) - so a testInstrumentationRunner override on any other build
type, including instrumentialTest, is unreachable regardless of which
gradle task or main-app variant is used. That's why the previous
attempt (assembleInstrumentialTestAndroidTest) failed outright: no
such task exists, since androidTest is only ever generated for
testBuildType.
Reverts the previous commit's task/path changes back to
assembleDebugAndroidTest / app/androidTest/debug/..., and instead
moves the testInstrumentationRunner override onto the debug build
type itself, which is what androidTest actually compiles against and
what run_balances_test.sh has always targeted. Also drops the dead
override from instrumentialTest now that it's confirmed non-functional
there.
* debug(ci): dump pm list instrumentation before running balances test
Diagnostic only - the previous fix (testInstrumentationRunner moved
to the debug build type's defaultConfig) still hit the identical
INSTRUMENTATION_FAILED for AllureAndroidJUnitRunner even though both
APKs now install successfully with the corrected paths. Need to see
what PackageManager actually registered for the installed test APK
rather than keep guessing from Gradle DSL evaluation-order theory -
allmodules.gradle's subprojects{} block also sets
testInstrumentationRunner on every module including app, and it's not
yet confirmed which assignment wins.
* fix(ci): use current io.pezkuwichain.wallet package instead of stale upstream name
Diagnostic (pm list instrumentation) confirmed the real, final root
cause: AllureAndroidJUnitRunner IS correctly registered on-device
after the previous fix, but under the app's real applicationId
(io.pezkuwichain.wallet, set in build.gradle since the Pezkuwi rebrand)
- not the pre-rebrand upstream Nova Wallet package
(io.novafoundation.nova) these scripts still hardcoded. Every previous
INSTRUMENTATION_FAILED was simply am instrument targeting a package
that was never installed.
Also fixes the identical stale reference in run_instrumental_tests.sh
(currently unused by any workflow, but has the same bug).
The BalancesIntegrationTest class's own Kotlin package
(io.novafoundation.nova.balances) is untouched - that's the test
class's real source package on disk, unrelated to the app's
applicationId, and was never part of the bug.
* fix(ci): bound run-tests to 25min and unbuffer python output
Latest verification run hung for 1h15m+ inside am instrument with
zero visible progress - had to be cancelled manually. Post-mortem:
Python's stdout is fully buffered (not line-buffered) when piped
under GitHub Actions' `run:`, so the script's own 5s heartbeat prints
(explicitly there to avoid CI's inactivity-based cancellation) never
actually reached the log until the buffer filled near the very end,
making the run look identical to a genuine hang for its entire
duration with no way to tell what BalancesIntegrationTest was doing.
- timeout-minutes: 25 on run-tests bounds any future hang to a fixed,
reasonable ceiling instead of requiring a manual cancel after an
hour+.
- python -u unbuffers stdout so the next run's logs show real-time
heartbeat/output, giving actual visibility into where a hang (or
slow chain RPC) occurs instead of a silent multi-hour gap.
Root cause of the hang itself is still open - this only makes it
diagnosable and bounds its cost.
* fix: skip WSS ChainConnection setup for Tron-based chains
Real root cause of the 1h15m+ hang in the previous verification run
(had to be cancelled manually): ChainRegistry registers all chains
sequentially (diff.newOrUpdated.forEach { registerChain(it) }), and
registerConnection() unconditionally called
connectionPool.setupConnection(chain), which creates a ChainConnection
backed by a WSS-only SocketService - for every chain, with no check
for chain type. Tron's node (https://api.trongrid.io) is a plain REST
API with no WebSocket support, so this connection attempt just hangs
forever with no timeout, and since registration is sequential and
Pezkuwi chains (including Tron) are ordered first in the merged chain
list, every chain after it in the list never finishes registering
either - including all the third-party chains BalancesIntegrationTest
actually exercises.
This isn't just a test problem: any real user with the Tron chain
enabled would hit the same registry-wide freeze during chain sync.
Tron balance/transfer operations already go through their own
TronGridApi REST client (built in the Phase 1/2 Tron work),
independent of ConnectionPool/ChainConnection - confirmed nothing else
reads chainRegistry.getConnection() for a Tron chain id - so skipping
ChainConnection setup entirely for isTronBased chains is safe.
* debug(ci): stream live logcat during test run
Diagnostic only. Previous fix (skip WSS ChainConnection for
isTronBased chains) did NOT resolve the hang - identical symptom
(silent for the full 25min timeout, just heartbeats, nothing from the
actual test process). Need real device-side visibility into what's
actually happening (network calls, coroutine timeouts, ANRs) instead
of guessing blind from am instrument's own stdout/stderr, which stays
completely silent until the process exits.
* debug(ci): SIGQUIT thread dump 3min into a hung run, bump logcat to debug level
Previous diagnostic (live logcat at info level) showed the app staying
genuinely alive (GC cycles, system chatter) but produced zero signal
from the test itself after the initial successful chains.json fetch -
no WSS/DB/coroutine activity visible, likely because relevant logging
is below info level. Static code reading (ChainConnection.setup(),
ChainSyncService.syncUp(), ChainFetcher) hasn't turned up an obvious
blocking call, and this exact hang predates this session by at least
a month (zero successes in the last 100 scheduled runs going back to
2026-06-09) - so it's a real, previously-latent bug in the app/test
itself, never previously reached because every earlier run failed at
an earlier CI-infra stage.
kill -3 on the app process forces ART to dump every thread's Java
stack trace to logcat (the standard ANR-diagnosis technique) - this
should show exactly which coroutine/thread is parked and where,
instead of continuing to guess from log filtering.
* fix(ci): bump run-tests timeout to 90min, drop now-unneeded SIGQUIT diagnostic
With the chain blacklist fixed, the previous run genuinely started
executing the 160-case parameterized test suite (visible TestRunner
started/finished events for real chains) instead of hanging silently
- first time this has happened in this entire investigation.
It still hit the 25min ceiling: each test case has an explicit
withTimeout(80.seconds), and several chains that aren't fully dead
(so not blacklisted) but are slow/unresponsive burn the full 80s
before failing. With 160 total test cases, the worst-case aggregate
easily exceeds 25 minutes even with zero actual hangs - so the
timeout needs to be realistic for the test's own design, not just
long enough to catch a true infinite hang. 90min matches
android_build.yml's existing precedent for this project's CI jobs.
The SIGQUIT-after-3-minutes diagnostic added earlier is now
counterproductive noise: any healthy run legitimately takes far
longer than 3 minutes to get through 160 cases, so it would fire on
every run rather than only genuine hangs. Removed now that its actual
purpose (finding the real hang) is done - the blacklist was the
answer, not something a thread dump would have caught anyway (dead
sockets, not deadlocked application code).
* chore: revert temporary android_build.yml ref pin back to @main
Was pointed at this branch only to verify android_build.yml's own
fixes actually took effect during iteration (reusable workflow calls
resolve against the ref they're pinned to, not the caller's branch).
android_build.yml is now byte-identical to main (the wrong task-name
attempt was fully reverted in an earlier commit), so this is a no-op
functionally - just restoring the correct long-term reference before
merge.
pull_request-triggered runs of this workflow have stopped firing
entirely for at least one recent PR (no check-suite created at all
for the GitHub Actions app, while two other installed apps sit stuck
in "queued") - root cause is outside what's fixable via the repo's
own Actions config/API access. This adds a manual workflow_dispatch
path (branch input) so a build+test run can still be triggered
directly against any branch while that's being sorted out.
Doc comments describing "/wallet/*" endpoints were parsed as opening
a nested block comment (Kotlin block comments nest, unlike Java/C),
leaving the outer KDoc unterminated - not just a lint nit, ktlint
flagged these as invalid Kotlin files. Reworded to drop the trailing
"*". Also wraps two over-long function signatures/calls.
Reuses the existing generic fee-sufficiency validation
(sufficientTransferableBalanceToPayOriginFee) and send flow
unchanged. Transaction construction goes through TronGrid's own
createtransaction/triggersmartcontract endpoints (verified live
against Shasta testnet) rather than a hand-rolled protobuf
implementation; signing reuses the existing secp256k1 path
(SignerPayloadRaw.skipMessageHashing) already used for Ethereum,
no new crypto library. Also fixes a Phase 1 gap: Chain.isValidAddress()
had no isTronBased branch, so every Tron send would have failed
address validation.
No Energy staking/delegation/rental UI - only Tron's default
protocol behavior (burn TRX to cover Bandwidth/Energy shortfall).
* Add read-only Tron (TRC20) chain family support
Adds Tron as a third chain family alongside Substrate and EVM:
address derivation (BIP44 coin type 195, secp256k1 reused from the
existing Ethereum path, Base58Check encoding), TronGrid-backed TRX
and TRC20 USDT balance display, and a Room migration for the new
per-account Tron key/address columns. Read-only only — no send,
signing, or broadcast support yet.
* fix: exhaustive when branches for Trc20/TronNative asset types
ChainExt.kt's onChainAssetId and Common.kt's existentialDepositError
both switch exhaustively over Chain.Asset.Type and didn't have cases
for the new Trc20/TronNative variants, breaking compilation. Trc20
mirrors EvmErc20 (contract address as the on-chain id, dust burns
rather than transfers on removal); TronNative mirrors EvmNative.
* redesign(dashboard): brand-book restyle of Pezkuwi dashboard card
Match the brand book / handoff screenshot:
- bg_pezkuwi_dashboard: blue/indigo gradient -> frosted dark-navy surface
(#1C1F2E) with a 1px periwinkle hairline, 20dp corners.
- item_pezkuwi_dashboard: replace hardcoded non-brand colors —
trust value amber #FFD54F -> zer #FDB813; welati count -> positive #2FC864;
Approve/start buttons Material-green -> kesk #009639; Sign red -> sor #E2231A;
Share button yellow #FDD835 -> frosted-navy secondary #2A2F45 (white text);
blue-grey text -> brand white tokens. Pill-er 12dp button corners.
* redesign(actions): circular action buttons (primary green Send + dark rest)
Match the brand-book screenshot: the balance action row (Send/Receive/Swap/
Buy/Gift) becomes circular icon buttons with a label below — Send on a kesk
green circle, the rest on frosted-dark circles. IDs preserved (used only for
click + isEnabled), so AssetsTotalBalanceView keeps working.
* redesign(dashboard): pro-level Pezkuwi card layout
Rework the card to match the brand-book screenshot: header row with a small
Newroz-flame icon + title/roles on the left and the citizen count on the right;
trust score row; full-height (48dp) pill-er (14dp) action buttons — Approve
(kesk, bold), Sign (sor), Share (frosted-dark with hairline). Adds the small
ic_nevroz_flame icon.
* release: bump versionName to 1.1.1 (dashboard & action-row redesign)
Add an 'inProgress' status + a user_fraction input so production releases can
roll out gradually (e.g. 0.2 = 20%) instead of only 100% (completed).
userFraction is passed only when status=inProgress; ignored otherwise.
* ci: wire develop_key.jks into PR build (fix validateSigningDevelop)
The PR build runs assembleDevelop (signingConfigs.dev = develop_key.jks) but the
reusable workflow only decoded github/market keystores, so validateSigningDevelop
always failed. Add a develop-keystore decode step (reusing the existing
BASE64_DEV_KEYSTORE_FILE secret + CI_KEYSTORE_* passwords) and pass
keystore-file-name: develop_key.jks from pull_request.yml.
[temp] pin reusable workflow to the branch to validate before merge; reverted to
@main in the next commit.
* ci: revert reusable-workflow pin back to @main
Fix validated green on PR #4 (test/Build app and test passed, develop signing
works). Restore @main pin for the final merged state.
* redesign(colors): purge forbidden magenta/purple from palette
Brand book permits only the Kurdistan palette (kesk/sor/zer/fire) + navy.
Replace the four Nova-legacy violations in common colors.xml (names preserved,
so no references break):
- crowdloan_banner_gradient_start #BD387F -> #009639 (kesk)
- networks_banner_gradient_end #661D78 -> #017A2F (kesk-700)
- chip_on_card_background #443679 -> #3D999EC7 (frosted periwinkle)
- button_wallet_connect_background #353D67 -> #1F2A4D (neutral navy)
Repo-wide grep confirmed no other hardcoded magenta/purple literals or
purple/pink-named resources remain.
* redesign(type): adopt brand fonts (Space Grotesk / Plus Jakarta Sans / JetBrains Mono)
Brand book typography. Add OFL static fonts under common/res/font and repoint
the theme font attrs (consumed app-wide via ?attr/font* in styles.xml):
- fontRegular -> Plus Jakarta Sans Regular (body/UI)
- fontSemiBold -> Plus Jakarta Sans SemiBold
- fontBold/ExtraBold -> Space Grotesk Bold (display: balances, titles)
- fontExtraLight -> Plus Jakarta Sans Light
- Monospace text appearance (addresses/hashes) -> JetBrains Mono
Static TTFs chosen (minSdk 24 < variable-font API 26). All five verified to
cover Turkish + Kurdish Kurmancî glyphs (ş ğ ı İ ê î û ç …).
* redesign(splash): replace map+wordmark logo with Newroz flame brand mark
The first-launch splash logo (ic_loading_screen_logo) showed a Kurdistan map
with a baked-in wordmark. Per the brand book the mark is the Newroz flame.
Rasterized assets/nevroz-fire-flame.svg into the existing 6 density slots
(same pixel dimensions = drop-in, no bg_splash.xml change, transparent WebP so
it composites cleanly over the splash background).
* redesign(onboarding): welcome hero -> Global United States of Pezkuwi
Replace the legacy onboarding hero (ic_create_wallet_background, whose six
density PNGs had inconsistent broken dimensions) with the brand 'Global United
States of Pezkuwi' infographic. Consolidate to a single high-res WebP in
drawable-nodpi (1408x768, 204K vs 1.75MB PNG) and switch the welcome ImageView
scaleType centerCrop -> fitCenter so it shows full and uncropped above the
CTA buttons, per the brand book.
* redesign(assets): purge magenta/purple from raster illustrations
colors.xml only covers named colors; these raster drawables still carried
forbidden magenta/purple/pink pixels. Selectively rotate the 255-350 deg hue
band (purple-magenta-pink) toward kesk green/teal across all densities,
preserving gradients, shapes and the on-brand blues:
- ic_pink_siri, ic_networks_banner_image, ic_no_added_networks,
ic_import_option_hardware, tinder_gov (6 densities each).
Third-party ic_powered_by_oak (OAK Network trademark) left untouched.
crowdloan_banner_image is being handled separately.
* redesign(assets): de-pink crowdloan banner across all densities
The crowdloan banner's magenta sphere was recolored to brand sor red (xxxhdpi).
Propagate that fix to the other five densities by downscaling the corrected
xxxhdpi master (LANCZOS), so every device shows the same brand-clean banner.
Pink-pixel scan now 0% on all six variants.
* chore: remove pre-production debug code (release-blocker)
CHANGELOG_PEZKUWI listed debug code to strip before production; two items were
still live:
- FeeLoaderV2Provider: the fee-retry dialog showed a raw "DEBUG: <err> | Runtime:
<diagnostics>" string to users -> reverted to the localized resource string and
dropped the diagnostics lookup.
- RuntimeFactory: removed the companion lastDiagnostics field and its assignment
(plus the now-dangling diagnostic vars), and the matching log line in the
PezkuwiLiveTransferTest androidTest.
Items #3-#6 were already clean. Repo now has no 'DEBUG:' literals or
lastDiagnostics references. CHANGELOG marked cleaned.
* docs: add BRAND.md — enforceable wallet brand rules
Kurdistan-only palette (no pink/magenta/purple), brand fonts, Newroz flame mark,
a PR brand checklist with a raster pink-scan snippet, the third-party-logo
exception, and the Apache-2.0 attribution rule (keep LICENSE/NOTICE). Points to
the canonical brand book at wiki.pezkuwichain.io/brand.
* release: bump versionName to 1.1.0 (brand-book UI redesign)
Live Play version is 1.0.4; minor bump for the UI redesign. versionCode stays
CI-managed (CI_BUILD_ID). Add the v1.1.0 entry to CHANGELOG_PEZKUWI.
The scheduled 'Run balances tests' workflow has been failing for weeks
with 'No space left on device' — assembleDebug + assembleDebugAndroidTest
for every module exceeds the ~14 GB free on GitHub-hosted runners.
Add an opt-in free-disk-space input to the reusable build workflow that
removes preinstalled toolchains we never use (dotnet, ghc, CodeQL, swift,
boost, etc., ~30+ GB), and enable it for the balances test build. Other
callers of the reusable workflow are unaffected.
Branch.io was inherited from Nova Wallet fork but never configured.
Removed SDK integration, manifest intent filters, Gradle dependencies,
Dagger DI modules, and string resources. Deep linking now routes
exclusively through app.pezkuwichain.io with verified assetlinks.json.
Set jniLibs.useLegacyPackaging = false to ensure .so files are stored
uncompressed and 16KB-aligned within the AAB, as required by Google Play
for Android 15+ compatibility.
- Rebuild renderscript-toolkit.aar from source with NDK 27 and 16KB alignment
- Rebuild libsr25519java.so from substrate-sdk source with 16KB alignment
- Realign libsqlcipher.so ELF segments to 16KB (preserves JNI API compatibility)
- Add jniLibs overrides for sr25519java and sqlcipher to replace AAR versions
- All native .so files now have 0x4000 (16KB) LOAD segment alignment
- Add 16KB page size alignment for native Rust libraries (Google Play requirement)
- Make Play Store workflow parametric (track/status as inputs)
- Update release notes for all languages (en, tr, ku)
- Add .gitignore entries for node_modules and version.properties
CI_BUILD_ID was using github.run_number which is per-workflow, causing
different workflows to produce different versionCodes (144 vs 9).
Now computed from git commit count + offset, consistent across all workflows.
Also added bundle task to local auto-increment.
Switch from submitExtrinsic (fire-and-forget) to submitExtrinsicAndAwaitExecution
which waits for block inclusion and checks ExtrinsicFailed events. Previously the
app showed success even when pallet-level errors occurred (e.g. ReferrerNotCitizen,
AlreadyExists). Now dispatch errors are properly surfaced to the user.
Added (KYC) suffix to Apply&Actions button label in all languages.
Added ~29 missing citizenship and dashboard string keys to 12 languages
that were missing them (ru, es, pt, fr, it, ko, ja, zh-rCN, in, vi, hu, pl).
Added 3 missing dashboard keys to TR and KU.
Add dashboardRefreshSignal to trigger re-fetch on swipe-to-refresh,
onResume, citizenship bottom sheet dismiss, and score tracking.
Rename button label to "Apply & Actions (KYC)" for clarity.
Query StakingScore::StakingStartBlock from People Chain to check
tracking status. Show "Start Tracking" button for approved citizens
who haven't opted in yet. Submits start_score_tracking() extrinsic
with loading state and error handling to prevent duplicate calls.