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