* DKS hub: open each app on its own domain
Seven tiles opened app.pezkuwichain.io routes or a front page while the app
has a domain of its own. Each now opens that domain, even where the site is
still a "coming soon" page, so the tile keeps its address when the site
arrives:
Wallet explorer front page -> cuzdan.pex.network
P2P / Buy-Sell app/p2p -> buy-sell.pezkiwi.app
B2B app/bereketli -> bereketli.pezkiwi.app
Vote app/elections -> governance.pezkuwichain.io
Proposals app/citizens/government-> governance.pezkuwichain.io
Identity app/identity -> be-citizen.pezkiwi.app
Forum app/forum -> forum.pezkuwichain.io
B2B went through a sign-in bridge that only helps a user signed in to the
web app, which the wallet's DApp browser never is: it loaded the whole web
app, then forwarded to bereketli.pezkiwi.app anyway.
* Release 1.3.0
Version 1.2.0 -> 1.3.0, with release notes in English, Turkish and
Kurmanci for the Ecosystem Hub, the config that keeps working when
GitHub is blocked, and the splash and deep link fixes.
* feat(network): fetch config from jsDelivr when raw.githubusercontent.com fails
Every config file the app reads -- chains, assets, DApps, XCM, banners, token
icons -- comes from raw.githubusercontent.com with no other source, so when that
host cannot be reached a fresh install has no chains at all.
ConfigMirrorInterceptor retries the same file on cdn.jsdelivr.net and then
fastly.jsdelivr.net when the primary fails to connect or answers 403, 429, 451
or 5xx. A 404 is an answer and is returned as is. A host that failed is skipped
for ten minutes so later requests do not each wait for its timeout. Mirror
requests revalidate, so a mirror's long max-age cannot pin a copy in the HTTP
cache after the primary is back.
The shared OkHttp client and Coil's image client both carry it.
* feat(runtime): start a first install from the chain list the APK was built with
A first install has no chains until chains.json is fetched, and the fetch
retries forever: with GitHub and its mirrors unreachable the wallet stays
empty.
The runtime module now bundles the live chains.json at build time
(bundleChainsConfig, run before preBuild, falling back to jsDelivr and
refusing anything that is not a non-empty JSON array). When the local
database is empty, ChainSyncService tries the network once and, if that
fails, applies the bundled list so the wallet opens; the fetched list then
replaces it through the usual diff as soon as it arrives.
* feat(dapps): show the bundled DApps catalog until one can be fetched
The DApps catalog lived only in memory and every read waited on first(), so
with the catalog URL unreachable the DApps screen waited forever.
feature-dapp-impl now bundles dapps_full.json at build time. On the first
sync the repository tries the network once and, if that fails, publishes the
bundled catalog so every read returns; the fetched catalog replaces it as
soon as it arrives.
The download task moves to scripts/bundled-config.gradle and serves both
the chain list and the DApps catalog. runtime and feature-dapp-impl unit
tests return defaults for android.util.Log, which the fallback calls.
* feat(network): resolve names over DNS-over-HTTPS first, system DNS as fallback
A network that answers plain DNS with a wrong address decides where the app
connects. The shared OkHttp client and Coil's client now resolve through
EncryptedFirstDns: Google, Cloudflare and Quad9 over HTTPS, each reached by
IP, then the system resolver if all of them fail, so no network is worse off.
- A resolver whose request failed is skipped for ten minutes, so a network
that blocks DoH pays the four-second timeout once. NXDOMAIN and SERVFAIL do
not count as failures.
- IPv4 addresses are returned first: DoH answers IPv6 whether or not the
network has it, and OkHttp tries addresses in order.
- IP literals and localhost never leave the device.
WebSocket RPC (nv-websocket) still resolves through the system.
* feat(config): verify signed config metadata, TUF style
The rules the app will apply to config it fetches, not wired yet:
- A root, signed by a threshold of root keys, names the keys that may sign
the targets list and how many must. The APK carries the first root.
- A new root is accepted one version at a time, signed by a threshold of the
old root's keys and of its own, so keys can be replaced without a release
and a lost targets key can be revoked.
- The targets list gives the sha256 of each config file; it cannot go back a
version, cannot jump more than 10,000 at once, and is refused once it or
its root has expired.
- Ed25519 over the payload bytes exactly as published (BouncyCastle, already
a dependency); a key signing twice counts once; a key must match its id.
* fix(config): parse metadata with the Gson API the app resolves
JsonParser.parseString is not in the Gson version on the app's classpath;
the instance parse method is, and behaves the same here.
* test(runtime): add the Log.e overload with a throwable to the test shim
The first-install fallback logs the failed fetch with its exception, and the
shim only had the two-argument form, so the test hit NoSuchMethodError.
* feat(config): refuse config files the signed list does not give
SignedConfigInterceptor checks every JSON file from pezkuwi-wallet-utils
against signed/targets.json, whichever host the mirrors got it from. A
file the list does not give, or one without a verified list behind it,
comes back as a 502, so callers keep the stored chains and bundled
lists as they do when the source is unreachable. Newer roots are
followed from signed/root.N.json; the targets version and the latest
time seen are stored, so an older list cannot be replayed.
Root keys may now be sr25519 wallet accounts: they sign through the
wallet's message signing, which wraps the bytes in <Bytes>, and the
SDK's schnorrkel verifies them. The APK carries root 1 as an asset,
trusted as the APK is.
* DApps: the Dijital Kurdistan Ecosystem Hub, a native screen
Tapping "Dijital Kurdistan" in the DApps tab used to open pezkuwichain.io in the
browser. It now opens the hub first, inside the DApps tab so the wallet's bottom
bar stays in view; the user picks an app there and it opens in the DApp browser.
- Four sections: Finance (8), Governance (9), Education (4), Social (8). Each is
a card with a glowing border in its colour (green, red, amber, blue), a header
band with its name and app count, and a four-column grid of apps.
- Apps open the same pages the web app's mobile home links to, at
app.pezkuwichain.io, or their own sites: Wallet and Validators the explorer,
Exchange pex.network, Delegation wekil.pex.mom, KurdAI kurdishtts.dks.news,
Games games.pex.mom, Rewshenbir rewshenbir.pezkiwi.app. Music has no page yet:
it is shown dimmed and does nothing when tapped.
- The entry is recognised by its URL (pezkuwichain.io root, from
wallet-utils dapps_full.json), so favourites open the hub too. Back, from the
arrow or the system, closes the hub before it leaves the tab.
- Strings in English, Turkish and Kurmanji, worded as the web app words them;
other languages fall back to English.
* DApps hub: open Music at music.dks.news
Every app in the hub now has an address, so the dimmed, inert state for an app
without one is gone along with its string.
* DApps hub: open KurdMedia at dks.news
---------
Co-authored-by: Claude <noreply@anthropic.com>
* build(rust): bump vulnerable crates in native binding lockfiles
- rand 0.8.5 -> 0.8.6 (GHSA-cq8v-f236-94qc) in hydra-dx-math and sr25519-bizinikiwi
- keccak 0.1.5 -> 0.1.6 (GHSA-3288-p39f-rqpv) in hydra-dx-math and sr25519-bizinikiwi
- bytes -> 1.11.1 (GHSA-434x-w66g-qw3r) in hydra-dx-math and metadata_shortener
- time 0.3.41 -> 0.3.47 (GHSA-r6v5-fh4h-64xc) in hydra-dx-math
- tracing-subscriber 0.3.19 -> 0.3.20 (GHSA-xwfj-jgwm-7wp5) in hydra-dx-math
Lockfile-only updates via cargo update --precise; Cargo.toml requirements
are unchanged. time 0.3.47 pulls serde 1.0.229/serde_core and requires
rustc 1.88, which the stable toolchain used by CI satisfies.
* ci: restrict default GITHUB_TOKEN permissions to contents: read
None of these workflows write through GITHUB_TOKEN: artifact upload,
Firebase, S3, Play Store, gh-pages and the Appium dispatch all use their
own credentials. Reusable workflows they call inherit the same cap.
* fix(dapp): send only fixed, JSON-quoted error messages to dApp pages
The polkadot.js responder forwarded Throwable.message of any rejection
error to the page and both responders interpolated the message into
JavaScript without escaping. Map polkadot.js rejections to a fixed public
message (unknown errors become "Request failed"), give MetamaskError an
explicit publicMessage, and quote both with JSONObject.quote so a message
can no longer break out of the string literal.
* ci: stop installing the removed legacy SDK 'tools' package
android-actions/setup-android installs 'tools platform-tools' by default.
sdkmanager now fails with "Failed to find package 'tools'", which aborts
dependency installation for every build and lint job. Only platform-tools
is needed; nothing in the workflows uses the legacy tools directory.