@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/truapi.ts CHANGED
@@ -3,310 +3,190 @@
3
3
  /**
4
4
  * TruAPI - the protocol for communicating between apps and the Polkadot host container.
5
5
  *
6
- * This module centralizes access to @novasamatech/host-api-wrapper and @novasamatech/host-api,
7
- * allowing other @parity/product-sdk-* packages to import from here rather than depending
8
- * directly on novasama packages.
6
+ * This module centralizes access to the in-house `@parity/truapi` client,
7
+ * allowing other `@parity/product-sdk-*` packages to import from here rather
8
+ * than depending directly on the protocol package. The client is built and
9
+ * cached by {@link module:transport}; this module adds the accessor plus the
10
+ * two helpers the convenience wrappers fold truapi's `ResultAsync` through —
11
+ * {@link mapHostResult} (returns a `Result`, used by the public operations) and
12
+ * {@link unwrapHostResult} (throws, used by the adapter-object methods).
9
13
  *
10
14
  * @module
11
15
  */
12
16
 
13
- import { createLogger } from "@parity/product-sdk-logger";
14
-
15
- import { enumValue } from "@novasamatech/host-api";
17
+ import { scale } from "@parity/truapi";
16
18
  import type {
17
- AllocatableResource as AllocatableResourceCodec,
18
- AllocationOutcome as AllocationOutcomeCodec,
19
- CodecType,
20
- RemotePermission as RemotePermissionCodec,
21
- } from "@novasamatech/host-api";
22
- import type { createAccountsProvider, preimageManager } from "@novasamatech/host-api-wrapper";
19
+ AllocatableResource,
20
+ AllocationOutcome,
21
+ HexString,
22
+ RemotePermission,
23
+ TrUApiClient,
24
+ } from "@parity/truapi";
25
+ import { createLogger } from "@parity/product-sdk-logger";
23
26
 
24
- import { isInsideContainer } from "./container.js";
25
- import type { Statement, StatementProof } from "./types.js";
27
+ import {
28
+ type HostError,
29
+ type HostErrorPayload,
30
+ HostCallFailedError,
31
+ HostUnavailableError,
32
+ formatHostError,
33
+ } from "./errors.js";
34
+ import { type Result, err, ok } from "./result.js";
35
+ import { getClient, subscribeWithInterrupt } from "./transport.js";
36
+ import type { HostSubscription, Statement, StatementProof } from "./types.js";
26
37
 
27
38
  const log = createLogger("host");
28
39
 
29
40
  /**
30
- * Extract a human-readable message from a host-side error. Hosts wrap
31
- * errors in versioned envelopes (`{ tag: "v1", value: CodecError }`); this
32
- * helper unwraps the envelope and renders the inner error's `name`/`message`
33
- * so callers see the host's actual diagnostic instead of `"[object Object]"`
34
- * (from `String(err)`) or a JSON-stringified envelope.
35
- *
36
- * Exported for the higher-level wrappers (`requestPermission`,
37
- * `deriveEntropy`, etc.) that build their `throw new Error(...)` messages.
41
+ * Await a host `ResultAsync`, returning its Ok value or throwing a diagnostic
42
+ * `Error` built from the host's error payload (preserved as `cause`).
43
+ *
44
+ * This is the *throwing* helper, retained for the methods of the adapter objects
45
+ * returned by the feature-detection getters (`PreimageManager.submit`,
46
+ * `HostLocalStorage.read`, `AccountsProvider` signing, …). Those objects often
47
+ * implement external interfaces (e.g. polkadot-api's `JsonRpcProvider`) whose
48
+ * method signatures can't carry a {@link Result}, so they keep the
49
+ * throw convention. The flat public operations use {@link mapHostResult} instead.
38
50
  */
39
- export function formatHostError(err: unknown): string {
40
- // Single-level unwrap only; nested envelopes fall through to JSON.stringify.
41
- const inner = isVersionedEnvelope(err) ? err.value : err;
42
-
43
- if (inner instanceof Error) return inner.message;
44
- if (typeof inner === "string") return inner;
45
- if (
46
- inner != null &&
47
- typeof inner === "object" &&
48
- "message" in inner &&
49
- typeof (inner as { message: unknown }).message === "string"
50
- ) {
51
- const named = inner as { name?: unknown; message: string };
52
- return typeof named.name === "string" ? `${named.name}: ${named.message}` : named.message;
53
- }
54
- try {
55
- return JSON.stringify(inner);
56
- } catch {
57
- return String(inner);
58
- }
51
+ export function unwrapHostResult<T, E>(result: ResultAsync<T, E>, label: string): Promise<T> {
52
+ return result.match(
53
+ (value) => value,
54
+ (error: E) => {
55
+ throw new Error(`${label}: ${formatHostError(error)}`, { cause: error });
56
+ },
57
+ );
59
58
  }
60
59
 
61
- function isVersionedEnvelope(value: unknown): value is { tag: string; value: unknown } {
62
- return (
63
- value != null &&
64
- typeof value === "object" &&
65
- "tag" in value &&
66
- "value" in value &&
67
- typeof (value as { tag: unknown }).tag === "string"
60
+ /**
61
+ * Await a host `ResultAsync` and fold it into a tagged {@link Result}: maps the
62
+ * Ok value through `map`, or wraps the host error payload in a
63
+ * {@link HostCallFailedError} on the `err` channel. This is the non-throwing
64
+ * boundary the flat public host operations (`requestPermission`, `deriveEntropy`,
65
+ * `requestResourceAllocation`, …) return through.
66
+ */
67
+ export function mapHostResult<T, U>(
68
+ result: ResultAsync<T, HostErrorPayload>,
69
+ map: (value: T) => U,
70
+ label: string,
71
+ ): Promise<Result<U, HostError>> {
72
+ return result.match(
73
+ (value) => ok(map(value)),
74
+ (error) => err(new HostCallFailedError(label, error)),
68
75
  );
69
76
  }
70
77
 
71
78
  // ─────────────────────────────────────────────────────────────────────────────
72
- // Helpers from @novasamatech/host-api (re-exported from @novasamatech/scale)
79
+ // Hex helpers
73
80
  // ─────────────────────────────────────────────────────────────────────────────
74
81
 
75
- export {
76
- /**
77
- * Construct an enum variant for TruAPI calls.
78
- *
79
- * @example
80
- * ```ts
81
- * import { enumValue, getTruApi } from "@parity/product-sdk-host";
82
- *
83
- * const truApi = await getTruApi();
84
- * if (truApi) {
85
- * await truApi.permission([enumValue("ChainSubmit")]);
86
- * }
87
- * ```
88
- */
89
- enumValue,
90
- /**
91
- * Check if a value is a specific enum variant.
92
- */
93
- isEnumVariant,
94
- /**
95
- * Assert that a value is a specific enum variant, throwing if not.
96
- */
97
- assertEnumVariant,
98
- /**
99
- * Unwrap a Result, throwing on error.
100
- */
101
- unwrapResultOrThrow,
102
- /**
103
- * Create an Ok result.
104
- */
105
- resultOk,
106
- /**
107
- * Create an Err result.
108
- */
109
- resultErr,
110
- /**
111
- * Convert bytes to hex string.
112
- */
113
- toHex,
114
- /**
115
- * Convert hex string to bytes.
116
- */
117
- fromHex,
118
- } from "@novasamatech/host-api";
119
-
120
- /** A `0x`-prefixed hex string (the template literal type ``\`0x${string}\``) used by the host API surface for raw byte payloads. Re-exported from `@novasamatech/host-api` so consumers bridging between host APIs and SDK code can reach the host-side type without an additional dependency. */
121
- export type { HexString } from "@novasamatech/host-api";
82
+ /** Convert bytes to a `0x`-prefixed lower-case hex string. */
83
+ export function toHex(bytes: Uint8Array): HexString {
84
+ return scale.bytesToHex(bytes);
85
+ }
86
+
87
+ /** Convert a hex string (with or without `0x`) to bytes. */
88
+ export function fromHex(hex: string): Uint8Array {
89
+ return scale.hexToBytes(hex);
90
+ }
91
+
92
+ /** A `0x`-prefixed hex string used by the host API surface for raw byte payloads. */
93
+ export type { HexString };
122
94
 
123
95
  // ─────────────────────────────────────────────────────────────────────────────
124
96
  // TruAPI accessor
125
97
  // ─────────────────────────────────────────────────────────────────────────────
126
98
 
127
99
  /**
128
- * The TruApi type - provides low-level methods for communicating with the host.
129
- *
130
- * Methods include:
131
- * - `navigateTo(url)` - Navigate to a URL within the host
132
- * - `permission(permissions)` - Request permissions from the host
133
- * - `localStorageRead/Write/Clear` - Host-backed storage
134
- * - `sign(payload)` - Request transaction signing
135
- * - `deriveEntropy(context)` - Derive deterministic entropy
136
- * - `themeSubscribe()` - Subscribe to host theme changes
137
- * - And many more...
138
- *
139
- * Type identical to `hostApi` from `@novasamatech/host-api-wrapper` so that
140
- * `truApi.X(...)` calls keep their full inference (return types, method
141
- * names, parameter shapes) instead of decaying to `any`.
142
- */
143
- export type TruApi = typeof import("@novasamatech/host-api-wrapper").hostApi;
144
-
145
- /** Cached TruApi instance */
146
- let cachedTruApi: TruApi | null = null;
147
-
148
- /**
149
- * Get the TruAPI instance for direct low-level access.
150
- *
151
- * Returns the `hostApi` object from `@novasamatech/host-api-wrapper` which provides
152
- * methods for communicating directly with the host container. Returns `null`
153
- * when running outside a container or when the SDK is unavailable.
154
- *
155
- * For most use cases, prefer the higher-level functions like `getHostLocalStorage()`,
156
- * `getHostProvider()`, etc. Use this when you need direct access to host methods
157
- * like `navigateTo()`, `permission()`, or `deriveEntropy()`.
100
+ * The TruApi client namespaced access to every host protocol domain
101
+ * (`permissions`, `entropy`, `signing`, `statementStore`, `system`,
102
+ * `localStorage`, …). Identical to `TrUApiClient` from `@parity/truapi`.
158
103
  *
159
104
  * @example
160
105
  * ```ts
161
- * import { getTruApi, enumValue } from "@parity/product-sdk-host";
162
- *
163
106
  * const truApi = await getTruApi();
164
107
  * if (truApi) {
165
- * // Request permission
166
- * const result = await truApi.permission([enumValue("ChainSubmit")]);
167
- *
168
- * // Navigate to a URL
169
- * await truApi.navigateTo("polkadot://settings");
170
- *
171
- * // Subscribe to theme changes
172
- * const sub = truApi.themeSubscribe(undefined, (theme) => {
173
- * console.log("Theme changed:", theme);
108
+ * await truApi.permissions.requestRemotePermission({
109
+ * permission: { tag: "ChainSubmit", value: undefined },
174
110
  * });
111
+ * await truApi.system.navigateTo({ url: "polkadot://settings" });
175
112
  * }
176
113
  * ```
177
- *
178
- * @returns The TruAPI instance, or `null` if unavailable.
179
114
  */
180
- export async function getTruApi(): Promise<TruApi | null> {
181
- if (cachedTruApi) return cachedTruApi;
182
-
183
- try {
184
- const sdk = await import("@novasamatech/host-api-wrapper");
185
- cachedTruApi = sdk.hostApi;
186
- log.debug("TruAPI loaded");
187
- return cachedTruApi;
188
- } catch {
189
- log.debug("TruAPI unavailable (not in container or SDK not installed)");
190
- return null;
191
- }
192
- }
115
+ export type TruApi = TrUApiClient;
193
116
 
194
117
  /**
195
- * Get the preimage manager for bulletin chain operations.
118
+ * Get the TruAPI client for direct low-level access to host protocol domains.
196
119
  *
197
- * The preimage manager handles uploading and looking up preimages (arbitrary data)
198
- * on the bulletin chain through the host's optimized path.
120
+ * Returns the cached `@parity/truapi` client once the host transport is built
121
+ * and the handshake has run, or `null` when running outside a container.
199
122
  *
200
- * @returns The preimage manager, or `null` if unavailable.
123
+ * For most use cases, prefer the higher-level functions like
124
+ * {@link requestPermission}, {@link deriveEntropy}, or `getHostLocalStorage()`.
201
125
  *
202
- * @example
203
- * ```ts
204
- * import { getPreimageManager } from "@parity/product-sdk-host";
205
- *
206
- * const manager = await getPreimageManager();
207
- * if (manager) {
208
- * // Submit a preimage
209
- * const key = await manager.submit(new Uint8Array([1, 2, 3]));
210
- *
211
- * // Look up a preimage
212
- * const sub = manager.lookup(key, (data) => {
213
- * if (data) console.log("Found:", data);
214
- * });
215
- * }
216
- * ```
126
+ * @returns The TruAPI client, or `null` if unavailable.
217
127
  */
218
- export async function getPreimageManager(): Promise<PreimageManager | null> {
219
- try {
220
- const sdk = await import("@novasamatech/host-api-wrapper");
221
- return sdk.preimageManager;
222
- } catch (err) {
223
- log.debug("getPreimageManager unavailable", err);
224
- return null;
225
- }
128
+ export async function getTruApi(): Promise<TruApi | null> {
129
+ return getClient();
226
130
  }
227
131
 
228
132
  /**
229
- * Preimage manager handle for bulletin chain operations. `lookup` returns a
230
- * `Subscription<void>` (`unsubscribe` + `onInterrupt`); `submit` returns a
231
- * `0x`-prefixed hex preimage key.
232
- *
233
- * Type identical to `preimageManager` from `@novasamatech/host-api-wrapper`.
133
+ * Preimage manager handle for bulletin chain operations, backed by
134
+ * `truApi.preimage.*`. `lookup` opens a {@link HostSubscription} (`unsubscribe`
135
+ * + `onInterrupt`) that delivers the preimage bytes — or `null` until the host
136
+ * finds them; `submit` uploads a preimage and resolves to its `0x`-prefixed hex
137
+ * key.
234
138
  */
235
- export type PreimageManager = typeof preimageManager;
139
+ export interface PreimageManager {
140
+ lookup(key: HexString, callback: (preimage: Uint8Array | null) => void): HostSubscription;
141
+ submit(value: Uint8Array): Promise<HexString>;
142
+ }
143
+
144
+ /** Build a {@link PreimageManager} over a TruAPI client's `preimage` domain. */
145
+ function adaptPreimageManager(client: TrUApiClient): PreimageManager {
146
+ const preimage = client.preimage;
147
+ return {
148
+ lookup(key, callback) {
149
+ return subscribeWithInterrupt(preimage.lookupSubscribe({ request: { key } }), (item) =>
150
+ callback(item.value !== undefined ? fromHex(item.value) : null),
151
+ );
152
+ },
153
+ submit(value) {
154
+ return unwrapHostResult(preimage.submit(toHex(value)), "preimage submit failed");
155
+ },
156
+ };
157
+ }
236
158
 
237
159
  /**
238
- * Construct a fresh `PreimageManager` instance with an optional custom
239
- * transport. Use this when you need a non-default transport; otherwise
240
- * prefer {@link getPreimageManager}, which returns the shared singleton.
241
- *
242
- * Mirrors `createPreimageManager` from `@novasamatech/host-api-wrapper`.
160
+ * Get the preimage manager for bulletin chain operations.
243
161
  *
244
- * @param transport - Optional transport; defaults to the sandbox transport.
245
- * @returns A new `PreimageManager` instance, or `null` if unavailable.
162
+ * @returns The preimage manager, or `null` if unavailable (outside a container).
246
163
  */
247
- export async function createHostPreimageManager(
248
- transport?: import("@novasamatech/host-api").Transport,
249
- ): Promise<PreimageManager | null> {
250
- if (!(await isInsideContainer())) return null;
251
- try {
252
- const sdk = await import("@novasamatech/host-api-wrapper");
253
- return sdk.createPreimageManager(transport);
254
- } catch (err) {
255
- log.debug("createHostPreimageManager unavailable", err);
256
- return null;
257
- }
164
+ export async function getPreimageManager(): Promise<PreimageManager | null> {
165
+ const client = await getClient();
166
+ return client ? adaptPreimageManager(client) : null;
258
167
  }
259
168
 
260
169
  /**
261
- * Get the accounts provider for managing host accounts.
170
+ * Construct a `PreimageManager`. Retained for API compatibility; with the single
171
+ * cached TruAPI client this is equivalent to {@link getPreimageManager}.
262
172
  *
263
- * @returns The accounts provider, or `null` if unavailable.
173
+ * @returns A `PreimageManager` instance, or `null` if unavailable.
264
174
  */
265
- export async function getAccountsProvider(): Promise<AccountsProvider | null> {
266
- try {
267
- const sdk = await import("@novasamatech/host-api-wrapper");
268
- return sdk.createAccountsProvider();
269
- } catch (err) {
270
- log.debug("getAccountsProvider unavailable", err);
271
- return null;
272
- }
175
+ export async function createHostPreimageManager(): Promise<PreimageManager | null> {
176
+ return getPreimageManager();
273
177
  }
274
178
 
275
179
  // ─────────────────────────────────────────────────────────────────────────────
276
180
  // Resource allocation
277
181
  // ─────────────────────────────────────────────────────────────────────────────
278
182
 
279
- /**
280
- * Resource types requestable via {@link requestResourceAllocation}.
281
- * Derived from the upstream codec so variant renames surface as compile
282
- * errors, not runtime failures.
283
- */
284
- export type AllocatableResource = CodecType<typeof AllocatableResourceCodec>;
285
-
286
- /** Tag-only view of {@link AllocatableResource} for places that just need the variant name. */
287
- export type AllocatableResourceTag = AllocatableResource["tag"];
288
-
289
- /**
290
- * Per-resource outcome from {@link requestResourceAllocation}.
291
- * The host strips secret payloads from `Allocated` before returning, so
292
- * `value` is always `undefined` on the product side.
293
- */
294
- export type AllocationOutcome = CodecType<typeof AllocationOutcomeCodec>;
295
-
296
- /** Tag-only view of {@link AllocationOutcome} (`"Allocated" | "Rejected" | "NotAvailable"`). */
297
- export type AllocationOutcomeTag = AllocationOutcome["tag"];
298
-
299
- /**
300
- * Remote permission the dapp can ask the host to grant via
301
- * {@link requestPermission}.
302
- *
303
- * Derived from the upstream codec so variant renames surface as compile
304
- * errors, not runtime failures.
305
- */
306
- export type RemotePermission = CodecType<typeof RemotePermissionCodec>;
307
-
308
- /** Tag-only view of {@link RemotePermission}. */
309
- export type RemotePermissionTag = RemotePermission["tag"];
183
+ // Resource-allocation / permission types, re-exported verbatim from
184
+ // `@parity/truapi` (imported above for the local signatures):
185
+ // - `AllocatableResource` resource types requestable via `requestResourceAllocation`.
186
+ // - `AllocationOutcome` — per-resource outcome, the string union
187
+ // `"Allocated" | "Rejected" | "NotAvailable"` (RFC-10).
188
+ // - `RemotePermission` permission the dapp asks the host to grant via `requestPermission`.
189
+ export type { AllocatableResource, AllocationOutcome, RemotePermission };
310
190
 
311
191
  /**
312
192
  * Request the host to pre-allocate one or more resource allowances.
@@ -315,34 +195,30 @@ export type RemotePermissionTag = RemotePermission["tag"];
315
195
  * granted allowance don't re-prompt.
316
196
  *
317
197
  * @param resources - Resources to request.
318
- * @returns Per-resource outcomes in the same order as `resources`.
319
- * @throws If the host is unavailable or the request fails.
198
+ * @returns `ok` with per-resource outcomes in the same order as `resources`, or
199
+ * `err(HostUnavailableError | HostCallFailedError)`.
320
200
  *
321
201
  * @example
322
202
  * ```ts
323
- * const outcomes = await requestResourceAllocation([
203
+ * const r = await requestResourceAllocation([
324
204
  * { tag: "BulletinAllowance", value: undefined },
325
205
  * ]);
326
- * if (outcomes[0].tag === "Allocated") { ... }
206
+ * if (r.ok && r.value[0] === "Allocated") { ... }
327
207
  * ```
328
208
  */
329
209
  export async function requestResourceAllocation(
330
210
  resources: AllocatableResource[],
331
- ): Promise<AllocationOutcome[]> {
211
+ ): Promise<Result<AllocationOutcome[], HostError>> {
332
212
  const truApi = await getTruApi();
333
213
  if (!truApi) {
334
- throw new Error("requestResourceAllocation: TruAPI unavailable");
214
+ return err(new HostUnavailableError("requestResourceAllocation: TruAPI unavailable"));
335
215
  }
336
216
  log.debug("requestResourceAllocation", { resources: resources.map((r) => r.tag) });
337
217
 
338
- // `.match()` because the host returns a neverthrow ResultAsync, not a Promise.
339
- return await truApi.requestResourceAllocation(enumValue("v1", resources)).match(
340
- (envelope: { tag: "v1"; value: AllocationOutcome[] }) => envelope.value,
341
- (err: unknown) => {
342
- throw new Error(`requestResourceAllocation failed: ${formatHostError(err)}`, {
343
- cause: err,
344
- });
345
- },
218
+ return mapHostResult(
219
+ truApi.resourceAllocation.request({ resources }),
220
+ (response) => response.outcomes,
221
+ "requestResourceAllocation failed",
346
222
  );
347
223
  }
348
224
 
@@ -351,105 +227,33 @@ export async function requestResourceAllocation(
351
227
  // ─────────────────────────────────────────────────────────────────────────────
352
228
 
353
229
  /**
354
- * Have the host sign a Statement using an allowance-bearing account it
355
- * picks internally — RFC-10 §"Statement Store allowance".
356
- *
357
- * The product passes only the Statement payload; the host chooses the
358
- * `//allowance//statement-store//{productId}` account that holds SSS
359
- * allowance and signs with it. Allowance is provisioned implicitly on
360
- * first use if the host hasn't already pre-allocated via
361
- * {@link requestResourceAllocation}; products never see the signing
362
- * account or its key material.
230
+ * Have the host sign a Statement using the product's allowance-bearing account,
231
+ * which it picks internally — RFC-10 §"Statement Store allowance". No per-call
232
+ * account id is needed (this is the sponsored-submission path).
363
233
  *
364
- * Pairs with {@link getStatementStore}'s `submit`: call this to obtain
365
- * a proof, attach it to the Statement, and submit the result.
234
+ * Pairs with {@link getStatementStore}'s `submit`: call this to obtain a proof,
235
+ * attach it to the Statement, and submit the result.
366
236
  *
367
237
  * @param statement - The Statement to be signed.
368
- * @returns The proof to attach before submitting.
369
- * @throws If the host is unavailable or the host-side signing fails.
370
- *
371
- * @example
372
- * ```ts
373
- * import { createProofAuthorized, getStatementStore } from "@parity/product-sdk-host";
374
- *
375
- * const statement = {
376
- * proof: undefined,
377
- * decryptionKey: undefined,
378
- * expiry: undefined,
379
- * channel: undefined,
380
- * topics: [],
381
- * data: payload,
382
- * };
383
- * const proof = await createProofAuthorized(statement);
384
- * const store = await getStatementStore();
385
- * await store?.submit({ ...statement, proof });
386
- * ```
387
- *
388
- * @remarks
389
- * RFC-10 introduces this as a new, strictly additive TruAPI call. The
390
- * pre-existing `HostStatementStore.createProof(accountId, statement)`
391
- * surface stays available for products that own a non-allowance signing
392
- * account; this wrapper is the sponsored-submission path.
238
+ * @returns `ok` with the proof to attach before submitting, or
239
+ * `err(HostUnavailableError | HostCallFailedError)`.
393
240
  */
394
- export async function createProofAuthorized(statement: Statement): Promise<StatementProof> {
241
+ export async function createProofAuthorized(
242
+ statement: Statement,
243
+ ): Promise<Result<StatementProof, HostError>> {
395
244
  const truApi = await getTruApi();
396
245
  if (!truApi) {
397
- throw new Error("createProofAuthorized: TruAPI unavailable");
246
+ return err(new HostUnavailableError("createProofAuthorized: TruAPI unavailable"));
398
247
  }
399
- log.debug("createProofAuthorized", {
400
- topics: statement.topics.length,
401
- dataLen: statement.data?.length ?? 0,
402
- });
248
+ log.debug("createProofAuthorized", { topics: statement.topics.length });
403
249
 
404
- // `.match()` because the host returns a neverthrow ResultAsync, not a Promise.
405
- return await truApi.statementStoreCreateProofAuthorized(enumValue("v1", statement)).match(
406
- (envelope: { tag: "v1"; value: StatementProof }) => envelope.value,
407
- (err: unknown) => {
408
- throw new Error(`createProofAuthorized failed: ${formatHostError(err)}`, {
409
- cause: err,
410
- });
411
- },
250
+ return mapHostResult(
251
+ truApi.statementStore.createProofAuthorized(statement),
252
+ (response) => response.proof,
253
+ "createProofAuthorized failed",
412
254
  );
413
255
  }
414
256
 
415
- /**
416
- * One of the user's existing wallet accounts, surfaced through the host and
417
- * identified by its public key and an optional name. Contrast with
418
- * {@link ProductAccount}, which is also user-controlled but derived by the
419
- * host for a specific app rather than picked from the user's existing keys.
420
- */
421
- export interface HostAccount {
422
- publicKey: Uint8Array;
423
- name?: string;
424
- }
425
-
426
- /**
427
- * A product account — an app-scoped derived account managed by the host wallet.
428
- *
429
- * The host derives a unique keypair for each app (identified by `dotNsIdentifier`)
430
- * so apps get their own account that the user controls but is scoped to the app.
431
- */
432
- export interface ProductAccount {
433
- /** App identifier (e.g., "mark3t.dot"). */
434
- dotNsIdentifier: string;
435
- /** Derivation index within the app scope. Default: 0 */
436
- derivationIndex: number;
437
- /** Raw public key (32 bytes). */
438
- publicKey: Uint8Array;
439
- }
440
-
441
- /**
442
- * A contextual alias obtained from Ring VRF.
443
- *
444
- * Proves account membership in a ring without revealing which account.
445
- */
446
- export interface ContextualAlias {
447
- /** Ring context (32 bytes). */
448
- context: Uint8Array;
449
- /** The Ring VRF alias bytes. */
450
- alias: Uint8Array;
451
- }
452
-
453
257
  /**
454
258
  * Neverthrow-style ResultAsync returned by product-sdk methods.
455
259
  *
@@ -459,18 +263,6 @@ export interface ResultAsync<T, E> {
459
263
  match: <A, B = A>(ok: (t: T) => A, err: (e: E) => B) => Promise<A | B>;
460
264
  }
461
265
 
462
- /**
463
- * Accounts provider handle from `@novasamatech/host-api-wrapper`. Surfaces the
464
- * full upstream API - host wallet accounts, app-scoped product accounts,
465
- * Ring VRF, user identity (`getUserId`, `requestLogin`), and connection
466
- * status subscription.
467
- *
468
- * Type identical to `createAccountsProvider()` from
469
- * `@novasamatech/host-api-wrapper`; methods return neverthrow `ResultAsync`
470
- * values with typed `CodecError` variants in the error channel.
471
- */
472
- export type AccountsProvider = ReturnType<typeof createAccountsProvider>;
473
-
474
266
  // ─────────────────────────────────────────────────────────────────────────────
475
267
  // Tests
476
268
  // ─────────────────────────────────────────────────────────────────────────────
@@ -478,17 +270,13 @@ export type AccountsProvider = ReturnType<typeof createAccountsProvider>;
478
270
  if (import.meta.vitest) {
479
271
  const { test, expect } = import.meta.vitest;
480
272
 
481
- test("getTruApi returns TruApi when SDK is available", async () => {
482
- // Reset cache for test
483
- cachedTruApi = null;
273
+ test("getTruApi returns null outside a container", async () => {
484
274
  const api = await getTruApi();
485
- // In dev/test mode, product-sdk is installed
486
275
  expect(api === null || typeof api === "object").toBe(true);
487
276
  });
488
277
 
489
- test("getPreimageManager returns manager when SDK is available", async () => {
278
+ test("getPreimageManager returns manager or null", async () => {
490
279
  const manager = await getPreimageManager();
491
- // In dev/test mode, product-sdk is installed
492
280
  expect(manager === null || typeof manager === "object").toBe(true);
493
281
  });
494
282
 
@@ -496,58 +284,27 @@ if (import.meta.vitest) {
496
284
  expect(await createHostPreimageManager()).toBeNull();
497
285
  });
498
286
 
499
- test("formatHostError unwraps versioned envelopes and renders CodecError", () => {
500
- expect(
501
- formatHostError({
502
- tag: "v1",
503
- value: { name: "GenericError", message: "boom" },
504
- }),
505
- ).toBe("GenericError: boom");
506
- expect(formatHostError(new Error("plain"))).toBe("plain");
507
- expect(formatHostError("string err")).toBe("string err");
508
- expect(formatHostError({ tag: "v1", value: { message: "no-name" } })).toBe("no-name");
509
- });
510
-
511
- test("getAccountsProvider returns provider when SDK is available", async () => {
512
- // In dev/test mode, product-sdk is installed, so this returns a provider
513
- const provider = await getAccountsProvider();
514
- // Just verify it returns something (null when SDK unavailable, provider when available)
515
- expect(provider === null || typeof provider === "object").toBe(true);
287
+ test("hex helpers", () => {
288
+ expect(toHex(new Uint8Array([0xde, 0xad]))).toBe("0xdead");
289
+ expect(Array.from(fromHex("0xdead"))).toEqual([0xde, 0xad]);
516
290
  });
517
291
 
518
- test("enumValue is exported", async () => {
519
- const { enumValue } = await import("./truapi.js");
520
- expect(typeof enumValue).toBe("function");
521
- });
522
-
523
- test("requestResourceAllocation throws when TruAPI is unavailable", async () => {
524
- cachedTruApi = null;
292
+ test("requestResourceAllocation returns err when TruAPI is unavailable", async () => {
525
293
  const api = await getTruApi();
526
294
  if (api === null) {
527
- await expect(
528
- requestResourceAllocation([{ tag: "BulletinAllowance", value: undefined }]),
529
- ).rejects.toThrow(/TruAPI unavailable/);
295
+ const result = await requestResourceAllocation([
296
+ { tag: "BulletinAllowance", value: undefined },
297
+ ]);
298
+ expect(result.ok).toBe(false);
299
+ if (!result.ok) {
300
+ expect(result.error).toBeInstanceOf(HostUnavailableError);
301
+ }
530
302
  } else {
531
303
  expect(typeof requestResourceAllocation).toBe("function");
532
304
  }
533
305
  });
534
306
 
535
- test("createProofAuthorized throws when TruAPI is unavailable", async () => {
536
- cachedTruApi = null;
537
- const api = await getTruApi();
538
- if (api === null) {
539
- await expect(
540
- createProofAuthorized({
541
- proof: undefined,
542
- decryptionKey: undefined,
543
- expiry: undefined,
544
- channel: undefined,
545
- topics: [],
546
- data: undefined,
547
- }),
548
- ).rejects.toThrow(/TruAPI unavailable/);
549
- } else {
550
- expect(typeof createProofAuthorized).toBe("function");
551
- }
307
+ test("createProofAuthorized is callable", () => {
308
+ expect(typeof createProofAuthorized).toBe("function");
552
309
  });
553
310
  }