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.
- package/README.md +12 -11
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/installers/wallet-generator.d.ts +4 -0
- package/dist/installers/wallet-generator.d.ts.map +1 -0
- package/dist/installers/wallet-generator.js +78 -0
- package/dist/installers/wallet-generator.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +65 -0
- package/dist/test.js.map +1 -0
- package/dist/utils/templates.d.ts.map +1 -1
- package/dist/utils/templates.js +67 -22
- package/dist/utils/templates.js.map +1 -1
- package/package.json +1 -1
- package/templates/hello-world/README.md.template +18 -3
- package/templates/hello-world/_gitignore +3 -0
- package/templates/hello-world/package.json.template +1 -1
- package/templates/hello-world/scripts/e2e-check.ts.template +11 -45
- package/templates/hello-world/src/check-balance.ts.template +10 -55
- package/templates/hello-world/src/cli.ts.template +12 -50
- package/templates/hello-world/src/deploy.ts.template +12 -55
- package/templates/hello-world/src/network.ts +2 -2
- package/templates/hello-world/src/wallet-state.ts +95 -0
- package/templates/hello-world/src/wallet.ts +189 -0
|
@@ -8,21 +8,14 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
10
|
import { WebSocket } from 'ws';
|
|
11
|
-
import { Buffer } from 'buffer';
|
|
12
11
|
|
|
13
12
|
import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
|
|
14
13
|
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
|
|
15
14
|
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
16
15
|
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
17
16
|
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
18
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
19
17
|
import { resolveNetwork, getOrCreateSeed, getDeployment } from '../src/network';
|
|
20
|
-
import
|
|
21
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
22
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
23
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
24
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
25
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
18
|
+
import { createWallet, persistWalletState } from '../src/wallet';
|
|
26
19
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
27
20
|
|
|
28
21
|
// @ts-expect-error wallet sync requires WebSocket
|
|
@@ -31,7 +24,6 @@ globalThis.WebSocket = WebSocket;
|
|
|
31
24
|
// ─── Network configuration ─────────────────────────────────────────────────────
|
|
32
25
|
|
|
33
26
|
const { network, config: networkConfig } = resolveNetwork();
|
|
34
|
-
setNetworkId(networkConfig.networkId);
|
|
35
27
|
const SEED = getOrCreateSeed(network);
|
|
36
28
|
|
|
37
29
|
function fail(msg: string): never {
|
|
@@ -54,7 +46,7 @@ async function main() {
|
|
|
54
46
|
fail(`Deployment address missing or invalid: ${JSON.stringify(deployment, null, 2)}`);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
// 2. Build wallet
|
|
49
|
+
// 2. Build wallet and providers
|
|
58
50
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
59
51
|
const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
|
|
60
52
|
const contractPath = path.join(zkConfigPath, 'contract', 'index.js');
|
|
@@ -65,37 +57,11 @@ async function main() {
|
|
|
65
57
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
66
58
|
);
|
|
67
59
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.deriveKeysAt(0);
|
|
74
|
-
if (derived.type !== 'keysDerived') fail('Key derivation failed.');
|
|
75
|
-
hd.hdWallet.clear();
|
|
76
|
-
|
|
77
|
-
const networkId = getNetworkId();
|
|
78
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(derived.keys[Roles.Zswap]);
|
|
79
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(derived.keys[Roles.Dust]);
|
|
80
|
-
const unshieldedKeystore = createKeystore(derived.keys[Roles.NightExternal], networkId);
|
|
81
|
-
|
|
82
|
-
const wallet = await WalletFacade.init({
|
|
83
|
-
configuration: {
|
|
84
|
-
networkId,
|
|
85
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
86
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
87
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
88
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
89
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
90
|
-
},
|
|
91
|
-
shielded: async (c) => ShieldedWallet(c).startWithSecretKeys(shieldedSecretKeys),
|
|
92
|
-
unshielded: async (c) =>
|
|
93
|
-
UnshieldedWallet(c).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
94
|
-
dust: async (c) =>
|
|
95
|
-
DustWallet(c).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
96
|
-
});
|
|
97
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
98
|
-
const state = await wallet.waitForSyncedState();
|
|
60
|
+
const walletCtx = await createWallet({ network, networkConfig, seed: SEED });
|
|
61
|
+
const state = await walletCtx.wallet.waitForSyncedState();
|
|
62
|
+
// Persist the sync state — saves time on the next e2e-check invocation in CI
|
|
63
|
+
// when run against the same persistent wallet directory.
|
|
64
|
+
await persistWalletState(network, walletCtx);
|
|
99
65
|
|
|
100
66
|
const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
|
|
101
67
|
const walletProvider = {
|
|
@@ -112,7 +78,7 @@ async function main() {
|
|
|
112
78
|
const providers = {
|
|
113
79
|
privateStateProvider: levelPrivateStateProvider({
|
|
114
80
|
privateStateStoreName: 'hello-world-state',
|
|
115
|
-
accountId: unshieldedKeystore.getBech32Address().toString(),
|
|
81
|
+
accountId: walletCtx.unshieldedKeystore.getBech32Address().toString(),
|
|
116
82
|
// SDK requires ≥16 chars. e2e-check is read-only so we don't expose
|
|
117
83
|
// the env-var override here — match the deploy script's local-devnet default.
|
|
118
84
|
privateStoragePasswordProvider: () => 'Local-Devnet-Development-Placeholder-1',
|
|
@@ -131,7 +97,7 @@ async function main() {
|
|
|
131
97
|
compiledContract: compiledContract as any,
|
|
132
98
|
});
|
|
133
99
|
} catch (err: any) {
|
|
134
|
-
await wallet.stop();
|
|
100
|
+
await walletCtx.wallet.stop();
|
|
135
101
|
fail(`findDeployedContract threw: ${err?.message ?? err}`);
|
|
136
102
|
}
|
|
137
103
|
|
|
@@ -140,7 +106,7 @@ async function main() {
|
|
|
140
106
|
// we know how to construct the local handle.
|
|
141
107
|
const onChainState = await providers.publicDataProvider.queryContractState(deployment.address);
|
|
142
108
|
if (!onChainState) {
|
|
143
|
-
await wallet.stop();
|
|
109
|
+
await walletCtx.wallet.stop();
|
|
144
110
|
fail(`queryContractState returned null for ${deployment.address}`);
|
|
145
111
|
}
|
|
146
112
|
|
|
@@ -148,7 +114,7 @@ async function main() {
|
|
|
148
114
|
console.log(` contractAddress: ${deployment.address}`);
|
|
149
115
|
console.log(` network: ${network}`);
|
|
150
116
|
|
|
151
|
-
await wallet.stop();
|
|
117
|
+
await walletCtx.wallet.stop();
|
|
152
118
|
process.exit(0);
|
|
153
119
|
}
|
|
154
120
|
|
|
@@ -2,19 +2,11 @@
|
|
|
2
2
|
* Check wallet balance on the local Midnight devnet
|
|
3
3
|
*/
|
|
4
4
|
import { WebSocket } from 'ws';
|
|
5
|
-
import * as Rx from 'rxjs';
|
|
6
|
-
import { Buffer } from 'buffer';
|
|
7
5
|
|
|
8
6
|
// Midnight SDK imports
|
|
9
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
10
|
-
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
11
7
|
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
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 { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
17
8
|
import { resolveNetwork, getOrCreateSeed } from './network';
|
|
9
|
+
import { createWallet, persistWalletState } from './wallet';
|
|
18
10
|
|
|
19
11
|
// Enable WebSocket for GraphQL subscriptions
|
|
20
12
|
// @ts-expect-error Required for wallet sync
|
|
@@ -23,48 +15,8 @@ globalThis.WebSocket = WebSocket;
|
|
|
23
15
|
// ─── Network configuration ─────────────────────────────────────────────────────
|
|
24
16
|
|
|
25
17
|
const { network, config: networkConfig } = resolveNetwork();
|
|
26
|
-
setNetworkId(networkConfig.networkId);
|
|
27
18
|
const SEED = getOrCreateSeed(network);
|
|
28
19
|
|
|
29
|
-
// ─── Wallet Functions ──────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
function deriveKeys(seed: string) {
|
|
32
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
33
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
34
|
-
const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
|
|
35
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
36
|
-
hdWallet.hdWallet.clear();
|
|
37
|
-
return result.keys;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function createWallet(seed: string) {
|
|
41
|
-
const keys = deriveKeys(seed);
|
|
42
|
-
const networkId = getNetworkId();
|
|
43
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
44
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
45
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
46
|
-
|
|
47
|
-
const walletConfig = {
|
|
48
|
-
networkId,
|
|
49
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
50
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
51
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
52
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
53
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const wallet = await WalletFacade.init({
|
|
57
|
-
configuration: walletConfig,
|
|
58
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
59
|
-
unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
60
|
-
dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
64
|
-
|
|
65
|
-
return { wallet, unshieldedKeystore };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
20
|
// ─── Main ──────────────────────────────────────────────────────────────────────
|
|
69
21
|
|
|
70
22
|
async function main() {
|
|
@@ -72,11 +24,13 @@ async function main() {
|
|
|
72
24
|
console.log('║ Wallet Balance Checker ║');
|
|
73
25
|
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
74
26
|
|
|
75
|
-
const seed = SEED;
|
|
76
|
-
|
|
77
27
|
try {
|
|
78
28
|
console.log(' Building wallet...');
|
|
79
|
-
const
|
|
29
|
+
const walletCtx = await createWallet({ network, networkConfig, seed: SEED });
|
|
30
|
+
const restoredCount = Object.values(walletCtx.restored).filter(Boolean).length;
|
|
31
|
+
if (restoredCount > 0) {
|
|
32
|
+
console.log(` Restored ${restoredCount}/3 child wallets from .midnight-wallet-state — sync will resume from saved point.`);
|
|
33
|
+
}
|
|
80
34
|
|
|
81
35
|
console.log(' Syncing with network...');
|
|
82
36
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -86,11 +40,11 @@ async function main() {
|
|
|
86
40
|
const elapsed = Math.round((Date.now() - syncStart) / 1000);
|
|
87
41
|
process.stdout.write(`\r ⏳ Still syncing... (${elapsed}s elapsed) `);
|
|
88
42
|
}, 5000);
|
|
89
|
-
const state = await wallet.waitForSyncedState();
|
|
43
|
+
const state = await walletCtx.wallet.waitForSyncedState();
|
|
90
44
|
clearInterval(syncInterval);
|
|
91
45
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
92
46
|
|
|
93
|
-
const address = unshieldedKeystore.getBech32Address();
|
|
47
|
+
const address = walletCtx.unshieldedKeystore.getBech32Address();
|
|
94
48
|
const tNightBalance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
95
49
|
const dustBalance = state.dust.balance(new Date());
|
|
96
50
|
|
|
@@ -117,7 +71,8 @@ async function main() {
|
|
|
117
71
|
console.log(' ✅ Wallet is funded and ready!\n');
|
|
118
72
|
}
|
|
119
73
|
|
|
120
|
-
await
|
|
74
|
+
await persistWalletState(network, walletCtx);
|
|
75
|
+
await walletCtx.wallet.stop();
|
|
121
76
|
} catch (error) {
|
|
122
77
|
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
123
78
|
process.exit(1);
|
|
@@ -7,7 +7,6 @@ import * as fs from 'node:fs';
|
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
9
|
import { WebSocket } from 'ws';
|
|
10
|
-
import * as Rx from 'rxjs';
|
|
11
10
|
import { Buffer } from 'buffer';
|
|
12
11
|
|
|
13
12
|
// Midnight SDK imports
|
|
@@ -16,15 +15,8 @@ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client
|
|
|
16
15
|
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
17
16
|
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
18
17
|
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
19
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
20
18
|
import { resolveNetwork, getOrCreateSeed, getDeployment } from './network';
|
|
21
|
-
import
|
|
22
|
-
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
23
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
24
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
25
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
26
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
27
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
19
|
+
import { createWallet, persistWalletState, unshieldedToken, type WalletContext } from './wallet';
|
|
28
20
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
29
21
|
|
|
30
22
|
// Enable WebSocket for GraphQL subscriptions
|
|
@@ -32,7 +24,6 @@ import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
|
32
24
|
globalThis.WebSocket = WebSocket;
|
|
33
25
|
|
|
34
26
|
const { network, config: networkConfig } = resolveNetwork();
|
|
35
|
-
setNetworkId(networkConfig.networkId);
|
|
36
27
|
const SEED = getOrCreateSeed(network);
|
|
37
28
|
|
|
38
29
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -54,46 +45,9 @@ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contrac
|
|
|
54
45
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
55
46
|
);
|
|
56
47
|
|
|
57
|
-
// ───
|
|
48
|
+
// ─── Providers ─────────────────────────────────────────────────────────────────
|
|
58
49
|
|
|
59
|
-
function
|
|
60
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
61
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
62
|
-
const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
|
|
63
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
64
|
-
hdWallet.hdWallet.clear();
|
|
65
|
-
return result.keys;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function createWallet(seed: string) {
|
|
69
|
-
const keys = deriveKeys(seed);
|
|
70
|
-
const networkId = getNetworkId();
|
|
71
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
72
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
73
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
74
|
-
|
|
75
|
-
const walletConfig = {
|
|
76
|
-
networkId,
|
|
77
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
78
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
79
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
80
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
81
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const wallet = await WalletFacade.init({
|
|
85
|
-
configuration: walletConfig,
|
|
86
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
87
|
-
unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
88
|
-
dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
92
|
-
|
|
93
|
-
return { wallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
|
|
50
|
+
async function createProviders(walletCtx: WalletContext) {
|
|
97
51
|
// The SDK requires the private-state password to be at least 16 characters.
|
|
98
52
|
// The default below is a placeholder for local devnet only — set a strong
|
|
99
53
|
// password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
|
|
@@ -157,7 +111,11 @@ async function main() {
|
|
|
157
111
|
const seed = SEED;
|
|
158
112
|
|
|
159
113
|
console.log(' Connecting to wallet...');
|
|
160
|
-
const walletCtx = await createWallet(seed);
|
|
114
|
+
const walletCtx = await createWallet({ network, networkConfig, seed });
|
|
115
|
+
const restoredCount = Object.values(walletCtx.restored).filter(Boolean).length;
|
|
116
|
+
if (restoredCount > 0) {
|
|
117
|
+
console.log(` Restored ${restoredCount}/3 child wallets from .midnight-wallet-state — sync will resume from saved point.`);
|
|
118
|
+
}
|
|
161
119
|
|
|
162
120
|
console.log(' Syncing with network...');
|
|
163
121
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -170,6 +128,9 @@ async function main() {
|
|
|
170
128
|
const state = await walletCtx.wallet.waitForSyncedState();
|
|
171
129
|
clearInterval(syncInterval);
|
|
172
130
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
131
|
+
|
|
132
|
+
// Persist sync state so the next run doesn't have to redo this work.
|
|
133
|
+
await persistWalletState(network, walletCtx);
|
|
173
134
|
const balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
174
135
|
console.log(` Balance: ${balance.toLocaleString()} tNight\n`);
|
|
175
136
|
|
|
@@ -258,6 +219,7 @@ async function main() {
|
|
|
258
219
|
}
|
|
259
220
|
}
|
|
260
221
|
|
|
222
|
+
await persistWalletState(network, walletCtx);
|
|
261
223
|
await walletCtx.wallet.stop();
|
|
262
224
|
} catch (error) {
|
|
263
225
|
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { resolveNetwork, getOrCreateSeed, recordDeployment } from './network';
|
|
10
|
+
import { createWallet, persistWalletState, unshieldedToken, type WalletContext } from './wallet';
|
|
10
11
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
12
|
import { WebSocket } from 'ws';
|
|
12
13
|
import * as Rx from 'rxjs';
|
|
13
|
-
import { Buffer } from 'buffer';
|
|
14
14
|
|
|
15
15
|
// Midnight SDK imports
|
|
16
16
|
import { deployContract } from '@midnight-ntwrk/midnight-js-contracts';
|
|
@@ -18,14 +18,6 @@ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client
|
|
|
18
18
|
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
19
19
|
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
20
20
|
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
21
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
22
|
-
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
23
|
-
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
24
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
25
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
26
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
27
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
28
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
29
21
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
30
22
|
|
|
31
23
|
// @ts-expect-error Required for wallet sync
|
|
@@ -37,7 +29,6 @@ globalThis.WebSocket = WebSocket;
|
|
|
37
29
|
// 'undeployed' (local devnet). Switch networks with: npm run network <name>
|
|
38
30
|
|
|
39
31
|
const { network, config: networkConfig } = resolveNetwork();
|
|
40
|
-
setNetworkId(networkConfig.networkId);
|
|
41
32
|
const SEED = getOrCreateSeed(network);
|
|
42
33
|
|
|
43
34
|
// ─── Proof server readiness ────────────────────────────────────────────────────
|
|
@@ -86,51 +77,9 @@ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contrac
|
|
|
86
77
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
87
78
|
);
|
|
88
79
|
|
|
89
|
-
// ───
|
|
90
|
-
|
|
91
|
-
function deriveKeys(seed: string) {
|
|
92
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
93
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
94
|
-
const result = hdWallet.hdWallet
|
|
95
|
-
.selectAccount(0)
|
|
96
|
-
.selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
|
|
97
|
-
.deriveKeysAt(0);
|
|
98
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
99
|
-
hdWallet.hdWallet.clear();
|
|
100
|
-
return result.keys;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function createWallet(seed: string) {
|
|
104
|
-
const keys = deriveKeys(seed);
|
|
105
|
-
const networkId = getNetworkId();
|
|
106
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
107
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
108
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
109
|
-
|
|
110
|
-
const walletConfig = {
|
|
111
|
-
networkId,
|
|
112
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
113
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
114
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
115
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
116
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const wallet = await WalletFacade.init({
|
|
120
|
-
configuration: walletConfig,
|
|
121
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
122
|
-
unshielded: async (config) =>
|
|
123
|
-
UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
124
|
-
dust: async (config) =>
|
|
125
|
-
DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
80
|
+
// ─── Providers ─────────────────────────────────────────────────────────────────
|
|
129
81
|
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
|
|
82
|
+
async function createProviders(walletCtx: WalletContext) {
|
|
134
83
|
// The SDK requires the private-state password to be at least 16 characters.
|
|
135
84
|
// The default below is a placeholder for local devnet only — set a strong
|
|
136
85
|
// password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
|
|
@@ -182,7 +131,11 @@ async function main() {
|
|
|
182
131
|
|
|
183
132
|
console.log('─── Wallet setup ───────────────────────────────────────────────\n');
|
|
184
133
|
console.log(' Creating wallet...');
|
|
185
|
-
const walletCtx = await createWallet(seed);
|
|
134
|
+
const walletCtx = await createWallet({ network, networkConfig, seed });
|
|
135
|
+
const restoredCount = Object.values(walletCtx.restored).filter(Boolean).length;
|
|
136
|
+
if (restoredCount > 0) {
|
|
137
|
+
console.log(` Restored ${restoredCount}/3 child wallets from .midnight-wallet-state — sync will resume from saved point.`);
|
|
138
|
+
}
|
|
186
139
|
|
|
187
140
|
console.log(' Syncing with network...');
|
|
188
141
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -196,6 +149,9 @@ async function main() {
|
|
|
196
149
|
clearInterval(syncInterval);
|
|
197
150
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
198
151
|
|
|
152
|
+
// Persist sync state now so a later deploy failure doesn't waste the sync work.
|
|
153
|
+
await persistWalletState(network, walletCtx);
|
|
154
|
+
|
|
199
155
|
const address = walletCtx.unshieldedKeystore.getBech32Address();
|
|
200
156
|
let balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
201
157
|
console.log(`\n Wallet Address: ${address}`);
|
|
@@ -391,6 +347,7 @@ async function main() {
|
|
|
391
347
|
recordDeployment(network, contractAddress, address.toString());
|
|
392
348
|
console.log(' Saved to .midnight-state.json\n');
|
|
393
349
|
|
|
350
|
+
await persistWalletState(network, walletCtx);
|
|
394
351
|
await walletCtx.wallet.stop();
|
|
395
352
|
console.log('─── Deployment complete ────────────────────────────────────────\n');
|
|
396
353
|
console.log(' Next: npm run cli\n');
|
|
@@ -53,7 +53,7 @@ export const NETWORK_CONFIGS: Record<NetworkId, NetworkConfig> = {
|
|
|
53
53
|
indexerWS: 'wss://indexer.preview.midnight.network/api/v4/graphql/ws',
|
|
54
54
|
node: 'https://rpc.preview.midnight.network',
|
|
55
55
|
proofServer: 'http://127.0.0.1:6300',
|
|
56
|
-
faucet: 'https://
|
|
56
|
+
faucet: 'https://midnight-tmnight-preview.nethermind.dev',
|
|
57
57
|
composeServices: ['proof-server'],
|
|
58
58
|
},
|
|
59
59
|
preprod: {
|
|
@@ -62,7 +62,7 @@ export const NETWORK_CONFIGS: Record<NetworkId, NetworkConfig> = {
|
|
|
62
62
|
indexerWS: 'wss://indexer.preprod.midnight.network/api/v4/graphql/ws',
|
|
63
63
|
node: 'https://rpc.preprod.midnight.network',
|
|
64
64
|
proofServer: 'http://127.0.0.1:6300',
|
|
65
|
-
faucet: 'https://
|
|
65
|
+
faucet: 'https://midnight-tmnight-preprod.nethermind.dev',
|
|
66
66
|
composeServices: ['proof-server'],
|
|
67
67
|
},
|
|
68
68
|
};
|
|
@@ -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
|
+
}
|