@zama-fhe/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/LICENSE +28 -0
- package/README.md +649 -0
- package/dist/chunk-AJFSZ47V.js +5115 -0
- package/dist/chunk-AJFSZ47V.js.map +1 -0
- package/dist/chunk-UE6IBC3M.js +101 -0
- package/dist/chunk-UE6IBC3M.js.map +1 -0
- package/dist/chunk-VRLLWHHL.js +278 -0
- package/dist/chunk-VRLLWHHL.js.map +1 -0
- package/dist/ethers/index.d.ts +37 -0
- package/dist/ethers/index.js +131 -0
- package/dist/ethers/index.js.map +1 -0
- package/dist/index.d.ts +32910 -0
- package/dist/index.js +2862 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.d.ts +158 -0
- package/dist/node/index.js +317 -0
- package/dist/node/index.js.map +1 -0
- package/dist/relayer-sdk.node-worker.d.ts +2 -0
- package/dist/relayer-sdk.node-worker.js +320 -0
- package/dist/relayer-sdk.node-worker.js.map +1 -0
- package/dist/relayer-sdk.types-CFkzNzRy.d.ts +293 -0
- package/dist/relayer-sdk.worker.d.ts +2 -0
- package/dist/relayer-sdk.worker.js +401 -0
- package/dist/relayer-sdk.worker.js.map +1 -0
- package/dist/relayer-utils-D_3834H0.d.ts +46 -0
- package/dist/token.types-CRs1iJh7.d.ts +447 -0
- package/dist/viem/index.d.ts +38 -0
- package/dist/viem/index.js +137 -0
- package/dist/viem/index.js.map +1 -0
- package/package.json +88 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Zama
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# @zama-fhe/sdk
|
|
2
|
+
|
|
3
|
+
A TypeScript SDK for building privacy-preserving token applications using Fully Homomorphic Encryption (FHE). It abstracts the complexity of encrypted ERC-20 operations — shielding, unshielding, confidential transfers, and balance decryption — behind a clean, high-level API. Works with any Web3 library (viem, ethers, or custom signers).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @zama-fhe/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer dependencies
|
|
12
|
+
|
|
13
|
+
| Package | Version | Required? |
|
|
14
|
+
| ----------------------- | ------- | -------------------------------------------------- |
|
|
15
|
+
| `viem` | >= 2 | Optional — for the `@zama-fhe/sdk/viem` adapter |
|
|
16
|
+
| `ethers` | >= 6 | Optional — for the `@zama-fhe/sdk/ethers` adapter |
|
|
17
|
+
| `@zama-fhe/relayer-sdk` | >= 0.4 | Optional — only for `@zama-fhe/sdk/node` (Node.js) |
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Browser
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { TokenSDK, RelayerWeb, IndexedDBStorage } from "@zama-fhe/sdk";
|
|
25
|
+
import { ViemSigner } from "@zama-fhe/sdk/viem";
|
|
26
|
+
|
|
27
|
+
// 1. Create signer and relayer
|
|
28
|
+
const signer = new ViemSigner(walletClient, publicClient);
|
|
29
|
+
|
|
30
|
+
const sdk = new TokenSDK({
|
|
31
|
+
relayer: new RelayerWeb({
|
|
32
|
+
getChainId: () => signer.getChainId(),
|
|
33
|
+
transports: {
|
|
34
|
+
[1]: {
|
|
35
|
+
relayerUrl: "https://relayer.zama.ai",
|
|
36
|
+
network: "https://mainnet.infura.io/v3/YOUR_KEY",
|
|
37
|
+
},
|
|
38
|
+
[11155111]: {
|
|
39
|
+
relayerUrl: "https://relayer.zama.ai",
|
|
40
|
+
network: "https://sepolia.infura.io/v3/YOUR_KEY",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
signer,
|
|
45
|
+
storage: new IndexedDBStorage(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 2. Create a token instance (wrapper is auto-discovered if omitted)
|
|
49
|
+
const token = sdk.createToken("0xEncryptedERC20Address");
|
|
50
|
+
// Or provide the wrapper explicitly:
|
|
51
|
+
// const token = sdk.createToken("0xEncryptedERC20Address", "0xWrapperAddress");
|
|
52
|
+
|
|
53
|
+
// 3. Shield (wrap) public tokens into confidential tokens
|
|
54
|
+
const wrapTx = await token.wrap(1000n);
|
|
55
|
+
|
|
56
|
+
// 4. Check decrypted balance
|
|
57
|
+
const balance = await token.balanceOf();
|
|
58
|
+
console.log("Confidential balance:", balance);
|
|
59
|
+
|
|
60
|
+
// 5. Transfer confidential tokens
|
|
61
|
+
const transferTx = await token.confidentialTransfer("0xRecipient", 500n);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Node.js
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { TokenSDK, MemoryStorage } from "@zama-fhe/sdk";
|
|
68
|
+
import { RelayerNode } from "@zama-fhe/sdk/node";
|
|
69
|
+
import { ViemSigner } from "@zama-fhe/sdk/viem";
|
|
70
|
+
|
|
71
|
+
const signer = new ViemSigner(walletClient, publicClient);
|
|
72
|
+
|
|
73
|
+
const sdk = new TokenSDK({
|
|
74
|
+
relayer: new RelayerNode({
|
|
75
|
+
getChainId: () => signer.getChainId(),
|
|
76
|
+
poolSize: 4, // number of worker threads (default: min(CPUs, 4))
|
|
77
|
+
transports: {
|
|
78
|
+
[1]: {
|
|
79
|
+
relayerUrl: "https://relayer.zama.ai",
|
|
80
|
+
network: "https://mainnet.infura.io/v3/YOUR_KEY",
|
|
81
|
+
},
|
|
82
|
+
[11155111]: {
|
|
83
|
+
relayerUrl: "https://relayer.zama.ai",
|
|
84
|
+
network: "https://sepolia.infura.io/v3/YOUR_KEY",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
signer,
|
|
89
|
+
storage: new MemoryStorage(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const token = sdk.createToken("0xEncryptedERC20Address");
|
|
93
|
+
const balance = await token.balanceOf();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Core Concepts
|
|
97
|
+
|
|
98
|
+
### TokenSDK
|
|
99
|
+
|
|
100
|
+
Entry point to the SDK. Composes a relayer backend with a signer and storage layer. Acts as a factory for token instances.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const sdk = new TokenSDK({
|
|
104
|
+
relayer, // RelayerSDK — either RelayerWeb (browser) or RelayerNode (Node.js)
|
|
105
|
+
signer, // GenericSigner
|
|
106
|
+
storage, // GenericStringStorage
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Read-only — balances, metadata, decryption. No wrapper needed.
|
|
110
|
+
const readonlyToken = sdk.createReadonlyToken("0xTokenAddress");
|
|
111
|
+
|
|
112
|
+
// Full read/write — shield, unshield, transfer, approve.
|
|
113
|
+
// The token address IS the wrapper (encrypted ERC20 = wrapper contract).
|
|
114
|
+
const token = sdk.createToken("0xTokenAddress");
|
|
115
|
+
// Override wrapper if it differs from the token address (rare):
|
|
116
|
+
// const token = sdk.createToken("0xTokenAddress", "0xWrapperAddress");
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The `relayer`, `signer`, and `storage` properties are public and accessible after construction. Low-level FHE operations (`encrypt`, `userDecrypt`, `publicDecrypt`, `generateKeypair`, etc.) are available via `sdk.relayer`. Call `sdk.terminate()` to clean up resources when done.
|
|
120
|
+
|
|
121
|
+
### Relayer Backends
|
|
122
|
+
|
|
123
|
+
The `RelayerSDK` interface defines the FHE operations contract. Two implementations are provided:
|
|
124
|
+
|
|
125
|
+
| Backend | Import | Environment | How it works |
|
|
126
|
+
| ------------- | -------------------- | ----------- | ------------------------------------------ |
|
|
127
|
+
| `RelayerWeb` | `@zama-fhe/sdk` | Browser | Runs WASM in a Web Worker via CDN |
|
|
128
|
+
| `RelayerNode` | `@zama-fhe/sdk/node` | Node.js | Uses `@zama-fhe/relayer-sdk/node` directly |
|
|
129
|
+
|
|
130
|
+
The `/node` sub-path also exports `NodeWorkerClient` and `NodeWorkerClientConfig` for running FHE operations in a Node.js worker thread.
|
|
131
|
+
|
|
132
|
+
You can also implement the `RelayerSDK` interface for custom backends.
|
|
133
|
+
|
|
134
|
+
### Token
|
|
135
|
+
|
|
136
|
+
Full read/write interface for a single confidential ERC-20. Extends `ReadonlyToken`. The encrypted ERC-20 contract IS the wrapper, so `wrapper` defaults to the token `address`. Pass an explicit `wrapper` only if they differ.
|
|
137
|
+
|
|
138
|
+
| Method | Description |
|
|
139
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
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). |
|
|
142
|
+
| `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
|
+
| `unshieldAll(callbacks?)` | Unwrap the entire balance and finalize in one call. Orchestrates: unwrapAll → wait receipt → parse event → finalizeUnwrap. Optional `UnshieldCallbacks` for progress tracking. |
|
|
144
|
+
| `unwrap(amount)` | Request unwrap for a specific amount (low-level, requires manual finalization). |
|
|
145
|
+
| `unwrapAll()` | Request unwrap for the entire balance (low-level, requires manual finalization). |
|
|
146
|
+
| `resumeUnshield(unwrapTxHash, callbacks?)` | Resume an interrupted unshield from an existing unwrap tx hash. Goes straight to wait receipt → finalize. |
|
|
147
|
+
| `finalizeUnwrap(burnAmountHandle)` | Complete unwrap with public decryption proof. |
|
|
148
|
+
| `confidentialTransfer(to, amount)` | Encrypted transfer. Encrypts amount, then calls the contract. |
|
|
149
|
+
| `confidentialTransferFrom(from, to, amt)` | Operator encrypted transfer. |
|
|
150
|
+
| `approve(spender, until?)` | Set operator approval. `until` defaults to now + 1 hour. |
|
|
151
|
+
| `isApproved(spender)` | Check if a spender is an approved operator. |
|
|
152
|
+
| `approveUnderlying(amount?)` | Approve wrapper to spend underlying ERC-20. Default: max uint256. |
|
|
153
|
+
| `balanceOf(owner?)` | Decrypt and return the plaintext balance. |
|
|
154
|
+
| `decryptHandles(handles, owner?)` | Batch-decrypt arbitrary encrypted handles. |
|
|
155
|
+
|
|
156
|
+
All write methods return the transaction hash (`Address`).
|
|
157
|
+
|
|
158
|
+
### ReadonlyToken
|
|
159
|
+
|
|
160
|
+
Read-only subset. No wrapper address needed.
|
|
161
|
+
|
|
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. |
|
|
177
|
+
|
|
178
|
+
Static methods for multi-token operations:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// Pre-authorize all tokens with a single wallet signature
|
|
182
|
+
const tokens = addresses.map((a) => sdk.createReadonlyToken(a));
|
|
183
|
+
await ReadonlyToken.authorizeAll(tokens);
|
|
184
|
+
// All subsequent decrypts reuse cached credentials — no more wallet prompts
|
|
185
|
+
|
|
186
|
+
// Decrypt balances for multiple tokens in parallel
|
|
187
|
+
const balances = await ReadonlyToken.batchBalanceOf(tokens, owner);
|
|
188
|
+
|
|
189
|
+
// Decrypt pre-fetched handles for multiple tokens
|
|
190
|
+
const balances = await ReadonlyToken.batchDecryptBalances(tokens, handles, owner);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Storage
|
|
194
|
+
|
|
195
|
+
FHE credentials (keypair + EIP-712 signature) are persisted to storage. Three options:
|
|
196
|
+
|
|
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. |
|
|
203
|
+
|
|
204
|
+
```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>;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Configuration Reference
|
|
213
|
+
|
|
214
|
+
### `TokenSDKConfig`
|
|
215
|
+
|
|
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. |
|
|
221
|
+
|
|
222
|
+
### `RelayerWebConfig` (browser)
|
|
223
|
+
|
|
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. |
|
|
230
|
+
|
|
231
|
+
#### `RelayerWebSecurityConfig`
|
|
232
|
+
|
|
233
|
+
| Field | Type | Description |
|
|
234
|
+
| ---------------- | -------------- | ------------------------------------------------------------------------------------------------ |
|
|
235
|
+
| `getCsrfToken` | `() => string` | Optional. Resolve the CSRF token before each authenticated network request. |
|
|
236
|
+
| `integrityCheck` | `boolean` | Optional. Verify SHA-384 integrity of the CDN bundle. Defaults to `true`. Set `false` for tests. |
|
|
237
|
+
|
|
238
|
+
### `RelayerNodeConfig` (Node.js)
|
|
239
|
+
|
|
240
|
+
| Field | Type | Description |
|
|
241
|
+
| ------------ | ------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
242
|
+
| `getChainId` | `() => Promise<number>` | Resolve the current chain ID. Called lazily; the pool is re-initialized on chain change. |
|
|
243
|
+
| `transports` | `Record<number, FhevmInstanceConfig>` | Chain-specific configs keyed by chain ID (includes relayerUrl, network, auth, contract addresses). |
|
|
244
|
+
|
|
245
|
+
### Network Preset Configs
|
|
246
|
+
|
|
247
|
+
Both the main entry (`@zama-fhe/sdk`) and the `/node` sub-path re-export preset configs so you don't need to import from `@zama-fhe/relayer-sdk` directly:
|
|
248
|
+
|
|
249
|
+
| Config | Chain ID | Description |
|
|
250
|
+
| --------------- | -------- | ----------------------------------- |
|
|
251
|
+
| `SepoliaConfig` | 11155111 | Sepolia testnet contract addresses. |
|
|
252
|
+
| `MainnetConfig` | 1 | Mainnet contract addresses. |
|
|
253
|
+
| `HardhatConfig` | 31337 | Local Hardhat node addresses. |
|
|
254
|
+
|
|
255
|
+
Each preset provides contract addresses and default values. Override `relayerUrl` and `network` (RPC URL) for your environment:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { SepoliaConfig, MainnetConfig } from "@zama-fhe/sdk";
|
|
259
|
+
|
|
260
|
+
const transports = {
|
|
261
|
+
[11155111]: {
|
|
262
|
+
...SepoliaConfig,
|
|
263
|
+
relayerUrl: "/api/proxy",
|
|
264
|
+
network: "https://sepolia.infura.io/v3/KEY",
|
|
265
|
+
},
|
|
266
|
+
[1]: {
|
|
267
|
+
...MainnetConfig,
|
|
268
|
+
relayerUrl: "/api/proxy",
|
|
269
|
+
network: "https://mainnet.infura.io/v3/KEY",
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## GenericSigner Interface
|
|
275
|
+
|
|
276
|
+
The `GenericSigner` interface has six methods. Any Web3 library can back it.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
interface GenericSigner {
|
|
280
|
+
getChainId(): Promise<number>;
|
|
281
|
+
getAddress(): Promise<Address>;
|
|
282
|
+
signTypedData(typedData: EIP712TypedData): Promise<Address>;
|
|
283
|
+
writeContract(config: ContractCallConfig): Promise<Address>;
|
|
284
|
+
readContract(config: ContractCallConfig): Promise<unknown>;
|
|
285
|
+
waitForTransactionReceipt(hash: Address): Promise<TransactionReceipt>;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Built-in Adapters
|
|
290
|
+
|
|
291
|
+
**viem** — `@zama-fhe/sdk/viem`
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { ViemSigner } from "@zama-fhe/sdk/viem";
|
|
295
|
+
|
|
296
|
+
const signer = new ViemSigner(walletClient, publicClient);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**ethers** — `@zama-fhe/sdk/ethers`
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { EthersSigner } from "@zama-fhe/sdk/ethers";
|
|
303
|
+
|
|
304
|
+
const signer = new EthersSigner(ethersSigner);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Contract Call Builders
|
|
308
|
+
|
|
309
|
+
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
|
+
|
|
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.
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
interface ContractCallConfig {
|
|
315
|
+
readonly address: Address;
|
|
316
|
+
readonly abi: readonly unknown[];
|
|
317
|
+
readonly functionName: string;
|
|
318
|
+
readonly args: readonly unknown[];
|
|
319
|
+
readonly value?: bigint;
|
|
320
|
+
readonly gas?: bigint;
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### ERC-20
|
|
325
|
+
|
|
326
|
+
| Function | Description |
|
|
327
|
+
| ------------------------------------------ | ------------------------ |
|
|
328
|
+
| `nameContract(token)` | Read token name. |
|
|
329
|
+
| `symbolContract(token)` | Read token symbol. |
|
|
330
|
+
| `decimalsContract(token)` | Read token decimals. |
|
|
331
|
+
| `balanceOfContract(token, owner)` | Read ERC-20 balance. |
|
|
332
|
+
| `allowanceContract(token, owner, spender)` | Read ERC-20 allowance. |
|
|
333
|
+
| `approveContract(token, spender, value)` | Approve ERC-20 spending. |
|
|
334
|
+
|
|
335
|
+
### Encryption (Confidential ERC-20)
|
|
336
|
+
|
|
337
|
+
| Function | Description |
|
|
338
|
+
| ----------------------------------------------------------------------- | ----------------------------------------- |
|
|
339
|
+
| `confidentialBalanceOfContract(token, user)` | Read encrypted balance handle. |
|
|
340
|
+
| `confidentialTransferContract(token, to, handle, inputProof)` | Encrypted transfer. |
|
|
341
|
+
| `confidentialTransferFromContract(token, from, to, handle, inputProof)` | Operator encrypted transfer. |
|
|
342
|
+
| `isOperatorContract(token, holder, spender)` | Check operator approval. |
|
|
343
|
+
| `setOperatorContract(token, spender, timestamp?)` | Set operator approval (default: +1 hour). |
|
|
344
|
+
| `confidentialTotalSupplyContract(token)` | Read encrypted total supply handle. |
|
|
345
|
+
| `totalSupplyContract(token)` | Read plaintext total supply. |
|
|
346
|
+
| `rateContract(token)` | Read conversion rate. |
|
|
347
|
+
| `deploymentCoordinatorContract(token)` | Read deployment coordinator address. |
|
|
348
|
+
| `isFinalizeUnwrapOperatorContract(token, holder, operator)` | Check finalize-unwrap operator status. |
|
|
349
|
+
| `setFinalizeUnwrapOperatorContract(token, operator, timestamp?)` | Set finalize-unwrap operator. |
|
|
350
|
+
|
|
351
|
+
### Wrapper
|
|
352
|
+
|
|
353
|
+
| Function | Description |
|
|
354
|
+
| ---------------------------------------------------------------- | --------------------------------------------- |
|
|
355
|
+
| `wrapContract(wrapper, to, amount)` | Wrap ERC-20 tokens. |
|
|
356
|
+
| `wrapETHContract(wrapper, to, amount, value)` | Wrap native ETH. |
|
|
357
|
+
| `unwrapContract(token, from, to, encryptedAmount, inputProof)` | Request unwrap with encrypted amount. |
|
|
358
|
+
| `unwrapFromBalanceContract(token, from, to, encryptedBalance)` | Request unwrap using on-chain balance handle. |
|
|
359
|
+
| `finalizeUnwrapContract(wrapper, burntAmount, cleartext, proof)` | Finalize unwrap with decryption proof. |
|
|
360
|
+
| `underlyingContract(wrapper)` | Read underlying ERC-20 address. |
|
|
361
|
+
|
|
362
|
+
### Deployment Coordinator
|
|
363
|
+
|
|
364
|
+
| Function | Description |
|
|
365
|
+
| ------------------------------------------- | ---------------------------- |
|
|
366
|
+
| `getWrapperContract(coordinator, token)` | Look up wrapper for a token. |
|
|
367
|
+
| `wrapperExistsContract(coordinator, token)` | Check if wrapper exists. |
|
|
368
|
+
|
|
369
|
+
### ERC-165
|
|
370
|
+
|
|
371
|
+
| Function | Description |
|
|
372
|
+
| ----------------------------------------------- | ------------------------ |
|
|
373
|
+
| `supportsInterfaceContract(token, interfaceId)` | ERC-165 interface check. |
|
|
374
|
+
|
|
375
|
+
### Fee Manager
|
|
376
|
+
|
|
377
|
+
| Function | Description |
|
|
378
|
+
| ---------------------------------------------------- | -------------------------- |
|
|
379
|
+
| `getWrapFeeContract(feeManager, amount, from, to)` | Calculate wrap fee. |
|
|
380
|
+
| `getUnwrapFeeContract(feeManager, amount, from, to)` | Calculate unwrap fee. |
|
|
381
|
+
| `getBatchTransferFeeContract(feeManager)` | Get batch transfer fee. |
|
|
382
|
+
| `getFeeRecipientContract(feeManager)` | Get fee recipient address. |
|
|
383
|
+
|
|
384
|
+
### Transfer Batcher
|
|
385
|
+
|
|
386
|
+
| Function | Description |
|
|
387
|
+
| -------------------------------------------------------------------------- | ----------------------------------- |
|
|
388
|
+
| `confidentialBatchTransferContract(batcher, token, from, transfers, fees)` | Batch multiple encrypted transfers. |
|
|
389
|
+
|
|
390
|
+
## Library-Specific Contract Helpers
|
|
391
|
+
|
|
392
|
+
Both the `/viem` and `/ethers` sub-paths export convenience wrappers that execute contract calls directly with library-native clients.
|
|
393
|
+
|
|
394
|
+
### viem (`@zama-fhe/sdk/viem`)
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import {
|
|
398
|
+
readConfidentialBalanceOfContract,
|
|
399
|
+
writeConfidentialTransferContract,
|
|
400
|
+
writeWrapContract,
|
|
401
|
+
// ... more
|
|
402
|
+
} from "@zama-fhe/sdk/viem";
|
|
403
|
+
|
|
404
|
+
// Read: pass a PublicClient
|
|
405
|
+
const handle = await readConfidentialBalanceOfContract(publicClient, tokenAddress, userAddress);
|
|
406
|
+
|
|
407
|
+
// Write: pass a WalletClient
|
|
408
|
+
const txHash = await writeConfidentialTransferContract(
|
|
409
|
+
walletClient,
|
|
410
|
+
tokenAddress,
|
|
411
|
+
to,
|
|
412
|
+
handle,
|
|
413
|
+
inputProof,
|
|
414
|
+
);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Read helpers:** `readConfidentialBalanceOfContract`, `readWrapperForTokenContract`, `readUnderlyingTokenContract`, `readWrapperExistsContract`, `readSupportsInterfaceContract`.
|
|
418
|
+
|
|
419
|
+
**Write helpers:** `writeConfidentialTransferContract`, `writeConfidentialBatchTransferContract`, `writeUnwrapContract`, `writeUnwrapFromBalanceContract`, `writeFinalizeUnwrapContract`, `writeSetOperatorContract`, `writeWrapContract`, `writeWrapETHContract`.
|
|
420
|
+
|
|
421
|
+
### ethers (`@zama-fhe/sdk/ethers`)
|
|
422
|
+
|
|
423
|
+
Same set of functions, but read helpers take `Provider | Signer` and write helpers take `Signer`.
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import {
|
|
427
|
+
readConfidentialBalanceOfContract,
|
|
428
|
+
writeConfidentialTransferContract,
|
|
429
|
+
} from "@zama-fhe/sdk/ethers";
|
|
430
|
+
|
|
431
|
+
const handle = await readConfidentialBalanceOfContract(provider, tokenAddress, userAddress);
|
|
432
|
+
const txHash = await writeConfidentialTransferContract(
|
|
433
|
+
signer,
|
|
434
|
+
tokenAddress,
|
|
435
|
+
to,
|
|
436
|
+
handle,
|
|
437
|
+
inputProof,
|
|
438
|
+
);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Event Decoders
|
|
442
|
+
|
|
443
|
+
Decode raw log entries from `eth_getLogs` into typed event objects.
|
|
444
|
+
|
|
445
|
+
### Topics
|
|
446
|
+
|
|
447
|
+
Use `TOKEN_TOPICS` as the `topics[0]` filter for `getLogs` to capture all confidential token events:
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
import { TOKEN_TOPICS } from "@zama-fhe/sdk";
|
|
451
|
+
|
|
452
|
+
const logs = await publicClient.getLogs({
|
|
453
|
+
address: tokenAddress,
|
|
454
|
+
topics: [TOKEN_TOPICS],
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Individual topic hashes are accessible via the `Topics` object: `Topics.ConfidentialTransfer`, `Topics.Wrapped`, `Topics.UnwrapRequested`, `Topics.UnwrappedFinalized`, `Topics.UnwrappedStarted`.
|
|
459
|
+
|
|
460
|
+
### Decoders
|
|
461
|
+
|
|
462
|
+
| Function | Returns |
|
|
463
|
+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
464
|
+
| `decodeConfidentialTransfer(log)` | `ConfidentialTransferEvent \| null` — `{ from, to, encryptedAmountHandle }` |
|
|
465
|
+
| `decodeWrapped(log)` | `WrappedEvent \| null` — `{ mintAmount, amountIn, feeAmount, to, mintTxId }` |
|
|
466
|
+
| `decodeUnwrapRequested(log)` | `UnwrapRequestedEvent \| null` — `{ receiver, encryptedAmount }` |
|
|
467
|
+
| `decodeUnwrappedFinalized(log)` | `UnwrappedFinalizedEvent \| null` — `{ burntAmountHandle, finalizeSuccess, burnAmount, unwrapAmount, feeAmount, ... }` |
|
|
468
|
+
| `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 |
|
|
471
|
+
|
|
472
|
+
### Finder Helpers
|
|
473
|
+
|
|
474
|
+
Convenience functions that decode a logs array and return the first matching event:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
import { findWrapped, findUnwrapRequested } from "@zama-fhe/sdk";
|
|
478
|
+
|
|
479
|
+
const wrappedEvent = findWrapped(receipt.logs);
|
|
480
|
+
const unwrapEvent = findUnwrapRequested(receipt.logs);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Activity Feed Helpers
|
|
484
|
+
|
|
485
|
+
Transform raw event logs into a user-friendly activity feed with decrypted amounts.
|
|
486
|
+
|
|
487
|
+
### Pipeline
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
import {
|
|
491
|
+
parseActivityFeed,
|
|
492
|
+
extractEncryptedHandles,
|
|
493
|
+
applyDecryptedValues,
|
|
494
|
+
sortByBlockNumber,
|
|
495
|
+
} from "@zama-fhe/sdk";
|
|
496
|
+
|
|
497
|
+
// 1. Parse raw logs into classified activity items
|
|
498
|
+
const items = parseActivityFeed(logs, userAddress);
|
|
499
|
+
|
|
500
|
+
// 2. Extract encrypted handles that need decryption
|
|
501
|
+
const handles = extractEncryptedHandles(items);
|
|
502
|
+
|
|
503
|
+
// 3. Decrypt handles (using your token instance)
|
|
504
|
+
const decryptedMap = await token.decryptHandles(handles);
|
|
505
|
+
|
|
506
|
+
// 4. Apply decrypted values back to activity items
|
|
507
|
+
const enrichedItems = applyDecryptedValues(items, decryptedMap);
|
|
508
|
+
|
|
509
|
+
// 5. Sort by block number (most recent first)
|
|
510
|
+
const sorted = sortByBlockNumber(enrichedItems);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Types
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
type ActivityDirection = "incoming" | "outgoing" | "self";
|
|
517
|
+
|
|
518
|
+
type ActivityType =
|
|
519
|
+
| "transfer"
|
|
520
|
+
| "shield"
|
|
521
|
+
| "unshield_requested"
|
|
522
|
+
| "unshield_started"
|
|
523
|
+
| "unshield_finalized";
|
|
524
|
+
|
|
525
|
+
type ActivityAmount =
|
|
526
|
+
| { type: "clear"; value: bigint }
|
|
527
|
+
| { type: "encrypted"; handle: string; decryptedValue?: bigint };
|
|
528
|
+
|
|
529
|
+
interface ActivityItem {
|
|
530
|
+
type: ActivityType;
|
|
531
|
+
direction: ActivityDirection;
|
|
532
|
+
amount: ActivityAmount;
|
|
533
|
+
from?: string;
|
|
534
|
+
to?: string;
|
|
535
|
+
fee?: ActivityAmount;
|
|
536
|
+
success?: boolean;
|
|
537
|
+
metadata: ActivityLogMetadata;
|
|
538
|
+
rawEvent: TokenEvent;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
interface ActivityLogMetadata {
|
|
542
|
+
transactionHash?: string;
|
|
543
|
+
blockNumber?: bigint | number;
|
|
544
|
+
logIndex?: number;
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Error Handling
|
|
549
|
+
|
|
550
|
+
All SDK errors extend `TokenError`. Use `instanceof` to catch specific error types:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { TokenError, SigningRejectedError, EncryptionFailedError } from "@zama-fhe/sdk";
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
await token.confidentialTransfer(to, amount);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (error instanceof SigningRejectedError) {
|
|
559
|
+
// User rejected wallet signature
|
|
560
|
+
}
|
|
561
|
+
if (error instanceof EncryptionFailedError) {
|
|
562
|
+
// FHE encryption failed
|
|
563
|
+
}
|
|
564
|
+
if (error instanceof TokenError) {
|
|
565
|
+
// Any other SDK error — check error.code for details
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Error Classes
|
|
571
|
+
|
|
572
|
+
| Error Class | Code | Description |
|
|
573
|
+
| --------------------------- | ------------------------ | ------------------------------------------------------------------------- |
|
|
574
|
+
| `SigningRejectedError` | `SIGNING_REJECTED` | User rejected the wallet signature request. |
|
|
575
|
+
| `SigningFailedError` | `SIGNING_FAILED` | Wallet signature failed for a non-rejection reason. |
|
|
576
|
+
| `EncryptionFailedError` | `ENCRYPTION_FAILED` | FHE encryption operation failed. |
|
|
577
|
+
| `DecryptionFailedError` | `DECRYPTION_FAILED` | FHE decryption operation failed. |
|
|
578
|
+
| `ApprovalFailedError` | `APPROVAL_FAILED` | ERC-20 approval transaction failed. |
|
|
579
|
+
| `TransactionRevertedError` | `TRANSACTION_REVERTED` | On-chain transaction reverted. |
|
|
580
|
+
| `InvalidCredentialsError` | `INVALID_CREDENTIALS` | Relayer rejected credentials (stale or expired). |
|
|
581
|
+
| `NoCiphertextError` | `NO_CIPHERTEXT` | No FHE ciphertext exists for this account (e.g. never shielded). |
|
|
582
|
+
| `RelayerRequestFailedError` | `RELAYER_REQUEST_FAILED` | Relayer HTTP error. Carries a `statusCode` property with the HTTP status. |
|
|
583
|
+
|
|
584
|
+
**Distinguishing "no ciphertext" from "zero balance":**
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
import { NoCiphertextError, RelayerRequestFailedError } from "@zama-fhe/sdk";
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
const balance = await token.balanceOf();
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (error instanceof NoCiphertextError) {
|
|
593
|
+
// Account has never shielded — show "no confidential balance" in UI
|
|
594
|
+
}
|
|
595
|
+
if (error instanceof RelayerRequestFailedError) {
|
|
596
|
+
console.error(`Relayer returned HTTP ${error.statusCode}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Unshield Progress Callbacks
|
|
602
|
+
|
|
603
|
+
`unshield()`, `unshieldAll()`, and `resumeUnshield()` accept optional callbacks for tracking progress through the two-phase unshield flow:
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
import type { UnshieldCallbacks } from "@zama-fhe/sdk";
|
|
607
|
+
|
|
608
|
+
const callbacks: UnshieldCallbacks = {
|
|
609
|
+
onUnwrapSubmitted: (txHash) => console.log("Unwrap tx:", txHash),
|
|
610
|
+
onFinalizing: () => console.log("Waiting for decryption proof..."),
|
|
611
|
+
onFinalizeSubmitted: (txHash) => console.log("Finalize tx:", txHash),
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
await token.unshield(500n, callbacks);
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Callbacks are safe — a throwing callback will not interrupt the unshield flow.
|
|
618
|
+
|
|
619
|
+
## RelayerSDK (Low-Level FHE)
|
|
620
|
+
|
|
621
|
+
Low-level FHE operations are available on the relayer backend via `sdk.relayer`:
|
|
622
|
+
|
|
623
|
+
| Method | Description |
|
|
624
|
+
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
625
|
+
| `encrypt(params)` | Encrypt values for smart contract calls. Returns `{ handles, inputProof }`. |
|
|
626
|
+
| `userDecrypt(params)` | Decrypt ciphertext handles with the user's FHE private key. |
|
|
627
|
+
| `publicDecrypt(handles)` | Public decryption (no private key needed). Returns `{ clearValues, decryptionProof }`. |
|
|
628
|
+
| `generateKeypair()` | Generate an FHE keypair. Returns `{ publicKey, privateKey }`. |
|
|
629
|
+
| `createEIP712(publicKey, contractAddresses, startTimestamp, durationDays?)` | Create EIP-712 typed data for decrypt authorization. Default duration: 7 days. |
|
|
630
|
+
| `createDelegatedUserDecryptEIP712(...)` | Create EIP-712 for delegated decryption. |
|
|
631
|
+
| `delegatedUserDecrypt(params)` | Decrypt via delegation. |
|
|
632
|
+
| `requestZKProofVerification(zkProof)` | Submit a ZK proof for on-chain verification. |
|
|
633
|
+
| `getPublicKey()` | Get the TFHE compact public key. |
|
|
634
|
+
| `getPublicParams(bits)` | Get public parameters for encryption capacity. |
|
|
635
|
+
| `terminate()` | Terminate the backend and clean up resources. |
|
|
636
|
+
|
|
637
|
+
## Constants
|
|
638
|
+
|
|
639
|
+
| Constant | Value | Description |
|
|
640
|
+
| ------------------------------ | --------------------------------- | --------------------------------------------- |
|
|
641
|
+
| `ZERO_HANDLE` | `"0x0000...0000"` (32 zero bytes) | Sentinel for empty/zero encrypted values. |
|
|
642
|
+
| `ERC7984_INTERFACE_ID` | `"0x4958f2a4"` | ERC-165 interface ID for confidential tokens. |
|
|
643
|
+
| `ERC7984_WRAPPER_INTERFACE_ID` | `"0xd04584ba"` | ERC-165 interface ID for wrapper contracts. |
|
|
644
|
+
|
|
645
|
+
## Exported ABIs
|
|
646
|
+
|
|
647
|
+
For direct use with viem, ethers, or any ABI-compatible library:
|
|
648
|
+
|
|
649
|
+
`ERC20_ABI`, `ERC20_METADATA_ABI`, `ENCRYPTION_ABI`, `WRAPPER_ABI`, `DEPLOYMENT_COORDINATOR_ABI`, `ERC165_ABI`, `FEE_MANAGER_ABI`, `TRANSFER_BATCHER_ABI`, `BATCH_SWAP_ABI`.
|