@parity/product-sdk 0.0.0-dev.312.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/address/index.d.ts +9 -0
- package/dist/address/index.js +3 -0
- package/dist/address/index.js.map +1 -0
- package/dist/chain/index.d.ts +1 -0
- package/dist/chain/index.js +3 -0
- package/dist/chain/index.js.map +1 -0
- package/dist/chunk-BRZNHTLQ.js +89 -0
- package/dist/chunk-BRZNHTLQ.js.map +1 -0
- package/dist/chunk-DHRCWMY5.js +240 -0
- package/dist/chunk-DHRCWMY5.js.map +1 -0
- package/dist/chunk-JVJ552RU.js +3 -0
- package/dist/chunk-JVJ552RU.js.map +1 -0
- package/dist/cloud-storage/index.d.ts +1 -0
- package/dist/cloud-storage/index.js +3 -0
- package/dist/cloud-storage/index.js.map +1 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +3 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/core/index.d.ts +12 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/crypto/index.d.ts +1 -0
- package/dist/crypto/index.js +3 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/dotns-bswWAmR2.d.ts +136 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.js +3 -0
- package/dist/host/index.js.map +1 -0
- package/dist/identity/index.d.ts +491 -0
- package/dist/identity/index.js +691 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/individuality/index.d.ts +1 -0
- package/dist/individuality/index.js +3 -0
- package/dist/individuality/index.js.map +1 -0
- package/dist/local-storage/index.d.ts +1 -0
- package/dist/local-storage/index.js +3 -0
- package/dist/local-storage/index.js.map +1 -0
- package/dist/react/index.d.ts +181 -0
- package/dist/react/index.js +198 -0
- package/dist/react/index.js.map +1 -0
- package/dist/testing/index.d.ts +61 -0
- package/dist/testing/index.js +149 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-B5Zx4RNq.d.ts +211 -0
- package/dist/wallet/index.d.ts +1 -0
- package/dist/wallet/index.js +3 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +118 -0
- package/src/address/index.ts +8 -0
- package/src/chain/index.ts +8 -0
- package/src/cloud-storage/index.ts +8 -0
- package/src/contracts/index.ts +8 -0
- package/src/core/createApp.ts +408 -0
- package/src/core/index.ts +12 -0
- package/src/core/logger.ts +15 -0
- package/src/core/types.ts +233 -0
- package/src/crypto/index.ts +8 -0
- package/src/host/index.ts +8 -0
- package/src/identity/dotns-abis.test.ts +43 -0
- package/src/identity/dotns-abis.ts +274 -0
- package/src/identity/dotns-errors.ts +34 -0
- package/src/identity/dotns-namehash.test.ts +155 -0
- package/src/identity/dotns-namehash.ts +149 -0
- package/src/identity/dotns-owner.test.ts +92 -0
- package/src/identity/dotns-registry.test.ts +1154 -0
- package/src/identity/dotns-registry.ts +870 -0
- package/src/identity/dotns.test.ts +128 -0
- package/src/identity/dotns.ts +221 -0
- package/src/identity/index.ts +61 -0
- package/src/identity/product-account.test.ts +130 -0
- package/src/identity/product-account.ts +115 -0
- package/src/identity/types.ts +87 -0
- package/src/index.ts +61 -0
- package/src/individuality/contract.test.ts +135 -0
- package/src/individuality/index.ts +8 -0
- package/src/local-storage/index.ts +8 -0
- package/src/react/context.ts +35 -0
- package/src/react/index.ts +43 -0
- package/src/react/provider.tsx +78 -0
- package/src/react/useChain.ts +35 -0
- package/src/react/useLocalStorage.ts +127 -0
- package/src/react/useWallet.ts +156 -0
- package/src/testing.ts +297 -0
- package/src/wallet/index.ts +11 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* createApp - Main entry point for the Product SDK
|
|
5
|
+
*
|
|
6
|
+
* Creates an App instance with wallet, storage, chain, and cloud storage APIs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ChainDefinition } from "polkadot-api";
|
|
10
|
+
import type {
|
|
11
|
+
App,
|
|
12
|
+
AppConfig,
|
|
13
|
+
WalletApi,
|
|
14
|
+
ChainApi,
|
|
15
|
+
Account,
|
|
16
|
+
CloudStorageApi,
|
|
17
|
+
LocalStorageApi,
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
import { configure, createLogger } from "@parity/product-sdk-logger";
|
|
20
|
+
import { createLocalKvStore } from "@parity/product-sdk-local-storage";
|
|
21
|
+
import { SignerManager } from "@parity/product-sdk-signer";
|
|
22
|
+
import {
|
|
23
|
+
CloudStorageClient,
|
|
24
|
+
ProductCloudStorageError,
|
|
25
|
+
calculateCid,
|
|
26
|
+
createLazySigner,
|
|
27
|
+
} from "@parity/product-sdk-cloud-storage";
|
|
28
|
+
import { err, ok } from "@parity/result";
|
|
29
|
+
import {
|
|
30
|
+
createChainClient,
|
|
31
|
+
getClient,
|
|
32
|
+
isConnected,
|
|
33
|
+
destroyAll,
|
|
34
|
+
} from "@parity/product-sdk-chain-client";
|
|
35
|
+
import { accountIdBytes } from "@parity/product-sdk-address";
|
|
36
|
+
import { getAccountsProvider } from "@parity/product-sdk-host";
|
|
37
|
+
import {
|
|
38
|
+
accountIdBytesToHex,
|
|
39
|
+
type PeopleUsernameQueryApi,
|
|
40
|
+
resolvePeopleUsernameOwner,
|
|
41
|
+
} from "../identity/dotns.js";
|
|
42
|
+
|
|
43
|
+
const log = createLogger("app");
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a new Product SDK app instance
|
|
47
|
+
*
|
|
48
|
+
* @param config - Application configuration
|
|
49
|
+
* @returns App instance with all APIs
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { createApp } from '@parity/product-sdk';
|
|
54
|
+
*
|
|
55
|
+
* // Default: cloud storage enabled with paseo environment
|
|
56
|
+
* const app = await createApp({
|
|
57
|
+
* name: 'my-app',
|
|
58
|
+
* logLevel: 'info',
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Custom cloud storage environment
|
|
62
|
+
* const prodApp = await createApp({
|
|
63
|
+
* name: 'my-app',
|
|
64
|
+
* cloudStorage: { environment: 'polkadot' },
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // Disable cloud storage entirely
|
|
68
|
+
* const noCloudStorageApp = await createApp({
|
|
69
|
+
* name: 'my-app',
|
|
70
|
+
* cloudStorage: false,
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Connect wallet
|
|
74
|
+
* const { accounts } = await app.wallet.connect();
|
|
75
|
+
*
|
|
76
|
+
* // Use storage
|
|
77
|
+
* await app.localStorage.set('key', 'value');
|
|
78
|
+
*
|
|
79
|
+
* // Use cloud storage (check for null if it might be disabled)
|
|
80
|
+
* if (app.cloudStorage) {
|
|
81
|
+
* const cid = await app.cloudStorage.upload('hello world');
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export async function createApp(config: AppConfig): Promise<App> {
|
|
86
|
+
// Set log level if specified
|
|
87
|
+
if (config.logLevel) {
|
|
88
|
+
configure({ level: config.logLevel });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
log.info("Creating Product SDK app", { name: config.name });
|
|
92
|
+
|
|
93
|
+
// Initialize storage (container-only - will throw if not in container)
|
|
94
|
+
const localKvStore = await createLocalKvStore({ prefix: config.name });
|
|
95
|
+
|
|
96
|
+
// Initialize signer manager
|
|
97
|
+
const signerManager = new SignerManager({
|
|
98
|
+
dappName: config.name,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Initialize cloud storage client (configurable, defaults to paseo).
|
|
102
|
+
//
|
|
103
|
+
// The signer is wrapped lazily so the cloud storage client can be built before
|
|
104
|
+
// an account is selected. Uploads will throw a clear error if no signer
|
|
105
|
+
// is available at submission time. Reads (fetch / fetchJson) don't need
|
|
106
|
+
// a signer, so they work regardless of whether an account is selected --
|
|
107
|
+
// but they are container-only and throw CloudStorageHostUnavailableError
|
|
108
|
+
// outside a host container (no IPFS-gateway fallback).
|
|
109
|
+
const cloudStorageEnabled = config.cloudStorage !== false;
|
|
110
|
+
const cloudStorageEnvironment =
|
|
111
|
+
typeof config.cloudStorage === "object" ? config.cloudStorage.environment : "paseo";
|
|
112
|
+
const cloudStorageClient = cloudStorageEnabled
|
|
113
|
+
? await CloudStorageClient.create({
|
|
114
|
+
environment: cloudStorageEnvironment,
|
|
115
|
+
signer: createLazySigner(() => signerManager.getSigner()),
|
|
116
|
+
})
|
|
117
|
+
: null;
|
|
118
|
+
|
|
119
|
+
if (cloudStorageEnabled) {
|
|
120
|
+
log.debug("Cloud Storage client (Bulletin) initialized", {
|
|
121
|
+
environment: cloudStorageEnvironment,
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
log.debug("Cloud Storage client disabled");
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
};
|
|
139
|
+
|
|
140
|
+
// Create wallet API adapter
|
|
141
|
+
const walletApi = createWalletApi(signerManager);
|
|
142
|
+
|
|
143
|
+
// Create chain API
|
|
144
|
+
const chainApi: ChainApi = {
|
|
145
|
+
getClient(descriptor) {
|
|
146
|
+
log.debug("getClient called", { genesis: descriptor.genesis });
|
|
147
|
+
const client = getClient(descriptor);
|
|
148
|
+
return client.getTypedApi(descriptor);
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
getRawClient(descriptor) {
|
|
152
|
+
log.debug("getRawClient called", { genesis: descriptor.genesis });
|
|
153
|
+
return getClient(descriptor);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
async connect<T extends Record<string, ChainDefinition>>(chains: T) {
|
|
157
|
+
log.debug("connect called", { chains: Object.keys(chains) });
|
|
158
|
+
return createChainClient({ chains });
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
isConnected(descriptor) {
|
|
162
|
+
return isConnected(descriptor);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
destroyAll() {
|
|
166
|
+
log.debug("destroyAll called");
|
|
167
|
+
destroyAll();
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Create Cloud Storage API adapter (null if disabled)
|
|
172
|
+
const cloudStorageApi: CloudStorageApi | null = cloudStorageClient
|
|
173
|
+
? {
|
|
174
|
+
upload: async (data) => {
|
|
175
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
176
|
+
try {
|
|
177
|
+
// Explicitly request a DAG-PB manifest so chunked uploads always
|
|
178
|
+
// resolve to a single root CID. Without this, AsyncBulletinClient
|
|
179
|
+
// can return `result.cid: undefined` for chunked-without-manifest
|
|
180
|
+
// uploads — but app consumers expect a CID they can hand to
|
|
181
|
+
// `fetch(cid)`, so treat a missing CID as an error.
|
|
182
|
+
const uploaded = await cloudStorageClient
|
|
183
|
+
.store(bytes)
|
|
184
|
+
.withManifest(true)
|
|
185
|
+
.send();
|
|
186
|
+
if (!uploaded.cid) {
|
|
187
|
+
return err(
|
|
188
|
+
new ProductCloudStorageError(
|
|
189
|
+
"Cloud storage upload returned no CID despite .withManifest(true). Upstream contract may have shifted — file an issue.",
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return ok(uploaded.cid.toString());
|
|
194
|
+
} catch (cause) {
|
|
195
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
196
|
+
return err(new ProductCloudStorageError(message, { cause }));
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
fetch: (cid) => cloudStorageClient.fetchBytes(cid),
|
|
200
|
+
computeCid: async (data) => {
|
|
201
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
202
|
+
const cid = await calculateCid(bytes);
|
|
203
|
+
return cid.toString();
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
: null;
|
|
207
|
+
|
|
208
|
+
log.info("Product SDK app created", {
|
|
209
|
+
name: config.name,
|
|
210
|
+
cloudStorage: cloudStorageEnabled ? cloudStorageEnvironment : "disabled",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
wallet: walletApi,
|
|
215
|
+
localStorage: localStorageApi,
|
|
216
|
+
chain: chainApi,
|
|
217
|
+
cloudStorage: cloudStorageApi,
|
|
218
|
+
getAppInfo: () => ({ ...config }),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create wallet API adapter using SignerManager from leaf package
|
|
224
|
+
*/
|
|
225
|
+
function createWalletApi(signerManager: SignerManager): WalletApi {
|
|
226
|
+
// Track account change subscribers
|
|
227
|
+
const accountChangeSubscribers = new Set<(account: Account | null) => void>();
|
|
228
|
+
|
|
229
|
+
// Subscribe to signer manager state changes
|
|
230
|
+
signerManager.subscribe((state) => {
|
|
231
|
+
const account = state.selectedAccount
|
|
232
|
+
? {
|
|
233
|
+
address: state.selectedAccount.address,
|
|
234
|
+
name: state.selectedAccount.name ?? undefined,
|
|
235
|
+
source: state.selectedAccount.source,
|
|
236
|
+
}
|
|
237
|
+
: null;
|
|
238
|
+
for (const callback of accountChangeSubscribers) {
|
|
239
|
+
try {
|
|
240
|
+
callback(account);
|
|
241
|
+
} catch (e) {
|
|
242
|
+
log.warn("Account change callback threw", { error: e });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
async connect(): Promise<{ accounts: Account[] }> {
|
|
249
|
+
const result = await signerManager.connect();
|
|
250
|
+
if (!result.ok) {
|
|
251
|
+
throw new Error(result.error.message);
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
accounts: result.value.map((a) => ({
|
|
255
|
+
address: a.address,
|
|
256
|
+
name: a.name ?? undefined,
|
|
257
|
+
source: a.source,
|
|
258
|
+
})),
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
async disconnect(): Promise<void> {
|
|
263
|
+
signerManager.disconnect();
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
getAccounts(): Account[] {
|
|
267
|
+
return signerManager.getState().accounts.map((a) => ({
|
|
268
|
+
address: a.address,
|
|
269
|
+
name: a.name ?? undefined,
|
|
270
|
+
source: a.source,
|
|
271
|
+
}));
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
getSelectedAccount(): Account | null {
|
|
275
|
+
const selected = signerManager.getState().selectedAccount;
|
|
276
|
+
if (!selected) return null;
|
|
277
|
+
return {
|
|
278
|
+
address: selected.address,
|
|
279
|
+
name: selected.name ?? undefined,
|
|
280
|
+
source: selected.source,
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
selectAccount(address: string): void {
|
|
285
|
+
signerManager.selectAccount(address);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async signMessage(message: string | Uint8Array): Promise<Uint8Array> {
|
|
289
|
+
const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
290
|
+
const result = await signerManager.signRaw(bytes);
|
|
291
|
+
if (!result.ok) {
|
|
292
|
+
throw new Error(result.error.message);
|
|
293
|
+
}
|
|
294
|
+
return result.value;
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
async signMessageWithDotNsIdentity(args) {
|
|
298
|
+
const message =
|
|
299
|
+
typeof args.message === "string"
|
|
300
|
+
? new TextEncoder().encode(args.message)
|
|
301
|
+
: args.message;
|
|
302
|
+
const accountsProvider = await getHostAccountsProvider();
|
|
303
|
+
const username = args.username ?? (await getPrimaryUsername(accountsProvider));
|
|
304
|
+
|
|
305
|
+
// Reuse an already-connected People chain when the caller has
|
|
306
|
+
// wired one up via `app.chain.connect({ ..., <name>: peopleChain })`.
|
|
307
|
+
// Fall back to opening a transient connection so first-time users
|
|
308
|
+
// don't have to think about chain lifecycles. The chain-client
|
|
309
|
+
// module caches by genesis fingerprint, so subsequent
|
|
310
|
+
// signMessageWithDotNsIdentity calls share whichever connection
|
|
311
|
+
// got established first.
|
|
312
|
+
const peopleClient = isConnected(args.peopleChain)
|
|
313
|
+
? getClient(args.peopleChain)
|
|
314
|
+
: await createChainClient({ chains: { people: args.peopleChain } }).then(
|
|
315
|
+
(c) => c.raw.people,
|
|
316
|
+
);
|
|
317
|
+
// PAPI's `TypedApi.query.X.Y.getValue` is variadic
|
|
318
|
+
// (`...args: [...WithCallOptions<Args>]`) so it's not assignable
|
|
319
|
+
// to our deliberately-narrow `(key) => Promise<...>` shape. The
|
|
320
|
+
// resolver only ever passes the storage key; the cast adapts the
|
|
321
|
+
// wider PAPI surface to the minimum we depend on.
|
|
322
|
+
const peopleApi = peopleClient.getTypedApi(
|
|
323
|
+
args.peopleChain,
|
|
324
|
+
) as unknown as PeopleUsernameQueryApi;
|
|
325
|
+
const owner = await resolvePeopleUsernameOwner(username, peopleApi);
|
|
326
|
+
if (!owner) {
|
|
327
|
+
throw new Error(`No account owns DotNS username "${username}"`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const publicKey = accountIdBytes(owner);
|
|
331
|
+
// This field is hex, not the SS58 the resolver now returns. Converted
|
|
332
|
+
// here and not in the try, so an address failure is not reported as a
|
|
333
|
+
// signing failure.
|
|
334
|
+
const accountId = accountIdBytesToHex(publicKey);
|
|
335
|
+
const signer = accountsProvider.getLegacyAccountSigner({ publicKey, name: username });
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
return {
|
|
339
|
+
username,
|
|
340
|
+
accountId,
|
|
341
|
+
signature: await signer.signBytes(message),
|
|
342
|
+
};
|
|
343
|
+
} catch (cause) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Failed to sign with DotNS username "${username}": ${
|
|
346
|
+
cause instanceof Error ? cause.message : String(cause)
|
|
347
|
+
}`,
|
|
348
|
+
{ cause },
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
onAccountChange(callback: (account: Account | null) => void): () => void {
|
|
354
|
+
accountChangeSubscribers.add(callback);
|
|
355
|
+
return () => accountChangeSubscribers.delete(callback);
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
getProductAccount(): Account | null {
|
|
359
|
+
// Product accounts require async call - this sync API can't support it properly
|
|
360
|
+
// Users should use SignerManager.getProductAccount() directly
|
|
361
|
+
log.warn(
|
|
362
|
+
"getProductAccount() is deprecated - use SignerManager.getProductAccount() directly",
|
|
363
|
+
);
|
|
364
|
+
return null;
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
getAnonymousAlias(): string | null {
|
|
368
|
+
// Anonymous aliases require async call - this sync API can't support it properly
|
|
369
|
+
// Users should use SignerManager.getProductAccountAlias() directly
|
|
370
|
+
log.warn(
|
|
371
|
+
"getAnonymousAlias() is deprecated - use SignerManager.getProductAccountAlias() directly",
|
|
372
|
+
);
|
|
373
|
+
return null;
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
async createProof(_message: Uint8Array): Promise<Uint8Array> {
|
|
377
|
+
// Ring VRF proofs require SignerManager.createRingVRFProof() directly
|
|
378
|
+
throw new Error(
|
|
379
|
+
"createProof() is not implemented in the App API. " +
|
|
380
|
+
"Use SignerManager.createRingVRFProof() directly.",
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
type HostAccountsProvider = NonNullable<Awaited<ReturnType<typeof getAccountsProvider>>>;
|
|
387
|
+
|
|
388
|
+
async function getHostAccountsProvider(): Promise<HostAccountsProvider> {
|
|
389
|
+
const provider = await getAccountsProvider();
|
|
390
|
+
if (!provider) {
|
|
391
|
+
throw new Error("Host accounts provider is not available");
|
|
392
|
+
}
|
|
393
|
+
return provider;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function getPrimaryUsername(accountsProvider: HostAccountsProvider): Promise<string> {
|
|
397
|
+
const result = await accountsProvider.getUserId().match(
|
|
398
|
+
(value) => value,
|
|
399
|
+
(err) => {
|
|
400
|
+
throw err;
|
|
401
|
+
},
|
|
402
|
+
);
|
|
403
|
+
const username = result.primaryUsername.trim();
|
|
404
|
+
if (!username) {
|
|
405
|
+
throw new Error("Host identity did not provide a primary DotNS username");
|
|
406
|
+
}
|
|
407
|
+
return username;
|
|
408
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* @parity/product-sdk core module
|
|
5
|
+
*
|
|
6
|
+
* Core functionality including createApp, logger, and type definitions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { createApp } from "./createApp.js";
|
|
10
|
+
export { configure, createLogger } from "./logger.js";
|
|
11
|
+
export type { LogEntry, LogHandler, LoggerConfig, Logger } from "./logger.js";
|
|
12
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Logger module for @parity/product-sdk
|
|
5
|
+
*
|
|
6
|
+
* Re-exports from @parity/product-sdk-logger.
|
|
7
|
+
*/
|
|
8
|
+
export { configure, createLogger } from "@parity/product-sdk-logger";
|
|
9
|
+
export type {
|
|
10
|
+
LogLevel,
|
|
11
|
+
LogEntry,
|
|
12
|
+
LogHandler,
|
|
13
|
+
LoggerConfig,
|
|
14
|
+
Logger,
|
|
15
|
+
} from "@parity/product-sdk-logger";
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Core types for @parity/product-sdk
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LogLevel } from "@parity/product-sdk-logger";
|
|
8
|
+
import type {
|
|
9
|
+
CloudStorageEnvironment,
|
|
10
|
+
ProductCloudStorageError,
|
|
11
|
+
} from "@parity/product-sdk-cloud-storage";
|
|
12
|
+
import type { Result } from "@parity/result";
|
|
13
|
+
import type { ChainClient } from "@parity/product-sdk-chain-client";
|
|
14
|
+
import type { PeopleUsernameChain } from "../identity/dotns.js";
|
|
15
|
+
import type { ChainDefinition, TypedApi, PolkadotClient } from "polkadot-api";
|
|
16
|
+
|
|
17
|
+
export type { LogLevel };
|
|
18
|
+
export type { ChainClient };
|
|
19
|
+
|
|
20
|
+
/** Cloud Storage configuration options */
|
|
21
|
+
export interface CloudStorageConfig {
|
|
22
|
+
/** Cloud Storage environment to connect to */
|
|
23
|
+
environment: CloudStorageEnvironment;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Configuration for createApp */
|
|
27
|
+
export interface AppConfig {
|
|
28
|
+
/** Application name - used to derive product accounts and namespace storage */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Log level for SDK operations (default: 'info') */
|
|
31
|
+
logLevel?: LogLevel;
|
|
32
|
+
/**
|
|
33
|
+
* Cloud Storage configuration.
|
|
34
|
+
* - Omit or pass config object to enable (default: { environment: "paseo" })
|
|
35
|
+
* - Pass `false` to disable cloud storage initialization
|
|
36
|
+
*/
|
|
37
|
+
cloudStorage?: CloudStorageConfig | false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Wallet API exposed by the SDK */
|
|
41
|
+
export interface WalletApi {
|
|
42
|
+
/** Connect to available wallet providers */
|
|
43
|
+
connect(): Promise<{ accounts: Account[] }>;
|
|
44
|
+
/** Disconnect from wallet */
|
|
45
|
+
disconnect(): Promise<void>;
|
|
46
|
+
/** Get all available accounts */
|
|
47
|
+
getAccounts(): Account[];
|
|
48
|
+
/** Get currently selected account */
|
|
49
|
+
getSelectedAccount(): Account | null;
|
|
50
|
+
/** Select an account by address */
|
|
51
|
+
selectAccount(address: string): void;
|
|
52
|
+
/** Sign an arbitrary message */
|
|
53
|
+
signMessage(message: string | Uint8Array): Promise<Uint8Array>;
|
|
54
|
+
/**
|
|
55
|
+
* Sign a message with the account that owns a People / People Lite DotNS identity.
|
|
56
|
+
*
|
|
57
|
+
* Pass the People / Individuality chain descriptor used for username lookup.
|
|
58
|
+
* Pass `username` to choose a specific identity. When omitted, the SDK first
|
|
59
|
+
* asks the host for the user's primary DotNS name and signs with the account
|
|
60
|
+
* that owns that username.
|
|
61
|
+
*/
|
|
62
|
+
signMessageWithDotNsIdentity(
|
|
63
|
+
args: SignMessageWithDotNsIdentityArgs,
|
|
64
|
+
): Promise<DotNsIdentitySignature>;
|
|
65
|
+
/** Subscribe to account changes */
|
|
66
|
+
onAccountChange(callback: (account: Account | null) => void): () => void;
|
|
67
|
+
/** Get product-scoped account (container mode only) */
|
|
68
|
+
getProductAccount(): Account | null;
|
|
69
|
+
/** Get anonymous alias via Ring VRF (container mode only) */
|
|
70
|
+
getAnonymousAlias(): string | null;
|
|
71
|
+
/** Create Ring VRF proof (container mode only) */
|
|
72
|
+
createProof(message: Uint8Array): Promise<Uint8Array>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Storage API exposed by the SDK */
|
|
76
|
+
export interface LocalStorageApi {
|
|
77
|
+
/** Get a value by key */
|
|
78
|
+
get(key: string): Promise<string | null>;
|
|
79
|
+
/** Set a value by key */
|
|
80
|
+
set(key: string, value: string): Promise<void>;
|
|
81
|
+
/** Get a JSON value by key */
|
|
82
|
+
getJSON<T = unknown>(key: string): Promise<T | null>;
|
|
83
|
+
/** Set a JSON value by key */
|
|
84
|
+
setJSON<T = unknown>(key: string, value: T): Promise<void>;
|
|
85
|
+
/** Remove a value by key */
|
|
86
|
+
remove(key: string): Promise<void>;
|
|
87
|
+
/** Clear all values */
|
|
88
|
+
clear(): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Chain API exposed by the SDK */
|
|
92
|
+
export interface ChainApi {
|
|
93
|
+
/**
|
|
94
|
+
* Get a typed PAPI client for a chain.
|
|
95
|
+
*
|
|
96
|
+
* Connections are routed through the host provider. The chain must be
|
|
97
|
+
* connected first via {@link connect}.
|
|
98
|
+
*
|
|
99
|
+
* @param descriptor - PAPI chain descriptor (from @parity/product-sdk-descriptors or custom)
|
|
100
|
+
* @returns Typed API for the chain
|
|
101
|
+
* @throws If the chain is not connected
|
|
102
|
+
*/
|
|
103
|
+
getClient<T extends ChainDefinition>(descriptor: T): TypedApi<T>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the raw PolkadotClient for a chain.
|
|
107
|
+
*
|
|
108
|
+
* Use this for advanced APIs like `createContractRuntime` from `@parity/product-sdk-contracts`.
|
|
109
|
+
*
|
|
110
|
+
* @param descriptor - PAPI chain descriptor
|
|
111
|
+
* @returns Raw PolkadotClient instance
|
|
112
|
+
* @throws If the chain is not connected
|
|
113
|
+
*/
|
|
114
|
+
getRawClient(descriptor: ChainDefinition): PolkadotClient;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Connect to one or more chains.
|
|
118
|
+
*
|
|
119
|
+
* Connections are routed through the host provider (container-only).
|
|
120
|
+
* Results are cached - calling with the same descriptors returns existing connections.
|
|
121
|
+
*
|
|
122
|
+
* @param chains - Record of named chain descriptors
|
|
123
|
+
* @returns Connected chain client with typed APIs
|
|
124
|
+
*/
|
|
125
|
+
connect<T extends Record<string, ChainDefinition>>(chains: T): Promise<ChainClient<T>>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if a chain is currently connected.
|
|
129
|
+
*/
|
|
130
|
+
isConnected(descriptor: ChainDefinition): boolean;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Destroy all chain connections.
|
|
134
|
+
*/
|
|
135
|
+
destroyAll(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Cloud Storage API exposed by the SDK */
|
|
139
|
+
export interface CloudStorageApi {
|
|
140
|
+
/**
|
|
141
|
+
* Upload data to the Cloud.
|
|
142
|
+
*
|
|
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)`.
|
|
153
|
+
*/
|
|
154
|
+
fetch(cid: string): Promise<Result<Uint8Array, ProductCloudStorageError>>;
|
|
155
|
+
/**
|
|
156
|
+
* Compute the CID for data without uploading.
|
|
157
|
+
*
|
|
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.
|
|
160
|
+
*/
|
|
161
|
+
computeCid(data: string | Uint8Array): Promise<string>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The main App instance returned by createApp */
|
|
165
|
+
export interface App {
|
|
166
|
+
/** Wallet/signing operations */
|
|
167
|
+
wallet: WalletApi;
|
|
168
|
+
/** Local Key-value storage operations */
|
|
169
|
+
localStorage: LocalStorageApi;
|
|
170
|
+
/** Chain interaction operations */
|
|
171
|
+
chain: ChainApi;
|
|
172
|
+
/** Cloud Storage operations (null if disabled via config) */
|
|
173
|
+
cloudStorage: CloudStorageApi | null;
|
|
174
|
+
/** Get app configuration */
|
|
175
|
+
getAppInfo(): AppConfig;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Account information */
|
|
179
|
+
export interface Account {
|
|
180
|
+
/** Account address (SS58 format) */
|
|
181
|
+
address: string;
|
|
182
|
+
/** Account name/label (if available) */
|
|
183
|
+
name?: string;
|
|
184
|
+
/** Source of the account (host, dev signer, etc.) */
|
|
185
|
+
source: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Arguments for signing with a DotNS / People username identity. */
|
|
189
|
+
export interface SignMessageWithDotNsIdentityArgs {
|
|
190
|
+
/**
|
|
191
|
+
* PAPI descriptor for the People / Individuality chain that exposes
|
|
192
|
+
* `Resources.UsernameOwnerOf`.
|
|
193
|
+
*
|
|
194
|
+
* The SDK reuses an already-connected client when one matches this
|
|
195
|
+
* descriptor's genesis. For long-running apps, call
|
|
196
|
+
* `app.chain.connect({ ..., <name>: peopleChain })` once at startup so
|
|
197
|
+
* every subsequent `signMessageWithDotNsIdentity` call reuses the same
|
|
198
|
+
* chainHead subscription. When the chain isn't already connected, the
|
|
199
|
+
* SDK opens a transient connection for the lookup; that connection then
|
|
200
|
+
* lives in the chain-client cache for the rest of the process.
|
|
201
|
+
*/
|
|
202
|
+
peopleChain: PeopleUsernameChain;
|
|
203
|
+
/**
|
|
204
|
+
* People / People Lite username to resolve before signing.
|
|
205
|
+
*
|
|
206
|
+
* If omitted, the SDK fetches the primary DotNS name associated with the
|
|
207
|
+
* connected user from the host identity API. Note: that fetch triggers a
|
|
208
|
+
* host identity-permission prompt, so callers who want to avoid the
|
|
209
|
+
* dialog on a likely-miss should resolve the username themselves first.
|
|
210
|
+
*
|
|
211
|
+
* The string is UTF-8 encoded and used as the storage key on
|
|
212
|
+
* `Resources.UsernameOwnerOf` exactly as supplied — no `.dot` suffix
|
|
213
|
+
* handling is applied. On paseo-individuality today usernames are
|
|
214
|
+
* stored as bare strings (e.g. `alice`, not `alice.dot`); pass the
|
|
215
|
+
* exact value the chain has registered.
|
|
216
|
+
*/
|
|
217
|
+
username?: string;
|
|
218
|
+
/** Message to sign. Strings are UTF-8 encoded before signing. */
|
|
219
|
+
message: string | Uint8Array;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Signature produced for a DotNS / People username identity. */
|
|
223
|
+
export interface DotNsIdentitySignature {
|
|
224
|
+
/** Username used for the lookup. */
|
|
225
|
+
username: string;
|
|
226
|
+
/** Raw `AccountId32` owner resolved from `Resources.UsernameOwnerOf`. */
|
|
227
|
+
accountId: `0x${string}`;
|
|
228
|
+
/** Signature bytes returned by the host wallet. */
|
|
229
|
+
signature: Uint8Array;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Re-export ChainDefinition from polkadot-api for convenience
|
|
233
|
+
export type { ChainDefinition, TypedApi, PolkadotClient } from "polkadot-api";
|