feat: port Rust core runtime#104
Draft
pgherveou wants to merge 210 commits into
Draft
Conversation
Lands the Rust runtime layer behind the TrUAPI protocol: codegen-emitted
dispatcher, platform abstraction crate, server runtime with frame/transport/
dispatcher/subscription, runtime adapter from platform traits, host_logic
helpers, WS bridge for native WebView hosts, UniFFI native bridge, and
wasm-bindgen surface for Web Workers.
New crates:
- truapi-platform: Storage, Navigation, Notifications, Permissions (split
device/remote callbacks), Features, ChainProvider, JsonRpcConnection,
Accounts, Signing, StatementStore, Preimage; Platform super-trait.
- truapi-server: TrUApiCore, Dispatcher, Frame (with [requestId][disc][payload]
envelope), Subscription lifecycle, PlatformRuntimeHost<P> adapter,
host_logic/{dotns,features,permissions,session}, ws_bridge (feature-gated),
native UniFFI bridge, wasm32 wasm-bindgen surface.
- uniffi-bindgen-cli: thin wrapper around uniffi::uniffi_bindgen_main()
for regenerating Kotlin/Swift bindings.
Codegen:
- truapi-codegen --rust-output emits dispatcher.rs and wire_table.rs.
- Methods are keyed by snake_case(trait)_method so StatementStore::submit
and Preimage::submit no longer collide.
- Responses wrap in SCALE Result discriminant byte ([0x00, ok] / [0x01, err])
matching the @parity/truapi TS codec.
- CallContext is constructed with the actual message request_id.
truapi crate (additive only):
- Display impls on v01 and versioned permission enums for modal copy and
storage key construction in host_logic.
Tests: 139 across the workspace (with ws-bridge feature), including WS
round-trip against a real tokio-tungstenite client, frame snapshot,
dotns scheme allowlist (rejects javascript:/file:/data:/vbscript:),
permission cache isolation, session broadcast, dispatcher error path
with Result discriminant.
Relates to #96. Remaining phases tracked in #97-#103.
- #98: SCALE-encode + hex the canonical remote permission bundle for the storage key instead of joining slugs with separators. Domains containing '|', ',', or 'truapi:permissions:' can no longer alias a different bundle's grant. - #99: golden_rust_emit tests use per-test tempdirs (no shared target/doc race) and panic loudly when nightly is unavailable instead of silently passing. - #100: strip phase/migration narration from doc comments and README; switch Chain trait stubs from CallError::HostFailure to CallError::Unsupported. - #102: replace ProtocolMessage::decode_error / call_error with free functions encode_decode_error / encode_call_error_payload returning Vec<u8>. Handlers now return Result<Vec<u8>, Vec<u8>>; the dispatcher owns envelope construction so a malformed empty-tag frame can no longer escape onto the wire. 3 new tests, 142 total. Workspace clean, clippy --all-features clean, wasm32 target clean. Relates to #96.
…subscription (#97) Subscriptions previously parked one OS thread each via std::thread::spawn(|| block_on(future)). On native this leaked a thread per active stream; on wasm32 it would panic outright since wasm has no threads. The dispatcher now requires a Spawner (Arc<dyn Fn(BoxFuture<'static, ()>) + Send + Sync>) provided at construction time. Each entry point picks the right executor: - native.rs: shared futures::executor::ThreadPool (one pool per NativeTrUApiCore, default sized to CPU count). Falls back to the thread-per-subscription spawner if pool construction fails. - wasm.rs: wasm_bindgen_futures::spawn_local. - ws_bridge tests: thread_per_subscription_spawner() helper. TrUApiCore::new and ::from_platform now take the spawner. Dispatcher::new takes it too — no Default impl, since an empty Default would silently regress the leak. Added the `thread-pool` feature to the non-wasm32 `futures` dep. Added subscription_uses_provided_spawner_not_native_thread test. 143 tests passing, wasm32 target clean. Relates to #96.
+33 tests across 7 files covering review gaps: Runtime delegation (core.rs, +13 tests): round-trip via TrUApiCore for get_account, get_account_alias, create_account_proof, get_legacy_accounts, get_user_id, sign_payload, sign_raw, LocalStorage read/write/clear, push_notification, request_remote_permission, connection_status_subscribe. Frame internals (frame.rs, +6): id_for_tag / tag_for_id known + unmapped, compose_action round-trip across every FrameKind, IdFactory monotonic and two-factory state isolation. WS bridge (ws_bridge.rs + native.rs, +3): wrong_token_is_rejected_at_handshake, drop_calls_stop_idempotently, start_ws_bridge_twice_returns_already_running. Session pruning (session.rs, +2): clear_when_empty_is_silent_no_op, dropped_subscriber_is_pruned. Permission error paths (permissions.rs, +3): prompt_failure_collapses_to_denial, corrupt_cache_entry_returns_none, storage_read_error_propagates. Codegen negative paths (truapi-codegen/src/rust.rs, +6): wire_table rejects request-with-subscription-id, subscription-with-request-id, missing IDs; dispatcher rejects multi-param methods and non-named-root response types. All via existing public APIs and test helpers — no production shims added. 176 total tests workspace-wide with ws-bridge feature. Relates to #96.
Lands chainHead-v1 state machine and light-client integration: - chain_runtime.rs (always compiled): ChainRuntime, RuntimeChainProvider, UnavailableChainProvider, json-rpc state machine + follow-event parsing into truapi::v01 RemoteChainHeadFollowItem variants. Spawner is threaded through to spawn the response loop, matching the dispatcher discipline. - smoldot_provider/ (feature `smoldot`, off by default): SmoldotChainProvider, SmoldotJsonRpcConnection wrapping smoldot-light. Native + wasm32 platform glue (websocket transport on wasm). Bundled paseo + asset-hub-paseo chainspecs. All 13 Chain trait methods are now routed through ChainRuntime instead of returning CallError::Unsupported. PlatformRuntimeHost wraps the host's ChainProvider into a RuntimeChainProvider via PlatformChainRuntimeProvider. Tests: 186 passing with --features ws-bridge,smoldot (+10 since previous). Smoldot adds ~36s to first compile; ~10s incremental. wasm32 target clean with --features smoldot. Closes #103 (Phase 4d portion). Relates to #96.
… JS, #103) Three new npm packages under host-libs/js/ wrapping the truapi-server WASM core for browser, worker, and electron host contexts. @parity/host-shared: - WasmRawCallbacks matching Rust JsBridge::from_js (navigateTo, devicePermission, remotePermission, featureSupported, getLegacyAccounts, ...) - createWasmProvider / createNodeWasmProvider (lazy WASM load) - Web Worker entrypoint + worker-protocol wire shape - Re-exports createHostServer / toResponsePayload / toFlatResponsePayload from @parity/truapi-host @parity/host-web: - createIframeHost: embeds an iframe, handshakes a MessagePort via the `truapi-init` message, surfaces the host-side port via onPort. - createWebWorkerProvider: bridges a Web Worker to a Provider. - No legacy @novasamatech/host-api compat path. @parity/host-electron: - createElectronProvider({port}): wraps an Electron MessagePortMain as a Provider. Pre-built WASM artifacts committed under host-libs/js/shared/dist/wasm/ (web + node targets, ~924 KB each, built with `wasm-pack --no-default-features`, smoldot feature off in the shipped bundle). Tests: 5 (shared) + 2 (web) + 2 (electron) = 9 passing via `node --test`. tsc --noEmit clean across all three packages. No changes to truapi/src/api/*. Relates to #96, #103.
Two thin language-idiomatic SDKs wrapping the UniFFI bindings emitted
from truapi-server, ready to be consumed by iOS/Android host repos.
host-libs/android/ (Kotlin):
- io.parity.truapi.{TrUAPIHostCore, HostBridge, HostStorage, CoreInbound,
WebViewTransport} wrapping NativeTrUApiCore via UniFFI
- build.gradle.kts (Android library, JDK 17, JNA 5.14, kotlinx-coroutines)
- AndroidManifest, settings.gradle.kts, gradle.properties for standalone
assembly; also includable as :host-libs:android from a parent project
- Generated UniFFI bindings under src/main/kotlin/generated/
host-libs/ios/TrUAPIHost/ (Swift Package):
- TrUAPIHost.{TrUAPIHostCore, HostBridge, HostStorageBackend, CoreInbound,
WebViewTransport, LocalhostBridgeBootstrap}
- Package.swift exposing TrUAPIHost + truapi_serverFFI systemLibrary targets
- Generated UniFFI bindings under Sources/TrUAPIHost/truapi_server.swift
and Sources/truapi_serverFFI/include/{truapi_serverFFI.h,module.modulemap}
cdylib built with --features ws-bridge so the native surface includes
startWsBridge/stopWsBridge for WebView hosts that prefer the localhost
WS rail.
HostBridge interface keeps the v0.1 device/remote permission split as
two named methods (no merged prompt_permission).
Bindings committed (consumers don't run Rust). 92K Kotlin (2467 LOC),
56K Swift (1744 LOC), 36K C header.
Relates to #96, #103.
Two pre-existing parser bugs in tests/wire_table_ts_parity.rs surfaced
once the TS wire-table.ts file appeared locally:
- parse_ts looked for `method: 'foo'` lines, but the TS codegen emits
`export const FOO_BAR = { request: N, response: N }` shape.
parse_ts now reads the const name and lowercases it.
- parse_rust used starts_with("WireKind::Subscription"), but the actual
line is `kind: WireKind::Subscription {` — starts with "kind:".
Switched to contains().
Wire tables now confirmed in lockstep across Rust and TS.
Makefile: - `make wasm` rebuilds the WASM bundle under host-libs/js/shared/dist/wasm/ - `make uniffi` regenerates Kotlin + Swift bindings from a release cdylib - build / test / check extended to install + test the three host-libs/js packages alongside the existing TS client and playground docs/design/: - Ported dotli-architecture-change.md and dotli-rust-core-proposal.md (the architectural rationale for the WASM-in-worker shared-core move), with @parity package names and current repo paths. .github/workflows/host-libs.yml: - wasm-bundle-check: rebuilds via `make wasm` and fails if the committed bundle drifts. - host-libs-js-tests: builds the @parity/truapi-host + @parity/truapi deps, then runs the three host-libs/js test suites. - Path-filtered to host-libs/** and rust/crates/truapi-server/**. README.md + CLAUDE.md: - Document the new truapi-platform / truapi-server / uniffi-bindgen-cli crates, the @parity/host-* packages, the iOS/Android shells, and the invariants (truapi crate canonical; types via versioned::*; pre-built WASM committed; make wasm / make uniffi to regenerate). host-libs/js/*/README.md note the npm publish workflow is intentionally deferred pending release-process discussion. `make check` runs end-to-end clean. Closes #97, #98, #99, #100, #101, #102, #103. Relates to #96.
…weaken wasm-bundle-check - ws_bridge.rs: nightly clippy fires `result_large_err` because the handshake callback returns `Result<Response, ErrorResponse>` and tokio-tungstenite's `ErrorResponse` carries the full HTTP response (~136 bytes). The closure signature is dictated by 3rd-party API; silence at the function level with an explanatory comment. - .github/workflows/host-libs.yml: the wasm-bundle-check job previously asserted the CI-rebuilt WASM was byte-identical to the committed bundle. wasm-pack output varies with the Rust toolchain and wasm-bindgen version, so cross-machine reproducibility isn't achievable. Weakened to "build succeeds + artifact files exist". The committed bundle stays as a consumer convenience.
Move host SDK SDKs out of the host-libs/ umbrella and rename the JS packages so they share the @parity/truapi-host- prefix consistently with @parity/truapi and @parity/truapi-host: - host-libs/android → android (now an Android library module; standalone settings.gradle.kts + gradle.properties dropped — consumers include this dir from their own root settings) - host-libs/ios → ios (keeps the inner TrUAPIHost/ wrapper, so the Swift Package root remains at ios/TrUAPIHost/Package.swift) - host-libs/js/shared → js/packages/truapi-host-shared (@parity/host-shared → @parity/truapi-host-shared) - host-libs/js/web → js/packages/truapi-host-web (@parity/host-web → @parity/truapi-host-web) - host-libs/js/electron → js/packages/truapi-host-electron (@parity/host-electron → @parity/truapi-host-electron) - host-libs/ removed entirely. Also: - Inter-package file: deps simplified (now siblings under js/packages/). - Makefile WASM_DIST + uniffi --out-dir paths updated. - CI workflow renamed host-libs.yml → host-packages.yml; path filters and step labels updated. - README + CLAUDE layout + design docs + package READMEs scrubbed for stale host-libs references. - chain_runtime::drop_follow_stream_sends_unfollow flake fixed by bumping the cleanup poll budget from 1s to 5s (was racing on loaded CI runners). Workspace clippy + tests pass; 3 JS packages tsc + npm test pass.
…rker on host.dot.li
The AFTER diagram in dotli-architecture-change.md previously placed the
truapi-server WASM Worker on the dot.li main thread. That's wrong:
- dot.li (and *.dot.li catchall) → host build → user-visible shell
- <cid>.app.dot.li → sandbox build → product iframes (per-CID origin)
- host.dot.li → protocol build → stable-origin hidden iframe
A Worker spawned from dot.li has stable storage on that origin (good)
but cohabits with paint frames (bad). A Worker spawned from a product
iframe at <cid>.app.dot.li loses its storage on every CID update.
The protocol iframe at host.dot.li is the only origin that's both stable
and UI-free. Constructing a SharedWorker from there gives:
- IndexedDB on host.dot.li (stable across product CID updates)
- Cross-tab single core (replaces the existing BroadcastChannel glue
for shared auth, folds the standalone smoldot SharedWorker into one)
- No protocol decoding on a paint thread
Updated:
- AFTER diagram in dotli-architecture-change.md now shows the three
origins, the host shell as a port relay, and the SharedWorker hosting
the WASM core with embedded smoldot.
- New "Origin model" section explains the nginx routing + why
host.dot.li is the right script origin for the worker.
- Module-diff section clarifies @parity/truapi-host-{shared,web} roles.
- dotli-rust-core-proposal.md recommendation flipped from Option 1
(per-tab worker) to Option 2 (SharedWorker), with a fallback note for
engines without SharedWorker support.
- Migration considerations add the localStorage → IndexedDB migration
required when state moves off the protocol-iframe main thread.
…t + typealiases Apply the SDK-team vision (HostCallbacks = OS primitives the Rust core can't do alone) and drop indirection that wasn't pulling its weight: - Trim Platform to Storage + Navigation + Notifications + Permissions + Features + ChainProvider + JsonRpcConnection. Drop Accounts, Signing, StatementStore, Preimage — these belong in the Rust core itself, backed by Storage + chain runtime, not as platform traits. - Drop #[async_trait]; use native `async fn -> impl Future + Send` in trait bodies. PlatformRuntimeHost<P> is generic over P: Platform so dyn-trait compatibility isn't required. - Drop the 11 typealiases at the top of platform/src/lib.rs (StorageKey/StorageValue/StorageError/NavigateToError/...). Trait signatures use the canonical v01 names directly. - Drop the `async-trait` and `parity-scale-codec` deps from truapi-platform/Cargo.toml. Cascade: - runtime.rs: remove Account/Signing/StatementStore/Preimage impls for PlatformRuntimeHost<P>; truapi::api::* default bodies return CallError::Unsupported, which is the right semantic until those flows land in-core. - native.rs, wasm.rs: drop the 4 CallbackPlatform impls and the corresponding callback fields on HostCallbacks / JsBridge. - host_logic/permissions.rs: PermissionsService<S, P> generic, peek() takes &impl Storage. HostLocalStorageReadError used directly. - chain_runtime.rs, smoldot_provider/mod.rs: drop GenesisHash type alias use; switch to Vec<u8> / &[u8]. - All test stubs drop #[async_trait]; the 7 round-trip tests that asserted specific Domain returns from the removed Platform traits are deleted (their replacement would be a one-line Unsupported assertion, already covered by request_login_returns_unsupported). - Regenerated UniFFI Kotlin + Swift bindings. CLAUDE.md adds two guidelines: 1. Don't introduce typealias chains that just rename a public type from another crate. 2. Update README.md after every code change so the top-level docs reflect what the repo actually contains. Verification: - cargo +nightly fmt --check, clippy --all-features, test --features ws-bridge: clean - 177 tests passing (was 184; 7 removed-trait round-trip tests deleted) - wasm32 target builds clean
Products running in a WebView/WKWebView now connect to the Rust core through the localhost WebSocket bridge instead of a base64-over- JavascriptInterface (Android) / base64-over-WKScriptMessageHandler (iOS) shim. The Rust ws_bridge already existed; this commit adds the matching JS client and strips the obsolete native transport. @parity/truapi: - New `createWebSocketProvider(url, opts?)` producing a `Provider` over a binary WebSocket. Connects to the localhost endpoint the native shell prints via `startWsBridge`. android/TrUAPIHost.kt: - Drop `WebViewTransport`, `CoreInbound`, `bootstrapScript`, and all base64/JavascriptInterface plumbing. - `TrUAPIHostCore` keeps its `receiveFromProduct` entrypoint for tests and alternative transports; the WS bridge feeds the core internally. ios/TrUAPIHost.swift: - Drop `WebViewTransport` (`WKScriptMessageHandler` + base64 path) and `CoreInbound` protocol. Drop the `WebKit` import. - Keep `LocalhostBridgeBootstrap` — its purpose is exactly to publish the WS endpoint to the product page so it can call `createWebSocketProvider(url)`. READMEs for both native shells now describe the WS-bridge flow: 1. Host calls `core.startWsBridge()` → gets port + token. 2. Inject the endpoint into the product page (Android: query string; iOS: `LocalhostBridgeBootstrap.script()` as a WKUserScript). 3. Product JS reads the URL and passes it to `createWebSocketProvider`. @parity/truapi build + tests pass with the new export.
…tform (#18) UniFFI already gives Kotlin (Android) and Swift (iOS) trait surfaces straight from `truapi-platform`. Web hosts had hand-written TS types in `@parity/truapi-host-shared/src/runtime.ts` that mirrored the Rust traits manually — drift hazard. Codegen now covers TS too so all three platforms track the same Rust source. What's new: - `truapi-codegen` gains a platform-crate rustdoc parser (`src/platform.rs`, ~470 LOC). Walks each `pub trait`, classifies method return types (`Unit` / `Result<...>` / `BoxStream` / `Box<dyn T>` / plain), records `impl Future + Send` returns as async. Detects the `Platform` super-trait (method-less, composed of other local traits) and preserves the composition order. - New TS emitter `src/ts/host_callbacks.rs` (~250 LOC) emits one `export interface` per Rust capability trait plus a composite `HostCallbacks extends Storage, Navigation, ...` interface that mirrors `Platform`. Types resolve to existing imports from `@parity/truapi`. - CLI flags `--platform-input` and `--platform-ts-output` wire the emitter into the workflow. Existing `--input`/`--output`/`--host-output`/ `--rust-output` flags are unchanged. - `scripts/codegen.sh` runs `cargo +nightly rustdoc -p truapi-platform` alongside the existing `-p truapi` step and emits to `js/packages/truapi-host-shared/src/generated/host-callbacks.ts`. Consumer integration in `@parity/truapi-host-shared`: - `src/generated/` and `dist/generated/` added to `.gitignore` (matches the `@parity/truapi` policy). - `runtime.ts` re-exports the typed surface (`HostCallbacks`, `Storage`, `Navigation`, ...) from the generated file. `WasmRawCallbacks` stays in place — it's the byte-level WASM bridge surface and additionally covers core-internal account/sign/statement- store callbacks that aren't part of `truapi-platform`. A short doc-block makes the typed-vs-raw layering explicit. Bridging the two via a SCALE adapter is a deliberate follow-up. Tests: - `golden_host_callbacks_ts` integration test pairs rustdoc-of-both- crates with the codegen binary and diffs against a checked-in golden. - 178 workspace tests passing (was 177; +1 for the new golden). - wasm32 target builds clean. - `npm install && npm run build && npm test` clean in `@parity/truapi-host-shared`.
…emitter The new TS HostCallbacks emitter from #18 hand-chained 15 writeln! calls to build the output. CLAUDE.md (this commit also) now requires codegen modules to prefer indoc::writedoc! / formatdoc! over writeln! chains so the emitted shape stays visible in source. Switched host_callbacks.rs to: - writedoc! for the header + import block (two multi-line writes) - formatdoc! returning strings for trait interfaces, methods, and the super-interface; the parent function joins them - render_jsdoc returns a String instead of writing through a sink writeln! count: 15 → 2 (both inside writedoc! invocations themselves). Golden test rebuilt clean; workspace tests all pass.
…ns, in PR descriptions Adds a guideline that PR descriptions, issue comments, and similar artifacts that survive after squash-merge should describe what the system *does* after the change, not the diff between commits. "Previously X, now Y" and "the old shim is gone" framing reads as ephemeral history once the PR lands and the writer is no longer in the picture. Commit messages remain the place for transition narrative; they stay readable via `git log` after the squash.
Bug fixes: - Makefile uniffi target now writes Swift bindings through a tempdir and copies into the actual SwiftPM layout (Sources/TrUAPIHost/ for the .swift file, Sources/truapi_serverFFI/include/ for the C header and module.modulemap). Cdylib path picks .dylib on macOS. - electron provider: onClose sets disposed=true and clears listener sets so subsequent postMessage no-ops cleanly and close listeners don't fire twice if the caller also calls dispose(). - web worker provider: init-failure paths call worker.terminate() before rejecting so a failed core init doesn't leak the worker. - createWebSocketProvider: subscribe / subscribeClose use Set instead of Array+indexOf so double-registration of the same callback is idempotent (matches the other providers in the package). Test coverage: - New js/packages/truapi/test/ws-provider.test.mjs exercises the WS provider against a stubbed WebSocket constructor (queue+flush, fan-out, set semantics, close fan-out, post-after-close throws). Doc + style fixes: - docs/design/dotli-architecture-change.md describes featureSupported as a current callback with a planned removal, matching the platform trait and the host shells. The implementation-vs-doc contradiction is gone. - Migration narrative stripped from runtime.rs, wasm.rs, native.rs, host_logic/features.rs, the Kotlin shell, and the Swift shell. - Em-dashes swept from 18 PR-scope files (CLAUDE.md, both design docs, both native READMEs, the TS host packages, a few platform doc comments). - create-iframe-host.ts comment now describes current behaviour without referencing the "legacy fallback". - tests/object_safety.rs renamed to tests/bounds.rs with a docstring that matches what the test actually asserts (the trait isn't object-safe; the bound check is on Send + Sync + 'static). - iOS: drop dead `_ = self.callbackRetainer` line; explain the retainer in plain prose instead of via a no-op suppression. - Android: drop redundant @Suppress("unused") on a property that is read in init. Deferred to follow-up issues (#105-#111).
…n artifact
The Android library is now consumed the same way the iOS Swift Package
is: a git tag in this monorepo. JitPack builds the artifact on demand
when a consumer pulls the tag; no Maven Central account, GPG signing,
or org-internal artifact server required.
Consumer integration:
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.paritytech.truapi:truapi-android:0.1.0")
}
Layout:
- `settings.gradle.kts` at the repo root declares `:truapi-android`
pointing at `android/`.
- `build.gradle.kts` at the repo root pins the AGP + Kotlin plugin
versions.
- `gradle.properties` carries the workspace Gradle settings.
- `jitpack.yml` tells JitPack which JDK to use and which Gradle task
produces the publication.
- `android/build.gradle.kts` applies `maven-publish`, declares the
`io.parity:truapi-host-android` publication with full POM metadata
(name, description, licenses, SCM, developers), and exposes sources
+ javadoc jars. JNA is an `api` dependency so consumers don't need
to redeclare it.
- `android/consumer-rules.pro` keeps `uniffi.truapi_server.*` and
`io.parity.truapi.*` reachable through R8/ProGuard.
CI:
- `host-packages.yml` gets an `android-assemble` job that runs
`gradle :truapi-android:assembleRelease` and
`publishReleasePublicationToMavenLocal` on every PR touching
`android/` or `truapi-server/`, then verifies the AAR + POM +
sources jar landed in `~/.m2`.
Release flow: tag the commit `truapi-host-android@<version>` after
bumping `publicationVersion` in `android/build.gradle.kts`. JitPack
picks up the tag on first consumer fetch.
Native cdylib is the consumer's responsibility: cross-build
`libtruapi_server.so` per ABI with `cargo-ndk` and drop into
`src/main/jniLibs/<abi>/`. README documents the flow. Pre-built ABI
bundles inside the AAR are tracked as a follow-up.
…host/`
Layout becomes parallel and lowercase:
android/
truapi-host/ io.parity:truapi-host-android (Maven library)
ios/
truapi-host/ TrUAPIHost (SwiftPM package)
Leaves room for additional native packages alongside (e.g.
`android/widgets/`, `ios/something-else/`) without re-shaping the
top-level directory layout.
Gradle subproject renames from `:truapi-android` to `:truapi-host`; the
JitPack consumer coordinate becomes
`com.github.paritytech.truapi:truapi-host:<tag>`. The Maven artifactId
stays `truapi-host-android` (Maven sees no platform path context, so the
suffix earns its keep against a hypothetical future
`truapi-host-jvm`/etc.).
Path updates everywhere they're referenced: settings.gradle.kts,
build.gradle.kts at root, jitpack.yml, Makefile, host-packages.yml CI,
README.md, CLAUDE.md, uniffi-bindgen-cli README, android/truapi-host
README, .gitignore.
Also align with polkadot-app-android-v2 conventions:
- minSdk bumped to 29 (their floor).
- README documents JNA as a transitive (~1.5MB; they don't currently
carry it).
- README's cross-build section leads with the
`mozilla-rust-android-gradle` plugin recipe (their existing toolchain)
and keeps `cargo-ndk` as the standalone option.
Reconcile the Rust core runtime port with main's evolved truapi surface.
main realigned the canonical truapi protocol while this branch was open:
- RemotePermissionRequest carries a single `permission`, not a `permissions`
bundle, so the runtime's permission storage-key canonicalization operates on
one permission (domain lists still sorted for stable, injection-safe keys).
- GenericError is a plain struct `{ reason }`; the runtime constructs it
directly.
- The protocol drops the JsonRpc capability, moves push notifications into a
dedicated Notifications trait (send/cancel with host-assigned ids and
scheduling per RFC 0019), and restructures Payment (request/response rename
plus top-up and balance/status subscriptions) alongside a new CoinPayment
capability.
The runtime tracks that surface: CoinPayment and the remaining
platform-unmodeled capabilities resolve to their default `unavailable` bodies,
and Notifications send/cancel return `unavailable` since the v0.1 platform
contract models neither notification ids, cancellation, nor scheduling. The
generated dispatcher, wire-table, codegen goldens, and TypeScript client are
regenerated from the merged trait surface; Rust↔TS wire ids agree.
The dotli submodule advances to main's pointer.
…kages
The `@parity/truapi*` packages form a build DAG (truapi → truapi-host →
truapi-host-shared → {truapi-host-web, truapi-host-electron}). Each package's
tsconfig is now a `composite` project that `references` the packages it
imports, and every `build` script runs `tsc -b`, so building any package first
builds its dependencies' `dist/*.d.ts` in topological order.
This makes a cold `npm ci` reliable: previously the workspace `prepare` builds
ran a bare `tsc` concurrently, so a dependent package (e.g. truapi-host-electron)
could compile against `@parity/truapi` before that sibling's `dist/` existed and
fail with `TS2307: Cannot find module '@parity/truapi'`. With `tsc -b` the
dependency is always built first, and TypeScript's `.tsbuildinfo` up-to-date
checks make the concurrent prepare invocations safe.
This was referenced Jun 11, 2026
Collaborator
Author
|
Superseded by a 3-PR stack carved from this branch, each independently reviewable and green on CI:
Review bottom-up. Authored with assistance from Claude Code. |
BREAKING CHANGE: RuntimeConfig and SessionUiInfo drop unused/redundant fields; host WASM removes early Electron/WebSocket surfaces.
pgherveou
commented
Jun 12, 2026
Comment on lines
+65
to
+69
| try { | ||
| const stored = globalThis.localStorage?.getItem(DEV_LOG_LEVEL_KEY); | ||
| return isLogLevel(stored) ? stored : null; | ||
| } catch { | ||
| return null; |
Collaborator
Author
There was a problem hiding this comment.
we should not try catch here
Collaborator
Author
There was a problem hiding this comment.
Resolved in 83030ab: removed the storage try/catch from readPersistedLogLevel; storage failures now surface normally.
Collaborator
Author
There was a problem hiding this comment.
let's move all the playground/ change to another PR
Collaborator
Author
There was a problem hiding this comment.
Resolved in b559d8f: reverted the playground directory changes so they can move to a separate PR.
Collaborator
Author
There was a problem hiding this comment.
Follow-up PR created with the playground-only diff: #214
Collaborator
Author
BREAKING CHANGE: WASM hosts must use WasmHostCore, receiveFrame, disconnectSession, cancelPairing, HostCoreRuntimeConfig, and StoredSession callback names.
341366d to
c5fb5c9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR ships
This PR makes the shared Rust host core real for TrUAPI and ports dotli's web
host onto it. The product-facing TypeScript client, the Rust dispatcher/runtime,
and the web host WASM package are generated from the same Rust protocol
definitions, so wire ids and typed request/response surfaces stay in lockstep.
Concretely, after this PR a product engineer can keep using the generated
@parity/truapiclient:...and the call is dispatched into the Rust core running inside the host. In
this PR that path is wired through dotli's web host. Mobile host scaffolding is tracked in child PR #215.
Stacked follow-ups
Runtime topology
Transports:
MessageChannel. The product opens aport; the host hands it to the WASM-backed runtime through
@parity/truapi-host-wasm.What gets generated, from one Rust source
The contract for every protocol domain (accounts, signing, chain access,
statements, preimage, payments, ...) is a Rust trait in
rust/crates/truapi/.Each method carries a stable
#[wire(id = N)]annotation. From that source thecodegen emits:
@parity/truapi, the TypeScript product client. Typed methods, typederrors, typed subscription items.
truapi-server/src/generated/) that routes incomingframes by numeric wire id to runtime trait impls. Request/response and
subscription start/stop/receive/interrupt ids are generated together and
checked against the TS wire table.
HostCallbacksTypeScript interface mirroringtruapi-platform's Rust traits for web hosts.The TS surfaces are derived from Rust rather than being maintained by hand.
make codegenregenerates the TS and Rust source artifactsand formats the generated Rust output; generated WASM outputs
remain gitignored.
The host surface (
truapi-platform)A host's job is to fulfil the small set of capabilities the Rust core cannot do
from its own process:
Storage— product-scoped key/value persistenceSessionStore— host-global SSO session persistenceNavigation— open URLsNotifications— push and cancel notificationsPermissions— user permission promptsFeatures— feature-support probingChainProvider+JsonRpcConnection— chain RPC factoryAuthPresenter— render the core-emitted auth state (pairing QR, accountbadge, login errors)
UserConfirmation,PreimageHost,ThemeHost— host-owned UI/backendcapabilities
Account/session state, auth/login UI state, product account derivation, SSO
pairing, persisted restore/logout, signing requests, statement-store requests,
preimage routing, permissions caching, notifications, theme updates, chainHead
v1 request handling, and TrUAPI debug events now live in the Rust core for the
current dotli parity scope.
Important recent decisions:
established only through SSO pairing.
requests flow over the encrypted statement-store SSO session channel.
encoding/versioning and relies on the host storage trust boundary rather than
adding a separate storage crypto wrapper here.
continue with change notifications.
(
Disconnected / Pairing / Connected / LoginFailed) emitted through a singleauthStateChangedhost callback; hosts are pure renderers and report usercancellation via an explicit
cancelLogin()call. The same contract isexposed on the web WASM surface here; mobile surfaces are tracked in Add mobile host scaffolding #215.
bridge idea was removed from this implementation scope and left as a separate
design note.
What lands in this PR
Rust crates:
truapi-platform— host capability traits and runtime config.truapi-server— dispatcher, SCALE frame handling, subscriptions, runtimehost implementation, chainHead v1 runtime, SSO pairing/session protocol,
statement-store helpers, WASM surface.
JS host package:
@parity/truapi-host-wasm— WASM-backed host runtime with web worker,iframe, Electron, and test entry points. The package is linked into
dotli by
make dev, and generated WASM outputs are gitignored.dotli web host integration:
product-account derivation, signing, statement-store, notifications, theme,
preimage, permissions, chain RPC, and debug events are wired through the new
runtime.
the pairing modal cancels the login in the core, and an unrelated disconnect
signal can never tear down an active pairing presentation.
session state, rejects pending SSO requests, and prevents stale pairing
responses from restoring the previous session.
dotliProductIdoverride for localhost paritytesting while keeping generated examples product-id stable.
Codegen and docs:
wire-id dispatch tables.
CLAUDE.md, including how to run thelocalhost stack and which mobile-confirmation paths to skip when no phone is
available.
What this PR explicitly does not ship
Scoped out and tracked separately:
beside SSO pairing and the statement-store SSO session channel.
tests locally/CI; release automation is a follow-up.
Rust core directly. The earlier nested-bridge idea was removed from this
implementation scope.
implement can remain typed unsupported/deferred until another host or product
needs them. This includes full payment/chat/coin-payment behavior and account
proof work beyond the currently wired statement proof paths.
automatically exercised; mobile host validation remains follow-up because it
requires the native app integration.
How to verify
make devfrom a fresh clone links@parity/truapi-host-wasminto dotli.make codegenregenerates TS/Rust host artifacts without committing generatedWASM outputs.
TRUAPI_SKIP_PACKAGE_BUILD=1 make codegencargo +nightly fmt --checkcargo test -p truapi-codegen --test golden_rust_emitcargo test -p truapi-server --libcargo test -p truapi-server --testsnpm test --prefix js/packages/truapinpm test --prefix js/packages/truapi-host-wasmnpm run build --prefix js/packages/truapi-host-wasmcd hosts/dotli && bun run typecheckcd playground && yarn e2e— handshake, unary, subscription, and the loginpairing-modal lifecycle (modal stays open while pairing, cancel stops the
statement-store polling, retry reopens a fresh QR).
cancel the pairing modal once, retry, scan the QR with Polkadot Mobile,
verify
get_account,get_account_alias,get_user_id, signing-relatedflows, notifications, logout, and relogin.
Closes #96, #97, #98, #99, #100, #101, #102, #103.