@parity/product-sdk-host 0.10.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/entropy.ts CHANGED
@@ -3,18 +3,19 @@
3
3
  /**
4
4
  * Higher-level wrapper for the host's entropy derivation (RFC-0007).
5
5
  *
6
- * `hostApi.deriveEntropy` is reachable via {@link getTruApi}, but consumers
7
- * have to wrap the value in the versioned envelope (`enumValue("v1", ...)`)
8
- * and unwrap the neverthrow `ResultAsync` themselves. `deriveEntropy`
9
- * collapses that to a throw-on-error Promise that matches the shape of
10
- * {@link requestPermission} and {@link requestResourceAllocation}.
6
+ * `truApi.entropy.derive` takes a hex `context` and returns a hex `entropy`
7
+ * payload wrapped in a neverthrow `ResultAsync`. `deriveEntropy` keeps the
8
+ * ergonomic `Uint8Array Result<Uint8Array, HostError>` signature: it
9
+ * hex-encodes the context on the way in and decodes the entropy on the way out.
11
10
  *
12
11
  * @module
13
12
  */
14
13
 
15
14
  import { createLogger } from "@parity/product-sdk-logger";
16
15
 
17
- import { enumValue, formatHostError, getTruApi } from "./truapi.js";
16
+ import { type HostError, HostUnavailableError } from "./errors.js";
17
+ import { type Result, err } from "./result.js";
18
+ import { fromHex, getTruApi, mapHostResult, toHex } from "./truapi.js";
18
19
 
19
20
  const log = createLogger("host:entropy");
20
21
 
@@ -26,42 +27,79 @@ const log = createLogger("host:entropy");
26
27
  * different keys (or different wallets) yield uncorrelated entropy.
27
28
  *
28
29
  * @param key - Context key bytes (typically a SCALE-encoded discriminator).
29
- * @returns The derived entropy bytes.
30
- * @throws If the host is unavailable or the host-side derivation fails.
30
+ * @returns `ok` with the derived entropy bytes, or
31
+ * `err(HostUnavailableError | HostCallFailedError)`.
31
32
  *
32
33
  * @example
33
34
  * ```ts
34
35
  * import { deriveEntropy } from "@parity/product-sdk-host";
35
36
  *
36
- * const seed = await deriveEntropy(new TextEncoder().encode("my-app:seed-v1"));
37
+ * const r = await deriveEntropy(new TextEncoder().encode("my-app:seed-v1"));
38
+ * if (r.ok) { const seed = r.value; }
37
39
  * ```
38
40
  */
39
- export async function deriveEntropy(key: Uint8Array): Promise<Uint8Array> {
41
+ export async function deriveEntropy(key: Uint8Array): Promise<Result<Uint8Array, HostError>> {
40
42
  const truApi = await getTruApi();
41
43
  if (!truApi) {
42
- throw new Error("deriveEntropy: TruAPI unavailable");
44
+ return err(new HostUnavailableError("deriveEntropy: TruAPI unavailable"));
43
45
  }
44
46
  log.debug("deriveEntropy", { keyLen: key.length });
45
47
 
46
- return await truApi.deriveEntropy(enumValue("v1", key)).match(
47
- (envelope: { tag: "v1"; value: Uint8Array }) => envelope.value,
48
- (err: unknown) => {
49
- throw new Error(`deriveEntropy failed: ${formatHostError(err)}`, { cause: err });
50
- },
48
+ return mapHostResult(
49
+ truApi.entropy.derive({ context: toHex(key) }),
50
+ (response) => fromHex(response.entropy),
51
+ "deriveEntropy failed",
51
52
  );
52
53
  }
53
54
 
54
55
  if (import.meta.vitest) {
55
- const { test, expect } = import.meta.vitest;
56
+ const { test, expect, describe, vi } = import.meta.vitest;
56
57
 
57
- test("deriveEntropy throws when TruAPI is unavailable", async () => {
58
- const api = await getTruApi();
59
- if (api === null) {
60
- await expect(deriveEntropy(new Uint8Array([1, 2, 3]))).rejects.toThrow(
61
- /TruAPI unavailable/,
62
- );
63
- } else {
64
- expect(typeof deriveEntropy).toBe("function");
58
+ function okAsync<T>(value: T) {
59
+ return { match: async (onOk: (v: T) => unknown) => onOk(value) };
60
+ }
61
+
62
+ async function withMockedTruApi<T>(
63
+ client: unknown,
64
+ fn: (mod: typeof import("./entropy.js")) => Promise<T>,
65
+ ): Promise<T> {
66
+ vi.resetModules();
67
+ vi.doMock("./truapi.js", async (importOriginal) => {
68
+ const original = await importOriginal<typeof import("./truapi.js")>();
69
+ return { ...original, getTruApi: async () => client };
70
+ });
71
+ try {
72
+ const mod = await import("./entropy.js");
73
+ return await fn(mod);
74
+ } finally {
75
+ vi.doUnmock("./truapi.js");
76
+ vi.resetModules();
65
77
  }
78
+ }
79
+
80
+ // Tests live inside `describe` so the re-import in `withMockedTruApi`
81
+ // (via `vi.resetModules`) doesn't re-register top-level `test()` calls.
82
+ describe("deriveEntropy", () => {
83
+ test("returns err(HostUnavailableError) when TruAPI is unavailable", async () => {
84
+ await withMockedTruApi(null, async (mod) => {
85
+ const result = await mod.deriveEntropy(new Uint8Array([1, 2, 3]));
86
+ expect(result.ok).toBe(false);
87
+ if (!result.ok) {
88
+ expect(result.error.name).toBe("HostUnavailableError");
89
+ }
90
+ });
91
+ });
92
+
93
+ test("hex-encodes the context and decodes the entropy bytes", async () => {
94
+ const derive = vi.fn(() => okAsync({ entropy: "0xc0ffee" }));
95
+ await withMockedTruApi({ entropy: { derive } }, async (mod) => {
96
+ const result = await mod.deriveEntropy(new Uint8Array([0xab, 0xcd]));
97
+ expect(derive).toHaveBeenCalledWith({ context: "0xabcd" });
98
+ expect(result.ok).toBe(true);
99
+ if (result.ok) {
100
+ expect(Array.from(result.value)).toEqual([0xc0, 0xff, 0xee]);
101
+ }
102
+ });
103
+ });
66
104
  });
67
105
  }
package/src/errors.ts ADDED
@@ -0,0 +1,198 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Typed errors carried on the `err` channel of the host public API's
5
+ * {@link Result} returns.
6
+ *
7
+ * The hierarchy mirrors `@parity/product-sdk-signer`'s error classes
8
+ * (`HostUnavailableError` / `HostRejectedError`), so the two layers share one
9
+ * idiom: branch with `instanceof`, and every error is a real `Error` with a
10
+ * stack trace and `cause`. The structured truapi wire error
11
+ * ({@link HostErrorPayload}) rides along as {@link HostCallFailedError.payload}
12
+ * for callers that want fine-grained tag-level handling.
13
+ *
14
+ * This module also owns {@link HostErrorPayload} (the wire-error shape) and
15
+ * {@link formatHostError} (renders a payload to a message) — co-located with the
16
+ * error classes that consume them so the host error model lives in one place.
17
+ *
18
+ * @module
19
+ */
20
+ import type { GenericError } from "@parity/truapi";
21
+
22
+ /**
23
+ * The structured error payload `@parity/truapi` surfaces on the `Err` channel of
24
+ * a host call, once unwrapped from the versioned wire envelope. Every host error
25
+ * union is built from these:
26
+ *
27
+ * - the catch-all {@link GenericError} (`{ reason }`),
28
+ * - a unit tagged variant (`{ tag }`), or
29
+ * - a tagged variant carrying a reason (`{ tag, value: { reason } }`).
30
+ *
31
+ * `GenericError` is imported from `@parity/truapi`; the `{ tag }` members are a
32
+ * deliberate widening of truapi's per-domain named variants (the formatter is
33
+ * tag-agnostic). truapi has no umbrella error union to import today — once it
34
+ * exports a canonical tagged-error union from codegen, replace these local
35
+ * members with that import so the type is protocol-sourced rather than
36
+ * hand-widened.
37
+ *
38
+ * This is the *payload* the host public API carries inside a
39
+ * {@link HostCallFailedError} on the `err` channel of its `Result` returns — not
40
+ * the error type consumers branch on.
41
+ */
42
+ export type HostErrorPayload =
43
+ | GenericError
44
+ | { tag: string; value?: undefined }
45
+ | { tag: string; value: { reason: string } };
46
+
47
+ /** Narrow an unknown `Err`-channel value to a {@link HostErrorPayload}. */
48
+ function isHostErrorPayload(error: unknown): error is HostErrorPayload {
49
+ if (error == null || typeof error !== "object") return false;
50
+ const obj = error as Record<string, unknown>;
51
+ return typeof obj.reason === "string" || typeof obj.tag === "string";
52
+ }
53
+
54
+ /**
55
+ * Extract a human-readable message from a host-side error.
56
+ *
57
+ * Renders the {@link HostErrorPayload} shapes `@parity/truapi` surfaces. Accepts
58
+ * `unknown` because it is also the catch-all formatter for thrown adapter-method
59
+ * `Error` messages, so it falls back to `Error`/string/JSON rendering for
60
+ * anything that isn't a recognized host error payload.
61
+ *
62
+ * Used by {@link HostCallFailedError} to render its message, and by the throwing
63
+ * adapter-method helper `unwrapHostResult`.
64
+ */
65
+ export function formatHostError(error: unknown): string {
66
+ if (error instanceof Error) return error.message;
67
+ if (typeof error === "string") return error;
68
+
69
+ if (isHostErrorPayload(error)) {
70
+ if ("tag" in error) {
71
+ // Tagged variant carrying a reason: { tag, value: { reason } }
72
+ if (error.value != null && typeof error.value.reason === "string") {
73
+ return `${error.tag}: ${error.value.reason}`;
74
+ }
75
+ // Unit tagged variant, e.g. { tag: "Full" } / { tag: "PermissionDenied" }
76
+ return error.tag;
77
+ }
78
+ // GenericError: { reason }
79
+ return error.reason;
80
+ }
81
+
82
+ if (error != null && typeof error === "object" && "message" in error) {
83
+ const message = (error as { message: unknown }).message;
84
+ if (typeof message === "string") return message;
85
+ }
86
+
87
+ try {
88
+ return JSON.stringify(error);
89
+ } catch {
90
+ return String(error);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Base class for all host errors. Use `instanceof HostError` (or {@link isHostError})
96
+ * to catch any host-related failure.
97
+ */
98
+ export class HostError extends Error {
99
+ constructor(message: string, options?: ErrorOptions) {
100
+ super(message, options);
101
+ this.name = "HostError";
102
+ }
103
+ }
104
+
105
+ /**
106
+ * The host API is not available — the app is running outside a Polkadot host
107
+ * container (no injected TruAPI transport). The dominant case during local
108
+ * development. Branch with `instanceof HostUnavailableError` to surface an
109
+ * "open this app in a Polkadot host" message.
110
+ */
111
+ export class HostUnavailableError extends HostError {
112
+ constructor(message = "Host API is not available") {
113
+ super(message);
114
+ this.name = "HostUnavailableError";
115
+ }
116
+ }
117
+
118
+ /**
119
+ * A host call reached the container but failed on the `Err` channel. Wraps the
120
+ * structured truapi {@link HostErrorPayload} as {@link payload} (also preserved
121
+ * as `cause`); the message is rendered via {@link formatHostError}.
122
+ */
123
+ export class HostCallFailedError extends HostError {
124
+ readonly payload: HostErrorPayload;
125
+
126
+ constructor(label: string, payload: HostErrorPayload) {
127
+ super(`${label}: ${formatHostError(payload)}`, { cause: payload });
128
+ this.name = "HostCallFailedError";
129
+ this.payload = payload;
130
+ }
131
+ }
132
+
133
+ /** Check whether a value is any {@link HostError}. */
134
+ export function isHostError(error: unknown): error is HostError {
135
+ return error instanceof HostError;
136
+ }
137
+
138
+ if (import.meta.vitest) {
139
+ const { test, expect, describe } = import.meta.vitest;
140
+
141
+ describe("host error classes", () => {
142
+ test("HostError is the base class", () => {
143
+ const e = new HostUnavailableError();
144
+ expect(e).toBeInstanceOf(HostError);
145
+ expect(e).toBeInstanceOf(Error);
146
+ });
147
+
148
+ test("HostUnavailableError default message", () => {
149
+ const e = new HostUnavailableError();
150
+ expect(e.name).toBe("HostUnavailableError");
151
+ expect(e.message).toBe("Host API is not available");
152
+ });
153
+
154
+ test("HostUnavailableError custom message", () => {
155
+ expect(new HostUnavailableError("nope").message).toBe("nope");
156
+ });
157
+
158
+ test("HostCallFailedError renders payload and preserves it", () => {
159
+ const payload = { tag: "PermissionDenied", value: { reason: "user said no" } };
160
+ const e = new HostCallFailedError("requestPermission failed", payload);
161
+ expect(e).toBeInstanceOf(HostError);
162
+ expect(e.payload).toBe(payload);
163
+ expect(e.cause).toBe(payload);
164
+ expect(e.message).toBe("requestPermission failed: PermissionDenied: user said no");
165
+ });
166
+
167
+ test("HostCallFailedError renders a GenericError payload", () => {
168
+ const e = new HostCallFailedError("submit failed", { reason: "timeout" });
169
+ expect(e.message).toBe("submit failed: timeout");
170
+ });
171
+
172
+ test("isHostError narrows host errors only", () => {
173
+ expect(isHostError(new HostUnavailableError())).toBe(true);
174
+ expect(isHostError(new HostCallFailedError("x", { reason: "y" }))).toBe(true);
175
+ expect(isHostError(new Error("plain"))).toBe(false);
176
+ expect(isHostError("string")).toBe(false);
177
+ });
178
+ });
179
+
180
+ describe("formatHostError", () => {
181
+ test("renders the TruAPI error payload shapes", () => {
182
+ // GenericError: { reason }
183
+ expect(formatHostError({ reason: "boom" })).toBe("boom");
184
+ // Tagged variant carrying a reason: { tag, value: { reason } }
185
+ expect(formatHostError({ tag: "Unknown", value: { reason: "boom" } })).toBe(
186
+ "Unknown: boom",
187
+ );
188
+ // Unit tagged variant: { tag }
189
+ expect(formatHostError({ tag: "Full" })).toBe("Full");
190
+ });
191
+
192
+ test("falls back for non-host-error input", () => {
193
+ expect(formatHostError(new Error("plain"))).toBe("plain");
194
+ expect(formatHostError("string err")).toBe("string err");
195
+ expect(formatHostError({ message: "loose" })).toBe("loose");
196
+ });
197
+ });
198
+ }
@@ -0,0 +1,172 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Higher-level wrappers for the host's feature-support probe.
5
+ *
6
+ * `truApi.system.featureSupported` returns a neverthrow `ResultAsync`;
7
+ * {@link featureSupported} collapses that to a `Result` carrying the host's
8
+ * boolean answer. {@link isChainSupported} is a convenience over the only
9
+ * feature variant the host exposes today (`Chain`).
10
+ *
11
+ * @module
12
+ */
13
+
14
+ import { createLogger } from "@parity/product-sdk-logger";
15
+
16
+ import { type HostError, HostUnavailableError } from "./errors.js";
17
+ import { type Result, err } from "./result.js";
18
+ import { getTruApi, type HexString, mapHostResult } from "./truapi.js";
19
+
20
+ const log = createLogger("host:features");
21
+
22
+ /**
23
+ * A feature the host can be probed for via {@link featureSupported}.
24
+ *
25
+ * The only variant today is `Chain`, carrying the chain's `0x`-prefixed genesis
26
+ * hash. This is a flattened form of truapi's `HostFeatureSupportedRequest`,
27
+ * which nests the hash as `{ tag: "Chain"; value: { genesisHash } }` — we
28
+ * inline `value` as the `HexString` for ergonomics and re-nest it at the call
29
+ * site. New variants surface here as a widening of the union.
30
+ */
31
+ export type Feature = { tag: "Chain"; value: HexString };
32
+
33
+ /**
34
+ * Probe the host for support of a specific feature.
35
+ *
36
+ * Calls `truApi.system.featureSupported`, unwraps the response, and returns the
37
+ * host's boolean answer.
38
+ *
39
+ * @param feature - The feature to probe for.
40
+ * @returns `ok(true)` if the host supports the feature, `ok(false)` otherwise,
41
+ * or `err(HostUnavailableError | HostCallFailedError)`.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { featureSupported } from "@parity/product-sdk-host";
46
+ *
47
+ * const r = await featureSupported({ tag: "Chain", value: genesisHash });
48
+ * if (r.ok && r.value) { ... }
49
+ * ```
50
+ */
51
+ export async function featureSupported(feature: Feature): Promise<Result<boolean, HostError>> {
52
+ const truApi = await getTruApi();
53
+ if (!truApi) {
54
+ return err(new HostUnavailableError("featureSupported: TruAPI unavailable"));
55
+ }
56
+ log.debug("featureSupported", { tag: feature.tag });
57
+
58
+ return mapHostResult(
59
+ truApi.system.featureSupported({ tag: feature.tag, value: { genesisHash: feature.value } }),
60
+ (response) => response.supported,
61
+ "featureSupported failed",
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Convenience probe: is the chain with the given genesis hash supported by the
67
+ * host? Wraps {@link featureSupported} for the `Chain` feature variant.
68
+ *
69
+ * @param genesisHash - The chain's `0x`-prefixed genesis hash.
70
+ * @returns `ok(true)` if the host supports the chain, `ok(false)` otherwise, or
71
+ * `err(HostUnavailableError | HostCallFailedError)`.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { isChainSupported } from "@parity/product-sdk-host";
76
+ *
77
+ * const r = await isChainSupported(genesisHash);
78
+ * if (!r.ok || !r.value) {
79
+ * tellUserChainUnavailable();
80
+ * }
81
+ * ```
82
+ */
83
+ export async function isChainSupported(
84
+ genesisHash: HexString,
85
+ ): Promise<Result<boolean, HostError>> {
86
+ return featureSupported({ tag: "Chain", value: genesisHash });
87
+ }
88
+
89
+ if (import.meta.vitest) {
90
+ const { test, expect, describe, vi } = import.meta.vitest;
91
+
92
+ async function withMockedTruApi<T>(
93
+ bridge: { system?: { featureSupported?: (req: unknown) => unknown } } | null,
94
+ fn: (mod: typeof import("./features.js")) => Promise<T>,
95
+ ): Promise<T> {
96
+ vi.resetModules();
97
+ vi.doMock("./truapi.js", async (importOriginal) => {
98
+ const original = await importOriginal<typeof import("./truapi.js")>();
99
+ return {
100
+ ...original,
101
+ getTruApi: async () => bridge,
102
+ };
103
+ });
104
+ try {
105
+ const mod = await import("./features.js");
106
+ return await fn(mod);
107
+ } finally {
108
+ vi.doUnmock("./truapi.js");
109
+ vi.resetModules();
110
+ }
111
+ }
112
+
113
+ const okBridge = (supported: boolean) => ({
114
+ system: {
115
+ featureSupported: vi.fn().mockReturnValue({
116
+ match: async (onOk: (v: unknown) => unknown) => onOk({ supported }),
117
+ }),
118
+ },
119
+ });
120
+
121
+ describe("featureSupported", () => {
122
+ test("returns err(HostUnavailableError) when TruAPI is unavailable", async () => {
123
+ await withMockedTruApi(null, async (mod) => {
124
+ const result = await mod.featureSupported({ tag: "Chain", value: "0x00" });
125
+ expect(result.ok).toBe(false);
126
+ if (!result.ok) {
127
+ expect(result.error.name).toBe("HostUnavailableError");
128
+ }
129
+ });
130
+ });
131
+
132
+ test("returns ok with the boolean outcome", async () => {
133
+ await withMockedTruApi(okBridge(true), async (mod) => {
134
+ expect(await mod.featureSupported({ tag: "Chain", value: "0x00" })).toEqual({
135
+ ok: true,
136
+ value: true,
137
+ });
138
+ });
139
+ });
140
+
141
+ test("wraps host errors in err(HostCallFailedError) with a diagnostic message", async () => {
142
+ await withMockedTruApi(
143
+ {
144
+ system: {
145
+ featureSupported: vi.fn().mockReturnValue({
146
+ match: async (
147
+ _onOk: (v: unknown) => unknown,
148
+ onErr: (e: unknown) => unknown,
149
+ ) => onErr({ reason: "boom" }),
150
+ }),
151
+ },
152
+ },
153
+ async (mod) => {
154
+ const result = await mod.featureSupported({ tag: "Chain", value: "0x00" });
155
+ expect(result.ok).toBe(false);
156
+ if (!result.ok) {
157
+ expect(result.error.name).toBe("HostCallFailedError");
158
+ expect(result.error.message).toMatch(/featureSupported failed: boom/);
159
+ }
160
+ },
161
+ );
162
+ });
163
+ });
164
+
165
+ describe("isChainSupported", () => {
166
+ test("delegates to featureSupported with the Chain variant", async () => {
167
+ await withMockedTruApi(okBridge(false), async (mod) => {
168
+ expect(await mod.isChainSupported("0x1234")).toEqual({ ok: true, value: false });
169
+ });
170
+ });
171
+ });
172
+ }
package/src/index.ts CHANGED
@@ -32,22 +32,14 @@ export type {
32
32
  } from "./types.js";
33
33
  export { BULLETIN_RPCS, DEFAULT_BULLETIN_ENDPOINT } from "./chains.js";
34
34
 
35
- // TruAPI - re-exports from @novasamatech/host-api-wrapper and @novasamatech/host-api
35
+ // TruAPI - @parity/truapi client accessor + convenience wrappers.
36
36
  export {
37
37
  getTruApi,
38
38
  getPreimageManager,
39
39
  createHostPreimageManager,
40
- getAccountsProvider,
41
40
  requestResourceAllocation,
42
41
  createProofAuthorized,
43
- formatHostError,
44
- // Helpers from @novasamatech/host-api
45
- enumValue,
46
- isEnumVariant,
47
- assertEnumVariant,
48
- unwrapResultOrThrow,
49
- resultOk,
50
- resultErr,
42
+ // Hex helpers
51
43
  toHex,
52
44
  fromHex,
53
45
  } from "./truapi.js";
@@ -55,19 +47,34 @@ export type {
55
47
  TruApi,
56
48
  HexString,
57
49
  PreimageManager,
58
- AccountsProvider,
59
- HostAccount,
60
- ProductAccount,
61
- ContextualAlias,
62
50
  ResultAsync,
63
51
  AllocatableResource,
64
- AllocatableResourceTag,
65
52
  AllocationOutcome,
66
- AllocationOutcomeTag,
67
53
  RemotePermission,
68
- RemotePermissionTag,
69
54
  } from "./truapi.js";
70
55
 
56
+ // Result type + typed host errors (the throw→Result boundary)
57
+ export { ok, err } from "./result.js";
58
+ export type { Result } from "./result.js";
59
+ export {
60
+ HostError,
61
+ HostUnavailableError,
62
+ HostCallFailedError,
63
+ isHostError,
64
+ formatHostError,
65
+ } from "./errors.js";
66
+ export type { HostErrorPayload } from "./errors.js";
67
+
68
+ // Accounts — host wallet accounts, product accounts, Ring VRF, and signers.
69
+ export { getAccountsProvider } from "./accounts.js";
70
+ export type {
71
+ AccountsProvider,
72
+ HostAccount,
73
+ ProductAccount,
74
+ ContextualAlias,
75
+ RingLocation,
76
+ } from "./accounts.js";
77
+
71
78
  // Higher-level permission wrappers
72
79
  export { requestPermission, requestDevicePermission } from "./permissions.js";
73
80
  export type { DevicePermissionKind, RemotePermissionItem } from "./permissions.js";
@@ -80,7 +87,7 @@ export type { ThemeMode, ThemeName, ThemeProvider, ThemeVariant } from "./theme.
80
87
  export { deriveEntropy } from "./entropy.js";
81
88
 
82
89
  // Chat
83
- export { getChatManager, matchChatCustomRenderers } from "./chat.js";
90
+ export { getChatManager } from "./chat.js";
84
91
  export type {
85
92
  ChatManager,
86
93
  ChatMessageContent,
@@ -88,18 +95,36 @@ export type {
88
95
  ChatRoom,
89
96
  ChatRoomRegistrationResult,
90
97
  ChatBotRegistrationResult,
91
- ChatCustomMessageRenderer,
92
- ChatCustomMessageRendererParams,
93
98
  } from "./chat.js";
94
99
 
95
100
  // Payments (RFC-0006)
96
101
  export { getPaymentManager } from "./payments.js";
97
- export type { PaymentManager, PaymentBalance, PaymentStatus, TopUpSource } from "./payments.js";
102
+ export type { PaymentManager } from "./payments.js";
103
+ export type {
104
+ HostPaymentBalanceSubscribeItem,
105
+ HostPaymentStatusSubscribeItem,
106
+ PaymentTopUpSource,
107
+ } from "@parity/truapi";
98
108
 
99
109
  // Notifications
100
- export { getNotificationManager, PushNotificationError } from "./notifications.js";
110
+ export { getNotificationManager } from "./notifications.js";
101
111
  export type {
102
112
  NotificationManager,
103
113
  NotificationId,
104
114
  PushNotificationInput,
115
+ PushNotificationError,
105
116
  } from "./notifications.js";
117
+
118
+ // Deep-link navigation
119
+ export { navigateTo } from "./navigation.js";
120
+
121
+ // Feature / chain support probes
122
+ export { featureSupported, isChainSupported } from "./features.js";
123
+ export type { Feature } from "./features.js";
124
+
125
+ // Chain spec lookups
126
+ export { getChainSpec } from "./chain-spec.js";
127
+ export type { ChainSpec, ChainProperties } from "./chain-spec.js";
128
+
129
+ // Transaction broadcast lifecycle
130
+ export { broadcastTransaction, stopTransaction } from "./chain-transaction.js";