@openzeppelin/contracts-ui-builder-react-core
Version:
Core React context providers and hooks for the OpenZeppelin Contracts UI Builder.
586 lines (572 loc) • 23.5 kB
JavaScript
// src/hooks/AdapterContext.tsx
import { createContext } from "react";
var AdapterContext = createContext(null);
// src/hooks/AdapterProvider.tsx
import { useCallback, useEffect, useMemo, useState } from "react";
import { logger } from "@openzeppelin/contracts-ui-builder-utils";
import { jsx } from "react/jsx-runtime";
function AdapterProvider({ children, resolveAdapter }) {
const [adapterRegistry, setAdapterRegistry] = useState({});
const [loadingNetworks, setLoadingNetworks] = useState(/* @__PURE__ */ new Set());
useEffect(() => {
const adapterCount = Object.keys(adapterRegistry).length;
if (adapterCount > 0) {
logger.info("AdapterProvider", `Registry contains ${adapterCount} adapters:`, {
networkIds: Object.keys(adapterRegistry),
loadingCount: loadingNetworks.size,
loadingNetworkIds: Array.from(loadingNetworks)
});
}
}, [adapterRegistry, loadingNetworks]);
const getAdapterForNetwork = useCallback(
(networkConfig) => {
if (!networkConfig) {
return { adapter: null, isLoading: false };
}
const networkId = networkConfig.id;
logger.debug("AdapterProvider", `Adapter requested for network ${networkId}`);
if (adapterRegistry[networkId]) {
logger.debug("AdapterProvider", `Using existing adapter for network ${networkId}`);
return {
adapter: adapterRegistry[networkId],
isLoading: false
};
}
if (loadingNetworks.has(networkId)) {
logger.debug("AdapterProvider", `Adapter for network ${networkId} is currently loading`);
return {
adapter: null,
isLoading: true
};
}
setLoadingNetworks((prev) => {
const newSet = new Set(prev);
newSet.add(networkId);
return newSet;
});
logger.info(
"AdapterProvider",
`Starting adapter initialization for network ${networkId} (${networkConfig.name})`
);
void resolveAdapter(networkConfig).then((adapter) => {
logger.info("AdapterProvider", `Adapter for network ${networkId} loaded successfully`, {
type: adapter.constructor.name,
objectId: Object.prototype.toString.call(adapter)
});
setAdapterRegistry((prev) => ({
...prev,
[networkId]: adapter
}));
setLoadingNetworks((prev) => {
const newSet = new Set(prev);
newSet.delete(networkId);
return newSet;
});
}).catch((error) => {
logger.error("AdapterProvider", `Error loading adapter for network ${networkId}:`, error);
setLoadingNetworks((prev) => {
const newSet = new Set(prev);
newSet.delete(networkId);
return newSet;
});
});
return {
adapter: null,
isLoading: true
};
},
[adapterRegistry, loadingNetworks, resolveAdapter]
);
const contextValue = useMemo(
() => ({
getAdapterForNetwork
}),
[getAdapterForNetwork]
);
return /* @__PURE__ */ jsx(AdapterContext.Provider, { value: contextValue, children });
}
// src/hooks/WalletStateContext.tsx
import React, { createContext as createContext2 } from "react";
var WalletStateContext = createContext2(void 0);
function useWalletState() {
const context = React.useContext(WalletStateContext);
if (context === void 0) {
throw new Error("useWalletState must be used within a WalletStateProvider");
}
return context;
}
// src/hooks/WalletStateProvider.tsx
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
import { logger as logger2 } from "@openzeppelin/contracts-ui-builder-utils";
// src/hooks/useAdapterContext.ts
import { useContext } from "react";
function useAdapterContext() {
const context = useContext(AdapterContext);
if (!context) {
throw new Error("useAdapterContext must be used within an AdapterProvider");
}
return context;
}
// src/hooks/WalletStateProvider.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
async function configureAdapterUiKit(adapter, loadConfigModule, programmaticOverrides = {}) {
try {
if (typeof adapter.configureUiKit === "function") {
logger2.info(
"[WSP configureAdapterUiKit] Calling configureUiKit for adapter:",
adapter?.networkConfig?.id
);
await adapter.configureUiKit(programmaticOverrides, {
loadUiKitNativeConfig: loadConfigModule
});
logger2.info(
"[WSP configureAdapterUiKit] configureUiKit completed for adapter:",
adapter?.networkConfig?.id
);
}
const providerComponent = adapter.getEcosystemReactUiContextProvider?.() || null;
const hooks = adapter.getEcosystemReactHooks?.() || null;
logger2.info("[WSP configureAdapterUiKit]", "UI provider and hooks retrieved successfully.");
return { providerComponent, hooks };
} catch (error) {
logger2.error("[WSP configureAdapterUiKit]", "Error during adapter UI setup:", error);
throw error;
}
}
function WalletStateProvider({
children,
initialNetworkId = null,
getNetworkConfigById,
loadConfigModule
}) {
const [currentGlobalNetworkId, setCurrentGlobalNetworkIdState] = useState2(
initialNetworkId
);
const [currentGlobalNetworkConfig, setCurrentGlobalNetworkConfig] = useState2(null);
const [globalActiveAdapter, setGlobalActiveAdapter] = useState2(null);
const [isGlobalAdapterLoading, setIsGlobalAdapterLoading] = useState2(false);
const [walletFacadeHooks, setWalletFacadeHooks] = useState2(
null
);
const [AdapterUiContextProviderToRender, setAdapterUiContextProviderToRender] = useState2(null);
const [uiKitConfigVersion, setUiKitConfigVersion] = useState2(0);
const [programmaticUiKitConfig, setProgrammaticUiKitConfig] = useState2(void 0);
const { getAdapterForNetwork } = useAdapterContext();
useEffect2(() => {
const abortController = new AbortController();
async function fetchNetworkConfig() {
if (!currentGlobalNetworkId) {
if (!abortController.signal.aborted) {
setCurrentGlobalNetworkConfig(null);
}
return;
}
try {
const config = await Promise.resolve(getNetworkConfigById(currentGlobalNetworkId));
if (!abortController.signal.aborted) {
setCurrentGlobalNetworkConfig(config || null);
}
} catch (error) {
if (!abortController.signal.aborted) {
logger2.error("[WSP fetchNetworkConfig]", "Failed to fetch network config:", error);
setCurrentGlobalNetworkConfig(null);
}
}
}
void fetchNetworkConfig();
return () => abortController.abort();
}, [currentGlobalNetworkId, getNetworkConfigById]);
useEffect2(() => {
const abortController = new AbortController();
async function loadAdapterAndConfigureUi() {
if (!currentGlobalNetworkConfig) {
if (!abortController.signal.aborted) {
setGlobalActiveAdapter(null);
setIsGlobalAdapterLoading(false);
setAdapterUiContextProviderToRender(null);
setWalletFacadeHooks(null);
}
return;
}
const { adapter: newAdapter, isLoading: newIsLoading } = getAdapterForNetwork(
currentGlobalNetworkConfig
);
if (abortController.signal.aborted) return;
setGlobalActiveAdapter(newAdapter);
setIsGlobalAdapterLoading(newIsLoading);
if (newAdapter && !newIsLoading) {
try {
const { providerComponent, hooks } = await configureAdapterUiKit(
newAdapter,
loadConfigModule,
programmaticUiKitConfig
);
if (!abortController.signal.aborted) {
setAdapterUiContextProviderToRender(() => providerComponent);
setWalletFacadeHooks(hooks);
}
} catch (error) {
if (!abortController.signal.aborted) {
logger2.error(
"[WSP loadAdapterAndConfigureUi]",
"Error during adapter UI setup:",
error
);
setAdapterUiContextProviderToRender(null);
setWalletFacadeHooks(null);
}
}
} else if (!newAdapter && !newIsLoading) {
if (!abortController.signal.aborted) {
setAdapterUiContextProviderToRender(null);
setWalletFacadeHooks(null);
}
}
}
void loadAdapterAndConfigureUi();
return () => abortController.abort();
}, [
currentGlobalNetworkConfig,
getAdapterForNetwork,
loadConfigModule,
uiKitConfigVersion,
programmaticUiKitConfig
]);
const setActiveNetworkIdCallback = useCallback2((networkId) => {
logger2.info("WalletStateProvider", `Setting global network ID to: ${networkId}`);
setCurrentGlobalNetworkIdState(networkId);
if (!networkId) {
setCurrentGlobalNetworkConfig(null);
setGlobalActiveAdapter(null);
setIsGlobalAdapterLoading(false);
setWalletFacadeHooks(null);
}
}, []);
const reconfigureActiveAdapterUiKit = useCallback2(
(uiKitConfig) => {
logger2.info(
"WalletStateProvider",
"Explicitly triggering UI kit re-configuration by bumping version.",
uiKitConfig
);
setProgrammaticUiKitConfig(uiKitConfig);
setUiKitConfigVersion((v) => v + 1);
},
[setProgrammaticUiKitConfig, setUiKitConfigVersion]
);
const contextValue = useMemo2(
() => ({
activeNetworkId: currentGlobalNetworkId,
setActiveNetworkId: setActiveNetworkIdCallback,
activeNetworkConfig: currentGlobalNetworkConfig,
activeAdapter: globalActiveAdapter,
isAdapterLoading: isGlobalAdapterLoading,
walletFacadeHooks,
reconfigureActiveAdapterUiKit
}),
[
currentGlobalNetworkId,
setActiveNetworkIdCallback,
currentGlobalNetworkConfig,
globalActiveAdapter,
isGlobalAdapterLoading,
walletFacadeHooks,
reconfigureActiveAdapterUiKit
]
);
const ActualProviderToRender = AdapterUiContextProviderToRender;
let childrenToRender;
if (ActualProviderToRender) {
const key = globalActiveAdapter?.lastFullUiKitConfiguration?.kitName;
logger2.info(
"[WSP RENDER]",
"Rendering adapter-provided UI context provider:",
ActualProviderToRender.displayName || ActualProviderToRender.name || "UnknownComponent"
);
childrenToRender = /* @__PURE__ */ jsx2(ActualProviderToRender, { children }, key);
} else {
logger2.info(
"[WSP RENDER]",
"No adapter UI context provider to render. Rendering direct children."
);
childrenToRender = children;
}
return /* @__PURE__ */ jsx2(WalletStateContext.Provider, { value: contextValue, children: childrenToRender });
}
// src/hooks/useDerivedAccountStatus.ts
import { isRecordWithProperties } from "@openzeppelin/contracts-ui-builder-utils";
var defaultAccountStatus = {
isConnected: false,
address: void 0,
chainId: void 0
};
function useDerivedAccountStatus() {
const { walletFacadeHooks } = useWalletState();
const accountHookOutput = walletFacadeHooks?.useAccount ? walletFacadeHooks.useAccount() : void 0;
if (isRecordWithProperties(accountHookOutput)) {
const isConnected = "isConnected" in accountHookOutput && typeof accountHookOutput.isConnected === "boolean" ? accountHookOutput.isConnected : defaultAccountStatus.isConnected;
const address = "address" in accountHookOutput && typeof accountHookOutput.address === "string" ? accountHookOutput.address : defaultAccountStatus.address;
const chainId = "chainId" in accountHookOutput && typeof accountHookOutput.chainId === "number" ? accountHookOutput.chainId : defaultAccountStatus.chainId;
return { isConnected, address, chainId };
}
return defaultAccountStatus;
}
// src/hooks/useDerivedSwitchChainStatus.ts
import { isRecordWithProperties as isRecordWithProperties2 } from "@openzeppelin/contracts-ui-builder-utils";
var defaultSwitchChainStatus = {
switchChain: void 0,
isSwitching: false,
error: null
};
function useDerivedSwitchChainStatus() {
const { walletFacadeHooks } = useWalletState();
const switchChainHookOutput = walletFacadeHooks?.useSwitchChain ? walletFacadeHooks.useSwitchChain() : void 0;
if (isRecordWithProperties2(switchChainHookOutput)) {
const execSwitchFn = "switchChain" in switchChainHookOutput && typeof switchChainHookOutput.switchChain === "function" ? switchChainHookOutput.switchChain : defaultSwitchChainStatus.switchChain;
const isPending = "isPending" in switchChainHookOutput && typeof switchChainHookOutput.isPending === "boolean" ? switchChainHookOutput.isPending : defaultSwitchChainStatus.isSwitching;
const err = "error" in switchChainHookOutput && switchChainHookOutput.error instanceof Error ? switchChainHookOutput.error : defaultSwitchChainStatus.error;
return { switchChain: execSwitchFn, isSwitching: isPending, error: err };
}
return defaultSwitchChainStatus;
}
// src/hooks/useDerivedChainInfo.ts
import { logger as logger3 } from "@openzeppelin/contracts-ui-builder-utils";
var defaultChainInfo = {
currentChainId: void 0,
availableChains: []
};
function useDerivedChainInfo() {
const { walletFacadeHooks } = useWalletState();
let chainIdToReturn = defaultChainInfo.currentChainId;
const chainIdHookOutput = walletFacadeHooks?.useChainId ? walletFacadeHooks.useChainId() : void 0;
if (typeof chainIdHookOutput === "number") {
chainIdToReturn = chainIdHookOutput;
} else if (chainIdHookOutput !== void 0) {
logger3.warn(
"useDerivedChainInfo",
"useChainId facade hook returned non-numeric value:",
chainIdHookOutput
);
}
let chainsToReturn = defaultChainInfo.availableChains;
const chainsHookOutput = walletFacadeHooks?.useChains ? walletFacadeHooks.useChains() : void 0;
if (Array.isArray(chainsHookOutput)) {
chainsToReturn = chainsHookOutput;
} else if (chainsHookOutput !== void 0) {
logger3.warn(
"useDerivedChainInfo",
"useChains facade hook returned non-array value:",
chainsHookOutput
);
}
return { currentChainId: chainIdToReturn, availableChains: chainsToReturn };
}
// src/hooks/useDerivedConnectStatus.ts
import { isRecordWithProperties as isRecordWithProperties3 } from "@openzeppelin/contracts-ui-builder-utils";
var defaultConnectStatus = {
connect: void 0,
connectors: [],
isConnecting: false,
error: null,
pendingConnector: void 0
};
function useDerivedConnectStatus() {
const { walletFacadeHooks } = useWalletState();
const connectHookOutput = walletFacadeHooks?.useConnect ? walletFacadeHooks.useConnect() : void 0;
if (isRecordWithProperties3(connectHookOutput)) {
const connectFn = "connect" in connectHookOutput && typeof connectHookOutput.connect === "function" ? connectHookOutput.connect : defaultConnectStatus.connect;
const conns = "connectors" in connectHookOutput && Array.isArray(connectHookOutput.connectors) ? connectHookOutput.connectors : defaultConnectStatus.connectors;
const isPending = "isPending" in connectHookOutput && typeof connectHookOutput.isPending === "boolean" ? connectHookOutput.isPending : "isLoading" in connectHookOutput && typeof connectHookOutput.isLoading === "boolean" ? connectHookOutput.isLoading : defaultConnectStatus.isConnecting;
const err = "error" in connectHookOutput && connectHookOutput.error instanceof Error ? connectHookOutput.error : defaultConnectStatus.error;
const pendingConn = "pendingConnector" in connectHookOutput && typeof connectHookOutput.pendingConnector === "object" ? connectHookOutput.pendingConnector : defaultConnectStatus.pendingConnector;
return {
connect: connectFn,
connectors: conns,
isConnecting: isPending,
error: err,
pendingConnector: pendingConn
};
}
return defaultConnectStatus;
}
// src/hooks/useDerivedDisconnect.ts
import { isRecordWithProperties as isRecordWithProperties4 } from "@openzeppelin/contracts-ui-builder-utils";
var defaultDisconnectStatus = {
disconnect: void 0,
isDisconnecting: false,
error: null
};
function useDerivedDisconnect() {
const { walletFacadeHooks } = useWalletState();
const disconnectHookOutput = walletFacadeHooks?.useDisconnect ? walletFacadeHooks.useDisconnect() : void 0;
if (isRecordWithProperties4(disconnectHookOutput)) {
const disconnectFn = "disconnect" in disconnectHookOutput && typeof disconnectHookOutput.disconnect === "function" ? disconnectHookOutput.disconnect : defaultDisconnectStatus.disconnect;
const isPending = "isPending" in disconnectHookOutput && typeof disconnectHookOutput.isPending === "boolean" ? disconnectHookOutput.isPending : "isLoading" in disconnectHookOutput && typeof disconnectHookOutput.isLoading === "boolean" ? disconnectHookOutput.isLoading : defaultDisconnectStatus.isDisconnecting;
const err = "error" in disconnectHookOutput && disconnectHookOutput.error instanceof Error ? disconnectHookOutput.error : defaultDisconnectStatus.error;
return { disconnect: disconnectFn, isDisconnecting: isPending, error: err };
}
return defaultDisconnectStatus;
}
// src/components/WalletConnectionHeader.tsx
import { useEffect as useEffect4 } from "react";
import { logger as logger5 } from "@openzeppelin/contracts-ui-builder-utils";
// src/components/WalletConnectionUI.tsx
import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
import { Button } from "@openzeppelin/contracts-ui-builder-ui";
import { cn, logger as logger4 } from "@openzeppelin/contracts-ui-builder-utils";
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
var WalletConnectionUI = ({ className }) => {
const [isError, setIsError] = useState3(false);
const { activeAdapter, walletFacadeHooks } = useWalletState();
useEffect3(() => {
const handleError = () => {
setIsError(true);
};
window.addEventListener("error", handleError);
return () => {
window.removeEventListener("error", handleError);
};
}, []);
useEffect3(() => {
logger4.debug("WalletConnectionUI", "[Debug] State from useWalletState:", {
adapterId: activeAdapter?.networkConfig.id,
hasFacadeHooks: !!walletFacadeHooks
});
}, [activeAdapter, walletFacadeHooks]);
const walletComponents = useMemo3(() => {
if (!activeAdapter || typeof activeAdapter.getEcosystemWalletComponents !== "function") {
logger4.debug(
"WalletConnectionUI",
"[Debug] No activeAdapter or getEcosystemWalletComponents method, returning null."
);
return null;
}
try {
const components = activeAdapter.getEcosystemWalletComponents();
logger4.debug("WalletConnectionUI", "[Debug] walletComponents from adapter:", components);
return components;
} catch (error) {
logger4.error("WalletConnectionUI", "[Debug] Error getting wallet components:", error);
setIsError(true);
return null;
}
}, [activeAdapter]);
if (!walletComponents) {
logger4.debug(
"WalletConnectionUI",
"[Debug] getEcosystemWalletComponents returned null/undefined, rendering null."
);
return null;
}
logger4.debug("WalletConnectionUI", "Rendering wallet components:", {
hasConnectButton: !!walletComponents.ConnectButton,
hasAccountDisplay: !!walletComponents.AccountDisplay,
hasNetworkSwitcher: !!walletComponents.NetworkSwitcher
});
const { ConnectButton, AccountDisplay, NetworkSwitcher } = walletComponents;
if (isError) {
return /* @__PURE__ */ jsx3("div", { className: cn("flex items-center gap-4", className), children: /* @__PURE__ */ jsx3(Button, { variant: "destructive", size: "sm", onClick: () => window.location.reload(), children: "Wallet Error - Retry" }) });
}
if (!walletComponents) {
logger4.debug(
"WalletConnectionUI",
"[Debug] walletComponents is null before rendering, rendering null."
);
return null;
}
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-4", className), children: [
NetworkSwitcher && /* @__PURE__ */ jsx3(NetworkSwitcher, {}),
AccountDisplay && /* @__PURE__ */ jsx3(AccountDisplay, {}),
ConnectButton && /* @__PURE__ */ jsx3(ConnectButton, {})
] });
};
// src/components/WalletConnectionHeader.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
var WalletConnectionHeader = () => {
const { isAdapterLoading, activeAdapter } = useWalletState();
useEffect4(() => {
logger5.debug("WalletConnectionHeader", "[Debug] State from useWalletState:", {
adapterPresent: !!activeAdapter,
adapterNetwork: activeAdapter?.networkConfig.id,
isLoading: isAdapterLoading
});
}, [activeAdapter, isAdapterLoading]);
if (isAdapterLoading) {
logger5.debug("WalletConnectionHeader", "[Debug] Adapter loading, showing skeleton.");
return /* @__PURE__ */ jsx4("div", { className: "h-9 w-28 animate-pulse rounded bg-muted" });
}
return /* @__PURE__ */ jsx4(WalletConnectionUI, {});
};
// src/components/WalletConnectionWithSettings.tsx
import { Settings } from "lucide-react";
import { useCallback as useCallback3, useEffect as useEffect5, useState as useState4 } from "react";
import {
Button as Button2,
NetworkSettingsDialog,
useNetworkErrors
} from "@openzeppelin/contracts-ui-builder-ui";
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
var WalletConnectionWithSettings = () => {
const { isAdapterLoading, activeAdapter, activeNetworkConfig } = useWalletState();
const { setOpenNetworkSettingsHandler } = useNetworkErrors();
const [showNetworkSettings, setShowNetworkSettings] = useState4(false);
const [defaultTab, setDefaultTab] = useState4("rpc");
const openNetworkSettings = useCallback3(
(networkId, tab = "rpc") => {
if (activeNetworkConfig && networkId === activeNetworkConfig.id) {
setDefaultTab(tab);
setShowNetworkSettings(true);
}
},
[activeNetworkConfig]
);
useEffect5(() => {
setOpenNetworkSettingsHandler(openNetworkSettings);
}, [openNetworkSettings, setOpenNetworkSettingsHandler]);
if (isAdapterLoading) {
return /* @__PURE__ */ jsx5("div", { className: "h-9 w-28 animate-pulse rounded bg-muted" });
}
return /* @__PURE__ */ jsxs2(Fragment, { children: [
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx5(WalletConnectionUI, {}),
activeAdapter && activeNetworkConfig && /* @__PURE__ */ jsx5(
Button2,
{
variant: "ghost",
size: "icon",
className: "h-9 w-9",
title: "Network Settings",
onClick: () => setShowNetworkSettings(true),
children: /* @__PURE__ */ jsx5(Settings, { className: "h-4 w-4" })
}
)
] }),
/* @__PURE__ */ jsx5(
NetworkSettingsDialog,
{
isOpen: showNetworkSettings,
onOpenChange: setShowNetworkSettings,
networkConfig: activeNetworkConfig,
adapter: activeAdapter,
defaultTab
}
)
] });
};
export {
AdapterContext,
AdapterProvider,
WalletConnectionHeader,
WalletConnectionUI,
WalletConnectionWithSettings,
WalletStateContext,
WalletStateProvider,
useAdapterContext,
useDerivedAccountStatus,
useDerivedChainInfo,
useDerivedConnectStatus,
useDerivedDisconnect,
useDerivedSwitchChainStatus,
useWalletState
};
//# sourceMappingURL=index.js.map