create-mn-app 0.4.0 → 0.4.2

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,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
+ }