web3ql-client 1.2.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.
Files changed (86) hide show
  1. package/README.md +66 -0
  2. package/contracts/PublicKeyRegistry.sol +87 -0
  3. package/dist/src/access.d.ts +176 -0
  4. package/dist/src/access.d.ts.map +1 -0
  5. package/dist/src/access.js +283 -0
  6. package/dist/src/access.js.map +1 -0
  7. package/dist/src/batch.d.ts +107 -0
  8. package/dist/src/batch.d.ts.map +1 -0
  9. package/dist/src/batch.js +188 -0
  10. package/dist/src/batch.js.map +1 -0
  11. package/dist/src/cli.d.ts +40 -0
  12. package/dist/src/cli.d.ts.map +1 -0
  13. package/dist/src/cli.js +361 -0
  14. package/dist/src/cli.js.map +1 -0
  15. package/dist/src/constraints.d.ts +126 -0
  16. package/dist/src/constraints.d.ts.map +1 -0
  17. package/dist/src/constraints.js +192 -0
  18. package/dist/src/constraints.js.map +1 -0
  19. package/dist/src/crypto.d.ts +118 -0
  20. package/dist/src/crypto.d.ts.map +1 -0
  21. package/dist/src/crypto.js +192 -0
  22. package/dist/src/crypto.js.map +1 -0
  23. package/dist/src/factory-client.d.ts +106 -0
  24. package/dist/src/factory-client.d.ts.map +1 -0
  25. package/dist/src/factory-client.js +202 -0
  26. package/dist/src/factory-client.js.map +1 -0
  27. package/dist/src/index-cache.d.ts +156 -0
  28. package/dist/src/index-cache.d.ts.map +1 -0
  29. package/dist/src/index-cache.js +265 -0
  30. package/dist/src/index-cache.js.map +1 -0
  31. package/dist/src/index.d.ts +60 -0
  32. package/dist/src/index.d.ts.map +1 -0
  33. package/dist/src/index.js +60 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/migrations.d.ts +114 -0
  36. package/dist/src/migrations.d.ts.map +1 -0
  37. package/dist/src/migrations.js +173 -0
  38. package/dist/src/migrations.js.map +1 -0
  39. package/dist/src/model.d.ts +198 -0
  40. package/dist/src/model.d.ts.map +1 -0
  41. package/dist/src/model.js +379 -0
  42. package/dist/src/model.js.map +1 -0
  43. package/dist/src/query.d.ts +155 -0
  44. package/dist/src/query.d.ts.map +1 -0
  45. package/dist/src/query.js +386 -0
  46. package/dist/src/query.js.map +1 -0
  47. package/dist/src/registry.d.ts +45 -0
  48. package/dist/src/registry.d.ts.map +1 -0
  49. package/dist/src/registry.js +80 -0
  50. package/dist/src/registry.js.map +1 -0
  51. package/dist/src/schema-manager.d.ts +109 -0
  52. package/dist/src/schema-manager.d.ts.map +1 -0
  53. package/dist/src/schema-manager.js +259 -0
  54. package/dist/src/schema-manager.js.map +1 -0
  55. package/dist/src/table-client.d.ts +156 -0
  56. package/dist/src/table-client.d.ts.map +1 -0
  57. package/dist/src/table-client.js +292 -0
  58. package/dist/src/table-client.js.map +1 -0
  59. package/dist/src/typed-table.d.ts +159 -0
  60. package/dist/src/typed-table.d.ts.map +1 -0
  61. package/dist/src/typed-table.js +246 -0
  62. package/dist/src/typed-table.js.map +1 -0
  63. package/dist/src/types.d.ts +48 -0
  64. package/dist/src/types.d.ts.map +1 -0
  65. package/dist/src/types.js +222 -0
  66. package/dist/src/types.js.map +1 -0
  67. package/keyManager.js +337 -0
  68. package/package.json +38 -0
  69. package/src/access.ts +421 -0
  70. package/src/batch.ts +259 -0
  71. package/src/cli.ts +349 -0
  72. package/src/constraints.ts +283 -0
  73. package/src/crypto.ts +239 -0
  74. package/src/factory-client.ts +237 -0
  75. package/src/index-cache.ts +351 -0
  76. package/src/index.ts +171 -0
  77. package/src/migrations.ts +215 -0
  78. package/src/model.ts +538 -0
  79. package/src/query.ts +508 -0
  80. package/src/registry.ts +100 -0
  81. package/src/schema-manager.ts +301 -0
  82. package/src/table-client.ts +393 -0
  83. package/src/typed-table.ts +340 -0
  84. package/src/types.ts +284 -0
  85. package/tsconfig.json +22 -0
  86. package/walletUtils.js +204 -0
package/keyManager.js ADDED
@@ -0,0 +1,337 @@
1
+ /**
2
+ * @file keyManager.js
3
+ * @notice Key Generation & Management for Web3QL
4
+ *
5
+ * Implements three distinct key types:
6
+ * A. WALLET KEY — secp256k1 keypair used to sign blockchain transactions
7
+ * B. ENCRYPTION KEY — 32-byte symmetric key (NaCl secretbox) for record encryption
8
+ * C. ACCESS KEY — per-recipient key wrapper (NaCl box / X25519-XSalsa20-Poly1305)
9
+ *
10
+ * Wire formats (matching sdk/src/crypto.ts):
11
+ * secretbox blob: [ 24-byte nonce | ciphertext+MAC ]
12
+ * box blob: [ 24-byte nonce | encrypted key+MAC ]
13
+ *
14
+ * Security guarantees:
15
+ * ✓ All randomness from nacl.randomBytes — NEVER Math.random()
16
+ * ✓ Private keys / mnemonics are never logged or stored here
17
+ * ✓ NaCl secretbox (XSalsa20-Poly1305) — audited, side-channel resistant
18
+ * ✓ NaCl box (X25519-XSalsa20-Poly1305) for key wrapping
19
+ * ✓ X25519 keypair derived from Ethereum private key via SHA-256 seed
20
+ * (same derivation as sdk/src/crypto.ts deriveKeypair)
21
+ *
22
+ * Compatibility:
23
+ * Records encrypted via this module ARE cross-readable by sdk/src/crypto.ts
24
+ * because both use the same NaCl primitives and wire format.
25
+ */
26
+
27
+ import { ethers } from 'ethers';
28
+ import nacl from 'tweetnacl';
29
+ import { sha256 } from '@noble/hashes/sha256';
30
+
31
+ // ─────────────────────────────────────────────────────────────
32
+ // Constants
33
+ // ─────────────────────────────────────────────────────────────
34
+
35
+ const CELO_SEPOLIA_RPC = 'https://forno.celo-sepolia.celo-testnet.org'; // Celo Sepolia (chainId 11142220)
36
+
37
+ // ─────────────────────────────────────────────────────────────
38
+ // A. WALLET KEY
39
+ // ─────────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Generate a fresh random Ethereum wallet.
43
+ *
44
+ * ⚠ PRODUCTION WARNING — generateWallet() is for initial key provisioning only.
45
+ * The returned privateKey / mnemonic are shown ONCE and never stored here.
46
+ * Caller must persist them in a secure, encrypted keystore.
47
+ *
48
+ * @returns {{ address: string, privateKey: string, mnemonic: string|null, publicKey: string }}
49
+ */
50
+ export function generateWallet() {
51
+ if (process.env.NODE_ENV === 'production') {
52
+ console.warn(
53
+ '[Web3QL KeyManager] WARNING: generateWallet() called in production. ' +
54
+ 'The returned privateKey must be stored in a secure encrypted keystore ' +
55
+ 'and MUST NOT be logged, committed, or sent over the network.',
56
+ );
57
+ }
58
+
59
+ const wallet = ethers.Wallet.createRandom();
60
+ return {
61
+ address : wallet.address,
62
+ privateKey: wallet.privateKey,
63
+ mnemonic : wallet.mnemonic?.phrase ?? null,
64
+ publicKey : wallet.publicKey, // compressed secp256k1, 66-char hex
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Bind an existing private key to an ethers.Wallet connected to Celo RPC.
70
+ *
71
+ * @param {string} privateKey 64-char hex (with or without "0x" prefix)
72
+ * @returns {ethers.Wallet} Wallet instance connected to Celo JSON-RPC
73
+ * @throws {Error} Descriptive error if key is invalid
74
+ */
75
+ export function bindWallet(privateKey) {
76
+ if (!privateKey || typeof privateKey !== 'string') {
77
+ throw new Error('KeyManager.bindWallet: privateKey must be a non-empty string');
78
+ }
79
+
80
+ const stripped = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
81
+
82
+ if (stripped.length !== 64) {
83
+ throw new Error(
84
+ `KeyManager.bindWallet: privateKey must be 32 bytes (64 hex chars), got ${stripped.length} chars`,
85
+ );
86
+ }
87
+ if (!/^[0-9a-fA-F]{64}$/.test(stripped)) {
88
+ throw new Error('KeyManager.bindWallet: privateKey contains non-hex characters');
89
+ }
90
+
91
+ const provider = new ethers.JsonRpcProvider(
92
+ process.env.CELO_RPC_URL ?? CELO_SEPOLIA_RPC,
93
+ );
94
+
95
+ try {
96
+ return new ethers.Wallet(`0x${stripped}`, provider);
97
+ } catch (err) {
98
+ throw new Error(`KeyManager.bindWallet: invalid private key — ${err.message}`);
99
+ }
100
+ }
101
+
102
+ // ─────────────────────────────────────────────────────────────
103
+ // B. ENCRYPTION KEY (symmetric, per-record)
104
+ // ─────────────────────────────────────────────────────────────
105
+
106
+ /**
107
+ * Generate a cryptographically secure 32-byte symmetric encryption key.
108
+ * Uses nacl.randomBytes — always CSPRNG.
109
+ *
110
+ * @returns {string} 64-char lowercase hex string
111
+ */
112
+ export function generateEncryptionKey() {
113
+ return Buffer.from(nacl.randomBytes(32)).toString('hex');
114
+ }
115
+
116
+ // ─────────────────────────────────────────────────────────────
117
+ // C. RECORD ENCRYPTION / DECRYPTION (NaCl secretbox)
118
+ // ─────────────────────────────────────────────────────────────
119
+
120
+ /**
121
+ * Encrypt a plaintext string with NaCl secretbox (XSalsa20-Poly1305).
122
+ * Wire format: "0x" + hex( [ nonce(24B) | ciphertext+MAC ] )
123
+ * Compatible with encryptData() in sdk/src/crypto.ts.
124
+ *
125
+ * @param {string} data UTF-8 plaintext to encrypt
126
+ * @param {string} encryptionKey 64-char hex symmetric key
127
+ * @returns {string} "0x"-prefixed hex blob
128
+ */
129
+ export function encryptRecord(data, encryptionKey) {
130
+ if (typeof data !== 'string') {
131
+ throw new TypeError('KeyManager.encryptRecord: data must be a string');
132
+ }
133
+ const keyBuf = _parseHexKey(encryptionKey, 'encryptRecord');
134
+ const plaintext = new TextEncoder().encode(data);
135
+ const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
136
+ const encrypted = nacl.secretbox(plaintext, nonce, keyBuf);
137
+ if (!encrypted) throw new Error('KeyManager.encryptRecord: nacl.secretbox returned null');
138
+ const blob = new Uint8Array(nonce.length + encrypted.length);
139
+ blob.set(nonce);
140
+ blob.set(encrypted, nonce.length);
141
+ return '0x' + Buffer.from(blob).toString('hex');
142
+ }
143
+
144
+ /**
145
+ * Decrypt a secretbox blob (from encryptRecord or the chain).
146
+ * Throws if MAC check fails (tampered data or wrong key).
147
+ * Compatible with decryptData() in sdk/src/crypto.ts.
148
+ *
149
+ * @param {string} ciphertextHex "0x"-prefixed hex blob
150
+ * @param {string} encryptionKey 64-char hex symmetric key
151
+ * @returns {string} Decrypted UTF-8 plaintext
152
+ */
153
+ export function decryptRecord(ciphertextHex, encryptionKey) {
154
+ const keyBuf = _parseHexKey(encryptionKey, 'decryptRecord');
155
+ const clean = ciphertextHex.startsWith('0x') ? ciphertextHex.slice(2) : ciphertextHex;
156
+ const blob = new Uint8Array(Buffer.from(clean, 'hex'));
157
+ if (blob.length < nacl.secretbox.nonceLength) {
158
+ throw new Error('KeyManager.decryptRecord: blob too short to contain nonce');
159
+ }
160
+ const nonce = blob.subarray(0, nacl.secretbox.nonceLength);
161
+ const ct = blob.subarray(nacl.secretbox.nonceLength);
162
+ const plaintext = nacl.secretbox.open(ct, nonce, keyBuf);
163
+ if (!plaintext) {
164
+ throw new Error('KeyManager.decryptRecord: authentication failed — wrong key or tampered data');
165
+ }
166
+ return new TextDecoder().decode(plaintext);
167
+ }
168
+
169
+ // ─────────────────────────────────────────────────────────────
170
+ // D. KEY WRAPPING (NaCl box — X25519-XSalsa20-Poly1305)
171
+ // ─────────────────────────────────────────────────────────────
172
+
173
+ /**
174
+ * Wrap (encrypt) an encryption key for a specific recipient.
175
+ * Uses NaCl box (X25519 ECDH + XSalsa20-Poly1305) — matches encryptKeyForRecipient()
176
+ * in sdk/src/crypto.ts and the on-chain PublicKeyRegistry scheme.
177
+ *
178
+ * @param {string} encryptionKey 64-char hex symmetric key to wrap
179
+ * @param {string} recipientX25519PubKey 64-char hex X25519 public key (from PublicKeyRegistry)
180
+ * @param {string} senderEthPrivateKey 64-char hex Ethereum private key
181
+ * (X25519 key is derived from it via SHA-256)
182
+ * @returns {string} "0x"-prefixed hex: [ nonce(24B) | encrypted+MAC(48B) ] = 72 bytes
183
+ */
184
+ export function wrapKey(encryptionKey, recipientX25519PubKey, senderEthPrivateKey) {
185
+ const keyBuf = _parseHexKey(encryptionKey, 'wrapKey');
186
+ const recipPub = _parseX25519Key(recipientX25519PubKey, 'wrapKey');
187
+ const stripped = senderEthPrivateKey.startsWith('0x') ? senderEthPrivateKey.slice(2) : senderEthPrivateKey;
188
+ if (stripped.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(stripped)) {
189
+ throw new Error('KeyManager.wrapKey: senderEthPrivateKey must be 32 bytes (64 hex chars)');
190
+ }
191
+ const seed = sha256(new Uint8Array(Buffer.from(stripped, 'hex')));
192
+ const senderKp = nacl.box.keyPair.fromSecretKey(seed);
193
+ const nonce = nacl.randomBytes(nacl.box.nonceLength);
194
+ const encrypted = nacl.box(keyBuf, nonce, recipPub, senderKp.secretKey);
195
+ if (!encrypted) throw new Error('KeyManager.wrapKey: nacl.box returned null');
196
+ const blob = new Uint8Array(nonce.length + encrypted.length);
197
+ blob.set(nonce);
198
+ blob.set(encrypted, nonce.length);
199
+ return '0x' + Buffer.from(blob).toString('hex');
200
+ }
201
+
202
+ /**
203
+ * Unwrap (decrypt) a wrapped key.
204
+ * Compatible with decryptKeyFromSender() in sdk/src/crypto.ts.
205
+ *
206
+ * @param {string} wrappedKeyHex "0x"-prefixed hex from wrapKey()
207
+ * @param {string} senderX25519PubKey 64-char hex X25519 public key of the sender
208
+ * @param {string} recipientEthPrivateKey 64-char hex Ethereum private key of recipient
209
+ * (X25519 key is derived from it via SHA-256)
210
+ * @returns {string} 64-char hex symmetric key
211
+ */
212
+ export function unwrapKey(wrappedKeyHex, senderX25519PubKey, recipientEthPrivateKey) {
213
+ const senderPub = _parseX25519Key(senderX25519PubKey, 'unwrapKey');
214
+ const stripped = recipientEthPrivateKey.startsWith('0x') ? recipientEthPrivateKey.slice(2) : recipientEthPrivateKey;
215
+ if (stripped.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(stripped)) {
216
+ throw new Error('KeyManager.unwrapKey: recipientEthPrivateKey must be 32 bytes (64 hex chars)');
217
+ }
218
+ const seed = sha256(new Uint8Array(Buffer.from(stripped, 'hex')));
219
+ const recipKp = nacl.box.keyPair.fromSecretKey(seed);
220
+ const clean = wrappedKeyHex.startsWith('0x') ? wrappedKeyHex.slice(2) : wrappedKeyHex;
221
+ const blob = new Uint8Array(Buffer.from(clean, 'hex'));
222
+ if (blob.length < nacl.box.nonceLength) {
223
+ throw new Error('KeyManager.unwrapKey: wrapped key blob too short');
224
+ }
225
+ const nonce = blob.subarray(0, nacl.box.nonceLength);
226
+ const ciphertext = blob.subarray(nacl.box.nonceLength);
227
+ const symKey = nacl.box.open(ciphertext, nonce, senderPub, recipKp.secretKey);
228
+ if (!symKey) {
229
+ throw new Error('KeyManager.unwrapKey: authentication failed — wrong key or tampered data');
230
+ }
231
+ return Buffer.from(symKey).toString('hex');
232
+ }
233
+
234
+ /**
235
+ * Rotate the key wrapper for a record.
236
+ *
237
+ * @param {string} encryptedKey Current hex blob from wrapKey()
238
+ * @param {string} senderX25519PubKey X25519 public key of the original sender
239
+ * @param {string} recipientEthPrivateKey Recipient's Ethereum private key (derives X25519)
240
+ * @param {string} newRecipX25519PubKey New recipient's X25519 public key
241
+ * @param {string} newSenderEthPrivKey New sender's Ethereum private key (derives X25519)
242
+ * @returns {string} New hex blob for the new recipient
243
+ */
244
+ export function rotateKey(encryptedKey, senderX25519PubKey, recipientEthPrivateKey, newRecipX25519PubKey, newSenderEthPrivKey) {
245
+ const rawKey = unwrapKey(encryptedKey, senderX25519PubKey, recipientEthPrivateKey);
246
+ return wrapKey(rawKey, newRecipX25519PubKey, newSenderEthPrivKey);
247
+ }
248
+
249
+ // ─────────────────────────────────────────────────────────────
250
+ // Encoding helpers (for on-chain byte blobs)
251
+ // ─────────────────────────────────────────────────────────────
252
+
253
+ /**
254
+ * Decode a secretbox blob (from contract read()) into its components.
255
+ * NaCl wire format: [ nonce(24B) | ciphertext+MAC ]
256
+ *
257
+ * @param {string} hex "0x"-prefixed hex from contract read()
258
+ * @returns {{ nonce: string, ciphertext: string }}
259
+ */
260
+ export function decodeCiphertextBlob(hex) {
261
+ const buf = Buffer.from(hex.startsWith('0x') ? hex.slice(2) : hex, 'hex');
262
+ if (buf.length < nacl.secretbox.nonceLength) {
263
+ throw new Error('KeyManager.decodeCiphertextBlob: blob too short to contain nonce');
264
+ }
265
+ return {
266
+ nonce : buf.subarray(0, nacl.secretbox.nonceLength).toString('hex'),
267
+ ciphertext: buf.subarray(nacl.secretbox.nonceLength).toString('hex'),
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Derive the X25519 public key corresponding to an Ethereum private key.
273
+ * Uses the same derivation as wrapKey/unwrapKey: SHA-256 of the private key bytes
274
+ * becomes the seed for nacl.box.keyPair.fromSecretKey().
275
+ *
276
+ * Callers use this to obtain the public key that should be registered in the
277
+ * PublicKeyRegistry and passed as `recipientX25519PubKey` to wrapKey().
278
+ *
279
+ * @param {string} ethPrivateKey 64-char hex Ethereum private key (with or without "0x")
280
+ * @returns {string} 64-char hex X25519 public key
281
+ */
282
+ export function deriveX25519PublicKey(ethPrivateKey) {
283
+ const stripped = ethPrivateKey.startsWith('0x') ? ethPrivateKey.slice(2) : ethPrivateKey;
284
+ if (stripped.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(stripped)) {
285
+ throw new Error('KeyManager.deriveX25519PublicKey: ethPrivateKey must be 32 bytes (64 hex chars)');
286
+ }
287
+ const seed = sha256(new Uint8Array(Buffer.from(stripped, 'hex')));
288
+ const kp = nacl.box.keyPair.fromSecretKey(seed);
289
+ return Buffer.from(kp.publicKey).toString('hex');
290
+ }
291
+
292
+ /**
293
+ * Ensure a wrapped key hex string has a "0x" prefix for ethers/contract calls.
294
+ * @param {string} wrappedKeyHex hex from wrapKey()
295
+ * @returns {string}
296
+ */
297
+ export function encodeWrappedKey(wrappedKeyHex) {
298
+ const clean = wrappedKeyHex.startsWith('0x') ? wrappedKeyHex.slice(2) : wrappedKeyHex;
299
+ return '0x' + clean;
300
+ }
301
+
302
+ /**
303
+ * Strip "0x" prefix from a wrapped key returned by getMyEncryptedKey().
304
+ * @param {string} hex "0x"-prefixed hex from contract
305
+ * @returns {string}
306
+ */
307
+ export function decodeWrappedKey(hex) {
308
+ return hex.startsWith('0x') ? hex.slice(2) : hex;
309
+ }
310
+
311
+ // ─────────────────────────────────────────────────────────────
312
+ // Private helpers
313
+ // ─────────────────────────────────────────────────────────────
314
+
315
+ function _parseHexKey(hex, fn) {
316
+ if (typeof hex !== 'string') {
317
+ throw new TypeError(`KeyManager.${fn}: key must be a string`);
318
+ }
319
+ const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
320
+ if (clean.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(clean)) {
321
+ throw new Error(`KeyManager.${fn}: key must be 32 bytes (64 hex chars), got ${clean.length}`);
322
+ }
323
+ return new Uint8Array(Buffer.from(clean, 'hex'));
324
+ }
325
+
326
+ function _parseX25519Key(hex, fn) {
327
+ if (typeof hex !== 'string') {
328
+ throw new TypeError(`KeyManager.${fn}: X25519 public key must be a string`);
329
+ }
330
+ const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
331
+ if (clean.length !== 64 || !/^[0-9a-fA-F]{64}$/.test(clean)) {
332
+ throw new Error(
333
+ `KeyManager.${fn}: X25519 public key must be 32 bytes (64 hex chars), got ${clean.length}`,
334
+ );
335
+ }
336
+ return new Uint8Array(Buffer.from(clean, 'hex'));
337
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "web3ql-client",
3
+ "version": "1.2.0",
4
+ "description": "Web3QL client SDK — end-to-end encrypted SQL-like storage on Celo. NaCl encryption, UUPS proxy contracts, full TypeScript. Includes CLI.",
5
+ "keywords": ["web3", "sql", "encrypted-storage", "celo", "nacl", "e2e-encryption", "cli"],
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/src/index.js",
9
+ "types": "./dist/src/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/src/index.js",
13
+ "types": "./dist/src/index.d.ts"
14
+ }
15
+ },
16
+ "bin": {
17
+ "web3ql": "./dist/src/cli.js"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "clean": "rm -rf dist",
23
+ "cli": "node ./dist/src/cli.js"
24
+ },
25
+ "dependencies": {
26
+ "@noble/hashes": "^1.4.0",
27
+ "@dotenvx/dotenvx": "^1.0.0",
28
+ "ethers": "^6.13.1",
29
+ "tweetnacl": "^1.0.3"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.14.10",
33
+ "typescript": "^5.5.3"
34
+ },
35
+ "engines": {
36
+ "node": ">=20.0.0"
37
+ }
38
+ }