@fairblock/stabletrust 1.0.22 → 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 +360 -106
- package/examples/anonymous-flow.js +334 -0
- package/package.json +1 -1
- package/pkg/README.md +310 -0
- package/pkg/confidential_transfer_proof_generation.d.ts +13 -9
- package/pkg/confidential_transfer_proof_generation.js +571 -604
- package/pkg/confidential_transfer_proof_generation_bg.wasm +0 -0
- package/pkg/confidential_transfer_proof_generation_bg.wasm.d.ts +2 -0
- package/src/anonymous-client.js +1371 -0
- package/src/{client.js → confidential-client.js} +11 -8
- package/src/constants.js +8 -9
- package/src/index.d.ts +344 -1
- package/src/index.js +2 -1
- package/pkg/proof_generator_wasm_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The
|
|
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:
|
|
@@ -45,9 +70,11 @@ The following contract addresses are available for confidential transfers on tes
|
|
|
45
70
|
| Arbitrum | 421614 | `0x14Afd604971bee5b5fac52df2d56CaE421519Cc5` |
|
|
46
71
|
| Tempo | 42431 | `0x08B6563C95dfe3a4F5533CAA6F7D55a74FCb4F6c` |
|
|
47
72
|
|
|
48
|
-
|
|
73
|
+
---
|
|
49
74
|
|
|
50
|
-
|
|
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
|
-
//
|
|
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,
|
|
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(
|
|
@@ -91,27 +116,21 @@ const arcClient = new ConfidentialTransferClient(
|
|
|
91
116
|
5042002,
|
|
92
117
|
);
|
|
93
118
|
|
|
94
|
-
// Tempo (
|
|
119
|
+
// Tempo (special fee handling via IPFS proof upload)
|
|
95
120
|
const tempoClient = new ConfidentialTransferClient(
|
|
96
121
|
"https://tempo-rpc.example.com",
|
|
97
122
|
42431,
|
|
98
123
|
);
|
|
99
124
|
```
|
|
100
125
|
|
|
101
|
-
**Note on Tempo Chain**: The Tempo network (chainId 42431)
|
|
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.
|
|
102
127
|
|
|
103
128
|
### Token Denomination
|
|
104
129
|
|
|
105
|
-
When
|
|
106
|
-
|
|
107
|
-
- To deposit, transfer, or withdraw tokens, parse the amount using `ethers.parseUnits(amount, tokenDecimals)`.
|
|
108
|
-
- To display a balance, format the raw amount using `ethers.formatUnits(balance.amount, tokenDecimals)`.
|
|
109
|
-
|
|
110
|
-
**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.
|
|
111
131
|
|
|
112
132
|
```javascript
|
|
113
|
-
|
|
114
|
-
const tokenDecimals = 6;
|
|
133
|
+
const tokenDecimals = 6; // e.g., USDC
|
|
115
134
|
|
|
116
135
|
const amountToDeposit = ethers.parseUnits("0.1", tokenDecimals);
|
|
117
136
|
await client.confidentialDeposit(signer, tokenAddress, amountToDeposit);
|
|
@@ -135,93 +154,326 @@ const balance = await client.getConfidentialBalance(
|
|
|
135
154
|
console.log("Balance:", ethers.formatUnits(balance.amount, tokenDecimals));
|
|
136
155
|
```
|
|
137
156
|
|
|
138
|
-
### Key
|
|
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.**
|
|
162
|
+
|
|
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.
|
|
139
166
|
|
|
140
|
-
|
|
167
|
+
```javascript
|
|
168
|
+
const keys = await client.ensureAccount(wallet);
|
|
169
|
+
```
|
|
141
170
|
|
|
142
171
|
#### `getAccountInfo(address)`
|
|
143
172
|
|
|
144
|
-
Fetches account
|
|
173
|
+
Fetches on-chain account state: `exists`, `finalized`, `elgamalPubkey`, `txId`, etc.
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
- `address` (string): The account address.
|
|
148
|
-
- **Returns**: Contract account data (exists, finalized, pubkey, etc.).
|
|
175
|
+
#### `getConfidentialBalance(address, privateKey, tokenAddress)`
|
|
149
176
|
|
|
150
|
-
|
|
177
|
+
Decrypts and returns the available and pending balances.
|
|
151
178
|
|
|
152
|
-
|
|
179
|
+
- **Returns**: `{ amount, available: { amount, ciphertext }, pending: { amount, ciphertext } }`
|
|
153
180
|
|
|
154
|
-
|
|
155
|
-
- `signer` (ethers.Signer): The ethers.js signer instance for the user.
|
|
156
|
-
- `options` (object, optional):
|
|
157
|
-
- `waitForFinalization` (boolean): Wait for account finalization. Default: `true`
|
|
158
|
-
- `maxAttempts` (number): Maximum attempts to check finalization. Default: `30`
|
|
159
|
-
- **Returns**: An object containing the user's private and public keys for the confidential system.
|
|
181
|
+
#### `confidentialDeposit(wallet, tokenAddress, amount, options?)`
|
|
160
182
|
|
|
161
|
-
|
|
183
|
+
Deposits ERC-20 tokens into the confidential contract. Handles ERC-20 approval automatically.
|
|
162
184
|
|
|
163
|
-
|
|
185
|
+
- **Returns**: Transaction receipt.
|
|
164
186
|
|
|
165
|
-
|
|
166
|
-
- `address` (string): The account address.
|
|
167
|
-
- `privateKey` (string): The private key for decryption.
|
|
168
|
-
- `tokenAddress` (string): The token contract address.
|
|
169
|
-
- **Returns**: An object containing:
|
|
170
|
-
- `amount` (bigint): The total (available + pending)
|
|
171
|
-
- `available` (object): `{ amount, ciphertext }`
|
|
172
|
-
- `pending` (object): `{ amount, ciphertext }`
|
|
187
|
+
#### `confidentialTransfer(senderWallet, recipientAddress, tokenAddress, amount, options?)`
|
|
173
188
|
|
|
174
|
-
|
|
189
|
+
Transfers a confidential amount to a recipient. The recipient must have an existing confidential account.
|
|
175
190
|
|
|
176
|
-
|
|
191
|
+
- **Returns**: Transaction receipt.
|
|
177
192
|
|
|
178
|
-
|
|
179
|
-
- `signer` (ethers.Signer): The transaction signer.
|
|
180
|
-
- `tokenAddress` (string): The contract address of the ERC20 token.
|
|
181
|
-
- `amount` (bigint | string | number): The amount to deposit, parsed with token decimals.
|
|
182
|
-
- `options` (object, optional):
|
|
183
|
-
- `waitForFinalization` (boolean): Wait for deposit finalization. Default: `true`
|
|
184
|
-
- **Returns**: A transaction receipt.
|
|
193
|
+
#### `withdraw(wallet, tokenAddress, amount, options?)`
|
|
185
194
|
|
|
186
|
-
|
|
195
|
+
Withdraws from the confidential available balance back to public ERC-20.
|
|
187
196
|
|
|
188
|
-
|
|
197
|
+
- **Returns**: Transaction receipt.
|
|
189
198
|
|
|
190
|
-
|
|
191
|
-
- `signer` (ethers.Signer): The sender's signer.
|
|
192
|
-
- `recipientAddress` (string): The public address of the recipient.
|
|
193
|
-
- `tokenAddress` (string): The token contract address.
|
|
194
|
-
- `amount` (bigint | string | number): The amount to transfer, parsed with token decimals.
|
|
195
|
-
- `options` (object, optional):
|
|
196
|
-
- `useOffchainVerify` (boolean): Use offchain verification. Default: `false`
|
|
197
|
-
- `waitForFinalization` (boolean): Wait for transfer finalization. Default: `true`
|
|
198
|
-
- **Returns**: A transaction receipt.
|
|
199
|
+
#### `getFeeAmount()`
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
Returns the native fee (in wei) required for confidential transfers on the current chain.
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
#### `getPublicBalance(address, tokenAddress)`
|
|
203
204
|
|
|
204
|
-
-
|
|
205
|
-
- `signer` (ethers.Signer): The user's signer.
|
|
206
|
-
- `tokenAddress` (string): The token contract address.
|
|
207
|
-
- `amount` (bigint | string | number): The amount to withdraw, parsed with token decimals.
|
|
208
|
-
- `options` (object, optional):
|
|
209
|
-
- `useOffchainVerify` (boolean): Use offchain verification. Default: `false`
|
|
210
|
-
- `waitForFinalization` (boolean): Wait for withdrawal finalization. Default: `true`
|
|
211
|
-
- **Returns**: A transaction receipt.
|
|
205
|
+
Returns the public ERC-20 balance for an address.
|
|
212
206
|
|
|
213
|
-
|
|
207
|
+
---
|
|
214
208
|
|
|
215
|
-
|
|
209
|
+
## AnonymousTransferClient
|
|
216
210
|
|
|
217
|
-
|
|
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.
|
|
218
212
|
|
|
219
|
-
|
|
220
|
-
- **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).
|
|
221
214
|
|
|
222
|
-
|
|
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
|
|
238
|
+
|
|
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
|
+
---
|
|
223
368
|
|
|
224
|
-
|
|
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
|
|
225
477
|
|
|
226
478
|
```javascript
|
|
227
479
|
try {
|
|
@@ -246,61 +498,63 @@ try {
|
|
|
246
498
|
|
|
247
499
|
### Common Issues and Solutions
|
|
248
500
|
|
|
249
|
-
| Issue
|
|
250
|
-
|
|
|
251
|
-
| "Account does not exist"
|
|
252
|
-
| "Insufficient balance"
|
|
253
|
-
| "
|
|
254
|
-
| "
|
|
255
|
-
| "
|
|
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
|
+
---
|
|
256
511
|
|
|
257
512
|
## Performance Metrics
|
|
258
513
|
|
|
259
|
-
The following are estimated execution times for standard operations
|
|
514
|
+
The following are estimated execution times for standard operations. Durations may vary based on network congestion and hardware.
|
|
260
515
|
|
|
261
|
-
|
|
516
|
+
| Operation | Avg Duration |
|
|
517
|
+
| :--------------- | :----------- |
|
|
518
|
+
| Account Creation | 45s |
|
|
519
|
+
| Deposit | 63s |
|
|
520
|
+
| Transfer | 58s |
|
|
521
|
+
| Withdraw | 58s |
|
|
262
522
|
|
|
263
|
-
|
|
264
|
-
| :-------- | :----------- |
|
|
265
|
-
| Creation | 45s |
|
|
266
|
-
| Deposit | 63s |
|
|
267
|
-
| Transfer | 58s |
|
|
268
|
-
| Withdraw | 58s |
|
|
523
|
+
---
|
|
269
524
|
|
|
270
525
|
## Security Considerations
|
|
271
526
|
|
|
272
|
-
When using the
|
|
527
|
+
When using the StableTrust SDK, follow these best practices to ensure the security of your confidential transactions:
|
|
273
528
|
|
|
274
529
|
1. **Private Key Management**
|
|
275
530
|
- Never expose or log private keys or seed phrases
|
|
276
531
|
- Store private keys securely (e.g., hardware wallets, encrypted vaults)
|
|
277
|
-
-
|
|
532
|
+
- Anonymous account `privateKey` values are sensitive — treat them the same as wallet private keys
|
|
278
533
|
|
|
279
534
|
2. **Signer Security**
|
|
280
535
|
- Use secure signer implementations (e.g., hardware wallets, encrypted key stores)
|
|
281
536
|
- Avoid using signers with exposed private keys in production
|
|
282
|
-
- Keep your ethers.js provider and signer in sync with your security setup
|
|
283
537
|
|
|
284
538
|
3. **Network Security**
|
|
285
539
|
- Use HTTPS-only RPC endpoints
|
|
286
|
-
- Verify contract addresses before initialization
|
|
287
|
-
- Consider
|
|
540
|
+
- Verify contract and diamond addresses before initialization
|
|
541
|
+
- Consider dedicated RPC providers for production environments
|
|
288
542
|
|
|
289
543
|
4. **Account Initialization**
|
|
290
|
-
-
|
|
544
|
+
- For `ConfidentialTransferClient`: always call `ensureAccount()` before any operation
|
|
545
|
+
- For `AnonymousTransferClient`: derive and securely store keys before creating an account
|
|
291
546
|
- Verify that recipient accounts exist before transferring funds
|
|
292
|
-
- Allow sufficient time for account finalization before proceeding with operations
|
|
293
547
|
|
|
294
548
|
5. **Balance Verification**
|
|
295
|
-
|
|
296
|
-
-
|
|
297
|
-
- Be aware of transaction fees that may vary by network
|
|
298
|
-
- 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
|
|
299
551
|
|
|
300
552
|
6. **Error Handling**
|
|
301
553
|
- Implement comprehensive error handling for all SDK operations
|
|
302
|
-
- Log errors
|
|
303
|
-
- Implement retry logic for transient
|
|
554
|
+
- Log errors without exposing sensitive information
|
|
555
|
+
- Implement retry logic for transient network failures
|
|
556
|
+
|
|
557
|
+
---
|
|
304
558
|
|
|
305
559
|
## Resources
|
|
306
560
|
|