@parity/product-sdk-chain-client 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -23
- package/dist/index.js +47 -43
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/clients.ts +99 -29
- package/src/index.ts +5 -1
- package/src/presets.ts +2 -48
- package/src/providers.ts +11 -0
- package/src/types.ts +2 -11
package/dist/index.d.ts
CHANGED
|
@@ -4,16 +4,13 @@ import { kusama_asset_hub } from '@parity/product-sdk-descriptors/kusama-asset-h
|
|
|
4
4
|
import { paseo_asset_hub } from '@parity/product-sdk-descriptors/paseo-asset-hub';
|
|
5
5
|
import { paseo_bulletin } from '@parity/product-sdk-descriptors/paseo-bulletin';
|
|
6
6
|
import { paseo_individuality } from '@parity/product-sdk-descriptors/paseo-individuality';
|
|
7
|
-
export { isInsideContainer, isInsideContainerSync } from '@parity/product-sdk-host';
|
|
7
|
+
export { ChainNotSupportedError, isInsideContainer, isInsideContainerSync } from '@parity/product-sdk-host';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Configuration for {@link createChainClient}.
|
|
11
11
|
*
|
|
12
|
-
* Provide named chain descriptors
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Note: The SDK routes all connections through the host provider. The `rpcs`
|
|
16
|
-
* field is currently unused but kept for API compatibility.
|
|
12
|
+
* Provide named chain descriptors. The SDK routes all connections through the
|
|
13
|
+
* host provider, so no RPC endpoints are required.
|
|
17
14
|
*
|
|
18
15
|
* @typeParam TChains - Record mapping user-chosen chain names to PAPI descriptors.
|
|
19
16
|
*
|
|
@@ -25,20 +22,12 @@ export { isInsideContainer, isInsideContainerSync } from '@parity/product-sdk-ho
|
|
|
25
22
|
*
|
|
26
23
|
* const client = await createChainClient({
|
|
27
24
|
* chains: { assetHub: paseo_asset_hub, bulletin: paseo_bulletin },
|
|
28
|
-
* rpcs: {
|
|
29
|
-
* assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
|
|
30
|
-
* bulletin: ["wss://paseo-bulletin-next-rpc.polkadot.io"],
|
|
31
|
-
* },
|
|
32
25
|
* });
|
|
33
26
|
* ```
|
|
34
27
|
*/
|
|
35
28
|
interface ChainClientConfig<TChains extends Record<string, ChainDefinition> = Record<string, ChainDefinition>> {
|
|
36
29
|
/** Named chain descriptors (PAPI `ChainDefinition` objects). */
|
|
37
30
|
chains: TChains;
|
|
38
|
-
/** RPC endpoints per chain name (currently unused - connections route through host). */
|
|
39
|
-
rpcs: {
|
|
40
|
-
[K in keyof TChains]: readonly string[];
|
|
41
|
-
};
|
|
42
31
|
}
|
|
43
32
|
/**
|
|
44
33
|
* A connected chain client returned by {@link createChainClient}.
|
|
@@ -83,9 +72,6 @@ interface ChainEntry {
|
|
|
83
72
|
* is designed to run exclusively inside a host container (Polkadot Browser / Desktop).
|
|
84
73
|
* Throws if no host provider is available; there is no direct-WebSocket fallback.
|
|
85
74
|
*
|
|
86
|
-
* The `config.rpcs` field is currently unused at runtime (kept for API compatibility
|
|
87
|
-
* and BYOD documentation), since the host owns the chain connection.
|
|
88
|
-
*
|
|
89
75
|
* Results are cached by genesis-hash fingerprint — calling with the same descriptors
|
|
90
76
|
* returns the same instance.
|
|
91
77
|
*
|
|
@@ -97,10 +83,6 @@ interface ChainEntry {
|
|
|
97
83
|
*
|
|
98
84
|
* const client = await createChainClient({
|
|
99
85
|
* chains: { assetHub: paseo_asset_hub, bulletin: paseo_bulletin },
|
|
100
|
-
* rpcs: {
|
|
101
|
-
* assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
|
|
102
|
-
* bulletin: ["wss://paseo-bulletin-next-rpc.polkadot.io"],
|
|
103
|
-
* },
|
|
104
86
|
* });
|
|
105
87
|
*
|
|
106
88
|
* // Fully typed from your descriptors
|
|
@@ -138,7 +120,7 @@ declare function getClient(descriptor: ChainDefinition): PolkadotClient;
|
|
|
138
120
|
*/
|
|
139
121
|
declare function isConnected(descriptor: ChainDefinition): boolean;
|
|
140
122
|
|
|
141
|
-
/** Known network environment with built-in descriptors
|
|
123
|
+
/** Known network environment with built-in descriptors. */
|
|
142
124
|
type Environment = "polkadot" | "kusama" | "paseo";
|
|
143
125
|
/** Per-environment descriptor types for each chain in the preset. */
|
|
144
126
|
type PresetDescriptors = {
|
|
@@ -161,7 +143,7 @@ type PresetDescriptors = {
|
|
|
161
143
|
/** The chain shape returned by {@link getChainAPI} for a given environment. */
|
|
162
144
|
type PresetChains<E extends Environment> = PresetDescriptors[E];
|
|
163
145
|
/**
|
|
164
|
-
* Get a chain client for a known environment with built-in descriptors
|
|
146
|
+
* Get a chain client for a known environment with built-in descriptors.
|
|
165
147
|
*
|
|
166
148
|
* This is the **zero-config** path — no need to import descriptors or specify
|
|
167
149
|
* endpoints. For custom chains or BYOD descriptors, use
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createClient } from 'polkadot-api';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { createLogger } from '@parity/product-sdk-logger';
|
|
3
|
+
import { ChainNotSupportedError, getHostProvider } from '@parity/product-sdk-host';
|
|
4
|
+
export { ChainNotSupportedError, isInsideContainer, isInsideContainerSync } from '@parity/product-sdk-host';
|
|
4
5
|
|
|
5
6
|
// src/clients.ts
|
|
6
7
|
async function createProvider(genesisHash) {
|
|
@@ -30,6 +31,19 @@ function clearClientCache() {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
// src/clients.ts
|
|
34
|
+
var log = createLogger("chain-client");
|
|
35
|
+
function unsupportedChainApi(error) {
|
|
36
|
+
const handler = {
|
|
37
|
+
get: () => {
|
|
38
|
+
throw error;
|
|
39
|
+
},
|
|
40
|
+
apply: () => {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return new Proxy((() => {
|
|
45
|
+
}), handler);
|
|
46
|
+
}
|
|
33
47
|
var cacheKey = (fingerprint, genesis) => `${fingerprint}:${genesis}`;
|
|
34
48
|
function findEntryByGenesis(genesis) {
|
|
35
49
|
for (const [key, entry] of getClientCache()) {
|
|
@@ -71,23 +85,40 @@ async function initChainClient(config, fingerprint) {
|
|
|
71
85
|
if (!genesis) {
|
|
72
86
|
throw new Error(`Descriptor for chain "${name}" has no genesis hash.`);
|
|
73
87
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
clientCache.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
try {
|
|
89
|
+
const provider = await createProvider(genesis);
|
|
90
|
+
const client = createClient(provider);
|
|
91
|
+
const key = cacheKey(fingerprint, genesis);
|
|
92
|
+
if (!clientCache.has(key)) {
|
|
93
|
+
clientCache.set(key, {
|
|
94
|
+
client,
|
|
95
|
+
api: /* @__PURE__ */ new Map()
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return { name, descriptor, client, genesis, error: void 0 };
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err instanceof ChainNotSupportedError) {
|
|
101
|
+
log.warn(
|
|
102
|
+
`Chain "${name}" is not supported by the host; its API will throw on use.`,
|
|
103
|
+
{ genesis }
|
|
104
|
+
);
|
|
105
|
+
return { name, descriptor, client: null, genesis, error: err };
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
82
108
|
}
|
|
83
|
-
return { name, descriptor, client, genesis };
|
|
84
109
|
})
|
|
85
110
|
);
|
|
86
111
|
const apis = {};
|
|
87
112
|
const raw = {};
|
|
88
|
-
for (const { name, descriptor, client } of entries) {
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
for (const { name, descriptor, client, error } of entries) {
|
|
114
|
+
if (client) {
|
|
115
|
+
apis[name] = client.getTypedApi(descriptor);
|
|
116
|
+
raw[name] = client;
|
|
117
|
+
} else {
|
|
118
|
+
const api = unsupportedChainApi(error);
|
|
119
|
+
apis[name] = api;
|
|
120
|
+
raw[name] = api;
|
|
121
|
+
}
|
|
91
122
|
}
|
|
92
123
|
return {
|
|
93
124
|
...apis,
|
|
@@ -128,30 +159,9 @@ function isConnected(descriptor) {
|
|
|
128
159
|
if (!genesis) return false;
|
|
129
160
|
return findEntryByGenesis(genesis) !== void 0;
|
|
130
161
|
}
|
|
162
|
+
|
|
163
|
+
// src/presets.ts
|
|
131
164
|
var AVAILABLE_ENVIRONMENTS = /* @__PURE__ */ new Set(["paseo"]);
|
|
132
|
-
var rpcs = {
|
|
133
|
-
polkadot: {
|
|
134
|
-
assetHub: [
|
|
135
|
-
"wss://polkadot-asset-hub-rpc.polkadot.io",
|
|
136
|
-
"wss://sys.ibp.network/asset-hub-polkadot"
|
|
137
|
-
],
|
|
138
|
-
bulletin: [...BULLETIN_RPCS.polkadot],
|
|
139
|
-
individuality: []
|
|
140
|
-
},
|
|
141
|
-
kusama: {
|
|
142
|
-
assetHub: [
|
|
143
|
-
"wss://kusama-asset-hub-rpc.polkadot.io",
|
|
144
|
-
"wss://sys.ibp.network/asset-hub-kusama"
|
|
145
|
-
],
|
|
146
|
-
bulletin: [...BULLETIN_RPCS.kusama],
|
|
147
|
-
individuality: []
|
|
148
|
-
},
|
|
149
|
-
paseo: {
|
|
150
|
-
assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
|
|
151
|
-
bulletin: [...BULLETIN_RPCS.paseo],
|
|
152
|
-
individuality: ["wss://paseo-people-next-system-rpc.polkadot.io"]
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
165
|
async function loadDescriptors(env) {
|
|
156
166
|
const loaders = {
|
|
157
167
|
polkadot: () => Promise.all([
|
|
@@ -183,17 +193,11 @@ async function getChainAPI(env) {
|
|
|
183
193
|
throw new Error(`Chain API for "${env}" is not yet available`);
|
|
184
194
|
}
|
|
185
195
|
const descriptors = await loadDescriptors(env);
|
|
186
|
-
const envRpcs = rpcs[env];
|
|
187
196
|
return createChainClient({
|
|
188
197
|
chains: {
|
|
189
198
|
assetHub: descriptors.assetHub,
|
|
190
199
|
bulletin: descriptors.bulletin,
|
|
191
200
|
individuality: descriptors.individuality
|
|
192
|
-
},
|
|
193
|
-
rpcs: {
|
|
194
|
-
assetHub: [...envRpcs.assetHub],
|
|
195
|
-
bulletin: [...envRpcs.bulletin],
|
|
196
|
-
individuality: [...envRpcs.individuality]
|
|
197
201
|
}
|
|
198
202
|
});
|
|
199
203
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers.ts","../src/hmr.ts","../src/clients.ts","../src/presets.ts","../src/well-known-chain.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { getHostProvider } from \"@parity/product-sdk-host\";\nimport type { JsonRpcProvider } from \"polkadot-api\";\n\n/**\n * Create a PAPI-compatible JSON-RPC provider for a chain.\n *\n * Routes connections through the host provider (`@parity/product-sdk-host`).\n * The SDK is designed to run exclusively inside a host container.\n *\n * @throws {Error} If the host provider is unavailable (not inside a container).\n */\nexport async function createProvider(genesisHash: string): Promise<JsonRpcProvider> {\n const hostProvider = await getHostProvider(genesisHash as `0x${string}`);\n if (!hostProvider) {\n throw new Error(\n `Host provider unavailable for chain ${genesisHash}. Ensure you are running inside a host container (Polkadot Browser / Desktop).`,\n );\n }\n return hostProvider;\n}\n\nif (import.meta.vitest) {\n const { test, expect, vi, beforeEach } = import.meta.vitest;\n\n // Shared state between hoisted mocks and tests\n const state = vi.hoisted(() => ({\n fakeProvider: (() => {}) as unknown as JsonRpcProvider,\n hostProviderCalls: [] as unknown[][],\n hostProviderAvailable: true,\n }));\n\n vi.mock(\"@parity/product-sdk-host\", async (importOriginal) => ({\n ...(await importOriginal<typeof import(\"@parity/product-sdk-host\")>()),\n getHostProvider: async (...args: unknown[]) => {\n state.hostProviderCalls.push(args);\n if (!state.hostProviderAvailable) return null;\n return state.fakeProvider;\n },\n }));\n\n beforeEach(() => {\n state.hostProviderCalls = [];\n state.hostProviderAvailable = true;\n });\n\n test(\"returns host provider when available\", async () => {\n const result = await createProvider(\"0xabc\");\n expect(result).toBe(state.fakeProvider);\n expect(state.hostProviderCalls.length).toBe(1);\n expect(state.hostProviderCalls[0][0]).toBe(\"0xabc\");\n });\n\n test(\"throws when host provider unavailable\", async () => {\n state.hostProviderAvailable = false;\n await expect(createProvider(\"0xabc\")).rejects.toThrow(/Host provider unavailable/);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChainEntry } from \"./types.js\";\n\ndeclare global {\n var __chainClientCache: Map<string, ChainEntry> | undefined;\n}\n\n/** Get the HMR-safe client cache, keyed by genesis hash. */\nexport function getClientCache(): Map<string, ChainEntry> {\n globalThis.__chainClientCache ??= new Map();\n return globalThis.__chainClientCache;\n}\n\n/** Clear all entries from the client cache. Destroys active clients. */\nexport function clearClientCache(): void {\n const cache = getClientCache();\n for (const entry of cache.values()) {\n try {\n entry.client.destroy();\n } catch {\n // client may already be destroyed\n }\n }\n cache.clear();\n}\n\nif (import.meta.vitest) {\n const { test, expect } = import.meta.vitest;\n\n test(\"getClientCache returns a Map\", () => {\n const cache = getClientCache();\n expect(cache).toBeInstanceOf(Map);\n });\n\n test(\"getClientCache returns the same instance on repeated calls\", () => {\n const a = getClientCache();\n const b = getClientCache();\n expect(a).toBe(b);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChainDefinition, PolkadotClient } from \"polkadot-api\";\nimport { createClient } from \"polkadot-api\";\nimport { createProvider } from \"./providers.js\";\nimport { getClientCache, clearClientCache } from \"./hmr.js\";\nimport type { ChainEntry, ChainClientConfig, ChainClient } from \"./types.js\";\n\n// Cache keys are scoped by a fingerprint of the config so that two\n// `createChainClient` calls with different chain sets don't collide.\nconst cacheKey = (fingerprint: string, genesis: string) => `${fingerprint}:${genesis}`;\n\nfunction findEntryByGenesis(genesis: string): ChainEntry | undefined {\n for (const [key, entry] of getClientCache()) {\n if (key.endsWith(`:${genesis}`)) return entry;\n }\n}\n\nconst clientInstances = new Map<string, Promise<ChainClient<any>>>();\n\n/** Build a stable fingerprint from sorted chain names + genesis hashes. */\nfunction configFingerprint(chains: Record<string, ChainDefinition>): string {\n return Object.entries(chains)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([name, desc]) => `${name}:${desc.genesis ?? \"unknown\"}`)\n .join(\"|\");\n}\n\n/**\n * Create a multi-chain client with user-provided descriptors and RPC endpoints.\n *\n * Returns fully-typed APIs for each chain plus raw `PolkadotClient` access via `.raw`.\n * Connections route through the host provider (`@parity/product-sdk-host`) — the SDK\n * is designed to run exclusively inside a host container (Polkadot Browser / Desktop).\n * Throws if no host provider is available; there is no direct-WebSocket fallback.\n *\n * The `config.rpcs` field is currently unused at runtime (kept for API compatibility\n * and BYOD documentation), since the host owns the chain connection.\n *\n * Results are cached by genesis-hash fingerprint — calling with the same descriptors\n * returns the same instance.\n *\n * @example\n * ```ts\n * import { createChainClient } from \"@parity/product-sdk-chain-client\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { paseo_bulletin } from \"@parity/product-sdk-descriptors/paseo-bulletin\";\n *\n * const client = await createChainClient({\n * chains: { assetHub: paseo_asset_hub, bulletin: paseo_bulletin },\n * rpcs: {\n * assetHub: [\"wss://paseo-asset-hub-next-rpc.polkadot.io\"],\n * bulletin: [\"wss://paseo-bulletin-next-rpc.polkadot.io\"],\n * },\n * });\n *\n * // Fully typed from your descriptors\n * const account = await client.assetHub.query.System.Account.getValue(addr);\n * const fee = await client.bulletin.query.TransactionStorage.ByteFee.getValue();\n *\n * // Raw client for advanced use (e.g., a ContractRuntime for pallet-revive contracts)\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);\n *\n * // Cleanup\n * client.destroy();\n * ```\n */\nexport async function createChainClient<const TChains extends Record<string, ChainDefinition>>(\n config: ChainClientConfig<TChains>,\n): Promise<ChainClient<TChains>> {\n const fingerprint = configFingerprint(config.chains);\n\n const existing = clientInstances.get(fingerprint);\n if (existing) return existing as Promise<ChainClient<TChains>>;\n\n const promise = initChainClient(config, fingerprint).catch((err) => {\n // Clean up any clients created before the failure to avoid leaking\n // WebSocket connections that are unreachable except via destroyAll().\n const cache = getClientCache();\n for (const [key, entry] of cache) {\n if (key.startsWith(`${fingerprint}:`)) {\n try {\n entry.client.destroy();\n } catch {\n /* already destroyed */\n }\n cache.delete(key);\n }\n }\n clientInstances.delete(fingerprint);\n throw err;\n });\n clientInstances.set(fingerprint, promise);\n return promise;\n}\n\n/* @integration */\nasync function initChainClient<const TChains extends Record<string, ChainDefinition>>(\n config: ChainClientConfig<TChains>,\n fingerprint: string,\n): Promise<ChainClient<TChains>> {\n const names = Object.keys(config.chains) as (string & keyof TChains)[];\n const clientCache = getClientCache();\n\n // Create providers and clients in parallel\n const entries = await Promise.all(\n names.map(async (name) => {\n const descriptor = config.chains[name] as ChainDefinition;\n const genesis = descriptor.genesis;\n if (!genesis) {\n throw new Error(`Descriptor for chain \"${name}\" has no genesis hash.`);\n }\n const provider = await createProvider(genesis);\n const client = createClient(provider);\n\n // Populate HMR cache so getClient() and isConnected() work\n const key = cacheKey(fingerprint, genesis);\n if (!clientCache.has(key)) {\n clientCache.set(key, {\n client,\n api: new Map(),\n } satisfies ChainEntry);\n }\n\n return { name, descriptor, client, genesis };\n }),\n );\n\n // Build typed APIs and raw client map\n const apis = {} as Record<string, unknown>;\n const raw = {} as Record<string, PolkadotClient>;\n\n for (const { name, descriptor, client } of entries) {\n apis[name] = client.getTypedApi(descriptor);\n raw[name] = client;\n }\n\n return {\n ...apis,\n raw,\n destroy() {\n for (const { genesis } of entries) {\n const key = cacheKey(fingerprint, genesis);\n const entry = clientCache.get(key);\n if (entry) {\n try {\n entry.client.destroy();\n } catch {\n /* already destroyed */\n }\n clientCache.delete(key);\n }\n }\n clientInstances.delete(fingerprint);\n },\n } as ChainClient<TChains>;\n}\n\n/**\n * Destroy all chain client instances and reset internal caches.\n *\n * Tears down every connection created by {@link createChainClient}.\n */\nexport function destroyAll(): void {\n clearClientCache();\n clientInstances.clear();\n}\n\n/**\n * Get the raw `PolkadotClient` for a connected chain by its descriptor.\n *\n * The chain must have been initialized via {@link createChainClient} first.\n * Alternatively, use `client.raw.<name>` on the returned {@link ChainClient}.\n *\n * @throws If the chain has not been connected yet.\n */\nexport function getClient(descriptor: ChainDefinition): PolkadotClient {\n const genesis = descriptor.genesis;\n if (!genesis) throw new Error(\"Descriptor has no genesis hash.\");\n const entry = findEntryByGenesis(genesis);\n if (!entry?.client) {\n throw new Error(\n `Chain not connected (genesis: ${genesis}). Call createChainClient() first to establish connections.`,\n );\n }\n return entry.client;\n}\n\n/**\n * Check if a chain is currently connected.\n *\n * Synchronous — no side effects, no initialization.\n */\nexport function isConnected(descriptor: ChainDefinition): boolean {\n const genesis = descriptor.genesis;\n if (!genesis) return false;\n return findEntryByGenesis(genesis) !== undefined;\n}\n\nif (import.meta.vitest) {\n const { test, expect, beforeEach } = import.meta.vitest;\n\n const fakeDescriptor = { genesis: \"0xtest\" } as ChainDefinition;\n const fakeClient = {\n destroy: () => {},\n getTypedApi: () => ({}),\n } as unknown as PolkadotClient;\n\n function seedCache(genesis: string, client: PolkadotClient, fp = \"test\") {\n getClientCache().set(cacheKey(fp, genesis), {\n client,\n api: new Map(),\n });\n }\n\n beforeEach(() => {\n clearClientCache();\n clientInstances.clear();\n });\n\n // --- isConnected ---\n\n test(\"isConnected returns false for unknown chain\", () => {\n expect(isConnected(fakeDescriptor)).toBe(false);\n });\n\n test(\"isConnected returns true after cache is populated\", () => {\n seedCache(\"0xtest\", fakeClient);\n expect(isConnected(fakeDescriptor)).toBe(true);\n });\n\n test(\"isConnected returns false for descriptor without genesis\", () => {\n expect(isConnected({} as ChainDefinition)).toBe(false);\n });\n\n // --- getClient ---\n\n test(\"getClient returns client from cache\", () => {\n seedCache(\"0xtest\", fakeClient);\n expect(getClient(fakeDescriptor)).toBe(fakeClient);\n });\n\n test(\"getClient throws for unconnected chain\", () => {\n expect(() => getClient(fakeDescriptor)).toThrow(/Chain not connected/);\n });\n\n test(\"getClient throws for descriptor without genesis\", () => {\n expect(() => getClient({} as ChainDefinition)).toThrow(/no genesis hash/);\n });\n\n // --- destroyAll ---\n\n test(\"destroyAll calls client.destroy() and clears caches\", () => {\n let destroyed = false;\n const trackableClient = {\n destroy: () => {\n destroyed = true;\n },\n getTypedApi: () => ({}),\n } as unknown as PolkadotClient;\n seedCache(\"0xtest\", trackableClient);\n clientInstances.set(\"test\", Promise.resolve({} as ChainClient<any>));\n destroyAll();\n expect(destroyed).toBe(true);\n expect(isConnected(fakeDescriptor)).toBe(false);\n expect(clientInstances.size).toBe(0);\n });\n\n // --- createChainClient ---\n\n test(\"createChainClient returns same promise for identical config\", async () => {\n const fakeResult = {} as ChainClient<any>;\n const fp = configFingerprint({ a: fakeDescriptor });\n clientInstances.set(fp, Promise.resolve(fakeResult));\n const result = await createChainClient({\n chains: { a: fakeDescriptor },\n rpcs: { a: [] },\n });\n expect(result).toBe(fakeResult);\n });\n\n test(\"createChainClient deduplicates concurrent calls\", async () => {\n const fakeResult = {} as ChainClient<any>;\n const fp = configFingerprint({ x: fakeDescriptor });\n clientInstances.set(fp, Promise.resolve(fakeResult));\n const [a, b] = await Promise.all([\n createChainClient({ chains: { x: fakeDescriptor }, rpcs: { x: [] } }),\n createChainClient({ chains: { x: fakeDescriptor }, rpcs: { x: [] } }),\n ]);\n expect(a).toBe(b);\n });\n\n test(\"createChainClient returns different results for different configs\", async () => {\n const descA = { genesis: \"0xaaa\" } as ChainDefinition;\n const descB = { genesis: \"0xbbb\" } as ChainDefinition;\n const resultA = {} as ChainClient<any>;\n const resultB = {} as ChainClient<any>;\n clientInstances.set(configFingerprint({ a: descA }), Promise.resolve(resultA));\n clientInstances.set(configFingerprint({ b: descB }), Promise.resolve(resultB));\n const a = await createChainClient({ chains: { a: descA }, rpcs: { a: [] } });\n const b = await createChainClient({ chains: { b: descB }, rpcs: { b: [] } });\n expect(a).not.toBe(b);\n });\n\n // --- configFingerprint ---\n\n test(\"configFingerprint is stable regardless of key order\", () => {\n const d1 = { genesis: \"0x1\" } as ChainDefinition;\n const d2 = { genesis: \"0x2\" } as ChainDefinition;\n expect(configFingerprint({ a: d1, b: d2 })).toBe(configFingerprint({ b: d2, a: d1 }));\n });\n\n // --- findEntryByGenesis ---\n\n test(\"findEntryByGenesis returns undefined for missing genesis\", () => {\n expect(findEntryByGenesis(\"0xnonexistent\")).toBeUndefined();\n });\n\n // --- full lifecycle ---\n\n test(\"full lifecycle: seed, verify connected, destroy, verify disconnected\", () => {\n seedCache(\"0xtest\", fakeClient);\n expect(isConnected(fakeDescriptor)).toBe(true);\n expect(getClient(fakeDescriptor)).toBe(fakeClient);\n destroyAll();\n expect(isConnected(fakeDescriptor)).toBe(false);\n expect(() => getClient(fakeDescriptor)).toThrow(/Chain not connected/);\n });\n\n test(\"two fingerprints cached independently, destroy one leaves other intact\", () => {\n const sharedGenesis = \"0xshared\";\n const clientA = { destroy: () => {} } as PolkadotClient;\n const clientB = { destroy: () => {} } as PolkadotClient;\n const descriptorShared = { genesis: sharedGenesis } as ChainDefinition;\n\n seedCache(sharedGenesis, clientA, \"fpA\");\n seedCache(sharedGenesis, clientB, \"fpB\");\n\n expect(isConnected(descriptorShared)).toBe(true);\n\n // Destroy only fpA's entry\n const cache = getClientCache();\n const keyA = cacheKey(\"fpA\", sharedGenesis);\n cache.get(keyA)?.client.destroy();\n cache.delete(keyA);\n\n // fpB's entry still alive\n expect(isConnected(descriptorShared)).toBe(true);\n expect(getClient(descriptorShared)).toBe(clientB);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChainDefinition } from \"polkadot-api\";\nimport { BULLETIN_RPCS } from \"@parity/product-sdk-host\";\nimport { createChainClient } from \"./clients.js\";\nimport type { ChainClient } from \"./types.js\";\n\n// Type-only imports — erased at compile time, zero bundle cost.\n// These give us per-chain TypedApi types without importing runtime descriptor data.\n// Every environment ships its own descriptor for each chain (asset hub, bulletin,\n// individuality) so that genesis hashes and metadata reflect the live chain\n// instance the consumer connects to.\nimport type { polkadot_asset_hub as PolkadotAssetHubDef } from \"@parity/product-sdk-descriptors/polkadot-asset-hub\";\nimport type { kusama_asset_hub as KusamaAssetHubDef } from \"@parity/product-sdk-descriptors/kusama-asset-hub\";\nimport type { paseo_asset_hub as PaseoAssetHubDef } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\nimport type { paseo_bulletin as PaseoBulletinDef } from \"@parity/product-sdk-descriptors/paseo-bulletin\";\nimport type { paseo_individuality as PaseoIndividualityDef } from \"@parity/product-sdk-descriptors/paseo-individuality\";\n\n/** Known network environment with built-in descriptors and RPC endpoints. */\nexport type Environment = \"polkadot\" | \"kusama\" | \"paseo\";\n\n/** Environments where all chains (asset hub, bulletin, individuality) are live. */\nconst AVAILABLE_ENVIRONMENTS: Set<Environment> = new Set([\"paseo\"]);\n\nconst rpcs = {\n polkadot: {\n assetHub: [\n \"wss://polkadot-asset-hub-rpc.polkadot.io\",\n \"wss://sys.ibp.network/asset-hub-polkadot\",\n ],\n bulletin: [...BULLETIN_RPCS.polkadot],\n individuality: [] as string[],\n },\n kusama: {\n assetHub: [\n \"wss://kusama-asset-hub-rpc.polkadot.io\",\n \"wss://sys.ibp.network/asset-hub-kusama\",\n ],\n bulletin: [...BULLETIN_RPCS.kusama],\n individuality: [] as string[],\n },\n paseo: {\n assetHub: [\"wss://paseo-asset-hub-next-rpc.polkadot.io\"],\n bulletin: [...BULLETIN_RPCS.paseo],\n individuality: [\"wss://paseo-people-next-system-rpc.polkadot.io\"],\n },\n} as const;\n\n/**\n * Lazy-load descriptors for a specific environment.\n *\n * Every chain (asset hub, bulletin, individuality) ships a per-environment\n * descriptor so that genesis hashes and metadata reflect the live chain\n * instance the consumer connects to. Dynamic imports are code-split per\n * environment, so a consumer using one environment doesn't bundle the others.\n */\nasync function loadDescriptors(env: Environment) {\n const loaders = {\n polkadot: () =>\n Promise.all([\n import(\"@parity/product-sdk-descriptors/polkadot-asset-hub\"),\n // Polkadot bulletin/individuality are not yet live; gated by\n // AVAILABLE_ENVIRONMENTS so this branch is unreachable today.\n Promise.reject(new Error(\"polkadot bulletin descriptor not yet available\")),\n Promise.reject(new Error(\"polkadot individuality descriptor not yet available\")),\n ]),\n kusama: () =>\n Promise.all([\n import(\"@parity/product-sdk-descriptors/kusama-asset-hub\"),\n Promise.reject(new Error(\"kusama bulletin descriptor not yet available\")),\n Promise.reject(new Error(\"kusama individuality descriptor not yet available\")),\n ]),\n paseo: () =>\n Promise.all([\n import(\"@parity/product-sdk-descriptors/paseo-asset-hub\"),\n import(\"@parity/product-sdk-descriptors/paseo-bulletin\"),\n import(\"@parity/product-sdk-descriptors/paseo-individuality\"),\n ]),\n };\n\n const [ahMod, bulletinMod, individualityMod] = await loaders[env]();\n\n const assetHub =\n \"polkadot_asset_hub\" in ahMod\n ? ahMod.polkadot_asset_hub\n : \"kusama_asset_hub\" in ahMod\n ? ahMod.kusama_asset_hub\n : (ahMod as { paseo_asset_hub: typeof PaseoAssetHubDef }).paseo_asset_hub;\n\n const bulletin = (bulletinMod as { paseo_bulletin: typeof PaseoBulletinDef }).paseo_bulletin;\n\n const individuality = (\n individualityMod as { paseo_individuality: typeof PaseoIndividualityDef }\n ).paseo_individuality;\n\n return { assetHub, bulletin, individuality };\n}\n\n/** Per-environment descriptor types for each chain in the preset. */\ntype PresetDescriptors = {\n polkadot: {\n assetHub: typeof PolkadotAssetHubDef;\n // Bulletin/individuality not yet live on polkadot — types reuse paseo\n // shape so the API surface stays consistent; runtime path is gated.\n bulletin: typeof PaseoBulletinDef;\n individuality: typeof PaseoIndividualityDef;\n };\n kusama: {\n assetHub: typeof KusamaAssetHubDef;\n bulletin: typeof PaseoBulletinDef;\n individuality: typeof PaseoIndividualityDef;\n };\n paseo: {\n assetHub: typeof PaseoAssetHubDef;\n bulletin: typeof PaseoBulletinDef;\n individuality: typeof PaseoIndividualityDef;\n };\n};\n\n/** The chain shape returned by {@link getChainAPI} for a given environment. */\nexport type PresetChains<E extends Environment> = PresetDescriptors[E];\n\n/**\n * Get a chain client for a known environment with built-in descriptors and RPCs.\n *\n * This is the **zero-config** path — no need to import descriptors or specify\n * endpoints. For custom chains or BYOD descriptors, use\n * {@link createChainClient} instead.\n *\n * Returns the same {@link ChainClient} type as `createChainClient`, with\n * `assetHub`, `bulletin`, and `individuality` chain keys.\n *\n * @example\n * ```ts\n * import { getChainAPI } from \"@parity/product-sdk-chain-client\";\n *\n * const client = await getChainAPI(\"paseo\");\n *\n * // Fully typed — no descriptor imports needed\n * const account = await client.assetHub.query.System.Account.getValue(addr);\n * const fee = await client.bulletin.query.TransactionStorage.ByteFee.getValue();\n *\n * // Raw client for advanced use (e.g., a ContractRuntime for pallet-revive contracts)\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);\n *\n * client.destroy();\n * ```\n */\nexport async function getChainAPI<E extends Environment>(\n env: E,\n): Promise<ChainClient<PresetChains<E>>> {\n if (!AVAILABLE_ENVIRONMENTS.has(env)) {\n throw new Error(`Chain API for \"${env}\" is not yet available`);\n }\n\n const descriptors = await loadDescriptors(env);\n const envRpcs = rpcs[env];\n\n return createChainClient({\n chains: {\n assetHub: descriptors.assetHub,\n bulletin: descriptors.bulletin,\n individuality: descriptors.individuality,\n },\n rpcs: {\n assetHub: [...envRpcs.assetHub],\n bulletin: [...envRpcs.bulletin],\n individuality: [...envRpcs.individuality],\n },\n }) as Promise<ChainClient<PresetChains<E>>>;\n}\n\nif (import.meta.vitest) {\n const { test, expect, beforeEach } = import.meta.vitest;\n const { destroyAll } = await import(\"./clients.js\");\n\n // Test-only genesis hashes for assertion — not used in production code.\n const GENESIS = {\n polkadot_asset_hub: \"0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f\",\n kusama_asset_hub: \"0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a\",\n paseo_asset_hub: \"0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f\",\n paseo_bulletin: \"0x8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22\",\n paseo_individuality: \"0xc5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5\",\n } as const;\n\n beforeEach(() => {\n destroyAll();\n });\n\n // --- GENESIS constants ---\n\n test(\"genesis constants are valid hex hashes\", () => {\n for (const hash of Object.values(GENESIS)) {\n expect(hash).toMatch(/^0x[a-f0-9]{64}$/);\n }\n });\n\n // --- RPC config ---\n\n test(\"rpcs defined for all environments\", () => {\n for (const env of [\"polkadot\", \"kusama\", \"paseo\"] as const) {\n const envRpcs = rpcs[env];\n expect(envRpcs.assetHub.length).toBeGreaterThan(0);\n }\n });\n\n test(\"paseo has RPCs for all chains\", () => {\n const envRpcs = rpcs.paseo;\n expect(envRpcs.bulletin.length).toBeGreaterThan(0);\n expect(envRpcs.individuality.length).toBeGreaterThan(0);\n });\n\n // --- getChainAPI ---\n\n test(\"polkadot and kusama throw as not yet available\", async () => {\n await expect(getChainAPI(\"polkadot\")).rejects.toThrow(\"not yet available\");\n await expect(getChainAPI(\"kusama\")).rejects.toThrow(\"not yet available\");\n });\n\n // --- loadDescriptors ---\n\n test(\"loadDescriptors returns descriptors with genesis hashes for paseo\", async () => {\n const descriptors = await loadDescriptors(\"paseo\");\n expect(descriptors).toBeDefined();\n expect(descriptors.assetHub).toBeDefined();\n expect(descriptors.bulletin).toBeDefined();\n expect(descriptors.individuality).toBeDefined();\n expect(descriptors.assetHub.genesis).toBe(GENESIS.paseo_asset_hub);\n expect(descriptors.bulletin.genesis).toBe(GENESIS.paseo_bulletin);\n expect(descriptors.individuality.genesis).toBe(GENESIS.paseo_individuality);\n });\n\n // --- AVAILABLE_ENVIRONMENTS ---\n\n test(\"paseo is currently available\", () => {\n expect(AVAILABLE_ENVIRONMENTS.has(\"paseo\")).toBe(true);\n expect(AVAILABLE_ENVIRONMENTS.has(\"polkadot\")).toBe(false);\n expect(AVAILABLE_ENVIRONMENTS.has(\"kusama\")).toBe(false);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Genesis hashes of well-known chains.\n *\n * Mirrors `WellKnownChain` from `@novasamatech/host-api-wrapper`. Hand-copied\n * (not re-exported) so chain-client doesn't pick up a direct Novasama\n * runtime dependency; genesis hashes are immutable so drift is impossible.\n */\nexport const WellKnownChain = {\n polkadotRelay: \"0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3\",\n polkadotAssetHub: \"0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f\",\n kusamaRelay: \"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe\",\n kusamaAssetHub: \"0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a\",\n westendRelay: \"0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e\",\n westendAssetHub: \"0x67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9\",\n rococo: \"0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e\",\n} as const;\n\n/** Genesis hash of a well-known chain - the value type of {@link WellKnownChain}. */\nexport type WellKnownChainHash = (typeof WellKnownChain)[keyof typeof WellKnownChain];\n\nif (import.meta.vitest) {\n const { test, expect } = import.meta.vitest;\n\n test(\"WellKnownChain entries are 0x-prefixed 32-byte hex strings\", () => {\n for (const hash of Object.values(WellKnownChain)) {\n expect(hash).toMatch(/^0x[0-9a-f]{64}$/);\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/providers.ts","../src/hmr.ts","../src/clients.ts","../src/presets.ts","../src/well-known-chain.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { getHostProvider } from \"@parity/product-sdk-host\";\nimport type { JsonRpcProvider } from \"polkadot-api\";\n\n/**\n * Create a PAPI-compatible JSON-RPC provider for a chain.\n *\n * Routes connections through the host provider (`@parity/product-sdk-host`).\n * The SDK is designed to run exclusively inside a host container.\n *\n * @throws {Error} If the host provider is unavailable (not inside a container).\n */\nexport async function createProvider(genesisHash: string): Promise<JsonRpcProvider> {\n const hostProvider = await getHostProvider(genesisHash as `0x${string}`);\n if (!hostProvider) {\n throw new Error(\n `Host provider unavailable for chain ${genesisHash}. Ensure you are running inside a host container (Polkadot Browser / Desktop).`,\n );\n }\n return hostProvider;\n}\n\nif (import.meta.vitest) {\n const { test, expect, vi, beforeEach } = import.meta.vitest;\n\n // Shared state between hoisted mocks and tests\n const state = vi.hoisted(() => ({\n fakeProvider: (() => {}) as unknown as JsonRpcProvider,\n hostProviderCalls: [] as unknown[][],\n hostProviderAvailable: true,\n hostProviderError: null as Error | null,\n }));\n\n vi.mock(\"@parity/product-sdk-host\", async (importOriginal) => ({\n ...(await importOriginal<typeof import(\"@parity/product-sdk-host\")>()),\n getHostProvider: async (...args: unknown[]) => {\n state.hostProviderCalls.push(args);\n if (state.hostProviderError) throw state.hostProviderError;\n if (!state.hostProviderAvailable) return null;\n return state.fakeProvider;\n },\n }));\n\n beforeEach(() => {\n state.hostProviderCalls = [];\n state.hostProviderAvailable = true;\n state.hostProviderError = null;\n });\n\n test(\"returns host provider when available\", async () => {\n const result = await createProvider(\"0xabc\");\n expect(result).toBe(state.fakeProvider);\n expect(state.hostProviderCalls.length).toBe(1);\n expect(state.hostProviderCalls[0][0]).toBe(\"0xabc\");\n });\n\n test(\"throws when host provider unavailable\", async () => {\n state.hostProviderAvailable = false;\n await expect(createProvider(\"0xabc\")).rejects.toThrow(/Host provider unavailable/);\n });\n\n test(\"propagates ChainNotSupportedError from the host provider\", async () => {\n const { ChainNotSupportedError } = await import(\"@parity/product-sdk-host\");\n state.hostProviderError = new ChainNotSupportedError(\"0xabc\");\n // The unsupported-chain error must surface to the caller rather than being\n // collapsed into the generic \"unavailable\" path or swallowed.\n await expect(createProvider(\"0xabc\")).rejects.toBeInstanceOf(ChainNotSupportedError);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChainEntry } from \"./types.js\";\n\ndeclare global {\n var __chainClientCache: Map<string, ChainEntry> | undefined;\n}\n\n/** Get the HMR-safe client cache, keyed by genesis hash. */\nexport function getClientCache(): Map<string, ChainEntry> {\n globalThis.__chainClientCache ??= new Map();\n return globalThis.__chainClientCache;\n}\n\n/** Clear all entries from the client cache. Destroys active clients. */\nexport function clearClientCache(): void {\n const cache = getClientCache();\n for (const entry of cache.values()) {\n try {\n entry.client.destroy();\n } catch {\n // client may already be destroyed\n }\n }\n cache.clear();\n}\n\nif (import.meta.vitest) {\n const { test, expect } = import.meta.vitest;\n\n test(\"getClientCache returns a Map\", () => {\n const cache = getClientCache();\n expect(cache).toBeInstanceOf(Map);\n });\n\n test(\"getClientCache returns the same instance on repeated calls\", () => {\n const a = getClientCache();\n const b = getClientCache();\n expect(a).toBe(b);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChainDefinition, PolkadotClient } from \"polkadot-api\";\nimport { createClient } from \"polkadot-api\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { ChainNotSupportedError } from \"@parity/product-sdk-host\";\nimport { createProvider } from \"./providers.js\";\nimport { getClientCache, clearClientCache } from \"./hmr.js\";\nimport type { ChainEntry, ChainClientConfig, ChainClient } from \"./types.js\";\n\nconst log = createLogger(\"chain-client\");\n\n/**\n * Build a stand-in for a chain the host can't serve. Any property access (e.g.\n * `.query`, `.tx`) or call throws the original {@link ChainNotSupportedError},\n * so touching an unsupported chain surfaces a clear, detectable error rather\n * than hanging — without tanking the supported chains in the same client.\n */\nfunction unsupportedChainApi(error: ChainNotSupportedError): never {\n const handler: ProxyHandler<() => void> = {\n get: () => {\n throw error;\n },\n apply: () => {\n throw error;\n },\n };\n return new Proxy((() => {}) as () => void, handler) as never;\n}\n\n// Cache keys are scoped by a fingerprint of the config so that two\n// `createChainClient` calls with different chain sets don't collide.\nconst cacheKey = (fingerprint: string, genesis: string) => `${fingerprint}:${genesis}`;\n\nfunction findEntryByGenesis(genesis: string): ChainEntry | undefined {\n for (const [key, entry] of getClientCache()) {\n if (key.endsWith(`:${genesis}`)) return entry;\n }\n}\n\nconst clientInstances = new Map<string, Promise<ChainClient<any>>>();\n\n/** Build a stable fingerprint from sorted chain names + genesis hashes. */\nfunction configFingerprint(chains: Record<string, ChainDefinition>): string {\n return Object.entries(chains)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([name, desc]) => `${name}:${desc.genesis ?? \"unknown\"}`)\n .join(\"|\");\n}\n\n/**\n * Create a multi-chain client with user-provided descriptors and RPC endpoints.\n *\n * Returns fully-typed APIs for each chain plus raw `PolkadotClient` access via `.raw`.\n * Connections route through the host provider (`@parity/product-sdk-host`) — the SDK\n * is designed to run exclusively inside a host container (Polkadot Browser / Desktop).\n * Throws if no host provider is available; there is no direct-WebSocket fallback.\n *\n * Results are cached by genesis-hash fingerprint — calling with the same descriptors\n * returns the same instance.\n *\n * @example\n * ```ts\n * import { createChainClient } from \"@parity/product-sdk-chain-client\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { paseo_bulletin } from \"@parity/product-sdk-descriptors/paseo-bulletin\";\n *\n * const client = await createChainClient({\n * chains: { assetHub: paseo_asset_hub, bulletin: paseo_bulletin },\n * });\n *\n * // Fully typed from your descriptors\n * const account = await client.assetHub.query.System.Account.getValue(addr);\n * const fee = await client.bulletin.query.TransactionStorage.ByteFee.getValue();\n *\n * // Raw client for advanced use (e.g., a ContractRuntime for pallet-revive contracts)\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);\n *\n * // Cleanup\n * client.destroy();\n * ```\n */\nexport async function createChainClient<const TChains extends Record<string, ChainDefinition>>(\n config: ChainClientConfig<TChains>,\n): Promise<ChainClient<TChains>> {\n const fingerprint = configFingerprint(config.chains);\n\n const existing = clientInstances.get(fingerprint);\n if (existing) return existing as Promise<ChainClient<TChains>>;\n\n const promise = initChainClient(config, fingerprint).catch((err) => {\n // Clean up any clients created before the failure to avoid leaking\n // WebSocket connections that are unreachable except via destroyAll().\n const cache = getClientCache();\n for (const [key, entry] of cache) {\n if (key.startsWith(`${fingerprint}:`)) {\n try {\n entry.client.destroy();\n } catch {\n /* already destroyed */\n }\n cache.delete(key);\n }\n }\n clientInstances.delete(fingerprint);\n throw err;\n });\n clientInstances.set(fingerprint, promise);\n return promise;\n}\n\n/* @integration */\nasync function initChainClient<const TChains extends Record<string, ChainDefinition>>(\n config: ChainClientConfig<TChains>,\n fingerprint: string,\n): Promise<ChainClient<TChains>> {\n const names = Object.keys(config.chains) as (string & keyof TChains)[];\n const clientCache = getClientCache();\n\n // Create providers and clients in parallel\n const entries = await Promise.all(\n names.map(async (name) => {\n const descriptor = config.chains[name] as ChainDefinition;\n const genesis = descriptor.genesis;\n if (!genesis) {\n throw new Error(`Descriptor for chain \"${name}\" has no genesis hash.`);\n }\n try {\n const provider = await createProvider(genesis);\n const client = createClient(provider);\n\n // Populate HMR cache so getClient() and isConnected() work\n const key = cacheKey(fingerprint, genesis);\n if (!clientCache.has(key)) {\n clientCache.set(key, {\n client,\n api: new Map(),\n } satisfies ChainEntry);\n }\n\n return { name, descriptor, client, genesis, error: undefined };\n } catch (err) {\n // A chain the host can't serve must not tank the whole multi-chain\n // client — the supported chains stay usable, and this one surfaces a\n // clear error on first use instead of hanging. Any other failure\n // (e.g. not inside a container) is a hard error and still rejects.\n if (err instanceof ChainNotSupportedError) {\n log.warn(\n `Chain \"${name}\" is not supported by the host; its API will throw on use.`,\n { genesis },\n );\n return { name, descriptor, client: null, genesis, error: err };\n }\n throw err;\n }\n }),\n );\n\n // Build typed APIs and raw client map. Unsupported chains get a stand-in\n // whose access throws the original ChainNotSupportedError.\n const apis = {} as Record<string, unknown>;\n const raw = {} as Record<string, PolkadotClient>;\n\n for (const { name, descriptor, client, error } of entries) {\n if (client) {\n apis[name] = client.getTypedApi(descriptor);\n raw[name] = client;\n } else {\n const api = unsupportedChainApi(error as ChainNotSupportedError);\n apis[name] = api;\n raw[name] = api as unknown as PolkadotClient;\n }\n }\n\n return {\n ...apis,\n raw,\n destroy() {\n for (const { genesis } of entries) {\n const key = cacheKey(fingerprint, genesis);\n const entry = clientCache.get(key);\n if (entry) {\n try {\n entry.client.destroy();\n } catch {\n /* already destroyed */\n }\n clientCache.delete(key);\n }\n }\n clientInstances.delete(fingerprint);\n },\n } as ChainClient<TChains>;\n}\n\n/**\n * Destroy all chain client instances and reset internal caches.\n *\n * Tears down every connection created by {@link createChainClient}.\n */\nexport function destroyAll(): void {\n clearClientCache();\n clientInstances.clear();\n}\n\n/**\n * Get the raw `PolkadotClient` for a connected chain by its descriptor.\n *\n * The chain must have been initialized via {@link createChainClient} first.\n * Alternatively, use `client.raw.<name>` on the returned {@link ChainClient}.\n *\n * @throws If the chain has not been connected yet.\n */\nexport function getClient(descriptor: ChainDefinition): PolkadotClient {\n const genesis = descriptor.genesis;\n if (!genesis) throw new Error(\"Descriptor has no genesis hash.\");\n const entry = findEntryByGenesis(genesis);\n if (!entry?.client) {\n throw new Error(\n `Chain not connected (genesis: ${genesis}). Call createChainClient() first to establish connections.`,\n );\n }\n return entry.client;\n}\n\n/**\n * Check if a chain is currently connected.\n *\n * Synchronous — no side effects, no initialization.\n */\nexport function isConnected(descriptor: ChainDefinition): boolean {\n const genesis = descriptor.genesis;\n if (!genesis) return false;\n return findEntryByGenesis(genesis) !== undefined;\n}\n\nif (import.meta.vitest) {\n const { test, expect, beforeEach, vi } = import.meta.vitest;\n\n // Mock the provider + PAPI client factories so initChainClient can run without\n // a real host. Tests that pre-seed clientInstances short-circuit before these.\n vi.mock(\"./providers.js\", () => ({ createProvider: vi.fn() }));\n vi.mock(\"polkadot-api\", async (importOriginal) => ({\n ...(await importOriginal<typeof import(\"polkadot-api\")>()),\n createClient: vi.fn(),\n }));\n\n const fakeDescriptor = { genesis: \"0xtest\" } as ChainDefinition;\n const fakeClient = {\n destroy: () => {},\n getTypedApi: () => ({}),\n } as unknown as PolkadotClient;\n\n function seedCache(genesis: string, client: PolkadotClient, fp = \"test\") {\n getClientCache().set(cacheKey(fp, genesis), {\n client,\n api: new Map(),\n });\n }\n\n beforeEach(() => {\n clearClientCache();\n clientInstances.clear();\n });\n\n // --- isConnected ---\n\n test(\"isConnected returns false for unknown chain\", () => {\n expect(isConnected(fakeDescriptor)).toBe(false);\n });\n\n test(\"isConnected returns true after cache is populated\", () => {\n seedCache(\"0xtest\", fakeClient);\n expect(isConnected(fakeDescriptor)).toBe(true);\n });\n\n test(\"isConnected returns false for descriptor without genesis\", () => {\n expect(isConnected({} as ChainDefinition)).toBe(false);\n });\n\n // --- getClient ---\n\n test(\"getClient returns client from cache\", () => {\n seedCache(\"0xtest\", fakeClient);\n expect(getClient(fakeDescriptor)).toBe(fakeClient);\n });\n\n test(\"getClient throws for unconnected chain\", () => {\n expect(() => getClient(fakeDescriptor)).toThrow(/Chain not connected/);\n });\n\n test(\"getClient throws for descriptor without genesis\", () => {\n expect(() => getClient({} as ChainDefinition)).toThrow(/no genesis hash/);\n });\n\n // --- destroyAll ---\n\n test(\"destroyAll calls client.destroy() and clears caches\", () => {\n let destroyed = false;\n const trackableClient = {\n destroy: () => {\n destroyed = true;\n },\n getTypedApi: () => ({}),\n } as unknown as PolkadotClient;\n seedCache(\"0xtest\", trackableClient);\n clientInstances.set(\"test\", Promise.resolve({} as ChainClient<any>));\n destroyAll();\n expect(destroyed).toBe(true);\n expect(isConnected(fakeDescriptor)).toBe(false);\n expect(clientInstances.size).toBe(0);\n });\n\n // --- createChainClient ---\n\n test(\"createChainClient returns same promise for identical config\", async () => {\n const fakeResult = {} as ChainClient<any>;\n const fp = configFingerprint({ a: fakeDescriptor });\n clientInstances.set(fp, Promise.resolve(fakeResult));\n const result = await createChainClient({\n chains: { a: fakeDescriptor },\n });\n expect(result).toBe(fakeResult);\n });\n\n test(\"createChainClient deduplicates concurrent calls\", async () => {\n const fakeResult = {} as ChainClient<any>;\n const fp = configFingerprint({ x: fakeDescriptor });\n clientInstances.set(fp, Promise.resolve(fakeResult));\n const [a, b] = await Promise.all([\n createChainClient({ chains: { x: fakeDescriptor } }),\n createChainClient({ chains: { x: fakeDescriptor } }),\n ]);\n expect(a).toBe(b);\n });\n\n test(\"createChainClient keeps supported chains usable and defers unsupported-chain errors\", async () => {\n // initChainClient runs for real here — drive the mocked factories.\n const { createProvider } = await import(\"./providers.js\");\n const { createClient } = await import(\"polkadot-api\");\n const fakeTyped = { query: {} };\n vi.mocked(createClient).mockReturnValue({\n getTypedApi: () => fakeTyped,\n destroy: () => {},\n } as unknown as PolkadotClient);\n vi.mocked(createProvider).mockImplementation(async (genesis: string) => {\n if (genesis === \"0xbad\") throw new ChainNotSupportedError(genesis);\n return (() => {}) as never;\n });\n\n const good = { genesis: \"0xgood\" } as ChainDefinition;\n const bad = { genesis: \"0xbad\" } as ChainDefinition;\n const client = (await createChainClient({\n chains: { good, bad },\n })) as any;\n\n // The whole client still resolves; the supported chain is fully usable.\n expect(client.good).toBe(fakeTyped);\n // The unsupported chain surfaces the original error on use — no hang.\n expect(() => client.bad.query).toThrow(ChainNotSupportedError);\n });\n\n test(\"createChainClient returns different results for different configs\", async () => {\n const descA = { genesis: \"0xaaa\" } as ChainDefinition;\n const descB = { genesis: \"0xbbb\" } as ChainDefinition;\n const resultA = {} as ChainClient<any>;\n const resultB = {} as ChainClient<any>;\n clientInstances.set(configFingerprint({ a: descA }), Promise.resolve(resultA));\n clientInstances.set(configFingerprint({ b: descB }), Promise.resolve(resultB));\n const a = await createChainClient({ chains: { a: descA } });\n const b = await createChainClient({ chains: { b: descB } });\n expect(a).not.toBe(b);\n });\n\n // --- configFingerprint ---\n\n test(\"configFingerprint is stable regardless of key order\", () => {\n const d1 = { genesis: \"0x1\" } as ChainDefinition;\n const d2 = { genesis: \"0x2\" } as ChainDefinition;\n expect(configFingerprint({ a: d1, b: d2 })).toBe(configFingerprint({ b: d2, a: d1 }));\n });\n\n // --- findEntryByGenesis ---\n\n test(\"findEntryByGenesis returns undefined for missing genesis\", () => {\n expect(findEntryByGenesis(\"0xnonexistent\")).toBeUndefined();\n });\n\n // --- full lifecycle ---\n\n test(\"full lifecycle: seed, verify connected, destroy, verify disconnected\", () => {\n seedCache(\"0xtest\", fakeClient);\n expect(isConnected(fakeDescriptor)).toBe(true);\n expect(getClient(fakeDescriptor)).toBe(fakeClient);\n destroyAll();\n expect(isConnected(fakeDescriptor)).toBe(false);\n expect(() => getClient(fakeDescriptor)).toThrow(/Chain not connected/);\n });\n\n test(\"two fingerprints cached independently, destroy one leaves other intact\", () => {\n const sharedGenesis = \"0xshared\";\n const clientA = { destroy: () => {} } as PolkadotClient;\n const clientB = { destroy: () => {} } as PolkadotClient;\n const descriptorShared = { genesis: sharedGenesis } as ChainDefinition;\n\n seedCache(sharedGenesis, clientA, \"fpA\");\n seedCache(sharedGenesis, clientB, \"fpB\");\n\n expect(isConnected(descriptorShared)).toBe(true);\n\n // Destroy only fpA's entry\n const cache = getClientCache();\n const keyA = cacheKey(\"fpA\", sharedGenesis);\n cache.get(keyA)?.client.destroy();\n cache.delete(keyA);\n\n // fpB's entry still alive\n expect(isConnected(descriptorShared)).toBe(true);\n expect(getClient(descriptorShared)).toBe(clientB);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChainDefinition } from \"polkadot-api\";\nimport { createChainClient } from \"./clients.js\";\nimport type { ChainClient } from \"./types.js\";\n\n// Type-only imports — erased at compile time, zero bundle cost.\n// These give us per-chain TypedApi types without importing runtime descriptor data.\n// Every environment ships its own descriptor for each chain (asset hub, bulletin,\n// individuality) so that genesis hashes and metadata reflect the live chain\n// instance the consumer connects to.\nimport type { polkadot_asset_hub as PolkadotAssetHubDef } from \"@parity/product-sdk-descriptors/polkadot-asset-hub\";\nimport type { kusama_asset_hub as KusamaAssetHubDef } from \"@parity/product-sdk-descriptors/kusama-asset-hub\";\nimport type { paseo_asset_hub as PaseoAssetHubDef } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\nimport type { paseo_bulletin as PaseoBulletinDef } from \"@parity/product-sdk-descriptors/paseo-bulletin\";\nimport type { paseo_individuality as PaseoIndividualityDef } from \"@parity/product-sdk-descriptors/paseo-individuality\";\n\n/** Known network environment with built-in descriptors. */\nexport type Environment = \"polkadot\" | \"kusama\" | \"paseo\";\n\n/** Environments where all chains (asset hub, bulletin, individuality) are live. */\nconst AVAILABLE_ENVIRONMENTS: Set<Environment> = new Set([\"paseo\"]);\n\n/**\n * Lazy-load descriptors for a specific environment.\n *\n * Every chain (asset hub, bulletin, individuality) ships a per-environment\n * descriptor so that genesis hashes and metadata reflect the live chain\n * instance the consumer connects to. Dynamic imports are code-split per\n * environment, so a consumer using one environment doesn't bundle the others.\n */\nasync function loadDescriptors(env: Environment) {\n const loaders = {\n polkadot: () =>\n Promise.all([\n import(\"@parity/product-sdk-descriptors/polkadot-asset-hub\"),\n // Polkadot bulletin/individuality are not yet live; gated by\n // AVAILABLE_ENVIRONMENTS so this branch is unreachable today.\n Promise.reject(new Error(\"polkadot bulletin descriptor not yet available\")),\n Promise.reject(new Error(\"polkadot individuality descriptor not yet available\")),\n ]),\n kusama: () =>\n Promise.all([\n import(\"@parity/product-sdk-descriptors/kusama-asset-hub\"),\n Promise.reject(new Error(\"kusama bulletin descriptor not yet available\")),\n Promise.reject(new Error(\"kusama individuality descriptor not yet available\")),\n ]),\n paseo: () =>\n Promise.all([\n import(\"@parity/product-sdk-descriptors/paseo-asset-hub\"),\n import(\"@parity/product-sdk-descriptors/paseo-bulletin\"),\n import(\"@parity/product-sdk-descriptors/paseo-individuality\"),\n ]),\n };\n\n const [ahMod, bulletinMod, individualityMod] = await loaders[env]();\n\n const assetHub =\n \"polkadot_asset_hub\" in ahMod\n ? ahMod.polkadot_asset_hub\n : \"kusama_asset_hub\" in ahMod\n ? ahMod.kusama_asset_hub\n : (ahMod as { paseo_asset_hub: typeof PaseoAssetHubDef }).paseo_asset_hub;\n\n const bulletin = (bulletinMod as { paseo_bulletin: typeof PaseoBulletinDef }).paseo_bulletin;\n\n const individuality = (\n individualityMod as { paseo_individuality: typeof PaseoIndividualityDef }\n ).paseo_individuality;\n\n return { assetHub, bulletin, individuality };\n}\n\n/** Per-environment descriptor types for each chain in the preset. */\ntype PresetDescriptors = {\n polkadot: {\n assetHub: typeof PolkadotAssetHubDef;\n // Bulletin/individuality not yet live on polkadot — types reuse paseo\n // shape so the API surface stays consistent; runtime path is gated.\n bulletin: typeof PaseoBulletinDef;\n individuality: typeof PaseoIndividualityDef;\n };\n kusama: {\n assetHub: typeof KusamaAssetHubDef;\n bulletin: typeof PaseoBulletinDef;\n individuality: typeof PaseoIndividualityDef;\n };\n paseo: {\n assetHub: typeof PaseoAssetHubDef;\n bulletin: typeof PaseoBulletinDef;\n individuality: typeof PaseoIndividualityDef;\n };\n};\n\n/** The chain shape returned by {@link getChainAPI} for a given environment. */\nexport type PresetChains<E extends Environment> = PresetDescriptors[E];\n\n/**\n * Get a chain client for a known environment with built-in descriptors.\n *\n * This is the **zero-config** path — no need to import descriptors or specify\n * endpoints. For custom chains or BYOD descriptors, use\n * {@link createChainClient} instead.\n *\n * Returns the same {@link ChainClient} type as `createChainClient`, with\n * `assetHub`, `bulletin`, and `individuality` chain keys.\n *\n * @example\n * ```ts\n * import { getChainAPI } from \"@parity/product-sdk-chain-client\";\n *\n * const client = await getChainAPI(\"paseo\");\n *\n * // Fully typed — no descriptor imports needed\n * const account = await client.assetHub.query.System.Account.getValue(addr);\n * const fee = await client.bulletin.query.TransactionStorage.ByteFee.getValue();\n *\n * // Raw client for advanced use (e.g., a ContractRuntime for pallet-revive contracts)\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);\n *\n * client.destroy();\n * ```\n */\nexport async function getChainAPI<E extends Environment>(\n env: E,\n): Promise<ChainClient<PresetChains<E>>> {\n if (!AVAILABLE_ENVIRONMENTS.has(env)) {\n throw new Error(`Chain API for \"${env}\" is not yet available`);\n }\n\n const descriptors = await loadDescriptors(env);\n\n return createChainClient({\n chains: {\n assetHub: descriptors.assetHub,\n bulletin: descriptors.bulletin,\n individuality: descriptors.individuality,\n },\n }) as Promise<ChainClient<PresetChains<E>>>;\n}\n\nif (import.meta.vitest) {\n const { test, expect, beforeEach } = import.meta.vitest;\n const { destroyAll } = await import(\"./clients.js\");\n\n // Test-only genesis hashes for assertion — not used in production code.\n const GENESIS = {\n polkadot_asset_hub: \"0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f\",\n kusama_asset_hub: \"0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a\",\n paseo_asset_hub: \"0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f\",\n paseo_bulletin: \"0x8cfe6717dc4becfda2e13c488a1e2061ff2dfee96e7d031157f72d36716c0a22\",\n paseo_individuality: \"0xc5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5\",\n } as const;\n\n beforeEach(() => {\n destroyAll();\n });\n\n // --- GENESIS constants ---\n\n test(\"genesis constants are valid hex hashes\", () => {\n for (const hash of Object.values(GENESIS)) {\n expect(hash).toMatch(/^0x[a-f0-9]{64}$/);\n }\n });\n\n // --- getChainAPI ---\n\n test(\"polkadot and kusama throw as not yet available\", async () => {\n await expect(getChainAPI(\"polkadot\")).rejects.toThrow(\"not yet available\");\n await expect(getChainAPI(\"kusama\")).rejects.toThrow(\"not yet available\");\n });\n\n // --- loadDescriptors ---\n\n test(\"loadDescriptors returns descriptors with genesis hashes for paseo\", async () => {\n const descriptors = await loadDescriptors(\"paseo\");\n expect(descriptors).toBeDefined();\n expect(descriptors.assetHub).toBeDefined();\n expect(descriptors.bulletin).toBeDefined();\n expect(descriptors.individuality).toBeDefined();\n expect(descriptors.assetHub.genesis).toBe(GENESIS.paseo_asset_hub);\n expect(descriptors.bulletin.genesis).toBe(GENESIS.paseo_bulletin);\n expect(descriptors.individuality.genesis).toBe(GENESIS.paseo_individuality);\n });\n\n // --- AVAILABLE_ENVIRONMENTS ---\n\n test(\"paseo is currently available\", () => {\n expect(AVAILABLE_ENVIRONMENTS.has(\"paseo\")).toBe(true);\n expect(AVAILABLE_ENVIRONMENTS.has(\"polkadot\")).toBe(false);\n expect(AVAILABLE_ENVIRONMENTS.has(\"kusama\")).toBe(false);\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Genesis hashes of well-known chains.\n *\n * Mirrors `WellKnownChain` from `@novasamatech/host-api-wrapper`. Hand-copied\n * (not re-exported) so chain-client doesn't pick up a direct Novasama\n * runtime dependency; genesis hashes are immutable so drift is impossible.\n */\nexport const WellKnownChain = {\n polkadotRelay: \"0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3\",\n polkadotAssetHub: \"0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f\",\n kusamaRelay: \"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe\",\n kusamaAssetHub: \"0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a\",\n westendRelay: \"0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e\",\n westendAssetHub: \"0x67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9\",\n rococo: \"0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e\",\n} as const;\n\n/** Genesis hash of a well-known chain - the value type of {@link WellKnownChain}. */\nexport type WellKnownChainHash = (typeof WellKnownChain)[keyof typeof WellKnownChain];\n\nif (import.meta.vitest) {\n const { test, expect } = import.meta.vitest;\n\n test(\"WellKnownChain entries are 0x-prefixed 32-byte hex strings\", () => {\n for (const hash of Object.values(WellKnownChain)) {\n expect(hash).toMatch(/^0x[0-9a-f]{64}$/);\n }\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-chain-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Multi-chain Polkadot API client with typed access to Asset Hub, Bulletin, and other chains",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"src"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"polkadot-api": "^2.1.
|
|
22
|
-
"@parity/product-sdk-
|
|
21
|
+
"polkadot-api": "^2.1.5",
|
|
22
|
+
"@parity/product-sdk-host": "0.7.0",
|
|
23
23
|
"@parity/product-sdk-logger": "0.1.1",
|
|
24
|
-
"@parity/product-sdk-
|
|
24
|
+
"@parity/product-sdk-descriptors": "0.5.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"tsup": "^8.
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
28
|
"typescript": "^5.9.3",
|
|
29
29
|
"vitest": "^3.1.4"
|
|
30
30
|
},
|
package/src/clients.ts
CHANGED
|
@@ -2,10 +2,32 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import type { ChainDefinition, PolkadotClient } from "polkadot-api";
|
|
4
4
|
import { createClient } from "polkadot-api";
|
|
5
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
6
|
+
import { ChainNotSupportedError } from "@parity/product-sdk-host";
|
|
5
7
|
import { createProvider } from "./providers.js";
|
|
6
8
|
import { getClientCache, clearClientCache } from "./hmr.js";
|
|
7
9
|
import type { ChainEntry, ChainClientConfig, ChainClient } from "./types.js";
|
|
8
10
|
|
|
11
|
+
const log = createLogger("chain-client");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build a stand-in for a chain the host can't serve. Any property access (e.g.
|
|
15
|
+
* `.query`, `.tx`) or call throws the original {@link ChainNotSupportedError},
|
|
16
|
+
* so touching an unsupported chain surfaces a clear, detectable error rather
|
|
17
|
+
* than hanging — without tanking the supported chains in the same client.
|
|
18
|
+
*/
|
|
19
|
+
function unsupportedChainApi(error: ChainNotSupportedError): never {
|
|
20
|
+
const handler: ProxyHandler<() => void> = {
|
|
21
|
+
get: () => {
|
|
22
|
+
throw error;
|
|
23
|
+
},
|
|
24
|
+
apply: () => {
|
|
25
|
+
throw error;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
return new Proxy((() => {}) as () => void, handler) as never;
|
|
29
|
+
}
|
|
30
|
+
|
|
9
31
|
// Cache keys are scoped by a fingerprint of the config so that two
|
|
10
32
|
// `createChainClient` calls with different chain sets don't collide.
|
|
11
33
|
const cacheKey = (fingerprint: string, genesis: string) => `${fingerprint}:${genesis}`;
|
|
@@ -34,9 +56,6 @@ function configFingerprint(chains: Record<string, ChainDefinition>): string {
|
|
|
34
56
|
* is designed to run exclusively inside a host container (Polkadot Browser / Desktop).
|
|
35
57
|
* Throws if no host provider is available; there is no direct-WebSocket fallback.
|
|
36
58
|
*
|
|
37
|
-
* The `config.rpcs` field is currently unused at runtime (kept for API compatibility
|
|
38
|
-
* and BYOD documentation), since the host owns the chain connection.
|
|
39
|
-
*
|
|
40
59
|
* Results are cached by genesis-hash fingerprint — calling with the same descriptors
|
|
41
60
|
* returns the same instance.
|
|
42
61
|
*
|
|
@@ -48,10 +67,6 @@ function configFingerprint(chains: Record<string, ChainDefinition>): string {
|
|
|
48
67
|
*
|
|
49
68
|
* const client = await createChainClient({
|
|
50
69
|
* chains: { assetHub: paseo_asset_hub, bulletin: paseo_bulletin },
|
|
51
|
-
* rpcs: {
|
|
52
|
-
* assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
|
|
53
|
-
* bulletin: ["wss://paseo-bulletin-next-rpc.polkadot.io"],
|
|
54
|
-
* },
|
|
55
70
|
* });
|
|
56
71
|
*
|
|
57
72
|
* // Fully typed from your descriptors
|
|
@@ -111,29 +126,51 @@ async function initChainClient<const TChains extends Record<string, ChainDefinit
|
|
|
111
126
|
if (!genesis) {
|
|
112
127
|
throw new Error(`Descriptor for chain "${name}" has no genesis hash.`);
|
|
113
128
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Populate HMR cache so getClient() and isConnected() work
|
|
118
|
-
const key = cacheKey(fingerprint, genesis);
|
|
119
|
-
if (!clientCache.has(key)) {
|
|
120
|
-
clientCache.set(key, {
|
|
121
|
-
client,
|
|
122
|
-
api: new Map(),
|
|
123
|
-
} satisfies ChainEntry);
|
|
124
|
-
}
|
|
129
|
+
try {
|
|
130
|
+
const provider = await createProvider(genesis);
|
|
131
|
+
const client = createClient(provider);
|
|
125
132
|
|
|
126
|
-
|
|
133
|
+
// Populate HMR cache so getClient() and isConnected() work
|
|
134
|
+
const key = cacheKey(fingerprint, genesis);
|
|
135
|
+
if (!clientCache.has(key)) {
|
|
136
|
+
clientCache.set(key, {
|
|
137
|
+
client,
|
|
138
|
+
api: new Map(),
|
|
139
|
+
} satisfies ChainEntry);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { name, descriptor, client, genesis, error: undefined };
|
|
143
|
+
} catch (err) {
|
|
144
|
+
// A chain the host can't serve must not tank the whole multi-chain
|
|
145
|
+
// client — the supported chains stay usable, and this one surfaces a
|
|
146
|
+
// clear error on first use instead of hanging. Any other failure
|
|
147
|
+
// (e.g. not inside a container) is a hard error and still rejects.
|
|
148
|
+
if (err instanceof ChainNotSupportedError) {
|
|
149
|
+
log.warn(
|
|
150
|
+
`Chain "${name}" is not supported by the host; its API will throw on use.`,
|
|
151
|
+
{ genesis },
|
|
152
|
+
);
|
|
153
|
+
return { name, descriptor, client: null, genesis, error: err };
|
|
154
|
+
}
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
127
157
|
}),
|
|
128
158
|
);
|
|
129
159
|
|
|
130
|
-
// Build typed APIs and raw client map
|
|
160
|
+
// Build typed APIs and raw client map. Unsupported chains get a stand-in
|
|
161
|
+
// whose access throws the original ChainNotSupportedError.
|
|
131
162
|
const apis = {} as Record<string, unknown>;
|
|
132
163
|
const raw = {} as Record<string, PolkadotClient>;
|
|
133
164
|
|
|
134
|
-
for (const { name, descriptor, client } of entries) {
|
|
135
|
-
|
|
136
|
-
|
|
165
|
+
for (const { name, descriptor, client, error } of entries) {
|
|
166
|
+
if (client) {
|
|
167
|
+
apis[name] = client.getTypedApi(descriptor);
|
|
168
|
+
raw[name] = client;
|
|
169
|
+
} else {
|
|
170
|
+
const api = unsupportedChainApi(error as ChainNotSupportedError);
|
|
171
|
+
apis[name] = api;
|
|
172
|
+
raw[name] = api as unknown as PolkadotClient;
|
|
173
|
+
}
|
|
137
174
|
}
|
|
138
175
|
|
|
139
176
|
return {
|
|
@@ -199,7 +236,15 @@ export function isConnected(descriptor: ChainDefinition): boolean {
|
|
|
199
236
|
}
|
|
200
237
|
|
|
201
238
|
if (import.meta.vitest) {
|
|
202
|
-
const { test, expect, beforeEach } = import.meta.vitest;
|
|
239
|
+
const { test, expect, beforeEach, vi } = import.meta.vitest;
|
|
240
|
+
|
|
241
|
+
// Mock the provider + PAPI client factories so initChainClient can run without
|
|
242
|
+
// a real host. Tests that pre-seed clientInstances short-circuit before these.
|
|
243
|
+
vi.mock("./providers.js", () => ({ createProvider: vi.fn() }));
|
|
244
|
+
vi.mock("polkadot-api", async (importOriginal) => ({
|
|
245
|
+
...(await importOriginal<typeof import("polkadot-api")>()),
|
|
246
|
+
createClient: vi.fn(),
|
|
247
|
+
}));
|
|
203
248
|
|
|
204
249
|
const fakeDescriptor = { genesis: "0xtest" } as ChainDefinition;
|
|
205
250
|
const fakeClient = {
|
|
@@ -275,7 +320,6 @@ if (import.meta.vitest) {
|
|
|
275
320
|
clientInstances.set(fp, Promise.resolve(fakeResult));
|
|
276
321
|
const result = await createChainClient({
|
|
277
322
|
chains: { a: fakeDescriptor },
|
|
278
|
-
rpcs: { a: [] },
|
|
279
323
|
});
|
|
280
324
|
expect(result).toBe(fakeResult);
|
|
281
325
|
});
|
|
@@ -285,12 +329,38 @@ if (import.meta.vitest) {
|
|
|
285
329
|
const fp = configFingerprint({ x: fakeDescriptor });
|
|
286
330
|
clientInstances.set(fp, Promise.resolve(fakeResult));
|
|
287
331
|
const [a, b] = await Promise.all([
|
|
288
|
-
createChainClient({ chains: { x: fakeDescriptor }
|
|
289
|
-
createChainClient({ chains: { x: fakeDescriptor }
|
|
332
|
+
createChainClient({ chains: { x: fakeDescriptor } }),
|
|
333
|
+
createChainClient({ chains: { x: fakeDescriptor } }),
|
|
290
334
|
]);
|
|
291
335
|
expect(a).toBe(b);
|
|
292
336
|
});
|
|
293
337
|
|
|
338
|
+
test("createChainClient keeps supported chains usable and defers unsupported-chain errors", async () => {
|
|
339
|
+
// initChainClient runs for real here — drive the mocked factories.
|
|
340
|
+
const { createProvider } = await import("./providers.js");
|
|
341
|
+
const { createClient } = await import("polkadot-api");
|
|
342
|
+
const fakeTyped = { query: {} };
|
|
343
|
+
vi.mocked(createClient).mockReturnValue({
|
|
344
|
+
getTypedApi: () => fakeTyped,
|
|
345
|
+
destroy: () => {},
|
|
346
|
+
} as unknown as PolkadotClient);
|
|
347
|
+
vi.mocked(createProvider).mockImplementation(async (genesis: string) => {
|
|
348
|
+
if (genesis === "0xbad") throw new ChainNotSupportedError(genesis);
|
|
349
|
+
return (() => {}) as never;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const good = { genesis: "0xgood" } as ChainDefinition;
|
|
353
|
+
const bad = { genesis: "0xbad" } as ChainDefinition;
|
|
354
|
+
const client = (await createChainClient({
|
|
355
|
+
chains: { good, bad },
|
|
356
|
+
})) as any;
|
|
357
|
+
|
|
358
|
+
// The whole client still resolves; the supported chain is fully usable.
|
|
359
|
+
expect(client.good).toBe(fakeTyped);
|
|
360
|
+
// The unsupported chain surfaces the original error on use — no hang.
|
|
361
|
+
expect(() => client.bad.query).toThrow(ChainNotSupportedError);
|
|
362
|
+
});
|
|
363
|
+
|
|
294
364
|
test("createChainClient returns different results for different configs", async () => {
|
|
295
365
|
const descA = { genesis: "0xaaa" } as ChainDefinition;
|
|
296
366
|
const descB = { genesis: "0xbbb" } as ChainDefinition;
|
|
@@ -298,8 +368,8 @@ if (import.meta.vitest) {
|
|
|
298
368
|
const resultB = {} as ChainClient<any>;
|
|
299
369
|
clientInstances.set(configFingerprint({ a: descA }), Promise.resolve(resultA));
|
|
300
370
|
clientInstances.set(configFingerprint({ b: descB }), Promise.resolve(resultB));
|
|
301
|
-
const a = await createChainClient({ chains: { a: descA }
|
|
302
|
-
const b = await createChainClient({ chains: { b: descB }
|
|
371
|
+
const a = await createChainClient({ chains: { a: descA } });
|
|
372
|
+
const b = await createChainClient({ chains: { b: descB } });
|
|
303
373
|
expect(a).not.toBe(b);
|
|
304
374
|
});
|
|
305
375
|
|
package/src/index.ts
CHANGED
|
@@ -27,4 +27,8 @@ export { WellKnownChain } from "./well-known-chain.js";
|
|
|
27
27
|
export type { WellKnownChainHash } from "./well-known-chain.js";
|
|
28
28
|
|
|
29
29
|
// Re-export from host
|
|
30
|
-
export {
|
|
30
|
+
export {
|
|
31
|
+
isInsideContainer,
|
|
32
|
+
isInsideContainerSync,
|
|
33
|
+
ChainNotSupportedError,
|
|
34
|
+
} from "@parity/product-sdk-host";
|
package/src/presets.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Copyright 2026 Parity Technologies (UK) Ltd.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import type { ChainDefinition } from "polkadot-api";
|
|
4
|
-
import { BULLETIN_RPCS } from "@parity/product-sdk-host";
|
|
5
4
|
import { createChainClient } from "./clients.js";
|
|
6
5
|
import type { ChainClient } from "./types.js";
|
|
7
6
|
|
|
@@ -16,36 +15,12 @@ import type { paseo_asset_hub as PaseoAssetHubDef } from "@parity/product-sdk-de
|
|
|
16
15
|
import type { paseo_bulletin as PaseoBulletinDef } from "@parity/product-sdk-descriptors/paseo-bulletin";
|
|
17
16
|
import type { paseo_individuality as PaseoIndividualityDef } from "@parity/product-sdk-descriptors/paseo-individuality";
|
|
18
17
|
|
|
19
|
-
/** Known network environment with built-in descriptors
|
|
18
|
+
/** Known network environment with built-in descriptors. */
|
|
20
19
|
export type Environment = "polkadot" | "kusama" | "paseo";
|
|
21
20
|
|
|
22
21
|
/** Environments where all chains (asset hub, bulletin, individuality) are live. */
|
|
23
22
|
const AVAILABLE_ENVIRONMENTS: Set<Environment> = new Set(["paseo"]);
|
|
24
23
|
|
|
25
|
-
const rpcs = {
|
|
26
|
-
polkadot: {
|
|
27
|
-
assetHub: [
|
|
28
|
-
"wss://polkadot-asset-hub-rpc.polkadot.io",
|
|
29
|
-
"wss://sys.ibp.network/asset-hub-polkadot",
|
|
30
|
-
],
|
|
31
|
-
bulletin: [...BULLETIN_RPCS.polkadot],
|
|
32
|
-
individuality: [] as string[],
|
|
33
|
-
},
|
|
34
|
-
kusama: {
|
|
35
|
-
assetHub: [
|
|
36
|
-
"wss://kusama-asset-hub-rpc.polkadot.io",
|
|
37
|
-
"wss://sys.ibp.network/asset-hub-kusama",
|
|
38
|
-
],
|
|
39
|
-
bulletin: [...BULLETIN_RPCS.kusama],
|
|
40
|
-
individuality: [] as string[],
|
|
41
|
-
},
|
|
42
|
-
paseo: {
|
|
43
|
-
assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
|
|
44
|
-
bulletin: [...BULLETIN_RPCS.paseo],
|
|
45
|
-
individuality: ["wss://paseo-people-next-system-rpc.polkadot.io"],
|
|
46
|
-
},
|
|
47
|
-
} as const;
|
|
48
|
-
|
|
49
24
|
/**
|
|
50
25
|
* Lazy-load descriptors for a specific environment.
|
|
51
26
|
*
|
|
@@ -121,7 +96,7 @@ type PresetDescriptors = {
|
|
|
121
96
|
export type PresetChains<E extends Environment> = PresetDescriptors[E];
|
|
122
97
|
|
|
123
98
|
/**
|
|
124
|
-
* Get a chain client for a known environment with built-in descriptors
|
|
99
|
+
* Get a chain client for a known environment with built-in descriptors.
|
|
125
100
|
*
|
|
126
101
|
* This is the **zero-config** path — no need to import descriptors or specify
|
|
127
102
|
* endpoints. For custom chains or BYOD descriptors, use
|
|
@@ -156,7 +131,6 @@ export async function getChainAPI<E extends Environment>(
|
|
|
156
131
|
}
|
|
157
132
|
|
|
158
133
|
const descriptors = await loadDescriptors(env);
|
|
159
|
-
const envRpcs = rpcs[env];
|
|
160
134
|
|
|
161
135
|
return createChainClient({
|
|
162
136
|
chains: {
|
|
@@ -164,11 +138,6 @@ export async function getChainAPI<E extends Environment>(
|
|
|
164
138
|
bulletin: descriptors.bulletin,
|
|
165
139
|
individuality: descriptors.individuality,
|
|
166
140
|
},
|
|
167
|
-
rpcs: {
|
|
168
|
-
assetHub: [...envRpcs.assetHub],
|
|
169
|
-
bulletin: [...envRpcs.bulletin],
|
|
170
|
-
individuality: [...envRpcs.individuality],
|
|
171
|
-
},
|
|
172
141
|
}) as Promise<ChainClient<PresetChains<E>>>;
|
|
173
142
|
}
|
|
174
143
|
|
|
@@ -197,21 +166,6 @@ if (import.meta.vitest) {
|
|
|
197
166
|
}
|
|
198
167
|
});
|
|
199
168
|
|
|
200
|
-
// --- RPC config ---
|
|
201
|
-
|
|
202
|
-
test("rpcs defined for all environments", () => {
|
|
203
|
-
for (const env of ["polkadot", "kusama", "paseo"] as const) {
|
|
204
|
-
const envRpcs = rpcs[env];
|
|
205
|
-
expect(envRpcs.assetHub.length).toBeGreaterThan(0);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("paseo has RPCs for all chains", () => {
|
|
210
|
-
const envRpcs = rpcs.paseo;
|
|
211
|
-
expect(envRpcs.bulletin.length).toBeGreaterThan(0);
|
|
212
|
-
expect(envRpcs.individuality.length).toBeGreaterThan(0);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
169
|
// --- getChainAPI ---
|
|
216
170
|
|
|
217
171
|
test("polkadot and kusama throw as not yet available", async () => {
|
package/src/providers.ts
CHANGED
|
@@ -29,12 +29,14 @@ if (import.meta.vitest) {
|
|
|
29
29
|
fakeProvider: (() => {}) as unknown as JsonRpcProvider,
|
|
30
30
|
hostProviderCalls: [] as unknown[][],
|
|
31
31
|
hostProviderAvailable: true,
|
|
32
|
+
hostProviderError: null as Error | null,
|
|
32
33
|
}));
|
|
33
34
|
|
|
34
35
|
vi.mock("@parity/product-sdk-host", async (importOriginal) => ({
|
|
35
36
|
...(await importOriginal<typeof import("@parity/product-sdk-host")>()),
|
|
36
37
|
getHostProvider: async (...args: unknown[]) => {
|
|
37
38
|
state.hostProviderCalls.push(args);
|
|
39
|
+
if (state.hostProviderError) throw state.hostProviderError;
|
|
38
40
|
if (!state.hostProviderAvailable) return null;
|
|
39
41
|
return state.fakeProvider;
|
|
40
42
|
},
|
|
@@ -43,6 +45,7 @@ if (import.meta.vitest) {
|
|
|
43
45
|
beforeEach(() => {
|
|
44
46
|
state.hostProviderCalls = [];
|
|
45
47
|
state.hostProviderAvailable = true;
|
|
48
|
+
state.hostProviderError = null;
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
test("returns host provider when available", async () => {
|
|
@@ -56,4 +59,12 @@ if (import.meta.vitest) {
|
|
|
56
59
|
state.hostProviderAvailable = false;
|
|
57
60
|
await expect(createProvider("0xabc")).rejects.toThrow(/Host provider unavailable/);
|
|
58
61
|
});
|
|
62
|
+
|
|
63
|
+
test("propagates ChainNotSupportedError from the host provider", async () => {
|
|
64
|
+
const { ChainNotSupportedError } = await import("@parity/product-sdk-host");
|
|
65
|
+
state.hostProviderError = new ChainNotSupportedError("0xabc");
|
|
66
|
+
// The unsupported-chain error must surface to the caller rather than being
|
|
67
|
+
// collapsed into the generic "unavailable" path or swallowed.
|
|
68
|
+
await expect(createProvider("0xabc")).rejects.toBeInstanceOf(ChainNotSupportedError);
|
|
69
|
+
});
|
|
59
70
|
}
|
package/src/types.ts
CHANGED
|
@@ -8,11 +8,8 @@ export type Environment = "polkadot" | "kusama" | "paseo" | "local" | "westend";
|
|
|
8
8
|
/**
|
|
9
9
|
* Configuration for {@link createChainClient}.
|
|
10
10
|
*
|
|
11
|
-
* Provide named chain descriptors
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Note: The SDK routes all connections through the host provider. The `rpcs`
|
|
15
|
-
* field is currently unused but kept for API compatibility.
|
|
11
|
+
* Provide named chain descriptors. The SDK routes all connections through the
|
|
12
|
+
* host provider, so no RPC endpoints are required.
|
|
16
13
|
*
|
|
17
14
|
* @typeParam TChains - Record mapping user-chosen chain names to PAPI descriptors.
|
|
18
15
|
*
|
|
@@ -24,10 +21,6 @@ export type Environment = "polkadot" | "kusama" | "paseo" | "local" | "westend";
|
|
|
24
21
|
*
|
|
25
22
|
* const client = await createChainClient({
|
|
26
23
|
* chains: { assetHub: paseo_asset_hub, bulletin: paseo_bulletin },
|
|
27
|
-
* rpcs: {
|
|
28
|
-
* assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
|
|
29
|
-
* bulletin: ["wss://paseo-bulletin-next-rpc.polkadot.io"],
|
|
30
|
-
* },
|
|
31
24
|
* });
|
|
32
25
|
* ```
|
|
33
26
|
*/
|
|
@@ -36,8 +29,6 @@ export interface ChainClientConfig<
|
|
|
36
29
|
> {
|
|
37
30
|
/** Named chain descriptors (PAPI `ChainDefinition` objects). */
|
|
38
31
|
chains: TChains;
|
|
39
|
-
/** RPC endpoints per chain name (currently unused - connections route through host). */
|
|
40
|
-
rpcs: { [K in keyof TChains]: readonly string[] };
|
|
41
32
|
}
|
|
42
33
|
|
|
43
34
|
/**
|