@parity/product-sdk-host 0.0.0-dev.312.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.
@@ -0,0 +1,181 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Access to the in-house TruAPI client (`@parity/truapi`) for the host package.
5
+ *
6
+ * Environment detection and the lazily-built, cached client come from
7
+ * `@parity/truapi/sandbox`; this module layers the product-sdk-specific glue on
8
+ * top — an async {@link getClient} accessor and {@link subscribeWithInterrupt},
9
+ * which adapts a truapi stream into the host's {@link HostSubscription} shape.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ import type { ObservableLike, TrUApiClient } from "@parity/truapi";
15
+ import {
16
+ getClientSync as sandboxGetClientSync,
17
+ isCorrectEnvironment as sandboxIsCorrectEnvironment,
18
+ } from "@parity/truapi/sandbox";
19
+
20
+ import type { HostSubscription } from "./types.js";
21
+
22
+ /** A {@link HostSubscription} carrying the transport-assigned subscription id. */
23
+ export interface TransportSubscription extends HostSubscription {
24
+ readonly subscriptionId: string;
25
+ }
26
+
27
+ // Test-only override. When set — via `setTruApiClient`, exposed through
28
+ // `@parity/product-sdk-host/testing` — every host accessor resolves this client
29
+ // instead of the sandbox one. `null` in production, so the branches below are
30
+ // no-ops there.
31
+ let clientOverride: TrUApiClient | null = null;
32
+
33
+ function isProductionBuild(): boolean {
34
+ try {
35
+ // Must stay a plain `process.env.NODE_ENV` member expression: bundlers
36
+ // (Vite, esbuild, webpack) substitute it textually, which is how this
37
+ // check works in browser builds where `process` doesn't exist.
38
+ return process.env.NODE_ENV === "production";
39
+ } catch {
40
+ // No `process` and no bundler define — can't tell, stay quiet.
41
+ return false;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Test-only seam: force {@link getClient} / {@link getClientSync} to return
47
+ * `client`, and {@link isCorrectEnvironment} to report `true`. Pass `null` to
48
+ * restore normal detection. Exposed through `@parity/product-sdk-host/testing`,
49
+ * not the package's main entry.
50
+ *
51
+ * Calling this in a production build silently reroutes every host accessor to
52
+ * the injected client, so we warn — it almost always means a `/testing` import
53
+ * leaked into a production path.
54
+ */
55
+ export function setTruApiClient(client: TrUApiClient | null): void {
56
+ if (client !== null && isProductionBuild()) {
57
+ console.warn(
58
+ "[product-sdk] setTruApiClient() was called in a production build. This is a test-only seam from @parity/product-sdk-host/testing; a leaked import will silently reroute all host access to the injected client.",
59
+ );
60
+ }
61
+ clientOverride = client;
62
+ }
63
+
64
+ /**
65
+ * Synchronous TruAPI client accessor. Returns the injected test client when one
66
+ * is set, otherwise the sandbox client (`null` outside a host container).
67
+ */
68
+ export function getClientSync(): TrUApiClient | null {
69
+ return clientOverride ?? sandboxGetClientSync();
70
+ }
71
+
72
+ /**
73
+ * Host-container detection. `true` when a test client is injected, otherwise the
74
+ * sandbox heuristic (iframe / webview marker / injected message port).
75
+ */
76
+ export function isCorrectEnvironment(): boolean {
77
+ return clientOverride !== null || sandboxIsCorrectEnvironment();
78
+ }
79
+
80
+ /**
81
+ * Get the TruAPI client. Returns `null` outside a host container. Async wrapper
82
+ * over {@link getClientSync} for the host wrappers that already `await` it.
83
+ */
84
+ export async function getClient(): Promise<TrUApiClient | null> {
85
+ return getClientSync();
86
+ }
87
+
88
+ /**
89
+ * Adapt a truapi `ObservableLike` stream into the host's callback-style
90
+ * {@link HostSubscription} (`unsubscribe` + `onInterrupt`). `onNext` fires for
91
+ * each item; the registered `onInterrupt` callback fires when the host ends the
92
+ * subscription server-side — which the generated client surfaces as either
93
+ * `complete` (a host interrupt frame) or `error` (transport close). Shared by
94
+ * the statement-store and preimage adapters, which both expose this shape.
95
+ */
96
+ export function subscribeWithInterrupt<Item, Reason = never>(
97
+ observable: ObservableLike<Item, Reason>,
98
+ onNext: (item: Item) => void,
99
+ ): TransportSubscription {
100
+ let interruptCallback: ((reason?: unknown) => void) | undefined;
101
+ const sub = observable.subscribe({
102
+ next: onNext,
103
+ error: (reason) => interruptCallback?.(reason),
104
+ complete: () => interruptCallback?.(),
105
+ });
106
+ return {
107
+ subscriptionId: sub.subscriptionId,
108
+ unsubscribe: () => sub.unsubscribe(),
109
+ onInterrupt: (callback) => {
110
+ interruptCallback = callback;
111
+ return () => {
112
+ if (interruptCallback === callback) interruptCallback = undefined;
113
+ };
114
+ },
115
+ };
116
+ }
117
+
118
+ if (import.meta.vitest) {
119
+ const { test, expect, afterEach } = import.meta.vitest;
120
+
121
+ afterEach(() => setTruApiClient(null));
122
+
123
+ // Environment detection and client building are covered by `@parity/truapi`'s
124
+ // own sandbox tests; here we only assert the local glue degrades outside a
125
+ // host container.
126
+ test("getClientSync returns null outside a container", () => {
127
+ expect(getClientSync()).toBeNull();
128
+ });
129
+
130
+ test("getClient resolves null outside a container", async () => {
131
+ expect(await getClient()).toBeNull();
132
+ });
133
+
134
+ test("setTruApiClient overrides the client and container detection", async () => {
135
+ const fake = {} as TrUApiClient;
136
+ setTruApiClient(fake);
137
+ expect(getClientSync()).toBe(fake);
138
+ expect(await getClient()).toBe(fake);
139
+ expect(isCorrectEnvironment()).toBe(true);
140
+
141
+ setTruApiClient(null);
142
+ expect(getClientSync()).toBeNull();
143
+ expect(isCorrectEnvironment()).toBe(false);
144
+ });
145
+
146
+ test("setTruApiClient warns when injecting in a production build", () => {
147
+ const original = process.env.NODE_ENV;
148
+ const warnings: string[] = [];
149
+ const realWarn = console.warn;
150
+ console.warn = (...args: unknown[]) => void warnings.push(String(args[0]));
151
+ try {
152
+ process.env.NODE_ENV = "production";
153
+ setTruApiClient({} as TrUApiClient);
154
+ expect(warnings).toHaveLength(1);
155
+ expect(warnings[0]).toContain("production build");
156
+
157
+ // Clearing the override must not warn.
158
+ setTruApiClient(null);
159
+ expect(warnings).toHaveLength(1);
160
+ } finally {
161
+ console.warn = realWarn;
162
+ process.env.NODE_ENV = original;
163
+ }
164
+ });
165
+
166
+ test("subscribeWithInterrupt preserves the transport subscription id", () => {
167
+ const observable = {
168
+ subscribe: () => ({
169
+ subscriptionId: "p:17",
170
+ unsubscribe: () => {},
171
+ }),
172
+ [Symbol.observable]() {
173
+ return this;
174
+ },
175
+ } as ObservableLike<never>;
176
+
177
+ const subscription = subscribeWithInterrupt(observable, () => {});
178
+
179
+ expect(subscription.subscriptionId).toBe("p:17");
180
+ });
181
+ }
package/src/truapi.ts ADDED
@@ -0,0 +1,313 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * TruAPI - the protocol for communicating between apps and the Polkadot host container.
5
+ *
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).
13
+ *
14
+ * @module
15
+ */
16
+
17
+ import { scale } from "@parity/truapi";
18
+ import type {
19
+ AllocatableResource,
20
+ AllocationOutcome,
21
+ HexString,
22
+ RemotePermission,
23
+ TrUApiClient,
24
+ } from "@parity/truapi";
25
+ import { createLogger } from "@parity/product-sdk-logger";
26
+
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";
37
+
38
+ const log = createLogger("host");
39
+
40
+ /**
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.
50
+ */
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
+ );
58
+ }
59
+
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)),
75
+ );
76
+ }
77
+
78
+ // ─────────────────────────────────────────────────────────────────────────────
79
+ // Hex helpers
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+
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 };
94
+
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+ // TruAPI accessor
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+
99
+ /**
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`.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const truApi = await getTruApi();
107
+ * if (truApi) {
108
+ * await truApi.permissions.requestRemotePermission({
109
+ * permission: { tag: "ChainSubmit", value: undefined },
110
+ * });
111
+ * await truApi.system.navigateTo({ url: "polkadot://settings" });
112
+ * }
113
+ * ```
114
+ */
115
+ export type TruApi = TrUApiClient;
116
+
117
+ /**
118
+ * Get the TruAPI client for direct low-level access to host protocol domains.
119
+ *
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.
122
+ *
123
+ * For most use cases, prefer the higher-level functions like
124
+ * {@link requestPermission}, {@link deriveEntropy}, or `getHostLocalStorage()`.
125
+ *
126
+ * @returns The TruAPI client, or `null` if unavailable.
127
+ */
128
+ export async function getTruApi(): Promise<TruApi | null> {
129
+ return getClient();
130
+ }
131
+
132
+ /**
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.
138
+ */
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
+ }
158
+
159
+ /**
160
+ * Get the preimage manager for bulletin chain operations.
161
+ *
162
+ * @returns The preimage manager, or `null` if unavailable (outside a container).
163
+ */
164
+ export async function getPreimageManager(): Promise<PreimageManager | null> {
165
+ const client = await getClient();
166
+ return client ? adaptPreimageManager(client) : null;
167
+ }
168
+
169
+ /**
170
+ * Construct a `PreimageManager`. Retained for API compatibility; with the single
171
+ * cached TruAPI client this is equivalent to {@link getPreimageManager}.
172
+ *
173
+ * @returns A `PreimageManager` instance, or `null` if unavailable.
174
+ */
175
+ export async function createHostPreimageManager(): Promise<PreimageManager | null> {
176
+ return getPreimageManager();
177
+ }
178
+
179
+ // ─────────────────────────────────────────────────────────────────────────────
180
+ // Resource allocation
181
+ // ─────────────────────────────────────────────────────────────────────────────
182
+
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
+ // Its `SmartContractAllowance` variant carries the tagged `DerivationIndex`
187
+ // selector (`{ tag: "Index", value: number }` for a plain index, `{ tag:
188
+ // "Raw", value: HexString }` for a raw 32-byte index).
189
+ // - `AllocationOutcome` — per-resource outcome, the string union
190
+ // `"Allocated" | "Rejected" | "NotAvailable"` (RFC-10).
191
+ // - `RemotePermission` — permission the dapp asks the host to grant via `requestPermission`.
192
+ export type { AllocatableResource, AllocationOutcome, RemotePermission };
193
+
194
+ /**
195
+ * Request the host to pre-allocate one or more resource allowances.
196
+ *
197
+ * The host prompts the user once; subsequent operations covered by the
198
+ * granted allowance don't re-prompt.
199
+ *
200
+ * @param resources - Resources to request.
201
+ * @returns `ok` with per-resource outcomes in the same order as `resources`, or
202
+ * `err(HostUnavailableError | HostCallFailedError)`.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * const r = await requestResourceAllocation([
207
+ * { tag: "BulletinAllowance", value: undefined },
208
+ * ]);
209
+ * if (r.ok && r.value[0] === "Allocated") { ... }
210
+ * ```
211
+ */
212
+ export async function requestResourceAllocation(
213
+ resources: AllocatableResource[],
214
+ ): Promise<Result<AllocationOutcome[], HostError>> {
215
+ const truApi = await getTruApi();
216
+ if (!truApi) {
217
+ return err(new HostUnavailableError("requestResourceAllocation: TruAPI unavailable"));
218
+ }
219
+ log.debug("requestResourceAllocation", { resources: resources.map((r) => r.tag) });
220
+
221
+ return mapHostResult(
222
+ truApi.resourceAllocation.request({ resources }),
223
+ (response) => response.outcomes,
224
+ "requestResourceAllocation failed",
225
+ );
226
+ }
227
+
228
+ // ─────────────────────────────────────────────────────────────────────────────
229
+ // Authorized Statement Store proof creation (RFC-10 §"Statement Store allowance")
230
+ // ─────────────────────────────────────────────────────────────────────────────
231
+
232
+ /**
233
+ * Have the host sign a Statement using the product's allowance-bearing account,
234
+ * which it picks internally — RFC-10 §"Statement Store allowance". No per-call
235
+ * account id is needed (this is the sponsored-submission path).
236
+ *
237
+ * Pairs with {@link getStatementStore}'s `submit`: call this to obtain a proof,
238
+ * attach it to the Statement, and submit the result.
239
+ *
240
+ * @param statement - The Statement to be signed.
241
+ * @returns `ok` with the proof to attach before submitting, or
242
+ * `err(HostUnavailableError | HostCallFailedError)`.
243
+ */
244
+ export async function createProofAuthorized(
245
+ statement: Statement,
246
+ ): Promise<Result<StatementProof, HostError>> {
247
+ const truApi = await getTruApi();
248
+ if (!truApi) {
249
+ return err(new HostUnavailableError("createProofAuthorized: TruAPI unavailable"));
250
+ }
251
+ log.debug("createProofAuthorized", { topics: statement.topics.length });
252
+
253
+ return mapHostResult(
254
+ truApi.statementStore.createProofAuthorized(statement),
255
+ (response) => response.proof,
256
+ "createProofAuthorized failed",
257
+ );
258
+ }
259
+
260
+ /**
261
+ * Neverthrow-style ResultAsync returned by product-sdk methods.
262
+ *
263
+ * Use `.match(onOk, onErr)` to handle success/error cases.
264
+ */
265
+ export interface ResultAsync<T, E> {
266
+ match: <A, B = A>(ok: (t: T) => A, err: (e: E) => B) => Promise<A | B>;
267
+ }
268
+
269
+ // ─────────────────────────────────────────────────────────────────────────────
270
+ // Tests
271
+ // ─────────────────────────────────────────────────────────────────────────────
272
+
273
+ if (import.meta.vitest) {
274
+ const { test, expect } = import.meta.vitest;
275
+
276
+ test("getTruApi returns null outside a container", async () => {
277
+ const api = await getTruApi();
278
+ expect(api === null || typeof api === "object").toBe(true);
279
+ });
280
+
281
+ test("getPreimageManager returns manager or null", async () => {
282
+ const manager = await getPreimageManager();
283
+ expect(manager === null || typeof manager === "object").toBe(true);
284
+ });
285
+
286
+ test("createHostPreimageManager returns null outside container", async () => {
287
+ expect(await createHostPreimageManager()).toBeNull();
288
+ });
289
+
290
+ test("hex helpers", () => {
291
+ expect(toHex(new Uint8Array([0xde, 0xad]))).toBe("0xdead");
292
+ expect(Array.from(fromHex("0xdead"))).toEqual([0xde, 0xad]);
293
+ });
294
+
295
+ test("requestResourceAllocation returns err when TruAPI is unavailable", async () => {
296
+ const api = await getTruApi();
297
+ if (api === null) {
298
+ const result = await requestResourceAllocation([
299
+ { tag: "BulletinAllowance", value: undefined },
300
+ ]);
301
+ expect(result.ok).toBe(false);
302
+ if (!result.ok) {
303
+ expect(result.error).toBeInstanceOf(HostUnavailableError);
304
+ }
305
+ } else {
306
+ expect(typeof requestResourceAllocation).toBe("function");
307
+ }
308
+ });
309
+
310
+ test("createProofAuthorized is callable", () => {
311
+ expect(typeof createProofAuthorized).toBe("function");
312
+ });
313
+ }
package/src/types.ts ADDED
@@ -0,0 +1,101 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Public types for the host wrappers.
5
+ *
6
+ * The statement-store types are re-exported from `@parity/truapi` so the Parity
7
+ * surface stays in lockstep with the in-house protocol codec types. Their fields
8
+ * are `0x`-prefixed hex strings (`HexString`) and their enums are `{ tag }` unions.
9
+ */
10
+
11
+ import type {
12
+ ProductAccountId,
13
+ RemoteStatementStoreSubscribeItem,
14
+ SignedStatement,
15
+ Statement,
16
+ StatementProof,
17
+ Topic,
18
+ } from "@parity/truapi";
19
+
20
+ // Statement-store types, re-exported verbatim from `@parity/truapi` (imported
21
+ // above for the local signatures): `Statement` / `SignedStatement` /
22
+ // `StatementProof` / `Topic` / `ProductAccountId`. Their fields are
23
+ // `0x`-prefixed `HexString`s and their enums are `{ tag }` unions; the proof
24
+ // variants cover `Sr25519` / `Ed25519` / `Ecdsa` / `OnChain`.
25
+ export type { ProductAccountId, SignedStatement, Statement, StatementProof, Topic };
26
+
27
+ /**
28
+ * Persistent storage exposed by the host container, including string, JSON
29
+ * and raw byte (`readBytes`/`writeBytes`) accessors. Most apps reach it
30
+ * indirectly through the Storage package's `KvStore`; reach for it directly
31
+ * via {@link getHostLocalStorage} when you need raw host storage without the
32
+ * KV abstraction.
33
+ *
34
+ * Backed by `truApi.localStorage.*` (raw `read`/`write`/`clear` over hex bytes);
35
+ * {@link getHostLocalStorage} adapts that into this richer surface. `readString`
36
+ * resolves to `""` for a missing key and `readJSON`/`readBytes` to
37
+ * `null`/`undefined`.
38
+ */
39
+ export interface HostLocalStorage {
40
+ /** Read a UTF-8 string value; `""` when the key is absent. */
41
+ readString(key: string): Promise<string>;
42
+ /** Write a UTF-8 string value. */
43
+ writeString(key: string, value: string): Promise<void>;
44
+ /** Read and JSON-parse a value; `null` when the key is absent. */
45
+ readJSON(key: string): Promise<unknown>;
46
+ /** JSON-stringify and write a value. */
47
+ writeJSON(key: string, value: unknown): Promise<void>;
48
+ /** Read raw bytes; `undefined` when the key is absent. */
49
+ readBytes(key: string): Promise<Uint8Array | undefined>;
50
+ /** Write raw bytes. */
51
+ writeBytes(key: string, value: Uint8Array): Promise<void>;
52
+ /** Remove a key. */
53
+ clear(key: string): Promise<void>;
54
+ }
55
+
56
+ /**
57
+ * Topic-based subscription filter. The host delivers statements that match
58
+ * either *all* of the listed topics (`matchAll`) or *any* of them (`matchAny`).
59
+ *
60
+ * This is a field-discriminated form of truapi's `RemoteStatementStoreSubscribeRequest`,
61
+ * which is a tagged union (`{ tag: "MatchAll"; value: Topic[] } | { tag: "MatchAny"; value: Topic[] }`).
62
+ * The transport maps between the two.
63
+ */
64
+ export type StatementTopicFilter = { matchAll: Topic[] } | { matchAny: Topic[] };
65
+
66
+ /**
67
+ * A page of signed statements delivered by {@link HostStatementStore.subscribe}.
68
+ *
69
+ * truapi's `RemoteStatementStoreSubscribeItem`, re-exported under a friendlier
70
+ * name. Pages arrive sequentially; `isComplete` is `false` while the host
71
+ * streams the historical backfill and `true` once it's done (and on every
72
+ * subsequent live-update page).
73
+ */
74
+ export type StatementsPage = RemoteStatementStoreSubscribeItem;
75
+
76
+ /**
77
+ * Subscription handle returned by the host. Exposes `unsubscribe()` plus an
78
+ * `onInterrupt` hook that fires if the host interrupts the subscription
79
+ * server-side; `onInterrupt` returns a function that cancels the hook.
80
+ */
81
+ export interface HostSubscription {
82
+ unsubscribe(): void;
83
+ onInterrupt(callback: (reason?: unknown) => void): () => void;
84
+ }
85
+
86
+ /**
87
+ * Statement Store handle exposed by the host container, backed by
88
+ * `truApi.statementStore.*`. `subscribe` streams matching statements;
89
+ * `createProofAuthorized` signs a statement with the product's RFC-10 allowance
90
+ * account (the sponsored path — no per-call account id); `submit` publishes a
91
+ * signed statement. The `statement-store` package layers a higher-level client
92
+ * on top.
93
+ */
94
+ export interface HostStatementStore {
95
+ subscribe(
96
+ filter: StatementTopicFilter,
97
+ callback: (page: StatementsPage) => void,
98
+ ): HostSubscription;
99
+ createProofAuthorized(statement: Statement): Promise<StatementProof>;
100
+ submit(signedStatement: SignedStatement): Promise<void>;
101
+ }