@fairblock/stabletrust 1.0.7 → 1.0.9

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/src/client.js CHANGED
@@ -88,7 +88,7 @@ export class ConfidentialTransferClient {
88
88
  * @param {ethers.Wallet|ethers.Signer} wallet - The wallet to derive keys for
89
89
  * @returns {Promise<{publicKey: string, privateKey: string}>}
90
90
  */
91
- async deriveKeys(wallet) {
91
+ async _deriveKeys(wallet) {
92
92
  try {
93
93
  if (!wallet) {
94
94
  throw new Error("Wallet is required");
@@ -138,7 +138,7 @@ export class ConfidentialTransferClient {
138
138
 
139
139
  try {
140
140
  const address = await wallet.getAddress();
141
- const keys = await this.deriveKeys(wallet);
141
+ const keys = await this._deriveKeys(wallet);
142
142
  let accountInfo = await this.getAccountInfo(address);
143
143
 
144
144
  if (!accountInfo.exists) {
@@ -180,18 +180,61 @@ export class ConfidentialTransferClient {
180
180
  }
181
181
 
182
182
  /**
183
- * Get decrypted balance for an address
183
+ * Get total decrypted balance (available + pending) for an address
184
184
  *
185
185
  * @param {string} address - Account address
186
186
  * @param {string} privateKey - Private key for decryption
187
187
  * @param {string} tokenAddress - Token address
188
- * @param {Object} [options] - Options
189
- * @param {string} [options.type='available'] - Balance type: 'available' or 'pending'
190
- * @returns {Promise<{amount: number, ciphertext: string|null}>}
188
+ * @returns {Promise<{amount: number, available: {amount: number, ciphertext: string|null}, pending: {amount: number, ciphertext: string|null}}>}
191
189
  */
192
- async getBalance(address, privateKey, tokenAddress, options = {}) {
193
- const { type = "available" } = options;
190
+ async getConfidentialBalance(address, privateKey, tokenAddress) {
191
+ try {
192
+ const [available, pending] = await Promise.all([
193
+ this._getAvailableBalance(address, privateKey, tokenAddress),
194
+ this._getPendingBalance(address, privateKey, tokenAddress),
195
+ ]);
196
+
197
+ return {
198
+ amount: available.amount + pending.amount,
199
+ available,
200
+ pending,
201
+ };
202
+ } catch (error) {
203
+ throw new Error(`Failed to get confidential balance: ${error.message}`);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Get decrypted available balance for an address
209
+ * @private
210
+ */
211
+ async _getAvailableBalance(address, privateKey, tokenAddress) {
212
+ return await this._getBalanceByType(
213
+ address,
214
+ privateKey,
215
+ tokenAddress,
216
+ "available",
217
+ );
218
+ }
219
+
220
+ /**
221
+ * Get decrypted pending balance for an address
222
+ * @private
223
+ */
224
+ async _getPendingBalance(address, privateKey, tokenAddress) {
225
+ return await this._getBalanceByType(
226
+ address,
227
+ privateKey,
228
+ tokenAddress,
229
+ "pending",
230
+ );
231
+ }
194
232
 
233
+ /**
234
+ * Shared balance retrieval by type
235
+ * @private
236
+ */
237
+ async _getBalanceByType(address, privateKey, tokenAddress, type) {
195
238
  try {
196
239
  if (!address || !ethers.isAddress(address)) {
197
240
  throw new Error(`Invalid address: ${address}`);
@@ -204,7 +247,7 @@ export class ConfidentialTransferClient {
204
247
  }
205
248
 
206
249
  let c1, c2;
207
- if (type.toLowerCase() === "pending") {
250
+ if (type === "pending") {
208
251
  [c1, c2] = await this.contract.getPending(address, tokenAddress);
209
252
  } else {
210
253
  [c1, c2] = await this.contract.getAvailable(address, tokenAddress);
@@ -228,6 +271,29 @@ export class ConfidentialTransferClient {
228
271
  }
229
272
  }
230
273
 
274
+ /**
275
+ * Apply pending balance if needed (internal helper)
276
+ * @private
277
+ */
278
+ async _applyPendingIfNeeded(wallet, privateKey, tokenAddress, actionLabel) {
279
+ const address = await wallet.getAddress();
280
+ const pendingBalance = await this._getPendingBalance(
281
+ address,
282
+ privateKey,
283
+ tokenAddress,
284
+ );
285
+
286
+ if (pendingBalance.amount > 0) {
287
+ try {
288
+ await this._applyPending(wallet, { waitForFinalization: true });
289
+ } catch (error) {
290
+ console.warn(
291
+ `Warning: Failed to apply pending balance before ${actionLabel}: ${error.message}. You may have pending balance that is not yet applied.`,
292
+ );
293
+ }
294
+ }
295
+ }
296
+
231
297
  /**
232
298
  * Deposit tokens into confidential account
233
299
  *
@@ -238,7 +304,7 @@ export class ConfidentialTransferClient {
238
304
  * @param {boolean} [options.waitForFinalization=true] - Wait for deposit finalization
239
305
  * @returns {Promise<Object>} Transaction receipt
240
306
  */
241
- async deposit(wallet, tokenAddress, amount, options = {}) {
307
+ async confidentialDeposit(wallet, tokenAddress, amount, options = {}) {
242
308
  const { waitForFinalization = true } = options;
243
309
 
244
310
  try {
@@ -253,6 +319,13 @@ export class ConfidentialTransferClient {
253
319
  }
254
320
 
255
321
  const address = await wallet.getAddress();
322
+ const derivedKeys = await this._deriveKeys(wallet);
323
+ await this._applyPendingIfNeeded(
324
+ wallet,
325
+ derivedKeys.privateKey,
326
+ tokenAddress,
327
+ "deposit",
328
+ );
256
329
  const depositAmount = BigInt(amount);
257
330
  const tokenContract = this._getTokenContract(tokenAddress);
258
331
 
@@ -280,7 +353,6 @@ export class ConfidentialTransferClient {
280
353
  throw new Error("Token approval failed");
281
354
  }
282
355
  }
283
-
284
356
  // Perform deposit
285
357
  const depositTx = await this.contract
286
358
  .connect(wallet)
@@ -316,7 +388,7 @@ export class ConfidentialTransferClient {
316
388
  * @param {boolean} [options.waitForFinalization=true] - Wait for transfer finalization
317
389
  * @returns {Promise<Object>} Transaction receipt
318
390
  */
319
- async transfer(
391
+ async confidentialTransfer(
320
392
  senderWallet,
321
393
  recipientAddress,
322
394
  tokenAddress,
@@ -343,7 +415,7 @@ export class ConfidentialTransferClient {
343
415
  const senderAddress = await senderWallet.getAddress();
344
416
 
345
417
  // Auto-derive sender keys
346
- const derivedSenderKeys = await this.deriveKeys(senderWallet);
418
+ const derivedSenderKeys = await this._deriveKeys(senderWallet);
347
419
  if (!derivedSenderKeys?.privateKey) {
348
420
  throw new Error("Failed to derive sender keys");
349
421
  }
@@ -370,22 +442,31 @@ export class ConfidentialTransferClient {
370
442
  ).toString("base64");
371
443
  }
372
444
 
373
- // Auto-fetch current balance
374
- const balanceInfo = await this.getBalance(
445
+ await this._applyPendingIfNeeded(
446
+ senderWallet,
447
+ derivedSenderKeys.privateKey,
448
+ tokenAddress,
449
+ "transfer",
450
+ );
451
+
452
+ const balanceSummary = await this.getConfidentialBalance(
375
453
  senderAddress,
376
454
  derivedSenderKeys.privateKey,
377
455
  tokenAddress,
378
456
  );
379
457
  const fee = await this.getFeeAmount();
380
- if (!balanceInfo) {
381
- throw new Error("Failed to fetch sender balance");
382
- }
383
- const derivedCurrentBalanceCiphertext = balanceInfo.ciphertext;
384
- const derivedCurrentBalance = balanceInfo.amount;
458
+ const derivedCurrentBalanceCiphertext =
459
+ balanceSummary.available.ciphertext;
460
+ const derivedCurrentBalance = balanceSummary.available.amount;
385
461
 
386
462
  if (!derivedCurrentBalanceCiphertext) {
387
463
  throw new Error(
388
- "Current balance ciphertext is required. Did you call getBalance()?",
464
+ "Current balance ciphertext is required. Did you call getConfidentialBalance()?",
465
+ );
466
+ }
467
+ if (balanceSummary.amount < amount) {
468
+ throw new Error(
469
+ `Insufficient balance. Required: ${amount}, Total: ${balanceSummary.amount}`,
389
470
  );
390
471
  }
391
472
  if (
@@ -612,42 +693,6 @@ export class ConfidentialTransferClient {
612
693
 
613
694
  return receipt;
614
695
  }
615
-
616
- /**
617
- * Apply pending balance to available balance
618
- *
619
- * @param {ethers.Wallet|ethers.Signer} wallet - The wallet to apply pending for
620
- * @param {Object} [options] - Options
621
- * @param {boolean} [options.waitForFinalization=true] - Wait for operation finalization
622
- * @returns {Promise<Object>} Transaction receipt
623
- */
624
- async applyPending(wallet, options = {}) {
625
- const { waitForFinalization = true } = options;
626
-
627
- try {
628
- if (!wallet) {
629
- throw new Error("Wallet is required");
630
- }
631
-
632
- const address = await wallet.getAddress();
633
-
634
- const tx = await this.contract.connect(wallet).applyPending();
635
- const receipt = await tx.wait();
636
-
637
- if (!receipt || receipt.status === 0) {
638
- throw new Error("Apply pending transaction failed");
639
- }
640
-
641
- if (waitForFinalization) {
642
- await this._waitForGlobalState(address, "apply pending");
643
- }
644
-
645
- return receipt;
646
- } catch (error) {
647
- throw new Error(`Failed to apply pending: ${error.message}`);
648
- }
649
- }
650
-
651
696
  /**
652
697
  * Withdraw confidential tokens to public ERC20
653
698
  *
@@ -675,28 +720,35 @@ export class ConfidentialTransferClient {
675
720
  }
676
721
 
677
722
  // Auto-derive keys
678
- const derivedKeys = await this.deriveKeys(wallet);
723
+ const derivedKeys = await this._deriveKeys(wallet);
679
724
  if (!derivedKeys?.privateKey) {
680
725
  throw new Error("Failed to derive keys");
681
726
  }
682
727
 
683
- const balanceInfo = await this.getBalance(
684
- wallet.address,
728
+ const address = await wallet.getAddress();
729
+ await this._applyPendingIfNeeded(
730
+ wallet,
731
+ derivedKeys.privateKey,
732
+ tokenAddress,
733
+ "withdraw",
734
+ );
735
+
736
+ const balanceSummary = await this.getConfidentialBalance(
737
+ address,
685
738
  derivedKeys.privateKey,
686
739
  tokenAddress,
687
- {
688
- type: "available",
689
- },
690
740
  );
691
- if (!balanceInfo) {
692
- throw new Error("Failed to fetch sender balance");
693
- }
694
- const currentBalanceCiphertext = balanceInfo.ciphertext;
695
- const currentBalance = balanceInfo.amount;
741
+ const currentBalanceCiphertext = balanceSummary.available.ciphertext;
742
+ const currentBalance = balanceSummary.available.amount;
696
743
 
697
744
  if (!currentBalanceCiphertext) {
698
745
  throw new Error(
699
- "Current balance ciphertext is required. Did you call getBalance()?",
746
+ "Current balance ciphertext is required. Did you call getConfidentialBalance()?",
747
+ );
748
+ }
749
+ if (balanceSummary.amount < amount) {
750
+ throw new Error(
751
+ `Insufficient balance. Required: ${amount}, Total: ${balanceSummary.amount}`,
700
752
  );
701
753
  }
702
754
  if (currentBalance === undefined || currentBalance < amount) {
@@ -705,8 +757,6 @@ export class ConfidentialTransferClient {
705
757
  );
706
758
  }
707
759
 
708
- const address = await wallet.getAddress();
709
-
710
760
  // Generate withdrawal proof
711
761
  const withdrawInput = {
712
762
  current_balance_ciphertext: currentBalanceCiphertext,
@@ -762,6 +812,41 @@ export class ConfidentialTransferClient {
762
812
  }
763
813
  }
764
814
 
815
+ /**
816
+ * Apply pending balance to available balance
817
+ *
818
+ * @param {ethers.Wallet|ethers.Signer} wallet - The wallet to apply pending for
819
+ * @param {Object} [options] - Options
820
+ * @param {boolean} [options.waitForFinalization=true] - Wait for operation finalization
821
+ * @returns {Promise<Object>} Transaction receipt
822
+ */
823
+ async _applyPending(wallet, options = {}) {
824
+ const { waitForFinalization = true } = options;
825
+
826
+ try {
827
+ if (!wallet) {
828
+ throw new Error("Wallet is required");
829
+ }
830
+
831
+ const address = await wallet.getAddress();
832
+
833
+ const tx = await this.contract.connect(wallet).applyPending();
834
+ const receipt = await tx.wait();
835
+
836
+ if (!receipt || receipt.status === 0) {
837
+ throw new Error("Apply pending transaction failed");
838
+ }
839
+
840
+ if (waitForFinalization) {
841
+ await this._waitForGlobalState(address, "apply pending");
842
+ }
843
+
844
+ return receipt;
845
+ } catch (error) {
846
+ throw new Error(`Failed to apply pending: ${error.message}`);
847
+ }
848
+ }
849
+
765
850
  /**
766
851
  * Wait for pending action to complete (internal method)
767
852
  *
@@ -809,18 +894,20 @@ export class ConfidentialTransferClient {
809
894
  * @param {number} [options.intervalMs=3000] - Polling interval in milliseconds
810
895
  * @returns {Promise<{amount: number, ciphertext: string}>}
811
896
  */
812
- async waitForPendingBalance(address, privateKey, tokenAddress, options = {}) {
897
+ async _waitForPendingBalance(
898
+ address,
899
+ privateKey,
900
+ tokenAddress,
901
+ options = {},
902
+ ) {
813
903
  const { maxAttempts = 60, intervalMs = 3000 } = options;
814
904
 
815
905
  try {
816
906
  for (let i = 0; i < maxAttempts; i++) {
817
- const pending = await this.getBalance(
907
+ const pending = await this._getPendingBalance(
818
908
  address,
819
909
  privateKey,
820
910
  tokenAddress,
821
- {
822
- type: "pending",
823
- },
824
911
  );
825
912
 
826
913
  if (pending.amount > 0) {
@@ -861,7 +948,7 @@ export class ConfidentialTransferClient {
861
948
  * @param {string} tokenAddress - Token address
862
949
  * @returns {Promise<bigint>} Token balance
863
950
  */
864
- async getTokenBalance(address, tokenAddress) {
951
+ async getPublicBalance(address, tokenAddress) {
865
952
  try {
866
953
  if (!address || !ethers.isAddress(address)) {
867
954
  throw new Error(`Invalid address: ${address}`);
package/src/index.d.ts CHANGED
@@ -26,6 +26,15 @@ declare module "@fairblock/stabletrust" {
26
26
  ciphertext: string | null;
27
27
  }
28
28
 
29
+ /**
30
+ * Confidential balance summary
31
+ */
32
+ export interface ConfidentialBalance {
33
+ amount: number;
34
+ available: Balance;
35
+ pending: Balance;
36
+ }
37
+
29
38
  /**
30
39
  * Account options
31
40
  */
@@ -49,13 +58,6 @@ declare module "@fairblock/stabletrust" {
49
58
  waitForFinalization?: boolean;
50
59
  }
51
60
 
52
- /**
53
- * Apply pending options
54
- */
55
- export interface ApplyPendingOptions {
56
- waitForFinalization?: boolean;
57
- }
58
-
59
61
  /**
60
62
  * Withdraw options
61
63
  */
@@ -64,21 +66,6 @@ declare module "@fairblock/stabletrust" {
64
66
  waitForFinalization?: boolean;
65
67
  }
66
68
 
67
- /**
68
- * Balance options
69
- */
70
- export interface BalanceOptions {
71
- type?: "available" | "pending";
72
- }
73
-
74
- /**
75
- * Wait for pending balance options
76
- */
77
- export interface WaitForPendingOptions {
78
- maxAttempts?: number;
79
- intervalMs?: number;
80
- }
81
-
82
69
  /**
83
70
  * Main SDK client class
84
71
  * WASM auto-initializes on first use - no manual initialization required!
@@ -92,11 +79,6 @@ declare module "@fairblock/stabletrust" {
92
79
  */
93
80
  constructor(rpcUrl: string, contractAddress: string, chainId: number);
94
81
 
95
- /**
96
- * Derive encryption keys for a wallet
97
- */
98
- deriveKeys(wallet: ethers.Wallet | ethers.Signer): Promise<Keys>;
99
-
100
82
  /**
101
83
  * Get account information from the contract
102
84
  */
@@ -111,18 +93,16 @@ declare module "@fairblock/stabletrust" {
111
93
  ): Promise<Keys>;
112
94
 
113
95
  /**
114
- * Get decrypted balance for an address
96
+ * Get total decrypted balance (available + pending) for an address
115
97
  * @param address Account address
116
98
  * @param privateKey Private key for decryption
117
99
  * @param tokenAddress Token contract address
118
- * @param options Balance options
119
100
  */
120
- getBalance(
101
+ getConfidentialBalance(
121
102
  address: string,
122
103
  privateKey: string,
123
104
  tokenAddress: string,
124
- options?: BalanceOptions,
125
- ): Promise<Balance>;
105
+ ): Promise<ConfidentialBalance>;
126
106
 
127
107
  /**
128
108
  * Deposit tokens into confidential account
@@ -131,7 +111,7 @@ declare module "@fairblock/stabletrust" {
131
111
  * @param amount Amount to deposit
132
112
  * @param options Deposit options
133
113
  */
134
- deposit(
114
+ confidentialDeposit(
135
115
  wallet: ethers.Wallet | ethers.Signer,
136
116
  tokenAddress: string,
137
117
  amount: bigint | string | number,
@@ -147,7 +127,7 @@ declare module "@fairblock/stabletrust" {
147
127
  * @param amount Amount to transfer
148
128
  * @param options Transfer options
149
129
  */
150
- transfer(
130
+ confidentialTransfer(
151
131
  senderWallet: ethers.Wallet | ethers.Signer,
152
132
  recipientAddress: string,
153
133
  tokenAddress: string,
@@ -155,14 +135,6 @@ declare module "@fairblock/stabletrust" {
155
135
  options?: TransferOptions,
156
136
  ): Promise<ethers.ContractTransactionReceipt>;
157
137
 
158
- /**
159
- * Apply pending balance to available balance
160
- */
161
- applyPending(
162
- wallet: ethers.Wallet | ethers.Signer,
163
- options?: ApplyPendingOptions,
164
- ): Promise<ethers.ContractTransactionReceipt>;
165
-
166
138
  /**
167
139
  * Withdraw confidential tokens to public ERC20
168
140
  * @param wallet Wallet to withdraw to
@@ -177,31 +149,17 @@ declare module "@fairblock/stabletrust" {
177
149
  options?: WithdrawOptions,
178
150
  ): Promise<ethers.ContractTransactionReceipt>;
179
151
 
180
- /**
181
- * Wait for pending balance to appear
182
- * @param address Account address
183
- * @param privateKey Private key for decryption
184
- * @param tokenAddress Token contract address
185
- * @param options Wait options
186
- */
187
- waitForPendingBalance(
188
- address: string,
189
- privateKey: string,
190
- tokenAddress: string,
191
- options?: WaitForPendingOptions,
192
- ): Promise<Balance>;
193
-
194
152
  /**
195
153
  * Get the current fee amount for confidential transfers
196
154
  */
197
155
  getFeeAmount(): Promise<bigint>;
198
156
 
199
157
  /**
200
- * Get ERC20 token balance
158
+ * Get the public ERC20 balance for an address (for comparison with confidential balance)
201
159
  * @param address Account address
202
160
  * @param tokenAddress Token contract address
203
161
  */
204
- getTokenBalance(address: string, tokenAddress: string): Promise<bigint>;
162
+ getPublicBalance(address: string, tokenAddress: string): Promise<bigint>;
205
163
  }
206
164
 
207
165
  /**