zcash-dev 0.1.0-alpha.0 → 0.1.0-alpha.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/dist/cli.d.ts CHANGED
@@ -1,7 +1,58 @@
1
1
  #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { send, syncWallet, WalletHandle } from '@zcash-app/sdk';
2
4
  import { readZebraCookie, buildTestnetLightwalletdArgs, redactLightwalletdArgs, TestnetLightwalletdOptions } from './testnet-lightwalletd';
3
5
  export { readZebraCookie, buildTestnetLightwalletdArgs, redactLightwalletdArgs, TestnetLightwalletdOptions, };
4
6
  /** Reads this package's own version so `zcash-dev --version` cannot drift from package.json. */
5
7
  export declare function readOwnVersion(): string;
6
8
  export declare function getRegtestAccount0MinerAddress(network?: string): string;
9
+ declare const program: Command;
10
+ export interface SendActionOptions {
11
+ to: string;
12
+ amount: string;
13
+ memo?: string;
14
+ yes?: boolean;
15
+ mainnet?: boolean;
16
+ endpoint?: string;
17
+ }
18
+ export interface SendDependencies {
19
+ client?: any;
20
+ sendFn?: typeof send;
21
+ syncWalletFn?: typeof syncWallet;
22
+ openWalletFn?: typeof WalletHandle.open;
23
+ }
24
+ export interface SendResult {
25
+ isDryRun: boolean;
26
+ network: string;
27
+ recipient: string;
28
+ amountZatoshis: bigint;
29
+ feeZatoshis: bigint;
30
+ endpoint: string;
31
+ memo?: string;
32
+ txid?: string;
33
+ }
34
+ export declare function executeSend(options: SendActionOptions, config?: Record<string, string>, deps?: SendDependencies): Promise<SendResult>;
35
+ export interface FundActionOptions {
36
+ address: string;
37
+ amount: string;
38
+ endpoint?: string;
39
+ stack?: string;
40
+ }
41
+ export interface FundDependencies {
42
+ client?: any;
43
+ sendFn?: typeof send;
44
+ syncWalletFn?: typeof syncWallet;
45
+ openWalletFn?: typeof WalletHandle.open;
46
+ fetchFn?: typeof fetch;
47
+ }
48
+ export interface FundResult {
49
+ txid: string;
50
+ stack: 'nu7' | 'z3';
51
+ recipient: string;
52
+ amountZec: string;
53
+ zatoshis: bigint;
54
+ endpoint: string;
55
+ }
56
+ export declare function executeFund(options: FundActionOptions, config?: Record<string, string>, deps?: FundDependencies): Promise<FundResult>;
7
57
  export declare function resolveRegtestDir(explicitPath?: string, stack?: string): string;
58
+ export { program };
package/dist/cli.js CHANGED
@@ -4,9 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.redactLightwalletdArgs = exports.buildTestnetLightwalletdArgs = exports.readZebraCookie = void 0;
7
+ exports.program = exports.redactLightwalletdArgs = exports.buildTestnetLightwalletdArgs = exports.readZebraCookie = void 0;
8
8
  exports.readOwnVersion = readOwnVersion;
9
9
  exports.getRegtestAccount0MinerAddress = getRegtestAccount0MinerAddress;
10
+ exports.executeSend = executeSend;
11
+ exports.executeFund = executeFund;
10
12
  exports.resolveRegtestDir = resolveRegtestDir;
11
13
  const commander_1 = require("commander");
12
14
  const prompts_1 = __importDefault(require("prompts"));
@@ -15,6 +17,8 @@ const path_1 = __importDefault(require("path"));
15
17
  const os_1 = __importDefault(require("os"));
16
18
  const child_process_1 = require("child_process");
17
19
  const sdk_1 = require("@zcash-app/sdk");
20
+ const env_1 = require("./env");
21
+ const redact_1 = require("./redact");
18
22
  const testnet_lightwalletd_1 = require("./testnet-lightwalletd");
19
23
  Object.defineProperty(exports, "readZebraCookie", { enumerable: true, get: function () { return testnet_lightwalletd_1.readZebraCookie; } });
20
24
  Object.defineProperty(exports, "buildTestnetLightwalletdArgs", { enumerable: true, get: function () { return testnet_lightwalletd_1.buildTestnetLightwalletdArgs; } });
@@ -41,6 +45,7 @@ const REMOTE_ENDPOINTS = {
41
45
  mainnet: 'zec.rocks:443',
42
46
  };
43
47
  const program = new commander_1.Command();
48
+ exports.program = program;
44
49
  program
45
50
  .name('zcash-dev')
46
51
  .description('Developer harness & regtest helper CLI for shielded Zcash apps')
@@ -75,54 +80,492 @@ program
75
80
  await client.close();
76
81
  }
77
82
  });
83
+ async function executeSend(options, config, deps) {
84
+ const env = config ?? (0, env_1.loadProjectEnv)();
85
+ const network = (env.ZCASH_NETWORK ?? 'regtest').trim().toLowerCase();
86
+ const regtestStack = (env.ZCASH_REGTEST_STACK ?? '').trim().toLowerCase();
87
+ const endpoint = options.endpoint || env.ZCASH_ENDPOINT || REMOTE_ENDPOINTS[network] || '127.0.0.1:39067';
88
+ const isNu7 = network === 'regtest' && (regtestStack === 'nu7' || endpoint.includes('39067') || (!regtestStack && !endpoint.includes('28137')));
89
+ const effectiveNetwork = isNu7 ? 'regtest-nu7' : network;
90
+ const mnemonic = (env.ZCASH_MNEMONIC ?? '').trim();
91
+ const ufvk = (env.ZCASH_UFVK ?? '').trim();
92
+ const accountIndex = parseInt(env.ZCASH_ACCOUNT_INDEX ?? '0', 10) || 0;
93
+ const birthdayRaw = (env.ZCASH_BIRTHDAY ?? '').trim();
94
+ const birthday = /^\d+$/.test(birthdayRaw) ? parseInt(birthdayRaw, 10) : undefined;
95
+ // Reject UFVK-only configs
96
+ if (ufvk && !mnemonic) {
97
+ throw new Error('Cannot send from a watch-only account (UFVK). A spending key (mnemonic) is required.');
98
+ }
99
+ if (!mnemonic && network !== 'regtest') {
100
+ throw new Error(`No spending key (ZCASH_MNEMONIC) configured for ${network}. Cannot send without a spending key.`);
101
+ }
102
+ const amountZatoshis = (0, env_1.parseZecToZatoshis)(options.amount);
103
+ const feeZatoshis = (0, sdk_1.quoteFee)(2);
104
+ // Dry run mode (default when --yes is not set)
105
+ if (!options.yes) {
106
+ return {
107
+ isDryRun: true,
108
+ network,
109
+ recipient: options.to,
110
+ amountZatoshis,
111
+ feeZatoshis,
112
+ endpoint: (0, redact_1.redactSecretUrl)(endpoint),
113
+ memo: options.memo,
114
+ };
115
+ }
116
+ // Broadcast mode (--yes passed)
117
+ if (network === 'mainnet' && !options.mainnet) {
118
+ throw new Error('Sending on mainnet requires the explicit --mainnet flag.');
119
+ }
120
+ const activeMnemonic = mnemonic || (network === 'regtest' ? sdk_1.KNOWN_TEST_MNEMONICS[accountIndex] || sdk_1.KNOWN_TEST_MNEMONICS[0] : '');
121
+ const account = (0, sdk_1.accountFromMnemonic)(activeMnemonic, accountIndex, effectiveNetwork);
122
+ const client = deps?.client ?? (0, sdk_1.createClient)({ endpoint, network: effectiveNetwork });
123
+ const openFn = deps?.openWalletFn ?? ((...args) => sdk_1.WalletHandle.open(...args));
124
+ const syncFn = deps?.syncWalletFn ?? sdk_1.syncWallet;
125
+ const sendFn = deps?.sendFn ?? sdk_1.send;
126
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'zcash-dev-wallet-'));
127
+ const dbPath = path_1.default.join(tempDir, 'wallet.db');
128
+ let result;
129
+ try {
130
+ const wallet = openFn(dbPath, effectiveNetwork, account.getUnifiedFullViewingKey(), `acct${accountIndex}`, birthday);
131
+ await syncFn(client, wallet);
132
+ result = await sendFn({
133
+ client,
134
+ wallet,
135
+ from: account,
136
+ to: options.to,
137
+ amount: amountZatoshis,
138
+ memo: options.memo,
139
+ });
140
+ }
141
+ finally {
142
+ if (!deps?.client) {
143
+ await client.close().catch(() => { });
144
+ }
145
+ try {
146
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
147
+ }
148
+ catch { }
149
+ }
150
+ return {
151
+ isDryRun: false,
152
+ network,
153
+ recipient: options.to,
154
+ amountZatoshis,
155
+ feeZatoshis: result.fee ? BigInt(result.fee) : feeZatoshis,
156
+ endpoint: (0, redact_1.redactSecretUrl)(endpoint),
157
+ memo: options.memo,
158
+ txid: result.txid,
159
+ };
160
+ }
161
+ program
162
+ .command('address')
163
+ .description('Display the derived address for the configured account')
164
+ .option('-i, --index <n>', 'Account index')
165
+ .action((options) => {
166
+ try {
167
+ (0, env_1.applyProjectEnv)();
168
+ const network = (process.env.ZCASH_NETWORK ?? 'regtest').trim().toLowerCase();
169
+ const regtestStack = (process.env.ZCASH_REGTEST_STACK ?? '').trim().toLowerCase();
170
+ const endpoint = process.env.ZCASH_ENDPOINT ?? '';
171
+ const isNu7 = network === 'regtest' && (regtestStack === 'nu7' || endpoint.includes('39067') || (!regtestStack && !endpoint.includes('28137')));
172
+ const effectiveNetwork = isNu7 ? 'regtest-nu7' : network;
173
+ const rawIndex = options.index ?? process.env.ZCASH_ACCOUNT_INDEX ?? '0';
174
+ const accountIndex = parseInt(rawIndex, 10) || 0;
175
+ const mnemonic = (process.env.ZCASH_MNEMONIC ?? '').trim();
176
+ const ufvk = (process.env.ZCASH_UFVK ?? '').trim();
177
+ if (!mnemonic && !ufvk) {
178
+ if (network === 'regtest') {
179
+ const acct = sdk_1.dev.fixtures.account(accountIndex, effectiveNetwork);
180
+ console.log(`\n--- Configured Account Address ---`);
181
+ console.log(`Network: ${network}`);
182
+ console.log(`Account Index: ${accountIndex} (regtest default fixture)`);
183
+ console.log(`Unified Address: ${acct.getUnifiedAddress()}`);
184
+ const tAddr = acct.getTransparentAddress?.();
185
+ if (tAddr)
186
+ console.log(`Transparent: ${tAddr}`);
187
+ console.log('');
188
+ return;
189
+ }
190
+ throw new Error(`Neither ZCASH_MNEMONIC nor ZCASH_UFVK is configured for ${network}. Set ZCASH_MNEMONIC or ZCASH_UFVK in .env.local.`);
191
+ }
192
+ if (mnemonic) {
193
+ const acct = (0, sdk_1.accountFromMnemonic)(mnemonic, accountIndex, effectiveNetwork);
194
+ console.log(`\n--- Configured Account Address ---`);
195
+ console.log(`Network: ${network}`);
196
+ console.log(`Account Index: ${accountIndex}`);
197
+ console.log(`Unified Address: ${acct.getUnifiedAddress()}`);
198
+ const tAddr = acct.getTransparentAddress?.();
199
+ if (tAddr)
200
+ console.log(`Transparent: ${tAddr}`);
201
+ console.log('');
202
+ return;
203
+ }
204
+ if (ufvk) {
205
+ const watchAcct = (0, sdk_1.accountFromUfvk)(ufvk, effectiveNetwork);
206
+ console.log(`\n--- Configured Account Address (Watch-Only) ---`);
207
+ console.log(`Network: ${network}`);
208
+ console.log(`Account Index: ${accountIndex} (UFVK)`);
209
+ console.log(`Unified Address: ${watchAcct.getUnifiedAddress()}`);
210
+ const tAddr = watchAcct.getTransparentAddress?.();
211
+ if (tAddr)
212
+ console.log(`Transparent: ${tAddr}`);
213
+ console.log('');
214
+ return;
215
+ }
216
+ }
217
+ catch (err) {
218
+ const sanitized = (0, redact_1.sanitizeError)(err, process.env.ZCASH_MNEMONIC);
219
+ console.error('Address derivation failed:', sanitized.message);
220
+ process.exit(1);
221
+ }
222
+ });
223
+ program
224
+ .command('balance')
225
+ .description('Check balance for the configured account')
226
+ .option('-e, --endpoint <endpoint>', 'Zaino / lightwalletd gRPC endpoint')
227
+ .action(async (options) => {
228
+ let client;
229
+ try {
230
+ (0, env_1.applyProjectEnv)();
231
+ const network = (process.env.ZCASH_NETWORK ?? 'regtest').trim().toLowerCase();
232
+ const regtestStack = (process.env.ZCASH_REGTEST_STACK ?? '').trim().toLowerCase();
233
+ const endpoint = options.endpoint || process.env.ZCASH_ENDPOINT || REMOTE_ENDPOINTS[network] || '127.0.0.1:39067';
234
+ const isNu7 = network === 'regtest' && (regtestStack === 'nu7' || endpoint.includes('39067') || (!regtestStack && !endpoint.includes('28137')));
235
+ const effectiveNetwork = isNu7 ? 'regtest-nu7' : network;
236
+ const birthdayRaw = (process.env.ZCASH_BIRTHDAY ?? '').trim();
237
+ const birthday = /^\d+$/.test(birthdayRaw) ? parseInt(birthdayRaw, 10) : undefined;
238
+ const accountIndex = parseInt(process.env.ZCASH_ACCOUNT_INDEX ?? '0', 10) || 0;
239
+ const mnemonic = (process.env.ZCASH_MNEMONIC ?? '').trim();
240
+ const ufvk = (process.env.ZCASH_UFVK ?? '').trim();
241
+ let account;
242
+ if (mnemonic) {
243
+ account = (0, sdk_1.accountFromMnemonic)(mnemonic, accountIndex, effectiveNetwork);
244
+ }
245
+ else if (ufvk) {
246
+ account = (0, sdk_1.accountFromUfvk)(ufvk, effectiveNetwork);
247
+ }
248
+ else if (network === 'regtest') {
249
+ account = sdk_1.dev.fixtures.account(accountIndex, effectiveNetwork);
250
+ }
251
+ else {
252
+ throw new Error(`Neither ZCASH_MNEMONIC nor ZCASH_UFVK is configured for ${network}. Set ZCASH_MNEMONIC or ZCASH_UFVK in .env.local.`);
253
+ }
254
+ console.log(`Querying balance on ${network} via ${(0, redact_1.redactSecretUrl)(endpoint)}...`);
255
+ client = (0, sdk_1.createClient)({ endpoint, network: effectiveNetwork });
256
+ const b = await (0, sdk_1.getBalance)(client, account, { sinceHeight: birthday });
257
+ console.log('\n--- Account Balance ---');
258
+ console.log(`Network: ${network}`);
259
+ console.log(`Total: ${(0, env_1.formatZec)(b.total)} ZEC (${b.total} zatoshis)`);
260
+ console.log(`Ironwood: ${(0, env_1.formatZec)(b.ironwood)} ZEC`);
261
+ console.log(`Orchard: ${(0, env_1.formatZec)(b.orchard)} ZEC`);
262
+ console.log(`Sapling: ${(0, env_1.formatZec)(b.sapling)} ZEC`);
263
+ console.log(`Transparent: ${(0, env_1.formatZec)(b.transparent)} ZEC`);
264
+ if (b.confirmed && b.pending) {
265
+ console.log(`Confirmed: ${(0, env_1.formatZec)(b.confirmed.total)} ZEC`);
266
+ console.log(`Pending: ${(0, env_1.formatZec)(b.pending.total)} ZEC`);
267
+ }
268
+ console.log('');
269
+ }
270
+ catch (err) {
271
+ const sanitized = (0, redact_1.sanitizeError)(err, process.env.ZCASH_MNEMONIC);
272
+ console.error('Balance check failed:', sanitized.message);
273
+ process.exit(1);
274
+ }
275
+ finally {
276
+ if (client) {
277
+ await client.close().catch(() => { });
278
+ }
279
+ }
280
+ });
281
+ program
282
+ .command('send')
283
+ .description('Send ZEC or perform a transaction dry run')
284
+ .requiredOption('--to <address>', 'Recipient address (unified, sapling, or transparent)')
285
+ .requiredOption('--amount <zec>', 'Amount to send in ZEC')
286
+ .option('--memo <memo>', 'Optional memo for shielded recipient')
287
+ .option('--yes', 'Broadcast the transaction (without --yes, runs in dry-run mode)')
288
+ .option('--mainnet', 'Safety confirmation flag required when broadcasting on mainnet')
289
+ .option('-e, --endpoint <endpoint>', 'Zaino / lightwalletd gRPC endpoint')
290
+ .action(async (options) => {
291
+ try {
292
+ (0, env_1.applyProjectEnv)();
293
+ const config = (0, env_1.loadProjectEnv)();
294
+ const res = await executeSend(options, config);
295
+ if (res.isDryRun) {
296
+ console.log('\n=== Transaction Dry Run ===');
297
+ console.log(`Network: ${res.network}`);
298
+ console.log(`Recipient: ${res.recipient}`);
299
+ console.log(`Amount: ${options.amount} ZEC (${res.amountZatoshis} zatoshis)`);
300
+ console.log(`Fee: ${(0, env_1.formatZec)(res.feeZatoshis)} ZEC (${res.feeZatoshis} zatoshis) [estimated (ZIP-317, 2 outputs assumed)]`);
301
+ console.log(`Endpoint: ${res.endpoint}`);
302
+ if (res.memo) {
303
+ console.log(`Memo: ${res.memo}`);
304
+ }
305
+ console.log('Status: DRY RUN ONLY — transaction was NOT broadcast.');
306
+ console.log(`Pass --yes to sign and broadcast this transaction${res.network === 'mainnet' ? ' (requires --mainnet)' : ''}.\n`);
307
+ }
308
+ else {
309
+ console.log('\n✅ Transaction broadcast successfully!');
310
+ console.log(`Txid: ${res.txid}`);
311
+ console.log(`Fee: ${(0, env_1.formatZec)(res.feeZatoshis)} ZEC (${res.feeZatoshis} zatoshis)\n`);
312
+ }
313
+ }
314
+ catch (err) {
315
+ const sanitized = (0, redact_1.sanitizeError)(err, process.env.ZCASH_MNEMONIC);
316
+ console.error('Send failed:', sanitized.message);
317
+ process.exit(1);
318
+ }
319
+ });
320
+ async function executeFund(options, config, deps) {
321
+ const env = config ?? (0, env_1.loadProjectEnv)();
322
+ const network = (env.ZCASH_NETWORK ?? 'regtest').trim().toLowerCase();
323
+ if (network !== 'regtest' && network !== 'regtest-nu7') {
324
+ throw new Error(`Fund is only available on regtest (current network: ${network}).`);
325
+ }
326
+ let stack = (options.stack || env.ZCASH_REGTEST_STACK || '').trim().toLowerCase();
327
+ const endpointOpt = options.endpoint || env.ZCASH_ENDPOINT;
328
+ if (!stack) {
329
+ if (endpointOpt && (endpointOpt.includes('39067') || endpointOpt.includes('39232'))) {
330
+ stack = 'nu7';
331
+ }
332
+ else if (endpointOpt && (endpointOpt.includes('28137') || endpointOpt.includes('8181'))) {
333
+ stack = 'z3';
334
+ }
335
+ else {
336
+ stack = 'nu7';
337
+ }
338
+ }
339
+ if (stack !== 'nu7' && stack !== 'z3') {
340
+ throw new Error(`Invalid regtest stack "${stack}". Must be "nu7" or "z3".`);
341
+ }
342
+ const zatoshis = (0, env_1.parseZecToZatoshis)(options.amount);
343
+ const effectiveFetch = deps?.fetchFn ?? fetch;
344
+ if (stack === 'nu7') {
345
+ const effectiveEndpoint = options.endpoint || env.ZCASH_ENDPOINT || '127.0.0.1:39067';
346
+ const minerAccount = (0, sdk_1.accountFromMnemonic)(sdk_1.KNOWN_TEST_MNEMONICS[0], 0, 'regtest-nu7');
347
+ const client = deps?.client ?? (0, sdk_1.createClient)({ endpoint: effectiveEndpoint, network: 'regtest-nu7' });
348
+ const openFn = deps?.openWalletFn ?? ((...args) => sdk_1.WalletHandle.open(...args));
349
+ const syncFn = deps?.syncWalletFn ?? sdk_1.syncWallet;
350
+ const sendFn = deps?.sendFn ?? sdk_1.send;
351
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'zcash-dev-fund-'));
352
+ const dbPath = path_1.default.join(tempDir, 'wallet.db');
353
+ let result;
354
+ try {
355
+ const wallet = openFn(dbPath, 'regtest-nu7', minerAccount.getUnifiedFullViewingKey(), 'miner0', 0);
356
+ await syncFn(client, wallet);
357
+ result = await sendFn({
358
+ client,
359
+ wallet,
360
+ from: minerAccount,
361
+ to: options.address,
362
+ amount: zatoshis,
363
+ });
364
+ }
365
+ catch (err) {
366
+ const msg = err.message || String(err);
367
+ if (msg.includes('ECONNREFUSED') || msg.includes('UNAVAILABLE') || msg.includes('connect')) {
368
+ throw new Error(`Could not connect to NU7 lightwalletd indexer at ${(0, redact_1.redactSecretUrl)(effectiveEndpoint)}. Is the NU7 stack running? Run: zcash-dev regtest up --stack nu7 (${msg})`);
369
+ }
370
+ throw err;
371
+ }
372
+ finally {
373
+ if (!deps?.client) {
374
+ await client.close().catch(() => { });
375
+ }
376
+ try {
377
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
378
+ }
379
+ catch { }
380
+ }
381
+ return {
382
+ txid: result.txid,
383
+ stack: 'nu7',
384
+ recipient: options.address,
385
+ amountZec: options.amount,
386
+ zatoshis,
387
+ endpoint: (0, redact_1.redactSecretUrl)(effectiveEndpoint),
388
+ };
389
+ }
390
+ else {
391
+ const rpcRouterUrl = 'http://127.0.0.1:8181';
392
+ let addrData;
393
+ try {
394
+ const addrRes = await effectiveFetch(rpcRouterUrl, {
395
+ method: 'POST',
396
+ headers: {
397
+ 'Content-Type': 'application/json',
398
+ Authorization: 'Basic ' + Buffer.from('zebra:zebra').toString('base64'),
399
+ },
400
+ body: JSON.stringify({
401
+ jsonrpc: '2.0',
402
+ id: 'fund-listaddresses',
403
+ method: 'listaddresses',
404
+ params: [],
405
+ }),
406
+ });
407
+ if (!addrRes.ok) {
408
+ throw new Error(`Zallet HTTP ${addrRes.status}: ${addrRes.statusText}`);
409
+ }
410
+ addrData = await addrRes.json();
411
+ }
412
+ catch (err) {
413
+ throw new Error(`Could not connect to Z3 RPC router / Zallet at ${rpcRouterUrl}. Is the Z3 stack running? Run: zcash-dev regtest up --stack nu6 (${err.message || err})`);
414
+ }
415
+ const accounts = addrData?.result?.[0]?.unified;
416
+ const senderUa = accounts?.[0]?.addresses?.[0]?.address;
417
+ if (!senderUa) {
418
+ throw new Error('No sender address found in Zallet regtest wallet');
419
+ }
420
+ const isNegative = zatoshis < 0n;
421
+ const abs = isNegative ? -zatoshis : zatoshis;
422
+ const whole = abs / 100000000n;
423
+ const frac = abs % 100000000n;
424
+ const amountZec = `${isNegative ? '-' : ''}${whole}.${frac.toString().padStart(8, '0')}`;
425
+ const sendRes = await effectiveFetch(rpcRouterUrl, {
426
+ method: 'POST',
427
+ headers: {
428
+ 'Content-Type': 'application/json',
429
+ Authorization: 'Basic ' + Buffer.from('zebra:zebra').toString('base64'),
430
+ },
431
+ body: JSON.stringify({
432
+ jsonrpc: '2.0',
433
+ id: 'fund-sendmany',
434
+ method: 'z_sendmany',
435
+ params: [senderUa, [{ address: options.address, amount: amountZec }]],
436
+ }),
437
+ });
438
+ if (!sendRes.ok) {
439
+ throw new Error(`Zallet z_sendmany HTTP ${sendRes.status}: ${sendRes.statusText}`);
440
+ }
441
+ const sendData = await sendRes.json();
442
+ if (sendData.error) {
443
+ throw new Error(`Zallet z_sendmany RPC error: ${sendData.error.message || JSON.stringify(sendData.error)}`);
444
+ }
445
+ const txid = sendData.result;
446
+ return {
447
+ txid,
448
+ stack: 'z3',
449
+ recipient: options.address,
450
+ amountZec: options.amount,
451
+ zatoshis,
452
+ endpoint: rpcRouterUrl,
453
+ };
454
+ }
455
+ }
78
456
  program
79
457
  .command('mine')
80
458
  .description('Mine blocks on regtest')
81
459
  .argument('[blocks]', 'Number of blocks to mine', '1')
82
- .option('-e, --endpoint <endpoint>', 'Zaino gRPC endpoint', '127.0.0.1:28137')
460
+ .option('-e, --endpoint <endpoint>', 'Zebra RPC or Zaino endpoint')
461
+ .option('--stack <stack>', 'Regtest stack (nu7 | z3)')
83
462
  .action(async (blocksStr, options) => {
84
- const blocks = parseInt(blocksStr, 10);
85
- const client = (0, sdk_1.createClient)({
86
- endpoint: options.endpoint,
87
- network: 'regtest',
88
- });
89
463
  try {
90
- console.log(`Mining ${blocks} block(s)...`);
91
- await sdk_1.dev.mine(client, blocks);
92
- console.log(`✅ Successfully mined ${blocks} block(s).`);
464
+ (0, env_1.applyProjectEnv)();
465
+ const env = (0, env_1.loadProjectEnv)();
466
+ const blocks = parseInt(blocksStr, 10);
467
+ let stack = (options.stack || env.ZCASH_REGTEST_STACK || '').trim().toLowerCase();
468
+ const endpointOpt = options.endpoint || env.ZCASH_ENDPOINT;
469
+ const zebraRpcOpt = env.ZCASH_ZEBRA_RPC;
470
+ if (!stack) {
471
+ if ((endpointOpt && (endpointOpt.includes('39067') || endpointOpt.includes('39232'))) || (zebraRpcOpt && zebraRpcOpt.includes('39232'))) {
472
+ stack = 'nu7';
473
+ }
474
+ else if ((endpointOpt && (endpointOpt.includes('28137') || endpointOpt.includes('8181'))) || (zebraRpcOpt && zebraRpcOpt.includes('18232'))) {
475
+ stack = 'z3';
476
+ }
477
+ else {
478
+ stack = 'nu7';
479
+ }
480
+ }
481
+ console.log(`Mining ${blocks} block(s) on ${stack.toUpperCase()} regtest stack...`);
482
+ if (stack === 'nu7') {
483
+ const zebraUrl = (options.endpoint && options.endpoint.startsWith('http'))
484
+ ? options.endpoint
485
+ : (zebraRpcOpt || 'http://127.0.0.1:39232');
486
+ let res;
487
+ try {
488
+ res = await fetch(zebraUrl, {
489
+ method: 'POST',
490
+ headers: { 'Content-Type': 'application/json' },
491
+ body: JSON.stringify({
492
+ jsonrpc: '2.0',
493
+ id: 'zcash-dev-mine',
494
+ method: 'generate',
495
+ params: [blocks],
496
+ }),
497
+ });
498
+ }
499
+ catch (err) {
500
+ throw new Error(`Could not connect to Zebra JSON-RPC at ${zebraUrl}. Is the NU7 stack running? Run: zcash-dev regtest up --stack nu7 (${err.message})`);
501
+ }
502
+ if (!res.ok) {
503
+ throw new Error(`Zebra mining HTTP ${res.status}: ${res.statusText}`);
504
+ }
505
+ const data = await res.json();
506
+ if (data.error) {
507
+ throw new Error(`Zebra mining JSON-RPC error: ${data.error.message || JSON.stringify(data.error)}`);
508
+ }
509
+ console.log(`✅ Successfully mined ${blocks} block(s) on Zebra.`);
510
+ }
511
+ else {
512
+ const rpcRouterUrl = 'http://127.0.0.1:8181';
513
+ let res;
514
+ try {
515
+ res = await fetch(rpcRouterUrl, {
516
+ method: 'POST',
517
+ headers: {
518
+ 'Content-Type': 'application/json',
519
+ Authorization: 'Basic ' + Buffer.from('zebra:zebra').toString('base64'),
520
+ },
521
+ body: JSON.stringify({
522
+ jsonrpc: '2.0',
523
+ id: 'zcash-dev-mine',
524
+ method: 'generate',
525
+ params: [blocks],
526
+ }),
527
+ });
528
+ }
529
+ catch (err) {
530
+ throw new Error(`Could not connect to Z3 RPC router at ${rpcRouterUrl}. Is the Z3 stack running? Run: zcash-dev regtest up --stack nu6 (${err.message})`);
531
+ }
532
+ if (!res.ok) {
533
+ throw new Error(`Z3 mining HTTP ${res.status}: ${res.statusText}`);
534
+ }
535
+ const data = await res.json();
536
+ if (data.error) {
537
+ throw new Error(`Z3 mining JSON-RPC error: ${data.error.message || JSON.stringify(data.error)}`);
538
+ }
539
+ console.log(`✅ Successfully mined ${blocks} block(s) on Z3 stack.`);
540
+ }
93
541
  }
94
542
  catch (err) {
95
543
  console.error('Mining failed:', err.message || err);
96
544
  process.exit(1);
97
545
  }
98
- finally {
99
- await client.close();
100
- }
101
546
  });
102
547
  program
103
548
  .command('fund')
104
549
  .description('Fund a Unified Address on regtest')
105
550
  .argument('<address>', 'Recipient Unified Address')
106
551
  .argument('[amount]', 'Amount in ZEC', '1.0')
107
- .option('-e, --endpoint <endpoint>', 'Zaino gRPC endpoint', '127.0.0.1:28137')
552
+ .option('-e, --endpoint <endpoint>', 'Zaino / lightwalletd gRPC endpoint')
553
+ .option('--stack <stack>', 'Regtest stack (nu7 | z3)')
108
554
  .action(async (address, amountStr, options) => {
109
- const zatoshis = BigInt(Math.round(parseFloat(amountStr) * 100000000));
110
- const client = (0, sdk_1.createClient)({
111
- endpoint: options.endpoint,
112
- network: 'regtest',
113
- });
114
555
  try {
115
- console.log(`Funding ${address} with ${amountStr} ZEC (${zatoshis} zatoshis)...`);
116
- const res = await sdk_1.dev.fund(client, address, zatoshis);
117
- console.log(`✅ Funded successfully. Txid: ${res.txid}`);
556
+ (0, env_1.applyProjectEnv)();
557
+ const config = (0, env_1.loadProjectEnv)();
558
+ const res = await executeFund({ address, amount: amountStr, endpoint: options.endpoint, stack: options.stack }, config);
559
+ console.log(`\n✅ Funded successfully via ${res.stack.toUpperCase()} stack!`);
560
+ console.log(`Recipient: ${res.recipient}`);
561
+ console.log(`Amount: ${res.amountZec} ZEC (${res.zatoshis} zatoshis)`);
562
+ console.log(`Txid: ${res.txid}\n`);
118
563
  }
119
564
  catch (err) {
120
- console.error('Funding failed:', err.message || err);
565
+ const sanitized = (0, redact_1.sanitizeError)(err, process.env.ZCASH_MNEMONIC);
566
+ console.error('Funding failed:', sanitized.message);
121
567
  process.exit(1);
122
568
  }
123
- finally {
124
- await client.close();
125
- }
126
569
  });
127
570
  program
128
571
  .command('fixtures')
@@ -377,7 +820,13 @@ regtestCmd
377
820
  console.log('\n🐳 Starting NU7 Regtest containers...');
378
821
  const initScript = path_1.default.join(regtestDir, 'regtest-init.sh');
379
822
  if (fs_1.default.existsSync(initScript)) {
380
- (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
823
+ try {
824
+ (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
825
+ }
826
+ catch {
827
+ console.error('regtest-init.sh failed, see output above');
828
+ process.exit(1);
829
+ }
381
830
  }
382
831
  else {
383
832
  (0, child_process_1.execSync)(`cd "${regtestDir}" && docker compose up -d`, { stdio: 'inherit' });
@@ -429,7 +878,13 @@ regtestCmd
429
878
  console.log('\n🐳 Starting Regtest containers...');
430
879
  const initScript = path_1.default.join(regtestDir, 'regtest-init.sh');
431
880
  if (fs_1.default.existsSync(initScript)) {
432
- (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
881
+ try {
882
+ (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
883
+ }
884
+ catch {
885
+ console.error('regtest-init.sh failed, see output above');
886
+ process.exit(1);
887
+ }
433
888
  }
434
889
  else {
435
890
  (0, child_process_1.execSync)(`cd "${regtestDir}" && docker compose up -d`, { stdio: 'inherit' });
@@ -637,7 +1092,13 @@ regtestCmd
637
1092
  console.log('\n=== 🛠️ Reinitializing fresh NU7 chain via regtest-init.sh ===\n');
638
1093
  const initScript = path_1.default.join(regtestDir, 'regtest-init.sh');
639
1094
  if (fs_1.default.existsSync(initScript)) {
640
- (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
1095
+ try {
1096
+ (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
1097
+ }
1098
+ catch {
1099
+ console.error('regtest-init.sh failed, see output above');
1100
+ process.exit(1);
1101
+ }
641
1102
  }
642
1103
  else {
643
1104
  (0, child_process_1.execSync)(`cd "${regtestDir}" && docker compose up -d`, { stdio: 'inherit' });
@@ -669,11 +1130,17 @@ regtestCmd
669
1130
  console.log('\n=== 🛠️ Reinitializing fresh chain via regtest-init.sh ===\n');
670
1131
  const initScript = path_1.default.join(regtestDir, 'regtest-init.sh');
671
1132
  const legacyInitScript = path_1.default.join(regtestDir, 'scripts/regtest-init.sh');
672
- if (fs_1.default.existsSync(initScript)) {
673
- (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
1133
+ try {
1134
+ if (fs_1.default.existsSync(initScript)) {
1135
+ (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${initScript}"`, { stdio: 'inherit' });
1136
+ }
1137
+ else if (fs_1.default.existsSync(legacyInitScript)) {
1138
+ (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${legacyInitScript}"`, { stdio: 'inherit' });
1139
+ }
674
1140
  }
675
- else if (fs_1.default.existsSync(legacyInitScript)) {
676
- (0, child_process_1.execSync)(`cd "${regtestDir}" && bash "${legacyInitScript}"`, { stdio: 'inherit' });
1141
+ catch {
1142
+ console.error('regtest-init.sh failed, see output above');
1143
+ process.exit(1);
677
1144
  }
678
1145
  (0, child_process_1.execSync)(`cd "${regtestDir}" && docker compose up -d && docker compose up -d zaino`, { stdio: 'inherit' });
679
1146
  const waitForRpc = async (payload, maxAttempts = 45) => {
package/dist/env.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function parseEnvContent(content: string): Record<string, string>;
2
+ export declare function loadProjectEnv(cwd?: string, processEnv?: NodeJS.ProcessEnv): Record<string, string>;
3
+ export declare function applyProjectEnv(cwd?: string): Record<string, string>;
4
+ export declare function formatZec(zatoshis: bigint | number): string;
5
+ export declare function parseZecToZatoshis(amountStr: string): bigint;
package/dist/env.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseEnvContent = parseEnvContent;
7
+ exports.loadProjectEnv = loadProjectEnv;
8
+ exports.applyProjectEnv = applyProjectEnv;
9
+ exports.formatZec = formatZec;
10
+ exports.parseZecToZatoshis = parseZecToZatoshis;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ function parseEnvContent(content) {
14
+ const result = {};
15
+ const lines = content.split('\n');
16
+ for (const line of lines) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed || trimmed.startsWith('#'))
19
+ continue;
20
+ const eqIdx = trimmed.indexOf('=');
21
+ if (eqIdx === -1)
22
+ continue;
23
+ const key = trimmed.slice(0, eqIdx).trim();
24
+ let val = trimmed.slice(eqIdx + 1).trim();
25
+ if ((val.startsWith('"') && val.endsWith('"')) ||
26
+ (val.startsWith("'") && val.endsWith("'"))) {
27
+ val = val.slice(1, -1);
28
+ }
29
+ result[key] = val;
30
+ }
31
+ return result;
32
+ }
33
+ function loadProjectEnv(cwd = process.cwd(), processEnv = process.env) {
34
+ const envPath = path_1.default.join(cwd, '.env');
35
+ const envLocalPath = path_1.default.join(cwd, '.env.local');
36
+ const fromEnv = fs_1.default.existsSync(envPath)
37
+ ? parseEnvContent(fs_1.default.readFileSync(envPath, 'utf8'))
38
+ : {};
39
+ const fromLocal = fs_1.default.existsSync(envLocalPath)
40
+ ? parseEnvContent(fs_1.default.readFileSync(envLocalPath, 'utf8'))
41
+ : {};
42
+ // Precedence: processEnv wins > .env.local > .env
43
+ const merged = { ...fromEnv, ...fromLocal };
44
+ for (const [k, v] of Object.entries(processEnv)) {
45
+ if (v !== undefined) {
46
+ merged[k] = v;
47
+ }
48
+ }
49
+ return merged;
50
+ }
51
+ function applyProjectEnv(cwd = process.cwd()) {
52
+ const config = loadProjectEnv(cwd);
53
+ for (const [k, v] of Object.entries(config)) {
54
+ if (process.env[k] === undefined) {
55
+ process.env[k] = v;
56
+ }
57
+ }
58
+ return config;
59
+ }
60
+ function formatZec(zatoshis) {
61
+ try {
62
+ const val = BigInt(zatoshis);
63
+ const neg = val < 0n;
64
+ const abs = neg ? -val : val;
65
+ const whole = abs / 100000000n;
66
+ const frac = (abs % 100000000n).toString().padStart(8, '0');
67
+ return `${neg ? '-' : ''}${whole}.${frac}`;
68
+ }
69
+ catch {
70
+ return String(zatoshis);
71
+ }
72
+ }
73
+ function parseZecToZatoshis(amountStr) {
74
+ const trimmed = (amountStr ?? '').trim();
75
+ if (!trimmed || !/^\d+(\.\d{1,8})?$/.test(trimmed)) {
76
+ throw new Error(`Invalid ZEC amount: "${amountStr}". Expected decimal number with up to 8 decimal places.`);
77
+ }
78
+ const [whole, fraction = ''] = trimmed.split('.');
79
+ const wholeZat = BigInt(whole) * 100000000n;
80
+ const fractionZat = BigInt(fraction.padEnd(8, '0'));
81
+ const total = wholeZat + fractionZat;
82
+ if (total <= 0n) {
83
+ throw new Error(`Amount must be greater than zero, got ${amountStr} ZEC`);
84
+ }
85
+ return total;
86
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from '@zcash-app/sdk';
2
2
  export * from './testnet-lightwalletd';
3
+ export * from './env';
4
+ export * from './redact';
5
+ export { program, executeSend, SendActionOptions, SendDependencies, SendResult, executeFund, FundActionOptions, FundDependencies, FundResult, } from './cli';
package/dist/index.js CHANGED
@@ -14,5 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.executeFund = exports.executeSend = exports.program = void 0;
17
18
  __exportStar(require("@zcash-app/sdk"), exports);
18
19
  __exportStar(require("./testnet-lightwalletd"), exports);
20
+ __exportStar(require("./env"), exports);
21
+ __exportStar(require("./redact"), exports);
22
+ var cli_1 = require("./cli");
23
+ Object.defineProperty(exports, "program", { enumerable: true, get: function () { return cli_1.program; } });
24
+ Object.defineProperty(exports, "executeSend", { enumerable: true, get: function () { return cli_1.executeSend; } });
25
+ Object.defineProperty(exports, "executeFund", { enumerable: true, get: function () { return cli_1.executeFund; } });
@@ -0,0 +1,2 @@
1
+ export declare function redactSecretUrl(rawUrl: string): string;
2
+ export declare function sanitizeError(err: unknown, mnemonic?: string): Error;
package/dist/redact.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redactSecretUrl = redactSecretUrl;
4
+ exports.sanitizeError = sanitizeError;
5
+ function redactSecretUrl(rawUrl) {
6
+ if (!rawUrl || typeof rawUrl !== 'string')
7
+ return rawUrl;
8
+ const trimmed = rawUrl.trim();
9
+ if (!trimmed.includes('://')) {
10
+ if (trimmed.includes('@')) {
11
+ return trimmed.replace(/^[^@]+@/, '<redacted>@');
12
+ }
13
+ return trimmed;
14
+ }
15
+ try {
16
+ const parsed = new URL(trimmed);
17
+ const hasAuth = Boolean(parsed.username || parsed.password);
18
+ const hasPathSecret = parsed.pathname.length > 1;
19
+ const hasQuerySecret = parsed.search.length > 1;
20
+ if (hasAuth || hasPathSecret || hasQuerySecret) {
21
+ const portPart = parsed.port ? `:${parsed.port}` : '';
22
+ return `${parsed.protocol}//${parsed.hostname}${portPart}/<redacted>`;
23
+ }
24
+ return trimmed;
25
+ }
26
+ catch {
27
+ return trimmed
28
+ .replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^@]+@([^\s/]+)/, '$1<redacted>@$2')
29
+ .replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s/?]+)\/[^\s]+/, '$1/<redacted>')
30
+ .replace(/([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s/?]+)\?[^\s]+/, '$1/<redacted>');
31
+ }
32
+ }
33
+ function sanitizeError(err, mnemonic) {
34
+ let message = err instanceof Error ? err.message : String(err);
35
+ if (mnemonic && mnemonic.trim()) {
36
+ message = message.split(mnemonic.trim()).join('<redacted>');
37
+ const words = mnemonic.trim().split(/\s+/);
38
+ if (words.length >= 12) {
39
+ const joinedWords = words.join(' ');
40
+ message = message.split(joinedWords).join('<redacted>');
41
+ }
42
+ }
43
+ // Redact URLs with secrets/paths
44
+ message = message.replace(/https?:\/\/[^\s"'<>]+/g, (url) => redactSecretUrl(url));
45
+ const clean = new Error(message);
46
+ clean.name = err instanceof Error ? err.name : 'Error';
47
+ return clean;
48
+ }
@@ -23,10 +23,34 @@ done
23
23
  echo " Zebra is ready."
24
24
 
25
25
  echo "==> Mining 2 blocks to activate NU6.3 at height 2..."
26
- curl -s \
27
- -X POST -H "Content-Type: application/json" \
28
- -d '{"jsonrpc":"2.0","method":"generate","params":[2],"id":1}' \
29
- "http://127.0.0.1:29232" | grep -q '"result"'
26
+ GEN_TIMEOUT="${GENERATE_TIMEOUT_SECS:-60}"
27
+ GEN_SUCCESS=0
28
+ LAST_GEN_RESPONSE=""
29
+ START_TIME=$(date +%s)
30
+ while true; do
31
+ LAST_GEN_RESPONSE="$(curl -s -X POST \
32
+ -H "Content-Type: application/json" \
33
+ -d '{"jsonrpc":"2.0","method":"generate","params":[2],"id":1}' \
34
+ "http://127.0.0.1:29232" 2>&1 || true)"
35
+ if echo "$LAST_GEN_RESPONSE" | grep -q '"result"' && ! echo "$LAST_GEN_RESPONSE" | grep -q '"error"'; then
36
+ GEN_SUCCESS=1
37
+ break
38
+ fi
39
+ NOW=$(date +%s)
40
+ if [ $((NOW - START_TIME)) -ge "$GEN_TIMEOUT" ]; then
41
+ break
42
+ fi
43
+ sleep 2
44
+ done
45
+
46
+ if [ "$GEN_SUCCESS" -ne 1 ]; then
47
+ echo "❌ Failed to mine initial blocks within ${GEN_TIMEOUT}s." >&2
48
+ echo "Last response body:" >&2
49
+ echo "$LAST_GEN_RESPONSE" >&2
50
+ echo "Last 20 lines of docker compose logs zebra:" >&2
51
+ $COMPOSE logs --tail 20 zebra >&2 || true
52
+ exit 1
53
+ fi
30
54
  echo " Blocks mined."
31
55
 
32
56
  echo "==> Preparing Zallet data volume..."
@@ -25,10 +25,34 @@ done
25
25
  echo " Zebra is ready."
26
26
 
27
27
  echo "==> Mining 2 initial blocks to activate NU7 at height 2..."
28
- curl -s \
29
- -X POST -H "Content-Type: application/json" \
30
- -d '{"jsonrpc":"2.0","method":"generate","params":[2],"id":1}' \
31
- "http://127.0.0.1:${ZEBRA_PORT}" | grep -q '"result"'
28
+ GEN_TIMEOUT="${GENERATE_TIMEOUT_SECS:-60}"
29
+ GEN_SUCCESS=0
30
+ LAST_GEN_RESPONSE=""
31
+ START_TIME=$(date +%s)
32
+ while true; do
33
+ LAST_GEN_RESPONSE="$(curl -s -X POST \
34
+ -H "Content-Type: application/json" \
35
+ -d '{"jsonrpc":"2.0","method":"generate","params":[2],"id":1}' \
36
+ "http://127.0.0.1:${ZEBRA_PORT}" 2>&1 || true)"
37
+ if echo "$LAST_GEN_RESPONSE" | grep -q '"result"' && ! echo "$LAST_GEN_RESPONSE" | grep -q '"error"'; then
38
+ GEN_SUCCESS=1
39
+ break
40
+ fi
41
+ NOW=$(date +%s)
42
+ if [ $((NOW - START_TIME)) -ge "$GEN_TIMEOUT" ]; then
43
+ break
44
+ fi
45
+ sleep 2
46
+ done
47
+
48
+ if [ "$GEN_SUCCESS" -ne 1 ]; then
49
+ echo "❌ Failed to mine initial blocks within ${GEN_TIMEOUT}s." >&2
50
+ echo "Last response body:" >&2
51
+ echo "$LAST_GEN_RESPONSE" >&2
52
+ echo "Last 20 lines of docker compose logs zebra:" >&2
53
+ $COMPOSE logs --tail 20 zebra >&2 || true
54
+ exit 1
55
+ fi
32
56
  echo " Blocks mined. NU7 is now active."
33
57
 
34
58
  echo "==> Starting lightwalletd service..."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcash-dev",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "Regtest developer helper CLI for shielded Zcash apps",
5
5
  "bin": {
6
6
  "zcash-dev": "./dist/cli.js"
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "commander": "^12.1.0",
36
36
  "prompts": "^2.4.2",
37
- "@zcash-app/sdk": "0.1.0-alpha.0"
37
+ "@zcash-app/sdk": "0.1.0-alpha.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.1",