@pyratzlabs/react-fhevm-utils 3.5.7 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,18 @@
1
1
  import React, { ReactNode } from "react";
2
+ import { FhevmInstance, FhevmInstanceConfig, FhevmStatus } from "../../lib/fhevmManager";
2
3
  import { Network } from "../../types/fhevmConfig";
3
- import { FhevmInstance, FhevmInstanceConfig } from "@zama-fhe/relayer-sdk/bundle";
4
4
  interface ProviderProps {
5
5
  children: ReactNode;
6
6
  network?: Network;
7
7
  config?: Partial<FhevmInstanceConfig>;
8
+ enabled?: boolean;
8
9
  }
9
10
  interface ContextProps {
10
11
  instance?: FhevmInstance;
12
+ status: FhevmStatus;
11
13
  config?: Partial<FhevmInstanceConfig>;
12
14
  }
13
15
  export declare const FhevmContext: React.Context<ContextProps>;
14
- declare const FhevmProvider: ({ children, network, config, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
16
+ declare const FhevmProvider: ({ children, network, config, enabled, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
15
17
  export declare const useFhevmContext: () => ContextProps;
16
18
  export default FhevmProvider;
@@ -8,27 +8,32 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
- import { createContext, useContext, useEffect, useState, } from "react";
12
- import { getOrCreateFhevmInstance } from "../../lib/fhevm";
11
+ import { createContext, useContext, useEffect, useSyncExternalStore, } from "react";
12
+ import { fhevmManager, } from "../../lib/fhevmManager";
13
13
  export const FhevmContext = createContext({
14
14
  instance: undefined,
15
+ status: "uninitialized",
15
16
  config: undefined,
16
17
  });
17
- const FhevmProvider = ({ children, network = "sepolia", config, }) => {
18
- const [instance, setInstance] = useState();
18
+ const FhevmProvider = ({ children, network = "sepolia", config, enabled = true, }) => {
19
+ const status = useSyncExternalStore(fhevmManager.subscribe, fhevmManager.getStatus, fhevmManager.getStatus);
19
20
  useEffect(() => {
20
- const initFhevm = (config, network) => __awaiter(void 0, void 0, void 0, function* () {
21
+ if (!enabled)
22
+ return;
23
+ const initFhevm = () => __awaiter(void 0, void 0, void 0, function* () {
21
24
  try {
22
- const instance = yield getOrCreateFhevmInstance(network, config);
23
- setInstance(instance);
25
+ yield fhevmManager.load();
26
+ yield fhevmManager.initSDK();
27
+ yield fhevmManager.createInstance({ network, customConfig: config });
24
28
  }
25
29
  catch (error) {
26
30
  throw new Error(`Failed to initialize FHEVM: ${error}. With config: ${JSON.stringify(config)}`);
27
31
  }
28
32
  });
29
- initFhevm(config, network);
30
- }, [config, network]);
31
- return (_jsx(FhevmContext.Provider, { value: { instance, config }, children: children }));
33
+ initFhevm();
34
+ }, [config, network, enabled]);
35
+ const instance = status === "ready" ? fhevmManager.getInstance() : undefined;
36
+ return (_jsx(FhevmContext.Provider, { value: { instance, status, config }, children: children }));
32
37
  };
33
38
  export const useFhevmContext = () => useContext(FhevmContext);
34
39
  export default FhevmProvider;
@@ -1,3 +1,4 @@
1
1
  export declare const useFhevmInstance: () => {
2
2
  instance: import("@zama-fhe/relayer-sdk/web").FhevmInstance | undefined;
3
+ status: import("../../lib/fhevmManager").FhevmStatus;
3
4
  };
@@ -1,5 +1,5 @@
1
1
  import { useFhevmContext } from "../context/FhevmProvider";
2
2
  export const useFhevmInstance = () => {
3
- const { instance } = useFhevmContext();
4
- return { instance };
3
+ const { instance, status } = useFhevmContext();
4
+ return { instance, status };
5
5
  };
@@ -0,0 +1,68 @@
1
+ import type { FhevmInstanceConfig, FhevmInstance } from "@zama-fhe/relayer-sdk/bundle";
2
+ import { Network } from "../types/fhevmConfig";
3
+ export type { FhevmInstance, FhevmInstanceConfig };
4
+ export type FhevmStatus = "uninitialized" | "creating" | "ready" | "error" | "not-supported";
5
+ export type RelayerSDK = {
6
+ initSDK: (options?: {
7
+ tfheParams?: unknown;
8
+ kmsParams?: unknown;
9
+ thread?: number;
10
+ }) => Promise<boolean>;
11
+ createInstance: (config: FhevmInstanceConfig) => Promise<FhevmInstance>;
12
+ SepoliaConfig: FhevmInstanceConfig;
13
+ MainnetConfig: FhevmInstanceConfig;
14
+ };
15
+ export type CreateInstanceOptions = {
16
+ network?: Network;
17
+ customConfig?: Partial<FhevmInstanceConfig>;
18
+ };
19
+ declare class FhevmManager {
20
+ private sdk;
21
+ private instance;
22
+ private loadPromise;
23
+ private instancePromise;
24
+ private _status;
25
+ private listeners;
26
+ private currentNetwork;
27
+ get status(): FhevmStatus;
28
+ get isLoaded(): boolean;
29
+ /**
30
+ * Subscribe to status changes. Returns an unsubscribe function.
31
+ * Compatible with React's useSyncExternalStore.
32
+ */
33
+ subscribe: (listener: () => void) => (() => void);
34
+ /**
35
+ * Get current status. For useSyncExternalStore.
36
+ */
37
+ getStatus: () => FhevmStatus;
38
+ /**
39
+ * Set status and notify listeners.
40
+ * External callers should only use 'uninitialized' or 'not-supported'.
41
+ * Setting 'uninitialized' also clears the instance.
42
+ */
43
+ setStatus: (status: FhevmStatus) => void;
44
+ private isBrowser;
45
+ private isValidSDK;
46
+ private getNetworkConfig;
47
+ /**
48
+ * Loads the relayer SDK script into the page.
49
+ * Safe to call multiple times - subsequent calls return the same promise.
50
+ */
51
+ load: () => Promise<void>;
52
+ /**
53
+ * Initializes the SDK after loading.
54
+ * Must be called after load() completes.
55
+ */
56
+ initSDK: () => Promise<boolean>;
57
+ /**
58
+ * Creates and initializes the FHEVM instance.
59
+ * Safe to call multiple times - returns existing promise if already creating.
60
+ */
61
+ createInstance: (options?: CreateInstanceOptions) => Promise<void>;
62
+ /**
63
+ * Gets the ready FHEVM instance.
64
+ * @throws Error if instance is not ready
65
+ */
66
+ getInstance: () => FhevmInstance;
67
+ }
68
+ export declare const fhevmManager: FhevmManager;
@@ -0,0 +1,231 @@
1
+ "use client";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { mainnet, sepolia } from "viem/chains";
12
+ import { ZAMA_RELAYER_CDN_URL } from "../config";
13
+ // ============================================================================
14
+ // FhevmManager Class
15
+ // ============================================================================
16
+ class FhevmManager {
17
+ constructor() {
18
+ this._status = "uninitialized";
19
+ this.listeners = new Set();
20
+ this.currentNetwork = "sepolia";
21
+ // ==========================================================================
22
+ // Subscription (for React's useSyncExternalStore)
23
+ // ==========================================================================
24
+ /**
25
+ * Subscribe to status changes. Returns an unsubscribe function.
26
+ * Compatible with React's useSyncExternalStore.
27
+ */
28
+ this.subscribe = (listener) => {
29
+ this.listeners.add(listener);
30
+ return () => this.listeners.delete(listener);
31
+ };
32
+ /**
33
+ * Get current status. For useSyncExternalStore.
34
+ */
35
+ this.getStatus = () => {
36
+ return this._status;
37
+ };
38
+ /**
39
+ * Set status and notify listeners.
40
+ * External callers should only use 'uninitialized' or 'not-supported'.
41
+ * Setting 'uninitialized' also clears the instance.
42
+ */
43
+ this.setStatus = (status) => {
44
+ if (status === "uninitialized") {
45
+ this.instance = undefined;
46
+ this.instancePromise = undefined;
47
+ }
48
+ if (this._status !== status) {
49
+ this._status = status;
50
+ this.listeners.forEach((listener) => listener());
51
+ }
52
+ };
53
+ // ==========================================================================
54
+ // Public Methods
55
+ // ==========================================================================
56
+ /**
57
+ * Loads the relayer SDK script into the page.
58
+ * Safe to call multiple times - subsequent calls return the same promise.
59
+ */
60
+ this.load = () => __awaiter(this, void 0, void 0, function* () {
61
+ if (!this.isBrowser()) {
62
+ throw new Error("Relayer SDK can only be used in the browser");
63
+ }
64
+ // Already loaded
65
+ if (this.isLoaded) {
66
+ return;
67
+ }
68
+ // Return existing promise if already loading
69
+ if (this.loadPromise) {
70
+ return this.loadPromise;
71
+ }
72
+ // Check if SDK is already on window (loaded by another source)
73
+ const windowWithSDK = window;
74
+ if (this.isValidSDK(windowWithSDK.relayerSDK)) {
75
+ this.sdk = windowWithSDK.relayerSDK;
76
+ return;
77
+ }
78
+ // Check if script tag already exists
79
+ const existingScript = document.querySelector(`script[src="${ZAMA_RELAYER_CDN_URL}"]`);
80
+ if (existingScript) {
81
+ // Wait for it to load and populate window
82
+ this.loadPromise = new Promise((resolve, reject) => {
83
+ const checkInterval = setInterval(() => {
84
+ if (this.isValidSDK(windowWithSDK.relayerSDK)) {
85
+ clearInterval(checkInterval);
86
+ this.sdk = windowWithSDK.relayerSDK;
87
+ resolve();
88
+ }
89
+ }, 50);
90
+ // Timeout after 10 seconds
91
+ setTimeout(() => {
92
+ clearInterval(checkInterval);
93
+ this.loadPromise = undefined;
94
+ reject(new Error("Timeout waiting for existing Relayer SDK script to load"));
95
+ }, 10000);
96
+ });
97
+ return this.loadPromise;
98
+ }
99
+ // Load the script
100
+ this.loadPromise = new Promise((resolve, reject) => {
101
+ const script = document.createElement("script");
102
+ script.src = ZAMA_RELAYER_CDN_URL;
103
+ script.type = "text/javascript";
104
+ script.async = true;
105
+ script.onload = () => {
106
+ const windowWithSDK = window;
107
+ if (this.isValidSDK(windowWithSDK.relayerSDK)) {
108
+ this.sdk = windowWithSDK.relayerSDK;
109
+ resolve();
110
+ }
111
+ else {
112
+ this.loadPromise = undefined;
113
+ reject(new Error("Relayer SDK script loaded but SDK is invalid"));
114
+ }
115
+ };
116
+ script.onerror = () => {
117
+ this.loadPromise = undefined;
118
+ reject(new Error(`Failed to load Relayer SDK from ${ZAMA_RELAYER_CDN_URL}`));
119
+ };
120
+ document.head.appendChild(script);
121
+ });
122
+ return this.loadPromise;
123
+ });
124
+ /**
125
+ * Initializes the SDK after loading.
126
+ * Must be called after load() completes.
127
+ */
128
+ this.initSDK = () => __awaiter(this, void 0, void 0, function* () {
129
+ if (!this.sdk) {
130
+ throw new Error("SDK not loaded. Call load() first.");
131
+ }
132
+ return this.sdk.initSDK();
133
+ });
134
+ /**
135
+ * Creates and initializes the FHEVM instance.
136
+ * Safe to call multiple times - returns existing promise if already creating.
137
+ */
138
+ this.createInstance = (...args_1) => __awaiter(this, [...args_1], void 0, function* (options = {}) {
139
+ var _a;
140
+ const network = (_a = options.network) !== null && _a !== void 0 ? _a : "sepolia";
141
+ // Return early if already created for same network
142
+ if (this.instancePromise && network === this.currentNetwork) {
143
+ yield this.instancePromise;
144
+ return;
145
+ }
146
+ if (!this.sdk) {
147
+ throw new Error("SDK not loaded. Call load() and initSDK() first.");
148
+ }
149
+ this.currentNetwork = network;
150
+ const baseConfig = this.getNetworkConfig(network);
151
+ try {
152
+ this.setStatus("creating");
153
+ const config = Object.assign(Object.assign({}, baseConfig), options.customConfig);
154
+ this.instancePromise = this.sdk.createInstance(config);
155
+ this.instance = yield this.instancePromise;
156
+ this.setStatus("ready");
157
+ }
158
+ catch (error) {
159
+ console.error("FHEVM instance creation failed:", error);
160
+ this.setStatus("error");
161
+ this.instancePromise = undefined;
162
+ throw error;
163
+ }
164
+ });
165
+ /**
166
+ * Gets the ready FHEVM instance.
167
+ * @throws Error if instance is not ready
168
+ */
169
+ this.getInstance = () => {
170
+ if (!this.instance) {
171
+ throw new Error("FHEVM instance not created. Call createInstance() first or wait for initialization.");
172
+ }
173
+ if (this._status === "creating") {
174
+ throw new Error("FHEVM instance is still being created. Please wait for initialization.");
175
+ }
176
+ if (this._status === "error") {
177
+ throw new Error("FHEVM instance creation failed. Check previous errors or reinitialize.");
178
+ }
179
+ if (this._status === "not-supported") {
180
+ throw new Error(`FHEVM is not supported on the current network. Please switch to a supported network.`);
181
+ }
182
+ if (this._status !== "ready") {
183
+ throw new Error(`FHEVM instance is not ready. Current status: ${this._status}`);
184
+ }
185
+ return this.instance;
186
+ };
187
+ }
188
+ // ==========================================================================
189
+ // Getters
190
+ // ==========================================================================
191
+ get status() {
192
+ return this._status;
193
+ }
194
+ get isLoaded() {
195
+ return this.sdk !== undefined && this.isValidSDK(this.sdk);
196
+ }
197
+ // ==========================================================================
198
+ // Private Helpers
199
+ // ==========================================================================
200
+ isBrowser() {
201
+ return typeof window === "object";
202
+ }
203
+ isValidSDK(sdk) {
204
+ if (!sdk || typeof sdk !== "object")
205
+ return false;
206
+ const relayerSDK = sdk;
207
+ return (typeof relayerSDK.initSDK === "function" &&
208
+ typeof relayerSDK.createInstance === "function" &&
209
+ typeof relayerSDK.SepoliaConfig === "object" &&
210
+ typeof relayerSDK.MainnetConfig === "object");
211
+ }
212
+ getNetworkConfig(network) {
213
+ if (!this.sdk) {
214
+ throw new Error("SDK not loaded. Call load() first.");
215
+ }
216
+ const configByChainId = {
217
+ [sepolia.id]: this.sdk.SepoliaConfig,
218
+ [mainnet.id]: this.sdk.MainnetConfig,
219
+ };
220
+ const chainId = network === "mainnet" ? mainnet.id : sepolia.id;
221
+ const config = configByChainId[chainId];
222
+ if (!config) {
223
+ throw new Error(`No relayer SDK config found for network: ${network}`);
224
+ }
225
+ return config;
226
+ }
227
+ }
228
+ // ============================================================================
229
+ // Singleton Export
230
+ // ============================================================================
231
+ export const fhevmManager = new FhevmManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyratzlabs/react-fhevm-utils",
3
- "version": "3.5.7",
3
+ "version": "3.6.0",
4
4
  "description": "React hooks and utilities for Fhevmjs",
5
5
  "main": "dist/index.js",
6
6
  "files": [