@parity/product-sdk 0.0.0-dev.312.0 → 0.0.0-dev.329.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.
Files changed (57) hide show
  1. package/dist/address/index.d.ts +3 -1
  2. package/dist/{chunk-BRZNHTLQ.js → chunk-7GM3HUZF.js} +2 -2
  3. package/dist/chunk-7GM3HUZF.js.map +1 -0
  4. package/dist/chunk-TUXU2VPS.js +14 -0
  5. package/dist/chunk-TUXU2VPS.js.map +1 -0
  6. package/dist/{chunk-DHRCWMY5.js → chunk-VWD6HK2C.js} +29 -20
  7. package/dist/chunk-VWD6HK2C.js.map +1 -0
  8. package/dist/core/index.d.ts +50 -3
  9. package/dist/core/index.js +3 -2
  10. package/dist/{dotns-bswWAmR2.d.ts → dotns-CtmOwVQW.d.ts} +2 -2
  11. package/dist/identity/index.d.ts +118 -89
  12. package/dist/identity/index.js +200 -78
  13. package/dist/identity/index.js.map +1 -1
  14. package/dist/index.d.ts +3 -53
  15. package/dist/index.js +3 -2
  16. package/dist/nfts/index.d.ts +1 -0
  17. package/dist/nfts/index.js +3 -0
  18. package/dist/nfts/index.js.map +1 -0
  19. package/dist/react/index.d.ts +5 -5
  20. package/dist/react/index.js +4 -3
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/renderer/index.d.ts +1 -0
  23. package/dist/renderer/index.js +3 -0
  24. package/dist/renderer/index.js.map +1 -0
  25. package/dist/testing/index.d.ts +2 -2
  26. package/dist/testing/index.js +3 -5
  27. package/dist/testing/index.js.map +1 -1
  28. package/dist/{types-B5Zx4RNq.d.ts → types-BpWw-OJl.d.ts} +19 -9
  29. package/package.json +22 -11
  30. package/src/core/createApp.test.ts +194 -0
  31. package/src/core/createApp.ts +35 -22
  32. package/src/core/local-storage-api.ts +74 -0
  33. package/src/core/types.ts +18 -7
  34. package/src/identity/dotns-abis.ts +44 -9
  35. package/src/identity/dotns-addresses.test.ts +536 -0
  36. package/src/identity/dotns-addresses.ts +320 -0
  37. package/src/identity/dotns-errors.ts +2 -0
  38. package/src/identity/dotns-namehash.ts +2 -2
  39. package/src/identity/dotns-registry.test.ts +158 -3
  40. package/src/identity/dotns-registry.ts +71 -73
  41. package/src/identity/dotns.ts +2 -2
  42. package/src/identity/index.test.ts +43 -0
  43. package/src/identity/index.ts +10 -6
  44. package/src/identity/types.ts +0 -19
  45. package/src/index.ts +3 -2
  46. package/src/individuality/contract.test.ts +426 -2
  47. package/src/nfts/contract.test.ts +58 -0
  48. package/src/nfts/index.ts +8 -0
  49. package/src/react/context.ts +1 -1
  50. package/src/react/index.ts +1 -1
  51. package/src/react/provider.tsx +3 -3
  52. package/src/renderer/index.ts +10 -0
  53. package/src/testing.ts +14 -11
  54. package/dist/chunk-BRZNHTLQ.js.map +0 -1
  55. package/dist/chunk-DHRCWMY5.js.map +0 -1
  56. package/src/identity/product-account.test.ts +0 -130
  57. package/src/identity/product-account.ts +0 -115
@@ -16,6 +16,7 @@ import type {
16
16
  CloudStorageApi,
17
17
  LocalStorageApi,
18
18
  } from "./types.js";
19
+ import { createLocalStorageApi } from "./local-storage-api.js";
19
20
  import { configure, createLogger } from "@parity/product-sdk-logger";
20
21
  import { createLocalKvStore } from "@parity/product-sdk-local-storage";
21
22
  import { SignerManager } from "@parity/product-sdk-signer";
@@ -33,7 +34,12 @@ import {
33
34
  destroyAll,
34
35
  } from "@parity/product-sdk-chain-client";
35
36
  import { accountIdBytes } from "@parity/product-sdk-address";
36
- import { getAccountsProvider } from "@parity/product-sdk-host";
37
+ import {
38
+ getAccountsProvider,
39
+ getTruApi,
40
+ HostCallFailedError,
41
+ HostUnavailableError,
42
+ } from "@parity/product-sdk-host";
37
43
  import {
38
44
  accountIdBytesToHex,
39
45
  type PeopleUsernameQueryApi,
@@ -54,19 +60,16 @@ const log = createLogger("app");
54
60
  *
55
61
  * // Default: cloud storage enabled with paseo environment
56
62
  * const app = await createApp({
57
- * name: 'my-app',
58
63
  * logLevel: 'info',
59
64
  * });
60
65
  *
61
66
  * // Custom cloud storage environment
62
67
  * const prodApp = await createApp({
63
- * name: 'my-app',
64
68
  * cloudStorage: { environment: 'polkadot' },
65
69
  * });
66
70
  *
67
71
  * // Disable cloud storage entirely
68
72
  * const noCloudStorageApp = await createApp({
69
- * name: 'my-app',
70
73
  * cloudStorage: false,
71
74
  * });
72
75
  *
@@ -82,20 +85,40 @@ const log = createLogger("app");
82
85
  * }
83
86
  * ```
84
87
  */
85
- export async function createApp(config: AppConfig): Promise<App> {
88
+ export async function createApp(config: AppConfig = {}): Promise<App> {
86
89
  // Set log level if specified
87
90
  if (config.logLevel) {
88
91
  configure({ level: config.logLevel });
89
92
  }
90
93
 
91
- log.info("Creating Product SDK app", { name: config.name });
94
+ if (config.name !== undefined) {
95
+ log.warn(
96
+ "createApp: name is deprecated and ignored. Remove the name option. The SDK resolves the app name from system.getProductContext(), dropping the product ID's final domain suffix (my-app.dot -> my-app). Local development IDs stay unchanged.",
97
+ );
98
+ }
99
+
100
+ const truApi = await getTruApi();
101
+ if (!truApi) {
102
+ throw new HostUnavailableError("createApp requires a host product context");
103
+ }
104
+ const { productId } = await truApi.system.getProductContext().match(
105
+ (context) => context,
106
+ (error) => {
107
+ throw new HostCallFailedError("system.getProductContext", error);
108
+ },
109
+ );
110
+
111
+ const isLocalHost = /^(?:localhost|127\.0\.0\.1|[^:]+\.localhost)(?::\d+)?$/i.test(productId);
112
+ const name = isLocalHost ? productId : productId.replace(/\.[^.]+$/, "");
113
+
114
+ log.info("Creating Product SDK app", { name });
92
115
 
93
116
  // Initialize storage (container-only - will throw if not in container)
94
- const localKvStore = await createLocalKvStore({ prefix: config.name });
117
+ const localKvStore = await createLocalKvStore({ prefix: name });
95
118
 
96
119
  // Initialize signer manager
97
120
  const signerManager = new SignerManager({
98
- dappName: config.name,
121
+ dappName: productId,
99
122
  });
100
123
 
101
124
  // Initialize cloud storage client (configurable, defaults to paseo).
@@ -124,18 +147,8 @@ export async function createApp(config: AppConfig): Promise<App> {
124
147
  log.debug("Cloud Storage client disabled");
125
148
  }
126
149
 
127
- // Create storage API adapter
128
- const localStorageApi: LocalStorageApi = {
129
- get: (key) => localKvStore.get(key),
130
- set: (key, value) => localKvStore.set(key, value),
131
- getJSON: <T>(key: string) => localKvStore.getJSON<T>(key),
132
- setJSON: <T>(key: string, value: T) => localKvStore.setJSON(key, value),
133
- remove: (key) => localKvStore.remove(key),
134
- clear: async () => {
135
- // LocalKvStore doesn't have clear - this is a no-op
136
- log.debug("clear() is not supported in container storage mode");
137
- },
138
- };
150
+ // Storage API adapter — shared with `createFakeApp` so the two cannot drift.
151
+ const localStorageApi: LocalStorageApi = createLocalStorageApi(localKvStore);
139
152
 
140
153
  // Create wallet API adapter
141
154
  const walletApi = createWalletApi(signerManager);
@@ -206,7 +219,7 @@ export async function createApp(config: AppConfig): Promise<App> {
206
219
  : null;
207
220
 
208
221
  log.info("Product SDK app created", {
209
- name: config.name,
222
+ name,
210
223
  cloudStorage: cloudStorageEnabled ? cloudStorageEnvironment : "disabled",
211
224
  });
212
225
 
@@ -215,7 +228,7 @@ export async function createApp(config: AppConfig): Promise<App> {
215
228
  localStorage: localStorageApi,
216
229
  chain: chainApi,
217
230
  cloudStorage: cloudStorageApi,
218
- getAppInfo: () => ({ ...config }),
231
+ getAppInfo: () => ({ ...config, name }),
219
232
  };
220
233
  }
221
234
 
@@ -0,0 +1,74 @@
1
+ // Copyright 2026 Parity Technologies (UK) Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * The one adapter from a {@link LocalKvStore} to the SDK's {@link LocalStorageApi}.
5
+ *
6
+ * Both `createApp` (host-backed store) and `createFakeApp` (in-memory store)
7
+ * build their `localStorage` through here, so the shipped adapter and the test
8
+ * fake cannot drift — which is exactly how `clear()` came to be a no-op in
9
+ * production while the fake emptied (#344). The in-source test below drives one
10
+ * assertion set through this factory over an in-memory store; anything the fake
11
+ * exposes, the real path exposes the same way.
12
+ */
13
+ import type { LocalKvStore } from "@parity/product-sdk-local-storage";
14
+ import type { LocalStorageApi } from "./types.js";
15
+
16
+ /** Wrap a {@link LocalKvStore} as the SDK's {@link LocalStorageApi}. */
17
+ export function createLocalStorageApi(kv: LocalKvStore): LocalStorageApi {
18
+ return {
19
+ get: (key) => kv.get(key),
20
+ set: (key, value) => kv.set(key, value),
21
+ getJSON: <T>(key: string) => kv.getJSON<T>(key),
22
+ setJSON: <T>(key: string, value: T) => kv.setJSON(key, value),
23
+ remove: (key) => kv.remove(key),
24
+ };
25
+ }
26
+
27
+ if (import.meta.vitest) {
28
+ const { describe, test, expect } = import.meta.vitest;
29
+
30
+ /** A minimal in-memory {@link LocalKvStore}, string- and JSON-aware. */
31
+ function memoryKvStore(): LocalKvStore {
32
+ const store = new Map<string, string>();
33
+ return {
34
+ async get(key) {
35
+ return store.get(key) ?? null;
36
+ },
37
+ async set(key, value) {
38
+ store.set(key, value);
39
+ },
40
+ async remove(key) {
41
+ store.delete(key);
42
+ },
43
+ async getJSON<T>(key: string) {
44
+ const raw = store.get(key);
45
+ return raw === undefined ? null : (JSON.parse(raw) as T);
46
+ },
47
+ async setJSON(key, value) {
48
+ store.set(key, JSON.stringify(value));
49
+ },
50
+ };
51
+ }
52
+
53
+ describe("createLocalStorageApi", () => {
54
+ test("round-trips strings and JSON, and remove deletes one key", async () => {
55
+ const api = createLocalStorageApi(memoryKvStore());
56
+
57
+ await api.set("k", "v");
58
+ await api.setJSON("o", { n: 1 });
59
+ expect(await api.get("k")).toBe("v");
60
+ expect(await api.getJSON("o")).toEqual({ n: 1 });
61
+ expect(await api.get("missing")).toBeNull();
62
+
63
+ await api.remove("k");
64
+ expect(await api.get("k")).toBeNull();
65
+ // No clear-all: an untouched key stays.
66
+ expect(await api.getJSON("o")).toEqual({ n: 1 });
67
+ });
68
+
69
+ test("exposes exactly the LocalStorageApi surface — no clear()", () => {
70
+ const api = createLocalStorageApi(memoryKvStore());
71
+ expect(Object.keys(api).sort()).toEqual(["get", "getJSON", "remove", "set", "setJSON"]);
72
+ });
73
+ });
74
+ }
package/src/core/types.ts CHANGED
@@ -25,8 +25,8 @@ export interface CloudStorageConfig {
25
25
 
26
26
  /** Configuration for createApp */
27
27
  export interface AppConfig {
28
- /** Application name - used to derive product accounts and namespace storage */
29
- name: string;
28
+ /** @deprecated Ignored with a warning. The host product ID supplies the app name. */
29
+ name?: string;
30
30
  /** Log level for SDK operations (default: 'info') */
31
31
  logLevel?: LogLevel;
32
32
  /**
@@ -37,6 +37,12 @@ export interface AppConfig {
37
37
  cloudStorage?: CloudStorageConfig | false;
38
38
  }
39
39
 
40
+ /** App configuration with the identity resolved from the host. */
41
+ export interface AppInfo extends Omit<AppConfig, "name"> {
42
+ /** App name and storage prefix, omitting the host ID's final domain suffix. Local host IDs stay unchanged. */
43
+ name: string;
44
+ }
45
+
40
46
  /** Wallet API exposed by the SDK */
41
47
  export interface WalletApi {
42
48
  /** Connect to available wallet providers */
@@ -72,7 +78,14 @@ export interface WalletApi {
72
78
  createProof(message: Uint8Array): Promise<Uint8Array>;
73
79
  }
74
80
 
75
- /** Storage API exposed by the SDK */
81
+ /**
82
+ * Storage API exposed by the SDK: per-key get / set / remove, string and JSON.
83
+ *
84
+ * There is no `clear()`. Host localStorage exposes no key enumeration (only
85
+ * per-key read / write / remove), so a clear-all cannot be implemented — it was
86
+ * a silent no-op in a host container. Remove keys individually with `remove`,
87
+ * or track the keys your app writes and remove them.
88
+ */
76
89
  export interface LocalStorageApi {
77
90
  /** Get a value by key */
78
91
  get(key: string): Promise<string | null>;
@@ -84,8 +97,6 @@ export interface LocalStorageApi {
84
97
  setJSON<T = unknown>(key: string, value: T): Promise<void>;
85
98
  /** Remove a value by key */
86
99
  remove(key: string): Promise<void>;
87
- /** Clear all values */
88
- clear(): Promise<void>;
89
100
  }
90
101
 
91
102
  /** Chain API exposed by the SDK */
@@ -171,8 +182,8 @@ export interface App {
171
182
  chain: ChainApi;
172
183
  /** Cloud Storage operations (null if disabled via config) */
173
184
  cloudStorage: CloudStorageApi | null;
174
- /** Get app configuration */
175
- getAppInfo(): AppConfig;
185
+ /** Get app configuration with the host-derived name. */
186
+ getAppInfo(): AppInfo;
176
187
  }
177
188
 
178
189
  /** Account information */
@@ -238,6 +238,39 @@ export const DOTNS_PROTOCOL_REGISTRY_ABI: AbiEntry[] = [
238
238
  outputs: [{ name: "node", type: "bytes32" }],
239
239
  stateMutability: "view",
240
240
  },
241
+ {
242
+ type: "function",
243
+ name: "get",
244
+ inputs: [{ name: "key", type: "bytes32" }],
245
+ outputs: [{ name: "addr", type: "address" }],
246
+ stateMutability: "view",
247
+ },
248
+ ];
249
+
250
+ /**
251
+ * `RootGatewayDispatcher.TARGET`, the PoP controller it forwards to. `immutable`,
252
+ * so a rotation means a new dispatcher and a new pallet value, which is why
253
+ * discovery starts at the pallet rather than here.
254
+ */
255
+ export const DOTNS_ROOT_GATEWAY_DISPATCHER_ABI: AbiEntry[] = [
256
+ {
257
+ type: "function",
258
+ name: "TARGET",
259
+ inputs: [],
260
+ outputs: [{ name: "", type: "address" }],
261
+ stateMutability: "view",
262
+ },
263
+ ];
264
+
265
+ /** `DotnsPopController.protocolRegistry`, the only hop that names the registry. */
266
+ export const DOTNS_POP_CONTROLLER_ABI: AbiEntry[] = [
267
+ {
268
+ type: "function",
269
+ name: "protocolRegistry",
270
+ inputs: [],
271
+ outputs: [{ name: "", type: "address" }],
272
+ stateMutability: "view",
273
+ },
241
274
  ];
242
275
 
243
276
  /** `IPopRules.PopStatus`. Ordering is meaningful: a user meets a tier when `userStatus >= status`. */
@@ -248,21 +281,23 @@ export const POP_STATUS = { NoStatus: 0, PopLite: 1, PopFull: 2, Reserved: 3 } a
248
281
  *
249
282
  * Not a per-network table, despite what the old name (`PASEO_ASSETHUB_DOTNS`)
250
283
  * implied: every DotNS network is deployed through the same CREATE3 factory, so
251
- * the addresses are identical everywhere and **only the TLD differs**. Verified
252
- * on both Paseo Asset Hub Next V2 and Previewnet — `protocolRegistry.get(...)`
253
- * returns the same registry on each, and all six addresses hold contracts on
254
- * both. `paritytech/dotns` `DEPLOYMENTS.md` states the mechanism, and
284
+ * the addresses are identical everywhere and **only the TLD differs**, `.paseo`
285
+ * on Paseo Asset Hub Next V2 and `.test` on Previewnet. Verified on both:
286
+ * `protocolRegistry.get(...)` returns the same registry on each, and all six
287
+ * addresses hold contracts on both. `paritytech/dotns` `DEPLOYMENTS.md` states the mechanism, and
255
288
  * `preview-net-v1` confirms it from the deploy side: the TLD is passed only as
256
289
  * registry init calldata, so it moves no address.
257
290
  *
258
291
  * So extending this module to another network means resolving its TLD, which
259
292
  * `resolveTld` already does — not adding a second address table.
260
293
  *
261
- * Each address answers `DotnsProtocolRegistry.get(bytes32)` under its
262
- * `DotnsConstants` key (the name below, right-padded to 32 bytes) and has a
263
- * `Revive.AccountInfoOf` entry. They are pinned rather than resolved at runtime,
264
- * so re-verify after a redeploy; `protocolRegistry` is the address to walk from
265
- * if we ever resolve the rest dynamically.
294
+ * Five answer `DotnsProtocolRegistry.get(bytes32)` under the keys in
295
+ * `DOTNS_REGISTRY_KEYS`, two of which differ from the field name used here.
296
+ * `protocolRegistry` has no key and comes from the gateway walk. All six have a
297
+ * `Revive.AccountInfoOf` entry.
298
+ *
299
+ * Pinned, not resolved at runtime: re-verify after a redeploy, or have the
300
+ * client do it with `verifyDotNsAddresses`.
266
301
  */
267
302
  export const DOTNS_ADDRESSES = {
268
303
  registry: "0xf34054fd76BbF85f216cf9908226D5f0A72E50CA",