The structural separation (dedicated screen, no amount input) was already
in place, but the screen itself was much sparser than Swap's own execution
UI - just a label and a timer, no "Do not close the app!" warning and no
summary of what's actually being bridged. Added both, reusing components
already proven on Bridge's own input screen (BridgeAssetInputView in
non-editable mode, same as the main screen's fromCard/toCard) rather than
reaching into feature-swap-impl's Swap-specific components.
Also added the signatories' contact channel (t.me/pezkuwidestek) to the
existing "still processing" message, and made explicit that funds are safe
and on-chain even when auto-pay doesn't complete within the wait window -
this is the message shown when a real transfer lands in manual review, not
just the pre-submit consent gate.
Symmetric across both bridge directions, checked in priority order before
any funds move:
1. Real reserve exceeded (withdrawal only) - the multisig doesn't hold
enough real USDT on Polkadot Asset Hub. No signature can fix this, so
this stays a hard block (existing bridge_wusdt_to_usdt_blocked message),
never offered as "proceed anyway".
2. Automation-key approval exceeded (either direction) - funds exist, the
automation key just isn't currently approved to auto-pay that much.
Resolvable by 3-of-5 signing, so this is now an explicit opt-in: a
warning banner names the ~2h typical review window and the signatories'
contact channel (t.me/pezkuwidestek), gated behind a WarningCheckBox the
user must tick before Swap re-enables. Previously there was no signal at
all here - a large amount would just silently queue for manual review
after debiting the user, with no way to know that going in.
Both getWusdtRemainingAllowance/getPolkadotUsdtRemainingAllowance query the
real on-chain Assets.Approvals amount per leg - not a guess. Confirmed
on-chain (2026-07-16) the Polkadot leg has never had an approval granted at
all, so every wUSDT->USDT withdrawal currently hits the consent gate
regardless of amount, symmetric with what happens once a signatory grants
one (mirrors the existing wUSDT-side renewal flow exactly, including the
same 40,000/200,000 20%-threshold pattern) - added a second sign button so
a signatory can actually grant it from this screen.
swapClicked()'s confirmation gate now also enforces the consent requirement
server-side (well, client-domain-side) rather than only via the button's
enabled look, consistent with how it already re-checks balance/reserve.
The single-screen design (amount input + live balance validation + submit
+ destination-wait, all in one ViewModel) was structurally fragile: the
live origin-balance observer and the amount-vs-balance validation ran in
the same reactive scope as execution, so a real successful transfer could
flash a false "insufficient balance" error the instant the balance dropped
post-submission. Patching that race (clearing the field, then an isExecuting
flag) kept fixing symptoms without addressing why the class of bug existed:
the app's own Swap flow avoids it entirely by keeping amount entry and
execution as separate screens/ViewModels, so validation code for the input
screen simply doesn't exist on the execution screen.
BridgeExecutionViewModel/Fragment now own the actual submit + destination-
wait, reached by navigating with a BridgeExecutionPayload once
BridgeViewModel.swapClicked() re-validates amount against the latest known
balance/reserve - a real confirmation gate, not just a UI button-disabled
look the domain layer never itself checked. A single BridgeExecutionState
sealed class (SubmittingOrigin/OriginFailed/WaitingForDestination/
DestinationConfirmed/DestinationPendingReview) replaces what used to be six
independently-updated LiveData flags that had to be kept in sync by hand.
Also fixed two real bugs found via live device testing with real funds:
- BridgeMultisigConstants.POLKADOT_USDT_ASSET_ID was 1 (the wallet's own
chains.json ordinal for USDT on Polkadot Asset Hub) instead of 1984 (the
real on-chain Assets pallet id, confirmed directly against the chain).
getPolkadotUsdtReserve() queried asset 1, which the multisig has never
held anything at, so the wUSDT->USDT reserve check always reported 0
USDT available regardless of the multisig's real (and growing) holdings.
- The deposit-wait label's text was static XML, never updated on the
success path - a genuinely completed bridge (destination balance
confirmed, checkmark shown) still read "Waiting for confirmation..."
forever, indistinguishable from actually being stuck. The new execution
screen's label is driven by BridgeExecutionState instead.
Tapping Swap on the Bridge screen previously gave zero feedback afterward -
the same countdown/success UX already used for swap execution didn't exist
here, so a user had no way to tell whether anything was happening. Moved
ExecutionTimerView (+ its layout) from feature-swap-impl to common, since it
was already fully self-contained (no swap-specific coupling, all its
resources already lived in common) - both features now share the one
component instead of duplicating it.
Wired into BridgeViewModel: after a swap is submitted, actively observe the
destination balance (walletInteractor.assetFlow) for a real increase within
a bounded window, showing the countdown while watching and Success only on
a confirmed balance delta - never a cosmetic timer that "completes"
regardless of whether funds arrived. If the window elapses without
confirmation, it says so plainly (may need manual 3-of-5 review) rather than
showing a false success or a false error.
BRIDGE_ADDRESS_GENERIC was still the retired legacy single-key bot's
address (5C5CW7xD...), not the real 3-of-5 multisig custody
(BridgeMultisigConstants.MULTISIG_ADDRESS). Confirmed live: a real user
swap sent there landed at the dead address and was never detected by
usdt-bridge's listener (which only watches the multisig account). Funds
were recovered separately using the still-intact legacy seed; this fixes
the actual destination so future swaps land where the listener watches.
Also added the user-confirmed Kurmancî translation for
bridge_wusdt_to_usdt_blocked (values-ku), replacing the now-stale text left
untouched in the previous commit pending native review.
BridgeViewModel's wUSDT->USDT gate called http://217.77.6.126:3030/status -
the legacy single-key bridge bot's endpoint. That service was stopped this
same session (see res/validators-tiki.md), so the check always threw,
always caught, and always reported "inactive" - the red warning reflected a
dead dependency, not real reserve state.
Replaced with a direct on-chain read of the multisig's actual USDT balance
on Polkadot Asset Hub (BridgeMultisigInteractor.getPolkadotUsdtReserve, via
a new Assets.Account storage query mirroring the existing Approvals one).
The gate is now per-request: a specific withdrawal is blocked only if it
exceeds the real reserve, not on a global on/off flag requiring full 1:1
parity between total circulating supply and total reserve - matching how
the Rust listener's own reserve-shortfall check already works. Re-checked
on every amount change, not just once per screen load.
bridge_wusdt_to_usdt_blocked now takes the available reserve amount as a
parameter; updated the English and Turkish strings. Left values-ku
untouched deliberately - not fabricating Kurdish translation text without
native review.
The DOT<->HEZ bridge backend (the legacy single-key bot) is being
permanently decommissioned - it had zero multisig protection and no
liquidity gate at all on that leg (unconditionally "active" regardless
of pool state, the single least-safe path in the whole bridge). Once
USDT bridging is solid, HEZ is reachable indirectly (bridge USDT, swap
for HEZ within Pezkuwi's own ecosystem) without a dedicated native
bridge that would need its own from-scratch security work (native
currency has no Assets.approve_transfer-style delegation to reuse).
- BridgePair/BridgeDirection collapsed to USDT-only (USDT_TO_WUSDT/
WUSDT_TO_USDT) - kept the pair/pairOptions/picker structure itself
since it costs nothing and is the natural seam for a future pair,
rather than hardcoding a single value inline.
- Removed the now-dead DOT/HEZ exchange-rate fetching (CoinGecko
polling, fallback rate) and liquidity-gate logic entirely, not just
the unreachable enum branches.
- Removed the separate "Bridge DOT ↔ HEZ" entry from the asset
selector bottom sheet (BuySellSelectorMixin) - both it and "Bridge
USDT" opened the exact same screen with no preset, so keeping a
DOT-HEZ-labeled entry pointing at a USDT-only screen would have been
actively misleading, not just redundant.
- bridge_title was the generic toolbar/picker-sheet title but hardcoded
"DOT ↔ HEZ Bridge" regardless of pair - fixed to "USDT Bridge".
- Removed now-unreferenced strings (wallet_asset_bridge,
bridge_pair_dot_hez, bridge_rate_format(_reverse),
bridge_hez_to_dot_note/warning/blocked) from the base strings.xml.
Confirmed via research: feature-bridge-api/feature-bridge-impl modules
exist but are unrelated orphaned scaffolding, not depended on by :app,
zero code overlap with this screen - correctly out of scope here.
Any of the USDT bridge multisig's 5 known signatories now sees a
status button on the Bridge screen: green when the automation key's
spending allowance is healthy, red with a Sign action once it drops
below threshold. Signing submits the renewal's as_multi approval
directly from the wallet, mirroring the same on-chain action already
available via pwap-web and pezbridge-sign.pex.mom - a third
independent channel for the same operation, none of which is a
single point of failure.
Replaces the segmented pair/direction buttons and plain-text amount
sections with token-icon cards (BridgeAssetInputView), a one-tap flip
button, and a bottom-sheet pair picker (BridgePairListBottomSheet).
BridgeViewModel gains only additive presentation data (fromCard/toCard/
pairOptions, derived from the existing pair/direction state and the
same chainId/assetId mapping swapClicked() already used) - all bridge
business logic (rate, fee, minimum, liquidity warnings, submission)
is unchanged.
Confirmed live via logcat: tapping "send" to a P2SH address force-closed
the app - FATAL EXCEPTION: IllegalArgumentException: Bech32 string is
mixed case, thrown from Chain.accountIdOf() -> bitcoinAddressToAccountId()
(native-SegWit-only) via the Confirm Send screen's address icon generator,
a completely separate call path from the transaction-building fix already
shipped. Chain.accountIdOf() now falls back to decodeBitcoinDestination()
for P2SH/P2PKH, safe here because this generic accountId is only ever used
for opaque purposes (identicons, presence checks) - the real send path
already bypasses it entirely and builds the scriptPubKey straight from the
typed BitcoinDestination.
CI caught a redeclaration - Base58Check is already a chain-agnostic object
in this same package (TronAddress.kt), used for Tron's own Base58Check
addresses. Bitcoin's legacy addresses are the same shape (1 version byte +
20-byte hash), just a different version byte - reuse it directly.
Confirmed live via logcat: the real "QR can't be decoded" error was
neither a QR format issue (the earlier BIP21 fix, while correct, wasn't
the cause here) nor a scan failure - the raw QR content was a bare P2SH
address (3...), which this wallet's native-SegWit-only address validation
rejected outright. A real exchange (OKX) withdrawal address turned out to
be P2SH-only with no way to request native SegWit instead.
Adds Base58Check decoding and BitcoinDestinationAddress.kt (P2WPKH/P2SH/
P2PKH, each producing the correct scriptPubKey shape - these differ from
each other, not just cosmetically). This wallet's OWN address/derivation
stays native-SegWit-only and untouched (bitcoinAddressToAccountId is
unaffected) - only the SEND path now recognizes wider destination types.
BitcoinTransactionService/RealBitcoinTransactionService now take the raw
recipient address string instead of a pre-resolved AccountId - converting
to AccountId this early would have discarded which script shape the
destination actually needs, silently building a P2WPKH output for a P2SH
recipient (a real fund-misdirection risk, not just a validation gap).
Test vectors: Satoshi's genesis P2PKH address, a well-known P2SH vanity
address, and the real OKX withdrawal address from this session's live QR
scan, all cross-verified against an independent Python implementation
before writing the Kotlin equivalent.
Reverts the temporary QrDiag logging added to find this - no longer needed.
Bitcoin was branched off main independently instead of continuing on top
of the Tron send branch, which caused real divergence: both branches touch
the same shared signing/DI code (multiChainEncryptionFor, getMetaAccountKeypair,
NodeHealthStateTesterFactory, AssetsModule/TypeBasedAssetSourceRegistry,
CloudBackup schema, Fee model), and both need to ship together in one
coordinated release once wallet-util's master is unblocked. Merging now
reconciles that before it gets any harder, and gives one branch/versionCode
history for device testing instead of two that silently diverge.
Conflict resolutions of note:
- SecretStoreV2/SecretsSigner: getKeypair and multiChainEncryptionFor now
recognize Tron AND Bitcoin accounts (isTronBased/isBitcoinBased both
threaded through, checked before the Ethereum fallback).
- AccountDataSourceImpl: both address-backfill migrations now run
sequentially in one coroutine (Tron's fix for a race against the legacy
migration applies equally to Bitcoin's backfill).
- CloudBackup: kept Tron's forward-looking Solana schema reservation
alongside Tron's and Bitcoin's now-real fields.
- ChainRegistryModule/NodeHealthStateTesterFactory: adopted Tron's
self-contained short-lived OkHttpClient (simpler than threading the
shared app-wide client through RuntimeDependencies, which is reverted).
- RealSecretsMetaAccount: extended Tron's chain-account MultiChainEncryption
fix to also cover Bitcoin, for the same underlying reason.
- ChainExt.isValidAddress: Tron's branch independently closed the
pre-existing Tron validation gap; kept alongside Bitcoin's own check.
RealBitcoinTransactionService builds and signs native SegWit transactions
client-side (mempool.space only exposes UTXOs/fee-rate/broadcast, not
construction, unlike TronGrid): greedy largest-first UTXO selection over
confirmed UTXOs, inputs*68 + outputs*31 + 11 vsize heuristic for fees
(matching pezkuwi-exchange's proven approach), RBF-signaled inputs, and a
546-sat dust threshold that folds sub-dust change into the fee rather than
creating an uneconomical output.
Each input gets its own BIP143 sighash, signed via the existing raw-hash
signing primitive (NovaSigner.signRaw with skipMessageHashing) already used
for Ethereum/Tron, then DER-encoded with low-S normalization - no new
signing call path was added, only the DER encoding step.
Also fixes multiChainEncryptionFor/getMetaAccountKeypair to recognize
Bitcoin accounts: previously neither function had any Bitcoin (or Tron)
branch, so signing would have silently used the wrong keypair. Threads a
bitcoin flag through mapMetaAccountSecretsToKeypair/DerivationPath and
AccountSecrets.keypair(chain)/derivationPath(chain) the same way ethereum
already is.
Derives a BIP84 (native SegWit) keypair alongside the existing substrate/
ethereum/tron ones, stores it in MetaAccountSecrets/CloudBackup, persists
bitcoinPublicKey/bitcoinAddress on meta_accounts (DB migration 74->75), and
threads isBitcoinBased through Chain/ChainLocal/mappers/ChainExt/ChainRegistry
the same way isTronBased was wired. Adds BitcoinAddressBackfillMigration,
mirroring TronAddressBackfillMigration, so existing wallets get a Bitcoin
address without re-import.
Input 0's sequence was set to 0xeeffffffL, but BIP143's doc shows "eeffffff"
as the on-wire (little-endian) bytes, not the integer value - the correct
value whose LE encoding is eeffffff is 0xffffffee, not 0xeeffffff. Caught by
CI: 83/84 common module tests passed, only bip143Sighash failed. Isolated by
building a standalone Kotlin+JDK repro outside Gradle/Android (no JDK available
in this environment) - every other intermediate value (hashPrevouts, outpoint,
scriptCode, hashOutputs) matched BIP143 exactly; only hashSequence was wrong,
narrowing it to this one transcription error rather than the implementation.
Phase 1 of native BTC send/receive support (native SegWit/P2WPKH only, bc1q...
addresses - Taproot and legacy/P2SH-SegWit explicitly out of scope). Hand-rolled
since no Bitcoin library exists on this project's classpath (bitcoinj/PSBT/Base58/
Bech32) - same rationale as this session's Tron work needing its own Base58Check.
- Bech32/Bech32m codec (BIP173/BIP350) + segwit address encode/decode
- DER ECDSA signature encoding with BIP62 low-S normalization
- HASH160/P2WPKH scriptPubKey construction and address<->accountId conversion
- BIP143 sighash computation and raw segwit transaction serialization
Every primitive is verified byte-for-byte against official BIP173/BIP350/BIP143
test vectors (fetched directly from github.com/bitcoin/bips at implementation
time) - see each *Test.kt's doc comment for exact vector provenance. No JDK is
available in this environment to run these locally; also cross-verified via an
independent Python transliteration of each function before committing.
SecretsSigner.multiChainEncryptionFor() only recognized substrate accounts, standard
Ethereum accounts, and explicit per-chain override accounts. A Tron account's accountId
matches none of those (same secp256k1 scheme as Ethereum, but a different SLIP-44 derivation
path/keypair), so it fell through to null and crashed on the `!!` - found live testing the
send flow end-to-end, reproduced on every Confirm tap.
Fixing just the null case would have signed Tron transfers with the Ethereum keypair
instead of the Tron one (getMetaAccountKeypair only distinguished ethereum/substrate),
producing an invalid signature. Threaded a proper isTronBased flag through
down to mapMetaAccountSecretsToKeypair so Tron gets its own MetaAccountSecrets.TronKeypair.
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(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.
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.
- 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
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.
Custom scheme pezkuwiwallet:// is not clickable in WhatsApp and
other messaging apps. Use https://t.me/pezkuwichainBot?start=ADDRESS
which is universally clickable and auto-fills the referrer field.
When Alice shares a referral link, it now generates a deep link URL
(pezkuwiwallet://pezkuwi/open/citizenship?referrer=address) that
auto-opens the citizenship form with Alice's address pre-filled as
the referrer, fixing the bug where referrer defaulted to founder.
isPagedExposuresUsed() called storageCache.getEntry() which suspends
forever if the entry doesn't exist. The flag is only written by
ValidatorExposureUpdater (staking detail flow), so the dashboard
would hang indefinitely waiting for it.
Check cache first with isFullKeyInCache() and default to paged
exposures when the flag is absent. Also remove debug log statements.
- Add maxAttempts parameter to retryUntilDone (default unlimited for
backward compat), use 5 retries for staking stats fetch
- Catch fetchStakingStats failure in dashboard update system and
fallback to empty stats instead of hanging forever
- Restore stale scope detection in ComputationalCache so cancelled
aggregate scopes are recreated instead of returning stale entries
- Add CitizenshipBottomSheet with form (name, father, grandfather, mother, tribe, region)
- Submit apply_for_citizenship extrinsic with referrer parameter to People Chain
- Query KYC status from IdentityKyc pallet and show appropriate UI state
- Referrer approval: query Referral + Applications entries, approve pending referrals
- Sign (confirm_citizenship) for applicants after referrer approval
- Share referral link for citizens to invite others
- Dashboard buttons adapt to citizenship status (hide Apply/Sign when approved)
- Balance check (1.1 HEZ) only before new applications, not for sign/approve
- i18n strings for en, tr, ku
Remove telegram_welcome image from dashboard card, add live Welati
citizen count fetched from kurds-counter API endpoint. Shows formatted
count with "Hejmara Kurd Le Cihane" label in green.
- Restore staking module to Nova upstream (fix Polkadot ACTIVE/shimmer)
- Add USDT(DOT) <-> USDT(HEZ) bridge option to Buy/Sell screen
- Dashboard card: add telegram_welcome image, info text, Non-Citizen role
- Add non-citizen info text to all 12 locale files
- Simplify ComputationalCache (remove stale scope recreation)
ValidatorProvider, DirectStakingProperties, and DirectStakingRecommendation
were querying Asset Hub chainId for staking data (validators, exposures,
minStake, maxNominations) but these live on the relay chain. Added
chain.parentId resolution so parachain staking correctly routes to relay.
Also:
- Add VoterBagsList pallet support (Pezkuwi naming)
- Wrap BagListRepository queries in runCatching for binding compat
- Remove debug logging
- StakeSummary and Alerts interactors now query elected exposures from
relay chain (parentId) instead of parachain for correct staking status
- Pending rewards flow catches missing NominationPoolsApi and emits zero
- Update Telegram link to @pezkuwichainBot