@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 +68 -0
- package/dist/addresses.d.ts +12 -0
- package/dist/addresses.js +30 -0
- package/dist/core.d.ts +7 -0
- package/dist/core.js +24 -0
- package/dist/environment.d.ts +37 -0
- package/dist/environment.js +101 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +26 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +36 -0
- package/dist/network.d.ts +6 -0
- package/dist/network.js +43 -0
- package/dist/primitives.d.ts +2 -0
- package/dist/primitives.js +14 -0
- package/dist/scenarios/dust.d.ts +14 -0
- package/dist/scenarios/dust.js +91 -0
- package/dist/scenarios/index.d.ts +2 -0
- package/dist/scenarios/index.js +19 -0
- package/dist/scenarios/token-transfer.d.ts +39 -0
- package/dist/scenarios/token-transfer.js +212 -0
- package/dist/seeds.d.ts +3 -0
- package/dist/seeds.js +27 -0
- package/dist/state-waiters.d.ts +69 -0
- package/dist/state-waiters.js +100 -0
- package/dist/testcontainers.d.ts +18 -0
- package/dist/testcontainers.js +87 -0
- package/dist/tx-history-asserts.d.ts +25 -0
- package/dist/tx-history-asserts.js +92 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.js +1 -0
- package/dist/vitest.d.ts +15 -0
- package/dist/vitest.js +62 -0
- package/dist/wallet.d.ts +34 -0
- package/dist/wallet.js +213 -0
- package/package.json +85 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type NetworkId } from '@midnightntwrk/wallet-sdk-abstractions';
|
|
2
|
+
import { type DefaultV1Configuration } from '@midnightntwrk/wallet-sdk-shielded/v1';
|
|
3
|
+
import { type DefaultV1Configuration as DefaultDustV1Configuration } from '@midnightntwrk/wallet-sdk-dust-wallet/v1';
|
|
4
|
+
import { type DefaultProvingConfiguration } from '@midnightntwrk/wallet-sdk-capabilities/proving';
|
|
5
|
+
import { type DefaultSubmissionConfiguration } from '@midnightntwrk/wallet-sdk-capabilities/submission';
|
|
6
|
+
/** Networks a wallet test environment can target. */
|
|
7
|
+
export type MidnightNetwork = 'undeployed' | 'qanet' | 'devnet' | 'preview' | 'preprod';
|
|
8
|
+
/** A remote (already-running) network — anything other than the local testcontainers stack. */
|
|
9
|
+
export type RemoteNetwork = Exclude<MidnightNetwork, 'undeployed'>;
|
|
10
|
+
/**
|
|
11
|
+
* The fully-resolved set of service endpoints a wallet needs. This is the single source of truth that replaces the
|
|
12
|
+
* env-var + container-port resolution previously baked into `TestContainersFixture`. Callers either let an environment
|
|
13
|
+
* factory resolve these or supply them directly.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResolvedEndpoints {
|
|
16
|
+
readonly networkId: NetworkId.NetworkId;
|
|
17
|
+
/** Proof-server base URL, e.g. `http://localhost:6300`. No public default exists. */
|
|
18
|
+
readonly proverUrl: string;
|
|
19
|
+
readonly indexerHttpUrl: string;
|
|
20
|
+
readonly indexerWsUrl: string;
|
|
21
|
+
/** Node RPC (relay) URL, e.g. `wss://rpc.devnet.midnight.network`. */
|
|
22
|
+
readonly nodeUrl: string;
|
|
23
|
+
}
|
|
24
|
+
/** Shielded + submission + proving configuration consumed by `ShieldedWallet`/`WalletFacade`. */
|
|
25
|
+
export type WalletConfiguration = DefaultV1Configuration & DefaultSubmissionConfiguration & DefaultProvingConfiguration;
|
|
26
|
+
/** Dust wallet configuration consumed by `DustWallet`. */
|
|
27
|
+
export type DustWalletConfiguration = DefaultDustV1Configuration;
|
|
28
|
+
/**
|
|
29
|
+
* A provisioned wallet test environment. Produced by {@link createRemoteEnvironment} (no Docker) or
|
|
30
|
+
* `createTestContainersEnvironment` (from the `/testcontainers` entry point). Replaces the old `TestContainersFixture`
|
|
31
|
+
* class — the wallet-config builders are identical, but the endpoints are injected rather than read from `process.env`
|
|
32
|
+
* / mapped container ports.
|
|
33
|
+
*/
|
|
34
|
+
export interface WalletTestEnvironment {
|
|
35
|
+
readonly network: MidnightNetwork;
|
|
36
|
+
readonly endpoints: ResolvedEndpoints;
|
|
37
|
+
getWalletConfig(): WalletConfiguration;
|
|
38
|
+
getDustWalletConfig(): DustWalletConfiguration;
|
|
39
|
+
/** Tears down any resources (containers) the environment owns. No-op for remote environments. */
|
|
40
|
+
down(): Promise<void>;
|
|
41
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type WalletTestEnvironment } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Registers `beforeAll`/`afterAll` hooks that build a {@link WalletTestEnvironment} from `factory` and tear it down
|
|
4
|
+
* after the suite. Returns an accessor for use inside tests.
|
|
5
|
+
*
|
|
6
|
+
* Replaces the old `useTestContainersFixture()`, but the environment is now whatever the factory returns — e.g.
|
|
7
|
+
* `createRemoteEnvironment({ network: 'devnet', proverUrl })` (no Docker) or `createTestContainersEnvironment({
|
|
8
|
+
* network: 'undeployed' })` (from the `/testcontainers` entry).
|
|
9
|
+
*/
|
|
10
|
+
export declare const useWalletTestEnvironment: (factory: () => Promise<WalletTestEnvironment> | WalletTestEnvironment) => (() => WalletTestEnvironment);
|
|
11
|
+
/**
|
|
12
|
+
* Installs a `beforeEach` hook that logs detailed failure information on each failed attempt of a retried test. Mirrors
|
|
13
|
+
* the old `setup-retry-logging.ts` setup file; call once per suite (or wire it into a vitest `setupFiles` entry).
|
|
14
|
+
*/
|
|
15
|
+
export declare const installRetryLogging: () => void;
|
package/dist/vitest.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
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 { afterAll, beforeAll, beforeEach, onTestFailed } from 'vitest';
|
|
14
|
+
import { logger } from './logger.js';
|
|
15
|
+
/**
|
|
16
|
+
* Registers `beforeAll`/`afterAll` hooks that build a {@link WalletTestEnvironment} from `factory` and tear it down
|
|
17
|
+
* after the suite. Returns an accessor for use inside tests.
|
|
18
|
+
*
|
|
19
|
+
* Replaces the old `useTestContainersFixture()`, but the environment is now whatever the factory returns — e.g.
|
|
20
|
+
* `createRemoteEnvironment({ network: 'devnet', proverUrl })` (no Docker) or `createTestContainersEnvironment({
|
|
21
|
+
* network: 'undeployed' })` (from the `/testcontainers` entry).
|
|
22
|
+
*/
|
|
23
|
+
export const useWalletTestEnvironment = (factory) => {
|
|
24
|
+
let environment;
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
environment = await factory();
|
|
27
|
+
logger.info(`Wallet test environment ready (network=${environment.network})`);
|
|
28
|
+
}, 120_000);
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
logger.info('Tearing down wallet test environment...');
|
|
31
|
+
await environment?.down();
|
|
32
|
+
logger.info('Wallet test environment torn down');
|
|
33
|
+
}, 60_000);
|
|
34
|
+
return () => {
|
|
35
|
+
if (!environment) {
|
|
36
|
+
throw new Error('Wallet test environment accessed before beforeAll completed');
|
|
37
|
+
}
|
|
38
|
+
return environment;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Installs a `beforeEach` hook that logs detailed failure information on each failed attempt of a retried test. Mirrors
|
|
43
|
+
* the old `setup-retry-logging.ts` setup file; call once per suite (or wire it into a vitest `setupFiles` entry).
|
|
44
|
+
*/
|
|
45
|
+
export const installRetryLogging = () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
onTestFailed(({ task: failedTask }) => {
|
|
48
|
+
const attempt = (failedTask.result?.retryCount ?? 0) + 1;
|
|
49
|
+
const retry = failedTask.retry;
|
|
50
|
+
const maxRetries = typeof retry === 'number' ? retry : (retry?.count ?? 0);
|
|
51
|
+
if (maxRetries > 0) {
|
|
52
|
+
logger.error(`Test "${failedTask.name}" failed on attempt ${attempt}/${maxRetries + 1}:`);
|
|
53
|
+
for (const error of failedTask.result?.errors ?? []) {
|
|
54
|
+
logger.error(error.message);
|
|
55
|
+
if (error.stack) {
|
|
56
|
+
logger.error(error.stack);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
};
|
package/dist/wallet.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
2
|
+
import { WalletFacade } from '@midnightntwrk/wallet-sdk-facade';
|
|
3
|
+
import { type UnshieldedKeystore } from '@midnightntwrk/wallet-sdk-unshielded-wallet';
|
|
4
|
+
import { type WalletTestEnvironment } from './types.js';
|
|
5
|
+
export type WalletInit = {
|
|
6
|
+
wallet: WalletFacade;
|
|
7
|
+
shieldedSecretKeys: ledger.ZswapSecretKeys;
|
|
8
|
+
dustSecretKey: ledger.DustSecretKey;
|
|
9
|
+
unshieldedKeystore: UnshieldedKeystore;
|
|
10
|
+
};
|
|
11
|
+
/** Options for {@link provideWallet}. */
|
|
12
|
+
export interface ProvideWalletOptions {
|
|
13
|
+
/** Hex seed used to derive the three sub-wallet keys. */
|
|
14
|
+
seed: string;
|
|
15
|
+
/**
|
|
16
|
+
* Directory used to persist/restore serialized wallet state across runs. When omitted, the wallet is always built
|
|
17
|
+
* from scratch and nothing is read or written (replaces the old `SYNC_CACHE` env var, which `exit(1)`-ed when unset).
|
|
18
|
+
* Explicitly allows `undefined` for pass-through under `exactOptionalPropertyTypes`.
|
|
19
|
+
*/
|
|
20
|
+
syncCacheDir?: string | undefined;
|
|
21
|
+
/** Filename suffix for the three serialized state files. Required when `syncCacheDir` is set. */
|
|
22
|
+
filename?: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds a fully-started {@link WalletFacade} (shielded + unshielded + dust) for the given seed.
|
|
26
|
+
*
|
|
27
|
+
* If `syncCacheDir`/`filename` are provided, attempts to restore serialized state from disk and verify it syncs;
|
|
28
|
+
* otherwise (or on any restore failure) builds from scratch via {@link initWalletWithSeed}.
|
|
29
|
+
*/
|
|
30
|
+
export declare const provideWallet: (env: WalletTestEnvironment, options: ProvideWalletOptions) => Promise<WalletInit>;
|
|
31
|
+
/** Serializes all three sub-wallet states into `syncCacheDir`, keyed by `filename`. */
|
|
32
|
+
export declare const saveState: (wallet: WalletFacade, syncCacheDir: string, filename: string) => Promise<void>;
|
|
33
|
+
/** Builds and starts a fresh {@link WalletFacade} from `seed`, with no disk persistence. */
|
|
34
|
+
export declare const initWalletWithSeed: (env: WalletTestEnvironment, seed: string) => Promise<WalletInit>;
|
package/dist/wallet.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
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 * as rx from 'rxjs';
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import * as fsAsync from 'node:fs/promises';
|
|
16
|
+
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
17
|
+
import { InMemoryTransactionHistoryStorage, } from '@midnightntwrk/wallet-sdk-abstractions';
|
|
18
|
+
import { ShieldedWallet } from '@midnightntwrk/wallet-sdk-shielded';
|
|
19
|
+
import { WalletFacade, WalletEntrySchema, mergeWalletEntries } from '@midnightntwrk/wallet-sdk-facade';
|
|
20
|
+
import { createKeystore, PublicKey, UnshieldedWallet, } from '@midnightntwrk/wallet-sdk-unshielded-wallet';
|
|
21
|
+
import { DustWallet } from '@midnightntwrk/wallet-sdk-dust-wallet';
|
|
22
|
+
import { logger } from './logger.js';
|
|
23
|
+
import { getDustSeed, getShieldedSeed, getUnshieldedSeed } from './seeds.js';
|
|
24
|
+
const waitForSyncProgress = async (wallet) => await rx.firstValueFrom(wallet.state().pipe(rx.throttleTime(5000), rx.tap((state) => {
|
|
25
|
+
const applyGap = state.unshielded.progress.highestTransactionId - state.unshielded.progress.appliedId;
|
|
26
|
+
logger.info(`Wallet facade behind by ${applyGap}`);
|
|
27
|
+
}), rx.filter((state) => state.unshielded.progress.isStrictlyComplete())));
|
|
28
|
+
const restoreShieldedWallet = async (path, Wallet, readIfExists) => {
|
|
29
|
+
try {
|
|
30
|
+
const serialized = await readIfExists(path);
|
|
31
|
+
if (serialized) {
|
|
32
|
+
const wallet = Wallet.restore(serialized);
|
|
33
|
+
logger.info(`Restored shielded wallet from ${path}`);
|
|
34
|
+
return wallet;
|
|
35
|
+
}
|
|
36
|
+
logger.warn('Unable to restore shielded wallet.');
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
logger.error(`Failed to restore shielded wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
};
|
|
43
|
+
const restoreUnshieldedWallet = async (path, seed, env, readIfExists, txHistoryStorage) => {
|
|
44
|
+
try {
|
|
45
|
+
const serialized = await readIfExists(path);
|
|
46
|
+
if (serialized) {
|
|
47
|
+
const keyStore = createKeystore(getUnshieldedSeed(seed), env.endpoints.networkId);
|
|
48
|
+
const wallet = UnshieldedWallet({
|
|
49
|
+
networkId: env.endpoints.networkId,
|
|
50
|
+
indexerClientConnection: {
|
|
51
|
+
indexerHttpUrl: env.endpoints.indexerHttpUrl,
|
|
52
|
+
indexerWsUrl: env.endpoints.indexerWsUrl,
|
|
53
|
+
},
|
|
54
|
+
txHistoryStorage,
|
|
55
|
+
}).startWithPublicKey(PublicKey.fromKeyStore(keyStore));
|
|
56
|
+
logger.info(`Restored unshielded wallet from ${path}`);
|
|
57
|
+
return wallet;
|
|
58
|
+
}
|
|
59
|
+
logger.warn('Unable to restore unshielded wallet.');
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.error(`Failed to restore unshielded wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
const restoreDustWallet = async (path, walletConfig, readIfExists) => {
|
|
67
|
+
try {
|
|
68
|
+
const serialized = await readIfExists(path);
|
|
69
|
+
if (serialized) {
|
|
70
|
+
const DustInstance = DustWallet({
|
|
71
|
+
...walletConfig,
|
|
72
|
+
costParameters: walletConfig?.costParameters ?? {
|
|
73
|
+
feeBlocksMargin: 5,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const wallet = DustInstance.restore(serialized);
|
|
77
|
+
logger.info(`Restored dust wallet from ${path}`);
|
|
78
|
+
return wallet;
|
|
79
|
+
}
|
|
80
|
+
logger.warn('Unable to restore dust wallet.');
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
logger.error(`Failed to restore dust wallet: ${err instanceof Error ? err.message : String(err)}`);
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Builds a fully-started {@link WalletFacade} (shielded + unshielded + dust) for the given seed.
|
|
89
|
+
*
|
|
90
|
+
* If `syncCacheDir`/`filename` are provided, attempts to restore serialized state from disk and verify it syncs;
|
|
91
|
+
* otherwise (or on any restore failure) builds from scratch via {@link initWalletWithSeed}.
|
|
92
|
+
*/
|
|
93
|
+
export const provideWallet = async (env, options) => {
|
|
94
|
+
const { seed, syncCacheDir, filename } = options;
|
|
95
|
+
if (!syncCacheDir || !filename) {
|
|
96
|
+
logger.info('No sync cache configured; building wallet facade from scratch');
|
|
97
|
+
return initWalletWithSeed(env, seed);
|
|
98
|
+
}
|
|
99
|
+
// Single shared tx-history storage so all three sub-wallets and the facade read/write
|
|
100
|
+
// the same instance; otherwise shielded/unshielded writes go to a storage the facade
|
|
101
|
+
// never queries.
|
|
102
|
+
const txHistoryStorage = new InMemoryTransactionHistoryStorage(WalletEntrySchema, mergeWalletEntries);
|
|
103
|
+
const walletConfig = { ...env.getWalletConfig(), txHistoryStorage };
|
|
104
|
+
const dustWalletConfig = { ...env.getDustWalletConfig(), txHistoryStorage };
|
|
105
|
+
const Wallet = ShieldedWallet(walletConfig);
|
|
106
|
+
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(getShieldedSeed(seed));
|
|
107
|
+
const dustSecretKey = ledger.DustSecretKey.fromSeed(getDustSeed(seed));
|
|
108
|
+
const unshieldedKeystore = createKeystore(getUnshieldedSeed(seed), env.endpoints.networkId);
|
|
109
|
+
const readIfExists = async (p) => {
|
|
110
|
+
try {
|
|
111
|
+
if (!existsSync(p))
|
|
112
|
+
return undefined;
|
|
113
|
+
return await fsAsync.readFile(p, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
logger.error(`Failed to read ${p}: ${err instanceof Error ? err.message : String(err)}`);
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const [restoredShielded, restoredUnshielded, restoredDust] = await Promise.all([
|
|
121
|
+
restoreShieldedWallet(`${syncCacheDir}/shielded-${filename}`, Wallet, readIfExists),
|
|
122
|
+
restoreUnshieldedWallet(`${syncCacheDir}/unshielded-${filename}`, seed, env, readIfExists, txHistoryStorage),
|
|
123
|
+
restoreDustWallet(`${syncCacheDir}/dust-${filename}`, { ...walletConfig, ...dustWalletConfig }, readIfExists),
|
|
124
|
+
]);
|
|
125
|
+
if (!restoredShielded || !restoredUnshielded || !restoredDust) {
|
|
126
|
+
logger.info('Building wallet facade from scratch');
|
|
127
|
+
return initWalletWithSeed(env, seed);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const restoredWallet = await WalletFacade.init({
|
|
131
|
+
configuration: {
|
|
132
|
+
...walletConfig,
|
|
133
|
+
...dustWalletConfig,
|
|
134
|
+
},
|
|
135
|
+
shielded: () => restoredShielded,
|
|
136
|
+
unshielded: () => restoredUnshielded,
|
|
137
|
+
dust: () => restoredDust,
|
|
138
|
+
});
|
|
139
|
+
await restoredWallet.start(shieldedSecretKeys, dustSecretKey);
|
|
140
|
+
// check if wallet is syncing correctly
|
|
141
|
+
await waitForSyncProgress(restoredWallet);
|
|
142
|
+
const restoredWalletState = await rx.firstValueFrom(restoredWallet.state());
|
|
143
|
+
const applyGap = restoredWalletState.unshielded.progress.highestTransactionId - restoredWalletState.unshielded.progress.appliedId;
|
|
144
|
+
logger.info(`Apply gap: ${applyGap}`);
|
|
145
|
+
if ((applyGap ?? 0) < 0) {
|
|
146
|
+
logger.warn('Unable to sync restored wallet. Building wallet facade from scratch');
|
|
147
|
+
await restoredWallet.stop();
|
|
148
|
+
return initWalletWithSeed(env, seed);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
logger.info('Successfully restored wallet facade.');
|
|
152
|
+
return { wallet: restoredWallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/** Serializes all three sub-wallet states into `syncCacheDir`, keyed by `filename`. */
|
|
157
|
+
export const saveState = async (wallet, syncCacheDir, filename) => {
|
|
158
|
+
logger.info(`Saving state in ${syncCacheDir}/${filename}`);
|
|
159
|
+
try {
|
|
160
|
+
await fsAsync.mkdir(syncCacheDir, { recursive: true });
|
|
161
|
+
// Serialize all three states
|
|
162
|
+
const [shieldedSerializedState, unshieldedSerializedState, dustSerializedState] = await Promise.all([
|
|
163
|
+
wallet.shielded.serializeState(),
|
|
164
|
+
wallet.unshielded.serializeState(),
|
|
165
|
+
wallet.dust.serializeState(),
|
|
166
|
+
]);
|
|
167
|
+
const files = [
|
|
168
|
+
{ suffix: 'shielded-', data: shieldedSerializedState },
|
|
169
|
+
{ suffix: 'unshielded-', data: unshieldedSerializedState },
|
|
170
|
+
{ suffix: 'dust-', data: dustSerializedState },
|
|
171
|
+
];
|
|
172
|
+
const results = await Promise.allSettled(files.map((f) => fsAsync.writeFile(`${syncCacheDir}/${f.suffix}${filename}`, f.data, 'utf-8')));
|
|
173
|
+
for (const [i, res] of results.entries()) {
|
|
174
|
+
const pathWritten = `${syncCacheDir}/${files[i].suffix}${filename}`;
|
|
175
|
+
if (res.status === 'fulfilled') {
|
|
176
|
+
logger.info(`State written to file ${pathWritten}`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
logger.error(`Failed to write ${pathWritten}: ${res.reason instanceof Error ? res.reason.message : String(res.reason)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
if (typeof e === 'string') {
|
|
185
|
+
logger.warn(e);
|
|
186
|
+
}
|
|
187
|
+
else if (e instanceof Error) {
|
|
188
|
+
logger.warn(e.message);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
logger.warn('Unknown error while saving state');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/** Builds and starts a fresh {@link WalletFacade} from `seed`, with no disk persistence. */
|
|
196
|
+
export const initWalletWithSeed = async (env, seed) => {
|
|
197
|
+
const walletConfig = env.getWalletConfig();
|
|
198
|
+
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(getShieldedSeed(seed));
|
|
199
|
+
const dustSecretKey = ledger.DustSecretKey.fromSeed(getDustSeed(seed));
|
|
200
|
+
const unshieldedKeystore = createKeystore(getUnshieldedSeed(seed), env.endpoints.networkId);
|
|
201
|
+
const facade = await WalletFacade.init({
|
|
202
|
+
configuration: {
|
|
203
|
+
...walletConfig,
|
|
204
|
+
...env.getDustWalletConfig(),
|
|
205
|
+
txHistoryStorage: new InMemoryTransactionHistoryStorage(WalletEntrySchema, mergeWalletEntries),
|
|
206
|
+
},
|
|
207
|
+
shielded: (config) => ShieldedWallet(config).startWithSeed(getShieldedSeed(seed)),
|
|
208
|
+
unshielded: (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
209
|
+
dust: (config) => DustWallet(config).startWithSeed(getDustSeed(seed), ledger.LedgerParameters.initialParameters().dust),
|
|
210
|
+
});
|
|
211
|
+
await facade.start(shieldedSecretKeys, dustSecretKey);
|
|
212
|
+
return { wallet: facade, shieldedSecretKeys, dustSecretKey, unshieldedKeystore };
|
|
213
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@midnightntwrk/wallet-sdk-testkit",
|
|
3
|
+
"description": "Reusable wallet-SDK test harness: environment provisioning, wallet bootstrapping, sync waiters and tx-history assertions",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"author": "Midnight Foundation",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://npm.pkg.github.com/"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/midnightntwrk/midnight-wallet.git",
|
|
20
|
+
"directory": "packages/wallet-sdk-testkit"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./core": {
|
|
28
|
+
"types": "./dist/core.d.ts",
|
|
29
|
+
"import": "./dist/core.js"
|
|
30
|
+
},
|
|
31
|
+
"./testcontainers": {
|
|
32
|
+
"types": "./dist/testcontainers.d.ts",
|
|
33
|
+
"import": "./dist/testcontainers.js"
|
|
34
|
+
},
|
|
35
|
+
"./scenarios": {
|
|
36
|
+
"types": "./dist/scenarios/index.d.ts",
|
|
37
|
+
"import": "./dist/scenarios/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@midnight-ntwrk/ledger-v8": "^8.1.0",
|
|
42
|
+
"@midnightntwrk/wallet-sdk-abstractions": "workspace:^",
|
|
43
|
+
"@midnightntwrk/wallet-sdk-address-format": "workspace:^",
|
|
44
|
+
"@midnightntwrk/wallet-sdk-capabilities": "workspace:^",
|
|
45
|
+
"@midnightntwrk/wallet-sdk-dust-wallet": "workspace:^",
|
|
46
|
+
"@midnightntwrk/wallet-sdk-facade": "workspace:^",
|
|
47
|
+
"@midnightntwrk/wallet-sdk-hd": "workspace:^",
|
|
48
|
+
"@midnightntwrk/wallet-sdk-indexer-client": "workspace:^",
|
|
49
|
+
"@midnightntwrk/wallet-sdk-shielded": "workspace:^",
|
|
50
|
+
"@midnightntwrk/wallet-sdk-unshielded-wallet": "workspace:^",
|
|
51
|
+
"pino": "^10.3.1",
|
|
52
|
+
"pino-pretty": "^13.1.3",
|
|
53
|
+
"rxjs": "^7.8.2"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@midnightntwrk/wallet-sdk-utilities": "workspace:^",
|
|
57
|
+
"testcontainers": "^11.12.0",
|
|
58
|
+
"vitest": "^4.1.4"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@midnightntwrk/wallet-sdk-utilities": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"testcontainers": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@midnightntwrk/wallet-sdk-utilities": "workspace:^",
|
|
70
|
+
"rimraf": "^6.1.3",
|
|
71
|
+
"testcontainers": "^11.12.0",
|
|
72
|
+
"typescript": "^5.9.3",
|
|
73
|
+
"vitest": "4.1.8"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"typecheck": "tsc -b ./tsconfig.json --noEmit --force",
|
|
77
|
+
"lint": "eslint --max-warnings 0",
|
|
78
|
+
"format": "prettier --write \"**/*.{ts,js,json,yaml,yml,md}\"",
|
|
79
|
+
"format:check": "prettier --check \"**/*.{ts,js,json,yaml,yml,md}\"",
|
|
80
|
+
"dist": "tsc -b ./tsconfig.build.json",
|
|
81
|
+
"dist:publish": "tsc -b ./tsconfig.publish.json",
|
|
82
|
+
"clean": "rimraf --glob dist 'tsconfig.*.tsbuildinfo'",
|
|
83
|
+
"publint": "publint --strict"
|
|
84
|
+
}
|
|
85
|
+
}
|