@fairblock/stabletrust 2.0.13 → 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 +9 -1
- package/src/confidential-client.js +71 -63
- 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",
|
|
@@ -16,5 +22,7 @@ export const CHAIN_KEY_BY_ID = {
|
|
|
16
22
|
* POST /api/requests — create a request row
|
|
17
23
|
*/
|
|
18
24
|
export async function createRequest(requestData, apiBaseUrl = DEFAULT_API_BASE_URL) {
|
|
19
|
-
await axios.post(`${apiBaseUrl}/api/requests`, requestData
|
|
25
|
+
await axios.post(`${apiBaseUrl}/api/requests`, requestData, {
|
|
26
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
27
|
+
});
|
|
20
28
|
}
|
|
@@ -121,7 +121,10 @@ export class ConfidentialTransferClient {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
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.
|
|
125
128
|
* @private
|
|
126
129
|
*/
|
|
127
130
|
_postRequest(data) {
|
|
@@ -132,7 +135,7 @@ export class ConfidentialTransferClient {
|
|
|
132
135
|
applicationName: this.config.applicationName,
|
|
133
136
|
...data,
|
|
134
137
|
};
|
|
135
|
-
createRequest(payload, apiBaseUrl).catch((err) => {
|
|
138
|
+
return createRequest(payload, apiBaseUrl).catch((err) => {
|
|
136
139
|
const status = err?.response?.status;
|
|
137
140
|
const body = err?.response?.data;
|
|
138
141
|
console.warn("[StableTrust] Failed to post request:", err.message);
|
|
@@ -882,67 +885,72 @@ export class ConfidentialTransferClient {
|
|
|
882
885
|
]);
|
|
883
886
|
const tokenSymbol = await tokenContract.symbol();
|
|
884
887
|
|
|
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
|
-
|
|
945
|
-
|
|
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
|
+
}
|
|
946
954
|
|
|
947
955
|
if (waitForFinalization) {
|
|
948
956
|
await this._waitForGlobalState(senderAddress, "transfer");
|
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(
|