@fairblock/stabletrust 2.0.5 → 2.0.7
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 +8 -21
- package/examples/anonymous-flow.js +33 -28
- package/examples/complete-flow.js +1 -2
- package/examples/simple-snippets.js +1 -2
- package/package.json +2 -3
- package/src/anonymous-client.js +39 -34
- package/src/confidential-client.js +0 -46
- package/src/constants.js +5 -5
- package/src/index.d.ts +5 -7
- package/examples/constants.js +0 -6
package/README.md
CHANGED
|
@@ -572,28 +572,15 @@ try {
|
|
|
572
572
|
|
|
573
573
|
### Common Issues and Solutions
|
|
574
574
|
|
|
575
|
-
| Issue
|
|
576
|
-
|
|
|
577
|
-
| "Account does not exist"
|
|
578
|
-
| "Insufficient balance"
|
|
579
|
-
| "Account finalization timeout"
|
|
580
|
-
| "Proof generation failed"
|
|
581
|
-
| "Amount too small"
|
|
575
|
+
| Issue | Cause | Solution |
|
|
576
|
+
| :--------------------------------- | :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
577
|
+
| "Account does not exist" | Recipient hasn't initialized their confidential account | Recipient must call `ensureAccount()` first |
|
|
578
|
+
| "Insufficient balance" | Transfer amount exceeds available confidential balance | Deposit more tokens or reduce transfer amount |
|
|
579
|
+
| "Account finalization timeout" | Account creation is still processing | Wait a few minutes and retry |
|
|
580
|
+
| "Proof generation failed" | Invalid inputs or cryptographic operation error | Verify all parameters and ensure sufficient balance |
|
|
581
|
+
| "Amount too small" | Amount rounds to 0 in contract scale | Use a larger amount (minimum depends on token decimals) |
|
|
582
582
|
| "Insufficient prepaid fee balance" | Anonymous account's prepaid fee reserve is below the per-transfer fee | Call `depositFees(wallet, accountId, amount)` to top up; use `getFeeToken()`/`getFeeAmount()` to check requirements |
|
|
583
|
-
| Fairycloak HTTP error
|
|
584
|
-
|
|
585
|
-
---
|
|
586
|
-
|
|
587
|
-
## Performance Metrics
|
|
588
|
-
|
|
589
|
-
The following are estimated execution times for standard operations. Durations may vary based on network congestion and hardware.
|
|
590
|
-
|
|
591
|
-
| Operation | Avg Duration |
|
|
592
|
-
| :--------------- | :----------- |
|
|
593
|
-
| Account Creation | 45s |
|
|
594
|
-
| Deposit | 63s |
|
|
595
|
-
| Transfer | 58s |
|
|
596
|
-
| Withdraw | 58s |
|
|
583
|
+
| Fairycloak HTTP error | Relay unreachable or request rejected | Check the relay URL, API key, and account authorization |
|
|
597
584
|
|
|
598
585
|
---
|
|
599
586
|
|
|
@@ -24,7 +24,7 @@ dotenv.config();
|
|
|
24
24
|
|
|
25
25
|
const EVM_RPC = process.env.EVM_RPC ?? "http://127.0.0.1:8545";
|
|
26
26
|
const FAIRYCLOAK_URL = process.env.FAIRYCLOAK_URL ?? "http://127.0.0.1:8080";
|
|
27
|
-
const CHAIN_ID = Number(process.env.CHAIN_ID ?? "
|
|
27
|
+
const CHAIN_ID = Number(process.env.CHAIN_ID ?? "5042002");
|
|
28
28
|
const TOKEN_ADDRESS =
|
|
29
29
|
process.env.TOKEN_ADDRESS ?? "0xE915164570b027C2A0FfadcB1B672192E35BF008";
|
|
30
30
|
const TOKEN_DECIMALS = Number(process.env.TOKEN_DECIMALS ?? "6");
|
|
@@ -236,34 +236,39 @@ async function main() {
|
|
|
236
236
|
log(" fee token :", feeToken);
|
|
237
237
|
log(" fee/xfer :", fmt(feeAmount, TOKEN_DECIMALS), "tokens");
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (feeBalance < MIN_FEE_BALANCE) {
|
|
243
|
-
log(
|
|
244
|
-
` balance < ${fmt(MIN_FEE_BALANCE, TOKEN_DECIMALS)}, depositing ${fmt(FEE_DEPOSIT_AMOUNT, TOKEN_DECIMALS)} fee tokens (anon1 pays gas) …`,
|
|
245
|
-
);
|
|
246
|
-
const fd = await client.depositFees(
|
|
247
|
-
anon1Wallet,
|
|
248
|
-
anon1Id,
|
|
249
|
-
FEE_DEPOSIT_AMOUNT,
|
|
250
|
-
);
|
|
251
|
-
log(" request_id:", fd.request_id, " status:", fd.status);
|
|
252
|
-
if (fd.error) log(" [!] Error:", fd.error);
|
|
253
|
-
if (fd.tx_hash) log(" tx_hash :", fd.tx_hash);
|
|
254
|
-
|
|
255
|
-
const fdFinal = await client.waitForRequest(fd.request_id, {
|
|
256
|
-
timeoutMs: 180_000,
|
|
257
|
-
});
|
|
258
|
-
log(" status :", fdFinal.status, " tx_hash:", fdFinal.tx_hash ?? "—");
|
|
259
|
-
if (fdFinal.error) log(" [!] Error:", fdFinal.error);
|
|
260
|
-
|
|
261
|
-
const newFeeBalance = await client.getPrepaidFeeBalance(anon1Id, feeToken);
|
|
262
|
-
log(" new fee balance:", fmt(newFeeBalance, TOKEN_DECIMALS), "tokens");
|
|
239
|
+
if (feeAmount === 0n) {
|
|
240
|
+
log(" no fee configured — transactions are free, skipping top-up.");
|
|
263
241
|
} else {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
242
|
+
const feeBalance = await client.getPrepaidFeeBalance(anon1Id, feeToken);
|
|
243
|
+
log(" fee balance:", fmt(feeBalance, TOKEN_DECIMALS), "tokens");
|
|
244
|
+
|
|
245
|
+
if (feeBalance < MIN_FEE_BALANCE) {
|
|
246
|
+
const currency = feeToken === ethers.ZeroAddress ? "native" : "ERC-20";
|
|
247
|
+
log(
|
|
248
|
+
` balance < ${fmt(MIN_FEE_BALANCE, TOKEN_DECIMALS)}, depositing ${fmt(FEE_DEPOSIT_AMOUNT, TOKEN_DECIMALS)} ${currency} fee tokens (anon1 pays gas) …`,
|
|
249
|
+
);
|
|
250
|
+
const fd = await client.depositFees(
|
|
251
|
+
anon1Wallet,
|
|
252
|
+
anon1Id,
|
|
253
|
+
FEE_DEPOSIT_AMOUNT,
|
|
254
|
+
);
|
|
255
|
+
log(" request_id:", fd.request_id, " status:", fd.status);
|
|
256
|
+
if (fd.error) log(" [!] Error:", fd.error);
|
|
257
|
+
if (fd.tx_hash) log(" tx_hash :", fd.tx_hash);
|
|
258
|
+
|
|
259
|
+
const fdFinal = await client.waitForRequest(fd.request_id, {
|
|
260
|
+
timeoutMs: 180_000,
|
|
261
|
+
});
|
|
262
|
+
log(" status :", fdFinal.status, " tx_hash:", fdFinal.tx_hash ?? "—");
|
|
263
|
+
if (fdFinal.error) log(" [!] Error:", fdFinal.error);
|
|
264
|
+
|
|
265
|
+
const newFeeBalance = await client.getPrepaidFeeBalance(anon1Id, feeToken);
|
|
266
|
+
log(" new fee balance:", fmt(newFeeBalance, TOKEN_DECIMALS), "tokens");
|
|
267
|
+
} else {
|
|
268
|
+
log(
|
|
269
|
+
` fee balance sufficient (${fmt(feeBalance, TOKEN_DECIMALS)} ≥ ${fmt(MIN_FEE_BALANCE, TOKEN_DECIMALS)}), skipping deposit.`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
// ── [6] Transfer anon1 → anon2 ───────────────────────────────────────────────
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ethers } from "ethers";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import { ConfidentialTransferClient } from "@fairblock/stabletrust";
|
|
4
|
-
import { ERC20_ABI } from "./constants.js";
|
|
3
|
+
import { ConfidentialTransferClient, ERC20_ABI } from "@fairblock/stabletrust";
|
|
5
4
|
dotenv.config();
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ethers } from "ethers";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import { ConfidentialTransferClient } from "@fairblock/stabletrust";
|
|
4
|
-
import { ERC20_ABI } from "./constants.js";
|
|
3
|
+
import { ConfidentialTransferClient, ERC20_ABI } from "@fairblock/stabletrust";
|
|
5
4
|
dotenv.config();
|
|
6
5
|
const RPC_URL = process.env.RPC_URL || "https://rpc.testnet.stable.xyz";
|
|
7
6
|
const EXPLORER_URL =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fairblock/stabletrust",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "SDK for confidential transfers using homomorphic encryption and zero-knowledge proofs",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"src/",
|
|
22
22
|
"pkg/",
|
|
23
23
|
"examples/",
|
|
24
|
-
"README.md"
|
|
25
|
-
"CHANGELOG.md"
|
|
24
|
+
"README.md"
|
|
26
25
|
],
|
|
27
26
|
"repository": {
|
|
28
27
|
"type": "git",
|
package/src/anonymous-client.js
CHANGED
|
@@ -1488,8 +1488,6 @@ export class AnonymousTransferClient {
|
|
|
1488
1488
|
]);
|
|
1489
1489
|
if (feeAmount === 0n) return; // no fee configured
|
|
1490
1490
|
|
|
1491
|
-
if (feeToken === ethers.ZeroAddress) return; // fee token not set
|
|
1492
|
-
|
|
1493
1491
|
const balance = await this.getPrepaidFeeBalance(accountId, feeToken);
|
|
1494
1492
|
if (balance < feeAmount) {
|
|
1495
1493
|
throw new Error(
|
|
@@ -1534,45 +1532,51 @@ export class AnonymousTransferClient {
|
|
|
1534
1532
|
if (amountBig <= 0n) throw new Error("Amount must be greater than 0");
|
|
1535
1533
|
|
|
1536
1534
|
const feeToken = await this.getFeeToken();
|
|
1537
|
-
|
|
1538
|
-
throw new Error(
|
|
1539
|
-
"No fee token is currently configured on the contract. Contact the protocol operator.",
|
|
1540
|
-
);
|
|
1535
|
+
const isNative = feeToken === ethers.ZeroAddress;
|
|
1541
1536
|
|
|
1542
1537
|
const depositorAddress = await authWallet.getAddress();
|
|
1543
1538
|
const walletWithProvider = authWallet.connect
|
|
1544
1539
|
? authWallet.connect(this.provider)
|
|
1545
1540
|
: authWallet;
|
|
1546
1541
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1542
|
+
if (isNative) {
|
|
1543
|
+
// Native currency: check wallet balance, no ERC-20 approve needed
|
|
1544
|
+
const balance = await this.provider.getBalance(depositorAddress);
|
|
1545
|
+
if (balance < amountBig)
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
`Insufficient native balance. Required: ${amountBig}, available: ${balance}`,
|
|
1548
|
+
);
|
|
1549
|
+
} else {
|
|
1550
|
+
// ERC-20: approve if needed
|
|
1551
|
+
const tokenContract = new ethers.Contract(
|
|
1552
|
+
feeToken,
|
|
1553
|
+
[
|
|
1554
|
+
"function balanceOf(address) view returns (uint256)",
|
|
1555
|
+
"function allowance(address,address) view returns (uint256)",
|
|
1556
|
+
"function approve(address,uint256) returns (bool)",
|
|
1557
|
+
],
|
|
1558
|
+
walletWithProvider,
|
|
1559
|
+
);
|
|
1557
1560
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1561
|
+
const [balance, allowance] = await Promise.all([
|
|
1562
|
+
tokenContract.balanceOf(depositorAddress),
|
|
1563
|
+
tokenContract.allowance(depositorAddress, this.diamondAddress),
|
|
1564
|
+
]);
|
|
1562
1565
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1566
|
+
if (balance < amountBig)
|
|
1567
|
+
throw new Error(
|
|
1568
|
+
`Insufficient fee token balance. Required: ${amountBig}, available: ${balance}`,
|
|
1569
|
+
);
|
|
1567
1570
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1571
|
+
if (allowance < amountBig) {
|
|
1572
|
+
const approveTx = await tokenContract.approve(
|
|
1573
|
+
this.diamondAddress,
|
|
1574
|
+
ethers.MaxUint256,
|
|
1575
|
+
);
|
|
1576
|
+
const receipt = await approveTx.wait();
|
|
1577
|
+
if (!receipt || receipt.status === 0)
|
|
1578
|
+
throw new Error("Fee token approval failed");
|
|
1579
|
+
}
|
|
1576
1580
|
}
|
|
1577
1581
|
|
|
1578
1582
|
// 2. EIP-712 AnonymousAddFees signature
|
|
@@ -1614,6 +1618,7 @@ export class AnonymousTransferClient {
|
|
|
1614
1618
|
this.provider.getFeeData(),
|
|
1615
1619
|
]);
|
|
1616
1620
|
|
|
1621
|
+
const txValue = isNative ? amountBig : 0n;
|
|
1617
1622
|
let txObj;
|
|
1618
1623
|
if (feeData.maxFeePerGas) {
|
|
1619
1624
|
txObj = {
|
|
@@ -1624,7 +1629,7 @@ export class AnonymousTransferClient {
|
|
|
1624
1629
|
gasLimit,
|
|
1625
1630
|
maxFeePerGas: (feeData.maxFeePerGas * 12n) / 10n,
|
|
1626
1631
|
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 0n,
|
|
1627
|
-
value:
|
|
1632
|
+
value: txValue,
|
|
1628
1633
|
};
|
|
1629
1634
|
} else {
|
|
1630
1635
|
txObj = {
|
|
@@ -1634,7 +1639,7 @@ export class AnonymousTransferClient {
|
|
|
1634
1639
|
chainId: BigInt(this.chainId),
|
|
1635
1640
|
gasLimit,
|
|
1636
1641
|
gasPrice: (feeData.gasPrice * 11n) / 10n,
|
|
1637
|
-
value:
|
|
1642
|
+
value: txValue,
|
|
1638
1643
|
};
|
|
1639
1644
|
}
|
|
1640
1645
|
|
|
@@ -840,52 +840,6 @@ export class ConfidentialTransferClient {
|
|
|
840
840
|
);
|
|
841
841
|
}
|
|
842
842
|
|
|
843
|
-
/**
|
|
844
|
-
* Wait for pending balance to appear
|
|
845
|
-
*
|
|
846
|
-
* @param {string} address - Account address
|
|
847
|
-
* @param {string} privateKey - Private key for decryption
|
|
848
|
-
* @param {string} tokenAddress - Token address
|
|
849
|
-
* @param {Object} [options] - Options
|
|
850
|
-
* @param {number} [options.maxAttempts=225] - Maximum polling attempts
|
|
851
|
-
* @param {number} [options.intervalMs=400] - Polling interval in milliseconds
|
|
852
|
-
* @returns {Promise<{amount: number, ciphertext: string}>}
|
|
853
|
-
*/
|
|
854
|
-
async _waitForPendingBalance(
|
|
855
|
-
address,
|
|
856
|
-
privateKey,
|
|
857
|
-
tokenAddress,
|
|
858
|
-
options = {},
|
|
859
|
-
) {
|
|
860
|
-
const { maxAttempts = 225, intervalMs = 400 } = options;
|
|
861
|
-
await sleep(1000);
|
|
862
|
-
|
|
863
|
-
try {
|
|
864
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
865
|
-
const pending = await this._getPendingBalance(
|
|
866
|
-
address,
|
|
867
|
-
privateKey,
|
|
868
|
-
tokenAddress,
|
|
869
|
-
);
|
|
870
|
-
|
|
871
|
-
if (pending.amount > 0n) {
|
|
872
|
-
return pending;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
await sleep(intervalMs);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
throw new Error(
|
|
879
|
-
`Timeout waiting for pending balance after ${maxAttempts} attempts. The transfer may still be processing.`,
|
|
880
|
-
);
|
|
881
|
-
} catch (error) {
|
|
882
|
-
if (error.message.includes("Timeout waiting for pending balance")) {
|
|
883
|
-
throw error;
|
|
884
|
-
}
|
|
885
|
-
throw new Error(`Failed to wait for pending balance: ${error.message}`);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
843
|
/**
|
|
890
844
|
* Get the current fee amount for confidential transfers
|
|
891
845
|
*
|
package/src/constants.js
CHANGED
|
@@ -24,11 +24,11 @@ export const ERC20_ABI = [
|
|
|
24
24
|
export const TEMPO_FEE_TOKEN_ADDRESS =
|
|
25
25
|
"0x20c0000000000000000000000000000000000000";
|
|
26
26
|
export const STABLETRUST_CONTRACTS_BY_CHAIN_ID = Object.freeze({
|
|
27
|
-
2201: "
|
|
28
|
-
5042002: "
|
|
29
|
-
84532: "
|
|
30
|
-
11155111: "
|
|
31
|
-
421614: "
|
|
27
|
+
2201: "0xe1c1456CAb802312759a8cFc3976f88bf87082cf", //Stable
|
|
28
|
+
5042002: "0x1B4f05f67CC33788Da4C89a7cd0b2f8E0055E605", //Arc
|
|
29
|
+
84532: "0x4a251C9D79faCa20b193630A4ee313af7cBCDD93", //Base
|
|
30
|
+
11155111: "0x7507a13352AFAa79D33E994f86f2f62463ba8DE4", //Ethereum
|
|
31
|
+
421614: "0x5acECCdeb5CbD3C727eCB49F8706Eb80EF2f977F", //Arbitrum
|
|
32
32
|
42431: "0xE559fB936C69c46E216bf61B07C16bF1a6d444aa", //Tempo
|
|
33
33
|
});
|
|
34
34
|
|
package/src/index.d.ts
CHANGED
|
@@ -188,9 +188,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
188
188
|
/**
|
|
189
189
|
* Get on-chain core state for an anonymous account.
|
|
190
190
|
*/
|
|
191
|
-
getAnonymousAccountInfo(
|
|
192
|
-
accountId: number | bigint,
|
|
193
|
-
): Promise<AnonymousAccountInfo>;
|
|
191
|
+
getAnonymousAccountInfo(accountId: string): Promise<AnonymousAccountInfo>;
|
|
194
192
|
|
|
195
193
|
/**
|
|
196
194
|
* Read the current authNonce for an anonymous account.
|
|
@@ -211,7 +209,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
211
209
|
* Use `.available` and `.pending` for individual balances, `.amount` for the total.
|
|
212
210
|
*/
|
|
213
211
|
getBalance(
|
|
214
|
-
accountId:
|
|
212
|
+
accountId: string,
|
|
215
213
|
tokenAddress: string,
|
|
216
214
|
elGamalPrivateKey: string,
|
|
217
215
|
): Promise<AnonymousAccountBalance>;
|
|
@@ -221,7 +219,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
221
219
|
* Use `getBalance` for a simpler numeric summary.
|
|
222
220
|
*/
|
|
223
221
|
getAnonymousBalance(
|
|
224
|
-
accountId:
|
|
222
|
+
accountId: string,
|
|
225
223
|
tokenAddress: string,
|
|
226
224
|
elGamalPrivateKey: string,
|
|
227
225
|
): Promise<AnonymousBalance>;
|
|
@@ -484,7 +482,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
484
482
|
/**
|
|
485
483
|
* Deposit options
|
|
486
484
|
*/
|
|
487
|
-
export interface
|
|
485
|
+
export interface ConfidentialDepositOptions {
|
|
488
486
|
waitForFinalization?: boolean;
|
|
489
487
|
}
|
|
490
488
|
|
|
@@ -560,7 +558,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
560
558
|
wallet: ethers.Wallet | ethers.Signer,
|
|
561
559
|
tokenAddress: string,
|
|
562
560
|
amount: bigint | string | number,
|
|
563
|
-
options?:
|
|
561
|
+
options?: ConfidentialDepositOptions,
|
|
564
562
|
): Promise<ethers.ContractTransactionReceipt>;
|
|
565
563
|
|
|
566
564
|
/**
|
package/examples/constants.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export const ERC20_ABI = [
|
|
2
|
-
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
3
|
-
"function allowance(address owner, address spender) external view returns (uint256)",
|
|
4
|
-
"function balanceOf(address account) external view returns (uint256)",
|
|
5
|
-
"function decimals() external view returns (uint8)",
|
|
6
|
-
];
|