create-mn-app 0.3.28 → 0.4.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.
@@ -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",
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,158 @@
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
+ import { Buffer } from 'buffer';
12
+
13
+ import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
14
+ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
15
+ import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
16
+ import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
17
+ import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
18
+ import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
19
+ import { resolveNetwork, getOrCreateSeed, getDeployment } from '../src/network';
20
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
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';
26
+ import { CompiledContract } from '@midnight-ntwrk/compact-js';
27
+
28
+ // @ts-expect-error wallet sync requires WebSocket
29
+ globalThis.WebSocket = WebSocket;
30
+
31
+ // ─── Network configuration ─────────────────────────────────────────────────────
32
+
33
+ const { network, config: networkConfig } = resolveNetwork();
34
+ setNetworkId(networkConfig.networkId);
35
+ const SEED = getOrCreateSeed(network);
36
+
37
+ function fail(msg: string): never {
38
+ console.error(`❌ e2e-check failed: ${msg}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ function isHexAddress(s: unknown): s is string {
43
+ return typeof s === 'string' && /^[0-9a-fA-F]+$/.test(s) && s.length >= 32;
44
+ }
45
+
46
+ async function main() {
47
+ // 1. Deployment sanity
48
+ const deployment = getDeployment(network);
49
+ if (!deployment) {
50
+ console.error(`No deploy on file for network ${network}.`);
51
+ process.exit(1);
52
+ }
53
+ if (!isHexAddress(deployment.address)) {
54
+ fail(`Deployment address missing or invalid: ${JSON.stringify(deployment, null, 2)}`);
55
+ }
56
+
57
+ // 2. Build wallet (genesis seed) and providers
58
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
59
+ const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
60
+ const contractPath = path.join(zkConfigPath, 'contract', 'index.js');
61
+ if (!fs.existsSync(contractPath)) fail('Compiled contract missing — run `npm run compile`.');
62
+ const HelloWorld = await import(pathToFileURL(contractPath).href);
63
+ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contract).pipe(
64
+ CompiledContract.withVacantWitnesses,
65
+ CompiledContract.withCompiledFileAssets(zkConfigPath),
66
+ );
67
+
68
+ const hd = HDWallet.fromSeed(Buffer.from(SEED, 'hex'));
69
+ if (hd.type !== 'seedOk') fail('Bad seed.');
70
+ const derived = hd.hdWallet
71
+ .selectAccount(0)
72
+ .selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
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();
99
+
100
+ const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
101
+ const walletProvider = {
102
+ getCoinPublicKey: () => state.shielded.coinPublicKey.toHexString(),
103
+ getEncryptionPublicKey: () => state.shielded.encryptionPublicKey.toHexString(),
104
+ async balanceTx() {
105
+ throw new Error('e2e-check is read-only and should not balance transactions');
106
+ },
107
+ submitTx() {
108
+ throw new Error('e2e-check is read-only and should not submit transactions');
109
+ },
110
+ } as any;
111
+
112
+ const providers = {
113
+ privateStateProvider: levelPrivateStateProvider({
114
+ privateStateStoreName: 'hello-world-state',
115
+ accountId: unshieldedKeystore.getBech32Address().toString(),
116
+ // SDK requires ≥16 chars. e2e-check is read-only so we don't expose
117
+ // the env-var override here — match the deploy script's local-devnet default.
118
+ privateStoragePasswordProvider: () => 'Local-Devnet-Development-Placeholder-1',
119
+ }),
120
+ publicDataProvider: indexerPublicDataProvider(networkConfig.indexer, networkConfig.indexerWS),
121
+ zkConfigProvider,
122
+ proofProvider: httpClientProofProvider(networkConfig.proofServer, zkConfigProvider),
123
+ walletProvider,
124
+ midnightProvider: walletProvider,
125
+ };
126
+
127
+ // 3. Reconnect to the deployed contract — proves callTx interface is wired
128
+ try {
129
+ await findDeployedContract(providers, {
130
+ contractAddress: deployment.address,
131
+ compiledContract: compiledContract as any,
132
+ });
133
+ } catch (err: any) {
134
+ await wallet.stop();
135
+ fail(`findDeployedContract threw: ${err?.message ?? err}`);
136
+ }
137
+
138
+ // 4. Read the on-chain contract state via the public data provider — proves
139
+ // the contract is indexed and queryable on the chain itself, not just that
140
+ // we know how to construct the local handle.
141
+ const onChainState = await providers.publicDataProvider.queryContractState(deployment.address);
142
+ if (!onChainState) {
143
+ await wallet.stop();
144
+ fail(`queryContractState returned null for ${deployment.address}`);
145
+ }
146
+
147
+ console.log(`✅ e2e-check passed`);
148
+ console.log(` contractAddress: ${deployment.address}`);
149
+ console.log(` network: ${network}`);
150
+
151
+ await wallet.stop();
152
+ process.exit(0);
153
+ }
154
+
155
+ main().catch(async (err) => {
156
+ console.error(err);
157
+ process.exit(1);
158
+ });
@@ -1,7 +1,6 @@
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
5
  import * as Rx from 'rxjs';
7
6
  import { Buffer } from 'buffer';
@@ -14,23 +13,18 @@ import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
14
13
  import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
15
14
  import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
16
15
  import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
17
- import { createKeystore, InMemoryTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
16
+ import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
17
+ import { resolveNetwork, getOrCreateSeed } from './network';
18
18
 
19
19
  // Enable WebSocket for GraphQL subscriptions
20
20
  // @ts-expect-error Required for wallet sync
21
21
  globalThis.WebSocket = WebSocket;
22
22
 
23
- // Set network to preprod
24
- setNetworkId('preprod');
23
+ // ─── Network configuration ─────────────────────────────────────────────────────
25
24
 
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
- };
25
+ const { network, config: networkConfig } = resolveNetwork();
26
+ setNetworkId(networkConfig.networkId);
27
+ const SEED = getOrCreateSeed(network);
34
28
 
35
29
  // ─── Wallet Functions ──────────────────────────────────────────────────────────
36
30
 
@@ -52,10 +46,10 @@ async function createWallet(seed: string) {
52
46
 
53
47
  const walletConfig = {
54
48
  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(),
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(),
59
53
  costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
60
54
  };
61
55
 
@@ -78,19 +72,7 @@ async function main() {
78
72
  console.log('║ Wallet Balance Checker ║');
79
73
  console.log('╚══════════════════════════════════════════════════════════════╝\n');
80
74
 
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();
75
+ const seed = SEED;
94
76
 
95
77
  try {
96
78
  console.log(' Building wallet...');
@@ -114,18 +96,23 @@ async function main() {
114
96
 
115
97
  console.log('\n─── Wallet Details ─────────────────────────────────────────────\n');
116
98
  console.log(` Address: ${address}`);
117
- console.log(` Network: preprod\n`);
99
+ console.log(` Network: ${networkConfig.networkId}\n`);
118
100
 
119
101
  console.log('─── Balances ───────────────────────────────────────────────────\n');
120
102
  console.log(` tNight: ${tNightBalance.toLocaleString()}`);
121
103
  console.log(` DUST: ${dustBalance.toLocaleString()}\n`);
122
104
 
123
105
  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`);
106
+ if (network === 'undeployed') {
107
+ console.log(' ⚠ Wallet has no tNight. Make sure the local devnet is running');
108
+ console.log(' (npm run setup) the genesis seed is pre-funded by the dev preset.\n');
109
+ } else if (networkConfig.faucet) {
110
+ console.log(` Wallet has no tNight. Fund it from the faucet:`);
111
+ console.log(` ${networkConfig.faucet}`);
112
+ console.log(` Wallet address: ${address}\n`);
113
+ } else {
114
+ console.log(' ⚠ Wallet has no tNight.\n');
115
+ }
129
116
  } else {
130
117
  console.log(' ✅ Wallet is funded and ready!\n');
131
118
  }
@@ -17,29 +17,23 @@ import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-p
17
17
  import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
18
18
  import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
19
19
  import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
20
+ import { resolveNetwork, getOrCreateSeed, getDeployment } from './network';
20
21
  import * as ledger from '@midnight-ntwrk/ledger-v8';
21
22
  import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
22
23
  import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
23
24
  import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
24
25
  import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
25
26
  import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
26
- import { createKeystore, InMemoryTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
27
+ import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
27
28
  import { CompiledContract } from '@midnight-ntwrk/compact-js';
28
29
 
29
30
  // Enable WebSocket for GraphQL subscriptions
30
31
  // @ts-expect-error Required for wallet sync
31
32
  globalThis.WebSocket = WebSocket;
32
33
 
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
- };
34
+ const { network, config: networkConfig } = resolveNetwork();
35
+ setNetworkId(networkConfig.networkId);
36
+ const SEED = getOrCreateSeed(network);
43
37
 
44
38
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
39
  const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
@@ -80,10 +74,10 @@ async function createWallet(seed: string) {
80
74
 
81
75
  const walletConfig = {
82
76
  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(),
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(),
87
81
  costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
88
82
  };
89
83
 
@@ -100,7 +94,10 @@ async function createWallet(seed: string) {
100
94
  }
101
95
 
102
96
  async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
103
- const privateStatePassword = process.env.PRIVATE_STATE_PASSWORD?.trim() || 'development';
97
+ // The SDK requires the private-state password to be at least 16 characters.
98
+ // The default below is a placeholder for local devnet only — set a strong
99
+ // password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
100
+ const privateStatePassword = process.env.PRIVATE_STATE_PASSWORD?.trim() || 'Local-Devnet-Development-Placeholder-1';
104
101
 
105
102
  const state = await walletCtx.wallet.waitForSyncedState();
106
103
 
@@ -130,9 +127,9 @@ async function createProviders(walletCtx: ReturnType<typeof createWallet> extend
130
127
  accountId,
131
128
  privateStoragePasswordProvider: () => privateStatePassword,
132
129
  }),
133
- publicDataProvider: indexerPublicDataProvider(CONFIG.indexer, CONFIG.indexerWS),
130
+ publicDataProvider: indexerPublicDataProvider(networkConfig.indexer, networkConfig.indexerWS),
134
131
  zkConfigProvider,
135
- proofProvider: httpClientProofProvider(CONFIG.proofServer, zkConfigProvider),
132
+ proofProvider: httpClientProofProvider(networkConfig.proofServer, zkConfigProvider),
136
133
  walletProvider,
137
134
  midnightProvider: walletProvider,
138
135
  };
@@ -148,22 +145,16 @@ async function main() {
148
145
  const rl = createInterface({ input: stdin, output: stdout });
149
146
 
150
147
  // Check for deployment
151
- if (!fs.existsSync('deployment.json')) {
152
- console.error('❌ No deployment.json found! Run: npm run deploy\n');
148
+ const deployment = getDeployment(network);
149
+ if (!deployment) {
150
+ console.error(`No deploy on file for network ${network}. Run \`npm run setup -- --network ${network}\` first.`);
153
151
  process.exit(1);
154
152
  }
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`);
153
+ console.log(` Contract: ${deployment.address}`);
154
+ console.log(` Network: ${network}\n`);
159
155
 
160
156
  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();
157
+ const seed = SEED;
167
158
 
168
159
  console.log(' Connecting to wallet...');
169
160
  const walletCtx = await createWallet(seed);
@@ -182,13 +173,24 @@ async function main() {
182
173
  const balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
183
174
  console.log(` Balance: ${balance.toLocaleString()} tNight\n`);
184
175
 
176
+ // Surface a faucet hint when a public-network wallet has 0 tNIGHT.
177
+ // Reads (option 2) work without funds, but writes (option 1) need DUST
178
+ // generated from registered NIGHT — without this hint the next failure
179
+ // mode is a confusing "Insufficient Funds" deep inside the tx builder.
180
+ if (balance === 0n && network !== 'undeployed' && networkConfig.faucet) {
181
+ const address = walletCtx.unshieldedKeystore.getBech32Address();
182
+ console.log(' ⚠ Wallet has no tNight. Fund it from the faucet to send transactions:');
183
+ console.log(` ${networkConfig.faucet}`);
184
+ console.log(` Wallet address: ${address}\n`);
185
+ }
186
+
185
187
  // Setup providers and connect to contract
186
188
  console.log(' Connecting to contract...');
187
189
  const providers = await createProviders(walletCtx);
188
190
 
189
191
  const deployed: any = await findDeployedContract(providers, {
190
192
  compiledContract: compiledContract as any,
191
- contractAddress: deployment.contractAddress,
193
+ contractAddress: deployment.address,
192
194
  });
193
195
 
194
196
  console.log(' ✅ Connected!\n');
@@ -222,7 +224,7 @@ async function main() {
222
224
  case '2': {
223
225
  console.log('\n Reading message from blockchain...');
224
226
  try {
225
- const contractState = await providers.publicDataProvider.queryContractState(deployment.contractAddress);
227
+ const contractState = await providers.publicDataProvider.queryContractState(deployment.address);
226
228
  if (contractState) {
227
229
  const ledgerState = HelloWorld.ledger(contractState.data);
228
230
  const message = Buffer.from(ledgerState.message).toString();