@midnight-ntwrk/testkit-js 3.0.0-alpha.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 (84) hide show
  1. package/README.md +256 -0
  2. package/dist/assertions.d.ts +15 -0
  3. package/dist/assertions.d.ts.map +1 -0
  4. package/dist/client/faucet-client.d.ts +30 -0
  5. package/dist/client/faucet-client.d.ts.map +1 -0
  6. package/dist/client/index.d.ts +5 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/indexer-client.d.ts +18 -0
  9. package/dist/client/indexer-client.d.ts.map +1 -0
  10. package/dist/client/node-client.d.ts +66 -0
  11. package/dist/client/node-client.d.ts.map +1 -0
  12. package/dist/client/proof-server-client.d.ts +25 -0
  13. package/dist/client/proof-server-client.d.ts.map +1 -0
  14. package/dist/configuration-types.d.ts +68 -0
  15. package/dist/configuration-types.d.ts.map +1 -0
  16. package/dist/configuration.d.ts +6 -0
  17. package/dist/configuration.d.ts.map +1 -0
  18. package/dist/contract/contract-types.d.ts +16 -0
  19. package/dist/contract/contract-types.d.ts.map +1 -0
  20. package/dist/contract/in-memory-private-state-provider.d.ts +10 -0
  21. package/dist/contract/in-memory-private-state-provider.d.ts.map +1 -0
  22. package/dist/contract/index.d.ts +4 -0
  23. package/dist/contract/index.d.ts.map +1 -0
  24. package/dist/contract/providers.d.ts +24 -0
  25. package/dist/contract/providers.d.ts.map +1 -0
  26. package/dist/env-vars.d.ts +10 -0
  27. package/dist/env-vars.d.ts.map +1 -0
  28. package/dist/errors.d.ts +11 -0
  29. package/dist/errors.d.ts.map +1 -0
  30. package/dist/index.cjs +2067 -0
  31. package/dist/index.cjs.map +1 -0
  32. package/dist/index.d.cts +789 -0
  33. package/dist/index.d.mts +789 -0
  34. package/dist/index.d.ts +789 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.mjs +1993 -0
  37. package/dist/index.mjs.map +1 -0
  38. package/dist/logger.d.ts +5 -0
  39. package/dist/logger.d.ts.map +1 -0
  40. package/dist/proof-server-container.d.ts +83 -0
  41. package/dist/proof-server-container.d.ts.map +1 -0
  42. package/dist/test-environment/environment-configuration.d.ts +23 -0
  43. package/dist/test-environment/environment-configuration.d.ts.map +1 -0
  44. package/dist/test-environment/environment-provider.d.ts +9 -0
  45. package/dist/test-environment/environment-provider.d.ts.map +1 -0
  46. package/dist/test-environment/index.d.ts +4 -0
  47. package/dist/test-environment/index.d.ts.map +1 -0
  48. package/dist/test-environment/test-environments/env-var-remote-test-environment.d.ts +22 -0
  49. package/dist/test-environment/test-environments/env-var-remote-test-environment.d.ts.map +1 -0
  50. package/dist/test-environment/test-environments/index.d.ts +7 -0
  51. package/dist/test-environment/test-environments/index.d.ts.map +1 -0
  52. package/dist/test-environment/test-environments/local-test-environment.d.ts +96 -0
  53. package/dist/test-environment/test-environments/local-test-environment.d.ts.map +1 -0
  54. package/dist/test-environment/test-environments/qanet-test-environment.d.ts +19 -0
  55. package/dist/test-environment/test-environments/qanet-test-environment.d.ts.map +1 -0
  56. package/dist/test-environment/test-environments/remote-test-environment.d.ts +40 -0
  57. package/dist/test-environment/test-environments/remote-test-environment.d.ts.map +1 -0
  58. package/dist/test-environment/test-environments/test-environment.d.ts +50 -0
  59. package/dist/test-environment/test-environments/test-environment.d.ts.map +1 -0
  60. package/dist/test-environment/test-environments/testnet2-test-environment.d.ts +19 -0
  61. package/dist/test-environment/test-environments/testnet2-test-environment.d.ts.map +1 -0
  62. package/dist/utils.d.ts +19 -0
  63. package/dist/utils.d.ts.map +1 -0
  64. package/dist/wallet/bigint-serialization.d.ts +7 -0
  65. package/dist/wallet/bigint-serialization.d.ts.map +1 -0
  66. package/dist/wallet/fluent-wallet-builder.d.ts +23 -0
  67. package/dist/wallet/fluent-wallet-builder.d.ts.map +1 -0
  68. package/dist/wallet/gzip-file.d.ts +29 -0
  69. package/dist/wallet/gzip-file.d.ts.map +1 -0
  70. package/dist/wallet/index.d.ts +9 -0
  71. package/dist/wallet/index.d.ts.map +1 -0
  72. package/dist/wallet/midnight-wallet-provider.d.ts +28 -0
  73. package/dist/wallet/midnight-wallet-provider.d.ts.map +1 -0
  74. package/dist/wallet/wallet-configuration-mapper.d.ts +10 -0
  75. package/dist/wallet/wallet-configuration-mapper.d.ts.map +1 -0
  76. package/dist/wallet/wallet-factory.d.ts +21 -0
  77. package/dist/wallet/wallet-factory.d.ts.map +1 -0
  78. package/dist/wallet/wallet-seed.d.ts +13 -0
  79. package/dist/wallet/wallet-seed.d.ts.map +1 -0
  80. package/dist/wallet/wallet-state-provider.d.ts +43 -0
  81. package/dist/wallet/wallet-state-provider.d.ts.map +1 -0
  82. package/dist/wallet/wallet-utils.d.ts +11 -0
  83. package/dist/wallet/wallet-utils.d.ts.map +1 -0
  84. package/package.json +70 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,2067 @@
1
+ 'use strict';
2
+
3
+ var midnightJsTypes = require('@midnight-ntwrk/midnight-js-types');
4
+ var axios = require('axios');
5
+ var promises = require('node:fs/promises');
6
+ var path = require('path');
7
+ var path$1 = require('node:path');
8
+ var pino = require('pino');
9
+ var pinoPretty = require('pino-pretty');
10
+ var testcontainers = require('testcontainers');
11
+ var ledgerV7 = require('@midnight-ntwrk/ledger-v7');
12
+ var midnightJsNetworkId = require('@midnight-ntwrk/midnight-js-network-id');
13
+ var midnightJsHttpClientProofProvider = require('@midnight-ntwrk/midnight-js-http-client-proof-provider');
14
+ var midnightJsIndexerPublicDataProvider = require('@midnight-ntwrk/midnight-js-indexer-public-data-provider');
15
+ var midnightJsLevelPrivateStateProvider = require('@midnight-ntwrk/midnight-js-level-private-state-provider');
16
+ var midnightJsNodeZkConfigProvider = require('@midnight-ntwrk/midnight-js-node-zk-config-provider');
17
+ var walletSdkUnshieldedWallet = require('@midnight-ntwrk/wallet-sdk-unshielded-wallet');
18
+ var walletSdkDustWallet = require('@midnight-ntwrk/wallet-sdk-dust-wallet');
19
+ var walletSdkFacade = require('@midnight-ntwrk/wallet-sdk-facade');
20
+ var walletSdkShielded = require('@midnight-ntwrk/wallet-sdk-shielded');
21
+ var walletSdkHd = require('@midnight-ntwrk/wallet-sdk-hd');
22
+ var node_zlib = require('node:zlib');
23
+ var fs = require('fs');
24
+ var midnightJsUtils = require('@midnight-ntwrk/midnight-js-utils');
25
+ var Rx = require('rxjs');
26
+ var fs$1 = require('node:fs');
27
+ var walletSdkAbstractions = require('@midnight-ntwrk/wallet-sdk-abstractions');
28
+
29
+ function _interopNamespaceDefault(e) {
30
+ var n = Object.create(null);
31
+ if (e) {
32
+ Object.keys(e).forEach(function (k) {
33
+ if (k !== 'default') {
34
+ var d = Object.getOwnPropertyDescriptor(e, k);
35
+ Object.defineProperty(n, k, d.get ? d : {
36
+ enumerable: true,
37
+ get: function () { return e[k]; }
38
+ });
39
+ }
40
+ });
41
+ }
42
+ n.default = e;
43
+ return Object.freeze(n);
44
+ }
45
+
46
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path$1);
47
+ var Rx__namespace = /*#__PURE__*/_interopNamespaceDefault(Rx);
48
+
49
+ /*
50
+ * This file is part of midnight-js.
51
+ * Copyright (C) 2025 Midnight Foundation
52
+ * SPDX-License-Identifier: Apache-2.0
53
+ * Licensed under the Apache License, Version 2.0 (the "License");
54
+ * You may not use this file except in compliance with the License.
55
+ * You may obtain a copy of the License at
56
+ * http://www.apache.org/licenses/LICENSE-2.0
57
+ * Unless required by applicable law or agreed to in writing, software
58
+ * distributed under the License is distributed on an "AS IS" BASIS,
59
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
60
+ * See the License for the specific language governing permissions and
61
+ * limitations under the License.
62
+ */
63
+ const stateValueEqual = (a, b) => {
64
+ return a.toString(false) === b.toString(false);
65
+ };
66
+ const txsEqual = (a, b) => {
67
+ return a.toString(false) === b.toString(false);
68
+ };
69
+ const expectFoundAndDeployedTxPublicDataEqual = (deployTxData, foundDeployTxData) => {
70
+ expect(stateValueEqual(deployTxData.public.initialContractState.data.state, foundDeployTxData.public.initialContractState.data.state)).toBeTruthy();
71
+ expect(deployTxData.public.contractAddress).toEqual(foundDeployTxData.public.contractAddress);
72
+ expect(deployTxData.public.blockHash).toEqual(foundDeployTxData.public.blockHash);
73
+ expect(deployTxData.public.blockHeight).toEqual(foundDeployTxData.public.blockHeight);
74
+ expect(deployTxData.public.txHash).toEqual(foundDeployTxData.public.txHash);
75
+ expect(deployTxData.public.identifiers).toEqual(foundDeployTxData.public.identifiers);
76
+ expect(deployTxData.public.status).toEqual(foundDeployTxData.public.status);
77
+ expect(txsEqual(deployTxData.public.tx, foundDeployTxData.public.tx)).toBeTruthy();
78
+ };
79
+ const expectFoundAndDeployedTxPrivateDataEqual = (deployTxData, foundDeployTxData) => {
80
+ // For our purposes, we always find with the same private state that the contract is deployed with
81
+ // so this comparison is justified.
82
+ expect(deployTxData.private.initialPrivateState).toEqual(foundDeployTxData.private.initialPrivateState);
83
+ };
84
+ const expectFoundAndDeployedTxDataEqual = (deployTxData, foundDeployTxData) => {
85
+ expectFoundAndDeployedTxPublicDataEqual(deployTxData, foundDeployTxData);
86
+ expectFoundAndDeployedTxPrivateDataEqual(deployTxData, foundDeployTxData);
87
+ };
88
+ const expectFoundAndDeployedStatesEqual = async (providers, deployTxData, foundDeployTxData, privateStateId, initialPrivateState) => {
89
+ const deployedLedgerState = await providers.publicDataProvider.queryContractState(deployTxData.public.contractAddress);
90
+ expect(deployedLedgerState).toBeDefined();
91
+ expect(stateValueEqual(deployedLedgerState.data.state, foundDeployTxData.public.initialContractState.data.state)).toBeTruthy();
92
+ if (privateStateId) {
93
+ const privateState = await providers.privateStateProvider.get(privateStateId);
94
+ expect(privateState).toEqual(foundDeployTxData.private.initialPrivateState);
95
+ if (initialPrivateState !== undefined) {
96
+ expect(privateState).toEqual(initialPrivateState);
97
+ }
98
+ }
99
+ };
100
+ const expectSuccessfulTxData = (finalizedTxData) => {
101
+ expect(finalizedTxData.status).toEqual(midnightJsTypes.SucceedEntirely);
102
+ expect(finalizedTxData.tx).toBeTruthy();
103
+ expect(finalizedTxData.txId).toBeTruthy();
104
+ expect(finalizedTxData.txHash).toBeTruthy();
105
+ expect(finalizedTxData.blockHeight).toBeTruthy();
106
+ expect(finalizedTxData.blockHash).toBeTruthy();
107
+ };
108
+ const expectSuccessfulDeployTx = async (providers, deployTxData, deployTxOptions) => {
109
+ expectSuccessfulTxData(deployTxData.public);
110
+ expect(deployTxData.public.contractAddress).toBeTruthy();
111
+ const deployedLedgerState = await providers.publicDataProvider.queryContractState(deployTxData.public.contractAddress);
112
+ expect(stateValueEqual(deployTxData.public.initialContractState.data.state, deployedLedgerState.data.state));
113
+ expect(deployTxData.public.initialContractState).toBeTruthy();
114
+ // Checks that the signing key and private state passed in the deploy configuration
115
+ // were stored correctly.
116
+ if (deployTxOptions) {
117
+ if (deployTxOptions.signingKey) {
118
+ expect(deployTxData.private.signingKey).toEqual(deployTxOptions.signingKey);
119
+ const storedSigningKey = await providers.privateStateProvider.getSigningKey(deployTxData.public.contractAddress);
120
+ expect(storedSigningKey).toBeDefined();
121
+ expect(storedSigningKey).toEqual(deployTxOptions.signingKey);
122
+ }
123
+ // We only test contracts that pass 'initialPrivateState' through the contract constructor unchanged
124
+ // so this equality comparison is justified.
125
+ if ('privateStateId' in deployTxOptions && 'initialPrivateState' in deployTxOptions) {
126
+ expect(deployTxData.private.initialPrivateState).toEqual(deployTxOptions.initialPrivateState);
127
+ const storedPrivateState = await providers.privateStateProvider.get(deployTxOptions.privateStateId);
128
+ expect(storedPrivateState).toBeDefined();
129
+ expect(storedPrivateState).toEqual(deployTxOptions.initialPrivateState);
130
+ }
131
+ }
132
+ };
133
+ const expectSuccessfulCallTx = async (providers, callTxData, callTxOptions, nextPrivateState) => {
134
+ expectSuccessfulTxData(callTxData.public);
135
+ expect(callTxData.public.nextContractState).toBeTruthy();
136
+ expect(callTxData.private.nextZswapLocalState);
137
+ if (callTxOptions) {
138
+ if ('privateStateId' in callTxOptions) {
139
+ const storedPrivateState = await providers.privateStateProvider.get(callTxOptions.privateStateId);
140
+ expect(storedPrivateState).toBeDefined();
141
+ expect(storedPrivateState).toEqual(callTxData.private.nextPrivateState);
142
+ if (nextPrivateState) {
143
+ expect(nextPrivateState).toEqual(callTxData.private.nextPrivateState);
144
+ }
145
+ }
146
+ }
147
+ };
148
+
149
+ /*
150
+ * This file is part of midnight-js.
151
+ * Copyright (C) 2025 Midnight Foundation
152
+ * SPDX-License-Identifier: Apache-2.0
153
+ * Licensed under the Apache License, Version 2.0 (the "License");
154
+ * You may not use this file except in compliance with the License.
155
+ * You may obtain a copy of the License at
156
+ * http://www.apache.org/licenses/LICENSE-2.0
157
+ * Unless required by applicable law or agreed to in writing, software
158
+ * distributed under the License is distributed on an "AS IS" BASIS,
159
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
160
+ * See the License for the specific language governing permissions and
161
+ * limitations under the License.
162
+ */
163
+ const currentWorkingDir = path.resolve(`${process.cwd()}`);
164
+ const defaultContainersConfiguration = {
165
+ proofServer: {
166
+ path: currentWorkingDir,
167
+ fileName: 'proof-server.yml',
168
+ container: {
169
+ name: 'proof-server',
170
+ port: 6300,
171
+ waitStrategy: testcontainers.Wait.forListeningPorts().withStartupTimeout(3 * 60_000)
172
+ }
173
+ },
174
+ standalone: {
175
+ path: currentWorkingDir,
176
+ fileName: 'compose.yml',
177
+ container: {
178
+ proofServer: {
179
+ name: 'proof-server',
180
+ port: 6300,
181
+ waitStrategy: testcontainers.Wait.forListeningPorts().withStartupTimeout(3 * 60_000)
182
+ },
183
+ node: {
184
+ name: 'node',
185
+ port: 9944,
186
+ waitStrategy: testcontainers.Wait.forListeningPorts()
187
+ },
188
+ indexer: {
189
+ name: 'indexer',
190
+ port: 8088,
191
+ waitStrategy: testcontainers.Wait.forListeningPorts()
192
+ }
193
+ }
194
+ },
195
+ log: {
196
+ fileName: `tests_${new Date().toISOString().replace(/:/g, '_')}.log`,
197
+ path: path.resolve(currentWorkingDir, 'logs', 'tests'),
198
+ level: 'info'
199
+ }
200
+ };
201
+ const latestContainersConfiguration = {
202
+ ...defaultContainersConfiguration,
203
+ standalone: {
204
+ ...defaultContainersConfiguration.standalone,
205
+ fileName: 'compose-latest.yml'
206
+ },
207
+ proofServer: {
208
+ ...defaultContainersConfiguration.proofServer,
209
+ fileName: 'proof-server-latest.yml'
210
+ }
211
+ };
212
+ let containersConfiguration = defaultContainersConfiguration;
213
+ const getContainersConfiguration = () => {
214
+ return containersConfiguration;
215
+ };
216
+ const setContainersConfiguration = (containersConfig) => {
217
+ containersConfiguration = containersConfig;
218
+ };
219
+
220
+ /*
221
+ * This file is part of midnight-js.
222
+ * Copyright (C) 2025 Midnight Foundation
223
+ * SPDX-License-Identifier: Apache-2.0
224
+ * Licensed under the Apache License, Version 2.0 (the "License");
225
+ * You may not use this file except in compliance with the License.
226
+ * You may obtain a copy of the License at
227
+ * http://www.apache.org/licenses/LICENSE-2.0
228
+ * Unless required by applicable law or agreed to in writing, software
229
+ * distributed under the License is distributed on an "AS IS" BASIS,
230
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
231
+ * See the License for the specific language governing permissions and
232
+ * limitations under the License.
233
+ */
234
+ const { level } = getContainersConfiguration().log;
235
+ function createLogger(fileName, dir = getContainersConfiguration().log.path) {
236
+ let logPath = path__namespace.resolve(fileName);
237
+ if (!fileName.includes('/')) {
238
+ logPath = path__namespace.resolve(dir, fileName);
239
+ }
240
+ const prettyStream = pinoPretty({
241
+ colorize: true,
242
+ sync: true
243
+ });
244
+ const prettyFileStream = pinoPretty({
245
+ mkdir: true,
246
+ colorize: false,
247
+ sync: true,
248
+ append: true,
249
+ destination: logPath
250
+ });
251
+ return pino({
252
+ level,
253
+ depthLimit: 20
254
+ }, pino.multistream([
255
+ { stream: prettyStream, level },
256
+ { stream: prettyFileStream, level }
257
+ ]));
258
+ }
259
+ function createDefaultTestLogger() {
260
+ return createLogger(getContainersConfiguration().log.fileName, getContainersConfiguration().log.path);
261
+ }
262
+ const logger = createDefaultTestLogger();
263
+
264
+ /*
265
+ * This file is part of midnight-js.
266
+ * Copyright (C) 2025 Midnight Foundation
267
+ * SPDX-License-Identifier: Apache-2.0
268
+ * Licensed under the Apache License, Version 2.0 (the "License");
269
+ * You may not use this file except in compliance with the License.
270
+ * You may obtain a copy of the License at
271
+ * http://www.apache.org/licenses/LICENSE-2.0
272
+ * Unless required by applicable law or agreed to in writing, software
273
+ * distributed under the License is distributed on an "AS IS" BASIS,
274
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
275
+ * See the License for the specific language governing permissions and
276
+ * limitations under the License.
277
+ */
278
+ const MINUTE = 60_000;
279
+ /**
280
+ * Creates a Promise that resolves after a specified delay.
281
+ * @param ms The delay duration in milliseconds.
282
+ * @returns A Promise that resolves after the specified delay.
283
+ * @example
284
+ * // Wait for 1 second
285
+ * await delay(1000);
286
+ */
287
+ const delay = (ms) => {
288
+ return new Promise((resolve) => {
289
+ setTimeout(resolve, ms);
290
+ });
291
+ };
292
+ /**
293
+ * Deletes a directory and its contents recursively.
294
+ * @param {string} dirPath - The path to the directory to delete
295
+ * @returns {Promise<void>} A promise that resolves when the directory is deleted
296
+ * @private
297
+ */
298
+ const deleteDirectory = async (dirPath) => {
299
+ try {
300
+ const resolvedPath = path.resolve(dirPath);
301
+ await promises.rm(resolvedPath, { recursive: true, force: true });
302
+ logger.info(`Directory ${resolvedPath} deleted successfully.`);
303
+ }
304
+ catch (error) {
305
+ if (error instanceof Error) {
306
+ logger.error(`Error deleting directory: ${error.message}`);
307
+ }
308
+ else {
309
+ logger.error('Unknown error occurred');
310
+ }
311
+ }
312
+ };
313
+ const extractHostnameAndPort = (url) => {
314
+ const { hostname, port } = new URL(url);
315
+ if (port !== '') {
316
+ return `${hostname}:${port}`;
317
+ }
318
+ return hostname;
319
+ };
320
+
321
+ /*
322
+ * This file is part of midnight-js.
323
+ * Copyright (C) 2025 Midnight Foundation
324
+ * SPDX-License-Identifier: Apache-2.0
325
+ * Licensed under the Apache License, Version 2.0 (the "License");
326
+ * You may not use this file except in compliance with the License.
327
+ * You may obtain a copy of the License at
328
+ * http://www.apache.org/licenses/LICENSE-2.0
329
+ * Unless required by applicable law or agreed to in writing, software
330
+ * distributed under the License is distributed on an "AS IS" BASIS,
331
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
332
+ * See the License for the specific language governing permissions and
333
+ * limitations under the License.
334
+ */
335
+ /**
336
+ * Client for interacting with the Midnight faucet service.
337
+ * Provides functionality to request test tokens for wallet addresses.
338
+ */
339
+ class FaucetClient {
340
+ faucetUrl;
341
+ logger;
342
+ /**
343
+ * Creates a new FaucetClient instance.
344
+ * @param {string} faucetUrl - The URL of the faucet service endpoint
345
+ * @param {Logger} logger - Logger instance for recording operations
346
+ */
347
+ constructor(faucetUrl, logger) {
348
+ this.faucetUrl = faucetUrl;
349
+ this.logger = logger;
350
+ }
351
+ /**
352
+ * Checks the health status of the faucet service.
353
+ * Makes a GET request to the health endpoint of the faucet service.
354
+ * @returns {Promise<AxiosResponse | void>} A promise that resolves to the response of the health check or logs an error if the request fails
355
+ */
356
+ async health() {
357
+ const url = `https://${extractHostnameAndPort(this.faucetUrl)}/api/health`;
358
+ return axios
359
+ .get(url, { timeout: 1000 })
360
+ .then((r) => {
361
+ this.logger.info(`Connected to faucet ${url}: ${JSON.stringify(r.data)}`);
362
+ return r;
363
+ })
364
+ .catch((error) => {
365
+ this.logger.warn(`Failed to connect to faucet service at '${url}'`, error);
366
+ });
367
+ }
368
+ /**
369
+ * Requests test tokens from the faucet for a specified wallet address.
370
+ * Makes a POST request to the faucet service with the wallet address.
371
+ * @param {string} walletAddress - The address to receive the test tokens
372
+ * @returns {Promise<void>} A promise that resolves when the request is complete
373
+ * @throws Will log but not throw if the request fails
374
+ */
375
+ async requestTokens(walletAddress) {
376
+ this.logger.info(`Requesting tokens from '${this.faucetUrl}' for address: '${walletAddress}'`);
377
+ try {
378
+ const response = await axios.post(this.faucetUrl, {
379
+ address: walletAddress,
380
+ captchaToken: 'XXXX.DUMMY.TOKEN.XXXX'
381
+ }, {
382
+ headers: {
383
+ 'Content-Type': 'application/json',
384
+ 'x-turnstile-token': process.env.TURNSTILE_HEADER ?? ''
385
+ }
386
+ });
387
+ this.logger.info(`Faucet response: ${response.statusText}`);
388
+ }
389
+ catch (error) {
390
+ if (error instanceof Error) {
391
+ this.logger.error(`Error requesting tokens: ${error.message}`);
392
+ }
393
+ else {
394
+ this.logger.error(`Error requesting tokens`);
395
+ }
396
+ }
397
+ }
398
+ }
399
+
400
+ /*
401
+ * This file is part of midnight-js.
402
+ * Copyright (C) 2025 Midnight Foundation
403
+ * SPDX-License-Identifier: Apache-2.0
404
+ * Licensed under the Apache License, Version 2.0 (the "License");
405
+ * You may not use this file except in compliance with the License.
406
+ * You may obtain a copy of the License at
407
+ * http://www.apache.org/licenses/LICENSE-2.0
408
+ * Unless required by applicable law or agreed to in writing, software
409
+ * distributed under the License is distributed on an "AS IS" BASIS,
410
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
411
+ * See the License for the specific language governing permissions and
412
+ * limitations under the License.
413
+ */
414
+ class IndexerClient {
415
+ indexerUrl;
416
+ logger;
417
+ /**
418
+ * Creates an instance of IndexerClient.
419
+ * @param {string} indexerUrl - The URL of the indexer service.
420
+ * @param {Logger} logger - The logger instance for logging information.
421
+ */
422
+ constructor(indexerUrl, logger) {
423
+ this.indexerUrl = indexerUrl;
424
+ this.logger = logger;
425
+ }
426
+ /**
427
+ * Checks the health status of the indexer service.
428
+ * Makes a GET request to the status endpoint of the indexer service.
429
+ * @returns {Promise<AxiosResponse | void>} A promise that resolves to the response of the health check or logs an error if the request fails.
430
+ */
431
+ async health() {
432
+ const url = `https://${extractHostnameAndPort(this.indexerUrl)}/ready`;
433
+ return axios
434
+ .get(url, { timeout: 1000 })
435
+ .then((r) => {
436
+ this.logger.info(`Connected to indexer ${url}: ${JSON.stringify(r.data)}`);
437
+ return r;
438
+ })
439
+ .catch((error) => {
440
+ this.logger.warn(`Failed to connect to indexer at '${url}'`, error);
441
+ });
442
+ }
443
+ }
444
+
445
+ /*
446
+ * This file is part of midnight-js.
447
+ * Copyright (C) 2025 Midnight Foundation
448
+ * SPDX-License-Identifier: Apache-2.0
449
+ * Licensed under the Apache License, Version 2.0 (the "License");
450
+ * You may not use this file except in compliance with the License.
451
+ * You may obtain a copy of the License at
452
+ * http://www.apache.org/licenses/LICENSE-2.0
453
+ * Unless required by applicable law or agreed to in writing, software
454
+ * distributed under the License is distributed on an "AS IS" BASIS,
455
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
456
+ * See the License for the specific language governing permissions and
457
+ * limitations under the License.
458
+ */
459
+ /**
460
+ * Client for interacting with a Midnight node's JSON-RPC API
461
+ */
462
+ class NodeClient {
463
+ nodeURL;
464
+ logger;
465
+ /**
466
+ * Creates a new NodeClient instance
467
+ * @param {string} nodeURL - URL of the Midnight node
468
+ * @param {Logger} logger - Logger instance for recording operations
469
+ */
470
+ constructor(nodeURL, logger) {
471
+ this.nodeURL = nodeURL;
472
+ this.logger = logger;
473
+ }
474
+ /**
475
+ * Checks the health status of the node.
476
+ * Makes a GET request to the health endpoint of the node.
477
+ * @returns {Promise<AxiosResponse | void>} A promise that resolves to the response of the health check or logs an error if the request fails.
478
+ */
479
+ async health() {
480
+ const url = `https://${extractHostnameAndPort(this.nodeURL)}/health`;
481
+ return axios
482
+ .get(url, { timeout: 1000 })
483
+ .then((r) => {
484
+ this.logger.info(`Connected to node ${url}: ${JSON.stringify(r.data)}`);
485
+ return r;
486
+ })
487
+ .catch((error) => {
488
+ this.logger.warn(`Failed to connect to node at '${url}'`, error);
489
+ });
490
+ }
491
+ /**
492
+ * Validates response format and throws if unexpected
493
+ * @param {AxiosResponse} response - Response from node API
494
+ * @throws {Error} If response format is unexpected
495
+ * @private
496
+ */
497
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
498
+ static throwOnUnexpected(response) {
499
+ if (typeof response.data !== 'object' || !response.data.result || typeof response.data.result !== 'string') {
500
+ throw new Error(`Unexpected response format: ${JSON.stringify(response.data)}`);
501
+ }
502
+ }
503
+ /**
504
+ * Makes a JSON-RPC request to the node
505
+ * @param {string} method - RPC method name
506
+ * @param {any[]} params - RPC method parameters
507
+ * @returns {Promise<string>} Response result as string
508
+ * @private
509
+ */
510
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
511
+ async jsonRPC(method, params) {
512
+ const response = await axios.post(this.nodeURL, {
513
+ id: 1,
514
+ jsonrpc: '2.0',
515
+ method,
516
+ params
517
+ }, {
518
+ headers: {
519
+ 'Content-Type': 'application/json'
520
+ }
521
+ });
522
+ this.logger.info(`Node response: ${response.statusText}`);
523
+ NodeClient.throwOnUnexpected(response);
524
+ return response.data.result;
525
+ }
526
+ /**
527
+ * Fetches the state of a contract
528
+ * @param {ContractAddress} contractAddress - Address of the contract
529
+ * @returns {Promise<ContractState | null>} Contract state or null if not found
530
+ */
531
+ async contractState(contractAddress) {
532
+ this.logger.info(`Fetching contract state for address '${contractAddress}'`);
533
+ const result = await this.jsonRPC('midnight_contractState', [
534
+ `${midnightJsNetworkId.getNetworkId()}${contractAddress}`
535
+ ]);
536
+ return result === '' ? null : ledgerV7.ContractState.deserialize(Buffer.from(result, 'hex'));
537
+ }
538
+ /**
539
+ * Fetches the ledger state at a given block
540
+ * @param {BlockHash} blockHash - Hash of the block
541
+ * @returns {Promise<LedgerState>} Ledger state
542
+ */
543
+ async ledgerState(blockHash) {
544
+ const blob = await this.ledgerStateBlob(blockHash);
545
+ return ledgerV7.LedgerState.deserialize(blob);
546
+ }
547
+ /**
548
+ * Fetches the raw ledger state blob at a given block
549
+ * @param {BlockHash} blockHash - Hash of the block
550
+ * @returns {Promise<Uint8Array>} Raw ledger state data
551
+ * @throws {Error} If no ledger state is found
552
+ */
553
+ async ledgerStateBlob(blockHash) {
554
+ this.logger.info(`Fetching ledger state at block hash '${blockHash}'`);
555
+ const result = await this.jsonRPC('midnight_getLedgerState', []);
556
+ if (result === '') {
557
+ throw new Error(`No ledger state found at block hash '${blockHash}'`);
558
+ }
559
+ return Buffer.from(result.slice(4), 'hex');
560
+ }
561
+ /**
562
+ * Fetches the ledger version at a given block
563
+ * @param {BlockHash} blockHash - Hash of the block
564
+ * @returns {Promise<string>} Ledger version
565
+ * @throws {Error} If no ledger version is found
566
+ */
567
+ async ledgerVersion(blockHash) {
568
+ this.logger.info(`Fetching ledger version at block hash '${blockHash}'`);
569
+ const result = await this.jsonRPC('midnight_ledgerVersion', []);
570
+ if (result === '') {
571
+ throw new Error(`No ledger version found at block hash '${blockHash}'`);
572
+ }
573
+ return result;
574
+ }
575
+ }
576
+
577
+ /*
578
+ * This file is part of midnight-js.
579
+ * Copyright (C) 2025 Midnight Foundation
580
+ * SPDX-License-Identifier: Apache-2.0
581
+ * Licensed under the Apache License, Version 2.0 (the "License");
582
+ * You may not use this file except in compliance with the License.
583
+ * You may obtain a copy of the License at
584
+ * http://www.apache.org/licenses/LICENSE-2.0
585
+ * Unless required by applicable law or agreed to in writing, software
586
+ * distributed under the License is distributed on an "AS IS" BASIS,
587
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
588
+ * See the License for the specific language governing permissions and
589
+ * limitations under the License.
590
+ */
591
+ class ProofServerClient {
592
+ proofServer;
593
+ logger;
594
+ /**
595
+ * Creates an instance of ProofServerClient.
596
+ * @param {string} proofServer - The URL of the proof server service.
597
+ * @param {Logger} logger - The logger instance for logging information.
598
+ */
599
+ constructor(proofServer, logger) {
600
+ this.proofServer = proofServer;
601
+ this.logger = logger;
602
+ }
603
+ /**
604
+ * Checks the health status of the indexer service.
605
+ * Makes a GET request to the status endpoint of the indexer service.
606
+ * @returns {Promise<AxiosResponse | void>} A promise that resolves to the response of the health check or logs an error if the request fails.
607
+ */
608
+ async health() {
609
+ const url = `http://${extractHostnameAndPort(this.proofServer)}/health`;
610
+ return axios
611
+ .get(url, { timeout: 1000 })
612
+ .then((r) => {
613
+ this.logger.info(`Connected to proof server ${url}: ${JSON.stringify(r.data)}`);
614
+ return r;
615
+ })
616
+ .catch((error) => {
617
+ this.logger.warn(`Failed to connect to proof server at '${url}'`, error);
618
+ });
619
+ }
620
+ /**
621
+ * Proves a transaction by sending a POST request to the proof server.
622
+ * @param data serialized transaction data
623
+ * @param config Axios request configuration
624
+ */
625
+ async proveTx(data, config = {
626
+ timeout: 3 * 60_000
627
+ }) {
628
+ const url = `http://${extractHostnameAndPort(this.proofServer)}/prove-tx`;
629
+ return axios
630
+ .post(url, data, config)
631
+ .then((r) => {
632
+ this.logger.info(`Received data from proof server ${url}`);
633
+ return r;
634
+ })
635
+ .catch((error) => {
636
+ this.logger.error(`Error in proof server at '${url}' ${error.toString()}`);
637
+ });
638
+ }
639
+ }
640
+
641
+ /*
642
+ * This file is part of midnight-js.
643
+ * Copyright (C) 2025 Midnight Foundation
644
+ * SPDX-License-Identifier: Apache-2.0
645
+ * Licensed under the Apache License, Version 2.0 (the "License");
646
+ * You may not use this file except in compliance with the License.
647
+ * You may obtain a copy of the License at
648
+ * http://www.apache.org/licenses/LICENSE-2.0
649
+ * Unless required by applicable law or agreed to in writing, software
650
+ * distributed under the License is distributed on an "AS IS" BASIS,
651
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
652
+ * See the License for the specific language governing permissions and
653
+ * limitations under the License.
654
+ */
655
+ /**
656
+ * A simple in-memory implementation of private state provider. Makes it easy to capture and rewrite private state from deploy.
657
+ * @template PSI - Type of the private state identifier.
658
+ * @template PS - Type of the private state.
659
+ * @returns {PrivateStateProvider<PSI, PS>} An in-memory private state provider.
660
+ */
661
+ const inMemoryPrivateStateProvider = () => {
662
+ const record = new Map();
663
+ const signingKeys = {};
664
+ return {
665
+ /**
666
+ * Sets the private state for a given key.
667
+ * @param {PSI} key - The key for the private state.
668
+ * @param {PS} state - The private state to set.
669
+ * @returns {Promise<void>} A promise that resolves when the state is set.
670
+ */
671
+ set(key, state) {
672
+ record.set(key, state);
673
+ return Promise.resolve();
674
+ },
675
+ /**
676
+ * Gets the private state for a given key.
677
+ * @param {PSI} key - The key for the private state.
678
+ * @returns {Promise<PS | null>} A promise that resolves to the private state or null if not found.
679
+ */
680
+ get(key) {
681
+ const value = record.get(key) ?? null;
682
+ return Promise.resolve(value);
683
+ },
684
+ /**
685
+ * Removes the private state for a given key.
686
+ * @param {PSI} key - The key for the private state.
687
+ * @returns {Promise<void>} A promise that resolves when the state is removed.
688
+ */
689
+ remove(key) {
690
+ record.delete(key);
691
+ return Promise.resolve();
692
+ },
693
+ /**
694
+ * Clears all private states.
695
+ * @returns {Promise<void>} A promise that resolves when all states are cleared.
696
+ */
697
+ clear() {
698
+ record.clear();
699
+ return Promise.resolve();
700
+ },
701
+ /**
702
+ * Sets the signing key for a given contract address.
703
+ * @param {ContractAddress} contractAddress - The contract address.
704
+ * @param {SigningKey} signingKey - The signing key to set.
705
+ * @returns {Promise<void>} A promise that resolves when the signing key is set.
706
+ */
707
+ setSigningKey(contractAddress, signingKey) {
708
+ signingKeys[contractAddress] = signingKey;
709
+ return Promise.resolve();
710
+ },
711
+ /**
712
+ * Gets the signing key for a given contract address.
713
+ * @param {ContractAddress} contractAddress - The contract address.
714
+ * @returns {Promise<SigningKey | null>} A promise that resolves to the signing key or null if not found.
715
+ */
716
+ getSigningKey(contractAddress) {
717
+ const value = signingKeys[contractAddress] ?? null;
718
+ return Promise.resolve(value);
719
+ },
720
+ /**
721
+ * Removes the signing key for a given contract address.
722
+ * @param {ContractAddress} contractAddress - The contract address.
723
+ * @returns {Promise<void>} A promise that resolves when the signing key is removed.
724
+ */
725
+ removeSigningKey(contractAddress) {
726
+ delete signingKeys[contractAddress];
727
+ return Promise.resolve();
728
+ },
729
+ /**
730
+ * Clears all signing keys.
731
+ * @returns {Promise<void>} A promise that resolves when all signing keys are cleared.
732
+ */
733
+ clearSigningKeys() {
734
+ Object.keys(signingKeys).forEach((contractAddress) => {
735
+ delete signingKeys[contractAddress];
736
+ });
737
+ return Promise.resolve();
738
+ }
739
+ };
740
+ };
741
+
742
+ /*
743
+ * This file is part of midnight-js.
744
+ * Copyright (C) 2025 Midnight Foundation
745
+ * SPDX-License-Identifier: Apache-2.0
746
+ * Licensed under the Apache License, Version 2.0 (the "License");
747
+ * You may not use this file except in compliance with the License.
748
+ * You may obtain a copy of the License at
749
+ * http://www.apache.org/licenses/LICENSE-2.0
750
+ * Unless required by applicable law or agreed to in writing, software
751
+ * distributed under the License is distributed on an "AS IS" BASIS,
752
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
753
+ * See the License for the specific language governing permissions and
754
+ * limitations under the License.
755
+ */
756
+ /**
757
+ * Configures and returns the required providers for a Midnight contract.
758
+ *
759
+ * @template ICK - Type parameter for the input circuit key string
760
+ * @template PS - Type parameter for the private state
761
+ *
762
+ * @param {MidnightWalletProvider} midnightWalletProvider - The midnightWalletProvider provider instance to use for transactions
763
+ * @param {EnvironmentConfiguration} environmentConfiguration - Configuration for the environment including indexer and proof server details
764
+ * @param {ContractConfiguration} contractConfiguration - Configuration specific to the contract including storage names and ZK config path
765
+ *
766
+ * @returns {MidnightProviders} An object containing all configured providers:
767
+ * - privateStateProvider: For managing private contract state
768
+ * - publicDataProvider: For accessing public blockchain data
769
+ * - zkConfigProvider: For zero-knowledge proof configurations
770
+ * - proofProvider: For generating and verifying proofs
771
+ * - walletProvider: For midnightWalletProvider operations
772
+ * - midnightProvider: For Midnight-specific operations
773
+ */
774
+ const initializeMidnightProviders = (midnightWalletProvider, environmentConfiguration, contractConfiguration) => {
775
+ const zkConfigProvider = new midnightJsNodeZkConfigProvider.NodeZkConfigProvider(contractConfiguration.zkConfigPath);
776
+ return {
777
+ privateStateProvider: midnightJsLevelPrivateStateProvider.levelPrivateStateProvider({
778
+ privateStateStoreName: contractConfiguration.privateStateStoreName,
779
+ signingKeyStoreName: `${contractConfiguration.privateStateStoreName}-signing-keys`,
780
+ privateStoragePasswordProvider: () => { return 'key-just-for-testing-here!'; }
781
+ }),
782
+ publicDataProvider: midnightJsIndexerPublicDataProvider.indexerPublicDataProvider(environmentConfiguration.indexer, environmentConfiguration.indexerWS),
783
+ zkConfigProvider,
784
+ proofProvider: midnightJsHttpClientProofProvider.httpClientProofProvider(environmentConfiguration.proofServer, zkConfigProvider),
785
+ walletProvider: midnightWalletProvider,
786
+ midnightProvider: midnightWalletProvider
787
+ };
788
+ };
789
+
790
+ /*
791
+ * This file is part of midnight-js.
792
+ * Copyright (C) 2025 Midnight Foundation
793
+ * SPDX-License-Identifier: Apache-2.0
794
+ * Licensed under the Apache License, Version 2.0 (the "License");
795
+ * You may not use this file except in compliance with the License.
796
+ * You may obtain a copy of the License at
797
+ * http://www.apache.org/licenses/LICENSE-2.0
798
+ * Unless required by applicable law or agreed to in writing, software
799
+ * distributed under the License is distributed on an "AS IS" BASIS,
800
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
801
+ * See the License for the specific language governing permissions and
802
+ * limitations under the License.
803
+ */
804
+ /**
805
+ * A proof server container that is started and stopped dynamically by the test
806
+ * suite on random port.
807
+ */
808
+ class DynamicProofServerContainer {
809
+ /** The Docker Compose environment running the container */
810
+ dockerEnv;
811
+ /** Unique identifier for the container instance */
812
+ uid;
813
+ /** Configuration for the proof server container */
814
+ config;
815
+ /**
816
+ * Creates a new DynamicProofServerContainer instance.
817
+ * @param {StartedDockerComposeEnvironment} dockerEnv - The started Docker Compose environment
818
+ * @param {string} uid - Unique identifier for the container
819
+ * @private
820
+ */
821
+ constructor(dockerEnv, uid) {
822
+ this.dockerEnv = dockerEnv;
823
+ this.uid = uid;
824
+ this.config = getContainersConfiguration().proofServer;
825
+ }
826
+ /**
827
+ * Starts a new proof server container.
828
+ * @param {Logger} logger - Logger instance for recording operations
829
+ * @param {string} [maybeUID] - Optional unique identifier for the container
830
+ * @param {string} [maybeNetworkId] - Optional network ID for the container
831
+ * @returns {Promise<DynamicProofServerContainer>} A promise that resolves to the new container instance
832
+ */
833
+ static async start(logger, maybeUID, maybeNetworkId) {
834
+ const config = getContainersConfiguration().proofServer;
835
+ const uid = maybeUID ?? Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString();
836
+ const networkId = maybeNetworkId;
837
+ logger.info(`Starting proof server: path='${config.path}', file=${config.fileName}, networkId=${networkId}, uid=${uid}`);
838
+ const dockerEnv = await new testcontainers.DockerComposeEnvironment(config.path, config.fileName)
839
+ .withWaitStrategy(`${config.container.name}_${uid}`, config.container.waitStrategy)
840
+ .withEnvironment({
841
+ TESTCONTAINERS_UID: uid,
842
+ NETWORK_ID: networkId ?? 'undeployed'
843
+ })
844
+ .up();
845
+ return new DynamicProofServerContainer(dockerEnv, uid);
846
+ }
847
+ /**
848
+ * Stops the proof server container.
849
+ * @returns {Promise<void>} A promise that resolves when the container is stopped
850
+ */
851
+ async stop() {
852
+ await this.dockerEnv.stop();
853
+ }
854
+ /**
855
+ * Gets the mapped port number for the container.
856
+ * @returns {number} The mapped port number
857
+ */
858
+ getMappedPort() {
859
+ return this.dockerEnv
860
+ .getContainer(`${this.config.container.name}_${this.uid}`)
861
+ .getMappedPort(this.config.container.port);
862
+ }
863
+ /**
864
+ * Gets the URL where the proof server can be accessed.
865
+ * @returns {string} The URL of the proof server
866
+ */
867
+ getUrl() {
868
+ return `http://localhost:${this.getMappedPort()}`;
869
+ }
870
+ }
871
+ /**
872
+ * A proof server that is currently running on a specific port.
873
+ * Used for connecting to an existing proof server instance.
874
+ */
875
+ class StaticProofServerContainer {
876
+ /** The port number where the proof server is running */
877
+ port;
878
+ /**
879
+ * Creates a new StaticProofServerContainer instance.
880
+ * @param {number} port - The port number where the proof server is running (default: 6300)
881
+ */
882
+ constructor(port = 6300) {
883
+ this.port = port;
884
+ }
885
+ /**
886
+ * Gets the URL where the proof server can be accessed.
887
+ * @returns {string} The URL of the proof server
888
+ */
889
+ getUrl() {
890
+ return `http://localhost:${this.port}`;
891
+ }
892
+ /**
893
+ * No-op stop method since this represents an external proof server.
894
+ * @returns {Promise<void>} A resolved promise
895
+ */
896
+ stop() {
897
+ return Promise.resolve(undefined);
898
+ }
899
+ }
900
+
901
+ /*
902
+ * This file is part of midnight-js.
903
+ * Copyright (C) 2025 Midnight Foundation
904
+ * SPDX-License-Identifier: Apache-2.0
905
+ * Licensed under the Apache License, Version 2.0 (the "License");
906
+ * You may not use this file except in compliance with the License.
907
+ * You may obtain a copy of the License at
908
+ * http://www.apache.org/licenses/LICENSE-2.0
909
+ * Unless required by applicable law or agreed to in writing, software
910
+ * distributed under the License is distributed on an "AS IS" BASIS,
911
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
912
+ * See the License for the specific language governing permissions and
913
+ * limitations under the License.
914
+ */
915
+ const getEnvVarEnvironment = () => {
916
+ if (process.env.MN_TEST_ENVIRONMENT === undefined || process.env.MN_TEST_ENVIRONMENT === 'undefined') {
917
+ return 'undeployed';
918
+ }
919
+ return process.env.MN_TEST_ENVIRONMENT;
920
+ };
921
+ const getEnvVarWalletSeeds = () => {
922
+ const envSeeds = process.env.MN_TEST_WALLET_SEED || process.env.TEST_WALLET_SEED;
923
+ return envSeeds ? envSeeds.split(',') : undefined;
924
+ };
925
+ const MN_TEST_INDEXER = process.env.MN_TEST_INDEXER;
926
+ const MN_TEST_INDEXER_WS = process.env.MN_TEST_INDEXER_WS;
927
+ const MN_TEST_NODE = process.env.MN_TEST_NODE;
928
+ const MN_TEST_NODE_WS = process.env.MN_TEST_NODE_WS;
929
+ const MN_TEST_FAUCET = process.env.MN_TEST_FAUCET;
930
+ const MN_TEST_NETWORK_ID = process.env.MN_TEST_NETWORK_ID;
931
+ const MN_TEST_WALLET_NETWORK_ID = process.env.MN_TEST_WALLET_NETWORK_ID;
932
+
933
+ /*
934
+ * This file is part of midnight-js.
935
+ * Copyright (C) 2025 Midnight Foundation
936
+ * SPDX-License-Identifier: Apache-2.0
937
+ * Licensed under the Apache License, Version 2.0 (the "License");
938
+ * You may not use this file except in compliance with the License.
939
+ * You may obtain a copy of the License at
940
+ * http://www.apache.org/licenses/LICENSE-2.0
941
+ * Unless required by applicable law or agreed to in writing, software
942
+ * distributed under the License is distributed on an "AS IS" BASIS,
943
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
944
+ * See the License for the specific language governing permissions and
945
+ * limitations under the License.
946
+ */
947
+ /**
948
+ * An error representing a required, but missing, environment variable.
949
+ */
950
+ class MissingEnvironmentVariable extends Error {
951
+ environmentVariable;
952
+ /**
953
+ * @param environmentVariable The name of the missing environment variable.
954
+ */
955
+ constructor(environmentVariable) {
956
+ super(`Environment variable '${environmentVariable}' is required`);
957
+ this.environmentVariable = environmentVariable;
958
+ }
959
+ }
960
+
961
+ /*
962
+ * This file is part of midnight-js.
963
+ * Copyright (C) 2025 Midnight Foundation
964
+ * SPDX-License-Identifier: Apache-2.0
965
+ * Licensed under the Apache License, Version 2.0 (the "License");
966
+ * You may not use this file except in compliance with the License.
967
+ * You may obtain a copy of the License at
968
+ * http://www.apache.org/licenses/LICENSE-2.0
969
+ * Unless required by applicable law or agreed to in writing, software
970
+ * distributed under the License is distributed on an "AS IS" BASIS,
971
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
972
+ * See the License for the specific language governing permissions and
973
+ * limitations under the License.
974
+ */
975
+ BigInt.prototype.toJSON = function () {
976
+ return Number(this);
977
+ };
978
+
979
+ /*
980
+ * This file is part of midnight-js.
981
+ * Copyright (C) 2025 Midnight Foundation
982
+ * SPDX-License-Identifier: Apache-2.0
983
+ * Licensed under the Apache License, Version 2.0 (the "License");
984
+ * You may not use this file except in compliance with the License.
985
+ * You may obtain a copy of the License at
986
+ * http://www.apache.org/licenses/LICENSE-2.0
987
+ * Unless required by applicable law or agreed to in writing, software
988
+ * distributed under the License is distributed on an "AS IS" BASIS,
989
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
990
+ * See the License for the specific language governing permissions and
991
+ * limitations under the License.
992
+ */
993
+ function mapEnvironmentToConfiguration(env) {
994
+ return {
995
+ indexerClientConnection: {
996
+ indexerHttpUrl: env.indexer,
997
+ indexerWsUrl: env.indexerWS,
998
+ },
999
+ provingServerUrl: new URL(env.proofServer),
1000
+ relayURL: new URL(env.nodeWS),
1001
+ networkId: env.walletNetworkId,
1002
+ };
1003
+ }
1004
+
1005
+ /*
1006
+ * This file is part of midnight-js.
1007
+ * Copyright (C) 2025 Midnight Foundation
1008
+ * SPDX-License-Identifier: Apache-2.0
1009
+ * Licensed under the Apache License, Version 2.0 (the "License");
1010
+ * You may not use this file except in compliance with the License.
1011
+ * You may obtain a copy of the License at
1012
+ * http://www.apache.org/licenses/LICENSE-2.0
1013
+ * Unless required by applicable law or agreed to in writing, software
1014
+ * distributed under the License is distributed on an "AS IS" BASIS,
1015
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1016
+ * See the License for the specific language governing permissions and
1017
+ * limitations under the License.
1018
+ */
1019
+ const DEFAULT_DUST_OPTIONS = {
1020
+ ledgerParams: ledgerV7.LedgerParameters.initialParameters(),
1021
+ additionalFeeOverhead: 500000000000000000000n,
1022
+ feeBlocksMargin: 5
1023
+ };
1024
+ class WalletFactory {
1025
+ static createShieldedWallet(config, seed) {
1026
+ const Shielded = walletSdkShielded.ShieldedWallet(config);
1027
+ return Shielded.startWithShieldedSeed(seed);
1028
+ }
1029
+ static createUnshieldedWallet(config, unshieldedKeystore) {
1030
+ return walletSdkUnshieldedWallet.UnshieldedWallet({
1031
+ ...config,
1032
+ txHistoryStorage: new walletSdkUnshieldedWallet.InMemoryTransactionHistoryStorage(),
1033
+ }).startWithPublicKey(walletSdkUnshieldedWallet.PublicKey.fromKeyStore(unshieldedKeystore));
1034
+ }
1035
+ static createDustWallet(config, seed, dustOptions = DEFAULT_DUST_OPTIONS) {
1036
+ const dustConfig = {
1037
+ ...config,
1038
+ costParameters: {
1039
+ ledgerParams: dustOptions.ledgerParams,
1040
+ additionalFeeOverhead: dustOptions.additionalFeeOverhead,
1041
+ feeBlocksMargin: dustOptions.feeBlocksMargin,
1042
+ },
1043
+ };
1044
+ logger.info(`Creating dust wallet with params: ${JSON.stringify(dustConfig)}`);
1045
+ const Dust = walletSdkDustWallet.DustWallet(dustConfig);
1046
+ const dustParameters = ledgerV7.LedgerParameters.initialParameters().dust;
1047
+ return Dust.startWithSeed(seed, dustParameters);
1048
+ }
1049
+ static createWalletFacade(shieldedWallet, unshieldedWallet, dustWallet) {
1050
+ return new walletSdkFacade.WalletFacade(shieldedWallet, unshieldedWallet, dustWallet);
1051
+ }
1052
+ static async startWalletFacade(wallet, shieldedSeed, dustSeed) {
1053
+ logger.info('Starting wallet facade...');
1054
+ await wallet.start(ledgerV7.ZswapSecretKeys.fromSeed(shieldedSeed), ledgerV7.DustSecretKey.fromSeed(dustSeed));
1055
+ return wallet;
1056
+ }
1057
+ static async restoreShieldedWallet(config, serializedState) {
1058
+ return walletSdkShielded.ShieldedWallet(config).restore(serializedState);
1059
+ }
1060
+ }
1061
+
1062
+ /*
1063
+ * This file is part of midnight-js.
1064
+ * Copyright (C) 2025 Midnight Foundation
1065
+ * SPDX-License-Identifier: Apache-2.0
1066
+ * Licensed under the Apache License, Version 2.0 (the "License");
1067
+ * You may not use this file except in compliance with the License.
1068
+ * You may obtain a copy of the License at
1069
+ * http://www.apache.org/licenses/LICENSE-2.0
1070
+ * Unless required by applicable law or agreed to in writing, software
1071
+ * distributed under the License is distributed on an "AS IS" BASIS,
1072
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1073
+ * See the License for the specific language governing permissions and
1074
+ * limitations under the License.
1075
+ */
1076
+ const deriveKeyForRole = (seed, role, account = 0, keyIndex = 0) => {
1077
+ if (!seed || seed.length === 0) {
1078
+ throw new Error('Seed cannot be empty');
1079
+ }
1080
+ const seedBuffer = Buffer.from(seed, 'hex');
1081
+ const hdWalletResult = walletSdkHd.HDWallet.fromSeed(seedBuffer);
1082
+ if (hdWalletResult.type !== 'seedOk') {
1083
+ throw new Error('Invalid seed: failed to create HD wallet');
1084
+ }
1085
+ const derivationResult = hdWalletResult.hdWallet
1086
+ .selectAccount(account)
1087
+ .selectRole(role)
1088
+ .deriveKeyAt(keyIndex);
1089
+ if (derivationResult.type === 'keyOutOfBounds') {
1090
+ throw new Error(`Key derivation out of bounds for role ${role} at index ${keyIndex}`);
1091
+ }
1092
+ return derivationResult.key;
1093
+ };
1094
+ const getShieldedSeed = (seed) => {
1095
+ return deriveKeyForRole(seed, walletSdkHd.Roles.Zswap);
1096
+ };
1097
+ const getUnshieldedSeed = (seed) => {
1098
+ return deriveKeyForRole(seed, walletSdkHd.Roles.NightExternal);
1099
+ };
1100
+ const getDustSeed = (seed) => {
1101
+ return deriveKeyForRole(seed, walletSdkHd.Roles.Dust);
1102
+ };
1103
+ class WalletSeeds {
1104
+ masterSeed;
1105
+ shielded;
1106
+ unshielded;
1107
+ dust;
1108
+ constructor(masterSeed, shielded, unshielded, dust) {
1109
+ this.masterSeed = masterSeed;
1110
+ this.shielded = shielded;
1111
+ this.unshielded = unshielded;
1112
+ this.dust = dust;
1113
+ }
1114
+ static fromMasterSeed(seed) {
1115
+ if (!seed || seed.length === 0) {
1116
+ throw new Error('Master seed cannot be empty');
1117
+ }
1118
+ return new WalletSeeds(seed, getShieldedSeed(seed), getUnshieldedSeed(seed), getDustSeed(seed));
1119
+ }
1120
+ static generateRandom() {
1121
+ const randomSeed = Buffer.from(walletSdkHd.generateRandomSeed()).toString('hex');
1122
+ return WalletSeeds.fromMasterSeed(randomSeed);
1123
+ }
1124
+ }
1125
+
1126
+ /*
1127
+ * This file is part of midnight-js.
1128
+ * Copyright (C) 2025 Midnight Foundation
1129
+ * SPDX-License-Identifier: Apache-2.0
1130
+ * Licensed under the Apache License, Version 2.0 (the "License");
1131
+ * You may not use this file except in compliance with the License.
1132
+ * You may obtain a copy of the License at
1133
+ * http://www.apache.org/licenses/LICENSE-2.0
1134
+ * Unless required by applicable law or agreed to in writing, software
1135
+ * distributed under the License is distributed on an "AS IS" BASIS,
1136
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1137
+ * See the License for the specific language governing permissions and
1138
+ * limitations under the License.
1139
+ */
1140
+ class FluentWalletBuilder {
1141
+ config;
1142
+ networkId;
1143
+ seeds;
1144
+ dustOptions;
1145
+ constructor(config, networkId, seeds, dustOptions = DEFAULT_DUST_OPTIONS) {
1146
+ this.config = config;
1147
+ this.networkId = networkId;
1148
+ this.seeds = seeds;
1149
+ this.dustOptions = dustOptions;
1150
+ }
1151
+ static forEnvironment(envConfig) {
1152
+ if (!envConfig) {
1153
+ throw new Error('Environment configuration is required');
1154
+ }
1155
+ logger.info(`Initializing wallet builder for ${envConfig.walletNetworkId}`);
1156
+ const config = mapEnvironmentToConfiguration(envConfig);
1157
+ return new FluentWalletBuilder(config, envConfig.walletNetworkId);
1158
+ }
1159
+ withSeed(seed) {
1160
+ if (!seed || seed.length === 0) {
1161
+ throw new Error('Seed cannot be empty');
1162
+ }
1163
+ this.seeds = WalletSeeds.fromMasterSeed(seed);
1164
+ return this;
1165
+ }
1166
+ withRandomSeed() {
1167
+ this.seeds = WalletSeeds.generateRandom();
1168
+ logger.info(`Generated random wallet seed: ${this.seeds.masterSeed}`);
1169
+ return this;
1170
+ }
1171
+ withDustOptions(options) {
1172
+ if (!options) {
1173
+ throw new Error('Dust options cannot be null or undefined');
1174
+ }
1175
+ this.dustOptions = options;
1176
+ return this;
1177
+ }
1178
+ async build() {
1179
+ if (!this.seeds) {
1180
+ logger.info('No seed provided, generating random seed');
1181
+ this.seeds = WalletSeeds.generateRandom();
1182
+ logger.info(`Generated random wallet seed: ${this.seeds.masterSeed}`);
1183
+ }
1184
+ logger.info(`Building wallet with configuration: ${JSON.stringify(this.config)}`);
1185
+ const unshieldedKeystore = walletSdkUnshieldedWallet.createKeystore(this.seeds.unshielded, this.networkId);
1186
+ const shieldedWallet = WalletFactory.createShieldedWallet(this.config, this.seeds.shielded);
1187
+ const unshieldedWallet = WalletFactory.createUnshieldedWallet(this.config, unshieldedKeystore);
1188
+ const dustWallet = WalletFactory.createDustWallet(this.config, this.seeds.dust, this.dustOptions);
1189
+ const walletFacade = WalletFactory.createWalletFacade(shieldedWallet, unshieldedWallet, dustWallet);
1190
+ return WalletFactory.startWalletFacade(walletFacade, this.seeds.shielded, this.seeds.dust);
1191
+ }
1192
+ async buildWithoutStarting() {
1193
+ if (!this.seeds) {
1194
+ logger.info('No seed provided, generating random seed');
1195
+ this.seeds = WalletSeeds.generateRandom();
1196
+ logger.info(`Generated random wallet seed: ${this.seeds.masterSeed}`);
1197
+ }
1198
+ logger.info(`Building wallet without starting with configuration: ${JSON.stringify(this.config)}`);
1199
+ const unshieldedKeystore = walletSdkUnshieldedWallet.createKeystore(this.seeds.unshielded, this.networkId);
1200
+ const shieldedWallet = WalletFactory.createShieldedWallet(this.config, this.seeds.shielded);
1201
+ const unshieldedWallet = WalletFactory.createUnshieldedWallet(this.config, unshieldedKeystore);
1202
+ const dustWallet = WalletFactory.createDustWallet(this.config, this.seeds.dust, this.dustOptions);
1203
+ const walletFacade = WalletFactory.createWalletFacade(shieldedWallet, unshieldedWallet, dustWallet);
1204
+ return {
1205
+ wallet: walletFacade,
1206
+ seeds: this.seeds,
1207
+ keystore: unshieldedKeystore
1208
+ };
1209
+ }
1210
+ }
1211
+
1212
+ /*
1213
+ * This file is part of midnight-js.
1214
+ * Copyright (C) 2025 Midnight Foundation
1215
+ * SPDX-License-Identifier: Apache-2.0
1216
+ * Licensed under the Apache License, Version 2.0 (the "License");
1217
+ * You may not use this file except in compliance with the License.
1218
+ * You may obtain a copy of the License at
1219
+ * http://www.apache.org/licenses/LICENSE-2.0
1220
+ * Unless required by applicable law or agreed to in writing, software
1221
+ * distributed under the License is distributed on an "AS IS" BASIS,
1222
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1223
+ * See the License for the specific language governing permissions and
1224
+ * limitations under the License.
1225
+ */
1226
+ /**
1227
+ * A class for compressing and decompressing files using gzip.
1228
+ */
1229
+ class GzipFile {
1230
+ /** The path to the input file */
1231
+ inputFile;
1232
+ /** The path to the output file */
1233
+ outputFile;
1234
+ /**
1235
+ * Creates a new GzipFile instance.
1236
+ * @param inputFile - The path to the input file to compress/decompress
1237
+ * @param outputFile - The path where the compressed file will be saved
1238
+ */
1239
+ constructor(inputFile, outputFile) {
1240
+ this.inputFile = inputFile;
1241
+ this.outputFile = outputFile;
1242
+ }
1243
+ /**
1244
+ * Compresses the input file using gzip compression.
1245
+ * @returns A promise that resolves when compression is complete
1246
+ * @throws If there is an error during compression
1247
+ */
1248
+ compress = () => {
1249
+ const gzip = node_zlib.createGzip();
1250
+ const source = fs.createReadStream(this.inputFile);
1251
+ const destination = fs.createWriteStream(this.outputFile);
1252
+ return new Promise((resolve, reject) => {
1253
+ source
1254
+ .pipe(gzip)
1255
+ .pipe(destination)
1256
+ .on('finish', () => {
1257
+ resolve();
1258
+ })
1259
+ .on('error', (err) => {
1260
+ reject(err);
1261
+ });
1262
+ });
1263
+ };
1264
+ /**
1265
+ * Decompresses the input gzip file and returns its contents as a string.
1266
+ * @returns A promise that resolves with the decompressed file contents as a string
1267
+ * @throws If there is an error during decompression
1268
+ */
1269
+ decompress = () => {
1270
+ const gunzip = node_zlib.createGunzip();
1271
+ const source = fs.createReadStream(this.inputFile);
1272
+ return new Promise((resolve, reject) => {
1273
+ let data = '';
1274
+ source
1275
+ .pipe(gunzip)
1276
+ .on('data', (chunk) => {
1277
+ data += chunk.toString();
1278
+ })
1279
+ .on('end', () => {
1280
+ resolve(data);
1281
+ })
1282
+ .on('error', (err) => {
1283
+ reject(err);
1284
+ });
1285
+ });
1286
+ };
1287
+ }
1288
+
1289
+ /*
1290
+ * This file is part of midnight-js.
1291
+ * Copyright (C) 2025 Midnight Foundation
1292
+ * SPDX-License-Identifier: Apache-2.0
1293
+ * Licensed under the Apache License, Version 2.0 (the "License");
1294
+ * You may not use this file except in compliance with the License.
1295
+ * You may obtain a copy of the License at
1296
+ * http://www.apache.org/licenses/LICENSE-2.0
1297
+ * Unless required by applicable law or agreed to in writing, software
1298
+ * distributed under the License is distributed on an "AS IS" BASIS,
1299
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1300
+ * See the License for the specific language governing permissions and
1301
+ * limitations under the License.
1302
+ */
1303
+ const getInitialState = async (wallet) => {
1304
+ if (wallet instanceof walletSdkShielded.ShieldedWallet) {
1305
+ return Rx__namespace.firstValueFrom(wallet.state);
1306
+ }
1307
+ else {
1308
+ return Rx__namespace.firstValueFrom(wallet.state);
1309
+ }
1310
+ };
1311
+ const getInitialShieldedState = async (wallet) => {
1312
+ logger.info('Getting initial state of wallet...');
1313
+ return Rx__namespace.firstValueFrom(wallet.state);
1314
+ };
1315
+ const getInitialUnshieldedState = async (wallet) => {
1316
+ logger.info('Getting initial state of wallet...');
1317
+ return Rx__namespace.firstValueFrom(wallet.state);
1318
+ };
1319
+ const syncWallet = (wallet, throttleTime = 2_000, timeout = 90_000) => {
1320
+ logger.info('Syncing wallet...');
1321
+ return Rx__namespace.firstValueFrom(wallet.state().pipe(Rx__namespace.tap((state) => {
1322
+ logger.info(`Wallet synced state emission: { shielded=${state.shielded.state.progress.isStrictlyComplete()}, unshielded=${state.unshielded.progress.isStrictlyComplete()}, dust=${state.dust.state.progress.isStrictlyComplete()} }`);
1323
+ }), Rx__namespace.throttleTime(throttleTime), Rx__namespace.tap((state) => {
1324
+ const isSynced = state.shielded.state.progress.isStrictlyComplete() &&
1325
+ state.dust.state.progress.isStrictlyComplete() &&
1326
+ state.unshielded.progress?.isStrictlyComplete() === true;
1327
+ logger.info(`Wallet synced state emission (synced=${isSynced}): { shielded=${state.shielded.state.progress.isStrictlyComplete()}, unshielded=${state.unshielded.progress.isStrictlyComplete()}, dust=${state.dust.state.progress.isStrictlyComplete()} }`);
1328
+ }), Rx__namespace.filter((state) => state.shielded.state.progress.isStrictlyComplete() &&
1329
+ state.dust.state.progress.isStrictlyComplete() &&
1330
+ state.unshielded.progress.isStrictlyComplete() === true), Rx__namespace.tap(() => logger.info('Sync complete')), Rx__namespace.tap((state) => {
1331
+ const shieldedBalances = state.shielded.balances || {};
1332
+ const unshieldedBalances = state.unshielded.balances || {};
1333
+ const dustBalances = state.dust.walletBalance(new Date(Date.now())) || {};
1334
+ logger.info(`Wallet balances after sync - Shielded: ${JSON.stringify(shieldedBalances)}, Unshielded: ${JSON.stringify(unshieldedBalances)}, Dust: ${JSON.stringify(dustBalances)}`);
1335
+ }), Rx__namespace.timeout({
1336
+ each: timeout,
1337
+ with: () => Rx__namespace.throwError(() => new Error(`Wallet sync timeout after ${timeout}ms`))
1338
+ })));
1339
+ };
1340
+ const waitForFunds = async (wallet, env, tokenType = ledgerV7.shieldedToken(), fundFromFaucet = false) => {
1341
+ const initialState = await getInitialShieldedState(wallet.shielded);
1342
+ logger.info(`Your wallet address is: ${initialState.address.coinPublicKeyString()}, waiting for funds...`);
1343
+ if (fundFromFaucet && env.faucet) {
1344
+ logger.info('Requesting tokens from faucet...');
1345
+ await new FaucetClient(env.faucet, logger).requestTokens(initialState.address.coinPublicKeyString());
1346
+ }
1347
+ const initialBalance = initialState.balances[tokenType.tag];
1348
+ if (initialBalance === undefined || initialBalance === 0n) {
1349
+ logger.info(`Your wallet balance is: 0`);
1350
+ logger.info(`Waiting to receive tokens...`);
1351
+ return syncWallet(wallet);
1352
+ }
1353
+ return initialBalance;
1354
+ };
1355
+
1356
+ /*
1357
+ * This file is part of midnight-js.
1358
+ * Copyright (C) 2025 Midnight Foundation
1359
+ * SPDX-License-Identifier: Apache-2.0
1360
+ * Licensed under the Apache License, Version 2.0 (the "License");
1361
+ * You may not use this file except in compliance with the License.
1362
+ * You may obtain a copy of the License at
1363
+ * http://www.apache.org/licenses/LICENSE-2.0
1364
+ * Unless required by applicable law or agreed to in writing, software
1365
+ * distributed under the License is distributed on an "AS IS" BASIS,
1366
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1367
+ * See the License for the specific language governing permissions and
1368
+ * limitations under the License.
1369
+ */
1370
+ /**
1371
+ * Provider class that implements wallet functionality for the Midnight network.
1372
+ * Handles transaction balancing, submission, and wallet state management.
1373
+ */
1374
+ class MidnightWalletProvider {
1375
+ logger;
1376
+ env;
1377
+ wallet;
1378
+ unshieldedKeystore;
1379
+ zswapSecretKeys;
1380
+ dustSecretKey;
1381
+ constructor(logger, environmentConfiguration, wallet, zswapSecretKeys, dustSecretKey, unshieldedKeystore) {
1382
+ this.logger = logger;
1383
+ this.env = environmentConfiguration;
1384
+ this.wallet = wallet;
1385
+ this.zswapSecretKeys = zswapSecretKeys;
1386
+ this.dustSecretKey = dustSecretKey;
1387
+ this.unshieldedKeystore = unshieldedKeystore;
1388
+ }
1389
+ getCoinPublicKey() {
1390
+ return this.zswapSecretKeys.coinPublicKey;
1391
+ }
1392
+ getEncryptionPublicKey() {
1393
+ return this.zswapSecretKeys.encryptionPublicKey;
1394
+ }
1395
+ async balanceTx(tx, _newCoins, ttl = midnightJsUtils.ttlOneHour()) {
1396
+ const bound = tx.bind();
1397
+ const finalizedTransactionRecipe = await this.wallet.balanceFinalizedTransaction(this.zswapSecretKeys, this.dustSecretKey, bound, ttl);
1398
+ finalizedTransactionRecipe.balancingTransaction = await this.wallet.signTransaction(finalizedTransactionRecipe.balancingTransaction, (payload) => this.unshieldedKeystore.signData(payload));
1399
+ return this.wallet.finalizeRecipe(finalizedTransactionRecipe);
1400
+ }
1401
+ submitTx(tx) {
1402
+ return this.wallet.submitTransaction(tx);
1403
+ }
1404
+ async start(waitForFundsInWallet = true, tokenType = ledgerV7.shieldedToken()) {
1405
+ this.logger.info('Starting wallet...');
1406
+ await this.wallet.start(this.zswapSecretKeys, this.dustSecretKey);
1407
+ if (waitForFundsInWallet) {
1408
+ const balance = await waitForFunds(this.wallet, this.env, tokenType, true);
1409
+ this.logger.info(`Your wallet balance is: ${JSON.stringify(balance)}`);
1410
+ }
1411
+ }
1412
+ async stop() {
1413
+ return this.wallet.stop();
1414
+ }
1415
+ static async build(logger, env, seed) {
1416
+ const builder = FluentWalletBuilder.forEnvironment(env);
1417
+ const { wallet, seeds, keystore } = seed
1418
+ ? await builder.withSeed(seed).buildWithoutStarting()
1419
+ : await builder.withRandomSeed().buildWithoutStarting();
1420
+ const initialState = await getInitialShieldedState(wallet.shielded);
1421
+ logger.info(`Your wallet seed is: ${seeds.masterSeed} and your address is: ${initialState.address.coinPublicKeyString()}`);
1422
+ return new MidnightWalletProvider(logger, env, wallet, ledgerV7.ZswapSecretKeys.fromSeed(seeds.shielded), ledgerV7.DustSecretKey.fromSeed(seeds.dust), keystore);
1423
+ }
1424
+ static async withWallet(logger, env, wallet, zswapSecretKeys, dustSecretKey, unshieldedKeystore) {
1425
+ return new MidnightWalletProvider(logger, env, wallet, zswapSecretKeys, dustSecretKey, unshieldedKeystore);
1426
+ }
1427
+ }
1428
+
1429
+ /*
1430
+ * This file is part of midnight-js.
1431
+ * Copyright (C) 2025 Midnight Foundation
1432
+ * SPDX-License-Identifier: Apache-2.0
1433
+ * Licensed under the Apache License, Version 2.0 (the "License");
1434
+ * You may not use this file except in compliance with the License.
1435
+ * You may obtain a copy of the License at
1436
+ * http://www.apache.org/licenses/LICENSE-2.0
1437
+ * Unless required by applicable law or agreed to in writing, software
1438
+ * distributed under the License is distributed on an "AS IS" BASIS,
1439
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1440
+ * See the License for the specific language governing permissions and
1441
+ * limitations under the License.
1442
+ */
1443
+ /** Default directory path for storing wallet state files */
1444
+ const DEFAULT_WALLET_STATE_DIRECTORY = `./.states`;
1445
+ /**
1446
+ * Generates a filename for the wallet state file based on environment and optional seed
1447
+ * @returns {string} Generated filename for the wallet state
1448
+ * @param seed
1449
+ */
1450
+ const getWalletStateFilename = (seed) => {
1451
+ if (seed === undefined) {
1452
+ return `wallet.${getEnvVarEnvironment()}.state.gz`;
1453
+ }
1454
+ return `wallet.${getEnvVarEnvironment()}.${seed}.state.gz`;
1455
+ };
1456
+ /**
1457
+ * Provider class for saving and loading wallet state to/from compressed files
1458
+ */
1459
+ class WalletSaveStateProvider {
1460
+ /** Logger instance for recording operations */
1461
+ logger;
1462
+ /** Absolute path to the directory containing wallet state files */
1463
+ directoryPath;
1464
+ /** Full path including filename for the wallet state file */
1465
+ filePath;
1466
+ /**
1467
+ * Creates a new WalletSaveStateProvider instance
1468
+ * @param {Logger} logger - Logger instance for recording operations
1469
+ * @param seed
1470
+ * @param {string} [directoryPath=DEFAULT_WALLET_STATE_DIRECTORY] - Directory path for wallet state files
1471
+ * @param {string} [filename] - Filename for the wallet state file
1472
+ */
1473
+ constructor(logger, seed, directoryPath = DEFAULT_WALLET_STATE_DIRECTORY, filename = getWalletStateFilename(seed)) {
1474
+ this.logger = logger;
1475
+ if (!directoryPath.startsWith('/')) {
1476
+ this.directoryPath = `${process.cwd()}/${directoryPath}`;
1477
+ }
1478
+ else {
1479
+ this.directoryPath = directoryPath;
1480
+ }
1481
+ this.filePath = `${this.directoryPath}/${filename}`;
1482
+ }
1483
+ /**
1484
+ * Saves the wallet state to a compressed file
1485
+ * @param {ShieldedWallet | UnshieldedWallet} wallet - The wallet instance to save state from
1486
+ * @returns {Promise<void>} A promise that resolves when the save is complete
1487
+ */
1488
+ async save(wallet) {
1489
+ this.logger.info(`Saving state in ${this.filePath}`);
1490
+ try {
1491
+ fs$1.mkdirSync(this.directoryPath, { recursive: true });
1492
+ const serializedState = await wallet.serializeState();
1493
+ try {
1494
+ const tempFile = `${this.filePath.replaceAll('.gz', '')}`;
1495
+ fs$1.writeFileSync(tempFile, serializedState);
1496
+ this.logger.info(`File '${tempFile}' written successfully.`);
1497
+ await new GzipFile(tempFile, `${this.filePath.replaceAll('.gz', '')}.gz`).compress();
1498
+ fs$1.rmSync(tempFile);
1499
+ this.logger.info(`File '${this.filePath}' written successfully.`);
1500
+ }
1501
+ catch (err) {
1502
+ this.logger.error(`Error writing file '${this.filePath}': ${err instanceof Error ? err.message : String(err)}`);
1503
+ }
1504
+ }
1505
+ catch (e) {
1506
+ this.logger.warn(e instanceof Error ? e.message : String(e));
1507
+ }
1508
+ }
1509
+ /**
1510
+ * Loads and decompresses the wallet state from a file
1511
+ * @returns {Promise<string>} A promise that resolves with the decompressed wallet state as a string
1512
+ * @throws {Error} If there is an error reading or decompressing the file
1513
+ */
1514
+ async load() {
1515
+ this.logger.info(`Loading state from ${this.filePath}`);
1516
+ try {
1517
+ return await new GzipFile(this.filePath, `${this.filePath.replaceAll('.gz', '')}.gz`).decompress();
1518
+ }
1519
+ catch (error) {
1520
+ this.logger.error(error instanceof Error ? error.message : String(error));
1521
+ throw error;
1522
+ }
1523
+ }
1524
+ }
1525
+
1526
+ /*
1527
+ * This file is part of midnight-js.
1528
+ * Copyright (C) 2025 Midnight Foundation
1529
+ * SPDX-License-Identifier: Apache-2.0
1530
+ * Licensed under the Apache License, Version 2.0 (the "License");
1531
+ * You may not use this file except in compliance with the License.
1532
+ * You may obtain a copy of the License at
1533
+ * http://www.apache.org/licenses/LICENSE-2.0
1534
+ * Unless required by applicable law or agreed to in writing, software
1535
+ * distributed under the License is distributed on an "AS IS" BASIS,
1536
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1537
+ * See the License for the specific language governing permissions and
1538
+ * limitations under the License.
1539
+ */
1540
+ /**
1541
+ * Abstract base class for test environments.
1542
+ * Provides common functionality for managing test wallets and environments.
1543
+ */
1544
+ class TestEnvironment {
1545
+ /** Logger instance for recording operations */
1546
+ logger;
1547
+ /** Unique identifier for this test environment instance */
1548
+ uid;
1549
+ envConfiguration;
1550
+ /**
1551
+ * Creates a new TestEnvironment instance.
1552
+ * @param {Logger} logger - Logger instance for recording operations
1553
+ */
1554
+ constructor(logger) {
1555
+ this.logger = logger;
1556
+ this.uid = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString();
1557
+ }
1558
+ /**
1559
+ * Starts a single wallet instance.
1560
+ * @returns {Promise<MidnightWalletProvider>} A promise that resolves to the started wallet
1561
+ * @throws {Error} If no wallet could be started
1562
+ */
1563
+ getMidnightWalletProvider = async () => {
1564
+ const [walletProvider] = await this.startMidnightWalletProviders();
1565
+ if (!walletProvider) {
1566
+ throw Error('Undefined walletProvider found, but expected to have one');
1567
+ }
1568
+ return walletProvider;
1569
+ };
1570
+ }
1571
+
1572
+ /*
1573
+ * This file is part of midnight-js.
1574
+ * Copyright (C) 2025 Midnight Foundation
1575
+ * SPDX-License-Identifier: Apache-2.0
1576
+ * Licensed under the Apache License, Version 2.0 (the "License");
1577
+ * You may not use this file except in compliance with the License.
1578
+ * You may obtain a copy of the License at
1579
+ * http://www.apache.org/licenses/LICENSE-2.0
1580
+ * Unless required by applicable law or agreed to in writing, software
1581
+ * distributed under the License is distributed on an "AS IS" BASIS,
1582
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1583
+ * See the License for the specific language governing permissions and
1584
+ * limitations under the License.
1585
+ */
1586
+ /**
1587
+ * Base class for remote test environments that connect to external network services.
1588
+ * Provides functionality for managing walletProviders and a proof server container.
1589
+ */
1590
+ class RemoteTestEnvironment extends TestEnvironment {
1591
+ proofServerContainer;
1592
+ environmentConfiguration;
1593
+ walletProviders = undefined;
1594
+ /**
1595
+ * Creates and starts the specified number of wallet providers.
1596
+ * @returns {Promise<MidnightWalletProvider[]>} Array of started wallet providers
1597
+ */
1598
+ startMidnightWalletProviders = async (amount = 1, seeds = getEnvVarWalletSeeds()) => {
1599
+ if (amount > 1 && seeds && seeds.length !== amount) {
1600
+ throw new Error(`Number of seeds provided (${seeds.length}) does not match the amount of wallets requested (${amount})`);
1601
+ }
1602
+ this.logger.info(`Getting ${amount} wallets...`);
1603
+ const buildWallet = (seed) => MidnightWalletProvider.build(this.logger, this.environmentConfiguration, seed);
1604
+ const seeds2 = seeds || Array(amount).fill(undefined);
1605
+ this.walletProviders = await Promise.all(seeds2.map(buildWallet));
1606
+ await Promise.all(this.walletProviders.map((wallet) => wallet.start()));
1607
+ return this.walletProviders;
1608
+ };
1609
+ /**
1610
+ * Shuts down the test environment by closing all walletProviders and stopping the proof server.
1611
+ */
1612
+ shutdown = async (saveWalletState) => {
1613
+ this.logger.info(`Shutting down test environment...`);
1614
+ if (this.walletProviders) {
1615
+ if (saveWalletState) {
1616
+ await Promise.all(this.walletProviders.map((midnightWallet) => new WalletSaveStateProvider(logger, midnightWallet.zswapSecretKeys.coinPublicKey).save(midnightWallet.wallet.shielded)));
1617
+ }
1618
+ await Promise.all(this.walletProviders.map((wallet) => wallet.stop()));
1619
+ }
1620
+ if (this.proofServerContainer) {
1621
+ await this.proofServerContainer.stop();
1622
+ }
1623
+ };
1624
+ /**
1625
+ * Performs a health check for the environment.
1626
+ * Checks the health of the node, indexer, and optionally the faucet services.
1627
+ * @returns {Promise<void>} A promise that resolves when the health check is complete.
1628
+ */
1629
+ healthCheck = async () => {
1630
+ this.logger.info('Performing env health check');
1631
+ await new NodeClient(this.environmentConfiguration.node, this.logger).health();
1632
+ await new IndexerClient(this.environmentConfiguration.indexer, this.logger).health();
1633
+ await new ProofServerClient(this.environmentConfiguration.proofServer, this.logger).health();
1634
+ if (this.environmentConfiguration.faucet) {
1635
+ await new FaucetClient(this.environmentConfiguration.faucet, this.logger).health();
1636
+ }
1637
+ };
1638
+ /**
1639
+ * Starts the test environment by initializing the proof server and environment configuration.
1640
+ * @param {ProofServerContainer} maybeProofServerContainer Optional proof server container to use instead of creating a new one
1641
+ * @returns {Promise<EnvironmentConfiguration>} The environment configuration
1642
+ */
1643
+ start = async (maybeProofServerContainer) => {
1644
+ this.logger.info(`Starting test environment... `);
1645
+ this.proofServerContainer =
1646
+ maybeProofServerContainer ?? (await DynamicProofServerContainer.start(this.logger, this.uid));
1647
+ this.environmentConfiguration = this.getEnvironmentConfiguration();
1648
+ this.logger.info(`Test environment configuration: ${JSON.stringify(this.environmentConfiguration)}`);
1649
+ await this.healthCheck();
1650
+ return this.environmentConfiguration;
1651
+ };
1652
+ }
1653
+
1654
+ /*
1655
+ * This file is part of midnight-js.
1656
+ * Copyright (C) 2025 Midnight Foundation
1657
+ * SPDX-License-Identifier: Apache-2.0
1658
+ * Licensed under the Apache License, Version 2.0 (the "License");
1659
+ * You may not use this file except in compliance with the License.
1660
+ * You may obtain a copy of the License at
1661
+ * http://www.apache.org/licenses/LICENSE-2.0
1662
+ * Unless required by applicable law or agreed to in writing, software
1663
+ * distributed under the License is distributed on an "AS IS" BASIS,
1664
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1665
+ * See the License for the specific language governing permissions and
1666
+ * limitations under the License.
1667
+ */
1668
+ /**
1669
+ * List of required environment variables that must be set for this test environment
1670
+ */
1671
+ const MN_REQUIRED_ENVIRONMENT_VARIABLES = ['MN_TEST_INDEXER', 'MN_TEST_INDEXER_WS', 'MN_TEST_NODE', 'MN_TEST_NODE_WS', 'MN_TEST_NETWORK_ID'];
1672
+ /**
1673
+ * Test environment that configures services using environment variables.
1674
+ * Allows specifying custom endpoints through environment variables.
1675
+ */
1676
+ class EnvVarRemoteTestEnvironment extends RemoteTestEnvironment {
1677
+ /**
1678
+ * Returns the configuration for environment services based on environment variables.
1679
+ * Required environment variables:
1680
+ * - MN_TEST_NETWORK_ID: Network identifier (e.g., 'testnet', 'devnet')
1681
+ * - MN_TEST_INDEXER: GraphQL API endpoint for the indexer
1682
+ * - MN_TEST_INDEXER_WS: WebSocket endpoint for the indexer
1683
+ * - MN_TEST_NODE: RPC endpoint for the blockchain node
1684
+ * Optional environment variables:
1685
+ * - MN_TEST_FAUCET: API endpoint for requesting test tokens
1686
+ * @returns {EnvironmentConfiguration} Object containing service URLs from environment variables
1687
+ * @throws {MissingEnvironmentVariable} If any required environment variable is not set
1688
+ */
1689
+ getEnvironmentConfiguration() {
1690
+ // Throw is any of the required MN_* environment variables are missing.
1691
+ MN_REQUIRED_ENVIRONMENT_VARIABLES.forEach((envVar) => {
1692
+ if (!process.env[envVar]) {
1693
+ throw new MissingEnvironmentVariable(envVar);
1694
+ }
1695
+ });
1696
+ return {
1697
+ walletNetworkId: MN_TEST_WALLET_NETWORK_ID,
1698
+ networkId: MN_TEST_NETWORK_ID,
1699
+ indexer: MN_TEST_INDEXER,
1700
+ indexerWS: MN_TEST_INDEXER_WS,
1701
+ node: MN_TEST_NODE,
1702
+ nodeWS: MN_TEST_NODE_WS,
1703
+ faucet: MN_TEST_FAUCET,
1704
+ proofServer: this.proofServerContainer?.getUrl()
1705
+ };
1706
+ }
1707
+ }
1708
+
1709
+ /*
1710
+ * This file is part of midnight-js.
1711
+ * Copyright (C) 2025 Midnight Foundation
1712
+ * SPDX-License-Identifier: Apache-2.0
1713
+ * Licensed under the Apache License, Version 2.0 (the "License");
1714
+ * You may not use this file except in compliance with the License.
1715
+ * You may obtain a copy of the License at
1716
+ * http://www.apache.org/licenses/LICENSE-2.0
1717
+ * Unless required by applicable law or agreed to in writing, software
1718
+ * distributed under the License is distributed on an "AS IS" BASIS,
1719
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1720
+ * See the License for the specific language governing permissions and
1721
+ * limitations under the License.
1722
+ */
1723
+ /**
1724
+ * Configuration class for local test environment implementing EnvironmentConfiguration
1725
+ */
1726
+ class LocalTestConfiguration {
1727
+ walletNetworkId;
1728
+ networkId;
1729
+ indexer;
1730
+ indexerWS;
1731
+ node;
1732
+ nodeWS;
1733
+ proofServer;
1734
+ faucet;
1735
+ /**
1736
+ * Creates a new LocalTestConfiguration instance
1737
+ * @param {ComponentPortsConfiguration} ports - Object containing port numbers for each component
1738
+ */
1739
+ constructor({ indexer, node, proofServer }) {
1740
+ this.walletNetworkId = walletSdkAbstractions.NetworkId.NetworkId.Undeployed;
1741
+ this.networkId = 'undeployed';
1742
+ this.indexer = `http://127.0.0.1:${indexer}/api/v3/graphql`;
1743
+ this.indexerWS = `ws://127.0.0.1:${indexer}/api/v3/graphql/ws`;
1744
+ this.node = `http://127.0.0.1:${node}`;
1745
+ this.nodeWS = `ws://127.0.0.1:${node}`;
1746
+ this.proofServer = `http://127.0.0.1:${proofServer}`;
1747
+ this.faucet = undefined;
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Test environment for local development using Docker containers
1752
+ * Manages containers for node, indexer and proof server components
1753
+ */
1754
+ class LocalTestEnvironment extends TestEnvironment {
1755
+ static MAX_NUMBER_OF_WALLETS = 4;
1756
+ genesisMintWalletSeed = [
1757
+ '0000000000000000000000000000000000000000000000000000000000000002',
1758
+ '0000000000000000000000000000000000000000000000000000000000000001',
1759
+ '0000000000000000000000000000000000000000000000000000000000000003',
1760
+ '0000000000000000000000000000000000000000000000000000000000000004'
1761
+ ];
1762
+ config;
1763
+ environmentConfiguration;
1764
+ dockerEnv;
1765
+ walletProviders;
1766
+ /**
1767
+ * Creates a new LocalTestEnvironment instance
1768
+ * @param {Logger} logger - Logger instance for recording operations
1769
+ */
1770
+ constructor(logger) {
1771
+ super(logger);
1772
+ this.config = getContainersConfiguration().standalone;
1773
+ }
1774
+ /**
1775
+ * Returns the configuration for the testnet environment services.
1776
+ * @returns {EnvironmentConfiguration} Object containing URLs for testnet services:
1777
+ * - indexer: GraphQL API endpoint for the indexer
1778
+ * - indexerWS: WebSocket endpoint for the indexer
1779
+ * - node: RPC endpoint for the blockchain node
1780
+ * - faucet: API endpoint for requesting test tokens
1781
+ * - proofServer: URL for the proof generation server
1782
+ */
1783
+ getEnvironmentConfiguration() {
1784
+ return {
1785
+ walletNetworkId: this.environmentConfiguration?.walletNetworkId,
1786
+ networkId: this.environmentConfiguration?.networkId,
1787
+ indexer: this.environmentConfiguration?.indexer,
1788
+ indexerWS: this.environmentConfiguration?.indexerWS,
1789
+ node: this.environmentConfiguration?.node,
1790
+ nodeWS: this.environmentConfiguration?.nodeWS,
1791
+ faucet: this.environmentConfiguration?.faucet,
1792
+ proofServer: this.environmentConfiguration?.proofServer
1793
+ };
1794
+ }
1795
+ /**
1796
+ * Gets the mapped ports for all containers in the environment
1797
+ * @returns {ComponentPortsConfiguration} Object containing mapped port numbers
1798
+ * @private
1799
+ */
1800
+ getMappedPorts = () => ({
1801
+ indexer: this.dockerEnv
1802
+ .getContainer(`${this.config.container.indexer.name}_${this.uid}`)
1803
+ .getMappedPort(this.config.container.indexer.port),
1804
+ node: this.dockerEnv
1805
+ .getContainer(`${this.config.container.node.name}_${this.uid}`)
1806
+ .getMappedPort(this.config.container.node.port),
1807
+ proofServer: this.dockerEnv
1808
+ .getContainer(`${this.config.container.proofServer.name}_${this.uid}`)
1809
+ .getMappedPort(this.config.container.proofServer.port)
1810
+ });
1811
+ /**
1812
+ * Instead of starting the test environment by building the docker containers
1813
+ * from the default configuration files in this package, start the test environment
1814
+ * by passing an existing {@link StartedDockerComposeEnvironment} along with the
1815
+ * ports for the containers in the environment.
1816
+ *
1817
+ * @param {StartedDockerComposeEnvironment} dockerEnv - A started docker compose environment
1818
+ * @param {ComponentPortsConfiguration} ports - The ports of the containers in the given environment
1819
+ * @returns {Promise<EnvironmentConfiguration>} The environment configuration
1820
+ */
1821
+ startWithInjectedEnvironment = async (dockerEnv, ports) => {
1822
+ this.logger.info(`Starting test environment...`);
1823
+ this.dockerEnv = dockerEnv;
1824
+ this.environmentConfiguration = new LocalTestConfiguration(ports);
1825
+ this.logger.info(`Test environment configuration: ${JSON.stringify(this.environmentConfiguration)}`);
1826
+ return this.environmentConfiguration;
1827
+ };
1828
+ /**
1829
+ * Starts the test environment by creating and configuring Docker containers
1830
+ * @param {ProofServerContainer} maybeProofServerContainer - Optional proof server container
1831
+ * @returns {Promise<EnvironmentConfiguration>} The environment configuration
1832
+ * @throws {Error} If trying to inject proof server container when starting new environment
1833
+ */
1834
+ start = async (maybeProofServerContainer) => {
1835
+ this.logger.info(`Starting test environment... path=${this.config.path}, file=${this.config.fileName}, uid=${this.uid}`);
1836
+ if (maybeProofServerContainer) {
1837
+ throw new Error('Invalid usage, trying to inject proof server container instance when starting new test environment with another proof server...');
1838
+ }
1839
+ this.dockerEnv = await new testcontainers.DockerComposeEnvironment(this.config.path, this.config.fileName)
1840
+ .withWaitStrategy(`${this.config.container.proofServer.name}_${this.uid}`, this.config.container.proofServer.waitStrategy)
1841
+ .withWaitStrategy(`${this.config.container.node.name}_${this.uid}`, this.config.container.node.waitStrategy)
1842
+ .withWaitStrategy(`${this.config.container.indexer.name}_${this.uid}`, this.config.container.indexer.waitStrategy)
1843
+ .withEnvironment({
1844
+ TESTCONTAINERS_UID: this.uid,
1845
+ NETWORK_ID: midnightJsNetworkId.getNetworkId()
1846
+ })
1847
+ .up();
1848
+ this.environmentConfiguration = new LocalTestConfiguration(this.getMappedPorts());
1849
+ this.logger.info(`Test environment configuration: ${JSON.stringify(this.environmentConfiguration)}`);
1850
+ return this.environmentConfiguration;
1851
+ };
1852
+ /**
1853
+ * Shuts down the test environment, closing walletProviders and stopping containers
1854
+ * @returns {Promise<void>}
1855
+ */
1856
+ shutdown = async (saveWalletState) => {
1857
+ this.logger.info(`Shutting down test environment...`);
1858
+ if (this.walletProviders) {
1859
+ if (saveWalletState) {
1860
+ this.logger.warn('Skipping wallet save state as it is obsolete in this context...');
1861
+ }
1862
+ await Promise.all(this.walletProviders.map((wallet) => wallet.stop()));
1863
+ }
1864
+ if (this.dockerEnv) {
1865
+ await this.dockerEnv.down({ timeout: 10000, removeVolumes: true });
1866
+ }
1867
+ };
1868
+ /**
1869
+ * Creates and starts the specified number of wallet providers
1870
+ * @throws {Error} If requested amount exceeds maximum supported walletProviders
1871
+ * @returns {Promise<MidnightWalletProvider[]>} A promise that resolves to an array of started wallets
1872
+ */
1873
+ startMidnightWalletProviders = async (amount = 1, seeds = getEnvVarWalletSeeds()) => {
1874
+ this.logger.info(`Getting ${amount} wallets...`);
1875
+ if (seeds) {
1876
+ this.logger.warn('Provided seeds will be ignored, using genesis mint wallet seeds');
1877
+ }
1878
+ if (amount > LocalTestEnvironment.MAX_NUMBER_OF_WALLETS) {
1879
+ throw new Error(`Maximum supported number of wallets for this environment reached: ${LocalTestEnvironment.MAX_NUMBER_OF_WALLETS}`);
1880
+ }
1881
+ this.walletProviders = await Promise.all(Array.from({ length: amount }).map((_elem, index) => MidnightWalletProvider.build(this.logger, this.environmentConfiguration, this.genesisMintWalletSeed[index])));
1882
+ await Promise.all(this.walletProviders.map((wallet) => wallet.start()));
1883
+ return this.walletProviders;
1884
+ };
1885
+ }
1886
+
1887
+ /*
1888
+ * This file is part of midnight-js.
1889
+ * Copyright (C) 2025 Midnight Foundation
1890
+ * SPDX-License-Identifier: Apache-2.0
1891
+ * Licensed under the Apache License, Version 2.0 (the "License");
1892
+ * You may not use this file except in compliance with the License.
1893
+ * You may obtain a copy of the License at
1894
+ * http://www.apache.org/licenses/LICENSE-2.0
1895
+ * Unless required by applicable law or agreed to in writing, software
1896
+ * distributed under the License is distributed on an "AS IS" BASIS,
1897
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1898
+ * See the License for the specific language governing permissions and
1899
+ * limitations under the License.
1900
+ */
1901
+ /**
1902
+ * Test environment configuration for the Midnight QA network.
1903
+ * Provides URLs and endpoints for QA network services.
1904
+ */
1905
+ class QanetTestEnvironment extends RemoteTestEnvironment {
1906
+ /**
1907
+ * Returns the configuration for the QA network environment services.
1908
+ * @returns {EnvironmentConfiguration} Object containing URLs for QA network services:
1909
+ * - indexer: GraphQL API endpoint for the indexer
1910
+ * - indexerWS: WebSocket endpoint for the indexer
1911
+ * - node: RPC endpoint for the blockchain node
1912
+ * - faucet: API endpoint for requesting test tokens
1913
+ * - proofServer: URL for the proof generation server
1914
+ */
1915
+ getEnvironmentConfiguration() {
1916
+ return {
1917
+ walletNetworkId: walletSdkAbstractions.NetworkId.NetworkId.DevNet,
1918
+ networkId: 'devnet',
1919
+ indexer: 'https://indexer.qanet.dev.midnight.network/api/v3/graphql',
1920
+ indexerWS: 'wss://indexer.qanet.dev.midnight.network/api/v3/graphql/ws',
1921
+ node: 'https://rpc.qanet.dev.midnight.network',
1922
+ nodeWS: 'wss://rpc.qanet.dev.midnight.network',
1923
+ faucet: 'https://faucet.qanet.dev.midnight.network/api/request-tokens',
1924
+ proofServer: this.proofServerContainer?.getUrl()
1925
+ };
1926
+ }
1927
+ }
1928
+
1929
+ /*
1930
+ * This file is part of midnight-js.
1931
+ * Copyright (C) 2025 Midnight Foundation
1932
+ * SPDX-License-Identifier: Apache-2.0
1933
+ * Licensed under the Apache License, Version 2.0 (the "License");
1934
+ * You may not use this file except in compliance with the License.
1935
+ * You may obtain a copy of the License at
1936
+ * http://www.apache.org/licenses/LICENSE-2.0
1937
+ * Unless required by applicable law or agreed to in writing, software
1938
+ * distributed under the License is distributed on an "AS IS" BASIS,
1939
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1940
+ * See the License for the specific language governing permissions and
1941
+ * limitations under the License.
1942
+ */
1943
+ /**
1944
+ * Test environment configuration for the Midnight testnet network.
1945
+ * Provides URLs and endpoints for testnet services.
1946
+ */
1947
+ class Testnet2TestEnvironment extends RemoteTestEnvironment {
1948
+ /**
1949
+ * Returns the configuration for the testnet environment services.
1950
+ * @returns {EnvironmentConfiguration} Object containing URLs for testnet services:
1951
+ * - indexer: GraphQL API endpoint for the indexer
1952
+ * - indexerWS: WebSocket endpoint for the indexer
1953
+ * - node: RPC endpoint for the blockchain node
1954
+ * - faucet: API endpoint for requesting test tokens
1955
+ * - proofServer: URL for the proof generation server
1956
+ */
1957
+ getEnvironmentConfiguration() {
1958
+ return {
1959
+ walletNetworkId: walletSdkAbstractions.NetworkId.NetworkId.TestNet,
1960
+ networkId: 'testnet-02',
1961
+ indexer: 'https://indexer.testnet-02.midnight.network/api/v3/graphql',
1962
+ indexerWS: 'wss://indexer.testnet-02.midnight.network/api/v3/graphql/ws',
1963
+ node: 'https://rpc.testnet-02.midnight.network',
1964
+ nodeWS: 'wss://rpc.testnet-02.midnight.network',
1965
+ faucet: 'https://faucet.testnet-02.midnight.network/api/request-tokens',
1966
+ proofServer: this.proofServerContainer?.getUrl()
1967
+ };
1968
+ }
1969
+ }
1970
+
1971
+ /*
1972
+ * This file is part of midnight-js.
1973
+ * Copyright (C) 2025 Midnight Foundation
1974
+ * SPDX-License-Identifier: Apache-2.0
1975
+ * Licensed under the Apache License, Version 2.0 (the "License");
1976
+ * You may not use this file except in compliance with the License.
1977
+ * You may obtain a copy of the License at
1978
+ * http://www.apache.org/licenses/LICENSE-2.0
1979
+ * Unless required by applicable law or agreed to in writing, software
1980
+ * distributed under the License is distributed on an "AS IS" BASIS,
1981
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1982
+ * See the License for the specific language governing permissions and
1983
+ * limitations under the License.
1984
+ */
1985
+ /**
1986
+ * Returns the appropriate test environment based on the MN_TEST_ENVIRONMENT variable.
1987
+ * @param {Logger} logger - The logger instance to be used by the test environment.
1988
+ * @returns { TestEnvironment} The selected test environment instance.
1989
+ */
1990
+ const getTestEnvironment = (logger) => {
1991
+ const testEnv = getEnvVarEnvironment().toLowerCase();
1992
+ let env;
1993
+ switch (testEnv) {
1994
+ case 'testnet':
1995
+ case 'testnet-02':
1996
+ env = new Testnet2TestEnvironment(logger);
1997
+ midnightJsNetworkId.setNetworkId('test');
1998
+ break;
1999
+ case 'qanet':
2000
+ env = new QanetTestEnvironment(logger);
2001
+ midnightJsNetworkId.setNetworkId('dev');
2002
+ break;
2003
+ case 'env-var-remote':
2004
+ env = new EnvVarRemoteTestEnvironment(logger);
2005
+ midnightJsNetworkId.setNetworkId(env.getEnvironmentConfiguration().networkId);
2006
+ break;
2007
+ default:
2008
+ env = new LocalTestEnvironment(logger);
2009
+ midnightJsNetworkId.setNetworkId('undeployed');
2010
+ }
2011
+ return env;
2012
+ };
2013
+
2014
+ exports.DEFAULT_DUST_OPTIONS = DEFAULT_DUST_OPTIONS;
2015
+ exports.DEFAULT_WALLET_STATE_DIRECTORY = DEFAULT_WALLET_STATE_DIRECTORY;
2016
+ exports.DynamicProofServerContainer = DynamicProofServerContainer;
2017
+ exports.EnvVarRemoteTestEnvironment = EnvVarRemoteTestEnvironment;
2018
+ exports.FaucetClient = FaucetClient;
2019
+ exports.FluentWalletBuilder = FluentWalletBuilder;
2020
+ exports.GzipFile = GzipFile;
2021
+ exports.IndexerClient = IndexerClient;
2022
+ exports.LocalTestConfiguration = LocalTestConfiguration;
2023
+ exports.LocalTestEnvironment = LocalTestEnvironment;
2024
+ exports.MINUTE = MINUTE;
2025
+ exports.MidnightWalletProvider = MidnightWalletProvider;
2026
+ exports.NodeClient = NodeClient;
2027
+ exports.ProofServerClient = ProofServerClient;
2028
+ exports.QanetTestEnvironment = QanetTestEnvironment;
2029
+ exports.RemoteTestEnvironment = RemoteTestEnvironment;
2030
+ exports.StaticProofServerContainer = StaticProofServerContainer;
2031
+ exports.TestEnvironment = TestEnvironment;
2032
+ exports.Testnet2TestEnvironment = Testnet2TestEnvironment;
2033
+ exports.WalletFactory = WalletFactory;
2034
+ exports.WalletSaveStateProvider = WalletSaveStateProvider;
2035
+ exports.WalletSeeds = WalletSeeds;
2036
+ exports.createDefaultTestLogger = createDefaultTestLogger;
2037
+ exports.createLogger = createLogger;
2038
+ exports.defaultContainersConfiguration = defaultContainersConfiguration;
2039
+ exports.delay = delay;
2040
+ exports.deleteDirectory = deleteDirectory;
2041
+ exports.expectFoundAndDeployedStatesEqual = expectFoundAndDeployedStatesEqual;
2042
+ exports.expectFoundAndDeployedTxDataEqual = expectFoundAndDeployedTxDataEqual;
2043
+ exports.expectFoundAndDeployedTxPrivateDataEqual = expectFoundAndDeployedTxPrivateDataEqual;
2044
+ exports.expectFoundAndDeployedTxPublicDataEqual = expectFoundAndDeployedTxPublicDataEqual;
2045
+ exports.expectSuccessfulCallTx = expectSuccessfulCallTx;
2046
+ exports.expectSuccessfulDeployTx = expectSuccessfulDeployTx;
2047
+ exports.expectSuccessfulTxData = expectSuccessfulTxData;
2048
+ exports.extractHostnameAndPort = extractHostnameAndPort;
2049
+ exports.getContainersConfiguration = getContainersConfiguration;
2050
+ exports.getDustSeed = getDustSeed;
2051
+ exports.getInitialShieldedState = getInitialShieldedState;
2052
+ exports.getInitialState = getInitialState;
2053
+ exports.getInitialUnshieldedState = getInitialUnshieldedState;
2054
+ exports.getShieldedSeed = getShieldedSeed;
2055
+ exports.getTestEnvironment = getTestEnvironment;
2056
+ exports.getUnshieldedSeed = getUnshieldedSeed;
2057
+ exports.getWalletStateFilename = getWalletStateFilename;
2058
+ exports.inMemoryPrivateStateProvider = inMemoryPrivateStateProvider;
2059
+ exports.initializeMidnightProviders = initializeMidnightProviders;
2060
+ exports.latestContainersConfiguration = latestContainersConfiguration;
2061
+ exports.logger = logger;
2062
+ exports.setContainersConfiguration = setContainersConfiguration;
2063
+ exports.stateValueEqual = stateValueEqual;
2064
+ exports.syncWallet = syncWallet;
2065
+ exports.txsEqual = txsEqual;
2066
+ exports.waitForFunds = waitForFunds;
2067
+ //# sourceMappingURL=index.cjs.map