@zama-fhe/sdk 1.0.0 → 1.0.1

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.
Files changed (57) hide show
  1. package/README.md +237 -84
  2. package/dist/activity-CmR2x4Bb.d.ts +969 -0
  3. package/dist/activity-fyiIrk3D.js +2 -0
  4. package/dist/activity-fyiIrk3D.js.map +1 -0
  5. package/dist/cleartext/index.d.ts +49 -0
  6. package/dist/cleartext/index.js +2 -0
  7. package/dist/cleartext/index.js.map +1 -0
  8. package/dist/eip1193-subscribe-DVgPhzbc.js +2 -0
  9. package/dist/eip1193-subscribe-DVgPhzbc.js.map +1 -0
  10. package/dist/erc165-C9GjJpLc.js +2 -0
  11. package/dist/erc165-C9GjJpLc.js.map +1 -0
  12. package/dist/errors-B0aFtthv.js +2 -0
  13. package/dist/errors-B0aFtthv.js.map +1 -0
  14. package/dist/ethers/index.d.ts +74 -30
  15. package/dist/ethers/index.js +1 -130
  16. package/dist/ethers/index.js.map +1 -1
  17. package/dist/index-XNCT2KaE.d.ts +27848 -0
  18. package/dist/index.d.ts +4685 -32883
  19. package/dist/index.js +1 -2861
  20. package/dist/index.js.map +1 -1
  21. package/dist/node/index.d.ts +152 -110
  22. package/dist/node/index.js +1 -316
  23. package/dist/node/index.js.map +1 -1
  24. package/dist/query/index.d.ts +528 -0
  25. package/dist/query/index.js +2 -0
  26. package/dist/query/index.js.map +1 -0
  27. package/dist/relayer-sdk-BHnGxGkv.d.ts +41 -0
  28. package/dist/relayer-sdk.node-worker.d.ts +1 -2
  29. package/dist/relayer-sdk.node-worker.js +1 -319
  30. package/dist/relayer-sdk.node-worker.js.map +1 -1
  31. package/dist/relayer-sdk.types-DQ1e-2NV.d.ts +329 -0
  32. package/dist/relayer-sdk.worker.js +1 -400
  33. package/dist/relayer-sdk.worker.js.map +1 -1
  34. package/dist/relayer-utils-BRMmJpoV.d.ts +51 -0
  35. package/dist/relayer-utils-DOqdqWEX.js +2 -0
  36. package/dist/relayer-utils-DOqdqWEX.js.map +1 -0
  37. package/dist/token.types-BCq4YUya.d.ts +419 -0
  38. package/dist/transfer-batcher-yW5FnZ-A.js +2 -0
  39. package/dist/transfer-batcher-yW5FnZ-A.js.map +1 -0
  40. package/dist/utils-AlGTGj_5.js +2 -0
  41. package/dist/utils-AlGTGj_5.js.map +1 -0
  42. package/dist/viem/index.d.ts +40 -20
  43. package/dist/viem/index.js +1 -136
  44. package/dist/viem/index.js.map +1 -1
  45. package/dist/worker.base-client-DGGSkczN.js +2 -0
  46. package/dist/worker.base-client-DGGSkczN.js.map +1 -0
  47. package/package.json +25 -13
  48. package/dist/chunk-AJFSZ47V.js +0 -5115
  49. package/dist/chunk-AJFSZ47V.js.map +0 -1
  50. package/dist/chunk-UE6IBC3M.js +0 -101
  51. package/dist/chunk-UE6IBC3M.js.map +0 -1
  52. package/dist/chunk-VRLLWHHL.js +0 -278
  53. package/dist/chunk-VRLLWHHL.js.map +0 -1
  54. package/dist/relayer-sdk.types-CFkzNzRy.d.ts +0 -293
  55. package/dist/relayer-sdk.worker.d.ts +0 -2
  56. package/dist/relayer-utils-D_3834H0.d.ts +0 -46
  57. package/dist/token.types-CRs1iJh7.d.ts +0 -447
package/README.md CHANGED
@@ -6,6 +6,10 @@ A TypeScript SDK for building privacy-preserving token applications using Fully
6
6
 
7
7
  ```bash
8
8
  pnpm add @zama-fhe/sdk
9
+ # or
10
+ npm install @zama-fhe/sdk
11
+ # or
12
+ yarn add @zama-fhe/sdk
9
13
  ```
10
14
 
11
15
  ### Peer dependencies
@@ -21,22 +25,23 @@ pnpm add @zama-fhe/sdk
21
25
  ### Browser
22
26
 
23
27
  ```ts
24
- import { TokenSDK, RelayerWeb, IndexedDBStorage } from "@zama-fhe/sdk";
28
+ import { ZamaSDK, RelayerWeb, IndexedDBStorage } from "@zama-fhe/sdk";
25
29
  import { ViemSigner } from "@zama-fhe/sdk/viem";
30
+ import { mainnet, sepolia } from "viem/chains";
26
31
 
27
32
  // 1. Create signer and relayer
28
- const signer = new ViemSigner(walletClient, publicClient);
33
+ const signer = new ViemSigner({ walletClient, publicClient });
29
34
 
30
- const sdk = new TokenSDK({
35
+ const sdk = new ZamaSDK({
31
36
  relayer: new RelayerWeb({
32
37
  getChainId: () => signer.getChainId(),
33
38
  transports: {
34
- [1]: {
35
- relayerUrl: "https://relayer.zama.ai",
39
+ [mainnet.id]: {
40
+ relayerUrl: "https://your-app.com/api/relayer/1",
36
41
  network: "https://mainnet.infura.io/v3/YOUR_KEY",
37
42
  },
38
- [11155111]: {
39
- relayerUrl: "https://relayer.zama.ai",
43
+ [sepolia.id]: {
44
+ relayerUrl: "https://your-app.com/api/relayer/11155111",
40
45
  network: "https://sepolia.infura.io/v3/YOUR_KEY",
41
46
  },
42
47
  },
@@ -51,7 +56,7 @@ const token = sdk.createToken("0xEncryptedERC20Address");
51
56
  // const token = sdk.createToken("0xEncryptedERC20Address", "0xWrapperAddress");
52
57
 
53
58
  // 3. Shield (wrap) public tokens into confidential tokens
54
- const wrapTx = await token.wrap(1000n);
59
+ const { txHash } = await token.shield(1000n);
55
60
 
56
61
  // 4. Check decrypted balance
57
62
  const balance = await token.balanceOf();
@@ -64,29 +69,30 @@ const transferTx = await token.confidentialTransfer("0xRecipient", 500n);
64
69
  ### Node.js
65
70
 
66
71
  ```ts
67
- import { TokenSDK, MemoryStorage } from "@zama-fhe/sdk";
68
- import { RelayerNode } from "@zama-fhe/sdk/node";
72
+ import { ZamaSDK } from "@zama-fhe/sdk";
73
+ import { RelayerNode, asyncLocalStorage } from "@zama-fhe/sdk/node";
69
74
  import { ViemSigner } from "@zama-fhe/sdk/viem";
75
+ import { mainnet, sepolia } from "viem/chains";
70
76
 
71
- const signer = new ViemSigner(walletClient, publicClient);
77
+ const signer = new ViemSigner({ walletClient, publicClient });
72
78
 
73
- const sdk = new TokenSDK({
79
+ const sdk = new ZamaSDK({
74
80
  relayer: new RelayerNode({
75
81
  getChainId: () => signer.getChainId(),
76
82
  poolSize: 4, // number of worker threads (default: min(CPUs, 4))
77
83
  transports: {
78
- [1]: {
79
- relayerUrl: "https://relayer.zama.ai",
84
+ [mainnet.id]: {
80
85
  network: "https://mainnet.infura.io/v3/YOUR_KEY",
86
+ auth: { __type: "ApiKeyHeader", value: process.env.RELAYER_API_KEY },
81
87
  },
82
- [11155111]: {
83
- relayerUrl: "https://relayer.zama.ai",
88
+ [sepolia.id]: {
84
89
  network: "https://sepolia.infura.io/v3/YOUR_KEY",
90
+ auth: { __type: "ApiKeyHeader", value: process.env.RELAYER_API_KEY },
85
91
  },
86
92
  },
87
93
  }),
88
94
  signer,
89
- storage: new MemoryStorage(),
95
+ storage: asyncLocalStorage,
90
96
  });
91
97
 
92
98
  const token = sdk.createToken("0xEncryptedERC20Address");
@@ -95,15 +101,15 @@ const balance = await token.balanceOf();
95
101
 
96
102
  ## Core Concepts
97
103
 
98
- ### TokenSDK
104
+ ### ZamaSDK
99
105
 
100
106
  Entry point to the SDK. Composes a relayer backend with a signer and storage layer. Acts as a factory for token instances.
101
107
 
102
108
  ```ts
103
- const sdk = new TokenSDK({
109
+ const sdk = new ZamaSDK({
104
110
  relayer, // RelayerSDK — either RelayerWeb (browser) or RelayerNode (Node.js)
105
111
  signer, // GenericSigner
106
- storage, // GenericStringStorage
112
+ storage, // GenericStorage
107
113
  });
108
114
 
109
115
  // Read-only — balances, metadata, decryption. No wrapper needed.
@@ -137,8 +143,8 @@ Full read/write interface for a single confidential ERC-20. Extends `ReadonlyTok
137
143
 
138
144
  | Method | Description |
139
145
  | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
140
- | `wrap(amount, options?)` | Shield (wrap) public ERC-20 tokens. Handles approval automatically. Options: `{ approvalStrategy: "max" \| "exact" \| "skip" }` (default `"exact"`). `"skip"` bypasses approval (use when already approved). |
141
- | `wrapETH(amount, value?)` | Shield (wrap) native ETH. `value` defaults to `amount`. Use this when the underlying token is the zero address (native ETH). |
146
+ | `shield(amount, options?)` | Shield (wrap) public ERC-20 tokens. Handles approval automatically. Options: `{ approvalStrategy: "max" \| "exact" \| "skip" }` (default `"exact"`). `"skip"` bypasses approval (use when already approved). |
147
+ | `shieldETH(amount, value?)` | Shield (wrap) native ETH. `value` defaults to `amount`. Use this when the underlying token is the zero address (native ETH). |
142
148
  | `unshield(amount, callbacks?)` | Unwrap a specific amount and finalize in one call. Orchestrates: unwrap → wait receipt → parse event → finalizeUnwrap. Optional `UnshieldCallbacks` for progress tracking. |
143
149
  | `unshieldAll(callbacks?)` | Unwrap the entire balance and finalize in one call. Orchestrates: unwrapAll → wait receipt → parse event → finalizeUnwrap. Optional `UnshieldCallbacks` for progress tracking. |
144
150
  | `unwrap(amount)` | Request unwrap for a specific amount (low-level, requires manual finalization). |
@@ -153,80 +159,206 @@ Full read/write interface for a single confidential ERC-20. Extends `ReadonlyTok
153
159
  | `balanceOf(owner?)` | Decrypt and return the plaintext balance. |
154
160
  | `decryptHandles(handles, owner?)` | Batch-decrypt arbitrary encrypted handles. |
155
161
 
156
- All write methods return the transaction hash (`Address`).
162
+ All write methods return a `TransactionResult` object:
163
+
164
+ ```ts
165
+ interface TransactionResult {
166
+ txHash: Hex;
167
+ receipt: TransactionReceipt;
168
+ }
169
+ ```
157
170
 
158
171
  ### ReadonlyToken
159
172
 
160
173
  Read-only subset. No wrapper address needed.
161
174
 
162
- | Method | Description |
163
- | ------------------------------------- | ----------------------------------------------------------------- |
164
- | `balanceOf(owner?)` | Decrypt and return the plaintext balance. |
165
- | `confidentialBalanceOf(owner?)` | Return the raw encrypted balance handle (no decryption). |
166
- | `decryptBalance(handle, owner?)` | Decrypt a single encrypted handle. |
167
- | `decryptHandles(handles, owner?)` | Batch-decrypt handles in a single relayer call. |
168
- | `authorize()` | Ensure FHE decrypt credentials exist (generates/signs if needed). |
169
- | `authorizeAll(tokens)` _(static)_ | Pre-authorize multiple tokens with a single wallet signature. |
170
- | `isConfidential()` | ERC-165 check for ERC-7984 support. |
171
- | `isWrapper()` | ERC-165 check for wrapper interface. |
172
- | `discoverWrapper(coordinatorAddress)` | Look up a wrapper for this token via the deployment coordinator. |
173
- | `underlyingToken()` | Read the underlying ERC-20 address from a wrapper. |
174
- | `allowance(wrapper, owner?)` | Read ERC-20 allowance of the underlying token. |
175
- | `isZeroHandle(handle)` | Returns `true` if the handle is the zero sentinel. |
176
- | `name()` / `symbol()` / `decimals()` | Read token metadata. |
175
+ | Method | Description |
176
+ | ------------------------------------- | --------------------------------------------------------------------------- |
177
+ | `balanceOf(owner?)` | Decrypt and return the plaintext balance. |
178
+ | `confidentialBalanceOf(owner?)` | Return the raw encrypted balance handle (no decryption). |
179
+ | `decryptBalance(handle, owner?)` | Decrypt a single encrypted handle. |
180
+ | `decryptHandles(handles, owner?)` | Batch-decrypt handles in a single relayer call. |
181
+ | `allow()` | Ensure FHE decrypt credentials exist (generates/signs if needed). |
182
+ | `allow(...tokens)` _(static)_ | Pre-authorize multiple tokens with a single wallet signature. |
183
+ | `isAllowed()` | Whether a session signature is currently cached for this token. |
184
+ | `revoke()` | Clear the session signature for the connected wallet. |
185
+ | `credentials.allow(...addresses)` | Pre-authorize and cache the session signature for specific token addresses. |
186
+ | `credentials.revoke(...addresses?)` | Clear the session signature for the connected wallet. |
187
+ | `credentials.isAllowed()` | Whether a session signature is currently cached. |
188
+ | `credentials.isExpired(address?)` | Whether stored credentials are past their expiration time. |
189
+ | `credentials.clear()` | Delete stored credentials for the connected wallet. |
190
+ | `isConfidential()` | ERC-165 check for ERC-7984 support. |
191
+ | `isWrapper()` | ERC-165 check for wrapper interface. |
192
+ | `discoverWrapper(coordinatorAddress)` | Look up a wrapper for this token via the deployment coordinator. |
193
+ | `underlyingToken()` | Read the underlying ERC-20 address from a wrapper. |
194
+ | `allowance(wrapper, owner?)` | Read ERC-20 allowance of the underlying token. |
195
+ | `isZeroHandle(handle)` | Returns `true` if the handle is the zero sentinel. |
196
+ | `name()` / `symbol()` / `decimals()` | Read token metadata. |
177
197
 
178
198
  Static methods for multi-token operations:
179
199
 
180
200
  ```ts
181
201
  // Pre-authorize all tokens with a single wallet signature
182
202
  const tokens = addresses.map((a) => sdk.createReadonlyToken(a));
183
- await ReadonlyToken.authorizeAll(tokens);
203
+ await ReadonlyToken.allow(...tokens);
184
204
  // All subsequent decrypts reuse cached credentials — no more wallet prompts
185
205
 
186
206
  // Decrypt balances for multiple tokens in parallel
187
- const balances = await ReadonlyToken.batchBalanceOf(tokens, owner);
207
+ const balances = await ReadonlyToken.batchDecryptBalances(tokens, { owner });
188
208
 
189
209
  // Decrypt pre-fetched handles for multiple tokens
190
- const balances = await ReadonlyToken.batchDecryptBalances(tokens, handles, owner);
210
+ const balances = await ReadonlyToken.batchDecryptBalances(tokens, { handles, owner });
211
+ ```
212
+
213
+ ### Pending Unshield Persistence
214
+
215
+ The unshield flow is two-phase: unwrap tx, then finalize. If the page reloads between phases, the unwrap tx hash is lost. Use these utilities to persist it:
216
+
217
+ ```ts
218
+ import { savePendingUnshield, loadPendingUnshield, clearPendingUnshield } from "@zama-fhe/sdk";
219
+
220
+ // Save the unwrap hash before finalization
221
+ await savePendingUnshield(storage, wrapperAddress, unwrapTxHash);
222
+
223
+ // On next load, check for pending unshields
224
+ const pending = await loadPendingUnshield(storage, wrapperAddress);
225
+ if (pending) {
226
+ await token.resumeUnshield(pending);
227
+ await clearPendingUnshield(storage, wrapperAddress);
228
+ }
191
229
  ```
192
230
 
193
231
  ### Storage
194
232
 
195
- FHE credentials (keypair + EIP-712 signature) are persisted to storage. Three options:
233
+ FHE credentials (encrypted keypair + metadata) are persisted to `storage`. The wallet signature is kept in `sessionStorage` (in-memory by default) — never written to disk. Two storage roles:
234
+
235
+ **Credential storage** (`storage`) — persists encrypted keypairs:
236
+
237
+ | Storage | Use case |
238
+ | ------------------- | -------------------------------------------------------- |
239
+ | `indexedDBStorage` | Browser apps — persists across page reloads and sessions |
240
+ | `memoryStorage` | Tests, scripts, throwaway sessions |
241
+ | `asyncLocalStorage` | Node.js servers — isolate credentials per request |
242
+ | Custom | Implement the `GenericStorage` interface |
243
+
244
+ **Session storage** (`sessionStorage`) — holds wallet signatures for the current session:
196
245
 
197
- | Storage | Use case |
198
- | ------------------ | ------------------------------------------------- |
199
- | `MemoryStorage` | Testing. In-memory `Map`, lost on page reload. |
200
- | `IndexedDBStorage` | Browser production. IndexedDB-backed, persistent. |
201
- | `indexedDBStorage` | Pre-built singleton `IndexedDBStorage` instance. |
202
- | Custom | Implement the `GenericStringStorage` interface. |
246
+ | Storage | Use case |
247
+ | ---------------------- | ----------------------------------------------------------- |
248
+ | Default (in-memory) | Standard web apps — signature lost on reload, user re-signs |
249
+ | `chromeSessionStorage` | MV3 web extensions survives service worker restarts |
250
+ | Custom | Implement the `GenericStorage` interface |
203
251
 
204
252
  ```ts
205
- interface GenericStringStorage {
206
- getItem(key: string): string | Promise<string | null> | null;
207
- setItem(key: string, value: string): void | Promise<void>;
208
- removeItem(key: string): void | Promise<void>;
253
+ interface GenericStorage<T = unknown> {
254
+ get(key: string): Promise<T | null>;
255
+ set(key: string, value: T): Promise<void>;
256
+ delete(key: string): Promise<void>;
209
257
  }
210
258
  ```
211
259
 
260
+ #### Web Extension Example
261
+
262
+ For MV3 extensions, use the built-in `chromeSessionStorage` singleton to share the wallet signature across popup, background, and content script contexts:
263
+
264
+ ```ts
265
+ import { ZamaSDK, indexedDBStorage, chromeSessionStorage } from "@zama-fhe/sdk";
266
+
267
+ const sdk = new ZamaSDK({
268
+ relayer,
269
+ signer,
270
+ storage: indexedDBStorage, // encrypted keypairs (persistent)
271
+ sessionStorage: chromeSessionStorage, // wallet signatures (ephemeral, shared across contexts)
272
+ });
273
+ ```
274
+
212
275
  ## Configuration Reference
213
276
 
214
- ### `TokenSDKConfig`
277
+ ### `ZamaSDKConfig`
215
278
 
216
- | Field | Type | Description |
217
- | --------- | ---------------------- | -------------------------------------------------------- |
218
- | `relayer` | `RelayerSDK` | Relayer backend (`RelayerWeb` or `RelayerNode` instance) |
219
- | `signer` | `GenericSigner` | Wallet signer interface. |
220
- | `storage` | `GenericStringStorage` | Credential storage backend. |
279
+ | Field | Type | Description |
280
+ | ---------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
281
+ | `relayer` | `RelayerSDK` | Relayer backend (`RelayerWeb` or `RelayerNode` instance) |
282
+ | `signer` | `GenericSigner` | Wallet signer interface. |
283
+ | `storage` | `GenericStorage` | Credential storage backend. |
284
+ | `sessionStorage` | `GenericStorage` | Optional. Session storage for wallet signatures. Default: in-memory (lost on reload). Use `chrome.storage.session` for web extensions. |
285
+ | `keypairTTL` | `number` | Optional. Seconds the ML-KEM re-encryption keypair remains valid. Default: `86400` (1 day). Must be positive. |
286
+ | `sessionTTL` | `number` | Optional. Seconds the session signature remains valid. Default: `2592000` (30 days). `0` = re-sign every operation. |
287
+ | `onEvent` | `ZamaSDKEventListener` | Optional. Structured event listener for debugging. |
288
+
289
+ #### Structured Event Listener
290
+
291
+ The `onEvent` callback receives typed events at key lifecycle points. Event payloads never contain sensitive data (amounts, keys, proofs) — only metadata useful for debugging and telemetry.
292
+
293
+ ```ts
294
+ const sdk = new ZamaSDK({
295
+ relayer,
296
+ signer,
297
+ storage,
298
+ onEvent: ({ type, tokenAddress, ...event }) => {
299
+ console.debug(`[Zama] ${type}`, {
300
+ tokenAddress: tokenAddress?.slice(0, 10),
301
+ ...event,
302
+ });
303
+ },
304
+ });
305
+ ```
306
+
307
+ **Event types:**
308
+
309
+ | Category | Events | Key fields |
310
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
311
+ | Credentials | `credentials:loading`, `credentials:cached`, `credentials:expired`, `credentials:creating`, `credentials:created`, `credentials:revoked`, `credentials:allowed` | `contractAddresses` |
312
+ | Encryption | `encrypt:start`, `encrypt:end`, `encrypt:error` | `durationMs` (end/error), `error` (error) |
313
+ | Decryption | `decrypt:start`, `decrypt:end`, `decrypt:error` | `durationMs` (end/error), `error` (error) |
314
+ | Transactions | `transaction:error` | `operation` (`"transfer"`, `"wrap"`, `"approve"`, etc.), `error` |
315
+ | Write confirmations | `wrap:submitted`, `transfer:submitted`, `transferFrom:submitted`, `approve:submitted`, `approveUnderlying:submitted`, `unwrap:submitted`, `finalizeUnwrap:submitted` | `txHash` |
316
+ | Unshield orchestration | `unshield:phase1_submitted`, `unshield:phase2_started`, `unshield:phase2_submitted` | `txHash`, `operationId` |
317
+
318
+ All events carry `tokenAddress`, `timestamp`, and an optional `operationId` (set on unshield phase events to correlate multi-step operations).
319
+
320
+ **Dispatching events to other systems:**
321
+
322
+ The `onEvent` callback is a simple function — you can bridge it to any event system:
323
+
324
+ ```ts
325
+ // Fan out to multiple listeners with EventEmitter
326
+ import { EventEmitter } from "events";
327
+ const emitter = new EventEmitter();
328
+ const sdk = new ZamaSDK({
329
+ // ...
330
+ onEvent: (event) => emitter.emit(event.type, event),
331
+ });
332
+ emitter.on("encrypt:start", (e) => {
333
+ /* listener A */
334
+ });
335
+ emitter.on("encrypt:start", (e) => {
336
+ /* listener B */
337
+ });
338
+
339
+ // Bridge to DOM CustomEvent (e.g. for cross-framework communication)
340
+ const sdk = new ZamaSDK({
341
+ // ...
342
+ onEvent: (event) => window.dispatchEvent(new CustomEvent(event.type, { detail: event })),
343
+ });
344
+
345
+ // Collect into React state
346
+ const [events, setEvents] = useState<ZamaSDKEvent[]>([]);
347
+ const sdk = new ZamaSDK({
348
+ // ...
349
+ onEvent: (event) => setEvents((prev) => [...prev, event]),
350
+ });
351
+ ```
221
352
 
222
353
  ### `RelayerWebConfig` (browser)
223
354
 
224
- | Field | Type | Description |
225
- | ------------ | ------------------------------------- | -------------------------------------------------------------------------------------------- |
226
- | `getChainId` | `() => Promise<number>` | Resolve the current chain ID. Called lazily; the worker is re-initialized on chain change. |
227
- | `transports` | `Record<number, FhevmInstanceConfig>` | Chain-specific configs keyed by chain ID (includes relayerUrl, network, contract addresses). |
228
- | `security` | `RelayerWebSecurityConfig` | Optional. Security options (see below). |
229
- | `logger` | `GenericLogger` | Optional. Logger for worker lifecycle and request timing. |
355
+ | Field | Type | Description |
356
+ | ------------ | ------------------------------------- | ----------------------------------------------------------------------------------------------- |
357
+ | `getChainId` | `() => Promise<number>` | Resolve the current chain ID. Called lazily; the worker is re-initialized on chain change. |
358
+ | `transports` | `Record<number, FhevmInstanceConfig>` | Chain-specific configs keyed by chain ID (includes relayerUrl, network, contract addresses). |
359
+ | `security` | `RelayerWebSecurityConfig` | Optional. Security options (see below). |
360
+ | `logger` | `GenericLogger` | Optional. Logger for worker lifecycle and request timing. |
361
+ | `threads` | `number` | Optional. WASM thread count for parallel FHE ops (4–8 recommended). Requires COOP/COEP headers. |
230
362
 
231
363
  #### `RelayerWebSecurityConfig`
232
364
 
@@ -235,6 +367,8 @@ interface GenericStringStorage {
235
367
  | `getCsrfToken` | `() => string` | Optional. Resolve the CSRF token before each authenticated network request. |
236
368
  | `integrityCheck` | `boolean` | Optional. Verify SHA-384 integrity of the CDN bundle. Defaults to `true`. Set `false` for tests. |
237
369
 
370
+ > **Security note:** `RelayerWeb` loads FHE WASM from a CDN at runtime. The `integrityCheck` option (enabled by default) verifies the SHA-384 hash of the bundle before execution, protecting against CDN compromise or MITM attacks. Only disable it in local development or testing.
371
+
238
372
  ### `RelayerNodeConfig` (Node.js)
239
373
 
240
374
  | Field | Type | Description |
@@ -252,21 +386,26 @@ Both the main entry (`@zama-fhe/sdk`) and the `/node` sub-path re-export preset
252
386
  | `MainnetConfig` | 1 | Mainnet contract addresses. |
253
387
  | `HardhatConfig` | 31337 | Local Hardhat node addresses. |
254
388
 
255
- Each preset provides contract addresses and default values. Override `relayerUrl` and `network` (RPC URL) for your environment:
389
+ Each preset provides contract addresses and default relayer URL. Override `network` (RPC URL) for your environment. Browser apps should override `relayerUrl` with a proxy; server-side apps add `auth`:
256
390
 
257
391
  ```ts
258
392
  import { SepoliaConfig, MainnetConfig } from "@zama-fhe/sdk";
259
393
 
394
+ // Browser — proxy through your backend
260
395
  const transports = {
261
- [11155111]: {
396
+ [SepoliaConfig.chainId]: {
262
397
  ...SepoliaConfig,
263
- relayerUrl: "/api/proxy",
398
+ relayerUrl: "https://your-app.com/api/relayer/11155111",
264
399
  network: "https://sepolia.infura.io/v3/KEY",
265
400
  },
266
- [1]: {
267
- ...MainnetConfig,
268
- relayerUrl: "/api/proxy",
269
- network: "https://mainnet.infura.io/v3/KEY",
401
+ };
402
+
403
+ // Node.js — auth is safe server-side
404
+ const transports = {
405
+ [SepoliaConfig.chainId]: {
406
+ ...SepoliaConfig,
407
+ network: "https://sepolia.infura.io/v3/KEY",
408
+ auth: { __type: "ApiKeyHeader", value: process.env.RELAYER_API_KEY },
270
409
  },
271
410
  };
272
411
  ```
@@ -279,10 +418,10 @@ The `GenericSigner` interface has six methods. Any Web3 library can back it.
279
418
  interface GenericSigner {
280
419
  getChainId(): Promise<number>;
281
420
  getAddress(): Promise<Address>;
282
- signTypedData(typedData: EIP712TypedData): Promise<Address>;
283
- writeContract(config: ContractCallConfig): Promise<Address>;
421
+ signTypedData(typedData: EIP712TypedData): Promise<Hex>;
422
+ writeContract(config: ContractCallConfig): Promise<Hex>;
284
423
  readContract(config: ContractCallConfig): Promise<unknown>;
285
- waitForTransactionReceipt(hash: Address): Promise<TransactionReceipt>;
424
+ waitForTransactionReceipt(hash: Hex): Promise<TransactionReceipt>;
286
425
  }
287
426
  ```
288
427
 
@@ -293,7 +432,7 @@ interface GenericSigner {
293
432
  ```ts
294
433
  import { ViemSigner } from "@zama-fhe/sdk/viem";
295
434
 
296
- const signer = new ViemSigner(walletClient, publicClient);
435
+ const signer = new ViemSigner({ walletClient, publicClient });
297
436
  ```
298
437
 
299
438
  **ethers** — `@zama-fhe/sdk/ethers`
@@ -301,14 +440,14 @@ const signer = new ViemSigner(walletClient, publicClient);
301
440
  ```ts
302
441
  import { EthersSigner } from "@zama-fhe/sdk/ethers";
303
442
 
304
- const signer = new EthersSigner(ethersSigner);
443
+ const signer = new EthersSigner({ signer: ethersSigner });
305
444
  ```
306
445
 
307
446
  ## Contract Call Builders
308
447
 
309
448
  Every function returns a `ContractCallConfig` object (address, ABI, function name, args) that can be used with any Web3 library. These are the low-level building blocks — they map 1:1 to on-chain contract calls without any orchestration. Use them when the high-level `Token` API doesn't cover your use case.
310
449
 
311
- > **High-level vs low-level:** `token.wrap()` / `token.unshield()` handle the full flow (approval, encryption, receipt waiting, finalization). The contract call builders (`wrapContract()`, `unwrapContract()`, etc.) produce raw call configs for a single contract interaction.
450
+ > **High-level vs low-level:** `token.shield()` / `token.unshield()` handle the full flow (approval, encryption, receipt waiting, finalization). The contract call builders (`wrapContract()`, `unwrapContract()`, etc.) produce raw call configs for a single contract interaction.
312
451
 
313
452
  ```ts
314
453
  interface ContractCallConfig {
@@ -466,8 +605,8 @@ Individual topic hashes are accessible via the `Topics` object: `Topics.Confiden
466
605
  | `decodeUnwrapRequested(log)` | `UnwrapRequestedEvent \| null` — `{ receiver, encryptedAmount }` |
467
606
  | `decodeUnwrappedFinalized(log)` | `UnwrappedFinalizedEvent \| null` — `{ burntAmountHandle, finalizeSuccess, burnAmount, unwrapAmount, feeAmount, ... }` |
468
607
  | `decodeUnwrappedStarted(log)` | `UnwrappedStartedEvent \| null` — `{ returnVal, requestId, txId, to, refund, requestedAmount, burnAmount }` |
469
- | `decodeTokenEvent(log)` | `TokenEvent \| null` — tries all decoders |
470
- | `decodeTokenEvents(logs)` | `TokenEvent[]` — batch decode, skips unrecognized logs |
608
+ | `decodeOnChainEvent(log)` | `OnChainEvent \| null` — tries all decoders |
609
+ | `decodeOnChainEvents(logs)` | `OnChainEvent[]` — batch decode, skips unrecognized logs |
471
610
 
472
611
  ### Finder Helpers
473
612
 
@@ -535,7 +674,7 @@ interface ActivityItem {
535
674
  fee?: ActivityAmount;
536
675
  success?: boolean;
537
676
  metadata: ActivityLogMetadata;
538
- rawEvent: TokenEvent;
677
+ rawEvent: OnChainEvent;
539
678
  }
540
679
 
541
680
  interface ActivityLogMetadata {
@@ -547,10 +686,10 @@ interface ActivityLogMetadata {
547
686
 
548
687
  ## Error Handling
549
688
 
550
- All SDK errors extend `TokenError`. Use `instanceof` to catch specific error types:
689
+ All SDK errors extend `ZamaError`. Use `instanceof` to catch specific error types:
551
690
 
552
691
  ```ts
553
- import { TokenError, SigningRejectedError, EncryptionFailedError } from "@zama-fhe/sdk";
692
+ import { ZamaError, SigningRejectedError, EncryptionFailedError } from "@zama-fhe/sdk";
554
693
 
555
694
  try {
556
695
  await token.confidentialTransfer(to, amount);
@@ -561,7 +700,7 @@ try {
561
700
  if (error instanceof EncryptionFailedError) {
562
701
  // FHE encryption failed
563
702
  }
564
- if (error instanceof TokenError) {
703
+ if (error instanceof ZamaError) {
565
704
  // Any other SDK error — check error.code for details
566
705
  }
567
706
  }
@@ -577,10 +716,24 @@ try {
577
716
  | `DecryptionFailedError` | `DECRYPTION_FAILED` | FHE decryption operation failed. |
578
717
  | `ApprovalFailedError` | `APPROVAL_FAILED` | ERC-20 approval transaction failed. |
579
718
  | `TransactionRevertedError` | `TRANSACTION_REVERTED` | On-chain transaction reverted. |
580
- | `InvalidCredentialsError` | `INVALID_CREDENTIALS` | Relayer rejected credentials (stale or expired). |
719
+ | `InvalidKeypairError` | `INVALID_KEYPAIR` | Relayer rejected FHE keypair (stale or expired). |
581
720
  | `NoCiphertextError` | `NO_CIPHERTEXT` | No FHE ciphertext exists for this account (e.g. never shielded). |
582
721
  | `RelayerRequestFailedError` | `RELAYER_REQUEST_FAILED` | Relayer HTTP error. Carries a `statusCode` property with the HTTP status. |
583
722
 
723
+ ### `matchZamaError`
724
+
725
+ Pattern-match on error codes without `instanceof` chains. Falls through to the `_` wildcard if no handler matches. Returns `undefined` for non-SDK errors when no `_` handler is provided.
726
+
727
+ ```ts
728
+ import { matchZamaError } from "@zama-fhe/sdk";
729
+
730
+ matchZamaError(error, {
731
+ SIGNING_REJECTED: () => toast("Please approve in wallet"),
732
+ TRANSACTION_REVERTED: (e) => toast(`Tx failed: ${e.message}`),
733
+ _: () => toast("Unknown error"),
734
+ });
735
+ ```
736
+
584
737
  **Distinguishing "no ciphertext" from "zero balance":**
585
738
 
586
739
  ```ts