@zama-fhe/react-sdk 1.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/README.md ADDED
@@ -0,0 +1,735 @@
1
+ # @zama-fhe/react-sdk
2
+
3
+ React hooks for confidential token operations, built on [React Query](https://tanstack.com/query). Provides declarative, cache-aware hooks for balances, confidential transfers, shielding, unshielding, and decryption — so you never deal with raw FHE operations in your components.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @zama-fhe/react-sdk @tanstack/react-query
9
+ ```
10
+
11
+ `@zama-fhe/sdk` is included as a direct dependency — no need to install it separately.
12
+
13
+ ### Peer dependencies
14
+
15
+ | Package | Version | Required? |
16
+ | ----------------------- | ------- | --------------------------------------------- |
17
+ | `react` | >= 18 | Yes |
18
+ | `@tanstack/react-query` | >= 5 | Yes |
19
+ | `viem` | >= 2 | Optional — for `/viem` and `/wagmi` sub-paths |
20
+ | `ethers` | >= 6 | Optional — for `/ethers` sub-path |
21
+ | `wagmi` | >= 2 | Optional — for `/wagmi` sub-path |
22
+
23
+ ## Quick Start
24
+
25
+ ### With wagmi
26
+
27
+ ```tsx
28
+ import { WagmiProvider, createConfig, http } from "wagmi";
29
+ import { mainnet, sepolia } from "wagmi/chains";
30
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
31
+ import { TokenSDKProvider, RelayerWeb, indexedDBStorage } from "@zama-fhe/react-sdk";
32
+ import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
33
+
34
+ const queryClient = new QueryClient();
35
+
36
+ const wagmiConfig = createConfig({
37
+ chains: [mainnet, sepolia],
38
+ transports: {
39
+ [mainnet.id]: http("https://mainnet.infura.io/v3/YOUR_KEY"),
40
+ [sepolia.id]: http("https://sepolia.infura.io/v3/YOUR_KEY"),
41
+ },
42
+ });
43
+
44
+ const signer = new WagmiSigner(wagmiConfig);
45
+
46
+ const relayer = new RelayerWeb({
47
+ getChainId: () => signer.getChainId(),
48
+ transports: {
49
+ [1]: {
50
+ relayerUrl: "https://relayer.zama.ai",
51
+ network: "https://mainnet.infura.io/v3/YOUR_KEY",
52
+ },
53
+ [11155111]: {
54
+ relayerUrl: "https://relayer.zama.ai",
55
+ network: "https://sepolia.infura.io/v3/YOUR_KEY",
56
+ },
57
+ },
58
+ });
59
+
60
+ function App() {
61
+ return (
62
+ <WagmiProvider config={wagmiConfig}>
63
+ <QueryClientProvider client={queryClient}>
64
+ <TokenSDKProvider relayer={relayer} signer={signer} storage={indexedDBStorage}>
65
+ <TokenBalance />
66
+ </TokenSDKProvider>
67
+ </QueryClientProvider>
68
+ </WagmiProvider>
69
+ );
70
+ }
71
+
72
+ function TokenBalance() {
73
+ const { data: balance, isLoading } = useConfidentialBalance("0xTokenAddress");
74
+
75
+ if (isLoading) return <p>Decrypting balance...</p>;
76
+ return <p>Balance: {balance?.toString()}</p>;
77
+ }
78
+ ```
79
+
80
+ ### With a custom signer
81
+
82
+ ```tsx
83
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
84
+ import {
85
+ TokenSDKProvider,
86
+ RelayerWeb,
87
+ useConfidentialBalance,
88
+ useConfidentialTransfer,
89
+ MemoryStorage,
90
+ } from "@zama-fhe/react-sdk";
91
+
92
+ const queryClient = new QueryClient();
93
+
94
+ const relayer = new RelayerWeb({
95
+ getChainId: () => yourCustomSigner.getChainId(),
96
+ transports: {
97
+ [1]: {
98
+ relayerUrl: "https://relayer.zama.ai",
99
+ network: "https://mainnet.infura.io/v3/YOUR_KEY",
100
+ },
101
+ [11155111]: {
102
+ relayerUrl: "https://relayer.zama.ai",
103
+ network: "https://sepolia.infura.io/v3/YOUR_KEY",
104
+ },
105
+ },
106
+ });
107
+
108
+ function App() {
109
+ return (
110
+ <QueryClientProvider client={queryClient}>
111
+ <TokenSDKProvider relayer={relayer} signer={yourCustomSigner} storage={new MemoryStorage()}>
112
+ <TransferForm />
113
+ </TokenSDKProvider>
114
+ </QueryClientProvider>
115
+ );
116
+ }
117
+
118
+ function TransferForm() {
119
+ const { data: balance } = useConfidentialBalance("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 `TokenSDKProvider`. Create a signer with the adapter for your library, then pass it directly.
143
+
144
+ ```tsx
145
+ import { TokenSDKProvider } from "@zama-fhe/react-sdk";
146
+
147
+ <TokenSDKProvider
148
+ relayer={relayer} // RelayerSDK (RelayerWeb or RelayerNode instance)
149
+ signer={signer} // GenericSigner (WagmiSigner, ViemSigner, EthersSigner, or custom)
150
+ storage={storage} // GenericStringStorage
151
+ >
152
+ {children}
153
+ </TokenSDKProvider>;
154
+ ```
155
+
156
+ ## Hooks Reference
157
+
158
+ All hooks require a `TokenSDKProvider` (or one of its variants) in the component tree.
159
+
160
+ ### SDK Access
161
+
162
+ #### `useTokenSDK`
163
+
164
+ Returns the `TokenSDK` instance from context. Use this when you need direct access to the SDK (e.g. for low-level relayer operations).
165
+
166
+ ```ts
167
+ function useTokenSDK(): TokenSDK;
168
+ ```
169
+
170
+ #### `useToken`
171
+
172
+ 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.
173
+
174
+ ```ts
175
+ function useToken(config: { tokenAddress: Address; wrapperAddress?: Address }): Token;
176
+ ```
177
+
178
+ #### `useReadonlyToken`
179
+
180
+ Returns a `ReadonlyToken` instance for a given token address (no wrapper needed). Memoized.
181
+
182
+ ```ts
183
+ function useReadonlyToken(tokenAddress: Address): ReadonlyToken;
184
+ ```
185
+
186
+ ### Balance Hooks
187
+
188
+ #### `useConfidentialBalance`
189
+
190
+ Single-token balance with automatic decryption. Uses two-phase polling: polls the encrypted handle at a configurable interval, and only triggers the expensive decryption when the handle changes.
191
+
192
+ ```ts
193
+ function useConfidentialBalance(
194
+ tokenAddress: Address,
195
+ owner?: Address, // defaults to connected wallet
196
+ options?: UseConfidentialBalanceOptions,
197
+ ): UseQueryResult<bigint, Error>;
198
+ ```
199
+
200
+ Options extend `UseQueryOptions` and add:
201
+
202
+ | Option | Type | Default | Description |
203
+ | ----------------------- | -------- | ------- | ----------------------------------------------- |
204
+ | `handleRefetchInterval` | `number` | `10000` | Polling interval (ms) for the encrypted handle. |
205
+
206
+ ```tsx
207
+ const {
208
+ data: balance,
209
+ isLoading,
210
+ error,
211
+ } = useConfidentialBalance(
212
+ "0xTokenAddress",
213
+ undefined, // use connected wallet
214
+ { handleRefetchInterval: 5_000 },
215
+ );
216
+ ```
217
+
218
+ #### `useConfidentialBalances`
219
+
220
+ Multi-token batch balance. Same two-phase polling pattern.
221
+
222
+ ```ts
223
+ function useConfidentialBalances(
224
+ tokenAddresses: Address[],
225
+ owner?: Address,
226
+ options?: UseConfidentialBalancesOptions,
227
+ ): UseQueryResult<Map<Address, bigint>, Error>;
228
+ ```
229
+
230
+ ```tsx
231
+ const { data: balances } = useConfidentialBalances(["0xTokenA", "0xTokenB", "0xTokenC"]);
232
+
233
+ // balances is a Map<Address, bigint>
234
+ const tokenABalance = balances?.get("0xTokenA");
235
+ ```
236
+
237
+ ### Authorization
238
+
239
+ #### `useAuthorizeAll`
240
+
241
+ Pre-authorize FHE decrypt credentials for a list of token addresses with a single wallet signature. Call this early (e.g. after loading the token list) so that subsequent individual decrypt operations reuse cached credentials without prompting the wallet again.
242
+
243
+ ```ts
244
+ function useAuthorizeAll(): UseMutationResult<void, Error, Address[]>;
245
+ ```
246
+
247
+ ```tsx
248
+ const { mutateAsync: authorizeAll, isPending } = useAuthorizeAll();
249
+
250
+ // Pre-authorize all known tokens up front
251
+ await authorizeAll(allTokenAddresses);
252
+
253
+ // Individual balance decrypts now reuse cached credentials
254
+ const { data: balance } = useConfidentialBalance("0xTokenA");
255
+ ```
256
+
257
+ ### Transfer Hooks
258
+
259
+ #### `useConfidentialTransfer`
260
+
261
+ Encrypted transfer. Encrypts the amount and calls the contract. Automatically invalidates balance caches on success.
262
+
263
+ ```ts
264
+ function useConfidentialTransfer(
265
+ config: UseTokenConfig,
266
+ options?: UseMutationOptions<Address, Error, ConfidentialTransferParams>,
267
+ ): UseMutationResult<Address, Error, ConfidentialTransferParams>;
268
+
269
+ interface ConfidentialTransferParams {
270
+ to: Address;
271
+ amount: bigint;
272
+ }
273
+ ```
274
+
275
+ ```tsx
276
+ const { mutateAsync: transfer, isPending } = useConfidentialTransfer({
277
+ tokenAddress: "0xTokenAddress",
278
+ });
279
+
280
+ const txHash = await transfer({ to: "0xRecipient", amount: 1000n });
281
+ ```
282
+
283
+ #### `useConfidentialTransferFrom`
284
+
285
+ Operator transfer on behalf of another address.
286
+
287
+ ```ts
288
+ function useConfidentialTransferFrom(
289
+ config: UseTokenConfig,
290
+ options?: UseMutationOptions<Address, Error, ConfidentialTransferFromParams>,
291
+ ): UseMutationResult<Address, Error, ConfidentialTransferFromParams>;
292
+
293
+ interface ConfidentialTransferFromParams {
294
+ from: Address;
295
+ to: Address;
296
+ amount: bigint;
297
+ }
298
+ ```
299
+
300
+ ### Shield Hooks
301
+
302
+ #### `useShield` (alias: `useWrap`)
303
+
304
+ Shield public ERC-20 tokens into confidential tokens. Handles ERC-20 approval automatically.
305
+
306
+ ```ts
307
+ function useShield(
308
+ config: UseTokenConfig,
309
+ options?: UseMutationOptions<Address, Error, ShieldParams>,
310
+ ): UseMutationResult<Address, Error, ShieldParams>;
311
+
312
+ interface ShieldParams {
313
+ amount: bigint;
314
+ approvalStrategy?: "max" | "exact" | "skip"; // default: "exact"
315
+ }
316
+ ```
317
+
318
+ ```tsx
319
+ const { mutateAsync: shield } = useShield({ tokenAddress: "0xTokenAddress" });
320
+
321
+ // Shield 1000 tokens with exact approval (default)
322
+ await shield({ amount: 1000n });
323
+
324
+ // Shield with max approval
325
+ await shield({ amount: 1000n, approvalStrategy: "max" });
326
+ ```
327
+
328
+ #### `useShieldETH` (alias: `useWrapETH`)
329
+
330
+ Shield native ETH into confidential tokens. Use when the underlying token is the zero address (native ETH).
331
+
332
+ ```ts
333
+ function useShieldETH(
334
+ config: UseTokenConfig,
335
+ options?: UseMutationOptions<Address, Error, ShieldETHParams>,
336
+ ): UseMutationResult<Address, Error, ShieldETHParams>;
337
+
338
+ interface ShieldETHParams {
339
+ amount: bigint;
340
+ value?: bigint; // defaults to amount
341
+ }
342
+ ```
343
+
344
+ ### Unshield Hooks (Combined)
345
+
346
+ These hooks orchestrate the full unshield flow in a single call: unwrap → wait for receipt → parse event → finalizeUnwrap. Use these for the simplest integration.
347
+
348
+ #### `useUnshield`
349
+
350
+ Unshield a specific amount. Handles the entire unwrap + finalize flow.
351
+
352
+ ```ts
353
+ function useUnshield(
354
+ config: UseTokenConfig,
355
+ options?: UseMutationOptions<Address, Error, UnshieldParams>,
356
+ ): UseMutationResult<Address, Error, UnshieldParams>;
357
+
358
+ interface UnshieldParams {
359
+ amount: bigint;
360
+ }
361
+ ```
362
+
363
+ ```tsx
364
+ const { mutateAsync: unshield, isPending } = useUnshield({
365
+ tokenAddress: "0xTokenAddress",
366
+ });
367
+
368
+ const finalizeTxHash = await unshield({ amount: 500n });
369
+ ```
370
+
371
+ #### `useUnshieldAll`
372
+
373
+ Unshield the entire balance. Handles the entire unwrap + finalize flow.
374
+
375
+ ```ts
376
+ function useUnshieldAll(
377
+ config: UseTokenConfig,
378
+ options?: UseMutationOptions<Address, Error, void>,
379
+ ): UseMutationResult<Address, Error, void>;
380
+ ```
381
+
382
+ ```tsx
383
+ const { mutateAsync: unshieldAll } = useUnshieldAll({
384
+ tokenAddress: "0xTokenAddress",
385
+ });
386
+
387
+ const finalizeTxHash = await unshieldAll();
388
+ ```
389
+
390
+ ### Unwrap Hooks (Low-Level)
391
+
392
+ These hooks expose the individual unwrap steps. Use them when you need fine-grained control over the flow.
393
+
394
+ #### `useUnwrap`
395
+
396
+ Request unwrap for a specific amount (requires manual finalization via `useFinalizeUnwrap`).
397
+
398
+ ```ts
399
+ function useUnwrap(
400
+ config: UseTokenConfig,
401
+ options?: UseMutationOptions<Address, Error, UnwrapParams>,
402
+ ): UseMutationResult<Address, Error, UnwrapParams>;
403
+
404
+ interface UnwrapParams {
405
+ amount: bigint;
406
+ }
407
+ ```
408
+
409
+ #### `useUnwrapAll`
410
+
411
+ Request unwrap for the entire balance (requires manual finalization).
412
+
413
+ ```ts
414
+ function useUnwrapAll(
415
+ config: UseTokenConfig,
416
+ options?: UseMutationOptions<Address, Error, void>,
417
+ ): UseMutationResult<Address, Error, void>;
418
+ ```
419
+
420
+ #### `useFinalizeUnwrap`
421
+
422
+ Complete an unwrap by providing the decryption proof.
423
+
424
+ ```ts
425
+ function useFinalizeUnwrap(
426
+ config: UseTokenConfig,
427
+ options?: UseMutationOptions<Address, Error, FinalizeUnwrapParams>,
428
+ ): UseMutationResult<Address, Error, FinalizeUnwrapParams>;
429
+
430
+ interface FinalizeUnwrapParams {
431
+ burnAmountHandle: Address;
432
+ }
433
+ ```
434
+
435
+ ### Approval Hooks
436
+
437
+ #### `useConfidentialApprove`
438
+
439
+ Set operator approval for the confidential token.
440
+
441
+ ```ts
442
+ function useConfidentialApprove(
443
+ config: UseTokenConfig,
444
+ options?: UseMutationOptions<Address, Error, ConfidentialApproveParams>,
445
+ ): UseMutationResult<Address, Error, ConfidentialApproveParams>;
446
+
447
+ interface ConfidentialApproveParams {
448
+ spender: Address;
449
+ until?: number; // Unix timestamp, defaults to now + 1 hour
450
+ }
451
+ ```
452
+
453
+ #### `useConfidentialIsApproved`
454
+
455
+ Check if a spender is an approved operator. Enabled only when `spender` is defined.
456
+
457
+ ```ts
458
+ function useConfidentialIsApproved(
459
+ config: UseTokenConfig,
460
+ spender: Address | undefined,
461
+ options?: Omit<UseQueryOptions<boolean, Error>, "queryKey" | "queryFn">,
462
+ ): UseQueryResult<boolean, Error>;
463
+ ```
464
+
465
+ #### `useUnderlyingAllowance`
466
+
467
+ Read the ERC-20 allowance of the underlying token for the wrapper.
468
+
469
+ ```ts
470
+ function useUnderlyingAllowance(
471
+ config: UseUnderlyingAllowanceConfig,
472
+ options?: Omit<UseQueryOptions<bigint, Error>, "queryKey" | "queryFn">,
473
+ ): UseQueryResult<bigint, Error>;
474
+
475
+ interface UseUnderlyingAllowanceConfig {
476
+ tokenAddress: Address;
477
+ wrapperAddress: Address;
478
+ }
479
+ ```
480
+
481
+ ### Discovery & Metadata
482
+
483
+ #### `useWrapperDiscovery`
484
+
485
+ Find the wrapper contract for a given token via the deployment coordinator. Enabled only when `coordinatorAddress` is defined. Results are cached indefinitely (`staleTime: Infinity`).
486
+
487
+ ```ts
488
+ function useWrapperDiscovery(
489
+ config: UseWrapperDiscoveryConfig,
490
+ options?: Omit<UseQueryOptions<Address | null, Error>, "queryKey" | "queryFn">,
491
+ ): UseQueryResult<Address | null, Error>;
492
+
493
+ interface UseWrapperDiscoveryConfig {
494
+ tokenAddress: Address;
495
+ coordinatorAddress: Address | undefined;
496
+ }
497
+ ```
498
+
499
+ #### `useTokenMetadata`
500
+
501
+ Fetch token name, symbol, and decimals in parallel. Cached indefinitely.
502
+
503
+ ```ts
504
+ function useTokenMetadata(
505
+ tokenAddress: Address,
506
+ options?: Omit<UseQueryOptions<TokenMetadata, Error>, "queryKey" | "queryFn">,
507
+ ): UseQueryResult<TokenMetadata, Error>;
508
+
509
+ interface TokenMetadata {
510
+ name: string;
511
+ symbol: string;
512
+ decimals: number;
513
+ }
514
+ ```
515
+
516
+ ```tsx
517
+ const { data: meta } = useTokenMetadata("0xTokenAddress");
518
+ // meta?.name, meta?.symbol, meta?.decimals
519
+ ```
520
+
521
+ ### Activity Feed
522
+
523
+ #### `useActivityFeed`
524
+
525
+ Parse raw event logs into a classified, optionally decrypted activity feed.
526
+
527
+ ```ts
528
+ function useActivityFeed(config: UseActivityFeedConfig): UseQueryResult<ActivityItem[], Error>;
529
+
530
+ interface UseActivityFeedConfig {
531
+ tokenAddress: Address;
532
+ userAddress: Address | undefined;
533
+ logs: readonly (RawLog & Partial<ActivityLogMetadata>)[] | undefined;
534
+ decrypt?: boolean; // default: true — batch-decrypt encrypted amounts
535
+ }
536
+ ```
537
+
538
+ Enabled when both `logs` and `userAddress` are defined. When `decrypt` is `true` (default), encrypted transfer amounts are automatically decrypted via the relayer.
539
+
540
+ ```tsx
541
+ const { data: feed } = useActivityFeed({
542
+ tokenAddress: "0xTokenAddress",
543
+ logs, // from getLogs or a similar source
544
+ userAddress,
545
+ decrypt: true,
546
+ });
547
+
548
+ feed?.forEach((item) => {
549
+ console.log(item.type, item.direction, item.amount);
550
+ });
551
+ ```
552
+
553
+ ### Low-Level FHE Hooks
554
+
555
+ These hooks expose the raw `RelayerSDK` operations as React Query mutations.
556
+
557
+ #### Encryption & Decryption
558
+
559
+ | Hook | Input | Output | Description |
560
+ | --------------------------- | ---------------------------- | ------------------------ | -------------------------------------------------------------------- |
561
+ | `useEncrypt()` | `EncryptParams` | `EncryptResult` | Encrypt values for smart contract calls. |
562
+ | `useUserDecrypt()` | `UserDecryptParams` | `Record<string, bigint>` | Decrypt with user's FHE private key. Populates the decryption cache. |
563
+ | `usePublicDecrypt()` | `string[]` (handles) | `PublicDecryptResult` | Public decryption. Populates the decryption cache. |
564
+ | `useDelegatedUserDecrypt()` | `DelegatedUserDecryptParams` | `Record<string, bigint>` | Decrypt via delegation. |
565
+
566
+ #### Key Management
567
+
568
+ | Hook | Input | Output | Description |
569
+ | --------------------------------------- | ---------------------------------------- | ----------------------------------- | ---------------------------------------------------- |
570
+ | `useGenerateKeypair()` | `void` | `FHEKeypair` | Generate an FHE keypair. |
571
+ | `useCreateEIP712()` | `CreateEIP712Params` | `EIP712TypedData` | Create EIP-712 typed data for decrypt authorization. |
572
+ | `useCreateDelegatedUserDecryptEIP712()` | `CreateDelegatedUserDecryptEIP712Params` | `KmsDelegatedUserDecryptEIP712Type` | Create EIP-712 for delegated decryption. |
573
+ | `useRequestZKProofVerification()` | `ZKProofLike` | `InputProofBytesType` | Submit a ZK proof for verification. |
574
+
575
+ #### Network
576
+
577
+ | Hook | Input | Output | Description |
578
+ | ------------------- | --------------- | ------------------------------------------ | ------------------------------------- |
579
+ | `usePublicKey()` | `void` | `{ publicKeyId, publicKey } \| null` | Get the TFHE compact public key. |
580
+ | `usePublicParams()` | `number` (bits) | `{ publicParams, publicParamsId } \| null` | Get public parameters for encryption. |
581
+
582
+ ### Decryption Cache Hooks
583
+
584
+ `useUserDecrypt` and `usePublicDecrypt` populate a shared React Query cache. These hooks read from that cache without triggering new decryption requests.
585
+
586
+ ```ts
587
+ // Single handle
588
+ function useUserDecryptedValue(handle: string | undefined): UseQueryResult<bigint>;
589
+
590
+ // Multiple handles
591
+ function useUserDecryptedValues(handles: string[]): {
592
+ data: Record<string, bigint | undefined>;
593
+ results: UseQueryResult<bigint>[];
594
+ };
595
+ ```
596
+
597
+ ```tsx
598
+ // First, trigger decryption
599
+ const { mutateAsync: decrypt } = useUserDecrypt();
600
+ await decrypt(decryptParams);
601
+
602
+ // Then read cached results anywhere in the tree
603
+ const { data: value } = useUserDecryptedValue("0xHandleHash");
604
+ ```
605
+
606
+ ## Query Keys
607
+
608
+ Exported query key factories for manual cache management (invalidation, prefetching, removal).
609
+
610
+ ```ts
611
+ import {
612
+ confidentialBalanceQueryKeys,
613
+ confidentialBalancesQueryKeys,
614
+ confidentialHandleQueryKeys,
615
+ confidentialHandlesQueryKeys,
616
+ underlyingAllowanceQueryKeys,
617
+ activityFeedQueryKeys,
618
+ decryptionKeys,
619
+ } from "@zama-fhe/react-sdk";
620
+ ```
621
+
622
+ | Factory | Keys | Description |
623
+ | ------------------------------- | --------------------------------------------------- | ----------------------------------- |
624
+ | `confidentialBalanceQueryKeys` | `.all`, `.token(address)`, `.owner(address, owner)` | Single-token decrypted balance. |
625
+ | `confidentialBalancesQueryKeys` | `.all`, `.tokens(addresses, owner)` | Multi-token batch balances. |
626
+ | `confidentialHandleQueryKeys` | `.all`, `.token(address)`, `.owner(address, owner)` | Single-token encrypted handle. |
627
+ | `confidentialHandlesQueryKeys` | `.all`, `.tokens(addresses, owner)` | Multi-token batch handles. |
628
+ | `underlyingAllowanceQueryKeys` | `.all`, `.token(address, wrapper)` | Underlying ERC-20 allowance. |
629
+ | `activityFeedQueryKeys` | `.all`, `.token(address)` | Activity feed items. |
630
+ | `decryptionKeys` | `.value(handle)` | Individual decrypted handle values. |
631
+
632
+ ```tsx
633
+ import { useQueryClient } from "@tanstack/react-query";
634
+ import { confidentialBalanceQueryKeys } from "@zama-fhe/react-sdk";
635
+
636
+ const queryClient = useQueryClient();
637
+
638
+ // Invalidate all balances
639
+ queryClient.invalidateQueries({ queryKey: confidentialBalanceQueryKeys.all });
640
+
641
+ // Invalidate a specific token's balance
642
+ queryClient.invalidateQueries({
643
+ queryKey: confidentialBalanceQueryKeys.token("0xTokenAddress"),
644
+ });
645
+ ```
646
+
647
+ ## Wagmi Adapter Hooks
648
+
649
+ `@zama-fhe/react-sdk/wagmi` exports low-level hooks that wrap wagmi's `useReadContract` and `useWriteContract` directly. These do **not** use the SDK provider for their contract calls — they operate through wagmi's `Config`. Use them for advanced scenarios where you need fine-grained control.
650
+
651
+ ### Read Hooks
652
+
653
+ | Hook | Parameters | Description |
654
+ | -------------------------------------------- | ------------------------------- | ---------------------------------------------------------- |
655
+ | `useBalanceOf(token, user?)` | Token and optional user address | ERC-20 balance with symbol, decimals, and formatted value. |
656
+ | `useConfidentialBalanceOf(token?, user?)` | Token and user addresses | Read encrypted balance handle. |
657
+ | `useWrapperForToken(coordinator?, token?)` | Coordinator and token addresses | Look up wrapper for token. |
658
+ | `useUnderlyingToken(wrapper?)` | Wrapper address | Read underlying ERC-20 address. |
659
+ | `useWrapperExists(coordinator?, token?)` | Coordinator and token addresses | Check if wrapper exists. |
660
+ | `useSupportsInterface(token?, interfaceId?)` | Token address and interface ID | ERC-165 support check. |
661
+
662
+ All read hooks are enabled only when their required parameters are defined. All read hooks have `*Suspense` variants for use with React Suspense boundaries.
663
+
664
+ ### Write Hooks
665
+
666
+ All write hooks return `{ mutate, mutateAsync, ...mutation }` from wagmi's `useWriteContract`.
667
+
668
+ | Hook | Mutation Parameters | Description |
669
+ | -------------------------------- | ------------------------------------------------ | ----------------------------- |
670
+ | `useConfidentialTransfer()` | `(token, to, handle, inputProof)` | Encrypted transfer. |
671
+ | `useConfidentialBatchTransfer()` | `(batcher, token, from, transfers, fees)` | Batch encrypted transfer. |
672
+ | `useUnwrap()` | `(token, from, to, encryptedAmount, inputProof)` | Request unwrap. |
673
+ | `useUnwrapFromBalance()` | `(token, from, to, encryptedBalance)` | Unwrap using on-chain handle. |
674
+ | `useFinalizeUnwrap()` | `(wrapper, burntAmount, cleartext, proof)` | Finalize unwrap. |
675
+ | `useSetOperator()` | `(token, spender, timestamp?)` | Set operator approval. |
676
+ | `useWrap()` | `(wrapper, to, amount)` | Wrap ERC-20 tokens. |
677
+ | `useWrapETH()` | `(wrapper, to, amount, value)` | Wrap native ETH. |
678
+
679
+ ### Wagmi Signer Adapter
680
+
681
+ ```ts
682
+ import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
683
+
684
+ const signer = new WagmiSigner(wagmiConfig);
685
+ ```
686
+
687
+ ## Viem & Ethers Adapter Hooks
688
+
689
+ Both `@zama-fhe/react-sdk/viem` and `@zama-fhe/react-sdk/ethers` export the same set of read/write hooks, but typed for their respective libraries. They also include `Suspense` variants of all read hooks.
690
+
691
+ ### Read hooks
692
+
693
+ `useConfidentialBalanceOf`, `useWrapperForToken`, `useUnderlyingToken`, `useWrapperExists`, `useSupportsInterface` — plus `*Suspense` variants.
694
+
695
+ - **viem:** First parameter is `PublicClient`.
696
+ - **ethers:** First parameter is `Provider | Signer`.
697
+
698
+ ### Write hooks
699
+
700
+ `useConfidentialTransfer`, `useConfidentialBatchTransfer`, `useUnwrap`, `useUnwrapFromBalance`, `useFinalizeUnwrap`, `useSetOperator`, `useShield`, `useShieldETH`.
701
+
702
+ - **viem:** Mutation params include `client: WalletClient`.
703
+ - **ethers:** Mutation params include `signer: Signer`.
704
+
705
+ ### Signer adapters
706
+
707
+ ```ts
708
+ // Re-exported for convenience
709
+ import { ViemSigner } from "@zama-fhe/react-sdk/viem";
710
+ import { EthersSigner } from "@zama-fhe/react-sdk/ethers";
711
+ ```
712
+
713
+ ## Re-exports from Core SDK
714
+
715
+ 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.
716
+
717
+ **Classes:** `RelayerWeb`, `TokenSDK`, `Token`, `ReadonlyToken`, `MemoryStorage`, `IndexedDBStorage`, `indexedDBStorage`, `CredentialsManager`.
718
+
719
+ **Network configs:** `SepoliaConfig`, `MainnetConfig`, `HardhatConfig`.
720
+
721
+ **Types:** `Address`, `TokenSDKConfig`, `TokenConfig`, `ReadonlyTokenConfig`, `NetworkType`, `RelayerSDK`, `RelayerSDKStatus`, `EncryptResult`, `EncryptParams`, `UserDecryptParams`, `PublicDecryptResult`, `FHEKeypair`, `EIP712TypedData`, `DelegatedUserDecryptParams`, `KmsDelegatedUserDecryptEIP712Type`, `ZKProofLike`, `InputProofBytesType`, `BatchTransferData`, `StoredCredentials`, `GenericSigner`, `GenericStringStorage`, `ContractCallConfig`, `TransactionReceipt`, `UnshieldCallbacks`.
722
+
723
+ **Errors:** `TokenError`, `TokenErrorCode`, `SigningRejectedError`, `SigningFailedError`, `EncryptionFailedError`, `DecryptionFailedError`, `ApprovalFailedError`, `TransactionRevertedError`, `InvalidCredentialsError`, `NoCiphertextError`, `RelayerRequestFailedError`.
724
+
725
+ **Constants:** `ZERO_HANDLE`, `ERC7984_INTERFACE_ID`, `ERC7984_WRAPPER_INTERFACE_ID`.
726
+
727
+ **ABIs:** `ERC20_ABI`, `ERC20_METADATA_ABI`, `DEPLOYMENT_COORDINATOR_ABI`, `ERC165_ABI`, `ENCRYPTION_ABI`, `FEE_MANAGER_ABI`, `TRANSFER_BATCHER_ABI`, `WRAPPER_ABI`, `BATCH_SWAP_ABI`.
728
+
729
+ **Events:** `RawLog`, `ConfidentialTransferEvent`, `WrappedEvent`, `UnwrapRequestedEvent`, `UnwrappedFinalizedEvent`, `UnwrappedStartedEvent`, `TokenEvent`, `Topics`, `TOKEN_TOPICS`.
730
+
731
+ **Event decoders:** `decodeConfidentialTransfer`, `decodeWrapped`, `decodeUnwrapRequested`, `decodeUnwrappedFinalized`, `decodeUnwrappedStarted`, `decodeTokenEvent`, `decodeTokenEvents`, `findUnwrapRequested`, `findWrapped`.
732
+
733
+ **Activity feed:** `ActivityDirection`, `ActivityType`, `ActivityAmount`, `ActivityLogMetadata`, `ActivityItem`, `parseActivityFeed`, `extractEncryptedHandles`, `applyDecryptedValues`, `sortByBlockNumber`.
734
+
735
+ **Contract call builders:** All 31 builders — `confidentialBalanceOfContract`, `confidentialTransferContract`, `confidentialTransferFromContract`, `isOperatorContract`, `confidentialBatchTransferContract`, `unwrapContract`, `unwrapFromBalanceContract`, `finalizeUnwrapContract`, `setOperatorContract`, `getWrapperContract`, `wrapperExistsContract`, `underlyingContract`, `wrapContract`, `wrapETHContract`, `supportsInterfaceContract`, `nameContract`, `symbolContract`, `decimalsContract`, `allowanceContract`, `approveContract`, `confidentialTotalSupplyContract`, `totalSupplyContract`, `rateContract`, `deploymentCoordinatorContract`, `isFinalizeUnwrapOperatorContract`, `setFinalizeUnwrapOperatorContract`, `getWrapFeeContract`, `getUnwrapFeeContract`, `getBatchTransferFeeContract`, `getFeeRecipientContract`.