@zama-fhe/react-sdk 1.0.0-alpha.10
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/LICENSE +28 -0
- package/README.md +1013 -0
- package/dist/index.d.ts +2024 -0
- package/dist/index.js +1151 -0
- package/dist/index.js.map +1 -0
- package/dist/wagmi/index.d.ts +25 -0
- package/dist/wagmi/index.js +76 -0
- package/dist/wagmi/index.js.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,1013 @@
|
|
|
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
|
+
# or
|
|
10
|
+
npm install @zama-fhe/react-sdk @tanstack/react-query
|
|
11
|
+
# or
|
|
12
|
+
yarn add @zama-fhe/react-sdk @tanstack/react-query
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`@zama-fhe/sdk` is included as a direct dependency — no need to install it separately.
|
|
16
|
+
|
|
17
|
+
### Peer dependencies
|
|
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 |
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### With wagmi
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { WagmiProvider, createConfig, http } from "wagmi";
|
|
33
|
+
import { mainnet, sepolia } from "wagmi/chains";
|
|
34
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
35
|
+
import { ZamaProvider, RelayerWeb, indexedDBStorage } from "@zama-fhe/react-sdk";
|
|
36
|
+
import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
|
|
37
|
+
|
|
38
|
+
const wagmiConfig = createConfig({
|
|
39
|
+
chains: [mainnet, sepolia],
|
|
40
|
+
transports: {
|
|
41
|
+
[mainnet.id]: http("https://mainnet.infura.io/v3/YOUR_KEY"),
|
|
42
|
+
[sepolia.id]: http("https://sepolia.infura.io/v3/YOUR_KEY"),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const signer = new WagmiSigner({ config: wagmiConfig });
|
|
47
|
+
|
|
48
|
+
const relayer = new RelayerWeb({
|
|
49
|
+
getChainId: () => signer.getChainId(),
|
|
50
|
+
transports: {
|
|
51
|
+
[mainnet.id]: {
|
|
52
|
+
relayerUrl: "https://your-app.com/api/relayer/1",
|
|
53
|
+
network: "https://mainnet.infura.io/v3/YOUR_KEY",
|
|
54
|
+
},
|
|
55
|
+
[sepolia.id]: {
|
|
56
|
+
relayerUrl: "https://your-app.com/api/relayer/11155111",
|
|
57
|
+
network: "https://sepolia.infura.io/v3/YOUR_KEY",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const queryClient = new QueryClient();
|
|
63
|
+
|
|
64
|
+
function App() {
|
|
65
|
+
return (
|
|
66
|
+
<WagmiProvider config={wagmiConfig}>
|
|
67
|
+
<QueryClientProvider client={queryClient}>
|
|
68
|
+
<ZamaProvider relayer={relayer} signer={signer} storage={indexedDBStorage}>
|
|
69
|
+
<TokenBalance />
|
|
70
|
+
</ZamaProvider>
|
|
71
|
+
</QueryClientProvider>
|
|
72
|
+
</WagmiProvider>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function TokenBalance() {
|
|
77
|
+
const { data: balance, isLoading } = useConfidentialBalance({ tokenAddress: "0xTokenAddress" });
|
|
78
|
+
|
|
79
|
+
if (isLoading) return <p>Decrypting balance...</p>;
|
|
80
|
+
return <p>Balance: {balance?.toString()}</p>;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### With a custom signer
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
88
|
+
import { mainnet, sepolia } from "wagmi/chains"; // or define your own chain IDs
|
|
89
|
+
import {
|
|
90
|
+
ZamaProvider,
|
|
91
|
+
RelayerWeb,
|
|
92
|
+
useConfidentialBalance,
|
|
93
|
+
useConfidentialTransfer,
|
|
94
|
+
memoryStorage,
|
|
95
|
+
} from "@zama-fhe/react-sdk";
|
|
96
|
+
|
|
97
|
+
const relayer = new RelayerWeb({
|
|
98
|
+
getChainId: () => yourCustomSigner.getChainId(),
|
|
99
|
+
transports: {
|
|
100
|
+
[mainnet.id]: {
|
|
101
|
+
relayerUrl: "https://your-app.com/api/relayer/1",
|
|
102
|
+
network: "https://mainnet.infura.io/v3/YOUR_KEY",
|
|
103
|
+
},
|
|
104
|
+
[sepolia.id]: {
|
|
105
|
+
relayerUrl: "https://your-app.com/api/relayer/11155111",
|
|
106
|
+
network: "https://sepolia.infura.io/v3/YOUR_KEY",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const queryClient = new QueryClient();
|
|
112
|
+
|
|
113
|
+
function App() {
|
|
114
|
+
return (
|
|
115
|
+
<QueryClientProvider client={queryClient}>
|
|
116
|
+
<ZamaProvider relayer={relayer} signer={yourCustomSigner} storage={memoryStorage}>
|
|
117
|
+
<TransferForm />
|
|
118
|
+
</ZamaProvider>
|
|
119
|
+
</QueryClientProvider>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function TransferForm() {
|
|
124
|
+
const { data: balance } = useConfidentialBalance({ tokenAddress: "0xTokenAddress" });
|
|
125
|
+
const { mutateAsync: transfer, isPending } = useConfidentialTransfer({
|
|
126
|
+
tokenAddress: "0xTokenAddress",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const handleTransfer = async () => {
|
|
130
|
+
const txHash = await transfer({ to: "0xRecipient", amount: 100n });
|
|
131
|
+
console.log("Transfer tx:", txHash);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div>
|
|
136
|
+
<p>Balance: {balance?.toString()}</p>
|
|
137
|
+
<button onClick={handleTransfer} disabled={isPending}>
|
|
138
|
+
{isPending ? "Transferring..." : "Send 100 tokens"}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Provider Setup
|
|
146
|
+
|
|
147
|
+
All setups use `ZamaProvider`. Create a signer with the adapter for your library, then pass it directly.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { ZamaProvider } from "@zama-fhe/react-sdk";
|
|
151
|
+
|
|
152
|
+
<ZamaProvider
|
|
153
|
+
relayer={relayer} // RelayerSDK (RelayerWeb or RelayerNode instance)
|
|
154
|
+
signer={signer} // GenericSigner (WagmiSigner, ViemSigner, EthersSigner, or custom)
|
|
155
|
+
storage={storage} // GenericStorage
|
|
156
|
+
sessionStorage={sessionStorage} // Optional. Session storage for wallet signatures. Default: in-memory (lost on reload).
|
|
157
|
+
credentialDurationDays={1} // Optional. Days FHE credentials remain valid. Default: 1. Set 0 for sign-every-time.
|
|
158
|
+
onEvent={(event) => console.debug(event)} // Optional. Structured event listener for debugging.
|
|
159
|
+
>
|
|
160
|
+
{children}
|
|
161
|
+
</ZamaProvider>;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Which Hooks Should I Use?
|
|
165
|
+
|
|
166
|
+
The React SDK exports hooks from two layers. **Pick one layer per operation — never mix them.**
|
|
167
|
+
|
|
168
|
+
**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:
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useShield, useConfidentialTransfer } from "@zama-fhe/react-sdk";
|
|
172
|
+
|
|
173
|
+
const { mutateAsync: shield } = useShield({ tokenAddress });
|
|
174
|
+
await shield({ amount: 1000n }); // encryption + approval handled for you
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { ViemSigner } from "@zama-fhe/sdk/viem";
|
|
179
|
+
import { EthersSigner } from "@zama-fhe/sdk/ethers";
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The `WagmiSigner` is the only adapter in the react-sdk since wagmi is React-specific:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Hooks Reference
|
|
189
|
+
|
|
190
|
+
All hooks require a `ZamaProvider` (or one of its variants) in the component tree.
|
|
191
|
+
|
|
192
|
+
### SDK Access
|
|
193
|
+
|
|
194
|
+
#### `useZamaSDK`
|
|
195
|
+
|
|
196
|
+
Returns the `ZamaSDK` instance from context. Use this when you need direct access to the SDK (e.g. for low-level relayer operations).
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
function useZamaSDK(): ZamaSDK;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### `useToken`
|
|
203
|
+
|
|
204
|
+
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.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
function useToken(config: { tokenAddress: Address; wrapperAddress?: Address }): Token;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### `useReadonlyToken`
|
|
211
|
+
|
|
212
|
+
Returns a `ReadonlyToken` instance for a given token address (no wrapper needed). Memoized.
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
function useReadonlyToken(tokenAddress: Address): ReadonlyToken;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Balance Hooks
|
|
219
|
+
|
|
220
|
+
#### `useConfidentialBalance`
|
|
221
|
+
|
|
222
|
+
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.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
function useConfidentialBalance(
|
|
226
|
+
config: UseConfidentialBalanceConfig,
|
|
227
|
+
options?: UseConfidentialBalanceOptions,
|
|
228
|
+
): UseQueryResult<bigint, Error>;
|
|
229
|
+
|
|
230
|
+
interface UseConfidentialBalanceConfig {
|
|
231
|
+
tokenAddress: Address;
|
|
232
|
+
handleRefetchInterval?: number; // default: 10000ms
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Options extend `UseQueryOptions`.
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
const {
|
|
240
|
+
data: balance,
|
|
241
|
+
isLoading,
|
|
242
|
+
error,
|
|
243
|
+
} = useConfidentialBalance({
|
|
244
|
+
tokenAddress: "0xTokenAddress",
|
|
245
|
+
handleRefetchInterval: 5_000,
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### `useConfidentialBalances`
|
|
250
|
+
|
|
251
|
+
Multi-token batch balance. Same two-phase polling pattern.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
function useConfidentialBalances(
|
|
255
|
+
config: UseConfidentialBalancesConfig,
|
|
256
|
+
options?: UseConfidentialBalancesOptions,
|
|
257
|
+
): UseQueryResult<Map<Address, bigint>, Error>;
|
|
258
|
+
|
|
259
|
+
interface UseConfidentialBalancesConfig {
|
|
260
|
+
tokenAddresses: Address[];
|
|
261
|
+
handleRefetchInterval?: number;
|
|
262
|
+
maxConcurrency?: number;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
const { data: balances } = useConfidentialBalances({
|
|
268
|
+
tokenAddresses: ["0xTokenA", "0xTokenB", "0xTokenC"],
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// balances is a Map<Address, bigint>
|
|
272
|
+
const tokenABalance = balances?.get("0xTokenA");
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Authorization
|
|
276
|
+
|
|
277
|
+
#### `useTokenAllow`
|
|
278
|
+
|
|
279
|
+
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.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
function useTokenAllow(): UseMutationResult<void, Error, Address[]>;
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
const { mutateAsync: tokenAllow, isPending } = useTokenAllow();
|
|
287
|
+
|
|
288
|
+
// Pre-authorize all known tokens up front
|
|
289
|
+
await tokenAllow(allTokenAddresses);
|
|
290
|
+
|
|
291
|
+
// Individual balance decrypts now reuse cached credentials
|
|
292
|
+
const { data: balance } = useConfidentialBalance("0xTokenA");
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### `useIsTokenAllowed`
|
|
296
|
+
|
|
297
|
+
Check whether a session signature is cached for a given token. Returns `true` if decrypt operations can proceed without a wallet prompt. Use this to conditionally enable UI elements (e.g. a "Reveal Balances" button).
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
function useIsTokenAllowed(tokenAddress: Address): UseQueryResult<boolean, Error>;
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
const { data: allowed } = useIsTokenAllowed("0xTokenAddress");
|
|
305
|
+
|
|
306
|
+
<button disabled={!allowed}>Reveal Balance</button>;
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Automatically invalidated when `useTokenAllow` or `useTokenRevoke` succeed.
|
|
310
|
+
|
|
311
|
+
#### `useTokenRevoke`
|
|
312
|
+
|
|
313
|
+
Revoke the session signature for the connected wallet. Stored credentials remain intact, but the next decrypt operation will require a fresh wallet signature.
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
function useTokenRevoke(): UseMutationResult<void, Error, Address[]>;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
const { mutate: tokenRevoke } = useTokenRevoke();
|
|
321
|
+
|
|
322
|
+
// Revoke session — addresses are included in the credentials:revoked event
|
|
323
|
+
tokenRevoke(["0xTokenA", "0xTokenB"]);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Transfer Hooks
|
|
327
|
+
|
|
328
|
+
#### `useConfidentialTransfer`
|
|
329
|
+
|
|
330
|
+
Encrypted transfer. Encrypts the amount and calls the contract. Automatically invalidates balance caches on success.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
function useConfidentialTransfer(
|
|
334
|
+
config: UseZamaConfig,
|
|
335
|
+
options?: UseMutationOptions<Address, Error, ConfidentialTransferParams>,
|
|
336
|
+
): UseMutationResult<Address, Error, ConfidentialTransferParams>;
|
|
337
|
+
|
|
338
|
+
interface ConfidentialTransferParams {
|
|
339
|
+
to: Address;
|
|
340
|
+
amount: bigint;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
const { mutateAsync: transfer, isPending } = useConfidentialTransfer({
|
|
346
|
+
tokenAddress: "0xTokenAddress",
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const txHash = await transfer({ to: "0xRecipient", amount: 1000n });
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### `useConfidentialTransferFrom`
|
|
353
|
+
|
|
354
|
+
Operator transfer on behalf of another address.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
function useConfidentialTransferFrom(
|
|
358
|
+
config: UseZamaConfig,
|
|
359
|
+
options?: UseMutationOptions<Address, Error, ConfidentialTransferFromParams>,
|
|
360
|
+
): UseMutationResult<Address, Error, ConfidentialTransferFromParams>;
|
|
361
|
+
|
|
362
|
+
interface ConfidentialTransferFromParams {
|
|
363
|
+
from: Address;
|
|
364
|
+
to: Address;
|
|
365
|
+
amount: bigint;
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Shield Hooks
|
|
370
|
+
|
|
371
|
+
#### `useShield`
|
|
372
|
+
|
|
373
|
+
Shield public ERC-20 tokens into confidential tokens. Handles ERC-20 approval automatically.
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
function useShield(
|
|
377
|
+
config: UseZamaConfig,
|
|
378
|
+
options?: UseMutationOptions<Address, Error, ShieldParams>,
|
|
379
|
+
): UseMutationResult<Address, Error, ShieldParams>;
|
|
380
|
+
|
|
381
|
+
interface ShieldParams {
|
|
382
|
+
amount: bigint;
|
|
383
|
+
approvalStrategy?: "max" | "exact" | "skip"; // default: "exact"
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
const { mutateAsync: shield } = useShield({ tokenAddress: "0xTokenAddress" });
|
|
389
|
+
|
|
390
|
+
// Shield 1000 tokens with exact approval (default)
|
|
391
|
+
await shield({ amount: 1000n });
|
|
392
|
+
|
|
393
|
+
// Shield with max approval
|
|
394
|
+
await shield({ amount: 1000n, approvalStrategy: "max" });
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### `useShieldETH`
|
|
398
|
+
|
|
399
|
+
Shield native ETH into confidential tokens. Use when the underlying token is the zero address (native ETH).
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
function useShieldETH(
|
|
403
|
+
config: UseZamaConfig,
|
|
404
|
+
options?: UseMutationOptions<Address, Error, ShieldETHParams>,
|
|
405
|
+
): UseMutationResult<Address, Error, ShieldETHParams>;
|
|
406
|
+
|
|
407
|
+
interface ShieldETHParams {
|
|
408
|
+
amount: bigint;
|
|
409
|
+
value?: bigint; // defaults to amount
|
|
410
|
+
}
|
|
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 {
|
|
428
|
+
amount: bigint;
|
|
429
|
+
callbacks?: UnshieldCallbacks;
|
|
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
|
+
callbacks: {
|
|
441
|
+
onUnwrapSubmitted: (txHash) => console.log("Unwrap tx:", txHash),
|
|
442
|
+
onFinalizing: () => console.log("Finalizing..."),
|
|
443
|
+
onFinalizeSubmitted: (txHash) => console.log("Finalize tx:", txHash),
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
#### `useUnshieldAll`
|
|
449
|
+
|
|
450
|
+
Unshield the entire balance. Handles the entire unwrap + finalize flow. Supports optional progress callbacks.
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
function useUnshieldAll(
|
|
454
|
+
config: UseZamaConfig,
|
|
455
|
+
options?: UseMutationOptions<Address, Error, UnshieldAllParams | void>,
|
|
456
|
+
): UseMutationResult<Address, Error, UnshieldAllParams | void>;
|
|
457
|
+
|
|
458
|
+
interface UnshieldAllParams {
|
|
459
|
+
callbacks?: UnshieldCallbacks;
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
const { mutateAsync: unshieldAll } = useUnshieldAll({
|
|
465
|
+
tokenAddress: "0xTokenAddress",
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const finalizeTxHash = await unshieldAll();
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### `useResumeUnshield`
|
|
472
|
+
|
|
473
|
+
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.
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
function useResumeUnshield(
|
|
477
|
+
config: UseZamaConfig,
|
|
478
|
+
options?: UseMutationOptions<Address, Error, ResumeUnshieldParams>,
|
|
479
|
+
): UseMutationResult<Address, Error, ResumeUnshieldParams>;
|
|
480
|
+
|
|
481
|
+
interface ResumeUnshieldParams {
|
|
482
|
+
unwrapTxHash: Hex;
|
|
483
|
+
callbacks?: UnshieldCallbacks;
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
import { loadPendingUnshield, clearPendingUnshield } from "@zama-fhe/react-sdk";
|
|
489
|
+
|
|
490
|
+
const { mutateAsync: resumeUnshield } = useResumeUnshield({
|
|
491
|
+
tokenAddress: "0xTokenAddress",
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// On mount, check for interrupted unshields
|
|
495
|
+
const pending = await loadPendingUnshield(storage, wrapperAddress);
|
|
496
|
+
if (pending) {
|
|
497
|
+
await resumeUnshield({ unwrapTxHash: pending });
|
|
498
|
+
await clearPendingUnshield(storage, wrapperAddress);
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### Pending Unshield Persistence
|
|
503
|
+
|
|
504
|
+
Save the unwrap tx hash before finalization so interrupted unshields can be resumed after page reloads:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
import {
|
|
508
|
+
savePendingUnshield,
|
|
509
|
+
loadPendingUnshield,
|
|
510
|
+
clearPendingUnshield,
|
|
511
|
+
} from "@zama-fhe/react-sdk";
|
|
512
|
+
|
|
513
|
+
// Save before the finalize step
|
|
514
|
+
await savePendingUnshield(storage, wrapperAddress, unwrapTxHash);
|
|
515
|
+
|
|
516
|
+
// Load on next visit
|
|
517
|
+
const pending = await loadPendingUnshield(storage, wrapperAddress);
|
|
518
|
+
|
|
519
|
+
// Clear after successful finalization
|
|
520
|
+
await clearPendingUnshield(storage, wrapperAddress);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Unwrap Hooks (Low-Level)
|
|
524
|
+
|
|
525
|
+
These hooks expose the individual unwrap steps. Use them when you need fine-grained control over the flow.
|
|
526
|
+
|
|
527
|
+
#### `useUnwrap`
|
|
528
|
+
|
|
529
|
+
Request unwrap for a specific amount (requires manual finalization via `useFinalizeUnwrap`).
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
function useUnwrap(
|
|
533
|
+
config: UseZamaConfig,
|
|
534
|
+
options?: UseMutationOptions<Address, Error, UnwrapParams>,
|
|
535
|
+
): UseMutationResult<Address, Error, UnwrapParams>;
|
|
536
|
+
|
|
537
|
+
interface UnwrapParams {
|
|
538
|
+
amount: bigint;
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### `useUnwrapAll`
|
|
543
|
+
|
|
544
|
+
Request unwrap for the entire balance (requires manual finalization).
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
function useUnwrapAll(
|
|
548
|
+
config: UseZamaConfig,
|
|
549
|
+
options?: UseMutationOptions<Address, Error, void>,
|
|
550
|
+
): UseMutationResult<Address, Error, void>;
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
#### `useFinalizeUnwrap`
|
|
554
|
+
|
|
555
|
+
Complete an unwrap by providing the decryption proof.
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
function useFinalizeUnwrap(
|
|
559
|
+
config: UseZamaConfig,
|
|
560
|
+
options?: UseMutationOptions<Address, Error, FinalizeUnwrapParams>,
|
|
561
|
+
): UseMutationResult<Address, Error, FinalizeUnwrapParams>;
|
|
562
|
+
|
|
563
|
+
interface FinalizeUnwrapParams {
|
|
564
|
+
burnAmountHandle: Address;
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Approval Hooks
|
|
569
|
+
|
|
570
|
+
#### `useConfidentialApprove`
|
|
571
|
+
|
|
572
|
+
Set operator approval for the confidential token.
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
function useConfidentialApprove(
|
|
576
|
+
config: UseZamaConfig,
|
|
577
|
+
options?: UseMutationOptions<Address, Error, ConfidentialApproveParams>,
|
|
578
|
+
): UseMutationResult<Address, Error, ConfidentialApproveParams>;
|
|
579
|
+
|
|
580
|
+
interface ConfidentialApproveParams {
|
|
581
|
+
spender: Address;
|
|
582
|
+
until?: number; // Unix timestamp, defaults to now + 1 hour
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
#### `useConfidentialIsApproved`
|
|
587
|
+
|
|
588
|
+
Check if a spender is an approved operator. Enabled only when `spender` is defined.
|
|
589
|
+
|
|
590
|
+
```ts
|
|
591
|
+
function useConfidentialIsApproved(
|
|
592
|
+
config: UseZamaConfig,
|
|
593
|
+
spender: Address | undefined,
|
|
594
|
+
options?: Omit<UseQueryOptions<boolean, Error>, "queryKey" | "queryFn">,
|
|
595
|
+
): UseQueryResult<boolean, Error>;
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
#### `useUnderlyingAllowance`
|
|
599
|
+
|
|
600
|
+
Read the ERC-20 allowance of the underlying token for the wrapper.
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
function useUnderlyingAllowance(
|
|
604
|
+
config: UseUnderlyingAllowanceConfig,
|
|
605
|
+
options?: Omit<UseQueryOptions<bigint, Error>, "queryKey" | "queryFn">,
|
|
606
|
+
): UseQueryResult<bigint, Error>;
|
|
607
|
+
|
|
608
|
+
interface UseUnderlyingAllowanceConfig {
|
|
609
|
+
tokenAddress: Address;
|
|
610
|
+
wrapperAddress: Address;
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Discovery & Metadata
|
|
615
|
+
|
|
616
|
+
#### `useWrapperDiscovery`
|
|
617
|
+
|
|
618
|
+
Find the wrapper contract for a given token via the deployment coordinator. Enabled only when `coordinatorAddress` is defined. Results are cached indefinitely (`staleTime: Infinity`).
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
function useWrapperDiscovery(
|
|
622
|
+
config: UseWrapperDiscoveryConfig,
|
|
623
|
+
options?: Omit<UseQueryOptions<Address | null, Error>, "queryKey" | "queryFn">,
|
|
624
|
+
): UseQueryResult<Address | null, Error>;
|
|
625
|
+
|
|
626
|
+
interface UseWrapperDiscoveryConfig {
|
|
627
|
+
tokenAddress: Address;
|
|
628
|
+
coordinatorAddress: Address | undefined;
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
#### `useTokenMetadata`
|
|
633
|
+
|
|
634
|
+
Fetch token name, symbol, and decimals in parallel. Cached indefinitely.
|
|
635
|
+
|
|
636
|
+
```ts
|
|
637
|
+
function useTokenMetadata(
|
|
638
|
+
tokenAddress: Address,
|
|
639
|
+
options?: Omit<UseQueryOptions<TokenMetadata, Error>, "queryKey" | "queryFn">,
|
|
640
|
+
): UseQueryResult<TokenMetadata, Error>;
|
|
641
|
+
|
|
642
|
+
interface TokenMetadata {
|
|
643
|
+
name: string;
|
|
644
|
+
symbol: string;
|
|
645
|
+
decimals: number;
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
```tsx
|
|
650
|
+
const { data: meta } = useTokenMetadata("0xTokenAddress");
|
|
651
|
+
// meta?.name, meta?.symbol, meta?.decimals
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Activity Feed
|
|
655
|
+
|
|
656
|
+
#### `useActivityFeed`
|
|
657
|
+
|
|
658
|
+
Parse raw event logs into a classified, optionally decrypted activity feed.
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
function useActivityFeed(config: UseActivityFeedConfig): UseQueryResult<ActivityItem[], Error>;
|
|
662
|
+
|
|
663
|
+
interface UseActivityFeedConfig {
|
|
664
|
+
tokenAddress: Address;
|
|
665
|
+
userAddress: Address | undefined;
|
|
666
|
+
logs: readonly (RawLog & Partial<ActivityLogMetadata>)[] | undefined;
|
|
667
|
+
decrypt?: boolean; // default: true — batch-decrypt encrypted amounts
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Enabled when both `logs` and `userAddress` are defined. When `decrypt` is `true` (default), encrypted transfer amounts are automatically decrypted via the relayer.
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
const { data: feed } = useActivityFeed({
|
|
675
|
+
tokenAddress: "0xTokenAddress",
|
|
676
|
+
logs, // from getLogs or a similar source
|
|
677
|
+
userAddress,
|
|
678
|
+
decrypt: true,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
feed?.forEach((item) => {
|
|
682
|
+
console.log(item.type, item.direction, item.amount);
|
|
683
|
+
});
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Fee Hooks
|
|
687
|
+
|
|
688
|
+
#### `useShieldFee`
|
|
689
|
+
|
|
690
|
+
Read the shield (wrap) fee for a given amount and address pair.
|
|
691
|
+
|
|
692
|
+
```ts
|
|
693
|
+
function useShieldFee(
|
|
694
|
+
config: UseFeeConfig,
|
|
695
|
+
options?: Omit<UseQueryOptions<bigint, Error>, "queryKey" | "queryFn">,
|
|
696
|
+
): UseQueryResult<bigint, Error>;
|
|
697
|
+
|
|
698
|
+
interface UseFeeConfig {
|
|
699
|
+
feeManagerAddress: Address;
|
|
700
|
+
amount: bigint;
|
|
701
|
+
from: Address;
|
|
702
|
+
to: Address;
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
```tsx
|
|
707
|
+
const { data: fee } = useShieldFee({
|
|
708
|
+
feeManagerAddress: "0xFeeManager",
|
|
709
|
+
amount: 1000n,
|
|
710
|
+
from: "0xSender",
|
|
711
|
+
to: "0xReceiver",
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
#### `useUnshieldFee`
|
|
716
|
+
|
|
717
|
+
Read the unshield (unwrap) fee for a given amount and address pair. Same signature as `useShieldFee`.
|
|
718
|
+
|
|
719
|
+
#### `useBatchTransferFee`
|
|
720
|
+
|
|
721
|
+
Read the batch transfer fee from the fee manager.
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
function useBatchTransferFee(
|
|
725
|
+
feeManagerAddress: Address,
|
|
726
|
+
options?: Omit<UseQueryOptions<bigint, Error>, "queryKey" | "queryFn">,
|
|
727
|
+
): UseQueryResult<bigint, Error>;
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
#### `useFeeRecipient`
|
|
731
|
+
|
|
732
|
+
Read the fee recipient address from the fee manager.
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
function useFeeRecipient(
|
|
736
|
+
feeManagerAddress: Address,
|
|
737
|
+
options?: Omit<UseQueryOptions<Address, Error>, "queryKey" | "queryFn">,
|
|
738
|
+
): UseQueryResult<Address, Error>;
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Low-Level FHE Hooks
|
|
742
|
+
|
|
743
|
+
These hooks expose the raw `RelayerSDK` operations as React Query mutations.
|
|
744
|
+
|
|
745
|
+
#### Encryption & Decryption
|
|
746
|
+
|
|
747
|
+
| Hook | Input | Output | Description |
|
|
748
|
+
| --------------------------- | ---------------------------- | ------------------------ | -------------------------------------------------------------------- |
|
|
749
|
+
| `useEncrypt()` | `EncryptParams` | `EncryptResult` | Encrypt values for smart contract calls. |
|
|
750
|
+
| `useUserDecrypt()` | `UserDecryptParams` | `Record<string, bigint>` | Decrypt with user's FHE private key. Populates the decryption cache. |
|
|
751
|
+
| `usePublicDecrypt()` | `string[]` (handles) | `PublicDecryptResult` | Public decryption. Populates the decryption cache. |
|
|
752
|
+
| `useDelegatedUserDecrypt()` | `DelegatedUserDecryptParams` | `Record<string, bigint>` | Decrypt via delegation. |
|
|
753
|
+
|
|
754
|
+
#### Key Management
|
|
755
|
+
|
|
756
|
+
| Hook | Input | Output | Description |
|
|
757
|
+
| --------------------------------------- | ---------------------------------------- | ----------------------------------- | ---------------------------------------------------- |
|
|
758
|
+
| `useGenerateKeypair()` | `void` | `FHEKeypair` | Generate an FHE keypair. |
|
|
759
|
+
| `useCreateEIP712()` | `CreateEIP712Params` | `EIP712TypedData` | Create EIP-712 typed data for decrypt authorization. |
|
|
760
|
+
| `useCreateDelegatedUserDecryptEIP712()` | `CreateDelegatedUserDecryptEIP712Params` | `KmsDelegatedUserDecryptEIP712Type` | Create EIP-712 for delegated decryption. |
|
|
761
|
+
| `useRequestZKProofVerification()` | `ZKProofLike` | `InputProofBytesType` | Submit a ZK proof for verification. |
|
|
762
|
+
|
|
763
|
+
#### Network
|
|
764
|
+
|
|
765
|
+
| Hook | Input | Output | Description |
|
|
766
|
+
| ------------------- | --------------- | ------------------------------------------ | ------------------------------------- |
|
|
767
|
+
| `usePublicKey()` | `void` | `{ publicKeyId, publicKey } \| null` | Get the TFHE compact public key. |
|
|
768
|
+
| `usePublicParams()` | `number` (bits) | `{ publicParams, publicParamsId } \| null` | Get public parameters for encryption. |
|
|
769
|
+
|
|
770
|
+
### Decryption Cache Hooks
|
|
771
|
+
|
|
772
|
+
`useUserDecrypt` and `usePublicDecrypt` populate a shared React Query cache. These hooks read from that cache without triggering new decryption requests.
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
// Single handle
|
|
776
|
+
function useUserDecryptedValue(handle: string | undefined): UseQueryResult<bigint>;
|
|
777
|
+
|
|
778
|
+
// Multiple handles
|
|
779
|
+
function useUserDecryptedValues(handles: string[]): {
|
|
780
|
+
data: Record<string, bigint | undefined>;
|
|
781
|
+
results: UseQueryResult<bigint>[];
|
|
782
|
+
};
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
```tsx
|
|
786
|
+
// First, trigger decryption
|
|
787
|
+
const { mutateAsync: decrypt } = useUserDecrypt();
|
|
788
|
+
await decrypt(decryptParams);
|
|
789
|
+
|
|
790
|
+
// Then read cached results anywhere in the tree
|
|
791
|
+
const { data: value } = useUserDecryptedValue("0xHandleHash");
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
## Query Keys
|
|
795
|
+
|
|
796
|
+
Exported query key factories for manual cache management (invalidation, prefetching, removal).
|
|
797
|
+
|
|
798
|
+
```ts
|
|
799
|
+
import {
|
|
800
|
+
confidentialBalanceQueryKeys,
|
|
801
|
+
confidentialBalancesQueryKeys,
|
|
802
|
+
confidentialHandleQueryKeys,
|
|
803
|
+
confidentialHandlesQueryKeys,
|
|
804
|
+
isAllowedQueryKeys,
|
|
805
|
+
underlyingAllowanceQueryKeys,
|
|
806
|
+
activityFeedQueryKeys,
|
|
807
|
+
feeQueryKeys,
|
|
808
|
+
decryptionKeys,
|
|
809
|
+
} from "@zama-fhe/react-sdk";
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
| Factory | Keys | Description |
|
|
813
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
814
|
+
| `confidentialBalanceQueryKeys` | `.all`, `.token(address)`, `.owner(address, owner)` | Single-token decrypted balance. |
|
|
815
|
+
| `confidentialBalancesQueryKeys` | `.all`, `.tokens(addresses, owner)` | Multi-token batch balances. |
|
|
816
|
+
| `confidentialHandleQueryKeys` | `.all`, `.token(address)`, `.owner(address, owner)` | Single-token encrypted handle. |
|
|
817
|
+
| `confidentialHandlesQueryKeys` | `.all`, `.tokens(addresses, owner)` | Multi-token batch handles. |
|
|
818
|
+
| `isAllowedQueryKeys` | `.all`, `.token(address)` | Session signature status. |
|
|
819
|
+
| `underlyingAllowanceQueryKeys` | `.all`, `.token(address, wrapper)` | Underlying ERC-20 allowance. |
|
|
820
|
+
| `activityFeedQueryKeys` | `.all`, `.token(address)` | Activity feed items. |
|
|
821
|
+
| `feeQueryKeys` | `.shieldFee(...)`, `.unshieldFee(...)`, `.batchTransferFee(addr)`, `.feeRecipient(addr)` | Fee manager queries. |
|
|
822
|
+
| `decryptionKeys` | `.value(handle)` | Individual decrypted handle values. |
|
|
823
|
+
|
|
824
|
+
```tsx
|
|
825
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
826
|
+
import { confidentialBalanceQueryKeys } from "@zama-fhe/react-sdk";
|
|
827
|
+
|
|
828
|
+
const queryClient = useQueryClient();
|
|
829
|
+
|
|
830
|
+
// Invalidate all balances
|
|
831
|
+
queryClient.invalidateQueries({ queryKey: confidentialBalanceQueryKeys.all });
|
|
832
|
+
|
|
833
|
+
// Invalidate a specific token's balance
|
|
834
|
+
queryClient.invalidateQueries({
|
|
835
|
+
queryKey: confidentialBalanceQueryKeys.token("0xTokenAddress"),
|
|
836
|
+
});
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
## Wagmi Adapter Hooks
|
|
840
|
+
|
|
841
|
+
`@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.
|
|
842
|
+
|
|
843
|
+
### Read Hooks
|
|
844
|
+
|
|
845
|
+
| Hook | Parameters | Description |
|
|
846
|
+
| -------------------------------------------- | ------------------------------- | ---------------------------------------------------------- |
|
|
847
|
+
| `useBalanceOf(token, user?)` | Token and optional user address | ERC-20 balance with symbol, decimals, and formatted value. |
|
|
848
|
+
| `useConfidentialBalanceOf(token?, user?)` | Token and user addresses | Read encrypted balance handle. |
|
|
849
|
+
| `useWrapperForToken(coordinator?, token?)` | Coordinator and token addresses | Look up wrapper for token. |
|
|
850
|
+
| `useUnderlyingToken(wrapper?)` | Wrapper address | Read underlying ERC-20 address. |
|
|
851
|
+
| `useWrapperExists(coordinator?, token?)` | Coordinator and token addresses | Check if wrapper exists. |
|
|
852
|
+
| `useSupportsInterface(token?, interfaceId?)` | Token address and interface ID | ERC-165 support check. |
|
|
853
|
+
|
|
854
|
+
All read hooks are enabled only when their required parameters are defined. All read hooks have `*Suspense` variants for use with React Suspense boundaries.
|
|
855
|
+
|
|
856
|
+
### Write Hooks
|
|
857
|
+
|
|
858
|
+
All write hooks return `{ mutate, mutateAsync, ...mutation }` from wagmi's `useWriteContract`.
|
|
859
|
+
|
|
860
|
+
| Hook | Mutation Parameters | Description |
|
|
861
|
+
| -------------------------------- | ------------------------------------------------ | ----------------------------- |
|
|
862
|
+
| `useConfidentialTransfer()` | `(token, to, handle, inputProof)` | Encrypted transfer. |
|
|
863
|
+
| `useConfidentialBatchTransfer()` | `(batcher, token, from, transfers, fees)` | Batch encrypted transfer. |
|
|
864
|
+
| `useUnwrap()` | `(token, from, to, encryptedAmount, inputProof)` | Request unwrap. |
|
|
865
|
+
| `useUnwrapFromBalance()` | `(token, from, to, encryptedBalance)` | Unwrap using on-chain handle. |
|
|
866
|
+
| `useFinalizeUnwrap()` | `(wrapper, burntAmount, cleartext, proof)` | Finalize unwrap. |
|
|
867
|
+
| `useSetOperator()` | `(token, spender, timestamp?)` | Set operator approval. |
|
|
868
|
+
| `useShield()` | `(wrapper, to, amount)` | Shield ERC-20 tokens. |
|
|
869
|
+
| `useShieldETH()` | `(wrapper, to, amount, value)` | Shield native ETH. |
|
|
870
|
+
|
|
871
|
+
### Wagmi Signer Adapter
|
|
872
|
+
|
|
873
|
+
```ts
|
|
874
|
+
import { WagmiSigner } from "@zama-fhe/react-sdk/wagmi";
|
|
875
|
+
|
|
876
|
+
const signer = new WagmiSigner({ config: wagmiConfig });
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
## Signer Adapters
|
|
880
|
+
|
|
881
|
+
Signer adapters are provided by the core SDK package:
|
|
882
|
+
|
|
883
|
+
```ts
|
|
884
|
+
import { ViemSigner } from "@zama-fhe/sdk/viem";
|
|
885
|
+
import { EthersSigner } from "@zama-fhe/sdk/ethers";
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
## Wallet Integration Guide
|
|
889
|
+
|
|
890
|
+
### SSR / Next.js
|
|
891
|
+
|
|
892
|
+
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.
|
|
893
|
+
|
|
894
|
+
```tsx
|
|
895
|
+
"use client";
|
|
896
|
+
|
|
897
|
+
import { useConfidentialBalance } from "@zama-fhe/react-sdk";
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
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.
|
|
901
|
+
|
|
902
|
+
### FHE Credentials Lifecycle
|
|
903
|
+
|
|
904
|
+
FHE decrypt credentials are generated once per wallet + token 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:
|
|
905
|
+
|
|
906
|
+
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.
|
|
907
|
+
2. **Same session** — Cached credentials and session signature are reused silently (no wallet prompt).
|
|
908
|
+
3. **Page reload** — Encrypted credentials are loaded from storage; the wallet is prompted once to re-sign for the session.
|
|
909
|
+
4. **Expiry** — Credentials expire based on `credentialDurationDays`. After expiry, the next decrypt regenerates and re-prompts.
|
|
910
|
+
5. **Pre-authorization** — Call `useTokenAllow(tokenAddresses)` early to batch-authorize all tokens in one wallet prompt, avoiding repeated popups.
|
|
911
|
+
6. **Check status** — Use `useIsTokenAllowed(tokenAddress)` to conditionally enable UI elements (e.g. disable "Reveal" until allowed).
|
|
912
|
+
7. **Disconnect** — Call `useTokenRevoke(tokenAddresses)` or `await credentials.revoke()` to clear the session signature from memory.
|
|
913
|
+
|
|
914
|
+
### Web Extension Support
|
|
915
|
+
|
|
916
|
+
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:
|
|
917
|
+
|
|
918
|
+
```tsx
|
|
919
|
+
import { chromeSessionStorage } from "@zama-fhe/react-sdk";
|
|
920
|
+
|
|
921
|
+
<ZamaProvider
|
|
922
|
+
relayer={relayer}
|
|
923
|
+
signer={signer}
|
|
924
|
+
storage={indexedDBStorage}
|
|
925
|
+
sessionStorage={chromeSessionStorage}
|
|
926
|
+
>
|
|
927
|
+
<App />
|
|
928
|
+
</ZamaProvider>;
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
This keeps the encrypted credentials in IndexedDB (persistent) while the unlock signature lives in `chrome.storage.session` (ephemeral, cleared when the browser closes).
|
|
932
|
+
|
|
933
|
+
### Error-to-User-Message Mapping
|
|
934
|
+
|
|
935
|
+
Map SDK errors to user-friendly messages in your UI:
|
|
936
|
+
|
|
937
|
+
```tsx
|
|
938
|
+
import {
|
|
939
|
+
SigningRejectedError,
|
|
940
|
+
EncryptionFailedError,
|
|
941
|
+
DecryptionFailedError,
|
|
942
|
+
TransactionRevertedError,
|
|
943
|
+
ApprovalFailedError,
|
|
944
|
+
} from "@zama-fhe/react-sdk";
|
|
945
|
+
|
|
946
|
+
function getUserMessage(error: Error): string {
|
|
947
|
+
if (error instanceof SigningRejectedError)
|
|
948
|
+
return "Transaction cancelled — please approve in your wallet.";
|
|
949
|
+
if (error instanceof EncryptionFailedError) return "Encryption failed — please try again.";
|
|
950
|
+
if (error instanceof DecryptionFailedError) return "Decryption failed — please try again.";
|
|
951
|
+
if (error instanceof ApprovalFailedError) return "Token approval failed — please try again.";
|
|
952
|
+
if (error instanceof TransactionRevertedError)
|
|
953
|
+
return "Transaction failed on-chain — check your balance.";
|
|
954
|
+
return "An unexpected error occurred.";
|
|
955
|
+
}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
Or use `matchZamaError` for a more concise pattern:
|
|
959
|
+
|
|
960
|
+
```tsx
|
|
961
|
+
import { matchZamaError } from "@zama-fhe/react-sdk";
|
|
962
|
+
|
|
963
|
+
const message = matchZamaError(error, {
|
|
964
|
+
SIGNING_REJECTED: () => "Transaction cancelled — please approve in your wallet.",
|
|
965
|
+
ENCRYPTION_FAILED: () => "Encryption failed — please try again.",
|
|
966
|
+
DECRYPTION_FAILED: () => "Decryption failed — please try again.",
|
|
967
|
+
APPROVAL_FAILED: () => "Token approval failed — please try again.",
|
|
968
|
+
TRANSACTION_REVERTED: () => "Transaction failed on-chain — check your balance.",
|
|
969
|
+
_: () => "An unexpected error occurred.",
|
|
970
|
+
});
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
### Balance Caching and Refresh
|
|
974
|
+
|
|
975
|
+
Balance queries use two-phase polling:
|
|
976
|
+
|
|
977
|
+
1. **Phase 1 (cheap)** — Polls the encrypted balance handle via a read-only RPC call at `handleRefetchInterval` (default: 10s).
|
|
978
|
+
2. **Phase 2 (expensive)** — Only when the handle changes (i.e. balance updated on-chain), triggers an FHE decryption via the relayer.
|
|
979
|
+
|
|
980
|
+
This means balances update within `handleRefetchInterval` ms of any on-chain change, without wasting decryption resources. Mutation hooks (`useConfidentialTransfer`, `useShield`, `useUnshield`, etc.) automatically invalidate the relevant caches on success, so the UI updates immediately after user actions.
|
|
981
|
+
|
|
982
|
+
To force a refresh:
|
|
983
|
+
|
|
984
|
+
```tsx
|
|
985
|
+
const queryClient = useQueryClient();
|
|
986
|
+
queryClient.invalidateQueries({ queryKey: confidentialBalanceQueryKeys.all });
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
## Re-exports from Core SDK
|
|
990
|
+
|
|
991
|
+
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.
|
|
992
|
+
|
|
993
|
+
**Classes:** `RelayerWeb`, `ZamaSDK`, `Token`, `ReadonlyToken`, `MemoryStorage`, `memoryStorage`, `IndexedDBStorage`, `indexedDBStorage`, `CredentialsManager`.
|
|
994
|
+
|
|
995
|
+
**Network configs:** `SepoliaConfig`, `MainnetConfig`, `HardhatConfig`.
|
|
996
|
+
|
|
997
|
+
**Pending unshield:** `savePendingUnshield`, `loadPendingUnshield`, `clearPendingUnshield`.
|
|
998
|
+
|
|
999
|
+
**Types:** `Address`, `ZamaSDKConfig`, `ZamaConfig`, `ReadonlyTokenConfig`, `NetworkType`, `RelayerSDK`, `RelayerSDKStatus`, `EncryptResult`, `EncryptParams`, `UserDecryptParams`, `PublicDecryptResult`, `FHEKeypair`, `EIP712TypedData`, `DelegatedUserDecryptParams`, `KmsDelegatedUserDecryptEIP712Type`, `ZKProofLike`, `InputProofBytesType`, `BatchTransferData`, `StoredCredentials`, `GenericSigner`, `GenericStorage`, `ContractCallConfig`, `TransactionReceipt`, `TransactionResult`, `UnshieldCallbacks`.
|
|
1000
|
+
|
|
1001
|
+
**Errors:** `ZamaError`, `ZamaErrorCode`, `SigningRejectedError`, `SigningFailedError`, `EncryptionFailedError`, `DecryptionFailedError`, `ApprovalFailedError`, `TransactionRevertedError`, `InvalidCredentialsError`, `NoCiphertextError`, `RelayerRequestFailedError`, `matchZamaError`.
|
|
1002
|
+
|
|
1003
|
+
**Constants:** `ZERO_HANDLE`, `ERC7984_INTERFACE_ID`, `ERC7984_WRAPPER_INTERFACE_ID`.
|
|
1004
|
+
|
|
1005
|
+
**ABIs:** `ERC20_ABI`, `ERC20_METADATA_ABI`, `DEPLOYMENT_COORDINATOR_ABI`, `ERC165_ABI`, `ENCRYPTION_ABI`, `FEE_MANAGER_ABI`, `TRANSFER_BATCHER_ABI`, `WRAPPER_ABI`, `BATCH_SWAP_ABI`.
|
|
1006
|
+
|
|
1007
|
+
**Events:** `RawLog`, `ConfidentialTransferEvent`, `WrappedEvent`, `UnwrapRequestedEvent`, `UnwrappedFinalizedEvent`, `UnwrappedStartedEvent`, `OnChainEvent`, `Topics`, `TOKEN_TOPICS`.
|
|
1008
|
+
|
|
1009
|
+
**Event decoders:** `decodeConfidentialTransfer`, `decodeWrapped`, `decodeUnwrapRequested`, `decodeUnwrappedFinalized`, `decodeUnwrappedStarted`, `decodeOnChainEvent`, `decodeOnChainEvents`, `findUnwrapRequested`, `findWrapped`.
|
|
1010
|
+
|
|
1011
|
+
**Activity feed:** `ActivityDirection`, `ActivityType`, `ActivityAmount`, `ActivityLogMetadata`, `ActivityItem`, `parseActivityFeed`, `extractEncryptedHandles`, `applyDecryptedValues`, `sortByBlockNumber`.
|
|
1012
|
+
|
|
1013
|
+
**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`.
|