@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.
- package/README.md +256 -0
- package/dist/assertions.d.ts +15 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/client/faucet-client.d.ts +30 -0
- package/dist/client/faucet-client.d.ts.map +1 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/indexer-client.d.ts +18 -0
- package/dist/client/indexer-client.d.ts.map +1 -0
- package/dist/client/node-client.d.ts +66 -0
- package/dist/client/node-client.d.ts.map +1 -0
- package/dist/client/proof-server-client.d.ts +25 -0
- package/dist/client/proof-server-client.d.ts.map +1 -0
- package/dist/configuration-types.d.ts +68 -0
- package/dist/configuration-types.d.ts.map +1 -0
- package/dist/configuration.d.ts +6 -0
- package/dist/configuration.d.ts.map +1 -0
- package/dist/contract/contract-types.d.ts +16 -0
- package/dist/contract/contract-types.d.ts.map +1 -0
- package/dist/contract/in-memory-private-state-provider.d.ts +10 -0
- package/dist/contract/in-memory-private-state-provider.d.ts.map +1 -0
- package/dist/contract/index.d.ts +4 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/providers.d.ts +24 -0
- package/dist/contract/providers.d.ts.map +1 -0
- package/dist/env-vars.d.ts +10 -0
- package/dist/env-vars.d.ts.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.cjs +2067 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +789 -0
- package/dist/index.d.mts +789 -0
- package/dist/index.d.ts +789 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1993 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/proof-server-container.d.ts +83 -0
- package/dist/proof-server-container.d.ts.map +1 -0
- package/dist/test-environment/environment-configuration.d.ts +23 -0
- package/dist/test-environment/environment-configuration.d.ts.map +1 -0
- package/dist/test-environment/environment-provider.d.ts +9 -0
- package/dist/test-environment/environment-provider.d.ts.map +1 -0
- package/dist/test-environment/index.d.ts +4 -0
- package/dist/test-environment/index.d.ts.map +1 -0
- package/dist/test-environment/test-environments/env-var-remote-test-environment.d.ts +22 -0
- package/dist/test-environment/test-environments/env-var-remote-test-environment.d.ts.map +1 -0
- package/dist/test-environment/test-environments/index.d.ts +7 -0
- package/dist/test-environment/test-environments/index.d.ts.map +1 -0
- package/dist/test-environment/test-environments/local-test-environment.d.ts +96 -0
- package/dist/test-environment/test-environments/local-test-environment.d.ts.map +1 -0
- package/dist/test-environment/test-environments/qanet-test-environment.d.ts +19 -0
- package/dist/test-environment/test-environments/qanet-test-environment.d.ts.map +1 -0
- package/dist/test-environment/test-environments/remote-test-environment.d.ts +40 -0
- package/dist/test-environment/test-environments/remote-test-environment.d.ts.map +1 -0
- package/dist/test-environment/test-environments/test-environment.d.ts +50 -0
- package/dist/test-environment/test-environments/test-environment.d.ts.map +1 -0
- package/dist/test-environment/test-environments/testnet2-test-environment.d.ts +19 -0
- package/dist/test-environment/test-environments/testnet2-test-environment.d.ts.map +1 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/wallet/bigint-serialization.d.ts +7 -0
- package/dist/wallet/bigint-serialization.d.ts.map +1 -0
- package/dist/wallet/fluent-wallet-builder.d.ts +23 -0
- package/dist/wallet/fluent-wallet-builder.d.ts.map +1 -0
- package/dist/wallet/gzip-file.d.ts +29 -0
- package/dist/wallet/gzip-file.d.ts.map +1 -0
- package/dist/wallet/index.d.ts +9 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/midnight-wallet-provider.d.ts +28 -0
- package/dist/wallet/midnight-wallet-provider.d.ts.map +1 -0
- package/dist/wallet/wallet-configuration-mapper.d.ts +10 -0
- package/dist/wallet/wallet-configuration-mapper.d.ts.map +1 -0
- package/dist/wallet/wallet-factory.d.ts +21 -0
- package/dist/wallet/wallet-factory.d.ts.map +1 -0
- package/dist/wallet/wallet-seed.d.ts +13 -0
- package/dist/wallet/wallet-seed.d.ts.map +1 -0
- package/dist/wallet/wallet-state-provider.d.ts +43 -0
- package/dist/wallet/wallet-state-provider.d.ts.map +1 -0
- package/dist/wallet/wallet-utils.d.ts +11 -0
- package/dist/wallet/wallet-utils.d.ts.map +1 -0
- 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
|