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.
@@ -8,40 +8,42 @@
8
8
  "scripts": {
9
9
  "compile": "compact compile contracts/hello-world.compact contracts/managed/hello-world",
10
10
  "build": "npx tsc --noEmit || true",
11
- "setup": "docker compose up -d && npm run compile && npm run deploy",
11
+ "setup": "npx tsx src/setup.ts",
12
12
  "deploy": "npx tsx src/deploy.ts",
13
13
  "cli": "npx tsx src/cli.ts",
14
14
  "check-balance": "npx tsx src/check-balance.ts",
15
+ "network": "npx tsx src/network.ts",
15
16
  "proof-server:start": "docker compose up -d",
16
17
  "proof-server:stop": "docker compose down",
17
- "clean": "rm -rf contracts/managed deployment.json",
18
- "test": "echo \"Error: no test specified\" && exit 1"
18
+ "clean": "rm -rf contracts/managed .midnight-state.json .midnight-wallet-state",
19
+ "test": "echo \"Error: no test specified\" && exit 1",
20
+ "test:e2e": "tsx scripts/e2e-check.ts"
19
21
  },
20
22
  "dependencies": {
21
23
  "@midnight-ntwrk/compact-js": "2.5.0",
22
- "rxjs": "^7.8.1",
23
- "@midnight-ntwrk/compact-runtime": "0.15.0",
24
+ "rxjs": "^7.8.2",
25
+ "@midnight-ntwrk/compact-runtime": "0.16.0",
24
26
  "@midnight-ntwrk/ledger-v8": "8.0.3",
25
- "@midnight-ntwrk/midnight-js-contracts": "4.0.2",
26
- "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.2",
27
- "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.2",
28
- "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.2",
29
- "@midnight-ntwrk/midnight-js-network-id": "4.0.2",
30
- "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.2",
31
- "@midnight-ntwrk/midnight-js-types": "4.0.2",
32
- "@midnight-ntwrk/midnight-js-utils": "4.0.2",
27
+ "@midnight-ntwrk/midnight-js-contracts": "4.0.4",
28
+ "@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.4",
29
+ "@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.4",
30
+ "@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.4",
31
+ "@midnight-ntwrk/midnight-js-network-id": "4.0.4",
32
+ "@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.4",
33
+ "@midnight-ntwrk/midnight-js-types": "4.0.4",
34
+ "@midnight-ntwrk/midnight-js-utils": "4.0.4",
33
35
  "@midnight-ntwrk/wallet-sdk-dust-wallet": "3.0.0",
34
36
  "@midnight-ntwrk/wallet-sdk-facade": "3.0.0",
35
37
  "@midnight-ntwrk/wallet-sdk-hd": "3.0.1",
36
38
  "@midnight-ntwrk/wallet-sdk-shielded": "2.1.0",
37
39
  "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "2.1.0",
38
- "ws": "^8.19.0"
40
+ "ws": "^8.20.0"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@types/node": "^22.0.0",
42
44
  "@types/ws": "^8.18.1",
43
45
  "tsx": "^4.21.0",
44
- "typescript": "^5.9.3"
46
+ "typescript": "^6.0.3"
45
47
  },
46
48
  "engines": {
47
49
  "node": ">=22.0.0"
@@ -0,0 +1,124 @@
1
+ /**
2
+ * End-to-end smoke check for {{projectName}}.
3
+ *
4
+ * Reconnects to the deployed contract, reads its ledger state, and exits 0
5
+ * on success. Used by `npm run test:e2e` and by the project's CI workflows.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import { fileURLToPath, pathToFileURL } from 'node:url';
10
+ import { WebSocket } from 'ws';
11
+
12
+ import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
13
+ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
14
+ import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
15
+ import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
16
+ import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
17
+ import { resolveNetwork, getOrCreateSeed, getDeployment } from '../src/network';
18
+ import { createWallet, persistWalletState } from '../src/wallet';
19
+ import { CompiledContract } from '@midnight-ntwrk/compact-js';
20
+
21
+ // @ts-expect-error wallet sync requires WebSocket
22
+ globalThis.WebSocket = WebSocket;
23
+
24
+ // ─── Network configuration ─────────────────────────────────────────────────────
25
+
26
+ const { network, config: networkConfig } = resolveNetwork();
27
+ const SEED = getOrCreateSeed(network);
28
+
29
+ function fail(msg: string): never {
30
+ console.error(`❌ e2e-check failed: ${msg}`);
31
+ process.exit(1);
32
+ }
33
+
34
+ function isHexAddress(s: unknown): s is string {
35
+ return typeof s === 'string' && /^[0-9a-fA-F]+$/.test(s) && s.length >= 32;
36
+ }
37
+
38
+ async function main() {
39
+ // 1. Deployment sanity
40
+ const deployment = getDeployment(network);
41
+ if (!deployment) {
42
+ console.error(`No deploy on file for network ${network}.`);
43
+ process.exit(1);
44
+ }
45
+ if (!isHexAddress(deployment.address)) {
46
+ fail(`Deployment address missing or invalid: ${JSON.stringify(deployment, null, 2)}`);
47
+ }
48
+
49
+ // 2. Build wallet and providers
50
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
51
+ const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
52
+ const contractPath = path.join(zkConfigPath, 'contract', 'index.js');
53
+ if (!fs.existsSync(contractPath)) fail('Compiled contract missing — run `npm run compile`.');
54
+ const HelloWorld = await import(pathToFileURL(contractPath).href);
55
+ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contract).pipe(
56
+ CompiledContract.withVacantWitnesses,
57
+ CompiledContract.withCompiledFileAssets(zkConfigPath),
58
+ );
59
+
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);
65
+
66
+ const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
67
+ const walletProvider = {
68
+ getCoinPublicKey: () => state.shielded.coinPublicKey.toHexString(),
69
+ getEncryptionPublicKey: () => state.shielded.encryptionPublicKey.toHexString(),
70
+ async balanceTx() {
71
+ throw new Error('e2e-check is read-only and should not balance transactions');
72
+ },
73
+ submitTx() {
74
+ throw new Error('e2e-check is read-only and should not submit transactions');
75
+ },
76
+ } as any;
77
+
78
+ const providers = {
79
+ privateStateProvider: levelPrivateStateProvider({
80
+ privateStateStoreName: 'hello-world-state',
81
+ accountId: walletCtx.unshieldedKeystore.getBech32Address().toString(),
82
+ // SDK requires ≥16 chars. e2e-check is read-only so we don't expose
83
+ // the env-var override here — match the deploy script's local-devnet default.
84
+ privateStoragePasswordProvider: () => 'Local-Devnet-Development-Placeholder-1',
85
+ }),
86
+ publicDataProvider: indexerPublicDataProvider(networkConfig.indexer, networkConfig.indexerWS),
87
+ zkConfigProvider,
88
+ proofProvider: httpClientProofProvider(networkConfig.proofServer, zkConfigProvider),
89
+ walletProvider,
90
+ midnightProvider: walletProvider,
91
+ };
92
+
93
+ // 3. Reconnect to the deployed contract — proves callTx interface is wired
94
+ try {
95
+ await findDeployedContract(providers, {
96
+ contractAddress: deployment.address,
97
+ compiledContract: compiledContract as any,
98
+ });
99
+ } catch (err: any) {
100
+ await walletCtx.wallet.stop();
101
+ fail(`findDeployedContract threw: ${err?.message ?? err}`);
102
+ }
103
+
104
+ // 4. Read the on-chain contract state via the public data provider — proves
105
+ // the contract is indexed and queryable on the chain itself, not just that
106
+ // we know how to construct the local handle.
107
+ const onChainState = await providers.publicDataProvider.queryContractState(deployment.address);
108
+ if (!onChainState) {
109
+ await walletCtx.wallet.stop();
110
+ fail(`queryContractState returned null for ${deployment.address}`);
111
+ }
112
+
113
+ console.log(`✅ e2e-check passed`);
114
+ console.log(` contractAddress: ${deployment.address}`);
115
+ console.log(` network: ${network}`);
116
+
117
+ await walletCtx.wallet.stop();
118
+ process.exit(0);
119
+ }
120
+
121
+ main().catch(async (err) => {
122
+ console.error(err);
123
+ process.exit(1);
124
+ });
@@ -1,75 +1,21 @@
1
1
  /**
2
- * Check wallet balance on Midnight Preprod network
2
+ * Check wallet balance on the local Midnight devnet
3
3
  */
4
- import * as fs from 'node:fs';
5
4
  import { WebSocket } from 'ws';
6
- import * as Rx from 'rxjs';
7
- import { Buffer } from 'buffer';
8
5
 
9
6
  // Midnight SDK imports
10
- import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
11
- import * as ledger from '@midnight-ntwrk/ledger-v8';
12
7
  import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
13
- import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
14
- import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
15
- import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
16
- import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
17
- import { createKeystore, InMemoryTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
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
21
13
  globalThis.WebSocket = WebSocket;
22
14
 
23
- // Set network to preprod
24
- setNetworkId('preprod');
15
+ // ─── Network configuration ─────────────────────────────────────────────────────
25
16
 
26
- // Preprod network configuration
27
- const CONFIG = {
28
- indexer: 'https://indexer.preprod.midnight.network/api/v3/graphql',
29
- indexerWS: 'wss://indexer.preprod.midnight.network/api/v3/graphql/ws',
30
- node: 'https://rpc.preprod.midnight.network',
31
- proofServer: 'http://127.0.0.1:6300',
32
- faucetUrl: 'https://faucet.preprod.midnight.network/',
33
- };
34
-
35
- // ─── Wallet Functions ──────────────────────────────────────────────────────────
36
-
37
- function deriveKeys(seed: string) {
38
- const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
39
- if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
40
- const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
41
- if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
42
- hdWallet.hdWallet.clear();
43
- return result.keys;
44
- }
45
-
46
- async function createWallet(seed: string) {
47
- const keys = deriveKeys(seed);
48
- const networkId = getNetworkId();
49
- const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
50
- const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
51
- const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
52
-
53
- const walletConfig = {
54
- networkId,
55
- indexerClientConnection: { indexerHttpUrl: CONFIG.indexer, indexerWsUrl: CONFIG.indexerWS },
56
- provingServerUrl: new URL(CONFIG.proofServer),
57
- relayURL: new URL(CONFIG.node.replace(/^http/, 'ws')),
58
- txHistoryStorage: new InMemoryTransactionHistoryStorage(),
59
- costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
60
- };
61
-
62
- const wallet = await WalletFacade.init({
63
- configuration: walletConfig,
64
- shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
65
- unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
66
- dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
67
- });
68
-
69
- await wallet.start(shieldedSecretKeys, dustSecretKey);
70
-
71
- return { wallet, unshieldedKeystore };
72
- }
17
+ const { network, config: networkConfig } = resolveNetwork();
18
+ const SEED = getOrCreateSeed(network);
73
19
 
74
20
  // ─── Main ──────────────────────────────────────────────────────────────────────
75
21
 
@@ -78,23 +24,13 @@ async function main() {
78
24
  console.log('║ Wallet Balance Checker ║');
79
25
  console.log('╚══════════════════════════════════════════════════════════════╝\n');
80
26
 
81
- // Check for deployment.json to get seed
82
- if (!fs.existsSync('deployment.json')) {
83
- console.error('❌ No deployment.json found! Run: npm run deploy\n');
84
- process.exit(1);
85
- }
86
-
87
- const deployment = JSON.parse(fs.readFileSync('deployment.json', 'utf-8'));
88
-
89
- if (!fs.existsSync('.midnight-seed')) {
90
- console.error('❌ No .midnight-seed file found! Run: npm run deploy\n');
91
- process.exit(1);
92
- }
93
- const seed = fs.readFileSync('.midnight-seed', 'utf-8').trim();
94
-
95
27
  try {
96
28
  console.log(' Building wallet...');
97
- const { wallet, unshieldedKeystore } = await createWallet(seed);
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
+ }
98
34
 
99
35
  console.log(' Syncing with network...');
100
36
  console.log(' ℹ This may take several minutes depending on network size.');
@@ -104,33 +40,39 @@ async function main() {
104
40
  const elapsed = Math.round((Date.now() - syncStart) / 1000);
105
41
  process.stdout.write(`\r ⏳ Still syncing... (${elapsed}s elapsed) `);
106
42
  }, 5000);
107
- const state = await wallet.waitForSyncedState();
43
+ const state = await walletCtx.wallet.waitForSyncedState();
108
44
  clearInterval(syncInterval);
109
45
  process.stdout.write('\r ✓ Synced with network. \n');
110
46
 
111
- const address = unshieldedKeystore.getBech32Address();
47
+ const address = walletCtx.unshieldedKeystore.getBech32Address();
112
48
  const tNightBalance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
113
49
  const dustBalance = state.dust.balance(new Date());
114
50
 
115
51
  console.log('\n─── Wallet Details ─────────────────────────────────────────────\n');
116
52
  console.log(` Address: ${address}`);
117
- console.log(` Network: preprod\n`);
53
+ console.log(` Network: ${networkConfig.networkId}\n`);
118
54
 
119
55
  console.log('─── Balances ───────────────────────────────────────────────────\n');
120
56
  console.log(` tNight: ${tNightBalance.toLocaleString()}`);
121
57
  console.log(` DUST: ${dustBalance.toLocaleString()}\n`);
122
58
 
123
59
  if (tNightBalance === 0n) {
124
- console.log('─── Need Funds? ────────────────────────────────────────────────\n');
125
- console.log(` 1. Visit: ${CONFIG.faucetUrl}`);
126
- console.log(` 2. Paste your address: ${address}`);
127
- console.log(` 3. Request tokens and wait ~2-5 minutes`);
128
- console.log(` 4. Run this command again to check balance\n`);
60
+ if (network === 'undeployed') {
61
+ console.log(' ⚠ Wallet has no tNight. Make sure the local devnet is running');
62
+ console.log(' (npm run setup) the genesis seed is pre-funded by the dev preset.\n');
63
+ } else if (networkConfig.faucet) {
64
+ console.log(` Wallet has no tNight. Fund it from the faucet:`);
65
+ console.log(` ${networkConfig.faucet}`);
66
+ console.log(` Wallet address: ${address}\n`);
67
+ } else {
68
+ console.log(' ⚠ Wallet has no tNight.\n');
69
+ }
129
70
  } else {
130
71
  console.log(' ✅ Wallet is funded and ready!\n');
131
72
  }
132
73
 
133
- await wallet.stop();
74
+ await persistWalletState(network, walletCtx);
75
+ await walletCtx.wallet.stop();
134
76
  } catch (error) {
135
77
  console.error('\n❌ Error:', error instanceof Error ? error.message : error);
136
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,30 +15,16 @@ 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
- import * as ledger from '@midnight-ntwrk/ledger-v8';
21
- import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
22
- import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
23
- import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
24
- import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
25
- import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
26
- import { createKeystore, InMemoryTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
18
+ import { resolveNetwork, getOrCreateSeed, getDeployment } from './network';
19
+ import { createWallet, persistWalletState, unshieldedToken, type WalletContext } from './wallet';
27
20
  import { CompiledContract } from '@midnight-ntwrk/compact-js';
28
21
 
29
22
  // Enable WebSocket for GraphQL subscriptions
30
23
  // @ts-expect-error Required for wallet sync
31
24
  globalThis.WebSocket = WebSocket;
32
25
 
33
- // Set network to preprod
34
- setNetworkId('preprod');
35
-
36
- // Preprod network configuration
37
- const CONFIG = {
38
- indexer: 'https://indexer.preprod.midnight.network/api/v3/graphql',
39
- indexerWS: 'wss://indexer.preprod.midnight.network/api/v3/graphql/ws',
40
- node: 'https://rpc.preprod.midnight.network',
41
- proofServer: 'http://127.0.0.1:6300',
42
- };
26
+ const { network, config: networkConfig } = resolveNetwork();
27
+ const SEED = getOrCreateSeed(network);
43
28
 
44
29
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
30
  const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
@@ -60,47 +45,13 @@ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contrac
60
45
  CompiledContract.withCompiledFileAssets(zkConfigPath),
61
46
  );
62
47
 
63
- // ─── Wallet Functions ──────────────────────────────────────────────────────────
64
-
65
- function deriveKeys(seed: string) {
66
- const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
67
- if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
68
- const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
69
- if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
70
- hdWallet.hdWallet.clear();
71
- return result.keys;
72
- }
73
-
74
- async function createWallet(seed: string) {
75
- const keys = deriveKeys(seed);
76
- const networkId = getNetworkId();
77
- const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
78
- const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
79
- const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
80
-
81
- const walletConfig = {
82
- networkId,
83
- indexerClientConnection: { indexerHttpUrl: CONFIG.indexer, indexerWsUrl: CONFIG.indexerWS },
84
- provingServerUrl: new URL(CONFIG.proofServer),
85
- relayURL: new URL(CONFIG.node.replace(/^http/, 'ws')),
86
- txHistoryStorage: new InMemoryTransactionHistoryStorage(),
87
- costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
88
- };
89
-
90
- const wallet = await WalletFacade.init({
91
- configuration: walletConfig,
92
- shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
93
- unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
94
- dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
95
- });
48
+ // ─── Providers ─────────────────────────────────────────────────────────────────
96
49
 
97
- await wallet.start(shieldedSecretKeys, dustSecretKey);
98
-
99
- return { wallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore };
100
- }
101
-
102
- async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
103
- const privateStatePassword = process.env.PRIVATE_STATE_PASSWORD?.trim() || 'development';
50
+ async function createProviders(walletCtx: WalletContext) {
51
+ // The SDK requires the private-state password to be at least 16 characters.
52
+ // The default below is a placeholder for local devnet only — set a strong
53
+ // password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
54
+ const privateStatePassword = process.env.PRIVATE_STATE_PASSWORD?.trim() || 'Local-Devnet-Development-Placeholder-1';
104
55
 
105
56
  const state = await walletCtx.wallet.waitForSyncedState();
106
57
 
@@ -130,9 +81,9 @@ async function createProviders(walletCtx: ReturnType<typeof createWallet> extend
130
81
  accountId,
131
82
  privateStoragePasswordProvider: () => privateStatePassword,
132
83
  }),
133
- publicDataProvider: indexerPublicDataProvider(CONFIG.indexer, CONFIG.indexerWS),
84
+ publicDataProvider: indexerPublicDataProvider(networkConfig.indexer, networkConfig.indexerWS),
134
85
  zkConfigProvider,
135
- proofProvider: httpClientProofProvider(CONFIG.proofServer, zkConfigProvider),
86
+ proofProvider: httpClientProofProvider(networkConfig.proofServer, zkConfigProvider),
136
87
  walletProvider,
137
88
  midnightProvider: walletProvider,
138
89
  };
@@ -148,25 +99,23 @@ async function main() {
148
99
  const rl = createInterface({ input: stdin, output: stdout });
149
100
 
150
101
  // Check for deployment
151
- if (!fs.existsSync('deployment.json')) {
152
- console.error('❌ No deployment.json found! Run: npm run deploy\n');
102
+ const deployment = getDeployment(network);
103
+ if (!deployment) {
104
+ console.error(`No deploy on file for network ${network}. Run \`npm run setup -- --network ${network}\` first.`);
153
105
  process.exit(1);
154
106
  }
155
-
156
- const deployment = JSON.parse(fs.readFileSync('deployment.json', 'utf-8'));
157
- console.log(` Contract: ${deployment.contractAddress}`);
158
- console.log(` Network: ${deployment.network || 'preprod'}\n`);
107
+ console.log(` Contract: ${deployment.address}`);
108
+ console.log(` Network: ${network}\n`);
159
109
 
160
110
  try {
161
- // Create wallet from saved seed
162
- if (!fs.existsSync('.midnight-seed')) {
163
- console.error('❌ No .midnight-seed file found! Run: npm run deploy\n');
164
- process.exit(1);
165
- }
166
- const seed = fs.readFileSync('.midnight-seed', 'utf-8').trim();
111
+ const seed = SEED;
167
112
 
168
113
  console.log(' Connecting to wallet...');
169
- 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
+ }
170
119
 
171
120
  console.log(' Syncing with network...');
172
121
  console.log(' ℹ This may take several minutes depending on network size.');
@@ -179,16 +128,30 @@ async function main() {
179
128
  const state = await walletCtx.wallet.waitForSyncedState();
180
129
  clearInterval(syncInterval);
181
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);
182
134
  const balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
183
135
  console.log(` Balance: ${balance.toLocaleString()} tNight\n`);
184
136
 
137
+ // Surface a faucet hint when a public-network wallet has 0 tNIGHT.
138
+ // Reads (option 2) work without funds, but writes (option 1) need DUST
139
+ // generated from registered NIGHT — without this hint the next failure
140
+ // mode is a confusing "Insufficient Funds" deep inside the tx builder.
141
+ if (balance === 0n && network !== 'undeployed' && networkConfig.faucet) {
142
+ const address = walletCtx.unshieldedKeystore.getBech32Address();
143
+ console.log(' ⚠ Wallet has no tNight. Fund it from the faucet to send transactions:');
144
+ console.log(` ${networkConfig.faucet}`);
145
+ console.log(` Wallet address: ${address}\n`);
146
+ }
147
+
185
148
  // Setup providers and connect to contract
186
149
  console.log(' Connecting to contract...');
187
150
  const providers = await createProviders(walletCtx);
188
151
 
189
152
  const deployed: any = await findDeployedContract(providers, {
190
153
  compiledContract: compiledContract as any,
191
- contractAddress: deployment.contractAddress,
154
+ contractAddress: deployment.address,
192
155
  });
193
156
 
194
157
  console.log(' ✅ Connected!\n');
@@ -222,7 +185,7 @@ async function main() {
222
185
  case '2': {
223
186
  console.log('\n Reading message from blockchain...');
224
187
  try {
225
- const contractState = await providers.publicDataProvider.queryContractState(deployment.contractAddress);
188
+ const contractState = await providers.publicDataProvider.queryContractState(deployment.address);
226
189
  if (contractState) {
227
190
  const ledgerState = HelloWorld.ledger(contractState.data);
228
191
  const message = Buffer.from(ledgerState.message).toString();
@@ -256,6 +219,7 @@ async function main() {
256
219
  }
257
220
  }
258
221
 
222
+ await persistWalletState(network, walletCtx);
259
223
  await walletCtx.wallet.stop();
260
224
  } catch (error) {
261
225
  console.error('\n❌ Error:', error instanceof Error ? error.message : error);