@zama-fhe/relayer-sdk 0.2.0-6 → 0.3.0-0
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 +1 -2
- package/bundle/kms_lib_bg.wasm +0 -0
- package/bundle/relayer-sdk-js.js +2974 -2959
- package/bundle/relayer-sdk-js.umd.cjs +9 -9
- package/lib/kms_lib_bg.wasm +0 -0
- package/lib/node.cjs +37 -16
- package/lib/node.d.ts +52 -1
- package/lib/node.js +37 -16
- package/lib/web.d.ts +52 -1
- package/lib/web.js +37 -16
- package/package.json +9 -9
package/lib/kms_lib_bg.wasm
CHANGED
|
Binary file
|
package/lib/node.cjs
CHANGED
|
@@ -57,6 +57,32 @@ const bytesToBigInt = function (byteArray) {
|
|
|
57
57
|
return BigInt(`0x${hex}`);
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
function setAuth(init, auth) {
|
|
61
|
+
if (auth) {
|
|
62
|
+
switch (auth.__type) {
|
|
63
|
+
case 'BearerToken':
|
|
64
|
+
init.headers['Authorization'] =
|
|
65
|
+
`Bearer ${auth.token}`;
|
|
66
|
+
break;
|
|
67
|
+
case 'ApiKeyHeader':
|
|
68
|
+
init.headers[auth.header || 'x-api-key'] =
|
|
69
|
+
auth.value;
|
|
70
|
+
break;
|
|
71
|
+
case 'ApiKeyCookie':
|
|
72
|
+
if (typeof window !== 'undefined') {
|
|
73
|
+
document.cookie = `${auth.cookie || 'x-api-key'}=${auth.value}; path=/; SameSite=Lax; Secure; HttpOnly;`;
|
|
74
|
+
init.credentials = 'include';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
let cookie = `${auth.cookie || 'x-api-key'}=${auth.value};`;
|
|
78
|
+
init.headers['Cookie'] = cookie;
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return init;
|
|
84
|
+
}
|
|
85
|
+
|
|
60
86
|
function getErrorCause(e) {
|
|
61
87
|
if (e instanceof Error && typeof e.cause === 'object' && e.cause !== null) {
|
|
62
88
|
return e.cause;
|
|
@@ -238,14 +264,13 @@ function assertIsRelayerFetchResponseJson(json) {
|
|
|
238
264
|
}
|
|
239
265
|
}
|
|
240
266
|
async function fetchRelayerJsonRpcPost(relayerOperation, url, payload, options) {
|
|
241
|
-
const init = {
|
|
267
|
+
const init = setAuth({
|
|
242
268
|
method: 'POST',
|
|
243
269
|
headers: {
|
|
244
270
|
'Content-Type': 'application/json',
|
|
245
|
-
...(options?.apiKey && { 'x-api-key': options.apiKey }),
|
|
246
271
|
},
|
|
247
272
|
body: JSON.stringify(payload),
|
|
248
|
-
};
|
|
273
|
+
}, options?.auth);
|
|
249
274
|
let response;
|
|
250
275
|
let json;
|
|
251
276
|
try {
|
|
@@ -615,7 +640,7 @@ function checkDeadlineValidity(startTimestamp, durationDays) {
|
|
|
615
640
|
throw Error('User decrypt request has expired');
|
|
616
641
|
}
|
|
617
642
|
}
|
|
618
|
-
const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider,
|
|
643
|
+
const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, privateKey, publicKey, signature, contractAddresses, userAddress, startTimestamp, durationDays, options) => {
|
|
619
644
|
const extraData = '0x00';
|
|
620
645
|
let pubKey;
|
|
621
646
|
let privKey;
|
|
@@ -674,7 +699,7 @@ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContra
|
|
|
674
699
|
publicKey: publicKeySanitized,
|
|
675
700
|
extraData,
|
|
676
701
|
};
|
|
677
|
-
const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, options);
|
|
702
|
+
const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, instanceOptions ?? options);
|
|
678
703
|
// assume the KMS Signers have the correct order
|
|
679
704
|
let indexedKmsSigners = kmsSigners.map((signer, index) => {
|
|
680
705
|
return TKMS.new_server_id_addr(index + 1, signer);
|
|
@@ -956,7 +981,7 @@ function isFhevmRelayerInputProofResponse(json) {
|
|
|
956
981
|
return (response.signatures.every((s) => typeof s === 'string') &&
|
|
957
982
|
response.handles.every((h) => typeof h === 'string'));
|
|
958
983
|
}
|
|
959
|
-
const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners) => (contractAddress, userAddress) => {
|
|
984
|
+
const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners, instanceOptions) => (contractAddress, userAddress) => {
|
|
960
985
|
if (!ethers.isAddress(contractAddress)) {
|
|
961
986
|
throw new Error('Contract address is not a valid address.');
|
|
962
987
|
}
|
|
@@ -1019,7 +1044,7 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
|
|
|
1019
1044
|
contractChainId: ('0x' + chainId.toString(16)),
|
|
1020
1045
|
extraData,
|
|
1021
1046
|
};
|
|
1022
|
-
const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options);
|
|
1047
|
+
const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options ?? instanceOptions);
|
|
1023
1048
|
if (!isFhevmRelayerInputProofResponse(json)) {
|
|
1024
1049
|
throwRelayerInternalError('INPUT_PROOF', json);
|
|
1025
1050
|
}
|
|
@@ -1141,7 +1166,7 @@ function deserializeDecryptedResult(handles, decryptedResult) {
|
|
|
1141
1166
|
handles.forEach((handle, idx) => (results[handle] = rawValues[idx]));
|
|
1142
1167
|
return results;
|
|
1143
1168
|
}
|
|
1144
|
-
const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider,
|
|
1169
|
+
const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, options) => {
|
|
1145
1170
|
const extraData = '0x00';
|
|
1146
1171
|
const acl = new ethers.ethers.Contract(aclContractAddress, aclABI, provider);
|
|
1147
1172
|
let handles;
|
|
@@ -1166,7 +1191,7 @@ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, veri
|
|
|
1166
1191
|
ciphertextHandles: handles,
|
|
1167
1192
|
extraData,
|
|
1168
1193
|
};
|
|
1169
|
-
const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options);
|
|
1194
|
+
const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options ?? instanceOptions);
|
|
1170
1195
|
// verify signatures on decryption:
|
|
1171
1196
|
const domain = {
|
|
1172
1197
|
name: 'Decryption',
|
|
@@ -1253,7 +1278,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1253
1278
|
DelegatedUserDecryptRequestVerification: [
|
|
1254
1279
|
{ name: 'publicKey', type: 'bytes' },
|
|
1255
1280
|
{ name: 'contractAddresses', type: 'address[]' },
|
|
1256
|
-
{ name: 'contractsChainId', type: 'uint256' },
|
|
1257
1281
|
{ name: 'startTimestamp', type: 'uint256' },
|
|
1258
1282
|
{ name: 'durationDays', type: 'uint256' },
|
|
1259
1283
|
{ name: 'extraData', type: 'bytes' },
|
|
@@ -1268,7 +1292,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1268
1292
|
message: {
|
|
1269
1293
|
publicKey: formattedPublicKey,
|
|
1270
1294
|
contractAddresses,
|
|
1271
|
-
contractsChainId,
|
|
1272
1295
|
startTimestamp: formattedStartTimestamp,
|
|
1273
1296
|
durationDays: formattedDurationDays,
|
|
1274
1297
|
extraData,
|
|
@@ -1282,7 +1305,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1282
1305
|
UserDecryptRequestVerification: [
|
|
1283
1306
|
{ name: 'publicKey', type: 'bytes' },
|
|
1284
1307
|
{ name: 'contractAddresses', type: 'address[]' },
|
|
1285
|
-
{ name: 'contractsChainId', type: 'uint256' },
|
|
1286
1308
|
{ name: 'startTimestamp', type: 'uint256' },
|
|
1287
1309
|
{ name: 'durationDays', type: 'uint256' },
|
|
1288
1310
|
{ name: 'extraData', type: 'bytes' },
|
|
@@ -1293,7 +1315,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1293
1315
|
message: {
|
|
1294
1316
|
publicKey: formattedPublicKey,
|
|
1295
1317
|
contractAddresses,
|
|
1296
|
-
contractsChainId,
|
|
1297
1318
|
startTimestamp: formattedStartTimestamp,
|
|
1298
1319
|
durationDays: formattedDurationDays,
|
|
1299
1320
|
extraData,
|
|
@@ -1330,7 +1351,7 @@ const SepoliaConfig = {
|
|
|
1330
1351
|
relayerUrl: 'https://relayer.testnet.zama.cloud',
|
|
1331
1352
|
};
|
|
1332
1353
|
const createInstance = async (config) => {
|
|
1333
|
-
const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, } = config;
|
|
1354
|
+
const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, auth, } = config;
|
|
1334
1355
|
if (!kmsContractAddress || !ethers.isAddress(kmsContractAddress)) {
|
|
1335
1356
|
throw new Error('KMS contract address is not valid or empty');
|
|
1336
1357
|
}
|
|
@@ -1362,8 +1383,8 @@ const createInstance = async (config) => {
|
|
|
1362
1383
|
createEncryptedInput: createRelayerEncryptedInput(aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, cleanURL(config.relayerUrl), publicKeyData.publicKey, publicParamsData, coprocessorSigners, thresholdCoprocessorSigners),
|
|
1363
1384
|
generateKeypair,
|
|
1364
1385
|
createEIP712: createEIP712(verifyingContractAddressDecryption, chainId),
|
|
1365
|
-
publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
|
|
1366
|
-
userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
|
|
1386
|
+
publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
|
|
1387
|
+
userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
|
|
1367
1388
|
getPublicKey: () => publicKeyData.publicKey
|
|
1368
1389
|
? {
|
|
1369
1390
|
publicKey: publicKeyData.publicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
|
package/lib/node.d.ts
CHANGED
|
@@ -3,6 +3,56 @@ import { Eip1193Provider } from 'ethers';
|
|
|
3
3
|
import { TfheClientKey } from 'node-tfhe';
|
|
4
4
|
import { TfheCompactPublicKey } from 'node-tfhe';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Custom cookie authentication
|
|
8
|
+
*/
|
|
9
|
+
declare type ApiKeyCookie = {
|
|
10
|
+
__type: 'ApiKeyCookie';
|
|
11
|
+
/**
|
|
12
|
+
* The cookie name. The default value is `x-api-key`.
|
|
13
|
+
*/
|
|
14
|
+
cookie?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The API key.
|
|
17
|
+
*/
|
|
18
|
+
value: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Custom header authentication
|
|
23
|
+
*/
|
|
24
|
+
declare type ApiKeyHeader = {
|
|
25
|
+
__type: 'ApiKeyHeader';
|
|
26
|
+
/**
|
|
27
|
+
* The header name. The default value is `x-api-key`.
|
|
28
|
+
*/
|
|
29
|
+
header?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The API key.
|
|
32
|
+
*/
|
|
33
|
+
value: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set the authentication method for the request. The default is no authentication.
|
|
38
|
+
* It supports:
|
|
39
|
+
* - Bearer Token
|
|
40
|
+
* - Custom header
|
|
41
|
+
* - Custom cookie
|
|
42
|
+
*/
|
|
43
|
+
declare type Auth = BearerToken | ApiKeyHeader | ApiKeyCookie;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Bearer Token Authentication
|
|
47
|
+
*/
|
|
48
|
+
declare type BearerToken = {
|
|
49
|
+
__type: 'BearerToken';
|
|
50
|
+
/**
|
|
51
|
+
* The Bearer token.
|
|
52
|
+
*/
|
|
53
|
+
token: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
6
56
|
/**
|
|
7
57
|
* Creates an EIP712 structure specifically for user decrypt requests
|
|
8
58
|
*
|
|
@@ -99,6 +149,7 @@ export declare type FhevmInstanceConfig = {
|
|
|
99
149
|
data: Uint8Array | null;
|
|
100
150
|
id: string | null;
|
|
101
151
|
};
|
|
152
|
+
auth?: Auth;
|
|
102
153
|
};
|
|
103
154
|
|
|
104
155
|
export declare const generateKeypair: () => {
|
|
@@ -133,7 +184,7 @@ export declare type RelayerEncryptedInput = {
|
|
|
133
184
|
addAddress: (value: string) => RelayerEncryptedInput;
|
|
134
185
|
getBits: () => EncryptionTypes[];
|
|
135
186
|
encrypt: (options?: {
|
|
136
|
-
|
|
187
|
+
auth?: Auth;
|
|
137
188
|
}) => Promise<{
|
|
138
189
|
handles: Uint8Array[];
|
|
139
190
|
inputProof: Uint8Array;
|
package/lib/node.js
CHANGED
|
@@ -36,6 +36,32 @@ const bytesToBigInt = function (byteArray) {
|
|
|
36
36
|
return BigInt(`0x${hex}`);
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
function setAuth(init, auth) {
|
|
40
|
+
if (auth) {
|
|
41
|
+
switch (auth.__type) {
|
|
42
|
+
case 'BearerToken':
|
|
43
|
+
init.headers['Authorization'] =
|
|
44
|
+
`Bearer ${auth.token}`;
|
|
45
|
+
break;
|
|
46
|
+
case 'ApiKeyHeader':
|
|
47
|
+
init.headers[auth.header || 'x-api-key'] =
|
|
48
|
+
auth.value;
|
|
49
|
+
break;
|
|
50
|
+
case 'ApiKeyCookie':
|
|
51
|
+
if (typeof window !== 'undefined') {
|
|
52
|
+
document.cookie = `${auth.cookie || 'x-api-key'}=${auth.value}; path=/; SameSite=Lax; Secure; HttpOnly;`;
|
|
53
|
+
init.credentials = 'include';
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
let cookie = `${auth.cookie || 'x-api-key'}=${auth.value};`;
|
|
57
|
+
init.headers['Cookie'] = cookie;
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return init;
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
function getErrorCause(e) {
|
|
40
66
|
if (e instanceof Error && typeof e.cause === 'object' && e.cause !== null) {
|
|
41
67
|
return e.cause;
|
|
@@ -217,14 +243,13 @@ function assertIsRelayerFetchResponseJson(json) {
|
|
|
217
243
|
}
|
|
218
244
|
}
|
|
219
245
|
async function fetchRelayerJsonRpcPost(relayerOperation, url, payload, options) {
|
|
220
|
-
const init = {
|
|
246
|
+
const init = setAuth({
|
|
221
247
|
method: 'POST',
|
|
222
248
|
headers: {
|
|
223
249
|
'Content-Type': 'application/json',
|
|
224
|
-
...(options?.apiKey && { 'x-api-key': options.apiKey }),
|
|
225
250
|
},
|
|
226
251
|
body: JSON.stringify(payload),
|
|
227
|
-
};
|
|
252
|
+
}, options?.auth);
|
|
228
253
|
let response;
|
|
229
254
|
let json;
|
|
230
255
|
try {
|
|
@@ -594,7 +619,7 @@ function checkDeadlineValidity(startTimestamp, durationDays) {
|
|
|
594
619
|
throw Error('User decrypt request has expired');
|
|
595
620
|
}
|
|
596
621
|
}
|
|
597
|
-
const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider,
|
|
622
|
+
const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, privateKey, publicKey, signature, contractAddresses, userAddress, startTimestamp, durationDays, options) => {
|
|
598
623
|
const extraData = '0x00';
|
|
599
624
|
let pubKey;
|
|
600
625
|
let privKey;
|
|
@@ -653,7 +678,7 @@ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContra
|
|
|
653
678
|
publicKey: publicKeySanitized,
|
|
654
679
|
extraData,
|
|
655
680
|
};
|
|
656
|
-
const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, options);
|
|
681
|
+
const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, instanceOptions ?? options);
|
|
657
682
|
// assume the KMS Signers have the correct order
|
|
658
683
|
let indexedKmsSigners = kmsSigners.map((signer, index) => {
|
|
659
684
|
return TKMS.new_server_id_addr(index + 1, signer);
|
|
@@ -935,7 +960,7 @@ function isFhevmRelayerInputProofResponse(json) {
|
|
|
935
960
|
return (response.signatures.every((s) => typeof s === 'string') &&
|
|
936
961
|
response.handles.every((h) => typeof h === 'string'));
|
|
937
962
|
}
|
|
938
|
-
const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners) => (contractAddress, userAddress) => {
|
|
963
|
+
const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners, instanceOptions) => (contractAddress, userAddress) => {
|
|
939
964
|
if (!isAddress(contractAddress)) {
|
|
940
965
|
throw new Error('Contract address is not a valid address.');
|
|
941
966
|
}
|
|
@@ -998,7 +1023,7 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
|
|
|
998
1023
|
contractChainId: ('0x' + chainId.toString(16)),
|
|
999
1024
|
extraData,
|
|
1000
1025
|
};
|
|
1001
|
-
const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options);
|
|
1026
|
+
const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options ?? instanceOptions);
|
|
1002
1027
|
if (!isFhevmRelayerInputProofResponse(json)) {
|
|
1003
1028
|
throwRelayerInternalError('INPUT_PROOF', json);
|
|
1004
1029
|
}
|
|
@@ -1120,7 +1145,7 @@ function deserializeDecryptedResult(handles, decryptedResult) {
|
|
|
1120
1145
|
handles.forEach((handle, idx) => (results[handle] = rawValues[idx]));
|
|
1121
1146
|
return results;
|
|
1122
1147
|
}
|
|
1123
|
-
const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider,
|
|
1148
|
+
const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, options) => {
|
|
1124
1149
|
const extraData = '0x00';
|
|
1125
1150
|
const acl = new ethers.Contract(aclContractAddress, aclABI, provider);
|
|
1126
1151
|
let handles;
|
|
@@ -1145,7 +1170,7 @@ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, veri
|
|
|
1145
1170
|
ciphertextHandles: handles,
|
|
1146
1171
|
extraData,
|
|
1147
1172
|
};
|
|
1148
|
-
const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options);
|
|
1173
|
+
const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options ?? instanceOptions);
|
|
1149
1174
|
// verify signatures on decryption:
|
|
1150
1175
|
const domain = {
|
|
1151
1176
|
name: 'Decryption',
|
|
@@ -1232,7 +1257,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1232
1257
|
DelegatedUserDecryptRequestVerification: [
|
|
1233
1258
|
{ name: 'publicKey', type: 'bytes' },
|
|
1234
1259
|
{ name: 'contractAddresses', type: 'address[]' },
|
|
1235
|
-
{ name: 'contractsChainId', type: 'uint256' },
|
|
1236
1260
|
{ name: 'startTimestamp', type: 'uint256' },
|
|
1237
1261
|
{ name: 'durationDays', type: 'uint256' },
|
|
1238
1262
|
{ name: 'extraData', type: 'bytes' },
|
|
@@ -1247,7 +1271,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1247
1271
|
message: {
|
|
1248
1272
|
publicKey: formattedPublicKey,
|
|
1249
1273
|
contractAddresses,
|
|
1250
|
-
contractsChainId,
|
|
1251
1274
|
startTimestamp: formattedStartTimestamp,
|
|
1252
1275
|
durationDays: formattedDurationDays,
|
|
1253
1276
|
extraData,
|
|
@@ -1261,7 +1284,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1261
1284
|
UserDecryptRequestVerification: [
|
|
1262
1285
|
{ name: 'publicKey', type: 'bytes' },
|
|
1263
1286
|
{ name: 'contractAddresses', type: 'address[]' },
|
|
1264
|
-
{ name: 'contractsChainId', type: 'uint256' },
|
|
1265
1287
|
{ name: 'startTimestamp', type: 'uint256' },
|
|
1266
1288
|
{ name: 'durationDays', type: 'uint256' },
|
|
1267
1289
|
{ name: 'extraData', type: 'bytes' },
|
|
@@ -1272,7 +1294,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
1272
1294
|
message: {
|
|
1273
1295
|
publicKey: formattedPublicKey,
|
|
1274
1296
|
contractAddresses,
|
|
1275
|
-
contractsChainId,
|
|
1276
1297
|
startTimestamp: formattedStartTimestamp,
|
|
1277
1298
|
durationDays: formattedDurationDays,
|
|
1278
1299
|
extraData,
|
|
@@ -1309,7 +1330,7 @@ const SepoliaConfig = {
|
|
|
1309
1330
|
relayerUrl: 'https://relayer.testnet.zama.cloud',
|
|
1310
1331
|
};
|
|
1311
1332
|
const createInstance = async (config) => {
|
|
1312
|
-
const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, } = config;
|
|
1333
|
+
const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, auth, } = config;
|
|
1313
1334
|
if (!kmsContractAddress || !isAddress(kmsContractAddress)) {
|
|
1314
1335
|
throw new Error('KMS contract address is not valid or empty');
|
|
1315
1336
|
}
|
|
@@ -1341,8 +1362,8 @@ const createInstance = async (config) => {
|
|
|
1341
1362
|
createEncryptedInput: createRelayerEncryptedInput(aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, cleanURL(config.relayerUrl), publicKeyData.publicKey, publicParamsData, coprocessorSigners, thresholdCoprocessorSigners),
|
|
1342
1363
|
generateKeypair,
|
|
1343
1364
|
createEIP712: createEIP712(verifyingContractAddressDecryption, chainId),
|
|
1344
|
-
publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
|
|
1345
|
-
userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
|
|
1365
|
+
publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
|
|
1366
|
+
userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
|
|
1346
1367
|
getPublicKey: () => publicKeyData.publicKey
|
|
1347
1368
|
? {
|
|
1348
1369
|
publicKey: publicKeyData.publicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
|
package/lib/web.d.ts
CHANGED
|
@@ -2,6 +2,56 @@ import { Eip1193Provider } from 'ethers';
|
|
|
2
2
|
import { InitInput as KMSInput } from 'tkms';
|
|
3
3
|
import { InitInput as TFHEInput } from 'tfhe';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Custom cookie authentication
|
|
7
|
+
*/
|
|
8
|
+
declare type ApiKeyCookie = {
|
|
9
|
+
__type: 'ApiKeyCookie';
|
|
10
|
+
/**
|
|
11
|
+
* The cookie name. The default value is `x-api-key`.
|
|
12
|
+
*/
|
|
13
|
+
cookie?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The API key.
|
|
16
|
+
*/
|
|
17
|
+
value: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Custom header authentication
|
|
22
|
+
*/
|
|
23
|
+
declare type ApiKeyHeader = {
|
|
24
|
+
__type: 'ApiKeyHeader';
|
|
25
|
+
/**
|
|
26
|
+
* The header name. The default value is `x-api-key`.
|
|
27
|
+
*/
|
|
28
|
+
header?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The API key.
|
|
31
|
+
*/
|
|
32
|
+
value: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set the authentication method for the request. The default is no authentication.
|
|
37
|
+
* It supports:
|
|
38
|
+
* - Bearer Token
|
|
39
|
+
* - Custom header
|
|
40
|
+
* - Custom cookie
|
|
41
|
+
*/
|
|
42
|
+
declare type Auth = BearerToken | ApiKeyHeader | ApiKeyCookie;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Bearer Token Authentication
|
|
46
|
+
*/
|
|
47
|
+
declare type BearerToken = {
|
|
48
|
+
__type: 'BearerToken';
|
|
49
|
+
/**
|
|
50
|
+
* The Bearer token.
|
|
51
|
+
*/
|
|
52
|
+
token: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
5
55
|
/**
|
|
6
56
|
* Creates an EIP712 structure specifically for user decrypt requests
|
|
7
57
|
*
|
|
@@ -90,6 +140,7 @@ export declare type FhevmInstanceConfig = {
|
|
|
90
140
|
data: Uint8Array | null;
|
|
91
141
|
id: string | null;
|
|
92
142
|
};
|
|
143
|
+
auth?: Auth;
|
|
93
144
|
};
|
|
94
145
|
|
|
95
146
|
export declare const generateKeypair: () => {
|
|
@@ -132,7 +183,7 @@ export declare type RelayerEncryptedInput = {
|
|
|
132
183
|
addAddress: (value: string) => RelayerEncryptedInput;
|
|
133
184
|
getBits: () => EncryptionTypes[];
|
|
134
185
|
encrypt: (options?: {
|
|
135
|
-
|
|
186
|
+
auth?: Auth;
|
|
136
187
|
}) => Promise<{
|
|
137
188
|
handles: Uint8Array[];
|
|
138
189
|
inputProof: Uint8Array;
|
package/lib/web.js
CHANGED
|
@@ -16167,6 +16167,32 @@ const bytesToBigInt = function (byteArray) {
|
|
|
16167
16167
|
return BigInt(`0x${hex}`);
|
|
16168
16168
|
};
|
|
16169
16169
|
|
|
16170
|
+
function setAuth(init, auth) {
|
|
16171
|
+
if (auth) {
|
|
16172
|
+
switch (auth.__type) {
|
|
16173
|
+
case 'BearerToken':
|
|
16174
|
+
init.headers['Authorization'] =
|
|
16175
|
+
`Bearer ${auth.token}`;
|
|
16176
|
+
break;
|
|
16177
|
+
case 'ApiKeyHeader':
|
|
16178
|
+
init.headers[auth.header || 'x-api-key'] =
|
|
16179
|
+
auth.value;
|
|
16180
|
+
break;
|
|
16181
|
+
case 'ApiKeyCookie':
|
|
16182
|
+
if (typeof window !== 'undefined') {
|
|
16183
|
+
document.cookie = `${auth.cookie || 'x-api-key'}=${auth.value}; path=/; SameSite=Lax; Secure; HttpOnly;`;
|
|
16184
|
+
init.credentials = 'include';
|
|
16185
|
+
}
|
|
16186
|
+
else {
|
|
16187
|
+
let cookie = `${auth.cookie || 'x-api-key'}=${auth.value};`;
|
|
16188
|
+
init.headers['Cookie'] = cookie;
|
|
16189
|
+
}
|
|
16190
|
+
break;
|
|
16191
|
+
}
|
|
16192
|
+
}
|
|
16193
|
+
return init;
|
|
16194
|
+
}
|
|
16195
|
+
|
|
16170
16196
|
function getErrorCause(e) {
|
|
16171
16197
|
if (e instanceof Error && typeof e.cause === 'object' && e.cause !== null) {
|
|
16172
16198
|
return e.cause;
|
|
@@ -16348,14 +16374,13 @@ function assertIsRelayerFetchResponseJson(json) {
|
|
|
16348
16374
|
}
|
|
16349
16375
|
}
|
|
16350
16376
|
async function fetchRelayerJsonRpcPost(relayerOperation, url, payload, options) {
|
|
16351
|
-
const init = {
|
|
16377
|
+
const init = setAuth({
|
|
16352
16378
|
method: 'POST',
|
|
16353
16379
|
headers: {
|
|
16354
16380
|
'Content-Type': 'application/json',
|
|
16355
|
-
...(options?.apiKey && { 'x-api-key': options.apiKey }),
|
|
16356
16381
|
},
|
|
16357
16382
|
body: JSON.stringify(payload),
|
|
16358
|
-
};
|
|
16383
|
+
}, options?.auth);
|
|
16359
16384
|
let response;
|
|
16360
16385
|
let json;
|
|
16361
16386
|
try {
|
|
@@ -16725,7 +16750,7 @@ function checkDeadlineValidity(startTimestamp, durationDays) {
|
|
|
16725
16750
|
throw Error('User decrypt request has expired');
|
|
16726
16751
|
}
|
|
16727
16752
|
}
|
|
16728
|
-
const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider,
|
|
16753
|
+
const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, privateKey, publicKey, signature, contractAddresses, userAddress, startTimestamp, durationDays, options) => {
|
|
16729
16754
|
const extraData = '0x00';
|
|
16730
16755
|
let pubKey;
|
|
16731
16756
|
let privKey;
|
|
@@ -16784,7 +16809,7 @@ const userDecryptRequest = (kmsSigners, gatewayChainId, chainId, verifyingContra
|
|
|
16784
16809
|
publicKey: publicKeySanitized,
|
|
16785
16810
|
extraData,
|
|
16786
16811
|
};
|
|
16787
|
-
const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, options);
|
|
16812
|
+
const json = await fetchRelayerJsonRpcPost('USER_DECRYPT', `${relayerUrl}/v1/user-decrypt`, payloadForRequest, instanceOptions ?? options);
|
|
16788
16813
|
// assume the KMS Signers have the correct order
|
|
16789
16814
|
let indexedKmsSigners = kmsSigners.map((signer, index) => {
|
|
16790
16815
|
return TKMS.new_server_id_addr(index + 1, signer);
|
|
@@ -17066,7 +17091,7 @@ function isFhevmRelayerInputProofResponse(json) {
|
|
|
17066
17091
|
return (response.signatures.every((s) => typeof s === 'string') &&
|
|
17067
17092
|
response.handles.every((h) => typeof h === 'string'));
|
|
17068
17093
|
}
|
|
17069
|
-
const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners) => (contractAddress, userAddress) => {
|
|
17094
|
+
const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, relayerUrl, tfheCompactPublicKey, publicParams, coprocessorSigners, thresholdCoprocessorSigners, instanceOptions) => (contractAddress, userAddress) => {
|
|
17070
17095
|
if (!isAddress(contractAddress)) {
|
|
17071
17096
|
throw new Error('Contract address is not a valid address.');
|
|
17072
17097
|
}
|
|
@@ -17129,7 +17154,7 @@ const createRelayerEncryptedInput = (aclContractAddress, verifyingContractAddres
|
|
|
17129
17154
|
contractChainId: ('0x' + chainId.toString(16)),
|
|
17130
17155
|
extraData,
|
|
17131
17156
|
};
|
|
17132
|
-
const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options);
|
|
17157
|
+
const json = await fetchRelayerJsonRpcPost('INPUT_PROOF', `${relayerUrl}/v1/input-proof`, payload, options ?? instanceOptions);
|
|
17133
17158
|
if (!isFhevmRelayerInputProofResponse(json)) {
|
|
17134
17159
|
throwRelayerInternalError('INPUT_PROOF', json);
|
|
17135
17160
|
}
|
|
@@ -17251,7 +17276,7 @@ function deserializeDecryptedResult(handles, decryptedResult) {
|
|
|
17251
17276
|
handles.forEach((handle, idx) => (results[handle] = rawValues[idx]));
|
|
17252
17277
|
return results;
|
|
17253
17278
|
}
|
|
17254
|
-
const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider,
|
|
17279
|
+
const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, verifyingContractAddress, aclContractAddress, relayerUrl, provider, instanceOptions) => async (_handles, options) => {
|
|
17255
17280
|
const extraData = '0x00';
|
|
17256
17281
|
const acl = new ethers.Contract(aclContractAddress, aclABI, provider);
|
|
17257
17282
|
let handles;
|
|
@@ -17276,7 +17301,7 @@ const publicDecryptRequest = (kmsSigners, thresholdSigners, gatewayChainId, veri
|
|
|
17276
17301
|
ciphertextHandles: handles,
|
|
17277
17302
|
extraData,
|
|
17278
17303
|
};
|
|
17279
|
-
const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options);
|
|
17304
|
+
const json = await fetchRelayerJsonRpcPost('PUBLIC_DECRYPT', `${relayerUrl}/v1/public-decrypt`, payloadForRequest, options ?? instanceOptions);
|
|
17280
17305
|
// verify signatures on decryption:
|
|
17281
17306
|
const domain = {
|
|
17282
17307
|
name: 'Decryption',
|
|
@@ -17363,7 +17388,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
17363
17388
|
DelegatedUserDecryptRequestVerification: [
|
|
17364
17389
|
{ name: 'publicKey', type: 'bytes' },
|
|
17365
17390
|
{ name: 'contractAddresses', type: 'address[]' },
|
|
17366
|
-
{ name: 'contractsChainId', type: 'uint256' },
|
|
17367
17391
|
{ name: 'startTimestamp', type: 'uint256' },
|
|
17368
17392
|
{ name: 'durationDays', type: 'uint256' },
|
|
17369
17393
|
{ name: 'extraData', type: 'bytes' },
|
|
@@ -17378,7 +17402,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
17378
17402
|
message: {
|
|
17379
17403
|
publicKey: formattedPublicKey,
|
|
17380
17404
|
contractAddresses,
|
|
17381
|
-
contractsChainId,
|
|
17382
17405
|
startTimestamp: formattedStartTimestamp,
|
|
17383
17406
|
durationDays: formattedDurationDays,
|
|
17384
17407
|
extraData,
|
|
@@ -17392,7 +17415,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
17392
17415
|
UserDecryptRequestVerification: [
|
|
17393
17416
|
{ name: 'publicKey', type: 'bytes' },
|
|
17394
17417
|
{ name: 'contractAddresses', type: 'address[]' },
|
|
17395
|
-
{ name: 'contractsChainId', type: 'uint256' },
|
|
17396
17418
|
{ name: 'startTimestamp', type: 'uint256' },
|
|
17397
17419
|
{ name: 'durationDays', type: 'uint256' },
|
|
17398
17420
|
{ name: 'extraData', type: 'bytes' },
|
|
@@ -17403,7 +17425,6 @@ const createEIP712 = (verifyingContract, contractsChainId) => (publicKey, contra
|
|
|
17403
17425
|
message: {
|
|
17404
17426
|
publicKey: formattedPublicKey,
|
|
17405
17427
|
contractAddresses,
|
|
17406
|
-
contractsChainId,
|
|
17407
17428
|
startTimestamp: formattedStartTimestamp,
|
|
17408
17429
|
durationDays: formattedDurationDays,
|
|
17409
17430
|
extraData,
|
|
@@ -17440,7 +17461,7 @@ const SepoliaConfig = {
|
|
|
17440
17461
|
relayerUrl: 'https://relayer.testnet.zama.cloud',
|
|
17441
17462
|
};
|
|
17442
17463
|
const createInstance = async (config) => {
|
|
17443
|
-
const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, } = config;
|
|
17464
|
+
const { verifyingContractAddressDecryption, verifyingContractAddressInputVerification, publicKey, kmsContractAddress, aclContractAddress, gatewayChainId, auth, } = config;
|
|
17444
17465
|
if (!kmsContractAddress || !isAddress(kmsContractAddress)) {
|
|
17445
17466
|
throw new Error('KMS contract address is not valid or empty');
|
|
17446
17467
|
}
|
|
@@ -17472,8 +17493,8 @@ const createInstance = async (config) => {
|
|
|
17472
17493
|
createEncryptedInput: createRelayerEncryptedInput(aclContractAddress, verifyingContractAddressInputVerification, chainId, gatewayChainId, cleanURL(config.relayerUrl), publicKeyData.publicKey, publicParamsData, coprocessorSigners, thresholdCoprocessorSigners),
|
|
17473
17494
|
generateKeypair,
|
|
17474
17495
|
createEIP712: createEIP712(verifyingContractAddressDecryption, chainId),
|
|
17475
|
-
publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
|
|
17476
|
-
userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider),
|
|
17496
|
+
publicDecrypt: publicDecryptRequest(kmsSigners, thresholdKMSSigners, gatewayChainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
|
|
17497
|
+
userDecrypt: userDecryptRequest(kmsSigners, gatewayChainId, chainId, verifyingContractAddressDecryption, aclContractAddress, cleanURL(config.relayerUrl), provider, auth && { auth }),
|
|
17477
17498
|
getPublicKey: () => publicKeyData.publicKey
|
|
17478
17499
|
? {
|
|
17479
17500
|
publicKey: publicKeyData.publicKey.safe_serialize(SERIALIZED_SIZE_LIMIT_PK),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zama-fhe/relayer-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-0",
|
|
4
4
|
"description": "fhevm Relayer SDK",
|
|
5
5
|
"main": "lib/node.js",
|
|
6
6
|
"types": "lib/node.d.ts",
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
"types": "./lib/node.d.ts"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
-
"engines": {
|
|
30
|
-
"node": ">=20"
|
|
31
|
-
},
|
|
32
29
|
"scripts": {
|
|
33
30
|
"lint": "eslint src/",
|
|
34
31
|
"generateKeys": "./generateKeys.js",
|
|
@@ -61,15 +58,15 @@
|
|
|
61
58
|
"ethers": "^6.15.0",
|
|
62
59
|
"fetch-retry": "^6.0.0",
|
|
63
60
|
"keccak": "^3.0.4",
|
|
64
|
-
"wasm-feature-detect": "^1.8.0",
|
|
65
61
|
"node-tfhe": "1.3.0",
|
|
62
|
+
"node-tkms": "^0.11.0",
|
|
66
63
|
"tfhe": "1.3.0",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
64
|
+
"tkms": "^0.11.0",
|
|
65
|
+
"wasm-feature-detect": "^1.8.0"
|
|
69
66
|
},
|
|
70
67
|
"devDependencies": {
|
|
71
|
-
"@fetch-mock/jest": "0.2.16",
|
|
72
68
|
"@fetch-mock/core": "0.7.1",
|
|
69
|
+
"@fetch-mock/jest": "0.2.16",
|
|
73
70
|
"@jest/globals": "30.0.4",
|
|
74
71
|
"@microsoft/api-extractor": "7.52.8",
|
|
75
72
|
"@rollup/plugin-alias": "5.1.1",
|
|
@@ -102,6 +99,9 @@
|
|
|
102
99
|
"typescript": "5.8.3",
|
|
103
100
|
"vite": "7.0.5",
|
|
104
101
|
"vite-plugin-node-polyfills": "0.24.0",
|
|
105
|
-
"vite-plugin-static-copy": "3.1.
|
|
102
|
+
"vite-plugin-static-copy": "^3.1.2"
|
|
103
|
+
},
|
|
104
|
+
"engines": {
|
|
105
|
+
"node": ">=22"
|
|
106
106
|
}
|
|
107
107
|
}
|