@openzeppelin/ui-react 1.2.0 → 2.0.1

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.cjs CHANGED
@@ -32,111 +32,159 @@ let react_jsx_runtime = require("react/jsx-runtime");
32
32
  let _openzeppelin_ui_components = require("@openzeppelin/ui-components");
33
33
 
34
34
  //#region src/version.ts
35
- const VERSION = "1.2.0";
35
+ const VERSION = "2.0.1";
36
36
 
37
37
  //#endregion
38
38
  //#region src/hooks/AdapterContext.tsx
39
39
  /**
40
40
  * AdapterContext.tsx
41
41
  *
42
- * This file defines the React Context used for the adapter singleton pattern.
42
+ * This file defines the React Context used for the runtime singleton pattern.
43
43
  * It provides types and the context definition, but the actual implementation
44
- * is in the AdapterProvider component.
44
+ * is in the `RuntimeProvider` component.
45
45
  *
46
- * The adapter singleton pattern ensures that only one adapter instance exists
46
+ * The runtime singleton pattern ensures that only one runtime instance exists
47
47
  * per network configuration, which is critical for consistent wallet connection
48
48
  * state across the application.
49
49
  */
50
50
  /**
51
- * The React Context that provides adapter registry access throughout the app
52
- * Components can access this through the useAdapterContext hook
51
+ * The React Context that provides runtime registry access throughout the app.
52
+ * Components can access this through the `useRuntimeContext` hook.
53
53
  */
54
- const AdapterContext = (0, react.createContext)(null);
54
+ const RuntimeContext = (0, react.createContext)(null);
55
55
 
56
56
  //#endregion
57
57
  //#region src/hooks/AdapterProvider.tsx
58
58
  /**
59
- * AdapterProvider.tsx
59
+ * RuntimeProvider.tsx
60
60
  *
61
- * This file implements the Adapter Provider component which manages a registry of
62
- * adapter instances. It's a key part of the adapter singleton pattern which ensures
63
- * that only one adapter instance exists per network configuration.
61
+ * This file implements the Runtime Provider component which manages a registry of
62
+ * runtime instances. It's a key part of the runtime singleton pattern which ensures
63
+ * that only one runtime instance exists per network configuration.
64
64
  *
65
- * The adapter registry is shared across the application via React Context, allowing
66
- * components to access the same adapter instances and maintain consistent wallet
65
+ * The runtime registry is shared across the application via React Context, allowing
66
+ * components to access the same runtime instances and maintain consistent wallet
67
67
  * connection state.
68
68
  *
69
69
  * IMPORTANT: This implementation needs special care to avoid React state update errors
70
70
  * during component rendering. Direct state updates during render are not allowed, which
71
- * is why adapter loading is controlled carefully.
71
+ * is why runtime loading is controlled carefully.
72
72
  */
73
73
  /**
74
- * Provider component that manages adapter instances centrally
75
- * to avoid creating multiple instances of the same adapter.
74
+ * Provider component that manages runtime instances centrally
75
+ * to avoid creating multiple instances of the same runtime.
76
76
  *
77
77
  * This component:
78
- * 1. Maintains a registry of adapter instances by network ID
79
- * 2. Tracks loading states for adapters being initialized
80
- * 3. Provides a function to get or load adapters for specific networks
81
- * 4. Ensures adapter instances are reused when possible
78
+ * 1. Maintains a registry of runtime instances by network ID
79
+ * 2. Tracks loading states for runtimes being initialized
80
+ * 3. Provides a function to get or load runtimes for specific networks
81
+ * 4. Ensures runtime instances are reused when possible
82
82
  */
83
- function AdapterProvider({ children, resolveAdapter }) {
84
- const [adapterRegistry, setAdapterRegistry] = (0, react.useState)({});
83
+ function RuntimeProvider({ children, resolveRuntime }) {
84
+ const [runtimeRegistry, setRuntimeRegistry] = (0, react.useState)({});
85
85
  const [loadingNetworks, setLoadingNetworks] = (0, react.useState)(/* @__PURE__ */ new Set());
86
+ const runtimeRegistryRef = (0, react.useRef)(runtimeRegistry);
87
+ const failedNetworksRef = (0, react.useRef)(/* @__PURE__ */ new Set());
86
88
  (0, react.useEffect)(() => {
87
- const adapterCount = Object.keys(adapterRegistry).length;
88
- if (adapterCount > 0) _openzeppelin_ui_utils.logger.info("AdapterProvider", `Registry contains ${adapterCount} adapters:`, {
89
- networkIds: Object.keys(adapterRegistry),
89
+ runtimeRegistryRef.current = runtimeRegistry;
90
+ }, [runtimeRegistry]);
91
+ (0, react.useEffect)(() => {
92
+ failedNetworksRef.current = /* @__PURE__ */ new Set();
93
+ }, [resolveRuntime]);
94
+ (0, react.useEffect)(() => {
95
+ return () => {
96
+ Object.values(runtimeRegistryRef.current).forEach((runtime) => {
97
+ runtime.dispose();
98
+ });
99
+ };
100
+ }, []);
101
+ (0, react.useEffect)(() => {
102
+ const runtimeCount = Object.keys(runtimeRegistry).length;
103
+ if (runtimeCount > 0) _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Registry contains ${runtimeCount} runtimes:`, {
104
+ networkIds: Object.keys(runtimeRegistry),
90
105
  loadingCount: loadingNetworks.size,
91
106
  loadingNetworkIds: Array.from(loadingNetworks)
92
107
  });
93
- }, [adapterRegistry, loadingNetworks]);
108
+ }, [runtimeRegistry, loadingNetworks]);
109
+ /**
110
+ * Evicts a runtime from the registry by network ID and disposes it.
111
+ * This is the safe counterpart to `getRuntimeForNetwork`: callers should only
112
+ * release a runtime after they have already promoted its replacement.
113
+ *
114
+ * Disposal is deferred to the next macrotask so React's commit phase
115
+ * (including development-mode prop diffing) can finish reading the old
116
+ * runtime's properties without hitting the RuntimeDisposedError proxy trap.
117
+ */
118
+ const releaseRuntime = (0, react.useCallback)((networkId) => {
119
+ const runtime = runtimeRegistryRef.current[networkId];
120
+ if (!runtime) return;
121
+ _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Releasing runtime for network ${networkId}`);
122
+ setRuntimeRegistry((prev) => {
123
+ const next = { ...prev };
124
+ delete next[networkId];
125
+ return next;
126
+ });
127
+ setTimeout(() => {
128
+ try {
129
+ runtime.dispose();
130
+ } catch (error) {
131
+ _openzeppelin_ui_utils.logger.error("RuntimeProvider", `Error disposing runtime for network ${networkId}:`, error);
132
+ }
133
+ }, 0);
134
+ }, []);
94
135
  /**
95
- * Function to get or create an adapter for a network
136
+ * Function to get or create a runtime for a network
96
137
  *
97
- * IMPORTANT: The actual adapter loading is handled in the useConfiguredAdapterSingleton hook
98
- * to avoid React state updates during render, which would cause errors.
138
+ * IMPORTANT: Runtime loading is coordinated carefully to avoid React state updates
139
+ * during render, which would cause errors.
99
140
  *
100
141
  * This function:
101
- * 1. Returns existing adapters immediately if available
102
- * 2. Reports loading state for adapters being initialized
103
- * 3. Initiates adapter loading when needed
142
+ * 1. Returns existing runtimes immediately if available
143
+ * 2. Reports loading state for runtimes being initialized
144
+ * 3. Initiates runtime loading when needed
104
145
  */
105
- const getAdapterForNetwork = (0, react.useCallback)((networkConfig) => {
146
+ const getRuntimeForNetwork = (0, react.useCallback)((networkConfig) => {
106
147
  if (!networkConfig) return {
107
- adapter: null,
148
+ runtime: null,
108
149
  isLoading: false
109
150
  };
110
151
  const networkId = networkConfig.id;
111
- _openzeppelin_ui_utils.logger.debug("AdapterProvider", `Adapter requested for network ${networkId}`);
112
- if (adapterRegistry[networkId]) {
113
- _openzeppelin_ui_utils.logger.debug("AdapterProvider", `Using existing adapter for network ${networkId}`);
152
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Runtime requested for network ${networkId}`);
153
+ if (runtimeRegistry[networkId]) {
154
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Using existing runtime for network ${networkId}`);
114
155
  return {
115
- adapter: adapterRegistry[networkId],
156
+ runtime: runtimeRegistry[networkId],
116
157
  isLoading: false
117
158
  };
118
159
  }
119
160
  if (loadingNetworks.has(networkId)) {
120
- _openzeppelin_ui_utils.logger.debug("AdapterProvider", `Adapter for network ${networkId} is currently loading`);
161
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Runtime for network ${networkId} is currently loading`);
121
162
  return {
122
- adapter: null,
163
+ runtime: null,
123
164
  isLoading: true
124
165
  };
125
166
  }
167
+ if (failedNetworksRef.current.has(networkId)) {
168
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Runtime for network ${networkId} previously failed; skipping retry`);
169
+ return {
170
+ runtime: null,
171
+ isLoading: false
172
+ };
173
+ }
126
174
  setLoadingNetworks((prev) => {
127
175
  const newSet = new Set(prev);
128
176
  newSet.add(networkId);
129
177
  return newSet;
130
178
  });
131
- _openzeppelin_ui_utils.logger.info("AdapterProvider", `Starting adapter initialization for network ${networkId} (${networkConfig.name})`);
132
- resolveAdapter(networkConfig).then((adapter) => {
133
- _openzeppelin_ui_utils.logger.info("AdapterProvider", `Adapter for network ${networkId} loaded successfully`, {
134
- type: adapter.constructor.name,
135
- objectId: Object.prototype.toString.call(adapter)
179
+ _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Starting runtime initialization for network ${networkId} (${networkConfig.name})`);
180
+ resolveRuntime(networkConfig).then((runtime) => {
181
+ _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Runtime for network ${networkId} loaded successfully`, {
182
+ type: runtime.constructor.name,
183
+ objectId: Object.prototype.toString.call(runtime)
136
184
  });
137
- setAdapterRegistry((prev) => ({
185
+ setRuntimeRegistry((prev) => ({
138
186
  ...prev,
139
- [networkId]: adapter
187
+ [networkId]: runtime
140
188
  }));
141
189
  setLoadingNetworks((prev) => {
142
190
  const newSet = new Set(prev);
@@ -144,7 +192,8 @@ function AdapterProvider({ children, resolveAdapter }) {
144
192
  return newSet;
145
193
  });
146
194
  }).catch((error) => {
147
- _openzeppelin_ui_utils.logger.error("AdapterProvider", `Error loading adapter for network ${networkId}:`, error);
195
+ _openzeppelin_ui_utils.logger.error("RuntimeProvider", `Error loading runtime for network ${networkId}:`, error);
196
+ failedNetworksRef.current.add(networkId);
148
197
  setLoadingNetworks((prev) => {
149
198
  const newSet = new Set(prev);
150
199
  newSet.delete(networkId);
@@ -152,16 +201,19 @@ function AdapterProvider({ children, resolveAdapter }) {
152
201
  });
153
202
  });
154
203
  return {
155
- adapter: null,
204
+ runtime: null,
156
205
  isLoading: true
157
206
  };
158
207
  }, [
159
- adapterRegistry,
208
+ runtimeRegistry,
160
209
  loadingNetworks,
161
- resolveAdapter
210
+ resolveRuntime
162
211
  ]);
163
- const contextValue = (0, react.useMemo)(() => ({ getAdapterForNetwork }), [getAdapterForNetwork]);
164
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdapterContext.Provider, {
212
+ const contextValue = (0, react.useMemo)(() => ({
213
+ getRuntimeForNetwork,
214
+ releaseRuntime
215
+ }), [getRuntimeForNetwork, releaseRuntime]);
216
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeContext.Provider, {
165
217
  value: contextValue,
166
218
  children
167
219
  });
@@ -179,7 +231,7 @@ function AdapterProvider({ children, resolveAdapter }) {
179
231
  * consuming package's bundle. This creates MULTIPLE instances of this module:
180
232
  *
181
233
  * 1. The app's direct import → packages/react/dist/index.js
182
- * 2. The adapter's inlined copy → .vite/deps/@openzeppelin_ui-builder-adapter-evm.js
234
+ * 2. The adapter package's inlined copy → .vite/deps/@openzeppelin_adapter_evm.js
183
235
  *
184
236
  * Since React contexts use referential identity, these two module instances have
185
237
  * DIFFERENT context objects. When the adapter's components call useWalletState(),
@@ -241,54 +293,88 @@ function useWalletState() {
241
293
  /**
242
294
  * useAdapterContext.ts
243
295
  *
244
- * This file provides a hook to access the AdapterContext throughout the application.
245
- * It's a critical part of the adapter singleton pattern, allowing components to
246
- * access the centralized adapter registry.
296
+ * This file provides a hook to access the runtime context throughout the application.
297
+ * It's a critical part of the runtime singleton pattern, allowing components to
298
+ * access the centralized runtime registry.
247
299
  *
248
- * The adapter singleton pattern ensures:
249
- * - Only one adapter instance exists per network
300
+ * The runtime singleton pattern ensures:
301
+ * - Only one runtime instance exists per network
250
302
  * - Wallet connection state is consistent across the app
251
- * - Better performance by eliminating redundant adapter initialization
303
+ * - Better performance by eliminating redundant runtime initialization
252
304
  */
253
305
  /**
254
- * Hook to access the adapter context
306
+ * Hook to access the runtime context
255
307
  *
256
- * This hook provides access to the getAdapterForNetwork function which
257
- * retrieves or creates adapter instances from the singleton registry.
308
+ * This hook provides access to the `getRuntimeForNetwork` function which
309
+ * retrieves or creates runtime instances from the singleton registry.
258
310
  *
259
- * Components should typically use useConfiguredAdapterSingleton instead
311
+ * Components should typically use the higher-level wallet/runtime hooks instead
260
312
  * of this hook directly, as it handles React state update timing properly.
261
313
  *
262
- * @throws Error if used outside of an AdapterProvider context
263
- * @returns The adapter context value
314
+ * @throws Error if used outside of a RuntimeProvider context
315
+ * @returns The runtime context value
264
316
  */
265
- function useAdapterContext() {
266
- const context = (0, react.useContext)(AdapterContext);
267
- if (!context) throw new Error("useAdapterContext must be used within an AdapterProvider");
317
+ function useRuntimeContext() {
318
+ const context = (0, react.useContext)(RuntimeContext);
319
+ if (!context) throw new Error("useRuntimeContext must be used within a RuntimeProvider");
268
320
  return context;
269
321
  }
270
322
 
323
+ //#endregion
324
+ //#region src/hooks/walletSessionRegistry.ts
325
+ /**
326
+ * Returns the cached wallet session for an ecosystem, if present.
327
+ *
328
+ * @param registry - Current internal wallet session registry.
329
+ * @param ecosystem - Ecosystem key to resolve.
330
+ * @returns The cached session entry or `null` when the ecosystem has not been configured yet.
331
+ */
332
+ function getWalletSession(registry, ecosystem) {
333
+ if (!ecosystem) return null;
334
+ return registry[ecosystem] ?? null;
335
+ }
336
+ /**
337
+ * Inserts or replaces the cached wallet session for a single ecosystem.
338
+ *
339
+ * @param registry - Current internal wallet session registry.
340
+ * @param session - Session entry to cache.
341
+ * @returns A new registry object containing the upserted ecosystem session.
342
+ */
343
+ function upsertWalletSession(registry, session) {
344
+ return {
345
+ ...registry,
346
+ [session.ecosystem]: session
347
+ };
348
+ }
349
+
271
350
  //#endregion
272
351
  //#region src/hooks/WalletStateProvider.tsx
273
352
  /**
274
- * Configures the adapter's UI kit and returns the UI provider component and hooks.
353
+ * Configures the runtime's UI kit capability and returns the ecosystem session artifacts.
275
354
  */
276
- async function configureAdapterUiKit(adapter, loadConfigModule, programmaticOverrides = {}) {
355
+ async function configureRuntimeUiKit(runtime, loadConfigModule, programmaticOverrides = {}) {
356
+ const uiKit = runtime.uiKit;
357
+ if (!uiKit) return {
358
+ providerComponent: null,
359
+ hooks: null
360
+ };
277
361
  try {
278
- if (typeof adapter.configureUiKit === "function") {
279
- _openzeppelin_ui_utils.logger.info("[WSP configureAdapterUiKit] Calling configureUiKit for adapter:", adapter?.networkConfig?.id);
280
- await adapter.configureUiKit(programmaticOverrides, { loadUiKitNativeConfig: loadConfigModule });
281
- _openzeppelin_ui_utils.logger.info("[WSP configureAdapterUiKit] configureUiKit completed for adapter:", adapter?.networkConfig?.id);
362
+ const hasUiKitOverride = Object.keys(programmaticOverrides).length > 0;
363
+ if (typeof uiKit.configureUiKit === "function") {
364
+ const nextUiKitConfig = { ...programmaticOverrides };
365
+ _openzeppelin_ui_utils.logger.info("[WSP configureRuntimeUiKit]", hasUiKitOverride ? `Applying explicit UI kit overrides for runtime: ${runtime.networkConfig.id}` : `Initializing runtime UI kit from adapter/app defaults for runtime: ${runtime.networkConfig.id}`);
366
+ await uiKit.configureUiKit(nextUiKitConfig, { loadUiKitNativeConfig: loadConfigModule });
367
+ _openzeppelin_ui_utils.logger.info("[WSP configureRuntimeUiKit] configureUiKit completed for runtime:", runtime.networkConfig.id);
282
368
  }
283
- const providerComponent = adapter.getEcosystemReactUiContextProvider?.() || null;
284
- const hooks = adapter.getEcosystemReactHooks?.() || null;
285
- _openzeppelin_ui_utils.logger.info("[WSP configureAdapterUiKit]", "UI provider and hooks retrieved successfully.");
369
+ const providerComponent = uiKit.getEcosystemReactUiContextProvider?.() || null;
370
+ const hooks = uiKit.getEcosystemReactHooks?.() || null;
371
+ _openzeppelin_ui_utils.logger.info("[WSP configureRuntimeUiKit]", "UI provider and hooks retrieved successfully.");
286
372
  return {
287
373
  providerComponent,
288
374
  hooks
289
375
  };
290
376
  } catch (error) {
291
- _openzeppelin_ui_utils.logger.error("[WSP configureAdapterUiKit]", "Error during adapter UI setup:", error);
377
+ _openzeppelin_ui_utils.logger.error("[WSP configureRuntimeUiKit]", "Error during runtime UI setup:", error);
292
378
  throw error;
293
379
  }
294
380
  }
@@ -298,26 +384,28 @@ async function configureAdapterUiKit(adapter, loadConfigModule, programmaticOver
298
384
  * It is responsible for:
299
385
  * 1. Managing the globally selected active network ID (`activeNetworkId`).
300
386
  * 2. Deriving the full `NetworkConfig` object (`activeNetworkConfig`) for the active network.
301
- * 3. Fetching and providing the corresponding `ContractAdapter` instance (`activeAdapter`) for the active network,
302
- * leveraging the `AdapterProvider` to ensure adapter singletons.
303
- * 4. Storing and providing the `EcosystemSpecificReactHooks` (`walletFacadeHooks`) from the active adapter.
304
- * 5. Rendering the adapter-specific UI context provider (e.g., WagmiProvider for EVM) around its children,
305
- * which is essential for the facade hooks to function correctly.
387
+ * 3. Fetching and providing the corresponding `EcosystemRuntime` (`activeRuntime`) for the active network,
388
+ * leveraging the `RuntimeProvider` to ensure runtime singletons.
389
+ * 4. Caching ecosystem-scoped wallet session artifacts (provider roots and facade hooks)
390
+ * independently from the network-scoped runtime.
391
+ * 5. Rendering the active ecosystem wallet provider (e.g., WagmiProvider for EVM) around its
392
+ * children, which is essential for the facade hooks to function correctly.
306
393
  * 6. Providing a function (`setActiveNetworkId`) to change the globally active network.
307
394
  *
308
395
  * Consumers use the `useWalletState()` hook to access this global state.
309
- * It should be placed high in the component tree, inside an `<AdapterProvider>`.
396
+ * It should be placed high in the component tree, inside a `<RuntimeProvider>`.
310
397
  */
311
398
  function WalletStateProvider({ children, initialNetworkId = null, getNetworkConfigById, loadConfigModule }) {
312
399
  const [currentGlobalNetworkId, setCurrentGlobalNetworkIdState] = (0, react.useState)(initialNetworkId);
313
400
  const [currentGlobalNetworkConfig, setCurrentGlobalNetworkConfig] = (0, react.useState)(null);
314
- const [globalActiveAdapter, setGlobalActiveAdapter] = (0, react.useState)(null);
315
- const [isGlobalAdapterLoading, setIsGlobalAdapterLoading] = (0, react.useState)(false);
316
- const [walletFacadeHooks, setWalletFacadeHooks] = (0, react.useState)(null);
317
- const [AdapterUiContextProviderToRender, setAdapterUiContextProviderToRender] = (0, react.useState)(null);
401
+ const [globalActiveRuntime, setGlobalActiveRuntime] = (0, react.useState)(null);
402
+ const [isGlobalRuntimeLoading, setIsGlobalRuntimeLoading] = (0, react.useState)(false);
403
+ const [walletSessionRegistry, setWalletSessionRegistry] = (0, react.useState)({});
404
+ const [activeWalletSessionEcosystem, setActiveWalletSessionEcosystem] = (0, react.useState)(null);
318
405
  const [uiKitConfigVersion, setUiKitConfigVersion] = (0, react.useState)(0);
319
406
  const [programmaticUiKitConfig, setProgrammaticUiKitConfig] = (0, react.useState)(void 0);
320
- const { getAdapterForNetwork } = useAdapterContext();
407
+ const { getRuntimeForNetwork, releaseRuntime } = useRuntimeContext();
408
+ const promotedNetworkIdRef = (0, react.useRef)(null);
321
409
  (0, react.useEffect)(() => {
322
410
  const abortController = new AbortController();
323
411
  async function fetchNetworkConfig() {
@@ -340,98 +428,115 @@ function WalletStateProvider({ children, initialNetworkId = null, getNetworkConf
340
428
  }, [currentGlobalNetworkId, getNetworkConfigById]);
341
429
  (0, react.useEffect)(() => {
342
430
  const abortController = new AbortController();
343
- async function loadAdapterAndConfigureUi() {
431
+ async function loadRuntimeAndConfigureUi() {
344
432
  if (!currentGlobalNetworkConfig) {
345
433
  if (!abortController.signal.aborted) {
346
- setGlobalActiveAdapter(null);
347
- setIsGlobalAdapterLoading(false);
348
- setAdapterUiContextProviderToRender(null);
349
- setWalletFacadeHooks(null);
434
+ const prevNetworkId = promotedNetworkIdRef.current;
435
+ setGlobalActiveRuntime(null);
436
+ setIsGlobalRuntimeLoading(false);
437
+ setActiveWalletSessionEcosystem(null);
438
+ if (prevNetworkId) {
439
+ releaseRuntime(prevNetworkId);
440
+ promotedNetworkIdRef.current = null;
441
+ }
350
442
  }
351
443
  return;
352
444
  }
353
- const { adapter: newAdapter, isLoading: newIsLoading } = getAdapterForNetwork(currentGlobalNetworkConfig);
445
+ const { runtime: newRuntime, isLoading: newIsLoading } = getRuntimeForNetwork(currentGlobalNetworkConfig);
354
446
  if (abortController.signal.aborted) return;
355
- setIsGlobalAdapterLoading(newIsLoading);
356
- if (newAdapter && !newIsLoading) try {
357
- const { providerComponent, hooks } = await configureAdapterUiKit(newAdapter, loadConfigModule, programmaticUiKitConfig);
447
+ setIsGlobalRuntimeLoading(newIsLoading);
448
+ if (newRuntime && !newIsLoading) try {
449
+ const { providerComponent, hooks } = await configureRuntimeUiKit(newRuntime, loadConfigModule, programmaticUiKitConfig);
358
450
  if (!abortController.signal.aborted) {
359
- setAdapterUiContextProviderToRender(() => providerComponent);
360
- setWalletFacadeHooks(hooks);
361
- setGlobalActiveAdapter(newAdapter);
451
+ const ecosystem = newRuntime.networkConfig.ecosystem;
452
+ const prevNetworkId = promotedNetworkIdRef.current;
453
+ const nextNetworkId = newRuntime.networkConfig.id;
454
+ setWalletSessionRegistry((prevRegistry) => upsertWalletSession(prevRegistry, {
455
+ ecosystem,
456
+ lastConfiguredNetworkId: nextNetworkId,
457
+ providerComponent,
458
+ hooks
459
+ }));
460
+ setGlobalActiveRuntime(newRuntime);
461
+ setActiveWalletSessionEcosystem(ecosystem);
462
+ promotedNetworkIdRef.current = nextNetworkId;
463
+ if (prevNetworkId && prevNetworkId !== nextNetworkId) releaseRuntime(prevNetworkId);
362
464
  }
363
465
  } catch (error) {
364
- if (!abortController.signal.aborted) {
365
- _openzeppelin_ui_utils.logger.error("[WSP loadAdapterAndConfigureUi]", "Error during adapter UI setup:", error);
366
- setAdapterUiContextProviderToRender(null);
367
- setWalletFacadeHooks(null);
368
- }
466
+ if (!abortController.signal.aborted) _openzeppelin_ui_utils.logger.error("[WSP loadRuntimeAndConfigureUi]", "Error during runtime UI setup:", error);
369
467
  }
370
- else if (!newAdapter && !newIsLoading) {
468
+ else if (!newRuntime && !newIsLoading) {
371
469
  if (!abortController.signal.aborted) {
372
- setAdapterUiContextProviderToRender(null);
373
- setWalletFacadeHooks(null);
374
- setGlobalActiveAdapter(null);
470
+ const prevNetworkId = promotedNetworkIdRef.current;
471
+ setGlobalActiveRuntime(null);
472
+ setActiveWalletSessionEcosystem(null);
473
+ if (prevNetworkId) {
474
+ releaseRuntime(prevNetworkId);
475
+ promotedNetworkIdRef.current = null;
476
+ }
375
477
  }
376
478
  }
377
479
  }
378
- loadAdapterAndConfigureUi();
480
+ loadRuntimeAndConfigureUi();
379
481
  return () => abortController.abort();
380
482
  }, [
381
483
  currentGlobalNetworkConfig,
382
- getAdapterForNetwork,
484
+ getRuntimeForNetwork,
485
+ releaseRuntime,
383
486
  loadConfigModule,
384
487
  uiKitConfigVersion,
385
488
  programmaticUiKitConfig
386
489
  ]);
387
490
  /**
388
491
  * Callback to set the globally active network ID.
389
- * Also clears dependent states (config, adapter, hooks) if the network ID is cleared.
492
+ * Also clears dependent states if the network ID is cleared.
390
493
  */
391
494
  const setActiveNetworkIdCallback = (0, react.useCallback)((networkId) => {
392
495
  _openzeppelin_ui_utils.logger.info("WalletStateProvider", `Setting global network ID to: ${networkId}`);
393
496
  setCurrentGlobalNetworkIdState(networkId);
394
497
  if (!networkId) {
395
498
  setCurrentGlobalNetworkConfig(null);
396
- setGlobalActiveAdapter(null);
397
- setIsGlobalAdapterLoading(false);
398
- setWalletFacadeHooks(null);
499
+ setGlobalActiveRuntime(null);
500
+ setIsGlobalRuntimeLoading(false);
501
+ setActiveWalletSessionEcosystem(null);
399
502
  }
400
503
  }, []);
401
504
  /**
402
- * Callback to explicitly trigger a re-configuration of the active adapter's UI kit.
505
+ * Callback to explicitly trigger a re-configuration of the active runtime's UI kit.
403
506
  * This is useful when a UI kit setting changes (e.g., via a wizard) without a network change.
404
507
  */
405
- const reconfigureActiveAdapterUiKit = (0, react.useCallback)((uiKitConfig) => {
508
+ const reconfigureActiveUiKit = (0, react.useCallback)((uiKitConfig) => {
406
509
  _openzeppelin_ui_utils.logger.info("WalletStateProvider", "Explicitly triggering UI kit re-configuration by bumping version.", uiKitConfig);
407
510
  setProgrammaticUiKitConfig(uiKitConfig);
408
511
  setUiKitConfigVersion((v) => v + 1);
409
512
  }, [setProgrammaticUiKitConfig, setUiKitConfigVersion]);
513
+ const activeWalletSession = (0, react.useMemo)(() => getWalletSession(walletSessionRegistry, activeWalletSessionEcosystem), [walletSessionRegistry, activeWalletSessionEcosystem]);
514
+ const walletFacadeHooks = activeWalletSession?.hooks ?? null;
410
515
  const contextValue = (0, react.useMemo)(() => ({
411
516
  activeNetworkId: currentGlobalNetworkId,
412
517
  setActiveNetworkId: setActiveNetworkIdCallback,
413
518
  activeNetworkConfig: currentGlobalNetworkConfig,
414
- activeAdapter: globalActiveAdapter,
415
- isAdapterLoading: isGlobalAdapterLoading,
519
+ activeRuntime: globalActiveRuntime,
520
+ isRuntimeLoading: isGlobalRuntimeLoading,
416
521
  walletFacadeHooks,
417
- reconfigureActiveAdapterUiKit
522
+ reconfigureActiveUiKit
418
523
  }), [
419
524
  currentGlobalNetworkId,
420
525
  setActiveNetworkIdCallback,
421
526
  currentGlobalNetworkConfig,
422
- globalActiveAdapter,
423
- isGlobalAdapterLoading,
527
+ globalActiveRuntime,
528
+ isGlobalRuntimeLoading,
424
529
  walletFacadeHooks,
425
- reconfigureActiveAdapterUiKit
530
+ reconfigureActiveUiKit
426
531
  ]);
427
- const ActualProviderToRender = AdapterUiContextProviderToRender;
532
+ const ActualProviderToRender = activeWalletSession?.providerComponent ?? null;
428
533
  let childrenToRender;
429
534
  if (ActualProviderToRender) {
430
- const key = `${globalActiveAdapter?.networkConfig?.ecosystem || "unknown"}-${globalActiveAdapter?.networkConfig?.id || "unknown"}`;
431
- _openzeppelin_ui_utils.logger.info("[WSP RENDER]", "Rendering adapter-provided UI context provider:", ActualProviderToRender.displayName || ActualProviderToRender.name || "UnknownComponent", "with key:", key);
535
+ const key = activeWalletSessionEcosystem || "unknown";
536
+ _openzeppelin_ui_utils.logger.debug("[WSP RENDER]", "Rendering runtime-provided UI context provider:", ActualProviderToRender.displayName || ActualProviderToRender.name || "UnknownComponent", "with key:", key);
432
537
  childrenToRender = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActualProviderToRender, { children }, key);
433
538
  } else {
434
- _openzeppelin_ui_utils.logger.info("[WSP RENDER]", "No adapter UI context provider to render. Rendering direct children.");
539
+ _openzeppelin_ui_utils.logger.debug("[WSP RENDER]", "No runtime UI context provider to render. Rendering direct children.");
435
540
  childrenToRender = children;
436
541
  }
437
542
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WalletStateContext.Provider, {
@@ -575,14 +680,14 @@ const useAnalytics = () => {
575
680
  //#endregion
576
681
  //#region src/hooks/useWalletComponents.ts
577
682
  /**
578
- * Hook that provides direct access to wallet UI components from the active adapter.
683
+ * Hook that provides direct access to wallet UI components from the active runtime.
579
684
  *
580
685
  * Use this hook when you need full control over the layout and composition of
581
686
  * wallet components. For standard layouts, prefer using `WalletConnectionUI`
582
687
  * with its props forwarding capabilities.
583
688
  *
584
- * @returns The wallet components object, or null if no adapter is active or
585
- * the adapter doesn't provide wallet components.
689
+ * @returns The wallet components object, or null if no runtime is active or
690
+ * the runtime doesn't provide wallet components.
586
691
  *
587
692
  * @example
588
693
  * ```tsx
@@ -617,10 +722,11 @@ const useAnalytics = () => {
617
722
  * ```
618
723
  */
619
724
  function useWalletComponents() {
620
- const { activeAdapter } = useWalletState();
621
- if (!activeAdapter || typeof activeAdapter.getEcosystemWalletComponents !== "function") return null;
725
+ const { activeNetworkConfig, activeRuntime, isRuntimeLoading } = useWalletState();
726
+ const activeUiKit = activeRuntime?.uiKit;
727
+ if (!!(isRuntimeLoading && activeRuntime?.networkConfig?.ecosystem && activeNetworkConfig?.ecosystem && activeRuntime.networkConfig.ecosystem !== activeNetworkConfig.ecosystem) || !activeUiKit || typeof activeUiKit.getEcosystemWalletComponents !== "function") return null;
622
728
  try {
623
- return activeAdapter.getEcosystemWalletComponents() ?? null;
729
+ return activeUiKit.getEcosystemWalletComponents() ?? null;
624
730
  } catch {
625
731
  return null;
626
732
  }
@@ -630,6 +736,10 @@ function useWalletComponents() {
630
736
  //#region src/hooks/useDerivedAccountStatus.ts
631
737
  const defaultAccountStatus = {
632
738
  isConnected: false,
739
+ isConnecting: false,
740
+ isDisconnected: true,
741
+ isReconnecting: false,
742
+ status: "disconnected",
633
743
  address: void 0,
634
744
  chainId: void 0
635
745
  };
@@ -644,6 +754,10 @@ function useDerivedAccountStatus() {
644
754
  const accountHookOutput = walletFacadeHooks?.useAccount ? walletFacadeHooks.useAccount() : void 0;
645
755
  if ((0, _openzeppelin_ui_utils.isRecordWithProperties)(accountHookOutput)) return {
646
756
  isConnected: "isConnected" in accountHookOutput && typeof accountHookOutput.isConnected === "boolean" ? accountHookOutput.isConnected : defaultAccountStatus.isConnected,
757
+ isConnecting: "isConnecting" in accountHookOutput && typeof accountHookOutput.isConnecting === "boolean" ? accountHookOutput.isConnecting : defaultAccountStatus.isConnecting,
758
+ isDisconnected: "isDisconnected" in accountHookOutput && typeof accountHookOutput.isDisconnected === "boolean" ? accountHookOutput.isDisconnected : defaultAccountStatus.isDisconnected,
759
+ isReconnecting: "isReconnecting" in accountHookOutput && typeof accountHookOutput.isReconnecting === "boolean" ? accountHookOutput.isReconnecting : defaultAccountStatus.isReconnecting,
760
+ status: "status" in accountHookOutput && typeof accountHookOutput.status === "string" ? accountHookOutput.status : defaultAccountStatus.status,
647
761
  address: "address" in accountHookOutput && typeof accountHookOutput.address === "string" ? accountHookOutput.address : defaultAccountStatus.address,
648
762
  chainId: "chainId" in accountHookOutput && typeof accountHookOutput.chainId === "number" ? accountHookOutput.chainId : defaultAccountStatus.chainId
649
763
  };
@@ -762,21 +876,21 @@ function useDerivedDisconnect() {
762
876
  * This hook detects that scenario and invokes a callback to re-queue the network switch.
763
877
  *
764
878
  * @param selectedNetworkConfigId - Currently selected network in the app
765
- * @param selectedAdapter - Currently active adapter instance
879
+ * @param selectedCapability - Currently active wallet capability instance
766
880
  * @param networkToSwitchTo - Current network switch queue state (null if no switch pending)
767
881
  * @param onRequeueSwitch - Callback invoked when a network switch should be re-queued
768
882
  */
769
- function useWalletReconnectionHandler(selectedNetworkConfigId, selectedAdapter, networkToSwitchTo, onRequeueSwitch) {
883
+ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedCapability, networkToSwitchTo, onRequeueSwitch) {
770
884
  const { isConnected, chainId: walletChainId } = useDerivedAccountStatus();
771
885
  const prevConnectedRef = (0, react.useRef)(isConnected);
772
886
  (0, react.useEffect)(() => {
773
887
  const isReconnection = !prevConnectedRef.current && isConnected;
774
888
  prevConnectedRef.current = isConnected;
775
- if (!isReconnection || !selectedNetworkConfigId || !selectedAdapter) return;
889
+ if (!isReconnection || !selectedNetworkConfigId || !selectedCapability) return;
776
890
  if (networkToSwitchTo === selectedNetworkConfigId) return;
777
- const adapterConfig = selectedAdapter.networkConfig;
778
- if (!("chainId" in adapterConfig) || !walletChainId) return;
779
- const targetChainId = Number(adapterConfig.chainId);
891
+ const selectedNetworkConfig = selectedCapability.networkConfig;
892
+ if (!("chainId" in selectedNetworkConfig) || !walletChainId) return;
893
+ const targetChainId = Number(selectedNetworkConfig.chainId);
780
894
  if (walletChainId !== targetChainId) {
781
895
  _openzeppelin_ui_utils.logger.info("useWalletReconnectionHandler", `Wallet reconnected with chain ${walletChainId}, but selected network requires ${targetChainId}. Re-queueing switch.`);
782
896
  onRequeueSwitch(selectedNetworkConfigId);
@@ -785,7 +899,7 @@ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedAdapter,
785
899
  isConnected,
786
900
  walletChainId,
787
901
  selectedNetworkConfigId,
788
- selectedAdapter,
902
+ selectedCapability,
789
903
  networkToSwitchTo,
790
904
  onRequeueSwitch
791
905
  ]);
@@ -817,37 +931,20 @@ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedAdapter,
817
931
  */
818
932
  const WalletConnectionUI = ({ className, connectButtonProps, accountDisplayProps, networkSwitcherProps }) => {
819
933
  const [isError, setIsError] = (0, react.useState)(false);
820
- const { activeAdapter, walletFacadeHooks } = useWalletState();
821
- (0, react.useEffect)(() => {
822
- _openzeppelin_ui_utils.logger.debug("WalletConnectionUI", "[Debug] State from useWalletState:", {
823
- adapterId: activeAdapter?.networkConfig.id,
824
- hasFacadeHooks: !!walletFacadeHooks
825
- });
826
- }, [activeAdapter, walletFacadeHooks]);
934
+ const { activeNetworkConfig, activeRuntime, isRuntimeLoading } = useWalletState();
935
+ const activeUiKit = activeRuntime?.uiKit;
936
+ if (!!(isRuntimeLoading && activeRuntime?.networkConfig?.ecosystem && activeNetworkConfig?.ecosystem && activeRuntime.networkConfig.ecosystem !== activeNetworkConfig.ecosystem)) return null;
827
937
  const walletComponents = (() => {
828
- if (!activeAdapter || typeof activeAdapter.getEcosystemWalletComponents !== "function") {
829
- _openzeppelin_ui_utils.logger.debug("WalletConnectionUI", "[Debug] No activeAdapter or getEcosystemWalletComponents method, returning null.");
830
- return null;
831
- }
938
+ if (!activeUiKit || typeof activeUiKit.getEcosystemWalletComponents !== "function") return null;
832
939
  try {
833
- const components = activeAdapter.getEcosystemWalletComponents();
834
- _openzeppelin_ui_utils.logger.debug("WalletConnectionUI", "[Debug] walletComponents from adapter:", components);
835
- return components;
940
+ return activeUiKit.getEcosystemWalletComponents();
836
941
  } catch (error) {
837
- _openzeppelin_ui_utils.logger.error("WalletConnectionUI", "[Debug] Error getting wallet components:", error);
942
+ _openzeppelin_ui_utils.logger.error("WalletConnectionUI", "Error getting wallet components:", error);
838
943
  setIsError(true);
839
944
  return null;
840
945
  }
841
946
  })();
842
- if (!walletComponents) {
843
- _openzeppelin_ui_utils.logger.debug("WalletConnectionUI", "[Debug] getEcosystemWalletComponents returned null/undefined, rendering null.");
844
- return null;
845
- }
846
- _openzeppelin_ui_utils.logger.debug("WalletConnectionUI", "Rendering wallet components:", {
847
- hasConnectButton: !!walletComponents.ConnectButton,
848
- hasAccountDisplay: !!walletComponents.AccountDisplay,
849
- hasNetworkSwitcher: !!walletComponents.NetworkSwitcher
850
- });
947
+ if (!walletComponents) return null;
851
948
  const { ConnectButton, AccountDisplay, NetworkSwitcher } = walletComponents;
852
949
  if (isError) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
853
950
  className: (0, _openzeppelin_ui_utils.cn)("flex items-center gap-4", className),
@@ -875,18 +972,8 @@ const WalletConnectionUI = ({ className, connectButtonProps, accountDisplayProps
875
972
  * Uses useWalletState to get its data.
876
973
  */
877
974
  const WalletConnectionHeader = () => {
878
- const { isAdapterLoading, activeAdapter } = useWalletState();
879
- (0, react.useEffect)(() => {
880
- _openzeppelin_ui_utils.logger.debug("WalletConnectionHeader", "[Debug] State from useWalletState:", {
881
- adapterPresent: !!activeAdapter,
882
- adapterNetwork: activeAdapter?.networkConfig.id,
883
- isLoading: isAdapterLoading
884
- });
885
- }, [activeAdapter, isAdapterLoading]);
886
- if (isAdapterLoading) {
887
- _openzeppelin_ui_utils.logger.debug("WalletConnectionHeader", "[Debug] Adapter loading, showing skeleton.");
888
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-9 w-28 animate-pulse rounded bg-muted" });
889
- }
975
+ const { isRuntimeLoading } = useWalletState();
976
+ if (isRuntimeLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-9 w-28 animate-pulse rounded bg-muted" });
890
977
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WalletConnectionUI, {});
891
978
  };
892
979
 
@@ -906,7 +993,7 @@ const WalletConnectionHeader = () => {
906
993
  * - Tracks switch attempts to prevent duplicate operations
907
994
  * - Provides completion callback for parent components to handle state cleanup
908
995
  */
909
- const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplete }) => {
996
+ const NetworkSwitchManager = ({ wallet, networkCatalog, targetNetworkId, onNetworkSwitchComplete }) => {
910
997
  const isMountedRef = (0, react.useRef)(true);
911
998
  const [hasAttemptedSwitch, setHasAttemptedSwitch] = (0, react.useState)(false);
912
999
  const { isConnected, chainId: currentChainIdFromHook } = useDerivedAccountStatus();
@@ -923,7 +1010,7 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
923
1010
  (0, react.useEffect)(() => {
924
1011
  _openzeppelin_ui_utils.logger.info("NetworkSwitchManager", "State Update:", {
925
1012
  target: targetNetworkId,
926
- adapterNetwork: adapter.networkConfig.id,
1013
+ walletNetwork: wallet.networkConfig.id,
927
1014
  isSwitching: isSwitchingNetworkViaHook,
928
1015
  hookError: !!switchNetworkError,
929
1016
  canExec: !!execSwitchNetwork,
@@ -932,7 +1019,7 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
932
1019
  attempted: hasAttemptedSwitch
933
1020
  });
934
1021
  }, [
935
- adapter,
1022
+ wallet,
936
1023
  targetNetworkId,
937
1024
  isSwitchingNetworkViaHook,
938
1025
  switchNetworkError,
@@ -959,19 +1046,23 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
959
1046
  _openzeppelin_ui_utils.logger.info("NetworkSwitchManager", "Previous switch attempt concluded. Deferring to completion effect.");
960
1047
  return;
961
1048
  }
962
- if (adapter.networkConfig.id !== targetNetworkId) {
963
- completeOperation(`CRITICAL: Adapter (${adapter.networkConfig.id}) vs Target (${targetNetworkId}) mismatch. Operation halted.`, { notifyComplete: false });
1049
+ if (!networkCatalog.getNetworks().find((network) => network.id === targetNetworkId)) {
1050
+ completeOperation(`Target network ${targetNetworkId} is not present in the network catalog.`, { notifyComplete: false });
1051
+ return;
1052
+ }
1053
+ if (wallet.networkConfig.id !== targetNetworkId) {
1054
+ completeOperation(`CRITICAL: Wallet capability (${wallet.networkConfig.id}) vs Target (${targetNetworkId}) mismatch. Operation halted.`, { notifyComplete: false });
964
1055
  return;
965
1056
  }
966
1057
  if (!isConnected) {
967
1058
  completeOperation("Wallet not connected (derived status). Awaiting connection.", { notifyComplete: false });
968
1059
  return;
969
1060
  }
970
- if (!("chainId" in adapter.networkConfig)) {
1061
+ if (!("chainId" in wallet.networkConfig)) {
971
1062
  completeOperation("Network does not support chain switching (non-EVM). Operation complete (no-op).");
972
1063
  return;
973
1064
  }
974
- const targetChainToBeSwitchedTo = Number(adapter.networkConfig.chainId);
1065
+ const targetChainToBeSwitchedTo = Number(wallet.networkConfig.chainId);
975
1066
  if (currentChainIdFromHook === targetChainToBeSwitchedTo) {
976
1067
  completeOperation("Already on correct chain (derived status). Operation complete.");
977
1068
  return;
@@ -988,7 +1079,8 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
988
1079
  const timeoutId = setTimeout(performSwitchActual, 100);
989
1080
  return () => clearTimeout(timeoutId);
990
1081
  }, [
991
- adapter,
1082
+ wallet,
1083
+ networkCatalog,
992
1084
  targetNetworkId,
993
1085
  execSwitchNetwork,
994
1086
  isSwitchingNetworkViaHook,
@@ -1020,23 +1112,23 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
1020
1112
  };
1021
1113
 
1022
1114
  //#endregion
1023
- exports.AdapterContext = AdapterContext;
1024
- exports.AdapterProvider = AdapterProvider;
1025
1115
  exports.AnalyticsContext = AnalyticsContext;
1026
1116
  exports.AnalyticsProvider = AnalyticsProvider;
1027
1117
  exports.NetworkSwitchManager = NetworkSwitchManager;
1118
+ exports.RuntimeContext = RuntimeContext;
1119
+ exports.RuntimeProvider = RuntimeProvider;
1028
1120
  exports.VERSION = VERSION;
1029
1121
  exports.WalletConnectionHeader = WalletConnectionHeader;
1030
1122
  exports.WalletConnectionUI = WalletConnectionUI;
1031
1123
  exports.WalletStateContext = WalletStateContext;
1032
1124
  exports.WalletStateProvider = WalletStateProvider;
1033
- exports.useAdapterContext = useAdapterContext;
1034
1125
  exports.useAnalytics = useAnalytics;
1035
1126
  exports.useDerivedAccountStatus = useDerivedAccountStatus;
1036
1127
  exports.useDerivedChainInfo = useDerivedChainInfo;
1037
1128
  exports.useDerivedConnectStatus = useDerivedConnectStatus;
1038
1129
  exports.useDerivedDisconnect = useDerivedDisconnect;
1039
1130
  exports.useDerivedSwitchChainStatus = useDerivedSwitchChainStatus;
1131
+ exports.useRuntimeContext = useRuntimeContext;
1040
1132
  exports.useWalletComponents = useWalletComponents;
1041
1133
  exports.useWalletReconnectionHandler = useWalletReconnectionHandler;
1042
1134
  exports.useWalletState = useWalletState;