@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.
@@ -428,7 +428,6 @@ export class ConfidentialTransferClient {
428
428
  * @param {string} tokenAddress - Token address to transfer
429
429
  * @param {number} amount - Amount to transfer
430
430
  * @param {Object} [options] - Options
431
- * @param {boolean} [options.useOffchainVerify=false] - Use offchain verification
432
431
  * @param {boolean} [options.waitForFinalization=true] - Wait for transfer finalization
433
432
  * @returns {Promise<Object>} Transaction receipt
434
433
  */
@@ -439,7 +438,7 @@ export class ConfidentialTransferClient {
439
438
  amount,
440
439
  options = {},
441
440
  ) {
442
- const { useOffchainVerify = false, waitForFinalization = true } = options;
441
+ const { waitForFinalization = true } = options;
443
442
 
444
443
  try {
445
444
  // Validate inputs
@@ -476,7 +475,7 @@ export class ConfidentialTransferClient {
476
475
  `Recipient account does not exist. Address: ${recipientAddress}`,
477
476
  );
478
477
  }
479
- let derivedRecipientPublicKey = recipientAccountInfo.pubkey;
478
+ let derivedRecipientPublicKey = recipientAccountInfo.elgamalPubkey;
480
479
  if (!derivedRecipientPublicKey) {
481
480
  throw new Error("Recipient public key is required");
482
481
  }
@@ -584,7 +583,6 @@ export class ConfidentialTransferClient {
584
583
  recipientAddress,
585
584
  tokenAddress,
586
585
  transferZkpArg,
587
- useOffchainVerify,
588
586
  txOverrides,
589
587
  );
590
588
 
@@ -617,12 +615,11 @@ export class ConfidentialTransferClient {
617
615
  * @param {string} tokenAddress - Token address to withdraw
618
616
  * @param {number} amount - Amount to withdraw
619
617
  * @param {Object} [options] - Options
620
- * @param {boolean} [options.useOffchainVerify=false] - Use offchain verification
621
618
  * @param {boolean} [options.waitForFinalization=true] - Wait for withdrawal finalization
622
619
  * @returns {Promise<Object>} Transaction receipt
623
620
  */
624
621
  async withdraw(wallet, tokenAddress, amount, options = {}) {
625
- const { useOffchainVerify = false, waitForFinalization = true } = options;
622
+ const { waitForFinalization = true } = options;
626
623
 
627
624
  try {
628
625
  // Validate inputs
@@ -692,12 +689,19 @@ export class ConfidentialTransferClient {
692
689
  const currentBalanceContractScale =
693
690
  (BigInt(currentBalance) * 100n) / 10n ** BigInt(tokenDecimals);
694
691
 
692
+ // Fetch the contract's per-account txId. Withdraw proofs bind to the *next*
693
+ // tx id (txId + 1), not the current one — the contract increments the counter
694
+ // on every action, and the proof must commit to what it will be after submission.
695
+ const accountInfo = await this.getAccountInfo(address);
696
+ const txId = BigInt(accountInfo.txId ?? 0n);
697
+
695
698
  // Generate withdrawal proof
696
699
  const withdrawInput = {
697
700
  current_balance_ciphertext: currentBalanceCiphertext,
698
701
  current_balance: Number(currentBalanceContractScale),
699
702
  withdraw_amount: Number(withdrawAmount),
700
703
  keypair: derivedKeys.privateKey,
704
+ nonce: Number(txId + 1n),
701
705
  };
702
706
 
703
707
  const wasm = await this._getWasm();
@@ -741,7 +745,6 @@ export class ConfidentialTransferClient {
741
745
  tokenAddress,
742
746
  BigInt(withdrawAmount),
743
747
  withdrawZkpArg,
744
- useOffchainVerify,
745
748
  );
746
749
 
747
750
  const receipt = await tx.wait();
@@ -816,7 +819,7 @@ export class ConfidentialTransferClient {
816
819
  while (attempts < maxAttempts) {
817
820
  try {
818
821
  const info = await this.contract.getAccountCore(address);
819
- if (!info.hasPendingAction) {
822
+ if (!info.pendingAction) {
820
823
  return; // Success
821
824
  }
822
825
  } catch (error) {
package/src/constants.js CHANGED
@@ -5,11 +5,11 @@
5
5
  export const CONTRACT_ABI = [
6
6
  "function createConfidentialAccount(bytes elgamalPubkey) external",
7
7
  "function deposit(address token, uint256 plainAmount) external",
8
- "function getAccountCore(address ownerAddr) external view returns ((bool exists, bool finalized, bool hasPendingAction, uint256 lastUpdate, bytes pubkey, bytes availableC1, bytes availableC2, uint64 nonce, uint64 lastProcessedNonce))",
8
+ "function getAccountCore(address ownerAddr) external view returns ((bool exists, bool finalized, bool pendingAction, uint256 txId, bytes elgamalPubkey, uint64 pendingCreditCounter))",
9
9
  "function getAvailable(address ownerAddr, address token) external view returns (bytes c1, bytes c2)",
10
10
  "function getPending(address ownerAddr, address token) external view returns (bytes c1, bytes c2)",
11
- "function transferConfidential(address recipient, address token, bytes proof, bool useOffchainVerify) external payable",
12
- "function withdraw(address token, uint256 plainAmount, bytes proof, bool useOffchainVerify) external",
11
+ "function transferConfidential(address recipient, address token, bytes proof) external payable",
12
+ "function withdraw(address token, uint256 plainAmount, bytes proof) external",
13
13
  "function applyPending() external",
14
14
  "function feeAmount() external view returns (uint256)",
15
15
  ];
@@ -23,13 +23,12 @@ export const ERC20_ABI = [
23
23
 
24
24
  export const TEMPO_FEE_TOKEN_ADDRESS =
25
25
  "0x20c0000000000000000000000000000000000000";
26
-
27
26
  export const STABLETRUST_CONTRACTS_BY_CHAIN_ID = Object.freeze({
28
- 2201: "0xdf43bC80B5b22A858860947d383b6F4d6C81d8EC", //Stable
29
- 5042002: "0x5d43CE5269Cd46badC67B3664369862F20eC5649", //Arc
30
- 84532: "0xFBEa2AbCf1208E09dd90266fE94Fb76d8BfC34d9", //Base
31
- 11155111: "0x72B87207791996F416D2F3B0dcAcbb07F445C496", //Ethereum
32
- 421614: "0x6C9eDBDd028Fe610b054187A34712714E341D112", //Arbitrum
27
+ 2201: "0xA98e5ba75Bb44459916D52Abf1604caB3d98CC4B", //Stable
28
+ 5042002: "0xdCF31bd9f325C34E0fb346b1975E141D99AEf731", //Arc
29
+ 84532: "0xF66A0a1670F14AE5D1852B4E7d1e4C693b2Accfd", //Base
30
+ 11155111: "0xa066b5C30382110d19925108BBa1Eef613a3A041", //Ethereum
31
+ 421614: "0xbda65d65A7833D28F9391FF01d0b212B75538Cf2", //Arbitrum
33
32
  42431: "0xE559fB936C69c46E216bf61B07C16bF1a6d444aa", //Tempo
34
33
  });
35
34
 
package/src/index.d.ts CHANGED
@@ -1,6 +1,349 @@
1
1
  declare module "@fairblock/stabletrust" {
2
2
  import { ethers } from "ethers";
3
3
 
4
+ // ─────────────────── AnonymousTransferClient ────────────────────────────
5
+
6
+ export interface AnonymousTransferClientConfig {
7
+ /** Fairycloak relay server base URL, e.g. "http://127.0.0.1:8080" */
8
+ fairycloakUrl: string;
9
+ /** Chain ID — used to auto-resolve the contract address for supported networks */
10
+ chainId: number | string;
11
+ /** EVM JSON-RPC URL */
12
+ rpcUrl: string;
13
+ /** Optional override for the diamond contract address. Auto-resolved from chainId if omitted. */
14
+ diamondAddress?: string;
15
+ /** Optional API key for Fairycloak */
16
+ apiKey?: string;
17
+ }
18
+
19
+ export interface AnonymousAccountInfo {
20
+ exists: boolean;
21
+ finalized: boolean;
22
+ hasPendingAction: boolean;
23
+ txId: bigint;
24
+ elgamalPubkey: string;
25
+ authNonce: bigint;
26
+ }
27
+
28
+ export interface FairycloakResponse {
29
+ request_id: string;
30
+ tx_hash?: string;
31
+ status: string;
32
+ action?: string;
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ export interface RequestEventHistory {
37
+ request_id: string;
38
+ events: Array<{
39
+ request_id: string;
40
+ sequence: number;
41
+ type: string;
42
+ action: string;
43
+ status: string;
44
+ created_at: string;
45
+ [key: string]: unknown;
46
+ }>;
47
+ }
48
+
49
+ export interface TransferProofParams {
50
+ currentBalanceCiphertext: string;
51
+ currentBalanceContractScale: number;
52
+ transferAmountContractScale: number;
53
+ destinationPublicKey: string;
54
+ }
55
+
56
+ export interface WithdrawProofParams {
57
+ currentBalanceCiphertext: string;
58
+ currentBalanceContractScale: number;
59
+ withdrawAmountContractScale: number;
60
+ /**
61
+ * The *next* contract-level per-account transaction ID ("user tx-id + 1").
62
+ * Withdraw proofs commit to the txId the account will have **after** the
63
+ * withdrawal is processed — pass `txId + 1`, not the current `txId`.
64
+ *
65
+ * - Confidential accounts: `Number(getAccountInfo(address).txId) + 1`
66
+ * - Anonymous accounts: `getAnonymousAccountInfo(accountId).txId + 1n`
67
+ *
68
+ * This is NOT the Ethereum account nonce, and NOT the EIP-712 `authNonce`.
69
+ */
70
+ nonce: number | bigint;
71
+ }
72
+
73
+ export interface TransferToPublicParams {
74
+ recipient: string;
75
+ token: string;
76
+ /** Pre-computed ZK proof hex. Required if elGamalPrivateKey is not provided. */
77
+ proof?: string;
78
+ /** ElGamal private key (base64) for auto-proof generation. */
79
+ elGamalPrivateKey?: string;
80
+ /** Transfer amount in token units (e.g. 3000000 for 3 USDC). Required for auto-proof. */
81
+ amount?: bigint | string | number;
82
+ /** Recipient ElGamal public key (base64). Auto-resolved from the chain if omitted. */
83
+ destinationPublicKey?: string;
84
+ }
85
+
86
+ export interface TransferToAnonymousParams {
87
+ recipientId: number | bigint;
88
+ token: string;
89
+ /** Pre-computed ZK proof hex. Required if elGamalPrivateKey is not provided. */
90
+ proof?: string;
91
+ /** ElGamal private key (base64) for auto-proof generation. */
92
+ elGamalPrivateKey?: string;
93
+ /** Transfer amount in token units (e.g. 2000000 for 2 USDC). Required for auto-proof. */
94
+ amount?: bigint | string | number;
95
+ /** Recipient ElGamal public key (base64). Auto-resolved from the chain if omitted. */
96
+ destinationPublicKey?: string;
97
+ }
98
+
99
+ export interface WithdrawParams {
100
+ destination: string;
101
+ token: string;
102
+ /** Withdrawal amount in token units. */
103
+ plainAmount: bigint | string | number;
104
+ /** Pre-computed ZK proof hex. Required if elGamalPrivateKey is not provided. */
105
+ proof?: string;
106
+ /** ElGamal private key (base64) for auto-proof generation. Contract scale is derived automatically. */
107
+ elGamalPrivateKey?: string;
108
+ }
109
+
110
+ export interface AnonymousAccountBalance {
111
+ /** Total balance: available + pending. Same units as amounts passed to deposit/transfer/withdraw. */
112
+ amount: number;
113
+ /** Spendable balance (already settled on-chain). */
114
+ available: number;
115
+ /** Incoming balance not yet applied via applyPending(). */
116
+ pending: number;
117
+ }
118
+
119
+ export interface AnonymousBalanceEntry {
120
+ /** Decrypted balance in contract-scale units (plainAmount × tokenMul). */
121
+ amount: number;
122
+ /** Base64 combined ciphertext, or null if no balance exists. */
123
+ ciphertext: string | null;
124
+ }
125
+
126
+ export interface AnonymousBalance {
127
+ available: AnonymousBalanceEntry;
128
+ pending: AnonymousBalanceEntry;
129
+ }
130
+
131
+ export interface UpdateAuthKeysParams {
132
+ add?: Array<string | ethers.Wallet>;
133
+ remove?: Array<string | ethers.Wallet>;
134
+ }
135
+
136
+ export interface WaitForRequestOptions {
137
+ timeoutMs?: number;
138
+ pollIntervalMs?: number;
139
+ }
140
+
141
+ export interface DeadlineOptions {
142
+ deadlineOffset?: number;
143
+ }
144
+
145
+ export interface DepositOptions extends DeadlineOptions {
146
+ gasLimit?: bigint;
147
+ }
148
+
149
+ /**
150
+ * AnonymousTransferClient — SDK class for anonymous confidential transfers via the Fairycloak relay.
151
+ *
152
+ * All operations (except `deposit`) are gas-free for the user — Fairycloak pays gas.
153
+ * `deposit` requires the user to sign and pay for a raw EVM transaction.
154
+ */
155
+ export class AnonymousTransferClient {
156
+ constructor(config: AnonymousTransferClientConfig);
157
+
158
+ // ── on-chain reads ──────────────────────────────────────────────
159
+
160
+ /**
161
+ * Get on-chain core state for an anonymous account.
162
+ */
163
+ getAnonymousAccountInfo(
164
+ accountId: number | bigint,
165
+ ): Promise<AnonymousAccountInfo>;
166
+
167
+ /**
168
+ * Read the current authNonce for an anonymous account.
169
+ */
170
+ getAuthNonce(accountId: number | bigint): Promise<bigint>;
171
+
172
+ /**
173
+ * Read the next anonymous account ID that will be assigned.
174
+ */
175
+ getNextAccountId(): Promise<bigint>;
176
+
177
+ /**
178
+ * Check whether an address is an authorised signer for an anonymous account.
179
+ */
180
+ isAuthorizedSigner(
181
+ accountId: number | bigint,
182
+ signerAddress: string,
183
+ ): Promise<boolean>;
184
+
185
+ /**
186
+ * Get the decrypted balance summary (available + pending) for an anonymous account.
187
+ * Returns plain numbers in the same units as the amounts passed to deposit/transfer/withdraw.
188
+ * Use `.available` and `.pending` for individual balances, `.amount` for the total.
189
+ */
190
+ getBalance(
191
+ accountId: number | bigint,
192
+ tokenAddress: string,
193
+ elGamalPrivateKey: string,
194
+ ): Promise<AnonymousAccountBalance>;
195
+
196
+ /**
197
+ * Get the decrypted available and pending balances, including raw ciphertexts.
198
+ * Use `getBalance` for a simpler numeric summary.
199
+ */
200
+ getAnonymousBalance(
201
+ accountId: number | bigint,
202
+ tokenAddress: string,
203
+ elGamalPrivateKey: string,
204
+ ): Promise<AnonymousBalance>;
205
+
206
+ // ── key derivation ──────────────────────────────────────────────
207
+
208
+ /**
209
+ * Derive a deterministic ElGamal keypair for an anonymous account.
210
+ * Tied to (chainId, diamondAddress, accountId, authWallet).
211
+ */
212
+ deriveAnonymousKeys(
213
+ authWallet: ethers.Wallet | ethers.Signer,
214
+ accountId: number | bigint,
215
+ ): Promise<Keys>;
216
+
217
+ // ── proof generation ────────────────────────────────────────────
218
+
219
+ /**
220
+ * Generate a ZK transfer proof via WASM.
221
+ * Pass the returned string to `transferToPublic` or `transferToAnonymous`.
222
+ */
223
+ generateTransferProof(
224
+ elGamalPrivateKey: string,
225
+ params: TransferProofParams,
226
+ ): Promise<string>;
227
+
228
+ /**
229
+ * Generate a ZK withdraw proof via WASM.
230
+ * Pass the returned string to `withdraw`.
231
+ */
232
+ generateWithdrawProof(
233
+ elGamalPrivateKey: string,
234
+ params: WithdrawProofParams,
235
+ ): Promise<string>;
236
+
237
+ // ── Fairycloak operations ───────────────────────────────────────
238
+
239
+ /**
240
+ * Create a new anonymous account. The relay pays gas.
241
+ * @param elgamalPublicKey Base64 or "0x"-prefixed hex (32 bytes)
242
+ */
243
+ createAccount(
244
+ authWallet: ethers.Wallet | ethers.Signer,
245
+ elgamalPublicKey: string,
246
+ options?: DeadlineOptions,
247
+ ): Promise<FairycloakResponse>;
248
+
249
+ /**
250
+ * Update the set of authorised signers. The relay pays gas.
251
+ * Pass ethers.Wallet objects or raw uncompressed hex pubkey strings.
252
+ */
253
+ updateAuthKeys(
254
+ authWallet: ethers.Wallet | ethers.Signer,
255
+ accountId: number | bigint,
256
+ keys: UpdateAuthKeysParams,
257
+ options?: DeadlineOptions,
258
+ ): Promise<FairycloakResponse>;
259
+
260
+ /**
261
+ * Deposit tokens. The user pays gas for this raw EVM transaction.
262
+ * Handles ERC-20 approval automatically.
263
+ */
264
+ deposit(
265
+ authWallet: ethers.Wallet,
266
+ accountId: number | bigint,
267
+ tokenAddress: string,
268
+ amount: bigint | string | number,
269
+ options?: DepositOptions,
270
+ ): Promise<FairycloakResponse>;
271
+
272
+ /**
273
+ * Transfer from an anonymous account to a public EVM address. The relay pays gas.
274
+ *
275
+ * Auto-proof: pass `elGamalPrivateKey`, `transferAmountContractScale`, and `destinationPublicKey`.
276
+ * Manual proof: pass a pre-computed `proof` hex string from `generateTransferProof()`.
277
+ */
278
+ transferToPublic(
279
+ authWallet: ethers.Wallet | ethers.Signer,
280
+ accountId: number | bigint,
281
+ params: TransferToPublicParams,
282
+ options?: DeadlineOptions,
283
+ ): Promise<FairycloakResponse>;
284
+
285
+ /**
286
+ * Transfer between two anonymous accounts. The relay pays gas.
287
+ *
288
+ * Auto-proof: pass `elGamalPrivateKey`, `transferAmountContractScale`, and `destinationPublicKey`.
289
+ * Manual proof: pass a pre-computed `proof` hex string from `generateTransferProof()`.
290
+ */
291
+ transferToAnonymous(
292
+ authWallet: ethers.Wallet | ethers.Signer,
293
+ senderAccountId: number | bigint,
294
+ params: TransferToAnonymousParams,
295
+ options?: DeadlineOptions,
296
+ ): Promise<FairycloakResponse>;
297
+
298
+ /**
299
+ * Apply a pending balance (move incoming credit to available). The relay pays gas.
300
+ */
301
+ applyPending(
302
+ authWallet: ethers.Wallet | ethers.Signer,
303
+ accountId: number | bigint,
304
+ options?: DeadlineOptions,
305
+ ): Promise<FairycloakResponse>;
306
+
307
+ /**
308
+ * Withdraw from an anonymous account to a public address. The relay pays gas.
309
+ *
310
+ * Auto-proof: pass `elGamalPrivateKey` and `withdrawAmountContractScale` — the SDK fetches
311
+ * the ciphertext and generates the proof internally.
312
+ * Manual proof: pass a pre-computed `proof` hex string from `generateWithdrawProof()`.
313
+ */
314
+ withdraw(
315
+ authWallet: ethers.Wallet | ethers.Signer,
316
+ accountId: number | bigint,
317
+ params: WithdrawParams,
318
+ options?: DeadlineOptions,
319
+ ): Promise<FairycloakResponse>;
320
+
321
+ // ── request tracking ────────────────────────────────────────────
322
+
323
+ /**
324
+ * Get the current status of a Fairycloak request.
325
+ * Terminal statuses: completed | failed
326
+ * Tx statuses: confirmed | mined | submitted
327
+ */
328
+ getRequestStatus(requestId: string): Promise<FairycloakResponse>;
329
+
330
+ /**
331
+ * Fetch durable event history for a request.
332
+ */
333
+ getRequestEvents(
334
+ requestId: string,
335
+ options?: { afterSeq?: number; limit?: number },
336
+ ): Promise<RequestEventHistory>;
337
+
338
+ /**
339
+ * Poll until a request reaches a terminal state (completed/confirmed/failed).
340
+ */
341
+ waitForRequest(
342
+ requestId: string,
343
+ options?: WaitForRequestOptions,
344
+ ): Promise<FairycloakResponse>;
345
+ }
346
+
4
347
  /**
5
348
  * SDK configuration
6
349
  */
@@ -10,7 +353,7 @@ declare module "@fairblock/stabletrust" {
10
353
  chainId: number;
11
354
  }
12
355
 
13
- export type StabletrustChainId =
356
+ export type StableTrustChainId =
14
357
  | 5042002
15
358
  | 2201
16
359
  | 42431
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export { ConfidentialTransferClient } from "./client.js";
1
+ export { ConfidentialTransferClient } from "./confidential-client.js";
2
+ export { AnonymousTransferClient } from "./anonymous-client.js";
2
3
  export { deriveKeys, decryptCiphertext, combineCiphertext } from "./crypto.js";
3
4
  export { encodeTransferProof, encodeWithdrawProof } from "./utils.js";
4
5
  export {
Binary file