@fairblock/stabletrust 2.0.6 → 2.0.10
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 +185 -74
- package/examples/anonymous-flow.js +42 -31
- package/package.json +10 -3
- package/src/anonymous-client.js +565 -105
- package/src/backend.js +19 -0
- package/src/confidential-client.js +497 -82
- package/src/constants.js +36 -9
- package/src/ibe.js +59 -0
- package/src/index.d.ts +85 -18
- package/src/index.js +6 -0
- package/src/utils.js +169 -62
- package/src/vendor/ts-ibe/age/age-encrypt-decrypt.js +127 -0
- package/src/vendor/ts-ibe/age/age-reader-writer.js +94 -0
- package/src/vendor/ts-ibe/age/armor.js +52 -0
- package/src/vendor/ts-ibe/age/no-op-encdec.js +72 -0
- package/src/vendor/ts-ibe/age/stream-cipher.js +99 -0
- package/src/vendor/ts-ibe/age/utils-crypto.js +60 -0
- package/src/vendor/ts-ibe/age/utils.js +59 -0
- package/src/vendor/ts-ibe/crypto/ibe.js +198 -0
- package/src/vendor/ts-ibe/crypto/utils.js +50 -0
- package/src/vendor/ts-ibe/index.js +87 -0
- package/src/vendor/ts-ibe/types/index.js +2 -0
- package/src/vendor/ts-ibe/version.js +2 -0
- package/src/wasm-loader.js +3 -3
- package/pkg/README.md +0 -310
package/src/constants.js
CHANGED
|
@@ -2,16 +2,42 @@
|
|
|
2
2
|
* Contract ABIs and Constants
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const CREATE_CONFIDENTIAL_ACCOUNT_SIGNATURE = "createConfidentialAccount(bytes)";
|
|
6
|
+
const DEPOSIT_SIGNATURE = "deposit(address,uint256)";
|
|
7
|
+
const GET_ACCOUNT_CORE_SIGNATURE = "getAccountCore(address)";
|
|
8
|
+
const GET_AVAILABLE_SIGNATURE = "getAvailable(address,address)";
|
|
9
|
+
const GET_PENDING_SIGNATURE = "getPending(address,address)";
|
|
10
|
+
const APPLY_PENDING_SIGNATURE = "applyPending()";
|
|
11
|
+
|
|
12
|
+
export const TRANSFER_CONFIDENTIAL_SIGNATURE = "transferConfidential(address,address,bytes,bool)";
|
|
13
|
+
export const WITHDRAW_CONFIDENTIAL_SIGNATURE = "withdraw(address,uint256,bytes,bool)";
|
|
14
|
+
export const FEE_TOKEN_SIGNATURE = "feeToken()";
|
|
15
|
+
export const NON_ANONYMOUS_TRANSFER_FEE_SIGNATURE = "nonAnonymousTransferFee()";
|
|
16
|
+
export const ANONYMOUS_IPFS_TRANSFER_FEE_SIGNATURE = "anonymousIpfsTransferFee()";
|
|
17
|
+
export const ANONYMOUS_INLINE_TRANSFER_FEE_SIGNATURE = "anonymousInlineTransferFee()";
|
|
18
|
+
|
|
5
19
|
export const CONTRACT_ABI = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
`function ${CREATE_CONFIDENTIAL_ACCOUNT_SIGNATURE} external`,
|
|
21
|
+
`function ${DEPOSIT_SIGNATURE} external`,
|
|
22
|
+
`function ${GET_ACCOUNT_CORE_SIGNATURE} external view returns ((bool exists, bool finalized, bool pendingAction, uint256 txId, bytes elgamalPubkey, uint64 pendingCreditCounter))`,
|
|
23
|
+
`function ${GET_AVAILABLE_SIGNATURE} external view returns (bytes c1, bytes c2)`,
|
|
24
|
+
`function ${GET_PENDING_SIGNATURE} external view returns (bytes c1, bytes c2)`,
|
|
25
|
+
`function ${TRANSFER_CONFIDENTIAL_SIGNATURE} external payable`,
|
|
26
|
+
`function ${WITHDRAW_CONFIDENTIAL_SIGNATURE} external`,
|
|
27
|
+
`function ${APPLY_PENDING_SIGNATURE} external`,
|
|
28
|
+
`function ${FEE_TOKEN_SIGNATURE} external view returns (address)`,
|
|
29
|
+
`function ${NON_ANONYMOUS_TRANSFER_FEE_SIGNATURE} external view returns (uint256)`,
|
|
30
|
+
`function ${ANONYMOUS_IPFS_TRANSFER_FEE_SIGNATURE} external view returns (uint256)`,
|
|
31
|
+
`function ${ANONYMOUS_INLINE_TRANSFER_FEE_SIGNATURE} external view returns (uint256)`,
|
|
32
|
+
// Events used to extract request/tx IDs from receipts for backend persistence
|
|
33
|
+
"event DepositRequested(address indexed ownerAddr, uint256 indexed txId, address indexed token, uint256 plainAmount, bytes availableShare, bytes pendingShare)",
|
|
34
|
+
"event DepositProcessed(address indexed ownerAddr, uint256 indexed txId, bool ok, string errorMsg, address token, bytes newAvailC1, bytes newAvailC2, uint64 pendingCreditCounter, uint64 minimumPendingCreditCounter, bytes availableShare, bytes pendingShare)",
|
|
35
|
+
"event TransferRequested(address indexed sender, address indexed recipient, uint256 indexed txId, address token, bytes proof, uint256 feePaid, bool useOffchainVerify, bytes senderPubkey, bytes recipientPubkey, bytes senderCurrC1, bytes senderCurrC2)",
|
|
36
|
+
"event TransferProcessed(address indexed sender, address indexed recipient, uint256 indexed txId, bool ok, string errorMsg, address token, bytes senderNewAvailC1, bytes senderNewAvailC2, bytes recipientNewPendingC1, bytes recipientNewPendingC2, uint64 senderPendingCreditCounter, bytes senderAvailableShare, uint64 recipientPendingCreditCounter, uint64 recipientMinimumPendingCreditCounter, bytes recipientPendingShare)",
|
|
37
|
+
"event ApplyPendingRequested(address indexed ownerAddr, uint256 indexed txId)",
|
|
38
|
+
"event ApplyPendingProcessed(address indexed ownerAddr, uint256 indexed txId, bool ok, string errorMsg, address[] tokens, bytes[] newAvailC1, bytes[] newAvailC2, uint64 pendingCreditCounter, uint64 minimumPendingCreditCounter, bytes availableShare)",
|
|
39
|
+
"event WithdrawRequested(address indexed ownerAddr, uint256 indexed txId, address token, uint256 plainAmount, bytes proof, bool useOffchainVerify, bytes userPubkey, bytes userCurrC1, bytes userCurrC2)",
|
|
40
|
+
"event WithdrawProcessed(address indexed ownerAddr, uint256 indexed txId, bool ok, string errorMsg, address token, uint256 plainAmount, bytes newAvailC1, bytes newAvailC2, uint64 pendingCreditCounter, uint64 minimumPendingCreditCounter, bytes availableShare)",
|
|
15
41
|
];
|
|
16
42
|
|
|
17
43
|
export const ERC20_ABI = [
|
|
@@ -19,6 +45,7 @@ export const ERC20_ABI = [
|
|
|
19
45
|
"function allowance(address owner, address spender) external view returns (uint256)",
|
|
20
46
|
"function balanceOf(address account) external view returns (uint256)",
|
|
21
47
|
"function decimals() external view returns (uint8)",
|
|
48
|
+
"function symbol() external view returns (string)",
|
|
22
49
|
];
|
|
23
50
|
|
|
24
51
|
export const TEMPO_FEE_TOKEN_ADDRESS =
|
package/src/ibe.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { timelockEncrypt } from "./vendor/ts-ibe/index.js";
|
|
2
|
+
|
|
3
|
+
const IBE_PUBKEY_URL =
|
|
4
|
+
"https://anon-testnet-api.fairblock.network/fairyring/keyshare/pubkey";
|
|
5
|
+
|
|
6
|
+
/** @type {{ active: string, queued: string | null } | null} */
|
|
7
|
+
let cachedIbePublicKeys = null;
|
|
8
|
+
|
|
9
|
+
function extractPublicKey(pubkeyObj) {
|
|
10
|
+
return pubkeyObj?.public_key || pubkeyObj?.pubkey || null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function getIbePublicKeys() {
|
|
14
|
+
if (cachedIbePublicKeys) return cachedIbePublicKeys;
|
|
15
|
+
|
|
16
|
+
const response = await fetch(IBE_PUBKEY_URL, {
|
|
17
|
+
headers: { Accept: "application/json" },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Failed to fetch IBE public key: ${response.status} ${response.statusText}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
const active = extractPublicKey(data?.active_pubkey);
|
|
28
|
+
const queued = extractPublicKey(data?.queued_pubkey);
|
|
29
|
+
|
|
30
|
+
if (!active) {
|
|
31
|
+
throw new Error("Active IBE public key not found in chain API response");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
cachedIbePublicKeys = { active, queued: queued || null };
|
|
35
|
+
return cachedIbePublicKeys;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Encrypts a base64-encoded randomness (PedersenOpening) using IBE.
|
|
40
|
+
* Always encrypts under the active pubkey; also encrypts under the queued
|
|
41
|
+
* pubkey when the chain reports one (backup for key rotation).
|
|
42
|
+
*
|
|
43
|
+
* @param {string} userAddress - Wallet address used as the IBE identity
|
|
44
|
+
* @param {string} randomnessBase64 - Base64-encoded randomness
|
|
45
|
+
* @returns {Promise<{ encrypted: string, queuedEncrypted: string | null }>}
|
|
46
|
+
* Hex-encoded IBE ciphertexts
|
|
47
|
+
*/
|
|
48
|
+
export async function encryptRandomness(userAddress, randomnessBase64) {
|
|
49
|
+
const { active, queued } = await getIbePublicKeys();
|
|
50
|
+
const id = userAddress.toLowerCase();
|
|
51
|
+
const randomnessBuffer = Buffer.from(randomnessBase64, "base64");
|
|
52
|
+
|
|
53
|
+
const [encrypted, queuedEncrypted] = await Promise.all([
|
|
54
|
+
timelockEncrypt(id, active, randomnessBuffer),
|
|
55
|
+
queued ? timelockEncrypt(id, queued, randomnessBuffer) : Promise.resolve(null),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
return { encrypted, queuedEncrypted };
|
|
59
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -14,6 +14,16 @@ declare module "@fairblock/stabletrust" {
|
|
|
14
14
|
diamondAddress?: string;
|
|
15
15
|
/** Optional API key for Fairycloak */
|
|
16
16
|
apiKey?: string;
|
|
17
|
+
/** StableTrust IPFS upload endpoint, used when a transfer sets `offchainZKP: true`.
|
|
18
|
+
* Falls back to `STABLETRUST_IPFS_UPLOAD_URL` in Node.js when omitted.
|
|
19
|
+
*/
|
|
20
|
+
ipfsUploadUrl?: string;
|
|
21
|
+
/** Optional bearer token for the StableTrust IPFS upload endpoint.
|
|
22
|
+
* Falls back to `STABLETRUST_IPFS_API_KEY` in Node.js when omitted.
|
|
23
|
+
*/
|
|
24
|
+
ipfsApiKey?: string;
|
|
25
|
+
/** Optional read gateway base URL, e.g. `https://ipfs.example.com`. */
|
|
26
|
+
ipfsGatewayUrl?: string;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
export interface AnonymousAccountInfo {
|
|
@@ -81,6 +91,11 @@ declare module "@fairblock/stabletrust" {
|
|
|
81
91
|
amount?: bigint | string | number;
|
|
82
92
|
/** Recipient ElGamal public key (base64). Auto-resolved from the chain if omitted. */
|
|
83
93
|
destinationPublicKey?: string;
|
|
94
|
+
/**
|
|
95
|
+
* When true, the ZK proof is uploaded to StableTrust IPFS and only its CID is put on-chain,
|
|
96
|
+
* instead of sending the full proof inline. Requires `ipfsUploadUrl`/`ipfsApiKey` config or the StableTrust IPFS environment variables.
|
|
97
|
+
*/
|
|
98
|
+
offchainZKP?: boolean;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
export interface TransferToAnonymousParams {
|
|
@@ -94,6 +109,11 @@ declare module "@fairblock/stabletrust" {
|
|
|
94
109
|
amount?: bigint | string | number;
|
|
95
110
|
/** Recipient ElGamal public key (base64). Auto-resolved from the chain if omitted. */
|
|
96
111
|
destinationPublicKey?: string;
|
|
112
|
+
/**
|
|
113
|
+
* When true, the ZK proof is uploaded to StableTrust IPFS and only its CID is put on-chain,
|
|
114
|
+
* instead of sending the full proof inline. Requires `ipfsUploadUrl`/`ipfsApiKey` config or the StableTrust IPFS environment variables.
|
|
115
|
+
*/
|
|
116
|
+
offchainZKP?: boolean;
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
export interface WithdrawParams {
|
|
@@ -105,6 +125,11 @@ declare module "@fairblock/stabletrust" {
|
|
|
105
125
|
proof?: string;
|
|
106
126
|
/** ElGamal private key (base64) for auto-proof generation. Contract scale is derived automatically. */
|
|
107
127
|
elGamalPrivateKey?: string;
|
|
128
|
+
/**
|
|
129
|
+
* When true, the ZK proof is uploaded to StableTrust IPFS and only its CID is put on-chain,
|
|
130
|
+
* instead of sending the full proof inline. Requires `ipfsUploadUrl`/`ipfsApiKey` config or `STABLETRUST_IPFS_UPLOAD_URL` / `STABLETRUST_IPFS_API_KEY`. Defaults to false.
|
|
131
|
+
*/
|
|
132
|
+
offchainZKP?: boolean;
|
|
108
133
|
}
|
|
109
134
|
|
|
110
135
|
export interface AnonymousAccountBalance {
|
|
@@ -263,14 +288,17 @@ declare module "@fairblock/stabletrust" {
|
|
|
263
288
|
*/
|
|
264
289
|
getFeeToken(): Promise<string>;
|
|
265
290
|
|
|
266
|
-
/**
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
291
|
+
/** Get the fixed fee used by all non-anonymous confidential transfers. */
|
|
292
|
+
getNonAnonymousTransferFee(): Promise<bigint>;
|
|
293
|
+
|
|
294
|
+
/** Get the fixed fee used by anonymous transfers whose proof is stored on IPFS. */
|
|
295
|
+
getAnonymousIpfsTransferFee(): Promise<bigint>;
|
|
296
|
+
|
|
297
|
+
/** Get the fixed fee used by anonymous transfers whose proof is submitted inline. */
|
|
298
|
+
getAnonymousInlineTransferFee(): Promise<bigint>;
|
|
271
299
|
|
|
272
300
|
/**
|
|
273
|
-
* Get the prepaid fee balance for an anonymous account and token (raw
|
|
301
|
+
* Get the prepaid fee balance for an anonymous account and token (raw fee-token units).
|
|
274
302
|
* Use `getFeeToken()` to get the active fee token address.
|
|
275
303
|
*/
|
|
276
304
|
getPrepaidFeeBalance(accountId: string, token: string): Promise<bigint>;
|
|
@@ -283,9 +311,10 @@ declare module "@fairblock/stabletrust" {
|
|
|
283
311
|
* Handles ERC-20 approval automatically.
|
|
284
312
|
*
|
|
285
313
|
* The fee token is read from the contract — use `getFeeToken()` to see it.
|
|
286
|
-
* Use `
|
|
314
|
+
* Use `getAnonymousInlineTransferFee()` or `getAnonymousIpfsTransferFee()`
|
|
315
|
+
* to see how much is deducted for the selected proof path.
|
|
287
316
|
*
|
|
288
|
-
* @param amount Amount in raw
|
|
317
|
+
* @param amount Amount in raw units of the active fee token (wei for native currency)
|
|
289
318
|
*/
|
|
290
319
|
depositFees(
|
|
291
320
|
authWallet: ethers.Wallet,
|
|
@@ -458,7 +487,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
458
487
|
* Balance information
|
|
459
488
|
*/
|
|
460
489
|
export interface Balance {
|
|
461
|
-
amount:
|
|
490
|
+
amount: bigint;
|
|
462
491
|
ciphertext: string | null;
|
|
463
492
|
}
|
|
464
493
|
|
|
@@ -466,7 +495,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
466
495
|
* Confidential balance summary
|
|
467
496
|
*/
|
|
468
497
|
export interface ConfidentialBalance {
|
|
469
|
-
amount:
|
|
498
|
+
amount: bigint;
|
|
470
499
|
available: Balance;
|
|
471
500
|
pending: Balance;
|
|
472
501
|
}
|
|
@@ -490,6 +519,14 @@ declare module "@fairblock/stabletrust" {
|
|
|
490
519
|
* Transfer options
|
|
491
520
|
*/
|
|
492
521
|
export interface TransferOptions {
|
|
522
|
+
/**
|
|
523
|
+
* When true, upload the ABI-encoded transfer proof to StableTrust IPFS and
|
|
524
|
+
* submit only the bare CID bytes on-chain with offchainZKP=true.
|
|
525
|
+
* Defaults to true on Tempo (chainId 42431) for backwards compatibility,
|
|
526
|
+
* and false on other chains.
|
|
527
|
+
*/
|
|
528
|
+
offchainZKP?: boolean;
|
|
529
|
+
/** @deprecated Use offchainZKP instead. */
|
|
493
530
|
useOffchainVerify?: boolean;
|
|
494
531
|
waitForFinalization?: boolean;
|
|
495
532
|
}
|
|
@@ -498,10 +535,27 @@ declare module "@fairblock/stabletrust" {
|
|
|
498
535
|
* Withdraw options
|
|
499
536
|
*/
|
|
500
537
|
export interface WithdrawOptions {
|
|
538
|
+
/**
|
|
539
|
+
* When true, upload the ABI-encoded withdraw proof to StableTrust IPFS and
|
|
540
|
+
* submit only the bare CID bytes on-chain with offchainZKP=true.
|
|
541
|
+
* Defaults to true on Tempo (chainId 42431) for backwards compatibility,
|
|
542
|
+
* and false on other chains.
|
|
543
|
+
*/
|
|
544
|
+
offchainZKP?: boolean;
|
|
545
|
+
/** @deprecated Use offchainZKP instead. */
|
|
501
546
|
useOffchainVerify?: boolean;
|
|
502
547
|
waitForFinalization?: boolean;
|
|
503
548
|
}
|
|
504
549
|
|
|
550
|
+
export interface StableTrustIpfsOptions {
|
|
551
|
+
/** StableTrust IPFS upload endpoint. Falls back to STABLETRUST_IPFS_UPLOAD_URL in Node.js. */
|
|
552
|
+
ipfsUploadUrl?: string;
|
|
553
|
+
/** Optional bearer token for the StableTrust IPFS upload endpoint. Falls back to STABLETRUST_IPFS_API_KEY in Node.js. */
|
|
554
|
+
ipfsApiKey?: string;
|
|
555
|
+
/** StableTrust backend API base URL for persisting request rows. Defaults to the production API. */
|
|
556
|
+
apiBaseUrl?: string;
|
|
557
|
+
}
|
|
558
|
+
|
|
505
559
|
/**
|
|
506
560
|
* Main SDK client class
|
|
507
561
|
* WASM auto-initializes on first use - no manual initialization required!
|
|
@@ -512,7 +566,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
512
566
|
* @param rpcUrl RPC endpoint URL
|
|
513
567
|
* @param chainId Chain ID (uses default Stabletrust contract for known networks)
|
|
514
568
|
*/
|
|
515
|
-
constructor(rpcUrl: string, chainId: number);
|
|
569
|
+
constructor(rpcUrl: string, chainId: number, options?: StableTrustIpfsOptions);
|
|
516
570
|
|
|
517
571
|
/**
|
|
518
572
|
* Create a new ConfidentialTransferClient
|
|
@@ -520,7 +574,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
520
574
|
* @param contractAddress Confidential transfer contract address
|
|
521
575
|
* @param chainId Chain ID
|
|
522
576
|
*/
|
|
523
|
-
constructor(rpcUrl: string, contractAddress: string, chainId: number);
|
|
577
|
+
constructor(rpcUrl: string, contractAddress: string, chainId: number, options?: StableTrustIpfsOptions);
|
|
524
578
|
|
|
525
579
|
/**
|
|
526
580
|
* Get account information from the contract
|
|
@@ -574,7 +628,7 @@ declare module "@fairblock/stabletrust" {
|
|
|
574
628
|
senderWallet: ethers.Wallet | ethers.Signer,
|
|
575
629
|
recipientAddress: string,
|
|
576
630
|
tokenAddress: string,
|
|
577
|
-
amount: number,
|
|
631
|
+
amount: bigint | string | number,
|
|
578
632
|
options?: TransferOptions,
|
|
579
633
|
): Promise<ethers.ContractTransactionReceipt>;
|
|
580
634
|
|
|
@@ -588,14 +642,21 @@ declare module "@fairblock/stabletrust" {
|
|
|
588
642
|
withdraw(
|
|
589
643
|
wallet: ethers.Wallet | ethers.Signer,
|
|
590
644
|
tokenAddress: string,
|
|
591
|
-
amount: number,
|
|
645
|
+
amount: bigint | string | number,
|
|
592
646
|
options?: WithdrawOptions,
|
|
593
647
|
): Promise<ethers.ContractTransactionReceipt>;
|
|
594
648
|
|
|
595
|
-
/**
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
649
|
+
/** Get the currently configured fee token. address(0) means native currency. */
|
|
650
|
+
getFeeToken(): Promise<string>;
|
|
651
|
+
|
|
652
|
+
/** Get the fixed fee used by all non-anonymous confidential transfers. */
|
|
653
|
+
getNonAnonymousTransferFee(): Promise<bigint>;
|
|
654
|
+
|
|
655
|
+
/** Get the fixed fee used by anonymous transfers whose proof is stored on IPFS. */
|
|
656
|
+
getAnonymousIpfsTransferFee(): Promise<bigint>;
|
|
657
|
+
|
|
658
|
+
/** Get the fixed fee used by anonymous transfers whose proof is submitted inline. */
|
|
659
|
+
getAnonymousInlineTransferFee(): Promise<bigint>;
|
|
599
660
|
|
|
600
661
|
/**
|
|
601
662
|
* Get the public ERC20 balance for an address (for comparison with confidential balance)
|
|
@@ -674,6 +735,12 @@ declare module "@fairblock/stabletrust" {
|
|
|
674
735
|
/**
|
|
675
736
|
* Contract ABI
|
|
676
737
|
*/
|
|
738
|
+
export const TRANSFER_CONFIDENTIAL_SIGNATURE: string;
|
|
739
|
+
export const WITHDRAW_CONFIDENTIAL_SIGNATURE: string;
|
|
740
|
+
export const FEE_TOKEN_SIGNATURE: string;
|
|
741
|
+
export const NON_ANONYMOUS_TRANSFER_FEE_SIGNATURE: string;
|
|
742
|
+
export const ANONYMOUS_IPFS_TRANSFER_FEE_SIGNATURE: string;
|
|
743
|
+
export const ANONYMOUS_INLINE_TRANSFER_FEE_SIGNATURE: string;
|
|
677
744
|
export const CONTRACT_ABI: any[];
|
|
678
745
|
|
|
679
746
|
/**
|
package/src/index.js
CHANGED
|
@@ -10,6 +10,12 @@ export {
|
|
|
10
10
|
export {
|
|
11
11
|
CONTRACT_ABI,
|
|
12
12
|
ERC20_ABI,
|
|
13
|
+
TRANSFER_CONFIDENTIAL_SIGNATURE,
|
|
14
|
+
WITHDRAW_CONFIDENTIAL_SIGNATURE,
|
|
15
|
+
FEE_TOKEN_SIGNATURE,
|
|
16
|
+
NON_ANONYMOUS_TRANSFER_FEE_SIGNATURE,
|
|
17
|
+
ANONYMOUS_IPFS_TRANSFER_FEE_SIGNATURE,
|
|
18
|
+
ANONYMOUS_INLINE_TRANSFER_FEE_SIGNATURE,
|
|
13
19
|
STABLETRUST_CONTRACTS_BY_CHAIN_ID,
|
|
14
20
|
getStabletrustContractAddress,
|
|
15
21
|
} from "./constants.js";
|
package/src/utils.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { ethers } from "ethers";
|
|
2
|
-
// Pinata uploads API endpoint
|
|
3
|
-
// Docs: https://docs.pinata.cloud
|
|
4
|
-
const PINATA_UPLOAD_URL = "https://uploads.pinata.cloud/v3/files";
|
|
5
|
-
const PINATA_JWT = process.env.PINATA_JWT;
|
|
6
2
|
|
|
7
3
|
// Anonymous account ID constraints (must match the Fairycloak relay and on-chain contract rules)
|
|
8
4
|
export const MAX_ANONYMOUS_ACCOUNT_ID_LENGTH = 20;
|
|
@@ -38,37 +34,96 @@ export function validateAnonymousAccountId(accountId, fieldName = "accountId") {
|
|
|
38
34
|
}
|
|
39
35
|
}
|
|
40
36
|
/**
|
|
41
|
-
*
|
|
37
|
+
* Convert a proof component returned by the WASM/proof generator into the exact
|
|
38
|
+
* bytes carried inside the contract/Fairyport ABI proof tuple. The current proof
|
|
39
|
+
* generator returns base64 text for each proof component; these ASCII bytes are
|
|
40
|
+
* what the Go relayer and CosmWasm contract expect to receive.
|
|
41
|
+
*
|
|
42
|
+
* Hex strings are also accepted for callers that provide raw proof component
|
|
43
|
+
* bytes manually.
|
|
44
|
+
*
|
|
45
|
+
* @param {string|Uint8Array|ArrayBuffer|Array<number>} value
|
|
46
|
+
* @returns {Uint8Array}
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
function _proofPartToBytes(value) {
|
|
50
|
+
if (value instanceof Uint8Array) return value;
|
|
51
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
52
|
+
if (Array.isArray(value)) return new Uint8Array(value);
|
|
53
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
54
|
+
throw new Error("invalid proof component: expected non-empty string or bytes");
|
|
55
|
+
}
|
|
56
|
+
if (value.startsWith("0x")) return ethers.getBytes(value);
|
|
57
|
+
return new TextEncoder().encode(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Encodes the ZK-Proof data for a transfer into the format expected by the
|
|
62
|
+
* latest EVM/Fairyport/CosmWasm flow: abi.encode(bytes equalityProof,
|
|
63
|
+
* bytes ciphertextValidityProof, bytes rangeProof).
|
|
42
64
|
*
|
|
43
65
|
* @param {Object} proofData - The proof data object
|
|
44
|
-
* @returns {string}
|
|
66
|
+
* @returns {string} ABI-encoded proof bytes
|
|
45
67
|
*/
|
|
46
68
|
export function encodeTransferProof(proofData) {
|
|
47
69
|
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
|
|
48
70
|
return abiCoder.encode(
|
|
49
|
-
["
|
|
71
|
+
["bytes", "bytes", "bytes"],
|
|
50
72
|
[
|
|
51
|
-
proofData.equality_proof,
|
|
52
|
-
proofData.ciphertext_validity_proof,
|
|
53
|
-
proofData.range_proof,
|
|
73
|
+
_proofPartToBytes(proofData.equality_proof),
|
|
74
|
+
_proofPartToBytes(proofData.ciphertext_validity_proof),
|
|
75
|
+
_proofPartToBytes(proofData.range_proof),
|
|
54
76
|
],
|
|
55
77
|
);
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
/**
|
|
59
|
-
* Encodes the ZK-Proof data for a withdrawal
|
|
81
|
+
* Encodes the ZK-Proof data for a withdrawal into the format expected by the
|
|
82
|
+
* latest EVM/Fairyport/CosmWasm flow: abi.encode(bytes equalityProof,
|
|
83
|
+
* bytes rangeProof).
|
|
60
84
|
*
|
|
61
85
|
* @param {Object} proofData - The proof data object
|
|
62
|
-
* @returns {string}
|
|
86
|
+
* @returns {string} ABI-encoded proof bytes
|
|
63
87
|
*/
|
|
64
88
|
export function encodeWithdrawProof(proofData) {
|
|
65
89
|
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
|
|
66
90
|
return abiCoder.encode(
|
|
67
|
-
["
|
|
68
|
-
[proofData.equality_proof, proofData.range_proof],
|
|
91
|
+
["bytes", "bytes"],
|
|
92
|
+
[_proofPartToBytes(proofData.equality_proof), _proofPartToBytes(proofData.range_proof)],
|
|
69
93
|
);
|
|
70
94
|
}
|
|
71
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Convert a raw ERC-20 token amount to the contract's 2-decimal scale
|
|
98
|
+
* (`rawAmount * 100 / 10^decimals`).
|
|
99
|
+
*
|
|
100
|
+
* Throws when the amount rounds to zero and warns when a sub-cent remainder
|
|
101
|
+
* is dropped, so callers never silently no-op or lose precision.
|
|
102
|
+
*
|
|
103
|
+
* @param {bigint|string|number} rawAmount - Amount in raw ERC-20 units
|
|
104
|
+
* @param {bigint|number} tokenDecimals - Token decimals
|
|
105
|
+
* @returns {bigint} Amount in contract scale
|
|
106
|
+
* @throws {Error} If the amount rounds to 0 in contract scale
|
|
107
|
+
*/
|
|
108
|
+
export function toContractScale(rawAmount, tokenDecimals) {
|
|
109
|
+
const raw = BigInt(rawAmount);
|
|
110
|
+
const divisor = 10n ** BigInt(tokenDecimals);
|
|
111
|
+
const scaled = (raw * 100n) / divisor;
|
|
112
|
+
|
|
113
|
+
if (scaled <= 0n) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Amount too small: ${raw} raw units rounds to 0 in contract scale`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if ((raw * 100n) % divisor !== 0n) {
|
|
119
|
+
console.warn(
|
|
120
|
+
`Amount ${raw} is not fully representable in contract scale (2 decimals); the sub-cent remainder will be dropped.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return scaled;
|
|
125
|
+
}
|
|
126
|
+
|
|
72
127
|
/**
|
|
73
128
|
* Delays execution for a specified time
|
|
74
129
|
*
|
|
@@ -121,76 +176,128 @@ export async function waitForCondition(
|
|
|
121
176
|
}
|
|
122
177
|
|
|
123
178
|
/**
|
|
124
|
-
*
|
|
125
|
-
* The object will be stored as a UTF-8 JSON blob.
|
|
179
|
+
* Browser-safe environment lookup.
|
|
126
180
|
*
|
|
127
|
-
* @param {
|
|
128
|
-
* @returns {
|
|
181
|
+
* @param {string} name - Environment variable name.
|
|
182
|
+
* @returns {string|undefined}
|
|
183
|
+
* @private
|
|
129
184
|
*/
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
185
|
+
function _env(name) {
|
|
186
|
+
if (typeof process !== "undefined" && process?.env?.[name]) {
|
|
187
|
+
return process.env[name];
|
|
133
188
|
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
134
191
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Resolve StableTrust IPFS upload options from explicit options or environment variables.
|
|
194
|
+
*
|
|
195
|
+
* @param {Object} [options]
|
|
196
|
+
* @param {string} [options.uploadUrl] - StableTrust IPFS upload endpoint.
|
|
197
|
+
* @param {string} [options.ipfsUploadUrl] - Alias for uploadUrl.
|
|
198
|
+
* @param {string} [options.apiKey] - Bearer token for upload endpoint.
|
|
199
|
+
* @param {string} [options.ipfsApiKey] - Alias for apiKey.
|
|
200
|
+
* @returns {{uploadUrl:string, apiKey:string|null}}
|
|
201
|
+
* @private
|
|
202
|
+
*/
|
|
203
|
+
function _resolveIpfsUploadOptions(options = {}) {
|
|
204
|
+
const uploadUrl =
|
|
205
|
+
options.uploadUrl ||
|
|
206
|
+
options.ipfsUploadUrl ||
|
|
207
|
+
_env("STABLETRUST_IPFS_UPLOAD_URL") ||
|
|
208
|
+
_env("IPFS_UPLOAD_URL");
|
|
150
209
|
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
|
|
210
|
+
if (!uploadUrl) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
"StableTrust IPFS upload URL is not set - pass `ipfsUploadUrl` in the client config or set STABLETRUST_IPFS_UPLOAD_URL",
|
|
213
|
+
);
|
|
154
214
|
}
|
|
155
215
|
|
|
156
|
-
|
|
157
|
-
|
|
216
|
+
return {
|
|
217
|
+
uploadUrl: String(uploadUrl).trim(),
|
|
218
|
+
apiKey:
|
|
219
|
+
options.apiKey ||
|
|
220
|
+
options.ipfsApiKey ||
|
|
221
|
+
_env("STABLETRUST_IPFS_API_KEY") ||
|
|
222
|
+
_env("IPFS_UPLOAD_API_KEY") ||
|
|
223
|
+
null,
|
|
224
|
+
};
|
|
158
225
|
}
|
|
159
226
|
|
|
160
227
|
/**
|
|
161
|
-
* Upload
|
|
228
|
+
* Upload a JSON-serializable object to the configured StableTrust IPFS upload endpoint
|
|
229
|
+
* and return its CID. The object is stored as UTF-8 JSON bytes.
|
|
162
230
|
*
|
|
163
|
-
* @param {
|
|
164
|
-
* @param {
|
|
165
|
-
* @
|
|
231
|
+
* @param {any} data - JSON-serializable data to store.
|
|
232
|
+
* @param {Object} [options]
|
|
233
|
+
* @param {string} [options.uploadUrl] - StableTrust IPFS upload endpoint.
|
|
234
|
+
* @param {string} [options.apiKey] - Bearer token for upload endpoint.
|
|
235
|
+
* @returns {Promise<string>} Bare CID string.
|
|
166
236
|
*/
|
|
167
|
-
export async function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
237
|
+
export async function uploadJsonToIpfs(data, options = {}) {
|
|
238
|
+
const json = JSON.stringify(data);
|
|
239
|
+
return uploadBytesToIpfs(new TextEncoder().encode(json), "proof.json", options);
|
|
240
|
+
}
|
|
171
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Upload raw proof bytes to the configured StableTrust IPFS upload endpoint and return the bare CID.
|
|
244
|
+
*
|
|
245
|
+
* Expected upload endpoint contract:
|
|
246
|
+
* - POST raw bytes to `uploadUrl`
|
|
247
|
+
* - `Content-Type: application/octet-stream`
|
|
248
|
+
* - optional `Authorization: Bearer <apiKey>`
|
|
249
|
+
* - response JSON contains `{ "cid": "..." }` (also accepts `Hash`/`Cid` for compatibility)
|
|
250
|
+
*
|
|
251
|
+
* @param {Uint8Array|ArrayBuffer|Array<number>} bytes - Raw proof bytes.
|
|
252
|
+
* @param {string} [name='proof.bin'] - Optional proof name, sent as `X-StableTrust-Filename` metadata.
|
|
253
|
+
* @param {Object} [options]
|
|
254
|
+
* @param {string} [options.uploadUrl] - StableTrust IPFS upload endpoint.
|
|
255
|
+
* @param {string} [options.apiKey] - Bearer token for upload endpoint.
|
|
256
|
+
* @returns {Promise<string>} Bare CID string.
|
|
257
|
+
*/
|
|
258
|
+
export async function uploadBytesToIpfs(bytes, name = "proof.bin", options = {}) {
|
|
259
|
+
const { uploadUrl, apiKey } = _resolveIpfsUploadOptions(options);
|
|
172
260
|
const uint8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
173
261
|
|
|
174
|
-
|
|
262
|
+
if (uint8.length === 0) {
|
|
263
|
+
throw new Error("cannot upload an empty proof to IPFS");
|
|
264
|
+
}
|
|
175
265
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
266
|
+
const headers = {
|
|
267
|
+
"Content-Type": "application/octet-stream",
|
|
268
|
+
};
|
|
269
|
+
if (name) {
|
|
270
|
+
headers["X-StableTrust-Filename"] = String(name);
|
|
271
|
+
}
|
|
272
|
+
if (apiKey) {
|
|
273
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
274
|
+
}
|
|
180
275
|
|
|
181
|
-
const res = await fetch(
|
|
276
|
+
const res = await fetch(uploadUrl, {
|
|
182
277
|
method: "POST",
|
|
183
|
-
headers
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
body: form,
|
|
278
|
+
headers,
|
|
279
|
+
body: uint8,
|
|
187
280
|
});
|
|
188
281
|
|
|
189
282
|
if (!res.ok) {
|
|
190
283
|
const text = await res.text();
|
|
191
|
-
throw new Error(`
|
|
284
|
+
throw new Error(`StableTrust IPFS upload failed: ${text}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const contentType = res.headers.get("content-type") || "";
|
|
288
|
+
let cid;
|
|
289
|
+
if (contentType.includes("application/json")) {
|
|
290
|
+
const jsonRes = await res.json();
|
|
291
|
+
cid = jsonRes?.cid ?? jsonRes?.CID ?? jsonRes?.Cid ?? jsonRes?.Hash ?? jsonRes?.hash;
|
|
292
|
+
} else {
|
|
293
|
+
cid = await res.text();
|
|
192
294
|
}
|
|
193
295
|
|
|
194
|
-
|
|
195
|
-
|
|
296
|
+
cid = String(cid || "").trim().replace(/^ipfs:\/\//i, "");
|
|
297
|
+
if (!cid) {
|
|
298
|
+
throw new Error("StableTrust IPFS upload returned no CID");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return cid;
|
|
196
302
|
}
|
|
303
|
+
|