create-mn-app 0.3.28 → 0.4.1

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.
@@ -0,0 +1,320 @@
1
+ // This module is structured to be extracted into a standalone package
2
+ // (@midnight-ntwrk/dapp-network or similar) without code changes. Do not
3
+ // introduce template substitutions, sibling-template imports, or globals
4
+ // here. All side-effecting inputs flow through function parameters.
5
+
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as crypto from 'node:crypto';
9
+
10
+ export type NetworkId = 'undeployed' | 'preview' | 'preprod';
11
+
12
+ export const NETWORK_IDS: readonly NetworkId[] = ['undeployed', 'preview', 'preprod'] as const;
13
+
14
+ export interface NetworkConfig {
15
+ networkId: NetworkId;
16
+ indexer: string;
17
+ indexerWS: string;
18
+ node: string;
19
+ proofServer: string;
20
+ faucet: string | null;
21
+ composeServices: string[];
22
+ }
23
+
24
+ export interface DeploymentRecord {
25
+ address: string;
26
+ deployedAt: string;
27
+ deployer: string;
28
+ }
29
+
30
+ export interface NetworkState {
31
+ version: 1;
32
+ activeNetwork: NetworkId;
33
+ wallets: Partial<Record<NetworkId, { seed: string; createdAt: string }>>;
34
+ deployments: Partial<Record<NetworkId, DeploymentRecord>>;
35
+ }
36
+
37
+ export const STATE_FILE_NAME = '.midnight-state.json';
38
+ export const STATE_VERSION = 1 as const;
39
+
40
+ export const NETWORK_CONFIGS: Record<NetworkId, NetworkConfig> = {
41
+ undeployed: {
42
+ networkId: 'undeployed',
43
+ indexer: 'http://127.0.0.1:8088/api/v4/graphql',
44
+ indexerWS: 'ws://127.0.0.1:8088/api/v4/graphql/ws',
45
+ node: 'ws://127.0.0.1:9944',
46
+ proofServer: 'http://127.0.0.1:6300',
47
+ faucet: null,
48
+ composeServices: ['node', 'indexer', 'proof-server'],
49
+ },
50
+ preview: {
51
+ networkId: 'preview',
52
+ indexer: 'https://indexer.preview.midnight.network/api/v4/graphql',
53
+ indexerWS: 'wss://indexer.preview.midnight.network/api/v4/graphql/ws',
54
+ node: 'https://rpc.preview.midnight.network',
55
+ proofServer: 'http://127.0.0.1:6300',
56
+ faucet: 'https://faucet.preview.midnight.network',
57
+ composeServices: ['proof-server'],
58
+ },
59
+ preprod: {
60
+ networkId: 'preprod',
61
+ indexer: 'https://indexer.preprod.midnight.network/api/v4/graphql',
62
+ indexerWS: 'wss://indexer.preprod.midnight.network/api/v4/graphql/ws',
63
+ node: 'https://rpc.preprod.midnight.network',
64
+ proofServer: 'http://127.0.0.1:6300',
65
+ faucet: 'https://faucet.preprod.midnight.network',
66
+ composeServices: ['proof-server'],
67
+ },
68
+ };
69
+
70
+ export function isNetworkId(v: unknown): v is NetworkId {
71
+ return typeof v === 'string' && (NETWORK_IDS as readonly string[]).includes(v);
72
+ }
73
+
74
+ export interface FsOptions {
75
+ cwd?: string;
76
+ }
77
+
78
+ function statePath(opts: FsOptions = {}): string {
79
+ return path.join(opts.cwd ?? process.cwd(), STATE_FILE_NAME);
80
+ }
81
+
82
+ export function loadState(opts: FsOptions = {}): NetworkState | null {
83
+ const p = statePath(opts);
84
+ if (!fs.existsSync(p)) return null;
85
+ const raw = fs.readFileSync(p, 'utf-8');
86
+ let parsed: unknown;
87
+ try {
88
+ parsed = JSON.parse(raw);
89
+ } catch (e) {
90
+ throw new Error(`Failed to parse ${p}: ${(e as Error).message}. Run \`npm run clean\` to reset.`);
91
+ }
92
+ if (
93
+ !parsed ||
94
+ typeof parsed !== 'object' ||
95
+ (parsed as { version?: unknown }).version !== STATE_VERSION
96
+ ) {
97
+ throw new Error(
98
+ `Unsupported state-file version in ${p} (expected ${STATE_VERSION}). Run \`npm run clean\` to reset.`,
99
+ );
100
+ }
101
+ if (!isNetworkId((parsed as { activeNetwork?: unknown }).activeNetwork)) {
102
+ throw new Error(
103
+ `Invalid activeNetwork in ${p}. Run \`npm run clean\` to reset.`,
104
+ );
105
+ }
106
+ return parsed as NetworkState;
107
+ }
108
+
109
+ export function saveState(state: NetworkState, opts: FsOptions = {}): void {
110
+ const p = statePath(opts);
111
+ // Write to a sibling tmp file then rename → atomic on POSIX.
112
+ const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
113
+ fs.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
114
+ fs.renameSync(tmp, p);
115
+ }
116
+
117
+ export function parseNetworkFlag(argv: string[]): NetworkId | null {
118
+ for (let i = 2; i < argv.length; i++) {
119
+ const arg = argv[i];
120
+ if (arg === '--network') {
121
+ const v = argv[i + 1];
122
+ if (v === undefined) throw new Error('--network requires a value');
123
+ if (!isNetworkId(v)) {
124
+ throw new Error(`Unknown network: ${v}. Supported: ${NETWORK_IDS.join(', ')}.`);
125
+ }
126
+ return v;
127
+ }
128
+ if (arg.startsWith('--network=')) {
129
+ const v = arg.slice('--network='.length);
130
+ if (!isNetworkId(v)) {
131
+ throw new Error(`Unknown network: ${v}. Supported: ${NETWORK_IDS.join(', ')}.`);
132
+ }
133
+ return v;
134
+ }
135
+ }
136
+ return null;
137
+ }
138
+
139
+ export interface ResolveOptions {
140
+ argv?: string[];
141
+ env?: NodeJS.ProcessEnv;
142
+ cwd?: string;
143
+ }
144
+
145
+ export type ResolveSource = 'flag' | 'state' | 'default';
146
+
147
+ export interface ResolveResult {
148
+ network: NetworkId;
149
+ config: NetworkConfig;
150
+ source: ResolveSource;
151
+ }
152
+
153
+ const ENV_OVERRIDES: Array<[keyof NetworkConfig, string]> = [
154
+ ['indexer', 'MIDNIGHT_INDEXER_URL'],
155
+ ['indexerWS', 'MIDNIGHT_INDEXER_WS_URL'],
156
+ ['node', 'MIDNIGHT_NODE_URL'],
157
+ ['faucet', 'MIDNIGHT_FAUCET_URL'],
158
+ ['proofServer', 'MIDNIGHT_PROOF_SERVER_URL'],
159
+ ];
160
+
161
+ function applyEnvOverrides(base: NetworkConfig, env: NodeJS.ProcessEnv): NetworkConfig {
162
+ const out: NetworkConfig = { ...base, composeServices: [...base.composeServices] };
163
+ for (const [field, varName] of ENV_OVERRIDES) {
164
+ const v = env[varName];
165
+ if (v) (out as unknown as Record<string, unknown>)[field] = v;
166
+ }
167
+ return out;
168
+ }
169
+
170
+ export function resolveNetwork(opts: ResolveOptions = {}): ResolveResult {
171
+ const argv = opts.argv ?? process.argv;
172
+ const env = opts.env ?? process.env;
173
+ const cwd = opts.cwd ?? process.cwd();
174
+
175
+ const flag = parseNetworkFlag(argv);
176
+ let network: NetworkId;
177
+ let source: ResolveSource;
178
+
179
+ if (flag) {
180
+ network = flag;
181
+ source = 'flag';
182
+ } else {
183
+ const state = loadState({ cwd });
184
+ if (state) {
185
+ network = state.activeNetwork;
186
+ source = 'state';
187
+ } else {
188
+ network = 'undeployed';
189
+ source = 'default';
190
+ }
191
+ }
192
+
193
+ const config = applyEnvOverrides(NETWORK_CONFIGS[network], env);
194
+ return { network, config, source };
195
+ }
196
+
197
+ export const GENESIS_SEED = '0000000000000000000000000000000000000000000000000000000000000001';
198
+
199
+ export interface SeedOptions {
200
+ env?: NodeJS.ProcessEnv;
201
+ cwd?: string;
202
+ }
203
+
204
+ export function getOrCreateSeed(network: NetworkId, opts: SeedOptions = {}): string {
205
+ const env = opts.env ?? process.env;
206
+ const cwd = opts.cwd ?? process.cwd();
207
+
208
+ if (network === 'undeployed') return GENESIS_SEED;
209
+
210
+ const fromEnv = env.MIDNIGHT_WALLET_SEED;
211
+ if (fromEnv) return fromEnv;
212
+
213
+ const existing = loadState({ cwd });
214
+ const persisted = existing?.wallets?.[network]?.seed;
215
+ if (persisted) return persisted;
216
+
217
+ const seed = crypto.randomBytes(32).toString('hex');
218
+ const next: NetworkState = existing ?? {
219
+ version: STATE_VERSION,
220
+ activeNetwork: network,
221
+ wallets: {},
222
+ deployments: {},
223
+ };
224
+ next.activeNetwork = network;
225
+ next.wallets = {
226
+ ...next.wallets,
227
+ [network]: { seed, createdAt: new Date().toISOString() },
228
+ };
229
+ saveState(next, { cwd });
230
+ return seed;
231
+ }
232
+
233
+ export function getDeployment(network: NetworkId, opts: FsOptions = {}): DeploymentRecord | null {
234
+ const state = loadState(opts);
235
+ return state?.deployments?.[network] ?? null;
236
+ }
237
+
238
+ export function recordDeployment(
239
+ network: NetworkId,
240
+ address: string,
241
+ deployer: string,
242
+ opts: FsOptions = {},
243
+ ): void {
244
+ const cwd = opts.cwd ?? process.cwd();
245
+ const existing = loadState({ cwd });
246
+ const next: NetworkState = existing ?? {
247
+ version: STATE_VERSION,
248
+ activeNetwork: network,
249
+ wallets: {},
250
+ deployments: {},
251
+ };
252
+ next.deployments = {
253
+ ...next.deployments,
254
+ [network]: { address, deployer, deployedAt: new Date().toISOString() },
255
+ };
256
+ saveState(next, { cwd });
257
+ }
258
+
259
+ export function setActiveNetwork(network: NetworkId, opts: FsOptions = {}): void {
260
+ const cwd = opts.cwd ?? process.cwd();
261
+ const existing = loadState({ cwd });
262
+ if (existing && existing.activeNetwork === network) return; // no-op
263
+ const next: NetworkState = existing ?? {
264
+ version: STATE_VERSION,
265
+ activeNetwork: network,
266
+ wallets: {},
267
+ deployments: {},
268
+ };
269
+ next.activeNetwork = network;
270
+ saveState(next, { cwd });
271
+ }
272
+
273
+ // CLI entry point. Activates only when the file is run directly via tsx,
274
+ // not when imported. Keeps the module tree-shakeable for the future
275
+ // extracted package.
276
+ function isMain(): boolean {
277
+ // import.meta.url is a `file://` URL; argv[1] is a filesystem path.
278
+ // Compare resolved paths to handle symlinks/aliases.
279
+ try {
280
+ const here = new URL(import.meta.url).pathname;
281
+ const invoked = process.argv[1] && fs.realpathSync(process.argv[1]);
282
+ return invoked === fs.realpathSync(here);
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+
288
+ function cliMain(argv: string[]): number {
289
+ const args = argv.slice(2);
290
+ if (args.length === 0) {
291
+ const r = resolveNetwork({ argv });
292
+ const dep = getDeployment(r.network);
293
+ process.stdout.write(`Active network: ${r.network}${r.source === 'default' ? ' (default)' : ''}\n`);
294
+ if (dep) process.stdout.write(`Last deploy: ${dep.address}\n`);
295
+ return 0;
296
+ }
297
+ const candidate = args[0];
298
+ if (!isNetworkId(candidate)) {
299
+ process.stderr.write(`Unknown network: ${candidate}. Supported: ${NETWORK_IDS.join(', ')}.\n`);
300
+ return 1;
301
+ }
302
+ setActiveNetwork(candidate);
303
+ process.stdout.write(`Active network is now: ${candidate}\n`);
304
+ if (candidate !== 'undeployed') {
305
+ const seed = loadState()?.wallets?.[candidate]?.seed;
306
+ if (!seed) {
307
+ process.stdout.write(`Wallet not yet generated — run \`npm run setup\` to fund and deploy.\n`);
308
+ }
309
+ }
310
+ return 0;
311
+ }
312
+
313
+ if (isMain()) {
314
+ try {
315
+ process.exit(cliMain(process.argv));
316
+ } catch (e) {
317
+ process.stderr.write(`${(e as Error).message}\n`);
318
+ process.exit(1);
319
+ }
320
+ }
@@ -0,0 +1,37 @@
1
+ // Orchestrator for `npm run setup`. Replaces the prior package.json chain
2
+ // `docker compose up -d --wait && npm run compile && npm run deploy` so
3
+ // we can branch on --network and forward it to deploy.
4
+ import { spawnSync } from 'node:child_process';
5
+ import { resolveNetwork, setActiveNetwork, parseNetworkFlag } from './network';
6
+
7
+ function run(cmd: string, args: string[]): void {
8
+ const r = spawnSync(cmd, args, { stdio: 'inherit', shell: false });
9
+ if (r.status !== 0) {
10
+ process.stderr.write(`\nCommand failed: ${cmd} ${args.join(' ')}\n`);
11
+ process.exit(r.status ?? 1);
12
+ }
13
+ }
14
+
15
+ async function main(): Promise<void> {
16
+ const argv = process.argv;
17
+ const flag = parseNetworkFlag(argv);
18
+ if (flag) setActiveNetwork(flag);
19
+ const { network, config } = resolveNetwork({ argv });
20
+
21
+ process.stdout.write(`\n→ Setting up {{projectName}} on network: ${network}\n\n`);
22
+
23
+ // 1. Bring up only the services this network needs.
24
+ run('docker', ['compose', 'up', '-d', '--wait', ...config.composeServices]);
25
+
26
+ // 2. Compile the contract (network-agnostic).
27
+ run('npm', ['run', 'compile']);
28
+
29
+ // 3. Deploy. Forward --network so deploy.ts sees the same network.
30
+ const deployArgs = network === 'undeployed' ? [] : ['--', '--network', network];
31
+ run('npm', ['run', 'deploy', ...deployArgs]);
32
+ }
33
+
34
+ main().catch((e) => {
35
+ process.stderr.write(`\nSetup failed: ${(e as Error).message}\n`);
36
+ process.exit(1);
37
+ });
@@ -0,0 +1,95 @@
1
+ // Wallet sync-state persistence.
2
+ //
3
+ // Mirrors network.ts: no template substitutions, all I/O via function
4
+ // parameters, no SDK imports — keeps the module unit-testable from the
5
+ // create-mn-app workspace (which doesn't install @midnight-ntwrk/* packages).
6
+ //
7
+ // Why: without persistence, every `npm run deploy` / `npm run cli` rebuilds
8
+ // each child wallet from seed and re-syncs against the chain. On public
9
+ // networks (preview, preprod) that's minutes per run — and painful on retries
10
+ // after a transient failure. The SDK exposes serializeState() and restore()
11
+ // on each child wallet class; wallet.ts is the glue that uses them, and this
12
+ // file is the on-disk format underneath.
13
+
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+
17
+ import type { NetworkId } from './network';
18
+
19
+ export const WALLET_STATE_DIR = '.midnight-wallet-state';
20
+ export const WALLET_STATE_VERSION = 1 as const;
21
+
22
+ export type ChildKind = 'shielded' | 'unshielded' | 'dust';
23
+ export const CHILD_KINDS: readonly ChildKind[] = ['shielded', 'unshielded', 'dust'] as const;
24
+
25
+ export interface PersistedWalletState {
26
+ shielded?: unknown;
27
+ unshielded?: unknown;
28
+ dust?: string;
29
+ }
30
+
31
+ export interface FsOptions {
32
+ cwd?: string;
33
+ }
34
+
35
+ function networkDir(network: NetworkId, opts: FsOptions = {}): string {
36
+ return path.join(opts.cwd ?? process.cwd(), WALLET_STATE_DIR, network);
37
+ }
38
+
39
+ function statePath(network: NetworkId, kind: ChildKind, opts: FsOptions = {}): string {
40
+ return path.join(networkDir(network, opts), `${kind}.json`);
41
+ }
42
+
43
+ function atomicWrite(file: string, content: string): void {
44
+ fs.mkdirSync(path.dirname(file), { recursive: true });
45
+ const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
46
+ fs.writeFileSync(tmp, content);
47
+ fs.renameSync(tmp, file);
48
+ }
49
+
50
+ interface VersionedState<T> {
51
+ version: typeof WALLET_STATE_VERSION;
52
+ state: T;
53
+ }
54
+
55
+ function readVersionedState<T>(file: string): T | undefined {
56
+ if (!fs.existsSync(file)) return undefined;
57
+ try {
58
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8')) as VersionedState<T>;
59
+ if (!parsed || typeof parsed !== 'object' || parsed.version !== WALLET_STATE_VERSION) {
60
+ return undefined;
61
+ }
62
+ return parsed.state;
63
+ } catch {
64
+ // Corrupt file — caller falls back to from-seed sync; we'll overwrite on save.
65
+ return undefined;
66
+ }
67
+ }
68
+
69
+ function writeVersionedState<T>(file: string, state: T): void {
70
+ const payload: VersionedState<T> = { version: WALLET_STATE_VERSION, state };
71
+ atomicWrite(file, `${JSON.stringify(payload)}\n`);
72
+ }
73
+
74
+ export function loadWalletState(network: NetworkId, opts: FsOptions = {}): PersistedWalletState {
75
+ return {
76
+ shielded: readVersionedState(statePath(network, 'shielded', opts)),
77
+ unshielded: readVersionedState(statePath(network, 'unshielded', opts)),
78
+ dust: readVersionedState<string>(statePath(network, 'dust', opts)),
79
+ };
80
+ }
81
+
82
+ export function saveWalletState(
83
+ network: NetworkId,
84
+ state: PersistedWalletState,
85
+ opts: FsOptions = {},
86
+ ): void {
87
+ if (state.shielded !== undefined) writeVersionedState(statePath(network, 'shielded', opts), state.shielded);
88
+ if (state.unshielded !== undefined) writeVersionedState(statePath(network, 'unshielded', opts), state.unshielded);
89
+ if (state.dust !== undefined) writeVersionedState(statePath(network, 'dust', opts), state.dust);
90
+ }
91
+
92
+ export function clearWalletState(network: NetworkId, opts: FsOptions = {}): void {
93
+ const dir = networkDir(network, opts);
94
+ if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
95
+ }
@@ -0,0 +1,189 @@
1
+ // Wallet construction + sync-state restore.
2
+ //
3
+ // Mirrors network.ts in structure. The on-disk format and pure I/O live in
4
+ // wallet-state.ts (unit-tested from the scaffolder workspace, no SDK deps);
5
+ // this file is the glue between that format and the wallet SDK.
6
+
7
+ import { Buffer } from 'buffer';
8
+
9
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
10
+ import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
11
+ import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
12
+ import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
13
+ import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
14
+ import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
15
+ import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
16
+ import {
17
+ createKeystore,
18
+ NoOpTransactionHistoryStorage,
19
+ PublicKey,
20
+ UnshieldedWallet,
21
+ } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
22
+
23
+ import type { NetworkConfig, NetworkId } from './network';
24
+ import {
25
+ CHILD_KINDS,
26
+ loadWalletState,
27
+ saveWalletState,
28
+ type ChildKind,
29
+ type PersistedWalletState,
30
+ } from './wallet-state';
31
+
32
+ export { unshieldedToken };
33
+ export type { PersistedWalletState };
34
+ export {
35
+ loadWalletState,
36
+ saveWalletState,
37
+ clearWalletState,
38
+ WALLET_STATE_DIR,
39
+ WALLET_STATE_VERSION,
40
+ } from './wallet-state';
41
+
42
+ function deriveKeys(seed: string) {
43
+ const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
44
+ if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
45
+ const result = hdWallet.hdWallet
46
+ .selectAccount(0)
47
+ .selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
48
+ .deriveKeysAt(0);
49
+ if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
50
+ hdWallet.hdWallet.clear();
51
+ return result.keys;
52
+ }
53
+
54
+ export interface WalletContext {
55
+ wallet: Awaited<ReturnType<typeof WalletFacade.init>>;
56
+ shieldedSecretKeys: ReturnType<typeof ledger.ZswapSecretKeys.fromSeed>;
57
+ dustSecretKey: ReturnType<typeof ledger.DustSecretKey.fromSeed>;
58
+ unshieldedKeystore: ReturnType<typeof createKeystore>;
59
+ restored: { shielded: boolean; unshielded: boolean; dust: boolean };
60
+ }
61
+
62
+ export interface CreateWalletOptions {
63
+ network: NetworkId;
64
+ networkConfig: NetworkConfig;
65
+ seed: string;
66
+ /**
67
+ * Whether to attempt to restore each child wallet from saved state.
68
+ * Defaults to true. Pass false to force a from-seed sync (used by tests).
69
+ */
70
+ restore?: boolean;
71
+ cwd?: string;
72
+ }
73
+
74
+ function warnRestoreFailure(kind: ChildKind, err: unknown): void {
75
+ const msg = err instanceof Error ? err.message : String(err);
76
+ process.stderr.write(` ⚠ Could not restore ${kind} wallet state (${msg}); falling back to fresh sync.\n`);
77
+ }
78
+
79
+ /**
80
+ * Build the wallet facade, restoring each child from saved state when
81
+ * available and falling back to a from-seed start when not (or when restore
82
+ * throws, e.g. after an SDK upgrade with an incompatible state format).
83
+ *
84
+ * Caller is responsible for `await wallet.waitForSyncedState()` afterwards.
85
+ */
86
+ export async function createWallet(opts: CreateWalletOptions): Promise<WalletContext> {
87
+ setNetworkId(opts.networkConfig.networkId);
88
+
89
+ const keys = deriveKeys(opts.seed);
90
+ const networkId = getNetworkId();
91
+ const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
92
+ const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
93
+ const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
94
+
95
+ const saved: PersistedWalletState = opts.restore === false
96
+ ? {}
97
+ : loadWalletState(opts.network, { cwd: opts.cwd });
98
+
99
+ const restored = { shielded: false, unshielded: false, dust: false };
100
+
101
+ const walletConfig = {
102
+ networkId,
103
+ indexerClientConnection: {
104
+ indexerHttpUrl: opts.networkConfig.indexer,
105
+ indexerWsUrl: opts.networkConfig.indexerWS,
106
+ },
107
+ provingServerUrl: new URL(opts.networkConfig.proofServer),
108
+ relayURL: new URL(opts.networkConfig.node.replace(/^http/, 'ws')),
109
+ txHistoryStorage: new NoOpTransactionHistoryStorage(),
110
+ costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
111
+ };
112
+
113
+ const wallet = await WalletFacade.init({
114
+ configuration: walletConfig,
115
+ shielded: async (config) => {
116
+ const cls = ShieldedWallet(config);
117
+ if (saved.shielded !== undefined) {
118
+ try {
119
+ const restoredWallet = await (cls as any).restore(saved.shielded);
120
+ restored.shielded = true;
121
+ return restoredWallet;
122
+ } catch (err) {
123
+ warnRestoreFailure('shielded', err);
124
+ }
125
+ }
126
+ return cls.startWithSecretKeys(shieldedSecretKeys);
127
+ },
128
+ unshielded: async (config) => {
129
+ const cls = UnshieldedWallet(config);
130
+ if (saved.unshielded !== undefined) {
131
+ try {
132
+ const restoredWallet = await (cls as any).restore(saved.unshielded);
133
+ restored.unshielded = true;
134
+ return restoredWallet;
135
+ } catch (err) {
136
+ warnRestoreFailure('unshielded', err);
137
+ }
138
+ }
139
+ return cls.startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore));
140
+ },
141
+ dust: async (config) => {
142
+ const cls = DustWallet(config);
143
+ if (saved.dust !== undefined) {
144
+ try {
145
+ const restoredWallet = await (cls as any).restore(saved.dust);
146
+ restored.dust = true;
147
+ return restoredWallet;
148
+ } catch (err) {
149
+ warnRestoreFailure('dust', err);
150
+ }
151
+ }
152
+ return cls.startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust);
153
+ },
154
+ });
155
+
156
+ await wallet.start(shieldedSecretKeys, dustSecretKey);
157
+
158
+ return { wallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore, restored };
159
+ }
160
+
161
+ /**
162
+ * Serialize each child wallet's current state and persist it for the next run.
163
+ * Safe to call multiple times. Logs but does not throw on individual failures —
164
+ * losing one child's state means the next run re-syncs that child only.
165
+ */
166
+ export async function persistWalletState(
167
+ network: NetworkId,
168
+ ctx: WalletContext,
169
+ cwd?: string,
170
+ ): Promise<void> {
171
+ const next: PersistedWalletState = {};
172
+
173
+ for (const kind of CHILD_KINDS) {
174
+ try {
175
+ const child = (ctx.wallet as unknown as Record<ChildKind, { serializeState: () => Promise<unknown> }>)[kind];
176
+ const serialized = await child.serializeState();
177
+ if (kind === 'dust') {
178
+ next.dust = serialized as string;
179
+ } else {
180
+ next[kind] = serialized;
181
+ }
182
+ } catch (err) {
183
+ const msg = err instanceof Error ? err.message : String(err);
184
+ process.stderr.write(` ⚠ Could not serialize ${kind} wallet state (${msg}); next run will re-sync.\n`);
185
+ }
186
+ }
187
+
188
+ saveWalletState(network, next, { cwd });
189
+ }
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
- "moduleResolution": "node",
5
+ "moduleResolution": "bundler",
6
6
  "outDir": "./dist",
7
7
  "rootDir": "./src",
8
8
  "strict": true,
@@ -1,4 +0,0 @@
1
- export declare class WalletGenerator {
2
- generate(projectPath: string): Promise<string>;
3
- }
4
- //# sourceMappingURL=wallet-generator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wallet-generator.d.ts","sourceRoot":"","sources":["../../src/installers/wallet-generator.ts"],"names":[],"mappings":"AAmBA,qBAAa,eAAe;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAwDrD"}