@midnightntwrk/wallet-sdk-testkit 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @midnightntwrk/wallet-sdk-testkit
2
+
3
+ Reusable wallet-SDK test harness, extracted from `packages/e2e-tests`. Provides environment provisioning, wallet
4
+ bootstrapping, sync waiters, and tx-history assertions as a published package so downstream consumers (e.g. monitoring /
5
+ healthcheck suites) can write their own test scenarios against the same harness instead of vendoring copies of these
6
+ files.
7
+
8
+ ## What's here
9
+
10
+ | Area | Exports |
11
+ | -------------------- | ------------------------------------------------------------------------------------------------------------- |
12
+ | Environment | `createRemoteEnvironment`, `NETWORK_PRESETS`, `makeEnvironment`, `WalletTestEnvironment`, `ResolvedEndpoints` |
13
+ | Environment (Docker) | `createTestContainersEnvironment` — from `@midnightntwrk/wallet-sdk-testkit/testcontainers` |
14
+ | Wallet | `provideWallet`, `initWalletWithSeed`, `saveState`, `WalletInit` |
15
+ | Seeds | `getShieldedSeed`, `getUnshieldedSeed`, `getDustSeed` |
16
+ | Sync waiters | `waitForSyncUnshielded`, `waitForDustBalance`, `waitForTxInHistory`, … |
17
+ | Assertions | `expectSenderShieldedTxHistory`, `expectReceiverUnshieldedTxHistory`, … |
18
+ | Addresses | `validateNetworkInAddress`, `getShieldedAddress`, `getUnshieldedAddress` |
19
+ | Vitest glue | `useWalletTestEnvironment`, `installRetryLogging` |
20
+ | Logging | `logger`, `setLogger`, `getLogger` |
21
+
22
+ ## Key change from `e2e-tests`
23
+
24
+ The old `TestContainersFixture` resolved endpoints from `process.env` (`NETWORK`, `PROOF_SERVER_URL`, `SYNC_CACHE`) and
25
+ mapped container ports. That coupling is gone: a `WalletTestEnvironment` now carries fully-resolved `endpoints`,
26
+ produced either by `createTestContainersEnvironment` (Docker) or `createRemoteEnvironment` (no Docker, point at an
27
+ already-running network). Downstream consumers no longer need to patch this file to inject a proof-server URL.
28
+
29
+ ## Usage — remote network, no Docker
30
+
31
+ ```ts
32
+ import { afterAll } from 'vitest';
33
+ import {
34
+ createRemoteEnvironment,
35
+ useWalletTestEnvironment,
36
+ provideWallet,
37
+ waitForDustBalance,
38
+ } from '@midnightntwrk/wallet-sdk-testkit';
39
+
40
+ const getEnv = useWalletTestEnvironment(() =>
41
+ createRemoteEnvironment({
42
+ network: 'devnet',
43
+ proverUrl: process.env.PROOF_SERVER_URL!, // a running proof server you control
44
+ }),
45
+ );
46
+
47
+ test('wallet reaches a dust balance', async () => {
48
+ const env = getEnv();
49
+ const { wallet } = await provideWallet(env, { seed: MY_SEED });
50
+ afterAll(() => wallet.stop());
51
+ await waitForDustBalance(wallet);
52
+ });
53
+ ```
54
+
55
+ ## Usage — local stack via testcontainers
56
+
57
+ ```ts
58
+ import { useWalletTestEnvironment } from '@midnightntwrk/wallet-sdk-testkit';
59
+ import { createTestContainersEnvironment } from '@midnightntwrk/wallet-sdk-testkit/testcontainers';
60
+
61
+ const getEnv = useWalletTestEnvironment(() => createTestContainersEnvironment({ network: 'undeployed' }));
62
+ ```
63
+
64
+ ## Peer dependencies
65
+
66
+ - `vitest` — required (assertions and the environment hooks use it).
67
+ - `testcontainers` and `@midnightntwrk/wallet-sdk-utilities` — optional; only needed for the `/testcontainers` entry
68
+ point.
@@ -0,0 +1,12 @@
1
+ import { type NetworkId } from '@midnightntwrk/wallet-sdk-abstractions';
2
+ import { ShieldedAddress, UnshieldedAddress } from '@midnightntwrk/wallet-sdk-address-format';
3
+ import { type MidnightNetwork } from './types.js';
4
+ /**
5
+ * Asserts the bech32m network prefix of `address` matches `expectedNetwork`.
6
+ *
7
+ * Replaces the old `TestContainersFixture.network` static read — the expected network is now an explicit argument
8
+ * (typically `env.network`).
9
+ */
10
+ export declare function validateNetworkInAddress(address: string, expectedNetwork: MidnightNetwork): void;
11
+ export declare function getShieldedAddress(networkId: NetworkId.NetworkId, walletAddress: ShieldedAddress): string;
12
+ export declare function getUnshieldedAddress(networkId: NetworkId.NetworkId, walletAddress: UnshieldedAddress): string;
@@ -0,0 +1,30 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { expect } from 'vitest';
14
+ import { MidnightBech32m, ShieldedAddress, UnshieldedAddress } from '@midnightntwrk/wallet-sdk-address-format';
15
+ /**
16
+ * Asserts the bech32m network prefix of `address` matches `expectedNetwork`.
17
+ *
18
+ * Replaces the old `TestContainersFixture.network` static read — the expected network is now an explicit argument
19
+ * (typically `env.network`).
20
+ */
21
+ export function validateNetworkInAddress(address, expectedNetwork) {
22
+ const parsed = MidnightBech32m.parse(address);
23
+ expect(parsed.network).toBe(expectedNetwork);
24
+ }
25
+ export function getShieldedAddress(networkId, walletAddress) {
26
+ return ShieldedAddress.codec.encode(networkId, walletAddress).asString();
27
+ }
28
+ export function getUnshieldedAddress(networkId, walletAddress) {
29
+ return UnshieldedAddress.codec.encode(networkId, walletAddress).asString();
30
+ }
package/dist/core.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './logger.js';
3
+ export * from './environment.js';
4
+ export * from './network.js';
5
+ export * from './seeds.js';
6
+ export * from './primitives.js';
7
+ export * from './wallet.js';
package/dist/core.js ADDED
@@ -0,0 +1,24 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ //
14
+ // Vitest-free harness: types, environment provisioning, wallet bootstrapping, seeds, network
15
+ // helpers, and logging. Import from `@midnightntwrk/wallet-sdk-testkit/core` in non-test contexts
16
+ // (e.g. standalone diagnostic scripts) to avoid transitively loading `vitest`, which the root
17
+ // entry's assertion / sync-waiter / suite-glue helpers require.
18
+ export * from './types.js';
19
+ export * from './logger.js';
20
+ export * from './environment.js';
21
+ export * from './network.js';
22
+ export * from './seeds.js';
23
+ export * from './primitives.js';
24
+ export * from './wallet.js';
@@ -0,0 +1,37 @@
1
+ import { type MidnightNetwork, type RemoteNetwork, type ResolvedEndpoints, type WalletTestEnvironment } from './types.js';
2
+ /** Public service endpoints for each remote network, minus the (caller-supplied) prover URL. */
3
+ export type RemoteNetworkPreset = Omit<ResolvedEndpoints, 'proverUrl'>;
4
+ /**
5
+ * Endpoint presets lifted verbatim from the old `TestContainersFixture` getters. The proof server is intentionally
6
+ * absent — there is no public prover, so every remote environment must be told where to find one.
7
+ */
8
+ export declare const NETWORK_PRESETS: Record<RemoteNetwork, RemoteNetworkPreset>;
9
+ /**
10
+ * Builds a {@link WalletTestEnvironment} from already-resolved endpoints. Shared by the remote factory below and the
11
+ * testcontainers factory (which resolves endpoints from mapped ports first). The two `get*Config` builders are
12
+ * byte-for-byte the behaviour of the old fixture, except the URLs come from `endpoints` instead of `process.env` /
13
+ * container introspection.
14
+ */
15
+ export declare const makeEnvironment: (network: MidnightNetwork, endpoints: ResolvedEndpoints, options?: {
16
+ down?: () => Promise<void>;
17
+ }) => WalletTestEnvironment;
18
+ /** Configuration for {@link createRemoteEnvironment}. */
19
+ export interface RemoteEnvironmentConfig {
20
+ /** Which remote network's endpoint preset to start from. */
21
+ network: RemoteNetwork;
22
+ /** Proof-server base URL. Required — there is no public prover. */
23
+ proverUrl: string;
24
+ /**
25
+ * Optional endpoint overrides. Use this to point at an internal indexer/node (e.g. a monitoring deployment that
26
+ * proxies the public ones) without abandoning the preset for the rest.
27
+ */
28
+ endpoints?: Partial<Omit<ResolvedEndpoints, 'proverUrl'>>;
29
+ }
30
+ /**
31
+ * Creates a no-Docker environment pointed at an already-running network.
32
+ *
33
+ * This is the path downstream consumers (e.g. sentinel monitoring) use: supply a prover URL and a network preset, get
34
+ * back the same `WalletTestEnvironment` the local testcontainers stack produces — with no testcontainers dependency
35
+ * loaded and no `process.env` patching required.
36
+ */
37
+ export declare const createRemoteEnvironment: (config: RemoteEnvironmentConfig) => WalletTestEnvironment;
@@ -0,0 +1,101 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { InMemoryTransactionHistoryStorage, NetworkId } from '@midnightntwrk/wallet-sdk-abstractions';
14
+ import { WalletEntrySchema, mergeWalletEntries } from '@midnightntwrk/wallet-sdk-facade';
15
+ /**
16
+ * Endpoint presets lifted verbatim from the old `TestContainersFixture` getters. The proof server is intentionally
17
+ * absent — there is no public prover, so every remote environment must be told where to find one.
18
+ */
19
+ export const NETWORK_PRESETS = {
20
+ devnet: {
21
+ networkId: NetworkId.NetworkId.DevNet,
22
+ indexerHttpUrl: 'https://indexer.devnet.midnight.network/api/v4/graphql',
23
+ indexerWsUrl: 'wss://indexer.devnet.midnight.network/api/v4/graphql/ws',
24
+ nodeUrl: 'wss://rpc.devnet.midnight.network',
25
+ },
26
+ qanet: {
27
+ networkId: NetworkId.NetworkId.QaNet,
28
+ indexerHttpUrl: 'https://indexer.qanet.midnight.network/api/v4/graphql',
29
+ // NB: qanet ws is served from the `indexer-blue` host — preserved from upstream.
30
+ indexerWsUrl: 'wss://indexer-blue.qanet.midnight.network/api/v4/graphql/ws',
31
+ nodeUrl: 'wss://rpc.qanet.midnight.network',
32
+ },
33
+ preview: {
34
+ networkId: NetworkId.NetworkId.Preview,
35
+ indexerHttpUrl: 'https://indexer.preview.midnight.network/api/v4/graphql',
36
+ indexerWsUrl: 'wss://indexer.preview.midnight.network/api/v4/graphql/ws',
37
+ nodeUrl: 'wss://rpc.preview.midnight.network',
38
+ },
39
+ preprod: {
40
+ networkId: NetworkId.NetworkId.PreProd,
41
+ indexerHttpUrl: 'https://indexer.preprod.midnight.network/api/v4/graphql',
42
+ indexerWsUrl: 'wss://indexer.preprod.midnight.network/api/v4/graphql/ws',
43
+ nodeUrl: 'wss://rpc.preprod.midnight.network',
44
+ },
45
+ };
46
+ /**
47
+ * Builds a {@link WalletTestEnvironment} from already-resolved endpoints. Shared by the remote factory below and the
48
+ * testcontainers factory (which resolves endpoints from mapped ports first). The two `get*Config` builders are
49
+ * byte-for-byte the behaviour of the old fixture, except the URLs come from `endpoints` instead of `process.env` /
50
+ * container introspection.
51
+ */
52
+ export const makeEnvironment = (network, endpoints, options = {}) => ({
53
+ network,
54
+ endpoints,
55
+ getWalletConfig() {
56
+ return {
57
+ indexerClientConnection: {
58
+ indexerHttpUrl: endpoints.indexerHttpUrl,
59
+ indexerWsUrl: endpoints.indexerWsUrl,
60
+ },
61
+ provingServerUrl: new URL(endpoints.proverUrl),
62
+ relayURL: new URL(endpoints.nodeUrl),
63
+ networkId: endpoints.networkId,
64
+ txHistoryStorage: new InMemoryTransactionHistoryStorage(WalletEntrySchema, mergeWalletEntries),
65
+ };
66
+ },
67
+ getDustWalletConfig() {
68
+ return {
69
+ networkId: endpoints.networkId,
70
+ costParameters: {
71
+ feeBlocksMargin: 5,
72
+ },
73
+ txHistoryStorage: new InMemoryTransactionHistoryStorage(WalletEntrySchema, mergeWalletEntries),
74
+ indexerClientConnection: {
75
+ indexerHttpUrl: endpoints.indexerHttpUrl,
76
+ },
77
+ };
78
+ },
79
+ down: options.down ?? (async () => { }),
80
+ });
81
+ /**
82
+ * Creates a no-Docker environment pointed at an already-running network.
83
+ *
84
+ * This is the path downstream consumers (e.g. sentinel monitoring) use: supply a prover URL and a network preset, get
85
+ * back the same `WalletTestEnvironment` the local testcontainers stack produces — with no testcontainers dependency
86
+ * loaded and no `process.env` patching required.
87
+ */
88
+ export const createRemoteEnvironment = (config) => {
89
+ if (!config.proverUrl) {
90
+ throw new Error('createRemoteEnvironment: `proverUrl` is required (there is no public proof server).');
91
+ }
92
+ const preset = NETWORK_PRESETS[config.network];
93
+ const endpoints = {
94
+ networkId: config.endpoints?.networkId ?? preset.networkId,
95
+ proverUrl: config.proverUrl,
96
+ indexerHttpUrl: config.endpoints?.indexerHttpUrl ?? preset.indexerHttpUrl,
97
+ indexerWsUrl: config.endpoints?.indexerWsUrl ?? preset.indexerWsUrl,
98
+ nodeUrl: config.endpoints?.nodeUrl ?? preset.nodeUrl,
99
+ };
100
+ return makeEnvironment(config.network, endpoints);
101
+ };
@@ -0,0 +1,5 @@
1
+ export * from './core.js';
2
+ export * from './addresses.js';
3
+ export * from './state-waiters.js';
4
+ export * from './tx-history-asserts.js';
5
+ export * from './vitest.js';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ //
14
+ // Full entry point. Re-exports the vitest-free core (environment, wallet, seeds, network, logging)
15
+ // plus the vitest-coupled helpers (assertions, sync waiters, address validation, suite glue).
16
+ // Importing this pulls in `vitest`; non-test consumers should import from
17
+ // `@midnightntwrk/wallet-sdk-testkit/core` instead.
18
+ //
19
+ // The Docker-backed environment lives at `@midnightntwrk/wallet-sdk-testkit/testcontainers` so it
20
+ // (and the `testcontainers` peer dependency) is only loaded when actually needed.
21
+ export * from './core.js';
22
+ // vitest-coupled (each imports from 'vitest' at module load):
23
+ export * from './addresses.js';
24
+ export * from './state-waiters.js';
25
+ export * from './tx-history-asserts.js';
26
+ export * from './vitest.js';
@@ -0,0 +1,6 @@
1
+ import { type Logger } from 'pino';
2
+ /** Replace the logger used by all testkit helpers. Pass any pino-compatible logger. */
3
+ export declare const setLogger: (next: Logger) => void;
4
+ /** Returns the currently-active logger instance. */
5
+ export declare const getLogger: () => Logger;
6
+ export declare const logger: Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,36 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import pinoPretty from 'pino-pretty';
14
+ import pino from 'pino';
15
+ // Unlike the original e2e-tests logger, a published package must not write log files to a
16
+ // path relative to its own source. The default here is a plain pretty stdout logger; consumers
17
+ // that want file output (or structured JSON, or their own pino instance) call `setLogger`.
18
+ const createDefaultLogger = () => {
19
+ const pretty = pinoPretty({ colorize: true, sync: true });
20
+ return pino({ level: process.env['WALLET_TESTKIT_LOG_LEVEL'] ?? 'info', depthLimit: 20 }, pretty);
21
+ };
22
+ let current = createDefaultLogger();
23
+ /** Replace the logger used by all testkit helpers. Pass any pino-compatible logger. */
24
+ export const setLogger = (next) => {
25
+ current = next;
26
+ };
27
+ /** Returns the currently-active logger instance. */
28
+ export const getLogger = () => current;
29
+ // A stable proxy so existing call sites (`logger.info(...)`) keep working *and* pick up
30
+ // whatever `setLogger` installed, without every helper having to call `getLogger()`.
31
+ export const logger = new Proxy({}, {
32
+ get(_target, property) {
33
+ const value = current[property];
34
+ return typeof value === 'function' ? value.bind(current) : value;
35
+ },
36
+ });
@@ -0,0 +1,6 @@
1
+ export declare const sleep: (secs: number) => Promise<void>;
2
+ /**
3
+ * Waits for the blockchain to produce at least one new block by polling the indexer for the current block height.
4
+ * Resolves as soon as the height increases from its initial value.
5
+ */
6
+ export declare const waitForBlockAdvancement: (indexerHttpUrl: string, timeoutMs?: number) => Promise<void>;
@@ -0,0 +1,43 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { BlockHash } from '@midnightntwrk/wallet-sdk-indexer-client';
14
+ import { QueryRunner } from '@midnightntwrk/wallet-sdk-indexer-client/effect';
15
+ import { logger } from './logger.js';
16
+ export const sleep = (secs) => {
17
+ return new Promise((resolve) => setTimeout(resolve, secs * 1000));
18
+ };
19
+ const fetchBlockHeight = async (indexerHttpUrl) => {
20
+ const result = await QueryRunner.runPromise(BlockHash, { offset: null }, { url: indexerHttpUrl });
21
+ if (!result.block)
22
+ throw new Error('No block returned from indexer');
23
+ return result.block.height;
24
+ };
25
+ /**
26
+ * Waits for the blockchain to produce at least one new block by polling the indexer for the current block height.
27
+ * Resolves as soon as the height increases from its initial value.
28
+ */
29
+ export const waitForBlockAdvancement = async (indexerHttpUrl, timeoutMs = 60_000) => {
30
+ const initialHeight = await fetchBlockHeight(indexerHttpUrl);
31
+ logger.info(`Waiting for block advancement beyond height ${initialHeight}...`);
32
+ const start = Date.now();
33
+ while (Date.now() - start < timeoutMs) {
34
+ await sleep(2);
35
+ const currentHeight = await fetchBlockHeight(indexerHttpUrl);
36
+ logger.info(`Current block height: ${currentHeight} (waiting for > ${initialHeight})`);
37
+ if (currentHeight > initialHeight) {
38
+ logger.info('Block advancement detected');
39
+ return;
40
+ }
41
+ }
42
+ throw new Error(`Timed out waiting for block advancement beyond height ${initialHeight} after ${timeoutMs}ms`);
43
+ };
@@ -0,0 +1,2 @@
1
+ export declare const tNightAmount: (amount: bigint) => bigint;
2
+ export declare const isArrayUnique: (arr: unknown[]) => boolean;
@@ -0,0 +1,14 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export const tNightAmount = (amount) => amount * 10n ** 6n;
14
+ export const isArrayUnique = (arr) => Array.isArray(arr) && new Set(arr).size === arr.length;
@@ -0,0 +1,14 @@
1
+ import { type WalletTestEnvironment } from '../types.js';
2
+ /** Dependencies the dust scenarios need from the consumer. */
3
+ export interface DustScenarioDeps {
4
+ /** Accessor for the active environment (typically the return of `useWalletTestEnvironment`). */
5
+ getEnv: () => WalletTestEnvironment;
6
+ /** Hex seed of a funded wallet holding registerable Night UTXOs. (Was the `SEED` env var.) */
7
+ seed: string;
8
+ /** Optional dir to persist/restore wallet state across runs. (Was the `SYNC_CACHE` env var.) */
9
+ syncCacheDir?: string | undefined;
10
+ /** Per-test timeout in ms. Defaults to the upstream value of 1 hour. */
11
+ timeout?: number | undefined;
12
+ }
13
+ /** Registers the dust register/deregister healthchecks under a `describe('Dust tests')` block. */
14
+ export declare function registerDustHealthchecks({ getEnv, seed, syncCacheDir, timeout }: DustScenarioDeps): void;
@@ -0,0 +1,91 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ //
14
+ // Dust healthcheck scenarios. The test bodies are single-sourced here and registered by both the
15
+ // upstream e2e-tests suite and downstream consumers (e.g. sentinel monitoring), each supplying its
16
+ // own environment via `getEnv`. The `@healthcheck`-tagged test names are preserved so consumers can
17
+ // select them with `vitest run -t @healthcheck`.
18
+ import { describe, test, expect, beforeEach, afterEach } from 'vitest';
19
+ import { inspect } from 'node:util';
20
+ import * as rx from 'rxjs';
21
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
22
+ import { provideWallet, saveState } from '../wallet.js';
23
+ import { logger } from '../logger.js';
24
+ /** Registers the dust register/deregister healthchecks under a `describe('Dust tests')` block. */
25
+ export function registerDustHealthchecks({ getEnv, seed, syncCacheDir, timeout = 3_600_000 }) {
26
+ describe('Dust tests', () => {
27
+ const unshieldedTokenRaw = ledger.unshieldedToken().raw;
28
+ let wallet;
29
+ let filenameWallet;
30
+ beforeEach(async () => {
31
+ const env = getEnv();
32
+ filenameWallet = `${seed.substring(0, 7)}-${env.network}.state`;
33
+ wallet = await provideWallet(env, { seed, syncCacheDir, filename: filenameWallet });
34
+ });
35
+ afterEach(async () => {
36
+ if (syncCacheDir) {
37
+ await saveState(wallet.wallet, syncCacheDir, filenameWallet);
38
+ }
39
+ await wallet.wallet.stop();
40
+ logger.info('Wallet stopped');
41
+ });
42
+ test('Able to register Night tokens for Dust generation @healthcheck', async () => {
43
+ const initialState = await wallet.wallet.waitForSyncedState();
44
+ const initialUnshieldedBalance = initialState.unshielded.balances[unshieldedTokenRaw];
45
+ const initialDustBalance = initialState.dust.balance(new Date());
46
+ logger.info(`Wallet: ${initialUnshieldedBalance} unshielded tokens`);
47
+ logger.info(`wallet dust balance: ${initialDustBalance}`);
48
+ logger.info(`Wallet total unshielded coins: ${initialState.unshielded.availableCoins.length}`);
49
+ logger.info(inspect(initialState.unshielded.availableCoins, { depth: null }));
50
+ const unregisteredNightUtxos = initialState.unshielded.availableCoins.filter((coin) => coin.utxo.type === unshieldedTokenRaw && coin.meta.registeredForDustGeneration === false);
51
+ const registeredNightUtxos = initialState.unshielded.availableCoins.filter((coin) => coin.utxo.type === unshieldedTokenRaw && coin.meta.registeredForDustGeneration === true);
52
+ const unregisteredUtxosNumber = unregisteredNightUtxos.length;
53
+ expect(unregisteredUtxosNumber, 'Not enough unregistered UTXOs found').toBeGreaterThan(1);
54
+ logger.info(`utxo length: ${unregisteredUtxosNumber}`);
55
+ const firstTwoNightUtxos = unregisteredNightUtxos.slice(0, 2);
56
+ logger.info(`Registering UTXOs: ${inspect(firstTwoNightUtxos, { depth: null })}`);
57
+ const dustRegistrationRecipe = await wallet.wallet.registerNightUtxosForDustGeneration(firstTwoNightUtxos, wallet.unshieldedKeystore.getPublicKey(), (payload) => wallet.unshieldedKeystore.signData(payload));
58
+ const finalizedDustTx = await wallet.wallet.finalizeRecipe(dustRegistrationRecipe);
59
+ const dustRegistrationTxid = await wallet.wallet.submitTransaction(finalizedDustTx);
60
+ expect(dustRegistrationTxid).toBeDefined();
61
+ logger.info(`Dust registration tx id: ${dustRegistrationTxid}`);
62
+ const finalWalletState = await rx.firstValueFrom(wallet.wallet.state().pipe(rx.tap((s) => {
63
+ const registeredTokens = s.unshielded.availableCoins.filter((coin) => coin.utxo.type === unshieldedTokenRaw && coin.meta.registeredForDustGeneration === true);
64
+ logger.info(`registered tokens: ${registeredTokens.length}`);
65
+ }), rx.filter((s) => s.unshielded.availableCoins.filter((coin) => coin.utxo.type === unshieldedTokenRaw && coin.meta.registeredForDustGeneration === true).length >=
66
+ registeredNightUtxos.length + 2 && s.isSynced === true)));
67
+ const finalNightUtxos = finalWalletState.unshielded.availableCoins.filter((coin) => coin.utxo.type === unshieldedTokenRaw && coin.meta.registeredForDustGeneration === true);
68
+ expect(finalNightUtxos.length).toBe(registeredNightUtxos.length + 2);
69
+ }, timeout);
70
+ test('Able to deregister night tokens for dust decay @healthcheck', async () => {
71
+ const initialWalletState = await wallet.wallet.waitForSyncedState();
72
+ const initialDustBalance = initialWalletState.dust.balance(new Date());
73
+ logger.info(`Initial Dust Balance: ${initialDustBalance}`);
74
+ const registeredNightUtxos = initialWalletState.unshielded.availableCoins.filter((coin) => coin.utxo.type === unshieldedTokenRaw && coin.meta.registeredForDustGeneration === true);
75
+ logger.info(`Registered night UTXOs: ${inspect(registeredNightUtxos, { depth: null })}`);
76
+ expect(registeredNightUtxos.length, 'Not enough registered UTXOs found').toBeGreaterThan(1);
77
+ const firstTwoRegisteredNightUtxos = registeredNightUtxos.slice(0, 2);
78
+ const dustDeregistrationRecipe = await wallet.wallet.deregisterFromDustGeneration(firstTwoRegisteredNightUtxos, wallet.unshieldedKeystore.getPublicKey(), (payload) => wallet.unshieldedKeystore.signData(payload));
79
+ const balancedTransactionRecipe = await wallet.wallet.balanceUnprovenTransaction(dustDeregistrationRecipe.transaction, {
80
+ shieldedSecretKeys: wallet.shieldedSecretKeys,
81
+ dustSecretKey: wallet.dustSecretKey,
82
+ }, {
83
+ ttl: new Date(Date.now() + 30 * 60 * 1000),
84
+ });
85
+ const finalizedDustTx = await wallet.wallet.finalizeRecipe(balancedTransactionRecipe);
86
+ const dustDeregistrationTxid = await wallet.wallet.submitTransaction(finalizedDustTx);
87
+ expect(dustDeregistrationTxid).toBeDefined();
88
+ logger.info(`Dust de-registration tx id: ${dustDeregistrationTxid}`);
89
+ }, timeout);
90
+ });
91
+ }
@@ -0,0 +1,2 @@
1
+ export * from './dust.js';
2
+ export * from './token-transfer.js';
@@ -0,0 +1,19 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ //
14
+ // Shared, single-sourced test scenarios. Each `register*` function declares its `describe/test`
15
+ // blocks; consumers supply the environment and seeds. Lives behind the `/scenarios` entry point
16
+ // because importing it has the side effect of registering vitest suites — only do so from a file
17
+ // the consumer's vitest discovers as a test.
18
+ export * from './dust.js';
19
+ export * from './token-transfer.js';
@@ -0,0 +1,39 @@
1
+ import { type NetworkId } from '@midnightntwrk/wallet-sdk-abstractions';
2
+ import { type WalletTestEnvironment } from '../types.js';
3
+ import { type WalletInit } from '../wallet.js';
4
+ /** Dependencies the token-transfer setup + scenarios need from the consumer. */
5
+ export interface TokenTransferScenarioDeps {
6
+ /** Accessor for the active environment (typically the return of `useWalletTestEnvironment`). */
7
+ getEnv: () => WalletTestEnvironment;
8
+ /** Hex seed of the primary funded wallet. (Was the `SEED` env var.) */
9
+ fundedSeed: string;
10
+ /** Hex seed of the second wallet. (Was the `SEED2` env var.) */
11
+ secondSeed: string;
12
+ /** Optional dir to persist/restore wallet state across runs. (Was the `SYNC_CACHE` env var.) */
13
+ syncCacheDir?: string | undefined;
14
+ /** Timeout for the sync-heavy `beforeEach` and the healthcheck test in ms. Defaults to 1 hour. */
15
+ syncTimeout?: number | undefined;
16
+ /** Timeout for the `afterEach` teardown in ms. Defaults to 10 minutes. */
17
+ timeout?: number | undefined;
18
+ }
19
+ /** Accessors returned by {@link useTokenTransferWallets}, valid inside `test`/`it` bodies. */
20
+ export interface TokenTransferWallets {
21
+ /** The wallet holding the larger shielded balance at setup time. */
22
+ getSender: () => WalletInit;
23
+ /** The other wallet. */
24
+ getReceiver: () => WalletInit;
25
+ getNetworkId: () => NetworkId.NetworkId;
26
+ }
27
+ /**
28
+ * Registers the shared two-wallet `beforeEach`/`afterEach` used by every token-transfer test and returns accessors.
29
+ * Call once inside a `describe`. Both the healthcheck scenario below and the upstream-only token-transfer tests reuse
30
+ * this so the sender/receiver selection lives in one place.
31
+ */
32
+ export declare function useTokenTransferWallets({ getEnv, fundedSeed, secondSeed, syncCacheDir, syncTimeout, timeout, }: TokenTransferScenarioDeps): TokenTransferWallets;
33
+ /**
34
+ * Registers the single `@healthcheck`-tagged token-transfer test under a `describe('Token transfer')` block. This is
35
+ * the only token-transfer test shipped in the testkit; downstream monitoring selects it with `vitest run -t
36
+ *
37
+ * @healthcheck`.
38
+ */
39
+ export declare function registerTokenTransferHealthchecks(deps: TokenTransferScenarioDeps): void;