@fairblock/stabletrust 1.0.21 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The Stabletrust SDK by Fairblock provides a robust interface for executing confidential transfers using homomorphic encryption and zero-knowledge proofs. This package enables developers to integrate confidentiality features directly into their applications, allowing for secure token deposits, private transfers, and withdrawals while maintaining the integrity and auditability of the underlying blockchain transactions.
5
+ The StableTrust SDK by Fairblock provides a robust interface for executing confidential and anonymous transfers using homomorphic encryption and zero-knowledge proofs. This package enables developers to integrate confidentiality features directly into their applications, allowing for secure token deposits, private transfers, and withdrawals while maintaining the integrity and auditability of the underlying blockchain transactions.
6
6
 
7
7
  For a comprehensive technical understanding of the architecture and cryptographic primitives, please refer to the following documentation:
8
8
 
@@ -32,6 +32,31 @@ Or with yarn:
32
32
  yarn add @fairblock/stabletrust
33
33
  ```
34
34
 
35
+ ---
36
+
37
+ ## Two Clients: Which One to Use?
38
+
39
+ The SDK ships two clients that serve different privacy models:
40
+
41
+ | Feature | `ConfidentialTransferClient` | `AnonymousTransferClient` |
42
+ | :----------------- | :----------------------------------------------- | :----------------------------------------------------------- |
43
+ | **Identity** | On-chain wallet address is visible | Account ID only — wallet address is hidden |
44
+ | **Gas** | User pays gas for all transactions | Fairycloak relay pays gas (except deposit) |
45
+ | **Setup** | Direct RPC connection | Requires a running Fairycloak relay server |
46
+ | **Recipient** | Must have a confidential account | Can receive to another anonymous account or a public address |
47
+ | **Key management** | Keys auto-derived from wallet signature | Keys derived per-account; store the private key yourself |
48
+ | **Best for** | Privacy over balances/amounts with known senders | Full sender anonymity |
49
+
50
+ ### `ConfidentialTransferClient` — Confidential Transfers
51
+
52
+ Transactions are submitted on-chain directly from your wallet. The **amount and balance are encrypted** (hidden), but the **sender's address is visible** on-chain. Ideal when you want confidential balances without hiding your identity.
53
+
54
+ ### `AnonymousTransferClient` — Anonymous Transfers
55
+
56
+ Transactions are routed through the **Fairycloak relay**, which submits them on-chain and pays gas. The sender's wallet address is **never revealed** on-chain — only a numeric account ID is associated with the transfer. Use this client when full sender anonymity is required.
57
+
58
+ ---
59
+
35
60
  ## Available Confidential Contract Addresses (Testnet)
36
61
 
37
62
  The following contract addresses are available for confidential transfers on testnet networks. These are test deployments and should not be used with mainnet assets:
@@ -39,15 +64,17 @@ The following contract addresses are available for confidential transfers on tes
39
64
  | Network(Testnet) | Chain ID | Contract Address |
40
65
  | :--------------- | :------- | :------------------------------------------- |
41
66
  | Stable | 2201 | `0xB96aa42b246a956B170fE426A72fB610E4976f9E` |
42
- | Arc | 1244 | `0xb20aB54e1c6AE55B0DD11FEB7FFf3fF1E9631f19` |
67
+ | Arc | 5042002 | `0xb20aB54e1c6AE55B0DD11FEB7FFf3fF1E9631f19` |
43
68
  | Base | 84532 | `0x962a8A7CD28BfFBb17C4F6Ec388782cca3ffd618` |
44
69
  | Ethereum | 11155111 | `0x2E48F3D9b8F4aCA9E6AF0630eaB2ceB7A3f5eEd1` |
45
70
  | Arbitrum | 421614 | `0x14Afd604971bee5b5fac52df2d56CaE421519Cc5` |
46
71
  | Tempo | 42431 | `0x08B6563C95dfe3a4F5533CAA6F7D55a74FCb4F6c` |
47
72
 
48
- ## Usage
73
+ ---
49
74
 
50
- The SDK revolves around the `ConfidentialTransferClient`, which manages interactions with the confidential transfer contract and handles the necessary cryptographic operations.
75
+ ## ConfidentialTransferClient
76
+
77
+ The `ConfidentialTransferClient` manages direct on-chain interactions with the confidential transfer contract. Amounts and balances are encrypted using homomorphic encryption; the sender's wallet address remains visible on-chain.
51
78
 
52
79
  ### Initialization
53
80
 
@@ -57,14 +84,14 @@ Import and initialize the client with your network configuration.
57
84
  import { ConfidentialTransferClient } from "@fairblock/stabletrust";
58
85
  import { ethers } from "ethers";
59
86
 
60
- // Configuration for Base Sepolia (uses SDK default Stabletrust contract for chainId 84532)
87
+ // Use SDK default StableTrust contract for a known chainId
61
88
  const client = new ConfidentialTransferClient(
62
89
  "https://sepolia.base.org",
63
90
  84532,
64
91
  );
65
92
  ```
66
93
 
67
- If you are using a custom deployment, you can still pass an explicit contract address:
94
+ If you are using a custom deployment, pass an explicit contract address:
68
95
 
69
96
  ```javascript
70
97
  const customClient = new ConfidentialTransferClient(
@@ -76,8 +103,6 @@ const customClient = new ConfidentialTransferClient(
76
103
 
77
104
  #### Network Configuration Examples
78
105
 
79
- For testnet networks listed above, use the following configurations:
80
-
81
106
  ```javascript
82
107
  // Stable testnet
83
108
  const stableClient = new ConfidentialTransferClient(
@@ -86,29 +111,26 @@ const stableClient = new ConfidentialTransferClient(
86
111
  );
87
112
 
88
113
  // Arc testnet
89
- const arcClient = new ConfidentialTransferClient("https://rpc.arc.xyz", 1244);
114
+ const arcClient = new ConfidentialTransferClient(
115
+ "https://rpc.arc.xyz",
116
+ 5042002,
117
+ );
90
118
 
91
- // Tempo (Stablecoin chain with special fee handling)
119
+ // Tempo (special fee handling via IPFS proof upload)
92
120
  const tempoClient = new ConfidentialTransferClient(
93
121
  "https://tempo-rpc.example.com",
94
122
  42431,
95
123
  );
96
124
  ```
97
125
 
98
- **Note on Tempo Chain**: The Tempo network (chainId 42431) uses token-based fees instead of native currency. The SDK automatically handles fee payment using PathUSD when detected.
126
+ **Note on Tempo Chain**: The Tempo network (chainId 42431) uploads ZK proofs to IPFS instead of passing them as calldata. The SDK handles this automatically.
99
127
 
100
128
  ### Token Denomination
101
129
 
102
- When interacting with the SDK for deposit, transfer, and withdraw operations, you should parse the token amount using the underlying token's decimals. When displaying the fetched balance, format it using the token's decimals.
103
-
104
- - To deposit, transfer, or withdraw tokens, parse the amount using `ethers.parseUnits(amount, tokenDecimals)`.
105
- - To display a balance, format the raw amount using `ethers.formatUnits(balance.amount, tokenDecimals)`.
106
-
107
- **Recommended helpers (consistent with examples):**
130
+ When depositing, transferring, or withdrawing, parse the token amount using the token's decimals. When displaying a fetched balance, format it back using those same decimals.
108
131
 
109
132
  ```javascript
110
- // Fetch or define your token's decimals (e.g., 6 for USDC)
111
- const tokenDecimals = 6;
133
+ const tokenDecimals = 6; // e.g., USDC
112
134
 
113
135
  const amountToDeposit = ethers.parseUnits("0.1", tokenDecimals);
114
136
  await client.confidentialDeposit(signer, tokenAddress, amountToDeposit);
@@ -132,93 +154,326 @@ const balance = await client.getConfidentialBalance(
132
154
  console.log("Balance:", ethers.formatUnits(balance.amount, tokenDecimals));
133
155
  ```
134
156
 
135
- ### Key Functions
157
+ ### Key Methods
158
+
159
+ #### `ensureAccount(wallet, options?)`
160
+
161
+ Creates a confidential account on-chain (if one doesn't exist) and waits for finalization. **Must be called before any confidential operation.**
136
162
 
137
- The following methods are the primary entry points for interacting with the confidential system.
163
+ - **Returns**: `{ publicKey, privateKey }` — derived ElGamal keypair for this wallet.
164
+ - `options.waitForFinalization` (default `true`) — wait for the account to be finalized.
165
+ - `options.maxAttempts` (default `225`) — maximum polling attempts.
166
+
167
+ ```javascript
168
+ const keys = await client.ensureAccount(wallet);
169
+ ```
138
170
 
139
171
  #### `getAccountInfo(address)`
140
172
 
141
- Fetches account core information from the contract.
173
+ Fetches on-chain account state: `exists`, `finalized`, `elgamalPubkey`, `txId`, etc.
142
174
 
143
- - **Parameters**:
144
- - `address` (string): The account address.
145
- - **Returns**: Contract account data (exists, finalized, pubkey, etc.).
175
+ #### `getConfidentialBalance(address, privateKey, tokenAddress)`
146
176
 
147
- #### `ensureAccount(signer, options)`
177
+ Decrypts and returns the available and pending balances.
148
178
 
149
- Initializes or retrieves the cryptographic keys associated with an account. This step is required before performing any confidential operations. Automatically creates the account onchain if it doesn't exist.
179
+ - **Returns**: `{ amount, available: { amount, ciphertext }, pending: { amount, ciphertext } }`
150
180
 
151
- - **Parameters**:
152
- - `signer` (ethers.Signer): The ethers.js signer instance for the user.
153
- - `options` (object, optional):
154
- - `waitForFinalization` (boolean): Wait for account finalization. Default: `true`
155
- - `maxAttempts` (number): Maximum attempts to check finalization. Default: `30`
156
- - **Returns**: An object containing the user's private and public keys for the confidential system.
181
+ #### `confidentialDeposit(wallet, tokenAddress, amount, options?)`
157
182
 
158
- #### `getConfidentialBalance(address, privateKey, tokenAddress)`
183
+ Deposits ERC-20 tokens into the confidential contract. Handles ERC-20 approval automatically.
159
184
 
160
- Retrieves the decrypted available and pending balances for a specific token, plus the total.
185
+ - **Returns**: Transaction receipt.
161
186
 
162
- - **Parameters**:
163
- - `address` (string): The account address.
164
- - `privateKey` (string): The private key for decryption.
165
- - `tokenAddress` (string): The token contract address.
166
- - **Returns**: An object containing:
167
- - `amount` (bigint): The total (available + pending)
168
- - `available` (object): `{ amount, ciphertext }`
169
- - `pending` (object): `{ amount, ciphertext }`
187
+ #### `confidentialTransfer(senderWallet, recipientAddress, tokenAddress, amount, options?)`
170
188
 
171
- #### `confidentialDeposit(signer, tokenAddress, amount, options)`
189
+ Transfers a confidential amount to a recipient. The recipient must have an existing confidential account.
172
190
 
173
- Deposits a specified amount of ERC20 tokens into the confidential contract, converting them into a "pending" confidential balance.
191
+ - **Returns**: Transaction receipt.
174
192
 
175
- - **Parameters**:
176
- - `signer` (ethers.Signer): The transaction signer.
177
- - `tokenAddress` (string): The contract address of the ERC20 token.
178
- - `amount` (bigint | string | number): The amount to deposit, parsed with token decimals.
179
- - `options` (object, optional):
180
- - `waitForFinalization` (boolean): Wait for deposit finalization. Default: `true`
181
- - **Returns**: A transaction receipt.
193
+ #### `withdraw(wallet, tokenAddress, amount, options?)`
182
194
 
183
- #### `confidentialTransfer(signer, recipientAddress, tokenAddress, amount, options)`
195
+ Withdraws from the confidential available balance back to public ERC-20.
184
196
 
185
- Executes a confidential transfer of tokens from the sender to a recipient. The amount and nature of the transfer are encrypted.
197
+ - **Returns**: Transaction receipt.
186
198
 
187
- - **Parameters**:
188
- - `signer` (ethers.Signer): The sender's signer.
189
- - `recipientAddress` (string): The public address of the recipient.
190
- - `tokenAddress` (string): The token contract address.
191
- - `amount` (bigint | string | number): The amount to transfer, parsed with token decimals.
192
- - `options` (object, optional):
193
- - `useOffchainVerify` (boolean): Use offchain verification. Default: `false`
194
- - `waitForFinalization` (boolean): Wait for transfer finalization. Default: `true`
195
- - **Returns**: A transaction receipt.
199
+ #### `getFeeAmount()`
196
200
 
197
- #### `withdraw(signer, tokenAddress, amount, options)`
201
+ Returns the native fee (in wei) required for confidential transfers on the current chain.
198
202
 
199
- Withdraws funds from the confidential "available" balance back to the public layer (ERC20 tokens).
203
+ #### `getPublicBalance(address, tokenAddress)`
200
204
 
201
- - **Parameters**:
202
- - `signer` (ethers.Signer): The user's signer.
203
- - `tokenAddress` (string): The token contract address.
204
- - `amount` (bigint | string | number): The amount to withdraw, parsed with token decimals.
205
- - `options` (object, optional):
206
- - `useOffchainVerify` (boolean): Use offchain verification. Default: `false`
207
- - `waitForFinalization` (boolean): Wait for withdrawal finalization. Default: `true`
208
- - **Returns**: A transaction receipt.
205
+ Returns the public ERC-20 balance for an address.
209
206
 
210
- ### Examples
207
+ ---
211
208
 
212
- For a complete implementation demonstrating the full lifecycle of a confidential transaction—from deposit to withdrawal—please refer to the `examples/complete-flow.js` file included in this repository.
209
+ ## AnonymousTransferClient
213
210
 
214
- Additional examples are available in the `examples/` directory:
211
+ The `AnonymousTransferClient` routes all operations through the **Fairycloak relay server**. The relay submits transactions on-chain and pays gas on your behalf (except for deposits, which require the user to pay). Your wallet address is never revealed — only a numeric anonymous account ID appears on-chain.
215
212
 
216
- - **complete-flow.js**: Full workflow example covering all operations
217
- - **simple-snippets.js**: Quick code snippets for common tasks
213
+ > **Access Required** — Anonymous transfers are available to teams building privacy-critical applications. To obtain a Fairycloak relay URL and API key, reach out to the Fairblock team at [hello@fairblock.network](mailto:hello@fairblock.network).
218
214
 
219
- ## Error Handling
215
+ ### Initialization
216
+
217
+ ```javascript
218
+ import { AnonymousTransferClient } from "@fairblock/stabletrust";
219
+
220
+ const client = new AnonymousTransferClient({
221
+ fairycloakUrl: "http://your-fairycloak-url", // provided by Fairblock
222
+ chainId: 84532,
223
+ rpcUrl: "https://sepolia.base.org",
224
+ });
225
+ ```
226
+
227
+ ### Key Derivation
228
+
229
+ Anonymous accounts use a per-account ElGamal keypair derived from a wallet signature. **Store the returned `privateKey` securely** — it cannot be recovered without the original wallet and account ID.
230
+
231
+ ```javascript
232
+ const keys = await client.deriveAnonymousKeys(authWallet, accountId);
233
+ // keys.publicKey — base64, register this when creating an account
234
+ // keys.privateKey — base64, keep this secret; used for decryption and proof generation
235
+ ```
236
+
237
+ ### Key Methods
220
238
 
221
- The SDK provides descriptive error messages for common issues. Here are some typical scenarios:
239
+ #### `getNextAccountId()`
240
+
241
+ Returns the account count. The ID that will be assigned to the **next** new account is `Number(count) + 1`.
242
+
243
+ ```javascript
244
+ const count = await client.getNextAccountId();
245
+ const myNewId = Number(count) + 1;
246
+ await client.createAccount(wallet, keys.publicKey);
247
+ ```
248
+
249
+ #### `createAccount(authWallet, elgamalPublicKey, options?)`
250
+
251
+ Creates a new anonymous account via the relay. The relay pays gas.
252
+
253
+ #### `updateAuthKeys(authWallet, accountId, { add, remove }, options?)`
254
+
255
+ Adds or removes authorised signers for an anonymous account. Pass `ethers.Wallet` instances or raw uncompressed hex pubkey strings.
256
+
257
+ #### `getAnonymousAccountInfo(accountId)`
258
+
259
+ Returns on-chain state: `exists`, `finalized`, `hasPendingAction`, `txId`, `elgamalPubkey`, `authNonce`.
260
+
261
+ #### `isAuthorizedSigner(accountId, signerAddress)`
262
+
263
+ Checks whether an address is an authorised signer for the given account.
264
+
265
+ #### `getBalance(accountId, tokenAddress, elGamalPrivateKey)`
266
+
267
+ Returns decrypted balance totals in contract scale.
268
+
269
+ - **Returns**: `{ amount, available, pending }`
270
+
271
+ #### `getAnonymousBalance(accountId, tokenAddress, elGamalPrivateKey)`
272
+
273
+ Returns decrypted balances including raw ciphertexts — useful when you need the ciphertext to generate proofs manually.
274
+
275
+ - **Returns**: `{ available: { amount, ciphertext }, pending: { amount, ciphertext } }`
276
+
277
+ #### `deposit(authWallet, accountId, tokenAddress, amount, options?)`
278
+
279
+ Deposits tokens into an anonymous account. The **user pays gas** for this operation. Handles ERC-20 approval automatically.
280
+
281
+ ```javascript
282
+ const result = await client.deposit(
283
+ wallet,
284
+ accountId,
285
+ tokenAddress,
286
+ ethers.parseUnits("10", 6),
287
+ );
288
+ await client.waitForRequest(result.request_id);
289
+ ```
290
+
291
+ #### `transferToPublic(authWallet, accountId, params, options?)`
292
+
293
+ Transfers from an anonymous account to a public EVM address. The relay pays gas.
294
+
295
+ **Auto-proof mode** (recommended):
296
+
297
+ ```javascript
298
+ const result = await client.transferToPublic(wallet, accountId, {
299
+ recipient: "0xRecipientAddress",
300
+ token: tokenAddress,
301
+ elGamalPrivateKey: keys.privateKey,
302
+ amount: ethers.parseUnits("5", 6),
303
+ });
304
+ ```
305
+
306
+ **Manual proof mode** (advanced):
307
+
308
+ ```javascript
309
+ const proofHex = await client.generateTransferProof(keys.privateKey, {
310
+ currentBalanceCiphertext: ciphertext,
311
+ currentBalanceContractScale: balanceInContractScale,
312
+ transferAmountContractScale: amountInContractScale,
313
+ destinationPublicKey: recipientElGamalPubkey,
314
+ });
315
+ const result = await client.transferToPublic(wallet, accountId, {
316
+ recipient: "0xRecipientAddress",
317
+ token: tokenAddress,
318
+ proof: proofHex,
319
+ });
320
+ ```
321
+
322
+ #### `transferToAnonymous(authWallet, senderAccountId, params, options?)`
323
+
324
+ Transfers between two anonymous accounts. The relay pays gas.
325
+
326
+ ```javascript
327
+ const result = await client.transferToAnonymous(wallet, senderAccountId, {
328
+ recipientId: recipientAccountId,
329
+ token: tokenAddress,
330
+ elGamalPrivateKey: keys.privateKey,
331
+ amount: ethers.parseUnits("3", 6),
332
+ });
333
+ ```
334
+
335
+ #### `applyPending(authWallet, accountId, options?)`
336
+
337
+ Moves a pending incoming balance into available. Must be called after receiving an anonymous-to-anonymous transfer. The relay pays gas.
338
+
339
+ #### `withdraw(authWallet, accountId, params, options?)`
340
+
341
+ Withdraws from an anonymous account to a public EVM address. The relay pays gas.
342
+
343
+ ```javascript
344
+ const result = await client.withdraw(wallet, accountId, {
345
+ destination: "0xDestinationAddress",
346
+ token: tokenAddress,
347
+ plainAmount: ethers.parseUnits("2", 6),
348
+ elGamalPrivateKey: keys.privateKey,
349
+ });
350
+ ```
351
+
352
+ #### Request Tracking
353
+
354
+ All relay operations return a `{ request_id, tx_hash, status }` object. Use the following to track completion:
355
+
356
+ ```javascript
357
+ // Poll until terminal state (completed / confirmed / failed)
358
+ const final = await client.waitForRequest(result.request_id);
359
+
360
+ // Get current status
361
+ const status = await client.getRequestStatus(result.request_id);
362
+
363
+ // Get full event history (useful for reconnect/recovery)
364
+ const history = await client.getRequestEvents(result.request_id);
365
+ ```
366
+
367
+ ---
368
+
369
+ ## End-to-End Flow Examples
370
+
371
+ ### Confidential Flow (ConfidentialTransferClient)
372
+
373
+ ```javascript
374
+ import { ConfidentialTransferClient } from "@fairblock/stabletrust";
375
+ import { ethers } from "ethers";
376
+
377
+ const client = new ConfidentialTransferClient(
378
+ "https://sepolia.base.org",
379
+ 84532,
380
+ );
381
+ const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
382
+ const tokenAddress = "0xYourTokenAddress";
383
+ const tokenDecimals = 6;
384
+
385
+ // 1. Ensure account exists and get encryption keys
386
+ const keys = await client.ensureAccount(wallet);
387
+
388
+ // 2. Deposit
389
+ const depositAmount = ethers.parseUnits("10", tokenDecimals);
390
+ await client.confidentialDeposit(wallet, tokenAddress, depositAmount);
391
+
392
+ // 3. Check balance
393
+ const balance = await client.getConfidentialBalance(
394
+ wallet.address,
395
+ keys.privateKey,
396
+ tokenAddress,
397
+ );
398
+ console.log(
399
+ "Available:",
400
+ ethers.formatUnits(balance.available.amount, tokenDecimals),
401
+ );
402
+
403
+ // 4. Transfer (recipient must have called ensureAccount)
404
+ const transferAmount = ethers.parseUnits("5", tokenDecimals);
405
+ await client.confidentialTransfer(
406
+ wallet,
407
+ recipientAddress,
408
+ tokenAddress,
409
+ transferAmount,
410
+ );
411
+
412
+ // 5. Withdraw
413
+ const withdrawAmount = ethers.parseUnits("2", tokenDecimals);
414
+ await client.withdraw(wallet, tokenAddress, withdrawAmount);
415
+ ```
416
+
417
+ ### Anonymous Flow (AnonymousTransferClient)
418
+
419
+ ```javascript
420
+ import { AnonymousTransferClient } from "@fairblock/stabletrust";
421
+ import { ethers } from "ethers";
422
+
423
+ const client = new AnonymousTransferClient({
424
+ fairycloakUrl: "http://your-fairycloak-url",
425
+ chainId: 84532,
426
+ rpcUrl: "https://sepolia.base.org",
427
+ });
428
+ const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
429
+ const tokenAddress = "0xYourTokenAddress";
430
+ const tokenDecimals = 6;
431
+
432
+ // 1. Determine the ID your account will get, then create it
433
+ const count = await client.getNextAccountId();
434
+ const accountId = Number(count) + 1;
435
+ const keys = await client.deriveAnonymousKeys(wallet, accountId);
436
+ await client.createAccount(wallet, keys.publicKey);
437
+
438
+ // 2. Deposit (user pays gas)
439
+ const depositResult = await client.deposit(
440
+ wallet,
441
+ accountId,
442
+ tokenAddress,
443
+ ethers.parseUnits("10", tokenDecimals),
444
+ );
445
+ await client.waitForRequest(depositResult.request_id);
446
+
447
+ // 3. Check balance
448
+ const balance = await client.getBalance(
449
+ accountId,
450
+ tokenAddress,
451
+ keys.privateKey,
452
+ );
453
+ console.log("Available:", balance.available, "Pending:", balance.pending);
454
+
455
+ // 4. Transfer to a public address (relay pays gas)
456
+ const transferResult = await client.transferToPublic(wallet, accountId, {
457
+ recipient: "0xRecipientAddress",
458
+ token: tokenAddress,
459
+ elGamalPrivateKey: keys.privateKey,
460
+ amount: ethers.parseUnits("3", tokenDecimals),
461
+ });
462
+ await client.waitForRequest(transferResult.request_id);
463
+
464
+ // 5. Withdraw (relay pays gas)
465
+ const withdrawResult = await client.withdraw(wallet, accountId, {
466
+ destination: wallet.address,
467
+ token: tokenAddress,
468
+ plainAmount: ethers.parseUnits("2", tokenDecimals),
469
+ elGamalPrivateKey: keys.privateKey,
470
+ });
471
+ await client.waitForRequest(withdrawResult.request_id);
472
+ ```
473
+
474
+ ---
475
+
476
+ ## Error Handling
222
477
 
223
478
  ```javascript
224
479
  try {
@@ -243,61 +498,63 @@ try {
243
498
 
244
499
  ### Common Issues and Solutions
245
500
 
246
- | Issue | Cause | Solution |
247
- | :------------------------------- | :------------------------------------------------------ | :-------------------------------------------------- |
248
- | "Account does not exist" | Recipient hasn't initialized their confidential account | Recipient must call `ensureAccount()` first |
249
- | "Insufficient balance" | Transfer amount exceeds available confidential balance | Deposit more tokens or reduce transfer amount |
250
- | "Insufficient fee token balance" | Not enough PathUSD on Tempo chain for fees | Top up fee token balance before transferring |
251
- | "Account finalization timeout" | Account creation is still processing | Wait a few minutes and retry the operation |
252
- | "Proof generation failed" | Invalid inputs or cryptographic operation error | Verify all parameters and ensure sufficient balance |
501
+ | Issue | Cause | Solution |
502
+ | :----------------------------- | :------------------------------------------------------ | :------------------------------------------------------ |
503
+ | "Account does not exist" | Recipient hasn't initialized their confidential account | Recipient must call `ensureAccount()` first |
504
+ | "Insufficient balance" | Transfer amount exceeds available confidential balance | Deposit more tokens or reduce transfer amount |
505
+ | "Account finalization timeout" | Account creation is still processing | Wait a few minutes and retry |
506
+ | "Proof generation failed" | Invalid inputs or cryptographic operation error | Verify all parameters and ensure sufficient balance |
507
+ | "Amount too small" | Amount rounds to 0 in contract scale | Use a larger amount (minimum depends on token decimals) |
508
+ | Fairycloak HTTP error | Relay unreachable or request rejected | Check the relay URL, API key, and account authorization |
509
+
510
+ ---
253
511
 
254
512
  ## Performance Metrics
255
513
 
256
- The following are estimated execution times for standard operations within the confidential flow. Please note that these durations may vary based on network congestion and client hardware performance.
514
+ The following are estimated execution times for standard operations. Durations may vary based on network congestion and hardware.
257
515
 
258
- The following are estimated execution times for standard operations within the confidential flow. Please note that these durations may vary based on network congestion and client hardware performance.
516
+ | Operation | Avg Duration |
517
+ | :--------------- | :----------- |
518
+ | Account Creation | 45s |
519
+ | Deposit | 63s |
520
+ | Transfer | 58s |
521
+ | Withdraw | 58s |
259
522
 
260
- | Operation | Avg Duration |
261
- | :-------- | :----------- |
262
- | Creation | 45s |
263
- | Deposit | 63s |
264
- | Transfer | 58s |
265
- | Withdraw | 58s |
523
+ ---
266
524
 
267
525
  ## Security Considerations
268
526
 
269
- When using the Stabletrust SDK, follow these best practices to ensure the security of your confidential transactions:
527
+ When using the StableTrust SDK, follow these best practices to ensure the security of your confidential transactions:
270
528
 
271
529
  1. **Private Key Management**
272
530
  - Never expose or log private keys or seed phrases
273
531
  - Store private keys securely (e.g., hardware wallets, encrypted vaults)
274
- - Derived keys are sensitive cryptographic material—handle with care
532
+ - Anonymous account `privateKey` values are sensitive — treat them the same as wallet private keys
275
533
 
276
534
  2. **Signer Security**
277
535
  - Use secure signer implementations (e.g., hardware wallets, encrypted key stores)
278
536
  - Avoid using signers with exposed private keys in production
279
- - Keep your ethers.js provider and signer in sync with your security setup
280
537
 
281
538
  3. **Network Security**
282
539
  - Use HTTPS-only RPC endpoints
283
- - Verify contract addresses before initialization to prevent man-in-the-middle attacks
284
- - Consider using dedicated RPC providers for production environments
540
+ - Verify contract and diamond addresses before initialization
541
+ - Consider dedicated RPC providers for production environments
285
542
 
286
543
  4. **Account Initialization**
287
- - Always call `ensureAccount()` before performing any confidential operations
544
+ - For `ConfidentialTransferClient`: always call `ensureAccount()` before any operation
545
+ - For `AnonymousTransferClient`: derive and securely store keys before creating an account
288
546
  - Verify that recipient accounts exist before transferring funds
289
- - Allow sufficient time for account finalization before proceeding with operations
290
547
 
291
548
  5. **Balance Verification**
292
-
293
- - Check available balance before initiating transfers
294
- - Be aware of transaction fees that may vary by network
295
- - On Tempo chain, ensure sufficient PathUSD balance for fee payment
549
+ - Check available balance before initiating transfers
550
+ - Anonymous transfers between anonymous accounts require `applyPending()` before the recipient can spend
296
551
 
297
552
  6. **Error Handling**
298
553
  - Implement comprehensive error handling for all SDK operations
299
- - Log errors appropriately without exposing sensitive information
300
- - Implement retry logic for transient failures (network timeouts, etc.)
554
+ - Log errors without exposing sensitive information
555
+ - Implement retry logic for transient network failures
556
+
557
+ ---
301
558
 
302
559
  ## Resources
303
560