@parity/product-sdk-host 0.3.0 → 0.5.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/dist/index.d.ts +462 -203
- package/dist/index.js +163 -19
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/chains.ts +2 -8
- package/src/chat.ts +124 -0
- package/src/container.ts +45 -9
- package/src/entropy.ts +65 -0
- package/src/index.ts +41 -1
- package/src/payments.ts +98 -0
- package/src/permissions.ts +232 -0
- package/src/theme.ts +82 -0
- package/src/truapi.ts +225 -126
- package/src/types.ts +62 -71
package/src/truapi.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TruAPI - the protocol for communicating between apps and the Polkadot host container.
|
|
3
3
|
*
|
|
4
|
-
* This module centralizes access to @novasamatech/
|
|
4
|
+
* This module centralizes access to @novasamatech/host-api-wrapper and @novasamatech/host-api,
|
|
5
5
|
* allowing other @parity/product-sdk-* packages to import from here rather than depending
|
|
6
6
|
* directly on novasama packages.
|
|
7
7
|
*
|
|
@@ -15,10 +15,57 @@ import type {
|
|
|
15
15
|
AllocatableResource as AllocatableResourceCodec,
|
|
16
16
|
AllocationOutcome as AllocationOutcomeCodec,
|
|
17
17
|
CodecType,
|
|
18
|
+
RemotePermission as RemotePermissionCodec,
|
|
18
19
|
} from "@novasamatech/host-api";
|
|
20
|
+
import type { createAccountsProvider, preimageManager } from "@novasamatech/host-api-wrapper";
|
|
21
|
+
|
|
22
|
+
import { isInsideContainer } from "./container.js";
|
|
23
|
+
import type { Statement, StatementProof } from "./types.js";
|
|
19
24
|
|
|
20
25
|
const log = createLogger("host");
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Extract a human-readable message from a host-side error. Hosts wrap
|
|
29
|
+
* errors in versioned envelopes (`{ tag: "v1", value: CodecError }`); this
|
|
30
|
+
* helper unwraps the envelope and renders the inner error's `name`/`message`
|
|
31
|
+
* so callers see the host's actual diagnostic instead of `"[object Object]"`
|
|
32
|
+
* (from `String(err)`) or a JSON-stringified envelope.
|
|
33
|
+
*
|
|
34
|
+
* Exported for the higher-level wrappers (`requestPermission`,
|
|
35
|
+
* `deriveEntropy`, etc.) that build their `throw new Error(...)` messages.
|
|
36
|
+
*/
|
|
37
|
+
export function formatHostError(err: unknown): string {
|
|
38
|
+
// Single-level unwrap only; nested envelopes fall through to JSON.stringify.
|
|
39
|
+
const inner = isVersionedEnvelope(err) ? err.value : err;
|
|
40
|
+
|
|
41
|
+
if (inner instanceof Error) return inner.message;
|
|
42
|
+
if (typeof inner === "string") return inner;
|
|
43
|
+
if (
|
|
44
|
+
inner != null &&
|
|
45
|
+
typeof inner === "object" &&
|
|
46
|
+
"message" in inner &&
|
|
47
|
+
typeof (inner as { message: unknown }).message === "string"
|
|
48
|
+
) {
|
|
49
|
+
const named = inner as { name?: unknown; message: string };
|
|
50
|
+
return typeof named.name === "string" ? `${named.name}: ${named.message}` : named.message;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.stringify(inner);
|
|
54
|
+
} catch {
|
|
55
|
+
return String(inner);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isVersionedEnvelope(value: unknown): value is { tag: string; value: unknown } {
|
|
60
|
+
return (
|
|
61
|
+
value != null &&
|
|
62
|
+
typeof value === "object" &&
|
|
63
|
+
"tag" in value &&
|
|
64
|
+
"value" in value &&
|
|
65
|
+
typeof (value as { tag: unknown }).tag === "string"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
22
69
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
70
|
// Helpers from @novasamatech/host-api (re-exported from @novasamatech/scale)
|
|
24
71
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -79,16 +126,19 @@ export type { HexString } from "@novasamatech/host-api";
|
|
|
79
126
|
* The TruApi type - provides low-level methods for communicating with the host.
|
|
80
127
|
*
|
|
81
128
|
* Methods include:
|
|
82
|
-
* - `navigateTo(url)`
|
|
83
|
-
* - `permission(permissions)`
|
|
84
|
-
* - `localStorageRead/Write/Clear`
|
|
85
|
-
* - `sign(payload)`
|
|
86
|
-
* - `deriveEntropy(context)`
|
|
87
|
-
* - `themeSubscribe()`
|
|
129
|
+
* - `navigateTo(url)` - Navigate to a URL within the host
|
|
130
|
+
* - `permission(permissions)` - Request permissions from the host
|
|
131
|
+
* - `localStorageRead/Write/Clear` - Host-backed storage
|
|
132
|
+
* - `sign(payload)` - Request transaction signing
|
|
133
|
+
* - `deriveEntropy(context)` - Derive deterministic entropy
|
|
134
|
+
* - `themeSubscribe()` - Subscribe to host theme changes
|
|
88
135
|
* - And many more...
|
|
136
|
+
*
|
|
137
|
+
* Type identical to `hostApi` from `@novasamatech/host-api-wrapper` so that
|
|
138
|
+
* `truApi.X(...)` calls keep their full inference (return types, method
|
|
139
|
+
* names, parameter shapes) instead of decaying to `any`.
|
|
89
140
|
*/
|
|
90
|
-
|
|
91
|
-
export type TruApi = any;
|
|
141
|
+
export type TruApi = typeof import("@novasamatech/host-api-wrapper").hostApi;
|
|
92
142
|
|
|
93
143
|
/** Cached TruApi instance */
|
|
94
144
|
let cachedTruApi: TruApi | null = null;
|
|
@@ -96,7 +146,7 @@ let cachedTruApi: TruApi | null = null;
|
|
|
96
146
|
/**
|
|
97
147
|
* Get the TruAPI instance for direct low-level access.
|
|
98
148
|
*
|
|
99
|
-
* Returns the `hostApi` object from `@novasamatech/
|
|
149
|
+
* Returns the `hostApi` object from `@novasamatech/host-api-wrapper` which provides
|
|
100
150
|
* methods for communicating directly with the host container. Returns `null`
|
|
101
151
|
* when running outside a container or when the SDK is unavailable.
|
|
102
152
|
*
|
|
@@ -129,7 +179,7 @@ export async function getTruApi(): Promise<TruApi | null> {
|
|
|
129
179
|
if (cachedTruApi) return cachedTruApi;
|
|
130
180
|
|
|
131
181
|
try {
|
|
132
|
-
const sdk = await import("@novasamatech/
|
|
182
|
+
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
133
183
|
cachedTruApi = sdk.hostApi;
|
|
134
184
|
log.debug("TruAPI loaded");
|
|
135
185
|
return cachedTruApi;
|
|
@@ -165,34 +215,44 @@ export async function getTruApi(): Promise<TruApi | null> {
|
|
|
165
215
|
*/
|
|
166
216
|
export async function getPreimageManager(): Promise<PreimageManager | null> {
|
|
167
217
|
try {
|
|
168
|
-
const sdk = await import("@novasamatech/
|
|
218
|
+
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
169
219
|
return sdk.preimageManager;
|
|
170
|
-
} catch {
|
|
220
|
+
} catch (err) {
|
|
221
|
+
log.debug("getPreimageManager unavailable", err);
|
|
171
222
|
return null;
|
|
172
223
|
}
|
|
173
224
|
}
|
|
174
225
|
|
|
175
226
|
/**
|
|
176
|
-
* Preimage manager
|
|
227
|
+
* Preimage manager handle for bulletin chain operations. `lookup` returns a
|
|
228
|
+
* `Subscription<void>` (`unsubscribe` + `onInterrupt`); `submit` returns a
|
|
229
|
+
* `0x`-prefixed hex preimage key.
|
|
230
|
+
*
|
|
231
|
+
* Type identical to `preimageManager` from `@novasamatech/host-api-wrapper`.
|
|
177
232
|
*/
|
|
178
|
-
export
|
|
179
|
-
/**
|
|
180
|
-
* Submit a preimage to the bulletin chain.
|
|
181
|
-
* @param data - The data to submit.
|
|
182
|
-
* @returns The preimage key (hex string).
|
|
183
|
-
*/
|
|
184
|
-
submit(data: Uint8Array): Promise<string>;
|
|
233
|
+
export type PreimageManager = typeof preimageManager;
|
|
185
234
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Construct a fresh `PreimageManager` instance with an optional custom
|
|
237
|
+
* transport. Use this when you need a non-default transport; otherwise
|
|
238
|
+
* prefer {@link getPreimageManager}, which returns the shared singleton.
|
|
239
|
+
*
|
|
240
|
+
* Mirrors `createPreimageManager` from `@novasamatech/host-api-wrapper`.
|
|
241
|
+
*
|
|
242
|
+
* @param transport - Optional transport; defaults to the sandbox transport.
|
|
243
|
+
* @returns A new `PreimageManager` instance, or `null` if unavailable.
|
|
244
|
+
*/
|
|
245
|
+
export async function createHostPreimageManager(
|
|
246
|
+
transport?: import("@novasamatech/host-api").Transport,
|
|
247
|
+
): Promise<PreimageManager | null> {
|
|
248
|
+
if (!(await isInsideContainer())) return null;
|
|
249
|
+
try {
|
|
250
|
+
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
251
|
+
return sdk.createPreimageManager(transport);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
log.debug("createHostPreimageManager unavailable", err);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
196
256
|
}
|
|
197
257
|
|
|
198
258
|
/**
|
|
@@ -202,9 +262,10 @@ export interface PreimageManager {
|
|
|
202
262
|
*/
|
|
203
263
|
export async function getAccountsProvider(): Promise<AccountsProvider | null> {
|
|
204
264
|
try {
|
|
205
|
-
const sdk = await import("@novasamatech/
|
|
206
|
-
return sdk.createAccountsProvider()
|
|
207
|
-
} catch {
|
|
265
|
+
const sdk = await import("@novasamatech/host-api-wrapper");
|
|
266
|
+
return sdk.createAccountsProvider();
|
|
267
|
+
} catch (err) {
|
|
268
|
+
log.debug("getAccountsProvider unavailable", err);
|
|
208
269
|
return null;
|
|
209
270
|
}
|
|
210
271
|
}
|
|
@@ -220,6 +281,9 @@ export async function getAccountsProvider(): Promise<AccountsProvider | null> {
|
|
|
220
281
|
*/
|
|
221
282
|
export type AllocatableResource = CodecType<typeof AllocatableResourceCodec>;
|
|
222
283
|
|
|
284
|
+
/** Tag-only view of {@link AllocatableResource} for places that just need the variant name. */
|
|
285
|
+
export type AllocatableResourceTag = AllocatableResource["tag"];
|
|
286
|
+
|
|
223
287
|
/**
|
|
224
288
|
* Per-resource outcome from {@link requestResourceAllocation}.
|
|
225
289
|
* The host strips secret payloads from `Allocated` before returning, so
|
|
@@ -227,6 +291,21 @@ export type AllocatableResource = CodecType<typeof AllocatableResourceCodec>;
|
|
|
227
291
|
*/
|
|
228
292
|
export type AllocationOutcome = CodecType<typeof AllocationOutcomeCodec>;
|
|
229
293
|
|
|
294
|
+
/** Tag-only view of {@link AllocationOutcome} (`"Allocated" | "Rejected" | "NotAvailable"`). */
|
|
295
|
+
export type AllocationOutcomeTag = AllocationOutcome["tag"];
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Remote permission the dapp can ask the host to grant via
|
|
299
|
+
* {@link requestPermission}.
|
|
300
|
+
*
|
|
301
|
+
* Derived from the upstream codec so variant renames surface as compile
|
|
302
|
+
* errors, not runtime failures.
|
|
303
|
+
*/
|
|
304
|
+
export type RemotePermission = CodecType<typeof RemotePermissionCodec>;
|
|
305
|
+
|
|
306
|
+
/** Tag-only view of {@link RemotePermission}. */
|
|
307
|
+
export type RemotePermissionTag = RemotePermission["tag"];
|
|
308
|
+
|
|
230
309
|
/**
|
|
231
310
|
* Request the host to pre-allocate one or more resource allowances.
|
|
232
311
|
*
|
|
@@ -258,7 +337,75 @@ export async function requestResourceAllocation(
|
|
|
258
337
|
return await truApi.requestResourceAllocation(enumValue("v1", resources)).match(
|
|
259
338
|
(envelope: { tag: "v1"; value: AllocationOutcome[] }) => envelope.value,
|
|
260
339
|
(err: unknown) => {
|
|
261
|
-
throw new Error(`requestResourceAllocation failed: ${
|
|
340
|
+
throw new Error(`requestResourceAllocation failed: ${formatHostError(err)}`, {
|
|
341
|
+
cause: err,
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
348
|
+
// Authorized Statement Store proof creation (RFC-10 §"Statement Store allowance")
|
|
349
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Have the host sign a Statement using an allowance-bearing account it
|
|
353
|
+
* picks internally — RFC-10 §"Statement Store allowance".
|
|
354
|
+
*
|
|
355
|
+
* The product passes only the Statement payload; the host chooses the
|
|
356
|
+
* `//allowance//statement-store//{productId}` account that holds SSS
|
|
357
|
+
* allowance and signs with it. Allowance is provisioned implicitly on
|
|
358
|
+
* first use if the host hasn't already pre-allocated via
|
|
359
|
+
* {@link requestResourceAllocation}; products never see the signing
|
|
360
|
+
* account or its key material.
|
|
361
|
+
*
|
|
362
|
+
* Pairs with {@link getStatementStore}'s `submit`: call this to obtain
|
|
363
|
+
* a proof, attach it to the Statement, and submit the result.
|
|
364
|
+
*
|
|
365
|
+
* @param statement - The Statement to be signed.
|
|
366
|
+
* @returns The proof to attach before submitting.
|
|
367
|
+
* @throws If the host is unavailable or the host-side signing fails.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* import { createProofAuthorized, getStatementStore } from "@parity/product-sdk-host";
|
|
372
|
+
*
|
|
373
|
+
* const statement = {
|
|
374
|
+
* proof: undefined,
|
|
375
|
+
* decryptionKey: undefined,
|
|
376
|
+
* expiry: undefined,
|
|
377
|
+
* channel: undefined,
|
|
378
|
+
* topics: [],
|
|
379
|
+
* data: payload,
|
|
380
|
+
* };
|
|
381
|
+
* const proof = await createProofAuthorized(statement);
|
|
382
|
+
* const store = await getStatementStore();
|
|
383
|
+
* await store?.submit({ ...statement, proof });
|
|
384
|
+
* ```
|
|
385
|
+
*
|
|
386
|
+
* @remarks
|
|
387
|
+
* RFC-10 introduces this as a new, strictly additive TruAPI call. The
|
|
388
|
+
* pre-existing `HostStatementStore.createProof(accountId, statement)`
|
|
389
|
+
* surface stays available for products that own a non-allowance signing
|
|
390
|
+
* account; this wrapper is the sponsored-submission path.
|
|
391
|
+
*/
|
|
392
|
+
export async function createProofAuthorized(statement: Statement): Promise<StatementProof> {
|
|
393
|
+
const truApi = await getTruApi();
|
|
394
|
+
if (!truApi) {
|
|
395
|
+
throw new Error("createProofAuthorized: TruAPI unavailable");
|
|
396
|
+
}
|
|
397
|
+
log.debug("createProofAuthorized", {
|
|
398
|
+
topics: statement.topics.length,
|
|
399
|
+
dataLen: statement.data?.length ?? 0,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// `.match()` because the host returns a neverthrow ResultAsync, not a Promise.
|
|
403
|
+
return await truApi.statementStoreCreateProofAuthorized(enumValue("v1", statement)).match(
|
|
404
|
+
(envelope: { tag: "v1"; value: StatementProof }) => envelope.value,
|
|
405
|
+
(err: unknown) => {
|
|
406
|
+
throw new Error(`createProofAuthorized failed: ${formatHostError(err)}`, {
|
|
407
|
+
cause: err,
|
|
408
|
+
});
|
|
262
409
|
},
|
|
263
410
|
);
|
|
264
411
|
}
|
|
@@ -311,99 +458,16 @@ export interface ResultAsync<T, E> {
|
|
|
311
458
|
}
|
|
312
459
|
|
|
313
460
|
/**
|
|
314
|
-
* Accounts provider
|
|
461
|
+
* Accounts provider handle from `@novasamatech/host-api-wrapper`. Surfaces the
|
|
462
|
+
* full upstream API - host wallet accounts, app-scoped product accounts,
|
|
463
|
+
* Ring VRF, user identity (`getUserId`, `requestLogin`), and connection
|
|
464
|
+
* status subscription.
|
|
315
465
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
466
|
+
* Type identical to `createAccountsProvider()` from
|
|
467
|
+
* `@novasamatech/host-api-wrapper`; methods return neverthrow `ResultAsync`
|
|
468
|
+
* values with typed `CodecError` variants in the error channel.
|
|
318
469
|
*/
|
|
319
|
-
export
|
|
320
|
-
/**
|
|
321
|
-
* Get legacy accounts (user's external wallets connected to the host).
|
|
322
|
-
*
|
|
323
|
-
* Renamed from `getNonProductAccounts` in @novasamatech/product-sdk 0.7.
|
|
324
|
-
*
|
|
325
|
-
* @returns ResultAsync resolving to array of accounts.
|
|
326
|
-
*/
|
|
327
|
-
getLegacyAccounts: () => ResultAsync<HostAccount[], unknown>;
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Get a signer for a legacy account.
|
|
331
|
-
*
|
|
332
|
-
* Renamed from `getNonProductAccountSigner` in @novasamatech/product-sdk 0.7.
|
|
333
|
-
*
|
|
334
|
-
* @param account - The product account (used for public key lookup).
|
|
335
|
-
* @returns A PolkadotSigner for signing transactions.
|
|
336
|
-
*/
|
|
337
|
-
getLegacyAccountSigner: (account: ProductAccount) => import("polkadot-api").PolkadotSigner;
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Get an app-scoped product account from the host.
|
|
341
|
-
*
|
|
342
|
-
* Product accounts are derived by the host wallet for each app, identified
|
|
343
|
-
* by `dotNsIdentifier` (e.g., "mark3t.dot"). The user controls these accounts
|
|
344
|
-
* but they are scoped to the requesting app.
|
|
345
|
-
*
|
|
346
|
-
* @param dotNsIdentifier - App identifier (e.g., "mark3t.dot").
|
|
347
|
-
* @param derivationIndex - Derivation index within the app scope. Default: 0
|
|
348
|
-
* @returns ResultAsync resolving to the account.
|
|
349
|
-
*/
|
|
350
|
-
getProductAccount: (
|
|
351
|
-
dotNsIdentifier: string,
|
|
352
|
-
derivationIndex?: number,
|
|
353
|
-
) => ResultAsync<HostAccount, unknown>;
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Get a signer for a product account.
|
|
357
|
-
*
|
|
358
|
-
* @param account - The product account.
|
|
359
|
-
* @returns A PolkadotSigner for signing transactions.
|
|
360
|
-
*/
|
|
361
|
-
getProductAccountSigner: (account: ProductAccount) => import("polkadot-api").PolkadotSigner;
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Get a contextual alias for a product account via Ring VRF.
|
|
365
|
-
*
|
|
366
|
-
* Aliases prove account membership in a ring without revealing which
|
|
367
|
-
* account produced the alias.
|
|
368
|
-
*
|
|
369
|
-
* @param dotNsIdentifier - App identifier.
|
|
370
|
-
* @param derivationIndex - Derivation index. Default: 0
|
|
371
|
-
* @returns ResultAsync resolving to the contextual alias.
|
|
372
|
-
*/
|
|
373
|
-
getProductAccountAlias: (
|
|
374
|
-
dotNsIdentifier: string,
|
|
375
|
-
derivationIndex?: number,
|
|
376
|
-
) => ResultAsync<ContextualAlias, unknown>;
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Create a Ring VRF proof for anonymous operations.
|
|
380
|
-
*
|
|
381
|
-
* Proves that the signer is a member of the ring at the given location
|
|
382
|
-
* without revealing which member.
|
|
383
|
-
*
|
|
384
|
-
* @param dotNsIdentifier - App identifier.
|
|
385
|
-
* @param derivationIndex - Derivation index.
|
|
386
|
-
* @param location - Ring location on-chain.
|
|
387
|
-
* @param message - Message to sign.
|
|
388
|
-
* @returns ResultAsync resolving to the proof bytes.
|
|
389
|
-
*/
|
|
390
|
-
createRingVRFProof: (
|
|
391
|
-
dotNsIdentifier: string,
|
|
392
|
-
derivationIndex: number,
|
|
393
|
-
location: unknown,
|
|
394
|
-
message: Uint8Array,
|
|
395
|
-
) => ResultAsync<Uint8Array, unknown>;
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Subscribe to account connection status changes.
|
|
399
|
-
*
|
|
400
|
-
* @param callback - Called with status string ("connected" | "disconnected").
|
|
401
|
-
* @returns Unsubscribe handle.
|
|
402
|
-
*/
|
|
403
|
-
subscribeAccountConnectionStatus: (
|
|
404
|
-
callback: (status: string) => void,
|
|
405
|
-
) => { unsubscribe: () => void } | (() => void);
|
|
406
|
-
}
|
|
470
|
+
export type AccountsProvider = ReturnType<typeof createAccountsProvider>;
|
|
407
471
|
|
|
408
472
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
409
473
|
// Tests
|
|
@@ -426,6 +490,22 @@ if (import.meta.vitest) {
|
|
|
426
490
|
expect(manager === null || typeof manager === "object").toBe(true);
|
|
427
491
|
});
|
|
428
492
|
|
|
493
|
+
test("createHostPreimageManager returns null outside container", async () => {
|
|
494
|
+
expect(await createHostPreimageManager()).toBeNull();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test("formatHostError unwraps versioned envelopes and renders CodecError", () => {
|
|
498
|
+
expect(
|
|
499
|
+
formatHostError({
|
|
500
|
+
tag: "v1",
|
|
501
|
+
value: { name: "GenericError", message: "boom" },
|
|
502
|
+
}),
|
|
503
|
+
).toBe("GenericError: boom");
|
|
504
|
+
expect(formatHostError(new Error("plain"))).toBe("plain");
|
|
505
|
+
expect(formatHostError("string err")).toBe("string err");
|
|
506
|
+
expect(formatHostError({ tag: "v1", value: { message: "no-name" } })).toBe("no-name");
|
|
507
|
+
});
|
|
508
|
+
|
|
429
509
|
test("getAccountsProvider returns provider when SDK is available", async () => {
|
|
430
510
|
// In dev/test mode, product-sdk is installed, so this returns a provider
|
|
431
511
|
const provider = await getAccountsProvider();
|
|
@@ -449,4 +529,23 @@ if (import.meta.vitest) {
|
|
|
449
529
|
expect(typeof requestResourceAllocation).toBe("function");
|
|
450
530
|
}
|
|
451
531
|
});
|
|
532
|
+
|
|
533
|
+
test("createProofAuthorized throws when TruAPI is unavailable", async () => {
|
|
534
|
+
cachedTruApi = null;
|
|
535
|
+
const api = await getTruApi();
|
|
536
|
+
if (api === null) {
|
|
537
|
+
await expect(
|
|
538
|
+
createProofAuthorized({
|
|
539
|
+
proof: undefined,
|
|
540
|
+
decryptionKey: undefined,
|
|
541
|
+
expiry: undefined,
|
|
542
|
+
channel: undefined,
|
|
543
|
+
topics: [],
|
|
544
|
+
data: undefined,
|
|
545
|
+
}),
|
|
546
|
+
).rejects.toThrow(/TruAPI unavailable/);
|
|
547
|
+
} else {
|
|
548
|
+
expect(typeof createProofAuthorized).toBe("function");
|
|
549
|
+
}
|
|
550
|
+
});
|
|
452
551
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,62 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Public types for the host wrappers.
|
|
3
|
+
*
|
|
4
|
+
* These are re-exported from `@novasamatech/host-api-wrapper` (the runtime
|
|
5
|
+
* objects the host getters cast to) rather than hand-mirrored, so the
|
|
6
|
+
* Parity surface stays in lockstep with the upstream codec types.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
hostLocalStorage,
|
|
11
|
+
createStatementStore,
|
|
12
|
+
ProductAccountId as NovasamaProductAccountId,
|
|
13
|
+
SignedStatement as NovasamaSignedStatement,
|
|
14
|
+
Statement as NovasamaStatement,
|
|
15
|
+
StatementTopicFilter as NovasamaStatementTopicFilter,
|
|
16
|
+
StatementsPage as NovasamaStatementsPage,
|
|
17
|
+
Topic as NovasamaTopic,
|
|
18
|
+
} from "@novasamatech/host-api-wrapper";
|
|
19
|
+
import type { Subscription } from "@novasamatech/host-api";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Persistent storage exposed by the host container, including string, JSON
|
|
23
|
+
* and raw byte (`readBytes`/`writeBytes`) accessors. Most apps reach it
|
|
24
|
+
* indirectly through the Storage package's `KvStore`; reach for it directly
|
|
25
|
+
* via {@link getHostLocalStorage} when you need raw host storage without the
|
|
26
|
+
* KV abstraction.
|
|
27
|
+
*
|
|
28
|
+
* Type identical to `hostLocalStorage` from `@novasamatech/host-api-wrapper`.
|
|
29
|
+
*/
|
|
30
|
+
export type HostLocalStorage = typeof hostLocalStorage;
|
|
18
31
|
|
|
19
32
|
/**
|
|
20
33
|
* Cryptographic proof attached to a statement before submission, returned by
|
|
21
34
|
* {@link HostStatementStore.createProof}. Variants cover the supported
|
|
22
|
-
* signature schemes
|
|
35
|
+
* signature schemes - `Sr25519`, `Ed25519`, `Ecdsa`, and `OnChain` (chain-
|
|
23
36
|
* attestation-based proofs).
|
|
24
37
|
*
|
|
25
|
-
*
|
|
38
|
+
* Inferred from `createStatementStore().createProof`'s return type so codec
|
|
39
|
+
* changes surface here as compile errors, not runtime decode failures.
|
|
26
40
|
*/
|
|
27
|
-
export type StatementProof =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
| { tag: "Ecdsa"; value: { signature: Uint8Array; signer: Uint8Array } }
|
|
31
|
-
| {
|
|
32
|
-
tag: "OnChain";
|
|
33
|
-
value: { who: Uint8Array; blockHash: Uint8Array; event: bigint };
|
|
34
|
-
};
|
|
41
|
+
export type StatementProof = Awaited<
|
|
42
|
+
ReturnType<ReturnType<typeof createStatementStore>["createProof"]>
|
|
43
|
+
>;
|
|
35
44
|
|
|
36
45
|
/**
|
|
37
|
-
* Topic-based subscription filter.
|
|
38
|
-
* `@novasamatech/product-sdk` — the host delivers statements that match
|
|
46
|
+
* Topic-based subscription filter. The host delivers statements that match
|
|
39
47
|
* either *all* of the listed topics (`matchAll`) or *any* of them
|
|
40
|
-
* (`matchAny`).
|
|
48
|
+
* (`matchAny`). Re-exported from `@novasamatech/host-api-wrapper`.
|
|
41
49
|
*/
|
|
42
|
-
export type StatementTopicFilter =
|
|
50
|
+
export type StatementTopicFilter = NovasamaStatementTopicFilter;
|
|
51
|
+
|
|
52
|
+
/** A single topic value used inside a {@link StatementTopicFilter}. Re-exported from `@novasamatech/host-api-wrapper`. */
|
|
53
|
+
export type Topic = NovasamaTopic;
|
|
54
|
+
|
|
55
|
+
/** `[ss58Address, chainPrefix]` tuple identifying a product account at the codec layer. Re-exported from `@novasamatech/host-api-wrapper`. */
|
|
56
|
+
export type ProductAccountId = NovasamaProductAccountId;
|
|
57
|
+
|
|
58
|
+
/** Unsigned statement payload. Re-exported from `@novasamatech/host-api-wrapper`. */
|
|
59
|
+
export type Statement = NovasamaStatement;
|
|
60
|
+
|
|
61
|
+
/** Statement bundled with its {@link StatementProof}. Re-exported from `@novasamatech/host-api-wrapper`. */
|
|
62
|
+
export type SignedStatement = NovasamaSignedStatement;
|
|
43
63
|
|
|
44
64
|
/**
|
|
45
65
|
* A page of signed statements delivered by {@link HostStatementStore.subscribe}.
|
|
46
66
|
*
|
|
47
67
|
* Pages arrive sequentially. `isComplete` is `true` on the final page of a
|
|
48
68
|
* subscription's initial backfill; subsequent pages contain new statements
|
|
49
|
-
* as they appear on chain.
|
|
69
|
+
* as they appear on chain. `statements` is `SignedStatement[]` (typed,
|
|
70
|
+
* not `unknown[]`).
|
|
50
71
|
*/
|
|
51
|
-
export
|
|
52
|
-
statements: unknown[];
|
|
53
|
-
isComplete: boolean;
|
|
54
|
-
}
|
|
72
|
+
export type StatementsPage = NovasamaStatementsPage;
|
|
55
73
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Subscription handle returned by the host - equivalent to
|
|
76
|
+
* `Subscription<void>` from `@novasamatech/host-api`. Exposes
|
|
77
|
+
* `unsubscribe()` plus an `onInterrupt` hook that fires if the host
|
|
78
|
+
* interrupts the subscription server-side.
|
|
79
|
+
*/
|
|
80
|
+
export type HostSubscription = Subscription<void>;
|
|
60
81
|
|
|
61
82
|
/**
|
|
62
83
|
* Statement Store handle exposed by the host container. Provides
|
|
@@ -64,37 +85,7 @@ export interface HostSubscription {
|
|
|
64
85
|
* host's native binary protocol; the `statement-store` package layers a
|
|
65
86
|
* higher-level client on top.
|
|
66
87
|
*
|
|
67
|
-
*
|
|
88
|
+
* Type identical to `createStatementStore()` from
|
|
89
|
+
* `@novasamatech/host-api-wrapper`.
|
|
68
90
|
*/
|
|
69
|
-
export
|
|
70
|
-
/**
|
|
71
|
-
* Subscribe to statements matching the given topic filter.
|
|
72
|
-
*
|
|
73
|
-
* The callback is invoked once per page of statements. After the initial
|
|
74
|
-
* backfill completes (signaled by `page.isComplete === true`), subsequent
|
|
75
|
-
* pages contain new statements as they're produced.
|
|
76
|
-
*
|
|
77
|
-
* @param filter - Topic match filter (`matchAll` or `matchAny`).
|
|
78
|
-
* @param callback - Called with each `StatementsPage` from the host.
|
|
79
|
-
* @returns Subscription handle with `unsubscribe`.
|
|
80
|
-
*/
|
|
81
|
-
subscribe(
|
|
82
|
-
filter: StatementTopicFilter,
|
|
83
|
-
callback: (page: StatementsPage) => void,
|
|
84
|
-
): HostSubscription;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Create a proof for a statement using the given account.
|
|
88
|
-
*
|
|
89
|
-
* @param accountId - The account ID tuple `[ss58Address, chainPrefix]` from product-sdk.
|
|
90
|
-
* @param statement - The unsigned statement.
|
|
91
|
-
* @returns The proof (signature + signer info, or chain-attestation reference).
|
|
92
|
-
*/
|
|
93
|
-
createProof(accountId: [string, number], statement: unknown): Promise<StatementProof>;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Submit a signed statement to the bulletin chain.
|
|
97
|
-
* @param signedStatement - Statement with attached proof.
|
|
98
|
-
*/
|
|
99
|
-
submit(signedStatement: unknown): Promise<void>;
|
|
100
|
-
}
|
|
91
|
+
export type HostStatementStore = ReturnType<typeof createStatementStore>;
|