@openzeppelin/ui-react 1.2.0 → 2.0.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.cjs CHANGED
@@ -32,94 +32,131 @@ 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.0";
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);
86
87
  (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),
88
+ runtimeRegistryRef.current = runtimeRegistry;
89
+ }, [runtimeRegistry]);
90
+ (0, react.useEffect)(() => {
91
+ return () => {
92
+ Object.values(runtimeRegistryRef.current).forEach((runtime) => {
93
+ runtime.dispose();
94
+ });
95
+ };
96
+ }, []);
97
+ (0, react.useEffect)(() => {
98
+ const runtimeCount = Object.keys(runtimeRegistry).length;
99
+ if (runtimeCount > 0) _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Registry contains ${runtimeCount} runtimes:`, {
100
+ networkIds: Object.keys(runtimeRegistry),
90
101
  loadingCount: loadingNetworks.size,
91
102
  loadingNetworkIds: Array.from(loadingNetworks)
92
103
  });
93
- }, [adapterRegistry, loadingNetworks]);
104
+ }, [runtimeRegistry, loadingNetworks]);
105
+ /**
106
+ * Evicts a runtime from the registry by network ID and disposes it.
107
+ * This is the safe counterpart to `getRuntimeForNetwork`: callers should only
108
+ * release a runtime after they have already promoted its replacement.
109
+ *
110
+ * Disposal is deferred to the next macrotask so React's commit phase
111
+ * (including development-mode prop diffing) can finish reading the old
112
+ * runtime's properties without hitting the RuntimeDisposedError proxy trap.
113
+ */
114
+ const releaseRuntime = (0, react.useCallback)((networkId) => {
115
+ const runtime = runtimeRegistryRef.current[networkId];
116
+ if (!runtime) return;
117
+ _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Releasing runtime for network ${networkId}`);
118
+ setRuntimeRegistry((prev) => {
119
+ const next = { ...prev };
120
+ delete next[networkId];
121
+ return next;
122
+ });
123
+ setTimeout(() => {
124
+ try {
125
+ runtime.dispose();
126
+ } catch (error) {
127
+ _openzeppelin_ui_utils.logger.error("RuntimeProvider", `Error disposing runtime for network ${networkId}:`, error);
128
+ }
129
+ }, 0);
130
+ }, []);
94
131
  /**
95
- * Function to get or create an adapter for a network
132
+ * Function to get or create a runtime for a network
96
133
  *
97
- * IMPORTANT: The actual adapter loading is handled in the useConfiguredAdapterSingleton hook
98
- * to avoid React state updates during render, which would cause errors.
134
+ * IMPORTANT: Runtime loading is coordinated carefully to avoid React state updates
135
+ * during render, which would cause errors.
99
136
  *
100
137
  * 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
138
+ * 1. Returns existing runtimes immediately if available
139
+ * 2. Reports loading state for runtimes being initialized
140
+ * 3. Initiates runtime loading when needed
104
141
  */
105
- const getAdapterForNetwork = (0, react.useCallback)((networkConfig) => {
142
+ const getRuntimeForNetwork = (0, react.useCallback)((networkConfig) => {
106
143
  if (!networkConfig) return {
107
- adapter: null,
144
+ runtime: null,
108
145
  isLoading: false
109
146
  };
110
147
  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}`);
148
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Runtime requested for network ${networkId}`);
149
+ if (runtimeRegistry[networkId]) {
150
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Using existing runtime for network ${networkId}`);
114
151
  return {
115
- adapter: adapterRegistry[networkId],
152
+ runtime: runtimeRegistry[networkId],
116
153
  isLoading: false
117
154
  };
118
155
  }
119
156
  if (loadingNetworks.has(networkId)) {
120
- _openzeppelin_ui_utils.logger.debug("AdapterProvider", `Adapter for network ${networkId} is currently loading`);
157
+ _openzeppelin_ui_utils.logger.debug("RuntimeProvider", `Runtime for network ${networkId} is currently loading`);
121
158
  return {
122
- adapter: null,
159
+ runtime: null,
123
160
  isLoading: true
124
161
  };
125
162
  }
@@ -128,15 +165,15 @@ function AdapterProvider({ children, resolveAdapter }) {
128
165
  newSet.add(networkId);
129
166
  return newSet;
130
167
  });
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)
168
+ _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Starting runtime initialization for network ${networkId} (${networkConfig.name})`);
169
+ resolveRuntime(networkConfig).then((runtime) => {
170
+ _openzeppelin_ui_utils.logger.info("RuntimeProvider", `Runtime for network ${networkId} loaded successfully`, {
171
+ type: runtime.constructor.name,
172
+ objectId: Object.prototype.toString.call(runtime)
136
173
  });
137
- setAdapterRegistry((prev) => ({
174
+ setRuntimeRegistry((prev) => ({
138
175
  ...prev,
139
- [networkId]: adapter
176
+ [networkId]: runtime
140
177
  }));
141
178
  setLoadingNetworks((prev) => {
142
179
  const newSet = new Set(prev);
@@ -144,7 +181,7 @@ function AdapterProvider({ children, resolveAdapter }) {
144
181
  return newSet;
145
182
  });
146
183
  }).catch((error) => {
147
- _openzeppelin_ui_utils.logger.error("AdapterProvider", `Error loading adapter for network ${networkId}:`, error);
184
+ _openzeppelin_ui_utils.logger.error("RuntimeProvider", `Error loading runtime for network ${networkId}:`, error);
148
185
  setLoadingNetworks((prev) => {
149
186
  const newSet = new Set(prev);
150
187
  newSet.delete(networkId);
@@ -152,16 +189,19 @@ function AdapterProvider({ children, resolveAdapter }) {
152
189
  });
153
190
  });
154
191
  return {
155
- adapter: null,
192
+ runtime: null,
156
193
  isLoading: true
157
194
  };
158
195
  }, [
159
- adapterRegistry,
196
+ runtimeRegistry,
160
197
  loadingNetworks,
161
- resolveAdapter
198
+ resolveRuntime
162
199
  ]);
163
- const contextValue = (0, react.useMemo)(() => ({ getAdapterForNetwork }), [getAdapterForNetwork]);
164
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdapterContext.Provider, {
200
+ const contextValue = (0, react.useMemo)(() => ({
201
+ getRuntimeForNetwork,
202
+ releaseRuntime
203
+ }), [getRuntimeForNetwork, releaseRuntime]);
204
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuntimeContext.Provider, {
165
205
  value: contextValue,
166
206
  children
167
207
  });
@@ -179,7 +219,7 @@ function AdapterProvider({ children, resolveAdapter }) {
179
219
  * consuming package's bundle. This creates MULTIPLE instances of this module:
180
220
  *
181
221
  * 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
222
+ * 2. The adapter package's inlined copy → .vite/deps/@openzeppelin_adapter_evm.js
183
223
  *
184
224
  * Since React contexts use referential identity, these two module instances have
185
225
  * DIFFERENT context objects. When the adapter's components call useWalletState(),
@@ -241,54 +281,88 @@ function useWalletState() {
241
281
  /**
242
282
  * useAdapterContext.ts
243
283
  *
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.
284
+ * This file provides a hook to access the runtime context throughout the application.
285
+ * It's a critical part of the runtime singleton pattern, allowing components to
286
+ * access the centralized runtime registry.
247
287
  *
248
- * The adapter singleton pattern ensures:
249
- * - Only one adapter instance exists per network
288
+ * The runtime singleton pattern ensures:
289
+ * - Only one runtime instance exists per network
250
290
  * - Wallet connection state is consistent across the app
251
- * - Better performance by eliminating redundant adapter initialization
291
+ * - Better performance by eliminating redundant runtime initialization
252
292
  */
253
293
  /**
254
- * Hook to access the adapter context
294
+ * Hook to access the runtime context
255
295
  *
256
- * This hook provides access to the getAdapterForNetwork function which
257
- * retrieves or creates adapter instances from the singleton registry.
296
+ * This hook provides access to the `getRuntimeForNetwork` function which
297
+ * retrieves or creates runtime instances from the singleton registry.
258
298
  *
259
- * Components should typically use useConfiguredAdapterSingleton instead
299
+ * Components should typically use the higher-level wallet/runtime hooks instead
260
300
  * of this hook directly, as it handles React state update timing properly.
261
301
  *
262
- * @throws Error if used outside of an AdapterProvider context
263
- * @returns The adapter context value
302
+ * @throws Error if used outside of a RuntimeProvider context
303
+ * @returns The runtime context value
264
304
  */
265
- function useAdapterContext() {
266
- const context = (0, react.useContext)(AdapterContext);
267
- if (!context) throw new Error("useAdapterContext must be used within an AdapterProvider");
305
+ function useRuntimeContext() {
306
+ const context = (0, react.useContext)(RuntimeContext);
307
+ if (!context) throw new Error("useRuntimeContext must be used within a RuntimeProvider");
268
308
  return context;
269
309
  }
270
310
 
311
+ //#endregion
312
+ //#region src/hooks/walletSessionRegistry.ts
313
+ /**
314
+ * Returns the cached wallet session for an ecosystem, if present.
315
+ *
316
+ * @param registry - Current internal wallet session registry.
317
+ * @param ecosystem - Ecosystem key to resolve.
318
+ * @returns The cached session entry or `null` when the ecosystem has not been configured yet.
319
+ */
320
+ function getWalletSession(registry, ecosystem) {
321
+ if (!ecosystem) return null;
322
+ return registry[ecosystem] ?? null;
323
+ }
324
+ /**
325
+ * Inserts or replaces the cached wallet session for a single ecosystem.
326
+ *
327
+ * @param registry - Current internal wallet session registry.
328
+ * @param session - Session entry to cache.
329
+ * @returns A new registry object containing the upserted ecosystem session.
330
+ */
331
+ function upsertWalletSession(registry, session) {
332
+ return {
333
+ ...registry,
334
+ [session.ecosystem]: session
335
+ };
336
+ }
337
+
271
338
  //#endregion
272
339
  //#region src/hooks/WalletStateProvider.tsx
273
340
  /**
274
- * Configures the adapter's UI kit and returns the UI provider component and hooks.
341
+ * Configures the runtime's UI kit capability and returns the ecosystem session artifacts.
275
342
  */
276
- async function configureAdapterUiKit(adapter, loadConfigModule, programmaticOverrides = {}) {
343
+ async function configureRuntimeUiKit(runtime, loadConfigModule, programmaticOverrides = {}) {
344
+ const uiKit = runtime.uiKit;
345
+ if (!uiKit) return {
346
+ providerComponent: null,
347
+ hooks: null
348
+ };
277
349
  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);
350
+ const hasUiKitOverride = Object.keys(programmaticOverrides).length > 0;
351
+ if (typeof uiKit.configureUiKit === "function") {
352
+ const nextUiKitConfig = { ...programmaticOverrides };
353
+ _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}`);
354
+ await uiKit.configureUiKit(nextUiKitConfig, { loadUiKitNativeConfig: loadConfigModule });
355
+ _openzeppelin_ui_utils.logger.info("[WSP configureRuntimeUiKit] configureUiKit completed for runtime:", runtime.networkConfig.id);
282
356
  }
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.");
357
+ const providerComponent = uiKit.getEcosystemReactUiContextProvider?.() || null;
358
+ const hooks = uiKit.getEcosystemReactHooks?.() || null;
359
+ _openzeppelin_ui_utils.logger.info("[WSP configureRuntimeUiKit]", "UI provider and hooks retrieved successfully.");
286
360
  return {
287
361
  providerComponent,
288
362
  hooks
289
363
  };
290
364
  } catch (error) {
291
- _openzeppelin_ui_utils.logger.error("[WSP configureAdapterUiKit]", "Error during adapter UI setup:", error);
365
+ _openzeppelin_ui_utils.logger.error("[WSP configureRuntimeUiKit]", "Error during runtime UI setup:", error);
292
366
  throw error;
293
367
  }
294
368
  }
@@ -298,26 +372,28 @@ async function configureAdapterUiKit(adapter, loadConfigModule, programmaticOver
298
372
  * It is responsible for:
299
373
  * 1. Managing the globally selected active network ID (`activeNetworkId`).
300
374
  * 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.
375
+ * 3. Fetching and providing the corresponding `EcosystemRuntime` (`activeRuntime`) for the active network,
376
+ * leveraging the `RuntimeProvider` to ensure runtime singletons.
377
+ * 4. Caching ecosystem-scoped wallet session artifacts (provider roots and facade hooks)
378
+ * independently from the network-scoped runtime.
379
+ * 5. Rendering the active ecosystem wallet provider (e.g., WagmiProvider for EVM) around its
380
+ * children, which is essential for the facade hooks to function correctly.
306
381
  * 6. Providing a function (`setActiveNetworkId`) to change the globally active network.
307
382
  *
308
383
  * Consumers use the `useWalletState()` hook to access this global state.
309
- * It should be placed high in the component tree, inside an `<AdapterProvider>`.
384
+ * It should be placed high in the component tree, inside a `<RuntimeProvider>`.
310
385
  */
311
386
  function WalletStateProvider({ children, initialNetworkId = null, getNetworkConfigById, loadConfigModule }) {
312
387
  const [currentGlobalNetworkId, setCurrentGlobalNetworkIdState] = (0, react.useState)(initialNetworkId);
313
388
  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);
389
+ const [globalActiveRuntime, setGlobalActiveRuntime] = (0, react.useState)(null);
390
+ const [isGlobalRuntimeLoading, setIsGlobalRuntimeLoading] = (0, react.useState)(false);
391
+ const [walletSessionRegistry, setWalletSessionRegistry] = (0, react.useState)({});
392
+ const [activeWalletSessionEcosystem, setActiveWalletSessionEcosystem] = (0, react.useState)(null);
318
393
  const [uiKitConfigVersion, setUiKitConfigVersion] = (0, react.useState)(0);
319
394
  const [programmaticUiKitConfig, setProgrammaticUiKitConfig] = (0, react.useState)(void 0);
320
- const { getAdapterForNetwork } = useAdapterContext();
395
+ const { getRuntimeForNetwork, releaseRuntime } = useRuntimeContext();
396
+ const promotedNetworkIdRef = (0, react.useRef)(null);
321
397
  (0, react.useEffect)(() => {
322
398
  const abortController = new AbortController();
323
399
  async function fetchNetworkConfig() {
@@ -340,98 +416,115 @@ function WalletStateProvider({ children, initialNetworkId = null, getNetworkConf
340
416
  }, [currentGlobalNetworkId, getNetworkConfigById]);
341
417
  (0, react.useEffect)(() => {
342
418
  const abortController = new AbortController();
343
- async function loadAdapterAndConfigureUi() {
419
+ async function loadRuntimeAndConfigureUi() {
344
420
  if (!currentGlobalNetworkConfig) {
345
421
  if (!abortController.signal.aborted) {
346
- setGlobalActiveAdapter(null);
347
- setIsGlobalAdapterLoading(false);
348
- setAdapterUiContextProviderToRender(null);
349
- setWalletFacadeHooks(null);
422
+ const prevNetworkId = promotedNetworkIdRef.current;
423
+ setGlobalActiveRuntime(null);
424
+ setIsGlobalRuntimeLoading(false);
425
+ setActiveWalletSessionEcosystem(null);
426
+ if (prevNetworkId) {
427
+ releaseRuntime(prevNetworkId);
428
+ promotedNetworkIdRef.current = null;
429
+ }
350
430
  }
351
431
  return;
352
432
  }
353
- const { adapter: newAdapter, isLoading: newIsLoading } = getAdapterForNetwork(currentGlobalNetworkConfig);
433
+ const { runtime: newRuntime, isLoading: newIsLoading } = getRuntimeForNetwork(currentGlobalNetworkConfig);
354
434
  if (abortController.signal.aborted) return;
355
- setIsGlobalAdapterLoading(newIsLoading);
356
- if (newAdapter && !newIsLoading) try {
357
- const { providerComponent, hooks } = await configureAdapterUiKit(newAdapter, loadConfigModule, programmaticUiKitConfig);
435
+ setIsGlobalRuntimeLoading(newIsLoading);
436
+ if (newRuntime && !newIsLoading) try {
437
+ const { providerComponent, hooks } = await configureRuntimeUiKit(newRuntime, loadConfigModule, programmaticUiKitConfig);
358
438
  if (!abortController.signal.aborted) {
359
- setAdapterUiContextProviderToRender(() => providerComponent);
360
- setWalletFacadeHooks(hooks);
361
- setGlobalActiveAdapter(newAdapter);
439
+ const ecosystem = newRuntime.networkConfig.ecosystem;
440
+ const prevNetworkId = promotedNetworkIdRef.current;
441
+ const nextNetworkId = newRuntime.networkConfig.id;
442
+ setWalletSessionRegistry((prevRegistry) => upsertWalletSession(prevRegistry, {
443
+ ecosystem,
444
+ lastConfiguredNetworkId: nextNetworkId,
445
+ providerComponent,
446
+ hooks
447
+ }));
448
+ setGlobalActiveRuntime(newRuntime);
449
+ setActiveWalletSessionEcosystem(ecosystem);
450
+ promotedNetworkIdRef.current = nextNetworkId;
451
+ if (prevNetworkId && prevNetworkId !== nextNetworkId) releaseRuntime(prevNetworkId);
362
452
  }
363
453
  } 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
- }
454
+ if (!abortController.signal.aborted) _openzeppelin_ui_utils.logger.error("[WSP loadRuntimeAndConfigureUi]", "Error during runtime UI setup:", error);
369
455
  }
370
- else if (!newAdapter && !newIsLoading) {
456
+ else if (!newRuntime && !newIsLoading) {
371
457
  if (!abortController.signal.aborted) {
372
- setAdapterUiContextProviderToRender(null);
373
- setWalletFacadeHooks(null);
374
- setGlobalActiveAdapter(null);
458
+ const prevNetworkId = promotedNetworkIdRef.current;
459
+ setGlobalActiveRuntime(null);
460
+ setActiveWalletSessionEcosystem(null);
461
+ if (prevNetworkId) {
462
+ releaseRuntime(prevNetworkId);
463
+ promotedNetworkIdRef.current = null;
464
+ }
375
465
  }
376
466
  }
377
467
  }
378
- loadAdapterAndConfigureUi();
468
+ loadRuntimeAndConfigureUi();
379
469
  return () => abortController.abort();
380
470
  }, [
381
471
  currentGlobalNetworkConfig,
382
- getAdapterForNetwork,
472
+ getRuntimeForNetwork,
473
+ releaseRuntime,
383
474
  loadConfigModule,
384
475
  uiKitConfigVersion,
385
476
  programmaticUiKitConfig
386
477
  ]);
387
478
  /**
388
479
  * Callback to set the globally active network ID.
389
- * Also clears dependent states (config, adapter, hooks) if the network ID is cleared.
480
+ * Also clears dependent states if the network ID is cleared.
390
481
  */
391
482
  const setActiveNetworkIdCallback = (0, react.useCallback)((networkId) => {
392
483
  _openzeppelin_ui_utils.logger.info("WalletStateProvider", `Setting global network ID to: ${networkId}`);
393
484
  setCurrentGlobalNetworkIdState(networkId);
394
485
  if (!networkId) {
395
486
  setCurrentGlobalNetworkConfig(null);
396
- setGlobalActiveAdapter(null);
397
- setIsGlobalAdapterLoading(false);
398
- setWalletFacadeHooks(null);
487
+ setGlobalActiveRuntime(null);
488
+ setIsGlobalRuntimeLoading(false);
489
+ setActiveWalletSessionEcosystem(null);
399
490
  }
400
491
  }, []);
401
492
  /**
402
- * Callback to explicitly trigger a re-configuration of the active adapter's UI kit.
493
+ * Callback to explicitly trigger a re-configuration of the active runtime's UI kit.
403
494
  * This is useful when a UI kit setting changes (e.g., via a wizard) without a network change.
404
495
  */
405
- const reconfigureActiveAdapterUiKit = (0, react.useCallback)((uiKitConfig) => {
496
+ const reconfigureActiveUiKit = (0, react.useCallback)((uiKitConfig) => {
406
497
  _openzeppelin_ui_utils.logger.info("WalletStateProvider", "Explicitly triggering UI kit re-configuration by bumping version.", uiKitConfig);
407
498
  setProgrammaticUiKitConfig(uiKitConfig);
408
499
  setUiKitConfigVersion((v) => v + 1);
409
500
  }, [setProgrammaticUiKitConfig, setUiKitConfigVersion]);
501
+ const activeWalletSession = (0, react.useMemo)(() => getWalletSession(walletSessionRegistry, activeWalletSessionEcosystem), [walletSessionRegistry, activeWalletSessionEcosystem]);
502
+ const walletFacadeHooks = activeWalletSession?.hooks ?? null;
410
503
  const contextValue = (0, react.useMemo)(() => ({
411
504
  activeNetworkId: currentGlobalNetworkId,
412
505
  setActiveNetworkId: setActiveNetworkIdCallback,
413
506
  activeNetworkConfig: currentGlobalNetworkConfig,
414
- activeAdapter: globalActiveAdapter,
415
- isAdapterLoading: isGlobalAdapterLoading,
507
+ activeRuntime: globalActiveRuntime,
508
+ isRuntimeLoading: isGlobalRuntimeLoading,
416
509
  walletFacadeHooks,
417
- reconfigureActiveAdapterUiKit
510
+ reconfigureActiveUiKit
418
511
  }), [
419
512
  currentGlobalNetworkId,
420
513
  setActiveNetworkIdCallback,
421
514
  currentGlobalNetworkConfig,
422
- globalActiveAdapter,
423
- isGlobalAdapterLoading,
515
+ globalActiveRuntime,
516
+ isGlobalRuntimeLoading,
424
517
  walletFacadeHooks,
425
- reconfigureActiveAdapterUiKit
518
+ reconfigureActiveUiKit
426
519
  ]);
427
- const ActualProviderToRender = AdapterUiContextProviderToRender;
520
+ const ActualProviderToRender = activeWalletSession?.providerComponent ?? null;
428
521
  let childrenToRender;
429
522
  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);
523
+ const key = activeWalletSessionEcosystem || "unknown";
524
+ _openzeppelin_ui_utils.logger.debug("[WSP RENDER]", "Rendering runtime-provided UI context provider:", ActualProviderToRender.displayName || ActualProviderToRender.name || "UnknownComponent", "with key:", key);
432
525
  childrenToRender = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActualProviderToRender, { children }, key);
433
526
  } else {
434
- _openzeppelin_ui_utils.logger.info("[WSP RENDER]", "No adapter UI context provider to render. Rendering direct children.");
527
+ _openzeppelin_ui_utils.logger.debug("[WSP RENDER]", "No runtime UI context provider to render. Rendering direct children.");
435
528
  childrenToRender = children;
436
529
  }
437
530
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WalletStateContext.Provider, {
@@ -575,14 +668,14 @@ const useAnalytics = () => {
575
668
  //#endregion
576
669
  //#region src/hooks/useWalletComponents.ts
577
670
  /**
578
- * Hook that provides direct access to wallet UI components from the active adapter.
671
+ * Hook that provides direct access to wallet UI components from the active runtime.
579
672
  *
580
673
  * Use this hook when you need full control over the layout and composition of
581
674
  * wallet components. For standard layouts, prefer using `WalletConnectionUI`
582
675
  * with its props forwarding capabilities.
583
676
  *
584
- * @returns The wallet components object, or null if no adapter is active or
585
- * the adapter doesn't provide wallet components.
677
+ * @returns The wallet components object, or null if no runtime is active or
678
+ * the runtime doesn't provide wallet components.
586
679
  *
587
680
  * @example
588
681
  * ```tsx
@@ -617,10 +710,11 @@ const useAnalytics = () => {
617
710
  * ```
618
711
  */
619
712
  function useWalletComponents() {
620
- const { activeAdapter } = useWalletState();
621
- if (!activeAdapter || typeof activeAdapter.getEcosystemWalletComponents !== "function") return null;
713
+ const { activeNetworkConfig, activeRuntime, isRuntimeLoading } = useWalletState();
714
+ const activeUiKit = activeRuntime?.uiKit;
715
+ if (!!(isRuntimeLoading && activeRuntime?.networkConfig?.ecosystem && activeNetworkConfig?.ecosystem && activeRuntime.networkConfig.ecosystem !== activeNetworkConfig.ecosystem) || !activeUiKit || typeof activeUiKit.getEcosystemWalletComponents !== "function") return null;
622
716
  try {
623
- return activeAdapter.getEcosystemWalletComponents() ?? null;
717
+ return activeUiKit.getEcosystemWalletComponents() ?? null;
624
718
  } catch {
625
719
  return null;
626
720
  }
@@ -630,6 +724,10 @@ function useWalletComponents() {
630
724
  //#region src/hooks/useDerivedAccountStatus.ts
631
725
  const defaultAccountStatus = {
632
726
  isConnected: false,
727
+ isConnecting: false,
728
+ isDisconnected: true,
729
+ isReconnecting: false,
730
+ status: "disconnected",
633
731
  address: void 0,
634
732
  chainId: void 0
635
733
  };
@@ -644,6 +742,10 @@ function useDerivedAccountStatus() {
644
742
  const accountHookOutput = walletFacadeHooks?.useAccount ? walletFacadeHooks.useAccount() : void 0;
645
743
  if ((0, _openzeppelin_ui_utils.isRecordWithProperties)(accountHookOutput)) return {
646
744
  isConnected: "isConnected" in accountHookOutput && typeof accountHookOutput.isConnected === "boolean" ? accountHookOutput.isConnected : defaultAccountStatus.isConnected,
745
+ isConnecting: "isConnecting" in accountHookOutput && typeof accountHookOutput.isConnecting === "boolean" ? accountHookOutput.isConnecting : defaultAccountStatus.isConnecting,
746
+ isDisconnected: "isDisconnected" in accountHookOutput && typeof accountHookOutput.isDisconnected === "boolean" ? accountHookOutput.isDisconnected : defaultAccountStatus.isDisconnected,
747
+ isReconnecting: "isReconnecting" in accountHookOutput && typeof accountHookOutput.isReconnecting === "boolean" ? accountHookOutput.isReconnecting : defaultAccountStatus.isReconnecting,
748
+ status: "status" in accountHookOutput && typeof accountHookOutput.status === "string" ? accountHookOutput.status : defaultAccountStatus.status,
647
749
  address: "address" in accountHookOutput && typeof accountHookOutput.address === "string" ? accountHookOutput.address : defaultAccountStatus.address,
648
750
  chainId: "chainId" in accountHookOutput && typeof accountHookOutput.chainId === "number" ? accountHookOutput.chainId : defaultAccountStatus.chainId
649
751
  };
@@ -762,21 +864,21 @@ function useDerivedDisconnect() {
762
864
  * This hook detects that scenario and invokes a callback to re-queue the network switch.
763
865
  *
764
866
  * @param selectedNetworkConfigId - Currently selected network in the app
765
- * @param selectedAdapter - Currently active adapter instance
867
+ * @param selectedCapability - Currently active wallet capability instance
766
868
  * @param networkToSwitchTo - Current network switch queue state (null if no switch pending)
767
869
  * @param onRequeueSwitch - Callback invoked when a network switch should be re-queued
768
870
  */
769
- function useWalletReconnectionHandler(selectedNetworkConfigId, selectedAdapter, networkToSwitchTo, onRequeueSwitch) {
871
+ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedCapability, networkToSwitchTo, onRequeueSwitch) {
770
872
  const { isConnected, chainId: walletChainId } = useDerivedAccountStatus();
771
873
  const prevConnectedRef = (0, react.useRef)(isConnected);
772
874
  (0, react.useEffect)(() => {
773
875
  const isReconnection = !prevConnectedRef.current && isConnected;
774
876
  prevConnectedRef.current = isConnected;
775
- if (!isReconnection || !selectedNetworkConfigId || !selectedAdapter) return;
877
+ if (!isReconnection || !selectedNetworkConfigId || !selectedCapability) return;
776
878
  if (networkToSwitchTo === selectedNetworkConfigId) return;
777
- const adapterConfig = selectedAdapter.networkConfig;
778
- if (!("chainId" in adapterConfig) || !walletChainId) return;
779
- const targetChainId = Number(adapterConfig.chainId);
879
+ const selectedNetworkConfig = selectedCapability.networkConfig;
880
+ if (!("chainId" in selectedNetworkConfig) || !walletChainId) return;
881
+ const targetChainId = Number(selectedNetworkConfig.chainId);
780
882
  if (walletChainId !== targetChainId) {
781
883
  _openzeppelin_ui_utils.logger.info("useWalletReconnectionHandler", `Wallet reconnected with chain ${walletChainId}, but selected network requires ${targetChainId}. Re-queueing switch.`);
782
884
  onRequeueSwitch(selectedNetworkConfigId);
@@ -785,7 +887,7 @@ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedAdapter,
785
887
  isConnected,
786
888
  walletChainId,
787
889
  selectedNetworkConfigId,
788
- selectedAdapter,
890
+ selectedCapability,
789
891
  networkToSwitchTo,
790
892
  onRequeueSwitch
791
893
  ]);
@@ -817,37 +919,20 @@ function useWalletReconnectionHandler(selectedNetworkConfigId, selectedAdapter,
817
919
  */
818
920
  const WalletConnectionUI = ({ className, connectButtonProps, accountDisplayProps, networkSwitcherProps }) => {
819
921
  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]);
922
+ const { activeNetworkConfig, activeRuntime, isRuntimeLoading } = useWalletState();
923
+ const activeUiKit = activeRuntime?.uiKit;
924
+ if (!!(isRuntimeLoading && activeRuntime?.networkConfig?.ecosystem && activeNetworkConfig?.ecosystem && activeRuntime.networkConfig.ecosystem !== activeNetworkConfig.ecosystem)) return null;
827
925
  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
- }
926
+ if (!activeUiKit || typeof activeUiKit.getEcosystemWalletComponents !== "function") return null;
832
927
  try {
833
- const components = activeAdapter.getEcosystemWalletComponents();
834
- _openzeppelin_ui_utils.logger.debug("WalletConnectionUI", "[Debug] walletComponents from adapter:", components);
835
- return components;
928
+ return activeUiKit.getEcosystemWalletComponents();
836
929
  } catch (error) {
837
- _openzeppelin_ui_utils.logger.error("WalletConnectionUI", "[Debug] Error getting wallet components:", error);
930
+ _openzeppelin_ui_utils.logger.error("WalletConnectionUI", "Error getting wallet components:", error);
838
931
  setIsError(true);
839
932
  return null;
840
933
  }
841
934
  })();
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
- });
935
+ if (!walletComponents) return null;
851
936
  const { ConnectButton, AccountDisplay, NetworkSwitcher } = walletComponents;
852
937
  if (isError) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
853
938
  className: (0, _openzeppelin_ui_utils.cn)("flex items-center gap-4", className),
@@ -875,18 +960,8 @@ const WalletConnectionUI = ({ className, connectButtonProps, accountDisplayProps
875
960
  * Uses useWalletState to get its data.
876
961
  */
877
962
  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
- }
963
+ const { isRuntimeLoading } = useWalletState();
964
+ if (isRuntimeLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-9 w-28 animate-pulse rounded bg-muted" });
890
965
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WalletConnectionUI, {});
891
966
  };
892
967
 
@@ -906,7 +981,7 @@ const WalletConnectionHeader = () => {
906
981
  * - Tracks switch attempts to prevent duplicate operations
907
982
  * - Provides completion callback for parent components to handle state cleanup
908
983
  */
909
- const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplete }) => {
984
+ const NetworkSwitchManager = ({ wallet, networkCatalog, targetNetworkId, onNetworkSwitchComplete }) => {
910
985
  const isMountedRef = (0, react.useRef)(true);
911
986
  const [hasAttemptedSwitch, setHasAttemptedSwitch] = (0, react.useState)(false);
912
987
  const { isConnected, chainId: currentChainIdFromHook } = useDerivedAccountStatus();
@@ -923,7 +998,7 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
923
998
  (0, react.useEffect)(() => {
924
999
  _openzeppelin_ui_utils.logger.info("NetworkSwitchManager", "State Update:", {
925
1000
  target: targetNetworkId,
926
- adapterNetwork: adapter.networkConfig.id,
1001
+ walletNetwork: wallet.networkConfig.id,
927
1002
  isSwitching: isSwitchingNetworkViaHook,
928
1003
  hookError: !!switchNetworkError,
929
1004
  canExec: !!execSwitchNetwork,
@@ -932,7 +1007,7 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
932
1007
  attempted: hasAttemptedSwitch
933
1008
  });
934
1009
  }, [
935
- adapter,
1010
+ wallet,
936
1011
  targetNetworkId,
937
1012
  isSwitchingNetworkViaHook,
938
1013
  switchNetworkError,
@@ -959,19 +1034,23 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
959
1034
  _openzeppelin_ui_utils.logger.info("NetworkSwitchManager", "Previous switch attempt concluded. Deferring to completion effect.");
960
1035
  return;
961
1036
  }
962
- if (adapter.networkConfig.id !== targetNetworkId) {
963
- completeOperation(`CRITICAL: Adapter (${adapter.networkConfig.id}) vs Target (${targetNetworkId}) mismatch. Operation halted.`, { notifyComplete: false });
1037
+ if (!networkCatalog.getNetworks().find((network) => network.id === targetNetworkId)) {
1038
+ completeOperation(`Target network ${targetNetworkId} is not present in the network catalog.`, { notifyComplete: false });
1039
+ return;
1040
+ }
1041
+ if (wallet.networkConfig.id !== targetNetworkId) {
1042
+ completeOperation(`CRITICAL: Wallet capability (${wallet.networkConfig.id}) vs Target (${targetNetworkId}) mismatch. Operation halted.`, { notifyComplete: false });
964
1043
  return;
965
1044
  }
966
1045
  if (!isConnected) {
967
1046
  completeOperation("Wallet not connected (derived status). Awaiting connection.", { notifyComplete: false });
968
1047
  return;
969
1048
  }
970
- if (!("chainId" in adapter.networkConfig)) {
1049
+ if (!("chainId" in wallet.networkConfig)) {
971
1050
  completeOperation("Network does not support chain switching (non-EVM). Operation complete (no-op).");
972
1051
  return;
973
1052
  }
974
- const targetChainToBeSwitchedTo = Number(adapter.networkConfig.chainId);
1053
+ const targetChainToBeSwitchedTo = Number(wallet.networkConfig.chainId);
975
1054
  if (currentChainIdFromHook === targetChainToBeSwitchedTo) {
976
1055
  completeOperation("Already on correct chain (derived status). Operation complete.");
977
1056
  return;
@@ -988,7 +1067,8 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
988
1067
  const timeoutId = setTimeout(performSwitchActual, 100);
989
1068
  return () => clearTimeout(timeoutId);
990
1069
  }, [
991
- adapter,
1070
+ wallet,
1071
+ networkCatalog,
992
1072
  targetNetworkId,
993
1073
  execSwitchNetwork,
994
1074
  isSwitchingNetworkViaHook,
@@ -1020,23 +1100,23 @@ const NetworkSwitchManager = ({ adapter, targetNetworkId, onNetworkSwitchComplet
1020
1100
  };
1021
1101
 
1022
1102
  //#endregion
1023
- exports.AdapterContext = AdapterContext;
1024
- exports.AdapterProvider = AdapterProvider;
1025
1103
  exports.AnalyticsContext = AnalyticsContext;
1026
1104
  exports.AnalyticsProvider = AnalyticsProvider;
1027
1105
  exports.NetworkSwitchManager = NetworkSwitchManager;
1106
+ exports.RuntimeContext = RuntimeContext;
1107
+ exports.RuntimeProvider = RuntimeProvider;
1028
1108
  exports.VERSION = VERSION;
1029
1109
  exports.WalletConnectionHeader = WalletConnectionHeader;
1030
1110
  exports.WalletConnectionUI = WalletConnectionUI;
1031
1111
  exports.WalletStateContext = WalletStateContext;
1032
1112
  exports.WalletStateProvider = WalletStateProvider;
1033
- exports.useAdapterContext = useAdapterContext;
1034
1113
  exports.useAnalytics = useAnalytics;
1035
1114
  exports.useDerivedAccountStatus = useDerivedAccountStatus;
1036
1115
  exports.useDerivedChainInfo = useDerivedChainInfo;
1037
1116
  exports.useDerivedConnectStatus = useDerivedConnectStatus;
1038
1117
  exports.useDerivedDisconnect = useDerivedDisconnect;
1039
1118
  exports.useDerivedSwitchChainStatus = useDerivedSwitchChainStatus;
1119
+ exports.useRuntimeContext = useRuntimeContext;
1040
1120
  exports.useWalletComponents = useWalletComponents;
1041
1121
  exports.useWalletReconnectionHandler = useWalletReconnectionHandler;
1042
1122
  exports.useWalletState = useWalletState;