@fairblock/stabletrust 2.0.13 → 2.0.15
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 +5 -5
- package/package.json +2 -2
- package/src/backend.js +9 -1
- package/src/confidential-client.js +71 -63
- package/src/constants.js +25 -15
- package/src/ibe.js +24 -3
- package/src/index.js +10 -1
- package/src/predicate-contract.js +126 -0
- package/src/predicate-fee-aware-clients.js +31 -0
package/README.md
CHANGED
|
@@ -63,11 +63,11 @@ The following contract addresses are available for confidential transfers on tes
|
|
|
63
63
|
|
|
64
64
|
| Network(Testnet) | Chain ID | Contract Address |
|
|
65
65
|
| :--------------- | :------- | :------------------------------------------- |
|
|
66
|
-
| Stable | 2201 | `
|
|
67
|
-
| Arc | 5042002 | `
|
|
68
|
-
| Base | 84532 | `
|
|
69
|
-
| Ethereum | 11155111 | `
|
|
70
|
-
| Arbitrum | 421614 | `
|
|
66
|
+
| Stable | 2201 | `0x0b6791C168ffBF52e82F5E862929Dbf505c3A46E` |
|
|
67
|
+
| Arc | 5042002 | `0xA90621B79d49c8E3A5eeEBcaaa839E2f886240C5` |
|
|
68
|
+
| Base | 84532 | `0xb6cdAE7ccfEE03e351694c63436D5c5c073aEF84` |
|
|
69
|
+
| Ethereum | 11155111 | `0x5A061604A1d94f4fa9939544707f6B200d6bB5cf` |
|
|
70
|
+
| Arbitrum | 421614 | `0x147C6D8cA1a4784Ed76d98b0E3CcA41C38a49A5f` |
|
|
71
71
|
| Tempo | 42431 | `0xE559fB936C69c46E216bf61B07C16bF1a6d444aa` |
|
|
72
72
|
|
|
73
73
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fairblock/stabletrust",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
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/constants.js
CHANGED
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
* Contract ABIs and Constants
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
export const PREDICATE_ATTESTATION_SIGNATURE =
|
|
6
|
+
"(string,uint256,address,bytes)";
|
|
7
|
+
const PREDICATE_ATTESTATION_ABI =
|
|
8
|
+
"(string uuid,uint256 expiration,address attester,bytes signature) attestation";
|
|
9
|
+
|
|
10
|
+
export const CREATE_CONFIDENTIAL_ACCOUNT_SIGNATURE =
|
|
11
|
+
`createConfidentialAccount(bytes,${PREDICATE_ATTESTATION_SIGNATURE})`;
|
|
12
|
+
export const DEPOSIT_SIGNATURE =
|
|
13
|
+
`deposit(address,uint256,${PREDICATE_ATTESTATION_SIGNATURE})`;
|
|
7
14
|
const GET_ACCOUNT_CORE_SIGNATURE = "getAccountCore(address)";
|
|
8
15
|
const GET_AVAILABLE_SIGNATURE = "getAvailable(address,address)";
|
|
9
16
|
const GET_PENDING_SIGNATURE = "getPending(address,address)";
|
|
10
|
-
const APPLY_PENDING_SIGNATURE =
|
|
17
|
+
export const APPLY_PENDING_SIGNATURE =
|
|
18
|
+
`applyPending(${PREDICATE_ATTESTATION_SIGNATURE})`;
|
|
11
19
|
|
|
12
|
-
export const TRANSFER_CONFIDENTIAL_SIGNATURE =
|
|
13
|
-
|
|
20
|
+
export const TRANSFER_CONFIDENTIAL_SIGNATURE =
|
|
21
|
+
`transferConfidential(address,address,bytes,bool,${PREDICATE_ATTESTATION_SIGNATURE})`;
|
|
22
|
+
export const WITHDRAW_CONFIDENTIAL_SIGNATURE =
|
|
23
|
+
`withdraw(address,uint256,bytes,bool,${PREDICATE_ATTESTATION_SIGNATURE})`;
|
|
14
24
|
export const FEE_TOKEN_SIGNATURE = "feeToken()";
|
|
15
25
|
export const FEE_ACCOUNT_SIGNATURE = "feeAccount()";
|
|
16
26
|
export const NON_ANONYMOUS_TRANSFER_FEE_SIGNATURE = "nonAnonymousTransferFee()";
|
|
@@ -21,14 +31,14 @@ export const ANONYMOUS_INLINE_TRANSFER_FEE_SIGNATURE = "anonymousInlineTransferF
|
|
|
21
31
|
export const ANONYMOUS_WITHDRAW_FEE_PPM_SIGNATURE = "anonymousWithdrawFeePpm()";
|
|
22
32
|
|
|
23
33
|
export const CONTRACT_ABI = [
|
|
24
|
-
`function
|
|
25
|
-
`function
|
|
34
|
+
`function createConfidentialAccount(bytes elgamalPubkey,${PREDICATE_ATTESTATION_ABI}) external`,
|
|
35
|
+
`function deposit(address token,uint256 plainAmount,${PREDICATE_ATTESTATION_ABI}) external`,
|
|
26
36
|
`function ${GET_ACCOUNT_CORE_SIGNATURE} external view returns ((bool exists, bool finalized, bool pendingAction, uint256 txId, bytes elgamalPubkey, uint64 pendingCreditCounter))`,
|
|
27
37
|
`function ${GET_AVAILABLE_SIGNATURE} external view returns (bytes c1, bytes c2)`,
|
|
28
38
|
`function ${GET_PENDING_SIGNATURE} external view returns (bytes c1, bytes c2)`,
|
|
29
|
-
`function
|
|
30
|
-
`function
|
|
31
|
-
`function ${
|
|
39
|
+
`function transferConfidential(address recipient,address token,bytes proof,bool offchainZKP,${PREDICATE_ATTESTATION_ABI}) external payable`,
|
|
40
|
+
`function withdraw(address token,uint256 plainAmount,bytes proof,bool offchainZKP,${PREDICATE_ATTESTATION_ABI}) external payable`,
|
|
41
|
+
`function applyPending(${PREDICATE_ATTESTATION_ABI}) external`,
|
|
32
42
|
`function ${FEE_TOKEN_SIGNATURE} external view returns (address)`,
|
|
33
43
|
`function ${FEE_ACCOUNT_SIGNATURE} external view returns (address)`,
|
|
34
44
|
`function ${NON_ANONYMOUS_TRANSFER_FEE_SIGNATURE} external view returns (uint256)`,
|
|
@@ -63,11 +73,11 @@ export const ERC20_ABI = [
|
|
|
63
73
|
export const TEMPO_FEE_TOKEN_ADDRESS =
|
|
64
74
|
"0x20c0000000000000000000000000000000000000";
|
|
65
75
|
export const STABLETRUST_CONTRACTS_BY_CHAIN_ID = Object.freeze({
|
|
66
|
-
2201: "
|
|
67
|
-
5042002: "
|
|
68
|
-
84532: "
|
|
69
|
-
11155111: "
|
|
70
|
-
421614: "
|
|
76
|
+
2201: "0x0b6791C168ffBF52e82F5E862929Dbf505c3A46E", //Stable
|
|
77
|
+
5042002: "0xA90621B79d49c8E3A5eeEBcaaa839E2f886240C5", //Arc
|
|
78
|
+
84532: "0xb6cdAE7ccfEE03e351694c63436D5c5c073aEF84", //Base
|
|
79
|
+
11155111: "0x5A061604A1d94f4fa9939544707f6B200d6bB5cf", //Ethereum
|
|
80
|
+
421614: "0x147C6D8cA1a4784Ed76d98b0E3CcA41C38a49A5f", //Arbitrum Sepolia
|
|
71
81
|
42431: "0xE559fB936C69c46E216bf61B07C16bF1a6d444aa", //Tempo
|
|
72
82
|
42161: "0xCAA6384D5Ac8b9111D482dd676BB890f2b6e9513", //Arbitrum One (mainnet)
|
|
73
83
|
});
|
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(
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export {
|
|
|
3
3
|
ControlledConfidentialTransferClient,
|
|
4
4
|
AnonymousTransferClient,
|
|
5
5
|
WITHDRAW_FEE_PPM_DENOMINATOR,
|
|
6
|
-
} from "./fee-aware-clients.js";
|
|
6
|
+
} from "./predicate-fee-aware-clients.js";
|
|
7
7
|
export { deriveKeys, decryptCiphertext, combineCiphertext } from "./crypto.js";
|
|
8
8
|
export {
|
|
9
9
|
encodeTransferProof,
|
|
@@ -14,7 +14,11 @@ export {
|
|
|
14
14
|
export {
|
|
15
15
|
CONTRACT_ABI,
|
|
16
16
|
ERC20_ABI,
|
|
17
|
+
PREDICATE_ATTESTATION_SIGNATURE,
|
|
18
|
+
CREATE_CONFIDENTIAL_ACCOUNT_SIGNATURE,
|
|
19
|
+
DEPOSIT_SIGNATURE,
|
|
17
20
|
TRANSFER_CONFIDENTIAL_SIGNATURE,
|
|
21
|
+
APPLY_PENDING_SIGNATURE,
|
|
18
22
|
WITHDRAW_CONFIDENTIAL_SIGNATURE,
|
|
19
23
|
FEE_TOKEN_SIGNATURE,
|
|
20
24
|
FEE_ACCOUNT_SIGNATURE,
|
|
@@ -27,4 +31,9 @@ export {
|
|
|
27
31
|
STABLETRUST_CONTRACTS_BY_CHAIN_ID,
|
|
28
32
|
getStabletrustContractAddress,
|
|
29
33
|
} from "./constants.js";
|
|
34
|
+
export {
|
|
35
|
+
emptyPredicateAttestation,
|
|
36
|
+
injectPredicateAttestation,
|
|
37
|
+
isPredicateAttestation,
|
|
38
|
+
} from "./predicate-contract.js";
|
|
30
39
|
// Note: initializeWasm is now internal - WASM auto-initializes on first client use
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
|
|
3
|
+
const PREDICATE_BUSINESS_ARG_COUNTS = Object.freeze({
|
|
4
|
+
createConfidentialAccount: 1,
|
|
5
|
+
deposit: 2,
|
|
6
|
+
transferConfidential: 4,
|
|
7
|
+
applyPending: 0,
|
|
8
|
+
withdraw: 4,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const METHOD_HELPERS = [
|
|
12
|
+
"estimateGas",
|
|
13
|
+
"populateTransaction",
|
|
14
|
+
"send",
|
|
15
|
+
"staticCall",
|
|
16
|
+
"staticCallResult",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const PREDICATE_WRAPPED = Symbol("stabletrust.predicateWrappedContract");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Empty Predicate attestation used while on-chain Predicate enforcement is off.
|
|
23
|
+
* A fresh array is returned for each call so callers/ethers never share mutable
|
|
24
|
+
* tuple state between transactions.
|
|
25
|
+
*/
|
|
26
|
+
export function emptyPredicateAttestation() {
|
|
27
|
+
return ["", 0n, ethers.ZeroAddress, "0x"];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isPredicateAttestation(value) {
|
|
31
|
+
if (Array.isArray(value)) return value.length === 4;
|
|
32
|
+
if (!value || typeof value !== "object") return false;
|
|
33
|
+
return (
|
|
34
|
+
"uuid" in value &&
|
|
35
|
+
"expiration" in value &&
|
|
36
|
+
"attester" in value &&
|
|
37
|
+
"signature" in value
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Convert the pre-Predicate SDK call shape into the upgraded Diamond call shape.
|
|
43
|
+
* Existing SDK methods may optionally append an ethers transaction-overrides
|
|
44
|
+
* object, so the attestation must be inserted immediately before overrides.
|
|
45
|
+
*
|
|
46
|
+
* Calls that already contain an explicit attestation are left unchanged. This
|
|
47
|
+
* makes the wrapped contract usable directly by applications that obtain a real
|
|
48
|
+
* Predicate attestation later.
|
|
49
|
+
*/
|
|
50
|
+
export function injectPredicateAttestation(methodName, args) {
|
|
51
|
+
const businessArgCount = PREDICATE_BUSINESS_ARG_COUNTS[methodName];
|
|
52
|
+
if (businessArgCount === undefined) return args;
|
|
53
|
+
|
|
54
|
+
if (args.length === businessArgCount) {
|
|
55
|
+
return [...args, emptyPredicateAttestation()];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (args.length === businessArgCount + 1) {
|
|
59
|
+
if (isPredicateAttestation(args[businessArgCount])) {
|
|
60
|
+
return args;
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
...args.slice(0, businessArgCount),
|
|
64
|
+
emptyPredicateAttestation(),
|
|
65
|
+
args[businessArgCount],
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
args.length === businessArgCount + 2 &&
|
|
71
|
+
isPredicateAttestation(args[businessArgCount])
|
|
72
|
+
) {
|
|
73
|
+
return args;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Let ethers surface its normal argument-count/type error for malformed calls.
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function wrapPredicateMethod(methodName, method) {
|
|
81
|
+
const wrapped = (...args) =>
|
|
82
|
+
method(...injectPredicateAttestation(methodName, args));
|
|
83
|
+
|
|
84
|
+
for (const helperName of METHOD_HELPERS) {
|
|
85
|
+
const helper = method?.[helperName];
|
|
86
|
+
if (typeof helper === "function") {
|
|
87
|
+
wrapped[helperName] = (...args) =>
|
|
88
|
+
helper(...injectPredicateAttestation(methodName, args));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return wrapped;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Wrap an ethers Contract so the SDK's existing high-level methods keep their
|
|
97
|
+
* public API while targeting the upgraded Attestation-bearing selectors.
|
|
98
|
+
*
|
|
99
|
+
* `connect()` is wrapped recursively because every state-changing SDK path uses
|
|
100
|
+
* `this.contract.connect(wallet)` before invoking the contract.
|
|
101
|
+
*/
|
|
102
|
+
export function wrapPredicateContract(contract) {
|
|
103
|
+
if (!contract || contract[PREDICATE_WRAPPED]) return contract;
|
|
104
|
+
|
|
105
|
+
return new Proxy(contract, {
|
|
106
|
+
get(target, prop) {
|
|
107
|
+
if (prop === PREDICATE_WRAPPED) return true;
|
|
108
|
+
|
|
109
|
+
if (prop === "connect") {
|
|
110
|
+
return (runner) => wrapPredicateContract(target.connect(runner));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
typeof prop === "string" &&
|
|
115
|
+
Object.prototype.hasOwnProperty.call(PREDICATE_BUSINESS_ARG_COUNTS, prop)
|
|
116
|
+
) {
|
|
117
|
+
const method = target[prop];
|
|
118
|
+
if (typeof method === "function") {
|
|
119
|
+
return wrapPredicateMethod(prop, method);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Reflect.get(target, prop, target);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfidentialTransferClient as FeeAwareConfidentialTransferClient,
|
|
3
|
+
ControlledConfidentialTransferClient as FeeAwareControlledConfidentialTransferClient,
|
|
4
|
+
AnonymousTransferClient,
|
|
5
|
+
WITHDRAW_FEE_PPM_DENOMINATOR,
|
|
6
|
+
} from "./fee-aware-clients.js";
|
|
7
|
+
import { wrapPredicateContract } from "./predicate-contract.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Public non-anonymous clients target the upgraded ConfidentialMirror ABI.
|
|
11
|
+
* Existing SDK methods still call the pre-Predicate argument shape internally;
|
|
12
|
+
* the wrapped ethers Contract inserts an empty attestation while enforcement is
|
|
13
|
+
* disabled, without changing the high-level SDK API.
|
|
14
|
+
*/
|
|
15
|
+
export class ConfidentialTransferClient extends FeeAwareConfidentialTransferClient {
|
|
16
|
+
constructor(...args) {
|
|
17
|
+
super(...args);
|
|
18
|
+
this.contract = wrapPredicateContract(this.contract);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ControlledConfidentialTransferClient extends FeeAwareControlledConfidentialTransferClient {
|
|
23
|
+
constructor(...args) {
|
|
24
|
+
super(...args);
|
|
25
|
+
this.contract = wrapPredicateContract(this.contract);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Anonymous flows are intentionally not Predicate-gated and must remain
|
|
30
|
+
// byte-for-byte/API compatible with the existing anonymous client behavior.
|
|
31
|
+
export { AnonymousTransferClient, WITHDRAW_FEE_PPM_DENOMINATOR };
|