@parity/product-sdk 0.17.0 → 0.18.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.
@@ -2,7 +2,8 @@ import { resolvePeopleUsernameOwner, accountIdHexToBytes } from './chunk-BIKT6X5
2
2
  import { createLogger, configure } from '@parity/product-sdk-logger';
3
3
  import { createLocalKvStore } from '@parity/product-sdk-local-storage';
4
4
  import { SignerManager } from '@parity/product-sdk-signer';
5
- import { CloudStorageClient, createLazySigner, calculateCid } from '@parity/product-sdk-cloud-storage';
5
+ import { CloudStorageClient, createLazySigner, calculateCid, ProductCloudStorageError } from '@parity/product-sdk-cloud-storage';
6
+ import { err, ok } from '@parity/result';
6
7
  import { isConnected, getClient, createChainClient, destroyAll } from '@parity/product-sdk-chain-client';
7
8
  import { getAccountsProvider } from '@parity/product-sdk-host';
8
9
 
@@ -65,13 +66,20 @@ async function createApp(config) {
65
66
  const cloudStorageApi = cloudStorageClient ? {
66
67
  upload: async (data) => {
67
68
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
68
- const result = await cloudStorageClient.store(bytes).withManifest(true).send();
69
- if (!result.cid) {
70
- throw new Error(
71
- "Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted \u2014 file an issue."
72
- );
69
+ try {
70
+ const uploaded = await cloudStorageClient.store(bytes).withManifest(true).send();
71
+ if (!uploaded.cid) {
72
+ return err(
73
+ new ProductCloudStorageError(
74
+ "Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted \u2014 file an issue."
75
+ )
76
+ );
77
+ }
78
+ return ok(uploaded.cid.toString());
79
+ } catch (cause) {
80
+ const message = cause instanceof Error ? cause.message : String(cause);
81
+ return err(new ProductCloudStorageError(message, { cause }));
73
82
  }
74
- return result.cid.toString();
75
83
  },
76
84
  fetch: (cid) => cloudStorageClient.fetchBytes(cid),
77
85
  computeCid: async (data) => {
@@ -217,8 +225,8 @@ async function getHostAccountsProvider() {
217
225
  async function getPrimaryUsername(accountsProvider) {
218
226
  const result = await accountsProvider.getUserId().match(
219
227
  (value) => value,
220
- (err) => {
221
- throw err;
228
+ (err2) => {
229
+ throw err2;
222
230
  }
223
231
  );
224
232
  const username = result.primaryUsername.trim();
@@ -229,5 +237,5 @@ async function getPrimaryUsername(accountsProvider) {
229
237
  }
230
238
 
231
239
  export { createApp };
232
- //# sourceMappingURL=chunk-6MNKFIU6.js.map
233
- //# sourceMappingURL=chunk-6MNKFIU6.js.map
240
+ //# sourceMappingURL=chunk-6BI66XUJ.js.map
241
+ //# sourceMappingURL=chunk-6BI66XUJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/createApp.ts"],"names":["err"],"mappings":"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","file":"chunk-6BI66XUJ.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * createApp - Main entry point for the Product SDK\n *\n * Creates an App instance with wallet, storage, chain, and cloud storage APIs.\n */\n\nimport type { ChainDefinition } from \"polkadot-api\";\nimport type {\n App,\n AppConfig,\n WalletApi,\n ChainApi,\n Account,\n CloudStorageApi,\n LocalStorageApi,\n} from \"./types.js\";\nimport { configure, createLogger } from \"@parity/product-sdk-logger\";\nimport { createLocalKvStore } from \"@parity/product-sdk-local-storage\";\nimport { SignerManager } from \"@parity/product-sdk-signer\";\nimport {\n CloudStorageClient,\n ProductCloudStorageError,\n calculateCid,\n createLazySigner,\n} from \"@parity/product-sdk-cloud-storage\";\nimport { err, ok } from \"@parity/result\";\nimport {\n createChainClient,\n getClient,\n isConnected,\n destroyAll,\n} from \"@parity/product-sdk-chain-client\";\nimport { getAccountsProvider } from \"@parity/product-sdk-host\";\nimport {\n accountIdHexToBytes,\n type PeopleUsernameQueryApi,\n resolvePeopleUsernameOwner,\n} from \"../identity/dotns.js\";\n\nconst log = createLogger(\"app\");\n\n/**\n * Create a new Product SDK app instance\n *\n * @param config - Application configuration\n * @returns App instance with all APIs\n *\n * @example\n * ```ts\n * import { createApp } from '@parity/product-sdk';\n *\n * // Default: cloud storage enabled with paseo environment\n * const app = await createApp({\n * name: 'my-app',\n * logLevel: 'info',\n * });\n *\n * // Custom cloud storage environment\n * const prodApp = await createApp({\n * name: 'my-app',\n * cloudStorage: { environment: 'polkadot' },\n * });\n *\n * // Disable cloud storage entirely\n * const noCloudStorageApp = await createApp({\n * name: 'my-app',\n * cloudStorage: false,\n * });\n *\n * // Connect wallet\n * const { accounts } = await app.wallet.connect();\n *\n * // Use storage\n * await app.localStorage.set('key', 'value');\n *\n * // Use cloud storage (check for null if it might be disabled)\n * if (app.cloudStorage) {\n * const cid = await app.cloudStorage.upload('hello world');\n * }\n * ```\n */\nexport async function createApp(config: AppConfig): Promise<App> {\n // Set log level if specified\n if (config.logLevel) {\n configure({ level: config.logLevel });\n }\n\n log.info(\"Creating Product SDK app\", { name: config.name });\n\n // Initialize storage (container-only - will throw if not in container)\n const localKvStore = await createLocalKvStore({ prefix: config.name });\n\n // Initialize signer manager\n const signerManager = new SignerManager({\n dappName: config.name,\n });\n\n // Initialize cloud storage client (configurable, defaults to paseo).\n //\n // The signer is wrapped lazily so the cloud storage client can be built before\n // an account is selected. Uploads will throw a clear error if no signer\n // is available at submission time. Reads (fetch / fetchJson) don't need\n // a signer, so they work regardless of whether an account is selected --\n // but they are container-only and throw CloudStorageHostUnavailableError\n // outside a host container (no IPFS-gateway fallback).\n const cloudStorageEnabled = config.cloudStorage !== false;\n const cloudStorageEnvironment =\n typeof config.cloudStorage === \"object\" ? config.cloudStorage.environment : \"paseo\";\n const cloudStorageClient = cloudStorageEnabled\n ? await CloudStorageClient.create({\n environment: cloudStorageEnvironment,\n signer: createLazySigner(() => signerManager.getSigner()),\n })\n : null;\n\n if (cloudStorageEnabled) {\n log.debug(\"Cloud Storage client (Bulletin) initialized\", {\n environment: cloudStorageEnvironment,\n });\n } else {\n log.debug(\"Cloud Storage client disabled\");\n }\n\n // Create storage API adapter\n const localStorageApi: LocalStorageApi = {\n get: (key) => localKvStore.get(key),\n set: (key, value) => localKvStore.set(key, value),\n getJSON: <T>(key: string) => localKvStore.getJSON<T>(key),\n setJSON: <T>(key: string, value: T) => localKvStore.setJSON(key, value),\n remove: (key) => localKvStore.remove(key),\n clear: async () => {\n // LocalKvStore doesn't have clear - this is a no-op\n log.debug(\"clear() is not supported in container storage mode\");\n },\n };\n\n // Create wallet API adapter\n const walletApi = createWalletApi(signerManager);\n\n // Create chain API\n const chainApi: ChainApi = {\n getClient(descriptor) {\n log.debug(\"getClient called\", { genesis: descriptor.genesis });\n const client = getClient(descriptor);\n return client.getTypedApi(descriptor);\n },\n\n getRawClient(descriptor) {\n log.debug(\"getRawClient called\", { genesis: descriptor.genesis });\n return getClient(descriptor);\n },\n\n async connect<T extends Record<string, ChainDefinition>>(chains: T) {\n log.debug(\"connect called\", { chains: Object.keys(chains) });\n return createChainClient({ chains });\n },\n\n isConnected(descriptor) {\n return isConnected(descriptor);\n },\n\n destroyAll() {\n log.debug(\"destroyAll called\");\n destroyAll();\n },\n };\n\n // Create Cloud Storage API adapter (null if disabled)\n const cloudStorageApi: CloudStorageApi | null = cloudStorageClient\n ? {\n upload: async (data) => {\n const bytes = typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n try {\n // Explicitly request a DAG-PB manifest so chunked uploads always\n // resolve to a single root CID. Without this, AsyncBulletinClient\n // can return `result.cid: undefined` for chunked-without-manifest\n // uploads — but app consumers expect a CID they can hand to\n // `fetch(cid)`, so treat a missing CID as an error.\n const uploaded = await cloudStorageClient\n .store(bytes)\n .withManifest(true)\n .send();\n if (!uploaded.cid) {\n return err(\n new ProductCloudStorageError(\n \"Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted — file an issue.\",\n ),\n );\n }\n return ok(uploaded.cid.toString());\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n return err(new ProductCloudStorageError(message, { cause }));\n }\n },\n fetch: (cid) => cloudStorageClient.fetchBytes(cid),\n computeCid: async (data) => {\n const bytes = typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n const cid = await calculateCid(bytes);\n return cid.toString();\n },\n }\n : null;\n\n log.info(\"Product SDK app created\", {\n name: config.name,\n cloudStorage: cloudStorageEnabled ? cloudStorageEnvironment : \"disabled\",\n });\n\n return {\n wallet: walletApi,\n localStorage: localStorageApi,\n chain: chainApi,\n cloudStorage: cloudStorageApi,\n getAppInfo: () => ({ ...config }),\n };\n}\n\n/**\n * Create wallet API adapter using SignerManager from leaf package\n */\nfunction createWalletApi(signerManager: SignerManager): WalletApi {\n // Track account change subscribers\n const accountChangeSubscribers = new Set<(account: Account | null) => void>();\n\n // Subscribe to signer manager state changes\n signerManager.subscribe((state) => {\n const account = state.selectedAccount\n ? {\n address: state.selectedAccount.address,\n name: state.selectedAccount.name ?? undefined,\n source: state.selectedAccount.source,\n }\n : null;\n for (const callback of accountChangeSubscribers) {\n try {\n callback(account);\n } catch (e) {\n log.warn(\"Account change callback threw\", { error: e });\n }\n }\n });\n\n return {\n async connect(): Promise<{ accounts: Account[] }> {\n const result = await signerManager.connect();\n if (!result.ok) {\n throw new Error(result.error.message);\n }\n return {\n accounts: result.value.map((a) => ({\n address: a.address,\n name: a.name ?? undefined,\n source: a.source,\n })),\n };\n },\n\n async disconnect(): Promise<void> {\n signerManager.disconnect();\n },\n\n getAccounts(): Account[] {\n return signerManager.getState().accounts.map((a) => ({\n address: a.address,\n name: a.name ?? undefined,\n source: a.source,\n }));\n },\n\n getSelectedAccount(): Account | null {\n const selected = signerManager.getState().selectedAccount;\n if (!selected) return null;\n return {\n address: selected.address,\n name: selected.name ?? undefined,\n source: selected.source,\n };\n },\n\n selectAccount(address: string): void {\n signerManager.selectAccount(address);\n },\n\n async signMessage(message: string | Uint8Array): Promise<Uint8Array> {\n const bytes = typeof message === \"string\" ? new TextEncoder().encode(message) : message;\n const result = await signerManager.signRaw(bytes);\n if (!result.ok) {\n throw new Error(result.error.message);\n }\n return result.value;\n },\n\n async signMessageWithDotNsIdentity(args) {\n const message =\n typeof args.message === \"string\"\n ? new TextEncoder().encode(args.message)\n : args.message;\n const accountsProvider = await getHostAccountsProvider();\n const username = args.username ?? (await getPrimaryUsername(accountsProvider));\n\n // Reuse an already-connected People chain when the caller has\n // wired one up via `app.chain.connect({ ..., <name>: peopleChain })`.\n // Fall back to opening a transient connection so first-time users\n // don't have to think about chain lifecycles. The chain-client\n // module caches by genesis fingerprint, so subsequent\n // signMessageWithDotNsIdentity calls share whichever connection\n // got established first.\n const peopleClient = isConnected(args.peopleChain)\n ? getClient(args.peopleChain)\n : await createChainClient({ chains: { people: args.peopleChain } }).then(\n (c) => c.raw.people,\n );\n // PAPI's `TypedApi.query.X.Y.getValue` is variadic\n // (`...args: [...WithCallOptions<Args>]`) so it's not assignable\n // to our deliberately-narrow `(key) => Promise<...>` shape. The\n // resolver only ever passes the storage key; the cast adapts the\n // wider PAPI surface to the minimum we depend on.\n const peopleApi = peopleClient.getTypedApi(\n args.peopleChain,\n ) as unknown as PeopleUsernameQueryApi;\n const accountId = await resolvePeopleUsernameOwner(username, peopleApi);\n if (!accountId) {\n throw new Error(`No account owns DotNS username \"${username}\"`);\n }\n\n const owner = accountIdHexToBytes(accountId);\n const signer = accountsProvider.getLegacyAccountSigner({\n publicKey: owner,\n name: username,\n });\n\n try {\n return {\n username,\n accountId,\n signature: await signer.signBytes(message),\n };\n } catch (cause) {\n throw new Error(\n `Failed to sign with DotNS username \"${username}\": ${\n cause instanceof Error ? cause.message : String(cause)\n }`,\n { cause },\n );\n }\n },\n\n onAccountChange(callback: (account: Account | null) => void): () => void {\n accountChangeSubscribers.add(callback);\n return () => accountChangeSubscribers.delete(callback);\n },\n\n getProductAccount(): Account | null {\n // Product accounts require async call - this sync API can't support it properly\n // Users should use SignerManager.getProductAccount() directly\n log.warn(\n \"getProductAccount() is deprecated - use SignerManager.getProductAccount() directly\",\n );\n return null;\n },\n\n getAnonymousAlias(): string | null {\n // Anonymous aliases require async call - this sync API can't support it properly\n // Users should use SignerManager.getProductAccountAlias() directly\n log.warn(\n \"getAnonymousAlias() is deprecated - use SignerManager.getProductAccountAlias() directly\",\n );\n return null;\n },\n\n async createProof(_message: Uint8Array): Promise<Uint8Array> {\n // Ring VRF proofs require SignerManager.createRingVRFProof() directly\n throw new Error(\n \"createProof() is not implemented in the App API. \" +\n \"Use SignerManager.createRingVRFProof() directly.\",\n );\n },\n };\n}\n\ntype HostAccountsProvider = NonNullable<Awaited<ReturnType<typeof getAccountsProvider>>>;\n\nasync function getHostAccountsProvider(): Promise<HostAccountsProvider> {\n const provider = await getAccountsProvider();\n if (!provider) {\n throw new Error(\"Host accounts provider is not available\");\n }\n return provider;\n}\n\nasync function getPrimaryUsername(accountsProvider: HostAccountsProvider): Promise<string> {\n const result = await accountsProvider.getUserId().match(\n (value) => value,\n (err) => {\n throw err;\n },\n );\n const username = result.primaryUsername.trim();\n if (!username) {\n throw new Error(\"Host identity did not provide a primary DotNS username\");\n }\n return username;\n}\n"]}
@@ -1,8 +1,10 @@
1
1
  export { createApp } from '../index.js';
2
2
  export { LogEntry, LogHandler, LogLevel, Logger, LoggerConfig, configure, createLogger } from '@parity/product-sdk-logger';
3
- export { a as Account, A as App, b as AppConfig, C as ChainApi, c as CloudStorageApi, d as CloudStorageConfig, D as DotNsIdentitySignature, L as LocalStorageApi, S as SignMessageWithDotNsIdentityArgs, W as WalletApi } from '../types-DsMUguUr.js';
3
+ export { A as Account, b as App, c as AppConfig, C as ChainApi, a as CloudStorageApi, d as CloudStorageConfig, D as DotNsIdentitySignature, L as LocalStorageApi, S as SignMessageWithDotNsIdentityArgs, W as WalletApi } from '../types-CztPAgZT.js';
4
4
  export { ChainClient } from '@parity/product-sdk-chain-client';
5
5
  export { ChainDefinition, PolkadotClient, TypedApi } from 'polkadot-api';
6
+ import '@parity/result';
7
+ import '@parity/product-sdk-errors';
6
8
  import '@parity/product-sdk-host';
7
9
  import '@parity/product-sdk-signer';
8
10
  import '@parity/product-sdk-local-storage';
@@ -1,5 +1,5 @@
1
1
  export { configure, createLogger } from '../chunk-JVJ552RU.js';
2
- export { createApp } from '../chunk-6MNKFIU6.js';
2
+ export { createApp } from '../chunk-6BI66XUJ.js';
3
3
  import '../chunk-BIKT6X54.js';
4
4
  //# sourceMappingURL=index.js.map
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { LogEntry, LogHandler, LogLevel, Logger, LoggerConfig, configure, createLogger } from '@parity/product-sdk-logger';
2
- import { b as AppConfig, A as App } from './types-DsMUguUr.js';
3
- export { a as Account, C as ChainApi, c as CloudStorageApi, D as DotNsIdentitySignature, L as LocalStorageApi, S as SignMessageWithDotNsIdentityArgs, W as WalletApi } from './types-DsMUguUr.js';
2
+ import { c as AppConfig, b as App } from './types-CztPAgZT.js';
3
+ export { A as Account, C as ChainApi, a as CloudStorageApi, D as DotNsIdentitySignature, L as LocalStorageApi, S as SignMessageWithDotNsIdentityArgs, W as WalletApi } from './types-CztPAgZT.js';
4
+ export { Result, err, isErrorOf, ok } from '@parity/result';
5
+ export { SdkError, isSdkError } from '@parity/product-sdk-errors';
4
6
  export { isInsideContainer, isInsideContainerSync } from '@parity/product-sdk-host';
5
7
  export { ChainClient, createChainClient } from '@parity/product-sdk-chain-client';
6
8
  export { SignerManager } from '@parity/product-sdk-signer';
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { configure, createLogger } from './chunk-JVJ552RU.js';
2
- export { createApp } from './chunk-6MNKFIU6.js';
2
+ export { createApp } from './chunk-6BI66XUJ.js';
3
3
  import './chunk-BIKT6X54.js';
4
+ export { err, isErrorOf, ok } from '@parity/result';
5
+ export { isSdkError } from '@parity/product-sdk-errors';
4
6
  export { isInsideContainer, isInsideContainerSync } from '@parity/product-sdk-host';
5
7
  export { createChainClient } from '@parity/product-sdk-chain-client';
6
8
  export { SignerManager } from '@parity/product-sdk-signer';
@@ -2,9 +2,10 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
4
  import { LogLevel } from '@parity/product-sdk-logger';
5
- import { A as App, S as SignMessageWithDotNsIdentityArgs, D as DotNsIdentitySignature, a as Account } from '../types-DsMUguUr.js';
5
+ import { b as App, S as SignMessageWithDotNsIdentityArgs, D as DotNsIdentitySignature, A as Account } from '../types-CztPAgZT.js';
6
6
  import { ChainDefinition, TypedApi } from 'polkadot-api';
7
7
  import '@parity/product-sdk-cloud-storage';
8
+ import '@parity/result';
8
9
  import '@parity/product-sdk-chain-client';
9
10
  import '../dotns-hLSvmo6u.js';
10
11
 
@@ -1,4 +1,4 @@
1
- import { createApp } from '../chunk-6MNKFIU6.js';
1
+ import { createApp } from '../chunk-6BI66XUJ.js';
2
2
  import '../chunk-BIKT6X54.js';
3
3
  import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
@@ -0,0 +1,61 @@
1
+ import { A as Account, L as LocalStorageApi, C as ChainApi, a as CloudStorageApi, b as App } from '../types-CztPAgZT.js';
2
+ export * from '@parity/product-sdk-local-storage/testing';
3
+ export * from '@parity/product-sdk-signer/testing';
4
+ export * from '@parity/product-sdk-contracts/testing';
5
+ export * from '@parity/product-sdk-host/testing';
6
+ import '@parity/product-sdk-logger';
7
+ import '@parity/product-sdk-cloud-storage';
8
+ import '@parity/result';
9
+ import '@parity/product-sdk-chain-client';
10
+ import '../dotns-hLSvmo6u.js';
11
+ import 'polkadot-api';
12
+
13
+ /** Configurable behavior for the fake {@link WalletApi}. */
14
+ interface FakeWalletOptions {
15
+ /** Accounts `connect()` / `getAccounts()` expose. Default: none. */
16
+ accounts?: Account[];
17
+ /** Initially selected account. Default: the first account, or `null`. */
18
+ selected?: Account | null;
19
+ /** Bytes returned by `signMessage` / `createProof`. Default: 64 zero bytes. */
20
+ signature?: Uint8Array;
21
+ /** Value returned by `getProductAccount()`. Default: the selected account. */
22
+ productAccount?: Account | null;
23
+ /** Value returned by `getAnonymousAlias()`. Default: `null`. */
24
+ anonymousAlias?: string | null;
25
+ }
26
+ /** Options for {@link createFakeApp}. */
27
+ interface CreateFakeAppOptions {
28
+ /** App name reported by `getAppInfo()`. Default: `"fake-app"`. */
29
+ name?: string;
30
+ /** Configure the fake wallet. */
31
+ wallet?: FakeWalletOptions;
32
+ /** Override the local-storage API. Default: in-memory (backed by `createFakeHostLocalStorage`). */
33
+ localStorage?: LocalStorageApi;
34
+ /** Override the chain API. Default: unconfigured (throws until you provide one). */
35
+ chain?: ChainApi;
36
+ /** Override cloud storage, or pass `null` to disable it. Default: an in-memory fake. */
37
+ cloudStorage?: CloudStorageApi | null;
38
+ }
39
+ /**
40
+ * Create a fake {@link App}. Wallet, local-storage, and cloud-storage are working
41
+ * in-memory fakes; `chain` is unconfigured until you override it. Every sub-API
42
+ * is overridable.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { createFakeApp } from "@parity/product-sdk/testing";
47
+ *
48
+ * const app = createFakeApp({ wallet: { accounts: [alice, bob], selected: alice } });
49
+ * await app.wallet.connect();
50
+ * expect(app.wallet.getAccounts()).toHaveLength(2);
51
+ * ```
52
+ *
53
+ * For React, feed the fake to the exported context:
54
+ * ```tsx
55
+ * import { ProductSDKContext } from "@parity/product-sdk/react";
56
+ * render(<ProductSDKContext.Provider value={app}><AccountPicker /></ProductSDKContext.Provider>);
57
+ * ```
58
+ */
59
+ declare function createFakeApp(options?: CreateFakeAppOptions): App;
60
+
61
+ export { type CreateFakeAppOptions, type FakeWalletOptions, createFakeApp };
@@ -0,0 +1,149 @@
1
+ import { ProductCloudStorageError } from '@parity/product-sdk-cloud-storage';
2
+ import { createFakeHostLocalStorage } from '@parity/product-sdk-local-storage/testing';
3
+ export * from '@parity/product-sdk-local-storage/testing';
4
+ import { err, ok } from '@parity/result';
5
+ export * from '@parity/product-sdk-signer/testing';
6
+ export * from '@parity/product-sdk-contracts/testing';
7
+ export * from '@parity/product-sdk-host/testing';
8
+
9
+ // src/testing.ts
10
+ var textEncoder = new TextEncoder();
11
+ function makeFakeWallet(options) {
12
+ const accounts = options?.accounts ?? [];
13
+ let selected = options?.selected !== void 0 ? options.selected : accounts[0] ?? null;
14
+ const signature = options?.signature ?? new Uint8Array(64);
15
+ const listeners = /* @__PURE__ */ new Set();
16
+ const notify = () => {
17
+ for (const l of listeners) l(selected);
18
+ };
19
+ return {
20
+ async connect() {
21
+ return { accounts };
22
+ },
23
+ async disconnect() {
24
+ selected = null;
25
+ notify();
26
+ },
27
+ getAccounts() {
28
+ return accounts;
29
+ },
30
+ getSelectedAccount() {
31
+ return selected;
32
+ },
33
+ selectAccount(address) {
34
+ selected = accounts.find((a) => a.address === address) ?? null;
35
+ notify();
36
+ },
37
+ async signMessage() {
38
+ return signature;
39
+ },
40
+ async signMessageWithDotNsIdentity(args) {
41
+ return {
42
+ username: args.username ?? "alice",
43
+ accountId: `0x${"00".repeat(32)}`,
44
+ signature
45
+ };
46
+ },
47
+ onAccountChange(callback) {
48
+ listeners.add(callback);
49
+ return () => {
50
+ listeners.delete(callback);
51
+ };
52
+ },
53
+ getProductAccount() {
54
+ return options?.productAccount !== void 0 ? options.productAccount : selected;
55
+ },
56
+ getAnonymousAlias() {
57
+ return options?.anonymousAlias ?? null;
58
+ },
59
+ async createProof() {
60
+ return signature;
61
+ }
62
+ };
63
+ }
64
+ function makeFakeLocalStorage() {
65
+ const host = createFakeHostLocalStorage();
66
+ return {
67
+ async get(key) {
68
+ return await host.readString(key) || null;
69
+ },
70
+ async set(key, value) {
71
+ await host.writeString(key, value);
72
+ },
73
+ async getJSON(key) {
74
+ return await host.readJSON(key) ?? null;
75
+ },
76
+ async setJSON(key, value) {
77
+ await host.writeJSON(key, value);
78
+ },
79
+ async remove(key) {
80
+ await host.clear(key);
81
+ },
82
+ async clear() {
83
+ host.reset();
84
+ }
85
+ };
86
+ }
87
+ function makeFakeCloudStorage() {
88
+ const store = /* @__PURE__ */ new Map();
89
+ const toBytes = (data) => typeof data === "string" ? textEncoder.encode(data) : data;
90
+ const cidOf = (bytes) => {
91
+ let h = 2166136261;
92
+ for (const b of bytes) {
93
+ h ^= b;
94
+ h = Math.imul(h, 16777619);
95
+ }
96
+ return `bafyfake${(h >>> 0).toString(16).padStart(8, "0")}`;
97
+ };
98
+ return {
99
+ async upload(data) {
100
+ const bytes = toBytes(data);
101
+ const cid = cidOf(bytes);
102
+ store.set(cid, bytes);
103
+ return ok(cid);
104
+ },
105
+ async fetch(cid) {
106
+ const bytes = store.get(cid);
107
+ if (!bytes) {
108
+ return err(
109
+ new ProductCloudStorageError(
110
+ `fake cloud storage: no object stored for CID ${cid}`
111
+ )
112
+ );
113
+ }
114
+ return ok(bytes);
115
+ },
116
+ async computeCid(data) {
117
+ return cidOf(toBytes(data));
118
+ }
119
+ };
120
+ }
121
+ function makeUnconfiguredChain() {
122
+ const fail = () => {
123
+ throw new Error(
124
+ "createFakeApp: `chain` is not configured. Pass a `chain` override in options. There is no chain-read fake \u2014 the host owns RPC selection; test chain-reading logic behind your own interface and cover the real wiring in e2e."
125
+ );
126
+ };
127
+ return {
128
+ getClient: fail,
129
+ getRawClient: fail,
130
+ connect: fail,
131
+ isConnected: () => false,
132
+ destroyAll: () => {
133
+ }
134
+ };
135
+ }
136
+ function createFakeApp(options) {
137
+ const name = options?.name ?? "fake-app";
138
+ return {
139
+ wallet: makeFakeWallet(options?.wallet),
140
+ localStorage: options?.localStorage ?? makeFakeLocalStorage(),
141
+ chain: options?.chain ?? makeUnconfiguredChain(),
142
+ cloudStorage: options?.cloudStorage !== void 0 ? options.cloudStorage : makeFakeCloudStorage(),
143
+ getAppInfo: () => ({ name })
144
+ };
145
+ }
146
+
147
+ export { createFakeApp };
148
+ //# sourceMappingURL=index.js.map
149
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test fakes for `@parity/product-sdk` (umbrella).\n *\n * `createFakeApp` returns a fake {@link App} — use it directly in logic tests or\n * hand it to `ProductSDKContext.Provider` for React component tests, so code that\n * calls `useProductSDK()` / `useWallet()` runs with no host. This entry also\n * re-exports every package's fakes, for a single meta-package import.\n *\n * @packageDocumentation\n */\nimport { ProductCloudStorageError } from \"@parity/product-sdk-cloud-storage\";\nimport { createFakeHostLocalStorage } from \"@parity/product-sdk-local-storage/testing\";\nimport { err, ok, unwrapErr, unwrapOk } from \"@parity/result\";\n\nimport type {\n Account,\n App,\n ChainApi,\n CloudStorageApi,\n LocalStorageApi,\n WalletApi,\n} from \"./core/types.js\";\n\nconst textEncoder = new TextEncoder();\n\n// Re-export the per-package fakes so `@parity/product-sdk/testing` is a one-stop import.\n// statement-store is deliberately absent: the umbrella doesn't wrap that package, so apps\n// using it depend on it directly — import `@parity/product-sdk-statement-store/testing`\n// there instead. Re-exporting its fake here would add a dependency the umbrella otherwise\n// doesn't have and could skew against the version the app actually installs.\nexport * from \"@parity/product-sdk-local-storage/testing\";\nexport * from \"@parity/product-sdk-signer/testing\";\nexport * from \"@parity/product-sdk-contracts/testing\";\nexport * from \"@parity/product-sdk-host/testing\";\n\n/** Configurable behavior for the fake {@link WalletApi}. */\nexport interface FakeWalletOptions {\n /** Accounts `connect()` / `getAccounts()` expose. Default: none. */\n accounts?: Account[];\n /** Initially selected account. Default: the first account, or `null`. */\n selected?: Account | null;\n /** Bytes returned by `signMessage` / `createProof`. Default: 64 zero bytes. */\n signature?: Uint8Array;\n /** Value returned by `getProductAccount()`. Default: the selected account. */\n productAccount?: Account | null;\n /** Value returned by `getAnonymousAlias()`. Default: `null`. */\n anonymousAlias?: string | null;\n}\n\n/** Options for {@link createFakeApp}. */\nexport interface CreateFakeAppOptions {\n /** App name reported by `getAppInfo()`. Default: `\"fake-app\"`. */\n name?: string;\n /** Configure the fake wallet. */\n wallet?: FakeWalletOptions;\n /** Override the local-storage API. Default: in-memory (backed by `createFakeHostLocalStorage`). */\n localStorage?: LocalStorageApi;\n /** Override the chain API. Default: unconfigured (throws until you provide one). */\n chain?: ChainApi;\n /** Override cloud storage, or pass `null` to disable it. Default: an in-memory fake. */\n cloudStorage?: CloudStorageApi | null;\n}\n\nfunction makeFakeWallet(options?: FakeWalletOptions): WalletApi {\n const accounts = options?.accounts ?? [];\n let selected = options?.selected !== undefined ? options.selected : (accounts[0] ?? null);\n const signature = options?.signature ?? new Uint8Array(64);\n const listeners = new Set<(account: Account | null) => void>();\n const notify = () => {\n for (const l of listeners) l(selected);\n };\n\n return {\n async connect() {\n return { accounts };\n },\n async disconnect() {\n selected = null;\n notify();\n },\n getAccounts() {\n return accounts;\n },\n getSelectedAccount() {\n return selected;\n },\n selectAccount(address) {\n selected = accounts.find((a) => a.address === address) ?? null;\n notify();\n },\n async signMessage() {\n return signature;\n },\n async signMessageWithDotNsIdentity(args) {\n return {\n username: args.username ?? \"alice\",\n accountId: `0x${\"00\".repeat(32)}`,\n signature,\n };\n },\n onAccountChange(callback) {\n listeners.add(callback);\n return () => {\n listeners.delete(callback);\n };\n },\n getProductAccount() {\n return options?.productAccount !== undefined ? options.productAccount : selected;\n },\n getAnonymousAlias() {\n return options?.anonymousAlias ?? null;\n },\n async createProof() {\n return signature;\n },\n };\n}\n\nfunction makeFakeLocalStorage(): LocalStorageApi {\n // Compose the local-storage fake; mirror createApp's adapter semantics\n // (get normalizes \"\" -> null, getJSON normalizes undefined -> null).\n const host = createFakeHostLocalStorage();\n return {\n async get(key) {\n return (await host.readString(key)) || null;\n },\n async set(key, value) {\n await host.writeString(key, value);\n },\n async getJSON<T>(key: string) {\n return ((await host.readJSON(key)) ?? null) as T | null;\n },\n async setJSON<T>(key: string, value: T) {\n await host.writeJSON(key, value);\n },\n async remove(key) {\n await host.clear(key);\n },\n async clear() {\n // Unlike the production adapter (a no-op in container mode), the fake\n // actually empties, so tests get real isolation.\n host.reset();\n },\n };\n}\n\nfunction makeFakeCloudStorage(): CloudStorageApi {\n const store = new Map<string, Uint8Array>();\n const toBytes = (data: string | Uint8Array) =>\n typeof data === \"string\" ? textEncoder.encode(data) : data;\n // Deterministic content-addressed id so upload / computeCid / fetch line up.\n const cidOf = (bytes: Uint8Array) => {\n let h = 0x811c9dc5;\n for (const b of bytes) {\n h ^= b;\n h = Math.imul(h, 0x01000193);\n }\n return `bafyfake${(h >>> 0).toString(16).padStart(8, \"0\")}`;\n };\n return {\n async upload(data) {\n const bytes = toBytes(data);\n const cid = cidOf(bytes);\n store.set(cid, bytes);\n return ok(cid);\n },\n async fetch(cid) {\n const bytes = store.get(cid);\n if (!bytes) {\n return err(\n new ProductCloudStorageError(\n `fake cloud storage: no object stored for CID ${cid}`,\n ),\n );\n }\n return ok(bytes);\n },\n async computeCid(data) {\n return cidOf(toBytes(data));\n },\n };\n}\n\nfunction makeUnconfiguredChain(): ChainApi {\n const fail = (): never => {\n throw new Error(\n \"createFakeApp: `chain` is not configured. Pass a `chain` override in options. \" +\n \"There is no chain-read fake — the host owns RPC selection; test chain-reading \" +\n \"logic behind your own interface and cover the real wiring in e2e.\",\n );\n };\n // Reads throw (fail fast, no silent undefined); state queries stay quiet.\n return {\n getClient: fail,\n getRawClient: fail,\n connect: fail,\n isConnected: () => false,\n destroyAll: () => {},\n };\n}\n\n/**\n * Create a fake {@link App}. Wallet, local-storage, and cloud-storage are working\n * in-memory fakes; `chain` is unconfigured until you override it. Every sub-API\n * is overridable.\n *\n * @example\n * ```ts\n * import { createFakeApp } from \"@parity/product-sdk/testing\";\n *\n * const app = createFakeApp({ wallet: { accounts: [alice, bob], selected: alice } });\n * await app.wallet.connect();\n * expect(app.wallet.getAccounts()).toHaveLength(2);\n * ```\n *\n * For React, feed the fake to the exported context:\n * ```tsx\n * import { ProductSDKContext } from \"@parity/product-sdk/react\";\n * render(<ProductSDKContext.Provider value={app}><AccountPicker /></ProductSDKContext.Provider>);\n * ```\n */\nexport function createFakeApp(options?: CreateFakeAppOptions): App {\n const name = options?.name ?? \"fake-app\";\n return {\n wallet: makeFakeWallet(options?.wallet),\n localStorage: options?.localStorage ?? makeFakeLocalStorage(),\n chain: options?.chain ?? makeUnconfiguredChain(),\n cloudStorage:\n options?.cloudStorage !== undefined ? options.cloudStorage : makeFakeCloudStorage(),\n getAppInfo: () => ({ name }),\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect } = import.meta.vitest;\n\n const alice: Account = { address: \"5Alice\", name: \"Alice\", source: \"dev\" };\n const bob: Account = { address: \"5Bob\", name: \"Bob\", source: \"dev\" };\n\n describe(\"createFakeApp\", () => {\n test(\"wallet lists, selects, and notifies on account change\", async () => {\n const app = createFakeApp({ wallet: { accounts: [alice, bob], selected: alice } });\n expect(app.wallet.getAccounts()).toHaveLength(2);\n expect(app.wallet.getSelectedAccount()).toEqual(alice);\n\n let changed: Account | null = null;\n app.wallet.onAccountChange((a) => {\n changed = a;\n });\n app.wallet.selectAccount(\"5Bob\");\n expect(app.wallet.getSelectedAccount()).toEqual(bob);\n expect(changed).toEqual(bob);\n\n const { accounts } = await app.wallet.connect();\n expect(accounts).toHaveLength(2);\n });\n\n test(\"localStorage round-trips and clear empties it\", async () => {\n const app = createFakeApp();\n await app.localStorage.set(\"k\", \"v\");\n await app.localStorage.setJSON(\"o\", { n: 1 });\n expect(await app.localStorage.get(\"k\")).toBe(\"v\");\n expect(await app.localStorage.getJSON(\"o\")).toEqual({ n: 1 });\n expect(await app.localStorage.get(\"missing\")).toBeNull();\n\n await app.localStorage.clear();\n expect(await app.localStorage.get(\"k\")).toBeNull();\n });\n\n test(\"cloudStorage round-trips upload -> fetch; computeCid matches upload\", async () => {\n const cs = createFakeApp().cloudStorage;\n if (!cs) throw new Error(\"expected default cloud storage\");\n const cid = unwrapOk(await cs.upload(\"hello\"));\n expect(await cs.computeCid(\"hello\")).toBe(cid);\n expect(new TextDecoder().decode(unwrapOk(await cs.fetch(cid)))).toBe(\"hello\");\n expect(unwrapErr(await cs.fetch(\"bafyfakemissing\"))).toBeInstanceOf(\n ProductCloudStorageError,\n );\n });\n\n test(\"cloudStorage can be disabled with null\", () => {\n expect(createFakeApp({ cloudStorage: null }).cloudStorage).toBeNull();\n });\n\n test(\"chain is unconfigured by default\", () => {\n const app = createFakeApp();\n expect(app.chain.isConnected({} as never)).toBe(false);\n expect(() => app.chain.getClient({} as never)).toThrow(/not configured/);\n });\n\n test(\"getAppInfo reports the name\", () => {\n expect(createFakeApp({ name: \"my-app\" }).getAppInfo().name).toBe(\"my-app\");\n });\n });\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { LogLevel } from '@parity/product-sdk-logger';
2
- import { CloudStorageEnvironment } from '@parity/product-sdk-cloud-storage';
2
+ import { ProductCloudStorageError, CloudStorageEnvironment } from '@parity/product-sdk-cloud-storage';
3
+ import { Result } from '@parity/result';
3
4
  import { ChainClient } from '@parity/product-sdk-chain-client';
4
5
  import { P as PeopleUsernameChain } from './dotns-hLSvmo6u.js';
5
6
  import { ChainDefinition, TypedApi, PolkadotClient } from 'polkadot-api';
@@ -122,16 +123,23 @@ interface CloudStorageApi {
122
123
  /**
123
124
  * Upload data to the Cloud.
124
125
  *
125
- * Requires a wallet to be connected and an account selected. Throws
126
- * "No signer available …" otherwise.
126
+ * Requires a wallet to be connected and an account selected.
127
+ *
128
+ * @returns A {@link Result}: `ok(cid)` on success, or `err(ProductCloudStorageError)`
129
+ * if the upload fails (no signer, network rejection, or no CID returned).
130
+ */
131
+ upload(data: string | Uint8Array): Promise<Result<string, ProductCloudStorageError>>;
132
+ /**
133
+ * Fetch data by CID.
134
+ *
135
+ * @returns A {@link Result}: `ok(bytes)` on success, or `err(ProductCloudStorageError)`.
127
136
  */
128
- upload(data: string | Uint8Array): Promise<string>;
129
- /** Fetch data by CID. */
130
- fetch(cid: string): Promise<Uint8Array>;
137
+ fetch(cid: string): Promise<Result<Uint8Array, ProductCloudStorageError>>;
131
138
  /**
132
139
  * Compute the CID for data without uploading.
133
140
  *
134
- * Async because the underlying hash is computed via Web Crypto.
141
+ * Async because the underlying hash is computed via Web Crypto. Does not
142
+ * perform I/O, so it throws on invalid input rather than returning a Result.
135
143
  */
136
144
  computeCid(data: string | Uint8Array): Promise<string>;
137
145
  }
@@ -200,4 +208,4 @@ interface DotNsIdentitySignature {
200
208
  signature: Uint8Array;
201
209
  }
202
210
 
203
- export type { App as A, ChainApi as C, DotNsIdentitySignature as D, LocalStorageApi as L, SignMessageWithDotNsIdentityArgs as S, WalletApi as W, Account as a, AppConfig as b, CloudStorageApi as c, CloudStorageConfig as d };
211
+ export type { Account as A, ChainApi as C, DotNsIdentitySignature as D, LocalStorageApi as L, SignMessageWithDotNsIdentityArgs as S, WalletApi as W, CloudStorageApi as a, App as b, AppConfig as c, CloudStorageConfig as d };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parity/product-sdk",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Unified SDK for building products in the Polkadot ecosystem - umbrella package",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -55,6 +55,10 @@
55
55
  "./wallet": {
56
56
  "import": "./dist/wallet/index.js",
57
57
  "types": "./dist/wallet/index.d.ts"
58
+ },
59
+ "./testing": {
60
+ "import": "./dist/testing/index.js",
61
+ "types": "./dist/testing/index.d.ts"
58
62
  }
59
63
  },
60
64
  "files": [
@@ -64,16 +68,18 @@
64
68
  "dependencies": {
65
69
  "polkadot-api": "^2.1.6",
66
70
  "@parity/product-sdk-address": "0.1.1",
67
- "@parity/product-sdk-host": "0.12.0",
71
+ "@parity/product-sdk-cloud-storage": "0.7.0",
72
+ "@parity/product-sdk-contracts": "0.9.0",
73
+ "@parity/product-sdk-chain-client": "0.8.0",
74
+ "@parity/product-sdk-host": "0.13.0",
68
75
  "@parity/product-sdk-crypto": "0.1.1",
69
- "@parity/product-sdk-cloud-storage": "0.6.7",
70
- "@parity/product-sdk-contracts": "0.8.3",
71
- "@parity/product-sdk-chain-client": "0.7.7",
72
- "@parity/product-sdk-keys": "0.3.13",
73
- "@parity/product-sdk-local-storage": "0.2.12",
76
+ "@parity/product-sdk-keys": "0.3.14",
74
77
  "@parity/product-sdk-logger": "0.1.1",
75
- "@parity/product-sdk-signer": "0.9.0",
76
- "@parity/product-sdk-tx": "0.2.17"
78
+ "@parity/product-sdk-errors": "0.2.0",
79
+ "@parity/product-sdk-signer": "0.10.0",
80
+ "@parity/product-sdk-local-storage": "0.3.0",
81
+ "@parity/result": "0.2.0",
82
+ "@parity/product-sdk-tx": "0.3.0"
77
83
  },
78
84
  "peerDependencies": {
79
85
  "react": "^18.0.0 || ^19.0.0"
@@ -100,6 +106,7 @@
100
106
  "license": "Apache-2.0",
101
107
  "scripts": {
102
108
  "build": "NODE_OPTIONS=--max-old-space-size=8192 tsup",
109
+ "test": "vitest run",
103
110
  "clean": "rm -rf dist"
104
111
  }
105
112
  }
@@ -21,9 +21,11 @@ import { createLocalKvStore } from "@parity/product-sdk-local-storage";
21
21
  import { SignerManager } from "@parity/product-sdk-signer";
22
22
  import {
23
23
  CloudStorageClient,
24
+ ProductCloudStorageError,
24
25
  calculateCid,
25
26
  createLazySigner,
26
27
  } from "@parity/product-sdk-cloud-storage";
28
+ import { err, ok } from "@parity/result";
27
29
  import {
28
30
  createChainClient,
29
31
  getClient,
@@ -170,20 +172,28 @@ export async function createApp(config: AppConfig): Promise<App> {
170
172
  ? {
171
173
  upload: async (data) => {
172
174
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
173
- // Explicitly request a DAG-PB manifest so chunked uploads always
174
- // resolve to a single root CID. Without this, AsyncBulletinClient
175
- // can return `result.cid: undefined` for chunked-without-manifest
176
- // uploads — but CloudStorageApi.upload promises a string return, and
177
- // app consumers expect a CID they can hand to `fetch(cid)`. Keep
178
- // the defensive null-check below as belt-and-braces in case the
179
- // upstream contract shifts.
180
- const result = await cloudStorageClient.store(bytes).withManifest(true).send();
181
- if (!result.cid) {
182
- throw new Error(
183
- "Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted — file an issue.",
184
- );
175
+ try {
176
+ // Explicitly request a DAG-PB manifest so chunked uploads always
177
+ // resolve to a single root CID. Without this, AsyncBulletinClient
178
+ // can return `result.cid: undefined` for chunked-without-manifest
179
+ // uploads — but app consumers expect a CID they can hand to
180
+ // `fetch(cid)`, so treat a missing CID as an error.
181
+ const uploaded = await cloudStorageClient
182
+ .store(bytes)
183
+ .withManifest(true)
184
+ .send();
185
+ if (!uploaded.cid) {
186
+ return err(
187
+ new ProductCloudStorageError(
188
+ "Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted — file an issue.",
189
+ ),
190
+ );
191
+ }
192
+ return ok(uploaded.cid.toString());
193
+ } catch (cause) {
194
+ const message = cause instanceof Error ? cause.message : String(cause);
195
+ return err(new ProductCloudStorageError(message, { cause }));
185
196
  }
186
- return result.cid.toString();
187
197
  },
188
198
  fetch: (cid) => cloudStorageClient.fetchBytes(cid),
189
199
  computeCid: async (data) => {
package/src/core/types.ts CHANGED
@@ -5,7 +5,11 @@
5
5
  */
6
6
 
7
7
  import type { LogLevel } from "@parity/product-sdk-logger";
8
- import type { CloudStorageEnvironment } from "@parity/product-sdk-cloud-storage";
8
+ import type {
9
+ CloudStorageEnvironment,
10
+ ProductCloudStorageError,
11
+ } from "@parity/product-sdk-cloud-storage";
12
+ import type { Result } from "@parity/result";
9
13
  import type { ChainClient } from "@parity/product-sdk-chain-client";
10
14
  import type { PeopleUsernameChain } from "../identity/dotns.js";
11
15
  import type { ChainDefinition, TypedApi, PolkadotClient } from "polkadot-api";
@@ -136,16 +140,23 @@ export interface CloudStorageApi {
136
140
  /**
137
141
  * Upload data to the Cloud.
138
142
  *
139
- * Requires a wallet to be connected and an account selected. Throws
140
- * "No signer available …" otherwise.
143
+ * Requires a wallet to be connected and an account selected.
144
+ *
145
+ * @returns A {@link Result}: `ok(cid)` on success, or `err(ProductCloudStorageError)`
146
+ * if the upload fails (no signer, network rejection, or no CID returned).
147
+ */
148
+ upload(data: string | Uint8Array): Promise<Result<string, ProductCloudStorageError>>;
149
+ /**
150
+ * Fetch data by CID.
151
+ *
152
+ * @returns A {@link Result}: `ok(bytes)` on success, or `err(ProductCloudStorageError)`.
141
153
  */
142
- upload(data: string | Uint8Array): Promise<string>;
143
- /** Fetch data by CID. */
144
- fetch(cid: string): Promise<Uint8Array>;
154
+ fetch(cid: string): Promise<Result<Uint8Array, ProductCloudStorageError>>;
145
155
  /**
146
156
  * Compute the CID for data without uploading.
147
157
  *
148
- * Async because the underlying hash is computed via Web Crypto.
158
+ * Async because the underlying hash is computed via Web Crypto. Does not
159
+ * perform I/O, so it throws on invalid input rather than returning a Result.
149
160
  */
150
161
  computeCid(data: string | Uint8Array): Promise<string>;
151
162
  }
package/src/index.ts CHANGED
@@ -45,6 +45,14 @@ export type {
45
45
  } from "./core/types.js";
46
46
  export type { LogEntry, LogHandler, LoggerConfig, Logger } from "./core/logger.js";
47
47
 
48
+ // The throw→Result model. Every fallible SDK operation returns `Result<T, E>`.
49
+ // `Result`/`ok`/`err`/`isErrorOf` come from the generic `@parity/result`; the
50
+ // cross-package `SdkError` marker (`isSdkError` recognizes any error raised by
51
+ // an `@parity/product-sdk-*` package) lives in `@parity/product-sdk-errors`.
52
+ export { ok, err, isErrorOf } from "@parity/result";
53
+ export type { Result } from "@parity/result";
54
+ export { type SdkError, isSdkError } from "@parity/product-sdk-errors";
55
+
48
56
  // Re-export common utilities from leaf packages
49
57
  export { isInsideContainer, isInsideContainerSync } from "@parity/product-sdk-host";
50
58
  export { createChainClient } from "@parity/product-sdk-chain-client";
package/src/testing.ts ADDED
@@ -0,0 +1,297 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Test fakes for `@parity/product-sdk` (umbrella).
5
+ *
6
+ * `createFakeApp` returns a fake {@link App} — use it directly in logic tests or
7
+ * hand it to `ProductSDKContext.Provider` for React component tests, so code that
8
+ * calls `useProductSDK()` / `useWallet()` runs with no host. This entry also
9
+ * re-exports every package's fakes, for a single meta-package import.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { ProductCloudStorageError } from "@parity/product-sdk-cloud-storage";
14
+ import { createFakeHostLocalStorage } from "@parity/product-sdk-local-storage/testing";
15
+ import { err, ok, unwrapErr, unwrapOk } from "@parity/result";
16
+
17
+ import type {
18
+ Account,
19
+ App,
20
+ ChainApi,
21
+ CloudStorageApi,
22
+ LocalStorageApi,
23
+ WalletApi,
24
+ } from "./core/types.js";
25
+
26
+ const textEncoder = new TextEncoder();
27
+
28
+ // Re-export the per-package fakes so `@parity/product-sdk/testing` is a one-stop import.
29
+ // statement-store is deliberately absent: the umbrella doesn't wrap that package, so apps
30
+ // using it depend on it directly — import `@parity/product-sdk-statement-store/testing`
31
+ // there instead. Re-exporting its fake here would add a dependency the umbrella otherwise
32
+ // doesn't have and could skew against the version the app actually installs.
33
+ export * from "@parity/product-sdk-local-storage/testing";
34
+ export * from "@parity/product-sdk-signer/testing";
35
+ export * from "@parity/product-sdk-contracts/testing";
36
+ export * from "@parity/product-sdk-host/testing";
37
+
38
+ /** Configurable behavior for the fake {@link WalletApi}. */
39
+ export interface FakeWalletOptions {
40
+ /** Accounts `connect()` / `getAccounts()` expose. Default: none. */
41
+ accounts?: Account[];
42
+ /** Initially selected account. Default: the first account, or `null`. */
43
+ selected?: Account | null;
44
+ /** Bytes returned by `signMessage` / `createProof`. Default: 64 zero bytes. */
45
+ signature?: Uint8Array;
46
+ /** Value returned by `getProductAccount()`. Default: the selected account. */
47
+ productAccount?: Account | null;
48
+ /** Value returned by `getAnonymousAlias()`. Default: `null`. */
49
+ anonymousAlias?: string | null;
50
+ }
51
+
52
+ /** Options for {@link createFakeApp}. */
53
+ export interface CreateFakeAppOptions {
54
+ /** App name reported by `getAppInfo()`. Default: `"fake-app"`. */
55
+ name?: string;
56
+ /** Configure the fake wallet. */
57
+ wallet?: FakeWalletOptions;
58
+ /** Override the local-storage API. Default: in-memory (backed by `createFakeHostLocalStorage`). */
59
+ localStorage?: LocalStorageApi;
60
+ /** Override the chain API. Default: unconfigured (throws until you provide one). */
61
+ chain?: ChainApi;
62
+ /** Override cloud storage, or pass `null` to disable it. Default: an in-memory fake. */
63
+ cloudStorage?: CloudStorageApi | null;
64
+ }
65
+
66
+ function makeFakeWallet(options?: FakeWalletOptions): WalletApi {
67
+ const accounts = options?.accounts ?? [];
68
+ let selected = options?.selected !== undefined ? options.selected : (accounts[0] ?? null);
69
+ const signature = options?.signature ?? new Uint8Array(64);
70
+ const listeners = new Set<(account: Account | null) => void>();
71
+ const notify = () => {
72
+ for (const l of listeners) l(selected);
73
+ };
74
+
75
+ return {
76
+ async connect() {
77
+ return { accounts };
78
+ },
79
+ async disconnect() {
80
+ selected = null;
81
+ notify();
82
+ },
83
+ getAccounts() {
84
+ return accounts;
85
+ },
86
+ getSelectedAccount() {
87
+ return selected;
88
+ },
89
+ selectAccount(address) {
90
+ selected = accounts.find((a) => a.address === address) ?? null;
91
+ notify();
92
+ },
93
+ async signMessage() {
94
+ return signature;
95
+ },
96
+ async signMessageWithDotNsIdentity(args) {
97
+ return {
98
+ username: args.username ?? "alice",
99
+ accountId: `0x${"00".repeat(32)}`,
100
+ signature,
101
+ };
102
+ },
103
+ onAccountChange(callback) {
104
+ listeners.add(callback);
105
+ return () => {
106
+ listeners.delete(callback);
107
+ };
108
+ },
109
+ getProductAccount() {
110
+ return options?.productAccount !== undefined ? options.productAccount : selected;
111
+ },
112
+ getAnonymousAlias() {
113
+ return options?.anonymousAlias ?? null;
114
+ },
115
+ async createProof() {
116
+ return signature;
117
+ },
118
+ };
119
+ }
120
+
121
+ function makeFakeLocalStorage(): LocalStorageApi {
122
+ // Compose the local-storage fake; mirror createApp's adapter semantics
123
+ // (get normalizes "" -> null, getJSON normalizes undefined -> null).
124
+ const host = createFakeHostLocalStorage();
125
+ return {
126
+ async get(key) {
127
+ return (await host.readString(key)) || null;
128
+ },
129
+ async set(key, value) {
130
+ await host.writeString(key, value);
131
+ },
132
+ async getJSON<T>(key: string) {
133
+ return ((await host.readJSON(key)) ?? null) as T | null;
134
+ },
135
+ async setJSON<T>(key: string, value: T) {
136
+ await host.writeJSON(key, value);
137
+ },
138
+ async remove(key) {
139
+ await host.clear(key);
140
+ },
141
+ async clear() {
142
+ // Unlike the production adapter (a no-op in container mode), the fake
143
+ // actually empties, so tests get real isolation.
144
+ host.reset();
145
+ },
146
+ };
147
+ }
148
+
149
+ function makeFakeCloudStorage(): CloudStorageApi {
150
+ const store = new Map<string, Uint8Array>();
151
+ const toBytes = (data: string | Uint8Array) =>
152
+ typeof data === "string" ? textEncoder.encode(data) : data;
153
+ // Deterministic content-addressed id so upload / computeCid / fetch line up.
154
+ const cidOf = (bytes: Uint8Array) => {
155
+ let h = 0x811c9dc5;
156
+ for (const b of bytes) {
157
+ h ^= b;
158
+ h = Math.imul(h, 0x01000193);
159
+ }
160
+ return `bafyfake${(h >>> 0).toString(16).padStart(8, "0")}`;
161
+ };
162
+ return {
163
+ async upload(data) {
164
+ const bytes = toBytes(data);
165
+ const cid = cidOf(bytes);
166
+ store.set(cid, bytes);
167
+ return ok(cid);
168
+ },
169
+ async fetch(cid) {
170
+ const bytes = store.get(cid);
171
+ if (!bytes) {
172
+ return err(
173
+ new ProductCloudStorageError(
174
+ `fake cloud storage: no object stored for CID ${cid}`,
175
+ ),
176
+ );
177
+ }
178
+ return ok(bytes);
179
+ },
180
+ async computeCid(data) {
181
+ return cidOf(toBytes(data));
182
+ },
183
+ };
184
+ }
185
+
186
+ function makeUnconfiguredChain(): ChainApi {
187
+ const fail = (): never => {
188
+ throw new Error(
189
+ "createFakeApp: `chain` is not configured. Pass a `chain` override in options. " +
190
+ "There is no chain-read fake — the host owns RPC selection; test chain-reading " +
191
+ "logic behind your own interface and cover the real wiring in e2e.",
192
+ );
193
+ };
194
+ // Reads throw (fail fast, no silent undefined); state queries stay quiet.
195
+ return {
196
+ getClient: fail,
197
+ getRawClient: fail,
198
+ connect: fail,
199
+ isConnected: () => false,
200
+ destroyAll: () => {},
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Create a fake {@link App}. Wallet, local-storage, and cloud-storage are working
206
+ * in-memory fakes; `chain` is unconfigured until you override it. Every sub-API
207
+ * is overridable.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * import { createFakeApp } from "@parity/product-sdk/testing";
212
+ *
213
+ * const app = createFakeApp({ wallet: { accounts: [alice, bob], selected: alice } });
214
+ * await app.wallet.connect();
215
+ * expect(app.wallet.getAccounts()).toHaveLength(2);
216
+ * ```
217
+ *
218
+ * For React, feed the fake to the exported context:
219
+ * ```tsx
220
+ * import { ProductSDKContext } from "@parity/product-sdk/react";
221
+ * render(<ProductSDKContext.Provider value={app}><AccountPicker /></ProductSDKContext.Provider>);
222
+ * ```
223
+ */
224
+ export function createFakeApp(options?: CreateFakeAppOptions): App {
225
+ const name = options?.name ?? "fake-app";
226
+ return {
227
+ wallet: makeFakeWallet(options?.wallet),
228
+ localStorage: options?.localStorage ?? makeFakeLocalStorage(),
229
+ chain: options?.chain ?? makeUnconfiguredChain(),
230
+ cloudStorage:
231
+ options?.cloudStorage !== undefined ? options.cloudStorage : makeFakeCloudStorage(),
232
+ getAppInfo: () => ({ name }),
233
+ };
234
+ }
235
+
236
+ if (import.meta.vitest) {
237
+ const { describe, test, expect } = import.meta.vitest;
238
+
239
+ const alice: Account = { address: "5Alice", name: "Alice", source: "dev" };
240
+ const bob: Account = { address: "5Bob", name: "Bob", source: "dev" };
241
+
242
+ describe("createFakeApp", () => {
243
+ test("wallet lists, selects, and notifies on account change", async () => {
244
+ const app = createFakeApp({ wallet: { accounts: [alice, bob], selected: alice } });
245
+ expect(app.wallet.getAccounts()).toHaveLength(2);
246
+ expect(app.wallet.getSelectedAccount()).toEqual(alice);
247
+
248
+ let changed: Account | null = null;
249
+ app.wallet.onAccountChange((a) => {
250
+ changed = a;
251
+ });
252
+ app.wallet.selectAccount("5Bob");
253
+ expect(app.wallet.getSelectedAccount()).toEqual(bob);
254
+ expect(changed).toEqual(bob);
255
+
256
+ const { accounts } = await app.wallet.connect();
257
+ expect(accounts).toHaveLength(2);
258
+ });
259
+
260
+ test("localStorage round-trips and clear empties it", async () => {
261
+ const app = createFakeApp();
262
+ await app.localStorage.set("k", "v");
263
+ await app.localStorage.setJSON("o", { n: 1 });
264
+ expect(await app.localStorage.get("k")).toBe("v");
265
+ expect(await app.localStorage.getJSON("o")).toEqual({ n: 1 });
266
+ expect(await app.localStorage.get("missing")).toBeNull();
267
+
268
+ await app.localStorage.clear();
269
+ expect(await app.localStorage.get("k")).toBeNull();
270
+ });
271
+
272
+ test("cloudStorage round-trips upload -> fetch; computeCid matches upload", async () => {
273
+ const cs = createFakeApp().cloudStorage;
274
+ if (!cs) throw new Error("expected default cloud storage");
275
+ const cid = unwrapOk(await cs.upload("hello"));
276
+ expect(await cs.computeCid("hello")).toBe(cid);
277
+ expect(new TextDecoder().decode(unwrapOk(await cs.fetch(cid)))).toBe("hello");
278
+ expect(unwrapErr(await cs.fetch("bafyfakemissing"))).toBeInstanceOf(
279
+ ProductCloudStorageError,
280
+ );
281
+ });
282
+
283
+ test("cloudStorage can be disabled with null", () => {
284
+ expect(createFakeApp({ cloudStorage: null }).cloudStorage).toBeNull();
285
+ });
286
+
287
+ test("chain is unconfigured by default", () => {
288
+ const app = createFakeApp();
289
+ expect(app.chain.isConnected({} as never)).toBe(false);
290
+ expect(() => app.chain.getClient({} as never)).toThrow(/not configured/);
291
+ });
292
+
293
+ test("getAppInfo reports the name", () => {
294
+ expect(createFakeApp({ name: "my-app" }).getAppInfo().name).toBe("my-app");
295
+ });
296
+ });
297
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/createApp.ts"],"names":[],"mappings":"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","file":"chunk-6MNKFIU6.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * createApp - Main entry point for the Product SDK\n *\n * Creates an App instance with wallet, storage, chain, and cloud storage APIs.\n */\n\nimport type { ChainDefinition } from \"polkadot-api\";\nimport type {\n App,\n AppConfig,\n WalletApi,\n ChainApi,\n Account,\n CloudStorageApi,\n LocalStorageApi,\n} from \"./types.js\";\nimport { configure, createLogger } from \"@parity/product-sdk-logger\";\nimport { createLocalKvStore } from \"@parity/product-sdk-local-storage\";\nimport { SignerManager } from \"@parity/product-sdk-signer\";\nimport {\n CloudStorageClient,\n calculateCid,\n createLazySigner,\n} from \"@parity/product-sdk-cloud-storage\";\nimport {\n createChainClient,\n getClient,\n isConnected,\n destroyAll,\n} from \"@parity/product-sdk-chain-client\";\nimport { getAccountsProvider } from \"@parity/product-sdk-host\";\nimport {\n accountIdHexToBytes,\n type PeopleUsernameQueryApi,\n resolvePeopleUsernameOwner,\n} from \"../identity/dotns.js\";\n\nconst log = createLogger(\"app\");\n\n/**\n * Create a new Product SDK app instance\n *\n * @param config - Application configuration\n * @returns App instance with all APIs\n *\n * @example\n * ```ts\n * import { createApp } from '@parity/product-sdk';\n *\n * // Default: cloud storage enabled with paseo environment\n * const app = await createApp({\n * name: 'my-app',\n * logLevel: 'info',\n * });\n *\n * // Custom cloud storage environment\n * const prodApp = await createApp({\n * name: 'my-app',\n * cloudStorage: { environment: 'polkadot' },\n * });\n *\n * // Disable cloud storage entirely\n * const noCloudStorageApp = await createApp({\n * name: 'my-app',\n * cloudStorage: false,\n * });\n *\n * // Connect wallet\n * const { accounts } = await app.wallet.connect();\n *\n * // Use storage\n * await app.localStorage.set('key', 'value');\n *\n * // Use cloud storage (check for null if it might be disabled)\n * if (app.cloudStorage) {\n * const cid = await app.cloudStorage.upload('hello world');\n * }\n * ```\n */\nexport async function createApp(config: AppConfig): Promise<App> {\n // Set log level if specified\n if (config.logLevel) {\n configure({ level: config.logLevel });\n }\n\n log.info(\"Creating Product SDK app\", { name: config.name });\n\n // Initialize storage (container-only - will throw if not in container)\n const localKvStore = await createLocalKvStore({ prefix: config.name });\n\n // Initialize signer manager\n const signerManager = new SignerManager({\n dappName: config.name,\n });\n\n // Initialize cloud storage client (configurable, defaults to paseo).\n //\n // The signer is wrapped lazily so the cloud storage client can be built before\n // an account is selected. Uploads will throw a clear error if no signer\n // is available at submission time. Reads (fetch / fetchJson) don't need\n // a signer, so they work regardless of whether an account is selected --\n // but they are container-only and throw CloudStorageHostUnavailableError\n // outside a host container (no IPFS-gateway fallback).\n const cloudStorageEnabled = config.cloudStorage !== false;\n const cloudStorageEnvironment =\n typeof config.cloudStorage === \"object\" ? config.cloudStorage.environment : \"paseo\";\n const cloudStorageClient = cloudStorageEnabled\n ? await CloudStorageClient.create({\n environment: cloudStorageEnvironment,\n signer: createLazySigner(() => signerManager.getSigner()),\n })\n : null;\n\n if (cloudStorageEnabled) {\n log.debug(\"Cloud Storage client (Bulletin) initialized\", {\n environment: cloudStorageEnvironment,\n });\n } else {\n log.debug(\"Cloud Storage client disabled\");\n }\n\n // Create storage API adapter\n const localStorageApi: LocalStorageApi = {\n get: (key) => localKvStore.get(key),\n set: (key, value) => localKvStore.set(key, value),\n getJSON: <T>(key: string) => localKvStore.getJSON<T>(key),\n setJSON: <T>(key: string, value: T) => localKvStore.setJSON(key, value),\n remove: (key) => localKvStore.remove(key),\n clear: async () => {\n // LocalKvStore doesn't have clear - this is a no-op\n log.debug(\"clear() is not supported in container storage mode\");\n },\n };\n\n // Create wallet API adapter\n const walletApi = createWalletApi(signerManager);\n\n // Create chain API\n const chainApi: ChainApi = {\n getClient(descriptor) {\n log.debug(\"getClient called\", { genesis: descriptor.genesis });\n const client = getClient(descriptor);\n return client.getTypedApi(descriptor);\n },\n\n getRawClient(descriptor) {\n log.debug(\"getRawClient called\", { genesis: descriptor.genesis });\n return getClient(descriptor);\n },\n\n async connect<T extends Record<string, ChainDefinition>>(chains: T) {\n log.debug(\"connect called\", { chains: Object.keys(chains) });\n return createChainClient({ chains });\n },\n\n isConnected(descriptor) {\n return isConnected(descriptor);\n },\n\n destroyAll() {\n log.debug(\"destroyAll called\");\n destroyAll();\n },\n };\n\n // Create Cloud Storage API adapter (null if disabled)\n const cloudStorageApi: CloudStorageApi | null = cloudStorageClient\n ? {\n upload: async (data) => {\n const bytes = typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n // Explicitly request a DAG-PB manifest so chunked uploads always\n // resolve to a single root CID. Without this, AsyncBulletinClient\n // can return `result.cid: undefined` for chunked-without-manifest\n // uploads — but CloudStorageApi.upload promises a string return, and\n // app consumers expect a CID they can hand to `fetch(cid)`. Keep\n // the defensive null-check below as belt-and-braces in case the\n // upstream contract shifts.\n const result = await cloudStorageClient.store(bytes).withManifest(true).send();\n if (!result.cid) {\n throw new Error(\n \"Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted — file an issue.\",\n );\n }\n return result.cid.toString();\n },\n fetch: (cid) => cloudStorageClient.fetchBytes(cid),\n computeCid: async (data) => {\n const bytes = typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n const cid = await calculateCid(bytes);\n return cid.toString();\n },\n }\n : null;\n\n log.info(\"Product SDK app created\", {\n name: config.name,\n cloudStorage: cloudStorageEnabled ? cloudStorageEnvironment : \"disabled\",\n });\n\n return {\n wallet: walletApi,\n localStorage: localStorageApi,\n chain: chainApi,\n cloudStorage: cloudStorageApi,\n getAppInfo: () => ({ ...config }),\n };\n}\n\n/**\n * Create wallet API adapter using SignerManager from leaf package\n */\nfunction createWalletApi(signerManager: SignerManager): WalletApi {\n // Track account change subscribers\n const accountChangeSubscribers = new Set<(account: Account | null) => void>();\n\n // Subscribe to signer manager state changes\n signerManager.subscribe((state) => {\n const account = state.selectedAccount\n ? {\n address: state.selectedAccount.address,\n name: state.selectedAccount.name ?? undefined,\n source: state.selectedAccount.source,\n }\n : null;\n for (const callback of accountChangeSubscribers) {\n try {\n callback(account);\n } catch (e) {\n log.warn(\"Account change callback threw\", { error: e });\n }\n }\n });\n\n return {\n async connect(): Promise<{ accounts: Account[] }> {\n const result = await signerManager.connect();\n if (!result.ok) {\n throw new Error(result.error.message);\n }\n return {\n accounts: result.value.map((a) => ({\n address: a.address,\n name: a.name ?? undefined,\n source: a.source,\n })),\n };\n },\n\n async disconnect(): Promise<void> {\n signerManager.disconnect();\n },\n\n getAccounts(): Account[] {\n return signerManager.getState().accounts.map((a) => ({\n address: a.address,\n name: a.name ?? undefined,\n source: a.source,\n }));\n },\n\n getSelectedAccount(): Account | null {\n const selected = signerManager.getState().selectedAccount;\n if (!selected) return null;\n return {\n address: selected.address,\n name: selected.name ?? undefined,\n source: selected.source,\n };\n },\n\n selectAccount(address: string): void {\n signerManager.selectAccount(address);\n },\n\n async signMessage(message: string | Uint8Array): Promise<Uint8Array> {\n const bytes = typeof message === \"string\" ? new TextEncoder().encode(message) : message;\n const result = await signerManager.signRaw(bytes);\n if (!result.ok) {\n throw new Error(result.error.message);\n }\n return result.value;\n },\n\n async signMessageWithDotNsIdentity(args) {\n const message =\n typeof args.message === \"string\"\n ? new TextEncoder().encode(args.message)\n : args.message;\n const accountsProvider = await getHostAccountsProvider();\n const username = args.username ?? (await getPrimaryUsername(accountsProvider));\n\n // Reuse an already-connected People chain when the caller has\n // wired one up via `app.chain.connect({ ..., <name>: peopleChain })`.\n // Fall back to opening a transient connection so first-time users\n // don't have to think about chain lifecycles. The chain-client\n // module caches by genesis fingerprint, so subsequent\n // signMessageWithDotNsIdentity calls share whichever connection\n // got established first.\n const peopleClient = isConnected(args.peopleChain)\n ? getClient(args.peopleChain)\n : await createChainClient({ chains: { people: args.peopleChain } }).then(\n (c) => c.raw.people,\n );\n // PAPI's `TypedApi.query.X.Y.getValue` is variadic\n // (`...args: [...WithCallOptions<Args>]`) so it's not assignable\n // to our deliberately-narrow `(key) => Promise<...>` shape. The\n // resolver only ever passes the storage key; the cast adapts the\n // wider PAPI surface to the minimum we depend on.\n const peopleApi = peopleClient.getTypedApi(\n args.peopleChain,\n ) as unknown as PeopleUsernameQueryApi;\n const accountId = await resolvePeopleUsernameOwner(username, peopleApi);\n if (!accountId) {\n throw new Error(`No account owns DotNS username \"${username}\"`);\n }\n\n const owner = accountIdHexToBytes(accountId);\n const signer = accountsProvider.getLegacyAccountSigner({\n publicKey: owner,\n name: username,\n });\n\n try {\n return {\n username,\n accountId,\n signature: await signer.signBytes(message),\n };\n } catch (cause) {\n throw new Error(\n `Failed to sign with DotNS username \"${username}\": ${\n cause instanceof Error ? cause.message : String(cause)\n }`,\n { cause },\n );\n }\n },\n\n onAccountChange(callback: (account: Account | null) => void): () => void {\n accountChangeSubscribers.add(callback);\n return () => accountChangeSubscribers.delete(callback);\n },\n\n getProductAccount(): Account | null {\n // Product accounts require async call - this sync API can't support it properly\n // Users should use SignerManager.getProductAccount() directly\n log.warn(\n \"getProductAccount() is deprecated - use SignerManager.getProductAccount() directly\",\n );\n return null;\n },\n\n getAnonymousAlias(): string | null {\n // Anonymous aliases require async call - this sync API can't support it properly\n // Users should use SignerManager.getProductAccountAlias() directly\n log.warn(\n \"getAnonymousAlias() is deprecated - use SignerManager.getProductAccountAlias() directly\",\n );\n return null;\n },\n\n async createProof(_message: Uint8Array): Promise<Uint8Array> {\n // Ring VRF proofs require SignerManager.createRingVRFProof() directly\n throw new Error(\n \"createProof() is not implemented in the App API. \" +\n \"Use SignerManager.createRingVRFProof() directly.\",\n );\n },\n };\n}\n\ntype HostAccountsProvider = NonNullable<Awaited<ReturnType<typeof getAccountsProvider>>>;\n\nasync function getHostAccountsProvider(): Promise<HostAccountsProvider> {\n const provider = await getAccountsProvider();\n if (!provider) {\n throw new Error(\"Host accounts provider is not available\");\n }\n return provider;\n}\n\nasync function getPrimaryUsername(accountsProvider: HostAccountsProvider): Promise<string> {\n const result = await accountsProvider.getUserId().match(\n (value) => value,\n (err) => {\n throw err;\n },\n );\n const username = result.primaryUsername.trim();\n if (!username) {\n throw new Error(\"Host identity did not provide a primary DotNS username\");\n }\n return username;\n}\n"]}