@parity/product-sdk-host 0.11.0 → 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
+ }
package/src/features.ts CHANGED
@@ -3,61 +3,62 @@
3
3
  /**
4
4
  * Higher-level wrappers for the host's feature-support probe.
5
5
  *
6
- * `hostApi.featureSupported` is reachable via {@link getTruApi}, but consumers
7
- * have to wrap the feature in the versioned envelope (`enumValue("v1", ...)`)
8
- * and unwrap the neverthrow `ResultAsync` themselves. {@link featureSupported}
9
- * collapses that to a throw-on-error Promise; {@link isChainSupported} is a
10
- * convenience over the only feature variant the host exposes today (`Chain`).
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`).
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, type HexString } from "./truapi.js";
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";
18
19
 
19
20
  const log = createLogger("host:features");
20
21
 
21
22
  /**
22
23
  * A feature the host can be probed for via {@link featureSupported}.
23
24
  *
24
- * As of `host-api` v0.8 the only variant is `Chain`, carrying the chain's
25
- * `0x`-prefixed genesis hash. Modeled locally (rather than derived from an
26
- * upstream codec) because the protocol exposes the feature only inline; new
27
- * variants surface here as a widening of the union.
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.
28
30
  */
29
31
  export type Feature = { tag: "Chain"; value: HexString };
30
32
 
31
33
  /**
32
34
  * Probe the host for support of a specific feature.
33
35
  *
34
- * Builds the `v1` envelope, calls `hostApi.featureSupported`, unwraps the
35
- * response, and returns the host's boolean answer.
36
+ * Calls `truApi.system.featureSupported`, unwraps the response, and returns the
37
+ * host's boolean answer.
36
38
  *
37
39
  * @param feature - The feature to probe for.
38
- * @returns `true` if the host supports the feature, `false` otherwise.
39
- * @throws If the host is unavailable or the probe fails (`GenericError`).
40
+ * @returns `ok(true)` if the host supports the feature, `ok(false)` otherwise,
41
+ * or `err(HostUnavailableError | HostCallFailedError)`.
40
42
  *
41
43
  * @example
42
44
  * ```ts
43
45
  * import { featureSupported } from "@parity/product-sdk-host";
44
46
  *
45
- * const ok = await featureSupported({ tag: "Chain", value: genesisHash });
47
+ * const r = await featureSupported({ tag: "Chain", value: genesisHash });
48
+ * if (r.ok && r.value) { ... }
46
49
  * ```
47
50
  */
48
- export async function featureSupported(feature: Feature): Promise<boolean> {
51
+ export async function featureSupported(feature: Feature): Promise<Result<boolean, HostError>> {
49
52
  const truApi = await getTruApi();
50
53
  if (!truApi) {
51
- throw new Error("featureSupported: TruAPI unavailable");
54
+ return err(new HostUnavailableError("featureSupported: TruAPI unavailable"));
52
55
  }
53
56
  log.debug("featureSupported", { tag: feature.tag });
54
57
 
55
- // `.match()` because the host returns a neverthrow ResultAsync, not a Promise.
56
- return await truApi.featureSupported(enumValue("v1", feature)).match(
57
- (envelope: { tag: "v1"; value: boolean }) => envelope.value,
58
- (err: unknown) => {
59
- throw new Error(`featureSupported failed: ${formatHostError(err)}`, { cause: err });
60
- },
58
+ return mapHostResult(
59
+ truApi.system.featureSupported({ tag: feature.tag, value: { genesisHash: feature.value } }),
60
+ (response) => response.supported,
61
+ "featureSupported failed",
61
62
  );
62
63
  }
63
64
 
@@ -66,27 +67,30 @@ export async function featureSupported(feature: Feature): Promise<boolean> {
66
67
  * host? Wraps {@link featureSupported} for the `Chain` feature variant.
67
68
  *
68
69
  * @param genesisHash - The chain's `0x`-prefixed genesis hash.
69
- * @returns `true` if the host supports the chain, `false` otherwise.
70
- * @throws If the host is unavailable or the probe fails.
70
+ * @returns `ok(true)` if the host supports the chain, `ok(false)` otherwise, or
71
+ * `err(HostUnavailableError | HostCallFailedError)`.
71
72
  *
72
73
  * @example
73
74
  * ```ts
74
75
  * import { isChainSupported } from "@parity/product-sdk-host";
75
76
  *
76
- * if (!(await isChainSupported(genesisHash))) {
77
+ * const r = await isChainSupported(genesisHash);
78
+ * if (!r.ok || !r.value) {
77
79
  * tellUserChainUnavailable();
78
80
  * }
79
81
  * ```
80
82
  */
81
- export async function isChainSupported(genesisHash: HexString): Promise<boolean> {
82
- return await featureSupported({ tag: "Chain", value: genesisHash });
83
+ export async function isChainSupported(
84
+ genesisHash: HexString,
85
+ ): Promise<Result<boolean, HostError>> {
86
+ return featureSupported({ tag: "Chain", value: genesisHash });
83
87
  }
84
88
 
85
89
  if (import.meta.vitest) {
86
90
  const { test, expect, describe, vi } = import.meta.vitest;
87
91
 
88
92
  async function withMockedTruApi<T>(
89
- bridge: { featureSupported?: (req: unknown) => unknown } | null,
93
+ bridge: { system?: { featureSupported?: (req: unknown) => unknown } } | null,
90
94
  fn: (mod: typeof import("./features.js")) => Promise<T>,
91
95
  ): Promise<T> {
92
96
  vi.resetModules();
@@ -95,7 +99,6 @@ if (import.meta.vitest) {
95
99
  return {
96
100
  ...original,
97
101
  getTruApi: async () => bridge,
98
- enumValue: (version: string, value: unknown) => ({ tag: version, value }),
99
102
  };
100
103
  });
101
104
  try {
@@ -107,45 +110,53 @@ if (import.meta.vitest) {
107
110
  }
108
111
  }
109
112
 
110
- const okBridge = (value: boolean) => ({
111
- featureSupported: vi.fn().mockReturnValue({
112
- match: async (onOk: (v: unknown) => unknown) => onOk({ tag: "v1", value }),
113
- }),
113
+ const okBridge = (supported: boolean) => ({
114
+ system: {
115
+ featureSupported: vi.fn().mockReturnValue({
116
+ match: async (onOk: (v: unknown) => unknown) => onOk({ supported }),
117
+ }),
118
+ },
114
119
  });
115
120
 
116
121
  describe("featureSupported", () => {
117
- test("throws when TruAPI is unavailable", async () => {
122
+ test("returns err(HostUnavailableError) when TruAPI is unavailable", async () => {
118
123
  await withMockedTruApi(null, async (mod) => {
119
- await expect(mod.featureSupported({ tag: "Chain", value: "0x00" })).rejects.toThrow(
120
- /TruAPI unavailable/,
121
- );
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
+ }
122
129
  });
123
130
  });
124
131
 
125
- test("unwraps the v1 boolean outcome", async () => {
132
+ test("returns ok with the boolean outcome", async () => {
126
133
  await withMockedTruApi(okBridge(true), async (mod) => {
127
- expect(await mod.featureSupported({ tag: "Chain", value: "0x00" })).toBe(true);
134
+ expect(await mod.featureSupported({ tag: "Chain", value: "0x00" })).toEqual({
135
+ ok: true,
136
+ value: true,
137
+ });
128
138
  });
129
139
  });
130
140
 
131
- test("wraps host errors with a diagnostic message", async () => {
141
+ test("wraps host errors in err(HostCallFailedError) with a diagnostic message", async () => {
132
142
  await withMockedTruApi(
133
143
  {
134
- featureSupported: vi.fn().mockReturnValue({
135
- match: async (
136
- _onOk: (v: unknown) => unknown,
137
- onErr: (e: unknown) => unknown,
138
- ) =>
139
- onErr({
140
- tag: "v1",
141
- value: { name: "GenericError", message: "boom" },
142
- }),
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
+ },
144
152
  },
145
153
  async (mod) => {
146
- await expect(
147
- mod.featureSupported({ tag: "Chain", value: "0x00" }),
148
- ).rejects.toThrow(/featureSupported failed: GenericError: boom/);
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
+ }
149
160
  },
150
161
  );
151
162
  });
@@ -154,7 +165,7 @@ if (import.meta.vitest) {
154
165
  describe("isChainSupported", () => {
155
166
  test("delegates to featureSupported with the Chain variant", async () => {
156
167
  await withMockedTruApi(okBridge(false), async (mod) => {
157
- expect(await mod.isChainSupported("0x1234")).toBe(false);
168
+ expect(await mod.isChainSupported("0x1234")).toEqual({ ok: true, value: false });
158
169
  });
159
170
  });
160
171
  });
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,20 +95,24 @@ 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";
106
117
 
107
118
  // Deep-link navigation