@zama-fhe/react-sdk 3.0.0-alpha.24 → 3.0.0-alpha.26

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/README.md CHANGED
@@ -1,959 +1,135 @@
1
1
  # @zama-fhe/react-sdk
2
2
 
3
- React hooks for confidential contract operations, built on [React Query](https://tanstack.com/query). Provides declarative, declarative hooks for session authorization, balances, confidential transfers, shielding, unshielding, and decryption — so you never deal with raw FHE operations in your components.
3
+ React bindings for the Zama SDK. Use this package to wire confidential smart contract operations into a React app through `ZamaProvider` and hooks for authorization, encryption, balances, transfers, shielding, unshielding, approvals, and delegation.
4
4
 
5
5
  ## Installation
6
6
 
7
+ Install the React package, the core SDK, and React Query:
8
+
7
9
  ```bash
8
- pnpm add @zama-fhe/react-sdk @tanstack/react-query
10
+ pnpm add @zama-fhe/react-sdk @zama-fhe/sdk @tanstack/react-query
9
11
  # or
10
- npm install @zama-fhe/react-sdk @tanstack/react-query
12
+ npm install @zama-fhe/react-sdk @zama-fhe/sdk @tanstack/react-query
11
13
  # or
12
- yarn add @zama-fhe/react-sdk @tanstack/react-query
14
+ yarn add @zama-fhe/react-sdk @zama-fhe/sdk @tanstack/react-query
13
15
  ```
14
16
 
15
- `@zama-fhe/sdk` is included as a direct dependency no need to install it separately.
16
-
17
- ### Peer dependencies
17
+ If you follow the viem example below, add `viem` too:
18
18
 
19
- | Package | Version | Required? |
20
- | ----------------------- | ------- | --------------------------------------------- |
21
- | `react` | >= 18 | Yes |
22
- | `@tanstack/react-query` | >= 5 | Yes |
23
- | `viem` | >= 2 | Optional — for `/viem` and `/wagmi` sub-paths |
24
- | `ethers` | >= 6 | Optional — for `/ethers` sub-path |
25
- | `wagmi` | >= 2 | Optional — for `/wagmi` sub-path |
19
+ ```bash
20
+ pnpm add viem
21
+ # or
22
+ npm install viem
23
+ # or
24
+ yarn add viem
25
+ ```
26
26
 
27
- ## Quick Start
27
+ `react` >= 18 is required. If you already build a `ZamaConfig` with the core SDK, pass it directly to `ZamaProvider`.
28
28
 
29
- ### With wagmi
29
+ ## Minimal React example
30
30
 
31
31
  ```tsx
32
- import { WagmiProvider, createConfig, http } from "wagmi";
33
- import { sepolia } from "wagmi/chains";
32
+ import type { ReactNode } from "react";
34
33
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
35
- import { ZamaProvider } from "@zama-fhe/react-sdk";
36
- import { createConfig as createZamaFheConfig } from "@zama-fhe/react-sdk/wagmi";
34
+ import { ZamaProvider, useAllow, useConfidentialBalance, useIsAllowed } from "@zama-fhe/react-sdk";
35
+ import { createConfig } from "@zama-fhe/sdk/viem";
37
36
  import { web } from "@zama-fhe/sdk/web";
38
- import { sepolia as sepoliaFhe } from "@zama-fhe/sdk/chains";
39
- import type { FheChain } from "@zama-fhe/sdk/chains";
37
+ import { sepolia as sepoliaFhe, type FheChain } from "@zama-fhe/sdk/chains";
38
+ import { createPublicClient, createWalletClient, custom, http, type Address } from "viem";
39
+ import { sepolia } from "viem/chains";
40
40
 
41
- const mySepolia = {
41
+ const tokenAddress = "0xYourConfidentialToken" as Address;
42
+ const rpcUrl = "https://sepolia.infura.io/v3/YOUR_KEY";
43
+ const publicClient = createPublicClient({
44
+ chain: sepolia,
45
+ transport: http(rpcUrl),
46
+ });
47
+
48
+ const walletClient = createWalletClient({
49
+ chain: sepolia,
50
+ transport: custom(window.ethereum!),
51
+ });
52
+
53
+ const chain = {
42
54
  ...sepoliaFhe,
55
+ network: rpcUrl,
43
56
  relayerUrl: "https://your-app.com/api/relayer/11155111",
44
57
  } as const satisfies FheChain;
45
58
 
46
- const wagmiConfig = createConfig({
47
- chains: [sepolia],
48
- transports: {
49
- [sepolia.id]: http("https://sepolia.infura.io/v3/YOUR_KEY"),
50
- },
59
+ const queryClient = new QueryClient();
60
+ const zamaConfig = createConfig({
61
+ chains: [chain],
62
+ publicClient,
63
+ walletClient,
64
+ relayers: { [chain.id]: web() },
51
65
  });
52
66
 
53
- const zamaConfig = createZamaFheConfig({
54
- chains: [mySepolia],
55
- wagmiConfig,
56
- relayers: {
57
- [mySepolia.id]: web(),
58
- },
59
- });
67
+ function AuthGate({
68
+ contractAddresses,
69
+ children,
70
+ }: {
71
+ contractAddresses: Address[];
72
+ children: ReactNode;
73
+ }) {
74
+ const { data: isAllowed, isLoading: isChecking } = useIsAllowed({ contractAddresses });
75
+ const { mutateAsync: allow, isPending: isAuthorizing } = useAllow();
60
76
 
61
- const queryClient = new QueryClient();
77
+ if (isChecking) return <p>Checking authorization...</p>;
78
+ if (isAllowed) return <>{children}</>;
62
79
 
63
- function App() {
64
80
  return (
65
- <WagmiProvider config={wagmiConfig}>
66
- <QueryClientProvider client={queryClient}>
67
- <ZamaProvider config={zamaConfig}>
68
- <TokenBalance />
69
- </ZamaProvider>
70
- </QueryClientProvider>
71
- </WagmiProvider>
81
+ <button onClick={() => void allow(contractAddresses)} disabled={isAuthorizing}>
82
+ {isAuthorizing ? "Signing..." : "Authorize decryption"}
83
+ </button>
72
84
  );
73
85
  }
74
86
 
75
- function TokenBalance() {
76
- const { data: balance, isLoading } = useConfidentialBalance({ tokenAddress: "0xTokenAddress" });
87
+ function Balance() {
88
+ const { data: balance, isLoading } = useConfidentialBalance({
89
+ tokenAddress,
90
+ });
77
91
 
78
- if (isLoading) return <p>Decrypting balance...</p>;
92
+ if (isLoading) return <p>Decrypting...</p>;
79
93
  return <p>Balance: {balance?.toString()}</p>;
80
94
  }
81
- ```
82
-
83
- ### With a custom signer
84
-
85
- ```tsx
86
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
87
- import { ZamaProvider, useConfidentialBalance, useConfidentialTransfer } from "@zama-fhe/react-sdk";
88
- import { createConfig } from "@zama-fhe/react-sdk/wagmi";
89
- import { web } from "@zama-fhe/sdk/web";
90
- import { sepolia } from "@zama-fhe/sdk/chains";
91
- import type { FheChain } from "@zama-fhe/sdk/chains";
92
-
93
- const mySepolia = {
94
- ...sepolia,
95
- relayerUrl: "https://your-app.com/api/relayer/11155111",
96
- } as const satisfies FheChain;
97
-
98
- const zamaConfig = createConfig({
99
- chains: [mySepolia],
100
- signer: yourCustomSigner,
101
- relayers: {
102
- [mySepolia.id]: web(),
103
- },
104
- });
105
-
106
- const queryClient = new QueryClient();
107
95
 
108
- function App() {
96
+ export function App() {
109
97
  return (
110
98
  <QueryClientProvider client={queryClient}>
111
99
  <ZamaProvider config={zamaConfig}>
112
- <TransferForm />
100
+ <AuthGate contractAddresses={[tokenAddress]}>
101
+ <Balance />
102
+ </AuthGate>
113
103
  </ZamaProvider>
114
104
  </QueryClientProvider>
115
105
  );
116
106
  }
117
-
118
- function TransferForm() {
119
- const { data: balance } = useConfidentialBalance({ tokenAddress: "0xTokenAddress" });
120
- const { mutateAsync: transfer, isPending } = useConfidentialTransfer({
121
- tokenAddress: "0xTokenAddress",
122
- });
123
-
124
- const handleTransfer = async () => {
125
- const txHash = await transfer({ to: "0xRecipient", amount: 100n });
126
- console.log("Transfer tx:", txHash);
127
- };
128
-
129
- return (
130
- <div>
131
- <p>Balance: {balance?.toString()}</p>
132
- <button onClick={handleTransfer} disabled={isPending}>
133
- {isPending ? "Transferring..." : "Send 100 tokens"}
134
- </button>
135
- </div>
136
- );
137
- }
138
- ```
139
-
140
- ## Provider Setup
141
-
142
- All setups use `ZamaProvider` with a config object from `createConfig`.
143
-
144
- ```tsx
145
- import { ZamaProvider } from "@zama-fhe/react-sdk";
146
- import { createConfig } from "@zama-fhe/react-sdk/wagmi";
147
- import { web } from "@zama-fhe/sdk/web";
148
- import { sepolia } from "@zama-fhe/sdk/chains";
149
- import type { FheChain } from "@zama-fhe/sdk/chains";
150
-
151
- const mySepolia = { ...sepolia, relayerUrl: "/api/relayer/11155111" } as const satisfies FheChain;
152
-
153
- const zamaConfig = createConfig({
154
- chains: [mySepolia],
155
- wagmiConfig, // or use createConfig from @zama-fhe/sdk/viem or @zama-fhe/sdk/ethers
156
- relayers: {
157
- [mySepolia.id]: web(),
158
- },
159
- // Optional fields:
160
- // storage: indexedDBStorage,
161
- // sessionStorage: chromeSessionStorage,
162
- // keypairTTL: 2592000, // 30 days (default)
163
- // sessionTTL: 2592000, // 30 days (default). 0 = re-sign every operation.
164
- // onEvent: (event) => console.debug(event),
165
- });
166
-
167
- <ZamaProvider config={zamaConfig}>{children}</ZamaProvider>;
168
- ```
169
-
170
- ## Which Hooks Should I Use?
171
-
172
- The React SDK exports hooks from two layers. **Pick one layer per operation — never mix them.**
173
-
174
- **Use the main import** (`@zama-fhe/react-sdk`) when you have a `ZamaProvider` in your component tree. These hooks handle FHE encryption, cache invalidation, and error wrapping automatically:
175
-
176
- ```tsx
177
- import { useShield, useConfidentialTransfer } from "@zama-fhe/react-sdk";
178
-
179
- const { mutateAsync: shield } = useShield({ tokenAddress });
180
- await shield({ amount: 1000n }); // encryption + approval handled for you
181
- ```
182
-
183
- ```tsx
184
- import { ViemSigner } from "@zama-fhe/sdk/viem";
185
- import { EthersSigner } from "@zama-fhe/sdk/ethers";
186
- ```
187
-
188
- The `WagmiSigner` is the only adapter in the react-sdk since wagmi is React-specific:
189
-
190
- ```tsx
191
- import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
192
- ```
193
-
194
- ## Hooks Reference
195
-
196
- All hooks require a `ZamaProvider` (or one of its variants) in the component tree.
197
-
198
- ### SDK Access
199
-
200
- #### `useZamaSDK`
201
-
202
- Returns the `ZamaSDK` instance from context. Use this when you need direct access to the SDK (e.g. for low-level relayer operations).
203
-
204
- ```ts
205
- function useZamaSDK(): ZamaSDK;
206
- ```
207
-
208
- #### `useToken`
209
-
210
- Returns a `Token` instance for a given token address. The encrypted ERC-20 contract IS the wrapper, so `wrapperAddress` defaults to `tokenAddress`. Pass it only if they differ. Memoized — same config returns the same instance.
211
-
212
- ```ts
213
- function useToken(config: { tokenAddress: Address; wrapperAddress?: Address }): Token;
214
- ```
215
-
216
- #### `useReadonlyToken`
217
-
218
- Returns a `ReadonlyToken` instance for a given token address (no wrapper needed). Memoized.
219
-
220
- ```ts
221
- function useReadonlyToken(tokenAddress: Address): ReadonlyToken;
222
- ```
223
-
224
- ### Balance Hooks
225
-
226
- #### `useConfidentialBalance`
227
-
228
- Single-token balance with automatic decryption. Calls `token.balanceOf(owner)` which reads the on-chain handle and decrypts via the SDK. Cached values are returned instantly — the relayer is only hit when the handle changes. Pass `refetchInterval` to poll for updates.
229
-
230
- ```ts
231
- function useConfidentialBalance(
232
- config: UseConfidentialBalanceConfig,
233
- options?: UseConfidentialBalanceOptions,
234
- ): UseQueryResult<bigint, Error>;
235
-
236
- interface UseConfidentialBalanceConfig {
237
- tokenAddress: Address;
238
- }
239
- ```
240
-
241
- Options extend `UseQueryOptions`.
242
-
243
- ```tsx
244
- const {
245
- data: balance,
246
- isLoading,
247
- error,
248
- } = useConfidentialBalance(
249
- {
250
- tokenAddress: "0xTokenAddress",
251
- },
252
- { refetchInterval: 5_000 },
253
- );
254
- ```
255
-
256
- #### `useConfidentialBalances`
257
-
258
- Multi-token batch balance. Calls `ReadonlyToken.batchBalancesOf()` which decrypts each token's balance via the SDK. Cached values are returned instantly — the relayer is only hit for changed handles. Returns partial results when some tokens fail.
259
-
260
- ```ts
261
- function useConfidentialBalances(
262
- config: UseConfidentialBalancesConfig,
263
- options?: UseConfidentialBalancesOptions,
264
- ): UseQueryResult<BatchBalancesResult, Error>;
265
-
266
- interface UseConfidentialBalancesConfig {
267
- tokenAddresses: Address[];
268
- }
269
-
270
- interface BatchBalancesResult {
271
- results: Map<Address, bigint>;
272
- errors: Map<Address, ZamaError>;
273
- }
274
- ```
275
-
276
- ```tsx
277
- const { data } = useConfidentialBalances({
278
- tokenAddresses: ["0xTokenA", "0xTokenB", "0xTokenC"],
279
- });
280
-
281
- const tokenABalance = data?.results.get("0xTokenA");
282
- if (data && data.errors.size > 0) {
283
- // some tokens failed — check data.errors
284
- }
285
- ```
286
-
287
- ### Authorization
288
-
289
- #### `useAllow`
290
-
291
- Pre-authorize FHE decrypt credentials for a list of contract addresses with a single wallet signature. Call this early (e.g. after wallet connect) so that subsequent decrypt operations reuse cached credentials without prompting the wallet again.
292
-
293
- ```ts
294
- function useAllow(): UseMutationResult<void, Error, Address[]>;
295
- ```
296
-
297
- ```tsx
298
- const { mutateAsync: allow, isPending } = useAllow();
299
-
300
- // Pre-authorize all known contracts up front
301
- await allow(allContractAddresses);
302
-
303
- // Individual balance decrypts now reuse cached credentials
304
- const { data: balance } = useConfidentialBalance({ tokenAddress: "0xTokenA" });
305
- ```
306
-
307
- #### `useIsAllowed`
308
-
309
- Check whether a session signature is cached, valid, and scoped to the contract addresses you want to decrypt. Returns `true` if decrypt operations can proceed without a wallet prompt. Use this to conditionally enable UI elements (e.g. a "Reveal Balances" button).
310
-
311
- ```ts
312
- function useIsAllowed(config: {
313
- contractAddresses: [Address, ...Address[]];
314
- }): UseQueryResult<boolean, Error>;
315
- ```
316
-
317
- ```tsx
318
- const { data: allowed } = useIsAllowed({
319
- contractAddresses: ["0xTokenA"],
320
- });
321
-
322
- <button disabled={!allowed}>Reveal Balance</button>;
323
- ```
324
-
325
- Automatically invalidated when `useAllow` or `useRevoke` succeed.
326
-
327
- #### `useRevoke`
328
-
329
- Revoke decrypt authorization for specific contract addresses. Stored credentials remain intact, but the next decrypt operation will require a fresh wallet signature.
330
-
331
- ```ts
332
- function useRevoke(): UseMutationResult<void, Error, Address[]>;
333
- ```
334
-
335
- ```tsx
336
- const { mutate: revoke } = useRevoke();
337
-
338
- // Revoke — addresses are included in the credentials:revoked event
339
- revoke(["0xContractA", "0xContractB"]);
340
- ```
341
-
342
- ### Transfer Hooks
343
-
344
- #### `useConfidentialTransfer`
345
-
346
- Encrypted transfer. Encrypts the amount and calls the contract. Automatically invalidates balance caches on success.
347
-
348
- ```ts
349
- function useConfidentialTransfer(
350
- config: UseZamaConfig,
351
- options?: UseMutationOptions<Address, Error, ConfidentialTransferParams>,
352
- ): UseMutationResult<Address, Error, ConfidentialTransferParams>;
353
-
354
- interface ConfidentialTransferParams {
355
- to: Address;
356
- amount: bigint;
357
- }
358
- ```
359
-
360
- ```tsx
361
- const { mutateAsync: transfer, isPending } = useConfidentialTransfer({
362
- tokenAddress: "0xTokenAddress",
363
- });
364
-
365
- const txHash = await transfer({ to: "0xRecipient", amount: 1000n });
366
- ```
367
-
368
- #### `useConfidentialTransferFrom`
369
-
370
- Operator transfer on behalf of another address.
371
-
372
- ```ts
373
- function useConfidentialTransferFrom(
374
- config: UseZamaConfig,
375
- options?: UseMutationOptions<Address, Error, ConfidentialTransferFromParams>,
376
- ): UseMutationResult<Address, Error, ConfidentialTransferFromParams>;
377
-
378
- interface ConfidentialTransferFromParams {
379
- from: Address;
380
- to: Address;
381
- amount: bigint;
382
- }
383
- ```
384
-
385
- ### Shield Hooks
386
-
387
- #### `useShield`
388
-
389
- Shield public ERC-20 tokens into confidential tokens. Handles ERC-20 approval automatically.
390
-
391
- ```ts
392
- function useShield(
393
- config: UseZamaConfig,
394
- options?: UseMutationOptions<Address, Error, ShieldParams>,
395
- ): UseMutationResult<Address, Error, ShieldParams>;
396
-
397
- interface ShieldParams {
398
- amount: bigint;
399
- approvalStrategy?: "max" | "exact" | "skip"; // default: "exact"
400
- }
401
- ```
402
-
403
- ```tsx
404
- const { mutateAsync: shield } = useShield({ tokenAddress: "0xTokenAddress" });
405
-
406
- // Shield 1000 tokens with exact approval (default)
407
- await shield({ amount: 1000n });
408
-
409
- // Shield with max approval
410
- await shield({ amount: 1000n, approvalStrategy: "max" });
411
- ```
412
-
413
- ### Unshield Hooks (Combined)
414
-
415
- These hooks orchestrate the full unshield flow in a single call: unwrap → wait for receipt → parse event → finalizeUnwrap. Use these for the simplest integration.
416
-
417
- #### `useUnshield`
418
-
419
- Unshield a specific amount. Handles the entire unwrap + finalize flow. Supports optional progress callbacks to track each step.
420
-
421
- ```ts
422
- function useUnshield(
423
- config: UseZamaConfig,
424
- options?: UseMutationOptions<Address, Error, UnshieldParams>,
425
- ): UseMutationResult<Address, Error, UnshieldParams>;
426
-
427
- interface UnshieldParams extends UnshieldCallbacks {
428
- amount: bigint;
429
- skipBalanceCheck?: boolean;
430
- }
431
- ```
432
-
433
- ```tsx
434
- const { mutateAsync: unshield, isPending } = useUnshield({
435
- tokenAddress: "0xTokenAddress",
436
- });
437
-
438
- const finalizeTxHash = await unshield({
439
- amount: 500n,
440
- onUnwrapSubmitted: (txHash) => console.log("Unwrap tx:", txHash),
441
- onFinalizing: () => console.log("Finalizing..."),
442
- onFinalizeSubmitted: (txHash) => console.log("Finalize tx:", txHash),
443
- });
444
- ```
445
-
446
- #### `useUnshieldAll`
447
-
448
- Unshield the entire balance. Handles the entire unwrap + finalize flow. Supports optional progress callbacks.
449
-
450
- ```ts
451
- function useUnshieldAll(
452
- config: UseZamaConfig,
453
- options?: UseMutationOptions<Address, Error, UnshieldAllParams | void>,
454
- ): UseMutationResult<Address, Error, UnshieldAllParams | void>;
455
-
456
- interface UnshieldAllParams extends UnshieldCallbacks {}
457
- ```
458
-
459
- ```tsx
460
- const { mutateAsync: unshieldAll } = useUnshieldAll({
461
- tokenAddress: "0xTokenAddress",
462
- });
463
-
464
- const finalizeTxHash = await unshieldAll();
465
- ```
466
-
467
- #### `useResumeUnshield`
468
-
469
- Resume an interrupted unshield from a saved unwrap tx hash. Useful when the user submitted the unwrap but the finalize step was interrupted (e.g. page reload, network error). Pair with the `savePendingUnshield`/`loadPendingUnshield`/`clearPendingUnshield` utilities for persistence.
470
-
471
- ```ts
472
- function useResumeUnshield(
473
- config: UseZamaConfig,
474
- options?: UseMutationOptions<Address, Error, ResumeUnshieldParams>,
475
- ): UseMutationResult<Address, Error, ResumeUnshieldParams>;
476
-
477
- interface ResumeUnshieldParams extends UnshieldCallbacks {
478
- unwrapTxHash: Hex;
479
- }
480
- ```
481
-
482
- ```tsx
483
- import { loadPendingUnshield, clearPendingUnshield } from "@zama-fhe/react-sdk";
484
-
485
- const { mutateAsync: resumeUnshield } = useResumeUnshield({
486
- tokenAddress: "0xTokenAddress",
487
- });
488
-
489
- // On mount, check for interrupted unshields
490
- const pending = await loadPendingUnshield(storage, wrapperAddress);
491
- if (pending) {
492
- await resumeUnshield({ unwrapTxHash: pending });
493
- await clearPendingUnshield(storage, wrapperAddress);
494
- }
495
- ```
496
-
497
- #### Pending Unshield Persistence
498
-
499
- Save the unwrap tx hash before finalization so interrupted unshields can be resumed after page reloads:
500
-
501
- ```ts
502
- import {
503
- savePendingUnshield,
504
- loadPendingUnshield,
505
- loadPendingUnshieldRequest,
506
- clearPendingUnshield,
507
- type PendingUnshieldRequest,
508
- } from "@zama-fhe/react-sdk";
509
-
510
- // Save before the finalize step.
511
- // Pass unwrapRequestId from upgraded UnwrapRequested events when available.
512
- const event = findUnwrapRequested(receipt.logs);
513
- await savePendingUnshield(storage, wrapperAddress, unwrapTxHash, event.unwrapRequestId);
514
-
515
- // On next visit: resume with the tx hash (works for both legacy and upgraded wrappers)
516
- const pending = await loadPendingUnshield(storage, wrapperAddress);
517
-
518
- // Or load the full request to access unwrapRequestId directly
519
- const request: PendingUnshieldRequest | null = await loadPendingUnshieldRequest(
520
- storage,
521
- wrapperAddress,
522
- );
523
- // request.unwrapTxHash — always present
524
- // request.unwrapRequestId — present only for requests from upgraded wrappers
525
-
526
- // Clear after successful finalization
527
- await clearPendingUnshield(storage, wrapperAddress);
528
- ```
529
-
530
- ### Unwrap Hooks (Low-Level)
531
-
532
- These hooks expose the individual unwrap steps. Use them when you need fine-grained control over the flow.
533
-
534
- #### `useUnwrap`
535
-
536
- Request unwrap for a specific amount (requires manual finalization via `useFinalizeUnwrap`).
537
-
538
- ```ts
539
- function useUnwrap(
540
- config: UseZamaConfig,
541
- options?: UseMutationOptions<Address, Error, UnwrapParams>,
542
- ): UseMutationResult<Address, Error, UnwrapParams>;
543
-
544
- interface UnwrapParams {
545
- amount: bigint;
546
- }
547
- ```
548
-
549
- #### `useUnwrapAll`
550
-
551
- Request unwrap for the entire balance (requires manual finalization).
552
-
553
- ```ts
554
- function useUnwrapAll(
555
- config: UseZamaConfig,
556
- options?: UseMutationOptions<Address, Error, void>,
557
- ): UseMutationResult<Address, Error, void>;
558
- ```
559
-
560
- #### `useFinalizeUnwrap`
561
-
562
- Complete an unwrap by providing the decryption proof.
563
-
564
- ```ts
565
- function useFinalizeUnwrap(
566
- config: UseZamaConfig,
567
- options?: UseMutationOptions<TransactionResult, Error, FinalizeUnwrapParams>,
568
- ): UseMutationResult<TransactionResult, Error, FinalizeUnwrapParams>;
569
-
570
- type FinalizeUnwrapParams =
571
- | { unwrapRequestId: Hex; burnAmountHandle?: never }
572
- | { unwrapRequestId?: never; burnAmountHandle: Hex };
573
- ```
574
-
575
- ### Delegation Hooks
576
-
577
- #### `useDelegateDecryption`
578
-
579
- Grant decryption delegation to another address via the on-chain ACL. ACL address is resolved automatically from the relayer transport config.
580
-
581
- ```ts
582
- function useDelegateDecryption(
583
- config: UseZamaConfig,
584
- options?: UseMutationOptions<TransactionResult, Error, DelegateDecryptionParams>,
585
- ): UseMutationResult<TransactionResult, Error, DelegateDecryptionParams>;
586
-
587
- interface DelegateDecryptionParams {
588
- delegateAddress: Address;
589
- expirationDate?: Date;
590
- }
591
- ```
592
-
593
- ```tsx
594
- const { mutateAsync: delegate, isPending } = useDelegateDecryption({
595
- tokenAddress: "0xToken",
596
- });
597
-
598
- // Permanent delegation
599
- await delegate({ delegateAddress: "0xDelegate" });
600
-
601
- // With expiration
602
- await delegate({
603
- delegateAddress: "0xDelegate",
604
- expirationDate: new Date("2025-12-31"),
605
- });
606
- ```
607
-
608
- #### `useDecryptBalanceAs`
609
-
610
- Decrypt another user's balance as a delegate. Uses the delegated EIP-712 flow — the connected wallet signs as the delegate, and the relayer verifies the on-chain delegation.
611
-
612
- ```ts
613
- function useDecryptBalanceAs(
614
- tokenAddress: Address,
615
- options?: UseMutationOptions<bigint, Error, DecryptBalanceAsParams>,
616
- ): UseMutationResult<bigint, Error, DecryptBalanceAsParams>;
617
-
618
- interface DecryptBalanceAsParams {
619
- delegatorAddress: Address;
620
- owner?: Address;
621
- }
622
- ```
623
-
624
- ```tsx
625
- const { mutateAsync: decryptAs, data: balance } = useDecryptBalanceAs("0xToken");
626
-
627
- // Decrypt the delegator's balance
628
- const result = await decryptAs({ delegatorAddress: "0xDelegator" });
629
- // result => bigint
630
- ```
631
-
632
- ### Approval Hooks
633
-
634
- #### `useConfidentialApprove`
635
-
636
- Set operator approval for the confidential token.
637
-
638
- ```ts
639
- function useConfidentialApprove(
640
- config: UseZamaConfig,
641
- options?: UseMutationOptions<Address, Error, ConfidentialApproveParams>,
642
- ): UseMutationResult<Address, Error, ConfidentialApproveParams>;
643
-
644
- interface ConfidentialApproveParams {
645
- spender: Address;
646
- until?: number; // Unix timestamp, defaults to now + 1 hour
647
- }
648
- ```
649
-
650
- #### `useConfidentialIsApproved`
651
-
652
- Check if a spender is an approved operator. Enabled only when `spender` is defined.
653
-
654
- ```ts
655
- function useConfidentialIsApproved(
656
- config: UseZamaConfig,
657
- spender: Address | undefined,
658
- options?: Omit<UseQueryOptions<boolean, Error>, "queryKey" | "queryFn">,
659
- ): UseQueryResult<boolean, Error>;
660
- ```
661
-
662
- #### `useUnderlyingAllowance`
663
-
664
- Read the underlying ERC-20 allowance granted to the wrapper.
665
-
666
- ```ts
667
- function useUnderlyingAllowance(
668
- config: UseUnderlyingAllowanceConfig,
669
- options?: Omit<UseQueryOptions<bigint, Error>, "queryKey" | "queryFn">,
670
- ): UseQueryResult<bigint, Error>;
671
-
672
- interface UseUnderlyingAllowanceConfig {
673
- tokenAddress: Address;
674
- wrapperAddress: Address;
675
- }
676
- ```
677
-
678
- ### Discovery & Metadata
679
-
680
- #### `useWrapperDiscovery`
681
-
682
- Find the wrapper contract for a given token via the on-chain registry. Enabled only when `erc20Address` is defined. Results are cached indefinitely (`staleTime: Infinity`).
683
-
684
- ```ts
685
- function useWrapperDiscovery(
686
- config: UseWrapperDiscoveryConfig,
687
- options?: Omit<UseQueryOptions<Address | null, Error>, "queryKey" | "queryFn">,
688
- ): UseQueryResult<Address | null, Error>;
689
-
690
- interface UseWrapperDiscoveryConfig {
691
- tokenAddress: Address;
692
- erc20Address: Address | undefined;
693
- }
694
- ```
695
-
696
- #### `useTokenMetadata`
697
-
698
- Fetch token name, symbol, and decimals in parallel. Cached indefinitely.
699
-
700
- ```ts
701
- function useTokenMetadata(
702
- tokenAddress: Address,
703
- options?: Omit<UseQueryOptions<TokenMetadata, Error>, "queryKey" | "queryFn">,
704
- ): UseQueryResult<TokenMetadata, Error>;
705
-
706
- interface TokenMetadata {
707
- name: string;
708
- symbol: string;
709
- decimals: number;
710
- }
711
107
  ```
712
108
 
713
- ```tsx
714
- const { data: meta } = useTokenMetadata("0xTokenAddress");
715
- // meta?.name, meta?.symbol, meta?.decimals
716
- ```
717
-
718
- ### Low-Level FHE Hooks
719
-
720
- These hooks are for **custom FHE contracts** (non-token contracts that use encrypted types directly). For confidential ERC-20 tokens, use the high-level token hooks above instead. For detailed usage examples, see the [Encrypt & Decrypt guide](../../docs/gitbook/src/guides/encrypt-decrypt.md).
721
-
722
- #### Encryption
723
-
724
- ```tsx
725
- const encrypt = useEncrypt();
726
-
727
- const { handles, inputProof } = await encrypt.mutateAsync({
728
- values: [{ value: 1000n, type: "euint64" }],
729
- contractAddress: "0xYourContract",
730
- userAddress,
731
- });
732
-
733
- // Pass handles and inputProof to your contract call
734
- ```
735
-
736
- #### Decryption (`useUserDecrypt`)
737
-
738
- `useUserDecrypt` is a TanStack Query hook that manages the full decrypt orchestration — keypair generation, EIP-712, wallet signature — and reuses cached credentials when available, avoiding redundant wallet prompts. It is **disabled by default**; pass `enabled: true` to fire the query.
739
-
740
- ```tsx
741
- const { data, isPending, isSuccess } = useUserDecrypt(
742
- {
743
- handles: [
744
- { handle: "0xabc...", contractAddress: "0xTokenA" },
745
- { handle: "0xdef...", contractAddress: "0xTokenB" },
746
- ],
747
- },
748
- { enabled: shouldDecrypt },
749
- );
750
- // data: { "0xabc...": 500n, "0xdef...": 1000n }
751
- ```
752
-
753
- #### All Encryption & Decryption Hooks
754
-
755
- | Hook | Input | Output | Description |
756
- | --------------------------- | ---------------------------- | ------------------------ | ---------------------------------------------------------------------------- |
757
- | `useEncrypt()` | `EncryptParams` | `EncryptResult` | Encrypt values for smart contract calls. |
758
- | `useUserDecrypt()` | `UserDecryptQueryConfig` | `DecryptResult` | User decryption query with TanStack Query semantics. Results cached. |
759
- | `usePublicDecrypt()` | `string[]` (handles) | `PublicDecryptResult` | Public decryption (no authorization needed). Populates the decryption cache. |
760
- | `useDelegatedUserDecrypt()` | `DelegatedUserDecryptParams` | `Record<string, bigint>` | Decrypt via delegation. |
761
-
762
- #### Key Management
763
-
764
- | Hook | Input | Output | Description |
765
- | --------------------------------------- | ---------------------------------------- | ----------------------------------- | ---------------------------------------------------- |
766
- | `useGenerateKeypair()` | `void` | `FHEKeypair` | Generate an FHE keypair. |
767
- | `useCreateEIP712()` | `CreateEIP712Params` | `EIP712TypedData` | Create EIP-712 typed data for decrypt authorization. |
768
- | `useCreateDelegatedUserDecryptEIP712()` | `CreateDelegatedUserDecryptEIP712Params` | `KmsDelegatedUserDecryptEIP712Type` | Create EIP-712 for delegated decryption. |
769
- | `useRequestZKProofVerification()` | `ZKProofLike` | `InputProofBytesType` | Submit a ZK proof for verification. |
770
-
771
- #### Network
772
-
773
- | Hook | Input | Output | Description |
774
- | ------------------- | --------------- | ------------------------------------------ | ------------------------------------- |
775
- | `usePublicKey()` | `void` | `{ publicKeyId, publicKey } \| null` | Get the TFHE compact public key. |
776
- | `usePublicParams()` | `number` (bits) | `{ publicParams, publicParamsId } \| null` | Get public parameters for encryption. |
777
-
778
- ## Query Keys
779
-
780
- Use `zamaQueryKeys` for manual cache management (invalidation, prefetching, removal).
781
-
782
- ```ts
783
- import { zamaQueryKeys, decryptionKeys } from "@zama-fhe/react-sdk";
784
- ```
785
-
786
- | Factory | Keys | Description |
787
- | ------------------------------------ | ------------------------------------------------------------ | ----------------------------------- |
788
- | `zamaQueryKeys.confidentialBalance` | `.all`, `.token(address)`, `.owner(address, owner)` | Single-token decrypted balance. |
789
- | `zamaQueryKeys.confidentialBalances` | `.all`, `.tokens(addresses, owner)` | Multi-token batch balances. |
790
- | `zamaQueryKeys.isAllowed` | `.all` | Session signature status. |
791
- | `zamaQueryKeys.underlyingAllowance` | `.all`, `.token(address)`, `.scope(address, owner, wrapper)` | Underlying ERC-20 allowance. |
792
- | `decryptionKeys` | `.value(handle)` | Individual decrypted handle values. |
793
-
794
- ```tsx
795
- import { useQueryClient } from "@tanstack/react-query";
796
- import { zamaQueryKeys } from "@zama-fhe/react-sdk";
797
-
798
- const queryClient = useQueryClient();
799
-
800
- // Invalidate all balances
801
- queryClient.invalidateQueries({ queryKey: zamaQueryKeys.confidentialBalance.all });
802
-
803
- // Invalidate a specific token's balance
804
- queryClient.invalidateQueries({
805
- queryKey: zamaQueryKeys.confidentialBalance.token("0xTokenAddress"),
806
- });
807
- ```
808
-
809
- ## Wagmi Signer Adapter
810
-
811
- ```ts
812
- import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
813
-
814
- const signer = new WagmiSigner({ config: wagmiConfig });
815
- ```
816
-
817
- ## Signer Adapters
818
-
819
- Signer adapters are provided by the core SDK package:
820
-
821
- ```ts
822
- import { ViemSigner } from "@zama-fhe/sdk/viem";
823
- import { EthersSigner } from "@zama-fhe/sdk/ethers";
824
- ```
825
-
826
- ## Wallet Integration Guide
827
-
828
- ### SSR / Next.js
829
-
830
- All components using SDK hooks must be client components. Add `"use client"` at the top of files that import from `@zama-fhe/react-sdk`. FHE operations (encryption, decryption) run in a Web Worker and require browser APIs — they cannot execute on the server.
831
-
832
- ```tsx
833
- "use client";
834
-
835
- import { useConfidentialBalance } from "@zama-fhe/react-sdk";
836
- ```
837
-
838
- Place `ZamaProvider` inside your client-only layout. Do **not** create the relayer or signer at the module level in a server component — wrap them in a client component or use lazy initialization.
839
-
840
- ### FHE Credentials Lifecycle
841
-
842
- FHE decrypt credentials are generated once per wallet + contract set and cached in the storage backend you provide (e.g. `IndexedDBStorage`). The wallet signature is kept **in memory only** — never persisted to disk. The lifecycle:
843
-
844
- 1. **First decrypt** — SDK generates an FHE keypair, creates EIP-712 typed data, and prompts the wallet to sign. The encrypted credential is stored; the signature is cached in memory.
845
- 2. **Same session** — Cached credentials and session signature are reused silently (no wallet prompt).
846
- 3. **Page reload** — Encrypted credentials are loaded from storage; the wallet is prompted once to re-sign for the session.
847
- 4. **Expiry** — Credentials expire based on `keypairTTL` (default: 2592000s = 30 days). After expiry, the next decrypt regenerates and re-prompts.
848
- 5. **Pre-authorization** — Call `useAllow(contractAddresses)` early to batch-authorize all contracts in one wallet prompt, avoiding repeated popups.
849
- 6. **Check status** — Use `useIsAllowed({ contractAddresses })` to conditionally enable UI elements (e.g. disable "Reveal" until allowed).
850
- 7. **Disconnect** — Call `useRevoke(contractAddresses)` or `await credentials.revoke()` to clear the session signature from memory.
851
-
852
- ### Web Extension Support
853
-
854
- By default, wallet signatures are stored in memory and lost on page reload (or service worker restart). For MV3 web extensions, use the built-in `chromeSessionStorage` singleton so signatures survive service worker restarts and are shared across popup, background, and content script contexts:
855
-
856
- ```tsx
857
- import { chromeSessionStorage, indexedDBStorage } from "@zama-fhe/react-sdk";
858
- import { createConfig } from "@zama-fhe/react-sdk/wagmi";
859
- import { web } from "@zama-fhe/sdk/web";
860
- import { sepolia } from "@zama-fhe/sdk/chains";
861
- import type { FheChain } from "@zama-fhe/sdk/chains";
862
-
863
- const mySepolia = { ...sepolia, relayerUrl: "/api/relayer/11155111" } as const satisfies FheChain;
864
-
865
- const zamaConfig = createConfig({
866
- chains: [mySepolia],
867
- wagmiConfig,
868
- storage: indexedDBStorage,
869
- sessionStorage: chromeSessionStorage,
870
- relayers: {
871
- [mySepolia.id]: web(),
872
- },
873
- });
874
-
875
- <ZamaProvider config={zamaConfig}>
876
- <App />
877
- </ZamaProvider>;
878
- ```
879
-
880
- This keeps the encrypted credentials in IndexedDB (persistent) while the unlock signature lives in `chrome.storage.session` (ephemeral, cleared when the browser closes).
881
-
882
- ### Error-to-User-Message Mapping
883
-
884
- Map SDK errors to user-friendly messages in your UI:
885
-
886
- ```tsx
887
- import {
888
- SigningRejectedError,
889
- EncryptionFailedError,
890
- DecryptionFailedError,
891
- TransactionRevertedError,
892
- ApprovalFailedError,
893
- } from "@zama-fhe/react-sdk";
894
-
895
- function getUserMessage(error: Error): string {
896
- if (error instanceof SigningRejectedError)
897
- return "Transaction cancelled — please approve in your wallet.";
898
- if (error instanceof EncryptionFailedError) return "Encryption failed — please try again.";
899
- if (error instanceof DecryptionFailedError) return "Decryption failed — please try again.";
900
- if (error instanceof ApprovalFailedError) return "Token approval failed — please try again.";
901
- if (error instanceof TransactionRevertedError)
902
- return "Transaction failed on-chain — check your balance.";
903
- return "An unexpected error occurred.";
904
- }
905
- ```
906
-
907
- Or use `matchZamaError` for a more concise pattern:
908
-
909
- ```tsx
910
- import { matchZamaError } from "@zama-fhe/react-sdk";
911
-
912
- const message = matchZamaError(error, {
913
- SIGNING_REJECTED: () => "Transaction cancelled — please approve in your wallet.",
914
- ENCRYPTION_FAILED: () => "Encryption failed — please try again.",
915
- DECRYPTION_FAILED: () => "Decryption failed — please try again.",
916
- APPROVAL_FAILED: () => "Token approval failed — please try again.",
917
- TRANSACTION_REVERTED: () => "Transaction failed on-chain — check your balance.",
918
- _: () => "An unexpected error occurred.",
919
- });
920
- ```
921
-
922
- ### Balance Caching and Refresh
923
-
924
- Balance queries call `token.balanceOf(owner)`, which reads the encrypted handle on-chain and decrypts via `sdk.userDecrypt`. The SDK's `DecryptCache` returns previously decrypted values instantly when the handle hasn't changed — the expensive relayer round-trip only runs when the balance actually changes. Pass `refetchInterval` to poll for on-chain updates.
925
-
926
- Mutation hooks (`useConfidentialTransfer`, `useShield`, `useUnshield`, etc.) automatically invalidate the relevant caches on success, so the UI updates immediately after user actions.
927
-
928
- To force a refresh:
929
-
930
- ```tsx
931
- const queryClient = useQueryClient();
932
- queryClient.invalidateQueries({ queryKey: zamaQueryKeys.confidentialBalance.all });
933
- ```
934
-
935
- ## Re-exports from Core SDK
936
-
937
- All public exports from `@zama-fhe/sdk` are re-exported from the main entry point. You never need to import from the core package directly.
938
-
939
- **Config:** `web`, `cleartext`. Use `createConfig` from `@zama-fhe/react-sdk/wagmi`, `@zama-fhe/sdk/viem`, or `@zama-fhe/sdk/ethers` as appropriate. Use `node` from `@zama-fhe/sdk/node`.
940
-
941
- **Classes:** `RelayerWeb`, `ZamaSDK`, `Token`, `ReadonlyToken`, `MemoryStorage`, `memoryStorage`, `IndexedDBStorage`, `indexedDBStorage`, `CredentialsManager`.
942
-
943
- **Network configs:** `SepoliaConfig`, `MainnetConfig`, `HardhatConfig`. Chain objects are available from `@zama-fhe/sdk/chains`.
944
-
945
- **Pending unshield:** `savePendingUnshield`, `loadPendingUnshield`, `loadPendingUnshieldRequest`, `clearPendingUnshield`. Type: `PendingUnshieldRequest`.
109
+ This keeps `Balance` from mounting until the contract is authorized, so the first decrypt happens after an explicit user action instead of an unsolicited wallet popup.
946
110
 
947
- **Types:** `Address`, `ZamaSDKConfig`, `ReadonlyTokenConfig`, `NetworkType`, `RelayerSDK`, `RelayerSDKStatus`, `EncryptResult`, `EncryptParams`, `UserDecryptParams`, `PublicDecryptResult`, `KeypairType`, `EIP712TypedData`, `DelegatedUserDecryptParams`, `KmsDelegatedUserDecryptEIP712Type`, `ZKProofLike`, `InputProofBytesType`, `StoredCredentials`, `GenericSigner`, `GenericStorage`, `TransactionReceipt`, `TransactionResult`, `UnshieldCallbacks`.
111
+ If you need a wagmi-based setup or another integration pattern, start from the [Quick start](https://github.com/zama-ai/sdk/blob/main/docs/gitbook/src/tutorials/quick-start.md) and the [Guides](https://github.com/zama-ai/sdk/blob/main/docs/gitbook/src/guides/README.md).
948
112
 
949
- **Errors:** `ZamaError`, `ZamaErrorCode`, `SigningRejectedError`, `SigningFailedError`, `EncryptionFailedError`, `DecryptionFailedError`, `ApprovalFailedError`, `TransactionRevertedError`, `InvalidKeypairError`, `NoCiphertextError`, `RelayerRequestFailedError`, `matchZamaError`.
113
+ ## Using the provider and hooks
950
114
 
951
- **Constants:** `ZERO_HANDLE`, `ERC7984_INTERFACE_ID`, `ERC7984_WRAPPER_INTERFACE_ID`, `ERC7984_WRAPPER_INTERFACE_ID_LEGACY`.
115
+ - `ZamaProvider` accepts a prebuilt `ZamaConfig`, so you can pair the React hooks with viem, ethers, or another signer setup from the core SDK.
116
+ - For wagmi-based apps, follow the Quick start and guides for the current recommended setup.
117
+ - Hooks from `@zama-fhe/react-sdk` handle confidential operations, cached decryption, and query invalidation for you.
118
+ - Lower-level SDK utilities, adapters, and token classes still come from `@zama-fhe/sdk`.
952
119
 
953
- **ABIs:** `ERC20_ABI`, `ERC20_METADATA_ABI`, `DEPLOYMENT_COORDINATOR_ABI`, `ERC165_ABI`, `ENCRYPTION_ABI`, `TRANSFER_BATCHER_ABI`, `WRAPPER_ABI`, `BATCH_SWAP_ABI`.
120
+ ## Common hooks
954
121
 
955
- **Events:** `RawLog`, `ConfidentialTransferEvent`, `WrappedEvent`, `UnwrapRequestedEvent`, `UnwrapFinalizedEvent`, `UnwrappedFinalizedEvent`, `UnwrappedStartedEvent`, `OnChainEvent`, `Topics`, `TOKEN_TOPICS`.
122
+ - `useIsAllowed` and `useAllow` let you gate decrypt flows behind an explicit user action.
123
+ - `useConfidentialBalance` reads and decrypts one token balance.
124
+ - `useConfidentialTransfer` sends a confidential transfer and invalidates affected queries.
125
+ - `useShield` converts public ERC-20 balances into confidential balances.
126
+ - `useUnshield` moves confidential balances back to public ERC-20 balances.
127
+ - `useDelegateDecryption` grants another account permission to decrypt a balance.
956
128
 
957
- **Event decoders:** `decodeConfidentialTransfer`, `decodeWrapped`, `decodeUnwrapRequested`, `decodeUnwrapFinalized`, `decodeUnwrappedFinalized`, `decodeUnwrappedStarted`, `decodeOnChainEvent`, `decodeOnChainEvents`, `findUnwrapRequested`, `findWrapped`.
129
+ ## Documentation
958
130
 
959
- **Contract call builders:** `confidentialBalanceOfContract`, `confidentialTransferContract`, `confidentialTransferFromContract`, `isOperatorContract`, `unwrapContract`, `unwrapFromBalanceContract`, `finalizeUnwrapContract`, `setOperatorContract`, `underlyingContract`, `inferredTotalSupplyContract`, `wrapContract`, `supportsInterfaceContract`, `isConfidentialTokenContract`, `isConfidentialWrapperContract`, `nameContract`, `symbolContract`, `decimalsContract`, `allowanceContract`, `approveContract`, `confidentialTotalSupplyContract`, `totalSupplyContract`, `rateContract`.
131
+ - [Official documentation](https://docs.zama.org/protocol) is the best starting point for the hosted SDK docs.
132
+ - [Quick start](https://github.com/zama-ai/sdk/blob/main/docs/gitbook/src/tutorials/quick-start.md) shows the full React setup from install to first transfer.
133
+ - [React reference](https://github.com/zama-ai/sdk/blob/main/docs/gitbook/src/reference/react/README.md) documents all hooks, provider components, and query helpers.
134
+ - [Guides](https://github.com/zama-ai/sdk/blob/main/docs/gitbook/src/guides/README.md) cover focused topics such as authentication, SSR, browser extensions, balances, and transfers.
135
+ - [Core SDK reference](https://github.com/zama-ai/sdk/blob/main/docs/gitbook/src/reference/sdk/README.md) documents lower-level SDK classes, adapters, and utilities.