@fairblock/stabletrust 2.0.12 → 2.0.14
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/package.json +2 -2
- package/src/backend.js +10 -1
- package/src/confidential-client.js +75 -66
- package/src/constants.js +1 -0
- package/src/ibe.js +24 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fairblock/stabletrust",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"description": "SDK for confidential transfers using homomorphic encryption and zero-knowledge proofs",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/withdrawal-fees.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@stablelib/chacha20poly1305": "^1.0.1",
|
|
50
50
|
"axios": "^1.16.0",
|
|
51
51
|
"dotenv": "^17.2.3",
|
|
52
|
-
"ethers": "^6.
|
|
52
|
+
"ethers": "^6.17.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=18.0.0"
|
package/src/backend.js
CHANGED
|
@@ -2,6 +2,12 @@ import axios from "axios";
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_API_BASE_URL = "https://stabletrust-backend-api.fairblock.network";
|
|
4
4
|
|
|
5
|
+
// Bound each request-row POST. Callers await these,
|
|
6
|
+
// so without a timeout a slow/hung backend would stall the caller's transfer()
|
|
7
|
+
// even though the on-chain transfer already completed. On timeout axios rejects
|
|
8
|
+
// with ECONNABORTED, which _postRequest turns into a warning.
|
|
9
|
+
const REQUEST_TIMEOUT_MS = 5000;
|
|
10
|
+
|
|
5
11
|
export const CHAIN_KEY_BY_ID = {
|
|
6
12
|
2201: "stable",
|
|
7
13
|
5042002: "arc",
|
|
@@ -9,11 +15,14 @@ export const CHAIN_KEY_BY_ID = {
|
|
|
9
15
|
11155111: "ethereumSepolia",
|
|
10
16
|
421614: "arbitrumSepolia",
|
|
11
17
|
42431: "tempo",
|
|
18
|
+
42161: "arbitrumOne",
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* POST /api/requests — create a request row
|
|
16
23
|
*/
|
|
17
24
|
export async function createRequest(requestData, apiBaseUrl = DEFAULT_API_BASE_URL) {
|
|
18
|
-
await axios.post(`${apiBaseUrl}/api/requests`, requestData
|
|
25
|
+
await axios.post(`${apiBaseUrl}/api/requests`, requestData, {
|
|
26
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
27
|
+
});
|
|
19
28
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ANONYMOUS_IPFS_TRANSFER_FEE_SIGNATURE,
|
|
10
10
|
ANONYMOUS_INLINE_TRANSFER_FEE_SIGNATURE,
|
|
11
11
|
getStabletrustContractAddress,
|
|
12
|
+
STABLETRUST_CONTRACTS_BY_CHAIN_ID,
|
|
12
13
|
} from "./constants.js";
|
|
13
14
|
import { deriveKeys, decryptCiphertext, combineCiphertext } from "./crypto.js";
|
|
14
15
|
import {
|
|
@@ -76,9 +77,9 @@ export class ConfidentialTransferClient {
|
|
|
76
77
|
throw new Error("chainId is required");
|
|
77
78
|
}
|
|
78
79
|
if (!resolvedContractAddress) {
|
|
79
|
-
const supportedChainIds =
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
const supportedChainIds = Object.keys(
|
|
81
|
+
STABLETRUST_CONTRACTS_BY_CHAIN_ID,
|
|
82
|
+
).join(", ");
|
|
82
83
|
throw new Error(
|
|
83
84
|
`contractAddress is required for chainId ${resolvedChainId}. No default Stabletrust contract is configured for this chain. Supported chainIds: ${supportedChainIds}`,
|
|
84
85
|
);
|
|
@@ -120,7 +121,10 @@ export class ConfidentialTransferClient {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
+
* Post a request row. Returns the createRequest promise (which resolves even
|
|
125
|
+
* on failure - errors are logged and swallowed) so callers that need the row
|
|
126
|
+
* to exist before returning (e.g. confidentialTransfer) can `await` it.
|
|
127
|
+
* Callers that don't await keep the old fire-and-forget behavior.
|
|
124
128
|
* @private
|
|
125
129
|
*/
|
|
126
130
|
_postRequest(data) {
|
|
@@ -131,7 +135,7 @@ export class ConfidentialTransferClient {
|
|
|
131
135
|
applicationName: this.config.applicationName,
|
|
132
136
|
...data,
|
|
133
137
|
};
|
|
134
|
-
createRequest(payload, apiBaseUrl).catch((err) => {
|
|
138
|
+
return createRequest(payload, apiBaseUrl).catch((err) => {
|
|
135
139
|
const status = err?.response?.status;
|
|
136
140
|
const body = err?.response?.data;
|
|
137
141
|
console.warn("[StableTrust] Failed to post request:", err.message);
|
|
@@ -881,67 +885,72 @@ export class ConfidentialTransferClient {
|
|
|
881
885
|
]);
|
|
882
886
|
const tokenSymbol = await tokenContract.symbol();
|
|
883
887
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
888
|
+
let senderEnc = null;
|
|
889
|
+
let receiverEnc = null;
|
|
890
|
+
try {
|
|
891
|
+
[senderEnc, receiverEnc] = await Promise.all([
|
|
892
|
+
encryptRandomness(senderAddress, proof.data.sender_randomness),
|
|
893
|
+
encryptRandomness(recipientAddress, proof.data.receiver_randomness),
|
|
894
|
+
]);
|
|
895
|
+
} catch (encErr) {
|
|
896
|
+
console.warn(
|
|
897
|
+
`[StableTrust] Transfer ${txHash} Failed to get IBE Randomness (${encErr?.message ?? encErr}). `,
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (senderEnc && receiverEnc) {
|
|
902
|
+
const transferDetails = {
|
|
903
|
+
sender: senderAddress.toLowerCase(),
|
|
904
|
+
recipient: recipientAddress.toLowerCase(),
|
|
905
|
+
token: tokenSymbol,
|
|
906
|
+
tokenAddress,
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
const queuedFields =
|
|
910
|
+
senderEnc.queuedEncrypted && receiverEnc.queuedEncrypted
|
|
911
|
+
? {
|
|
912
|
+
queuedEncryptedSenderRandomness: senderEnc.queuedEncrypted,
|
|
913
|
+
queuedEncryptedSenderRandomnessId: senderEnc.queuedEncryptedId,
|
|
914
|
+
queuedEncryptedReceiverRandomness: receiverEnc.queuedEncrypted,
|
|
915
|
+
queuedEncryptedReceiverRandomnessId:
|
|
916
|
+
receiverEnc.queuedEncryptedId,
|
|
917
|
+
}
|
|
918
|
+
: {};
|
|
919
|
+
|
|
920
|
+
await this._postRequest({
|
|
921
|
+
requestId,
|
|
922
|
+
userAddress: senderAddress.toLowerCase(),
|
|
923
|
+
operationKind: 2,
|
|
924
|
+
type: "transfer",
|
|
925
|
+
transactionHash: txHash,
|
|
926
|
+
status: "pending",
|
|
927
|
+
details: transferDetails,
|
|
928
|
+
encryptedSenderRandomness: senderEnc.encrypted,
|
|
929
|
+
encryptedSenderRandomnessId: senderEnc.encryptedId,
|
|
930
|
+
encryptedReceiverRandomness: receiverEnc.encrypted,
|
|
931
|
+
encryptedReceiverRandomnessId: receiverEnc.encryptedId,
|
|
932
|
+
...queuedFields,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
await this._postRequest({
|
|
936
|
+
requestId: `received-${requestId}-${recipientAddress.toLowerCase()}`,
|
|
937
|
+
userAddress: recipientAddress.toLowerCase(),
|
|
938
|
+
operationKind: 2,
|
|
939
|
+
type: "received",
|
|
940
|
+
transactionHash: txHash,
|
|
941
|
+
status: "pending",
|
|
942
|
+
details: transferDetails,
|
|
943
|
+
encryptedReceiverRandomness: receiverEnc.encrypted,
|
|
944
|
+
encryptedReceiverRandomnessId: receiverEnc.encryptedId,
|
|
945
|
+
...(receiverEnc.queuedEncrypted
|
|
946
|
+
? {
|
|
947
|
+
queuedEncryptedReceiverRandomness: receiverEnc.queuedEncrypted,
|
|
948
|
+
queuedEncryptedReceiverRandomnessId:
|
|
949
|
+
receiverEnc.queuedEncryptedId,
|
|
950
|
+
}
|
|
951
|
+
: {}),
|
|
952
|
+
});
|
|
953
|
+
}
|
|
945
954
|
|
|
946
955
|
if (waitForFinalization) {
|
|
947
956
|
await this._waitForGlobalState(senderAddress, "transfer");
|
package/src/constants.js
CHANGED
|
@@ -69,6 +69,7 @@ export const STABLETRUST_CONTRACTS_BY_CHAIN_ID = Object.freeze({
|
|
|
69
69
|
11155111: "0x7507a13352AFAa79D33E994f86f2f62463ba8DE4", //Ethereum
|
|
70
70
|
421614: "0x5acECCdeb5CbD3C727eCB49F8706Eb80EF2f977F", //Arbitrum
|
|
71
71
|
42431: "0xE559fB936C69c46E216bf61B07C16bF1a6d444aa", //Tempo
|
|
72
|
+
42161: "0xCAA6384D5Ac8b9111D482dd676BB890f2b6e9513", //Arbitrum One (mainnet)
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
export function getStabletrustContractAddress(chainId) {
|
package/src/ibe.js
CHANGED
|
@@ -3,6 +3,14 @@ import { timelockEncrypt } from "./vendor/ts-ibe/index.js";
|
|
|
3
3
|
const IBE_PUBKEY_URL =
|
|
4
4
|
"https://anon-testnet-api.fairblock.network/fairyring/keyshare/pubkey";
|
|
5
5
|
|
|
6
|
+
// Bound the IBE public-key fetch. Without this, a slow or hung endpoint (a
|
|
7
|
+
// socket that accepts the connection but never responds) would block the fetch
|
|
8
|
+
// indefinitely, and because confidentialTransfer awaits encryptRandomness that
|
|
9
|
+
// stall propagates to the caller's transfer() call even though the on-chain
|
|
10
|
+
// transfer already happened. On timeout the fetch aborts and encryptRandomness
|
|
11
|
+
// rejects, which the caller handles as "dashboard record skipped".
|
|
12
|
+
const IBE_FETCH_TIMEOUT_MS = 5000;
|
|
13
|
+
|
|
6
14
|
/** @type {{ active: string, queued: string | null } | null} */
|
|
7
15
|
let cachedIbePublicKeys = null;
|
|
8
16
|
|
|
@@ -23,9 +31,22 @@ function buildIbeId(userAddress, publicKey) {
|
|
|
23
31
|
async function getIbePublicKeys() {
|
|
24
32
|
if (cachedIbePublicKeys) return cachedIbePublicKeys;
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await fetch(IBE_PUBKEY_URL, {
|
|
37
|
+
headers: { Accept: "application/json" },
|
|
38
|
+
signal: AbortSignal.timeout(IBE_FETCH_TIMEOUT_MS),
|
|
39
|
+
});
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`IBE public key fetch timed out after ${IBE_FETCH_TIMEOUT_MS}ms`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
throw new Error(
|
|
47
|
+
`IBE public key fetch failed: ${err?.message ?? err}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
29
50
|
|
|
30
51
|
if (!response.ok) {
|
|
31
52
|
throw new Error(
|