@offckb/cli 0.4.9-canary-2b6b3af.0 → 0.4.9

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.
Files changed (3) hide show
  1. package/README.md +77 -23
  2. package/build/index.js +2270 -670
  3. package/package.json +2 -2
package/build/index.js CHANGED
@@ -353,6 +353,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
353
353
  });
354
354
  };
355
355
  Object.defineProperty(exports, "__esModule", ({ value: true }));
356
+ exports.runCli = runCli;
356
357
  const commander_1 = __nccwpck_require__(53497);
357
358
  const node_1 = __nccwpck_require__(998);
358
359
  const accounts_1 = __nccwpck_require__(87398);
@@ -366,6 +367,8 @@ const udt_1 = __nccwpck_require__(50703);
366
367
  const create_1 = __nccwpck_require__(51534);
367
368
  const config_1 = __nccwpck_require__(93004);
368
369
  const devnet_config_1 = __nccwpck_require__(10015);
370
+ const devnet_fork_1 = __nccwpck_require__(36709);
371
+ const devnet_info_1 = __nccwpck_require__(60905);
369
372
  const debug_1 = __nccwpck_require__(39591);
370
373
  const system_scripts_1 = __nccwpck_require__(86198);
371
374
  const transfer_all_1 = __nccwpck_require__(92039);
@@ -373,16 +376,26 @@ const gen_1 = __nccwpck_require__(750);
373
376
  const ckb_debugger_1 = __nccwpck_require__(14815);
374
377
  const logger_1 = __nccwpck_require__(65370);
375
378
  const status_1 = __nccwpck_require__(71420);
376
- const validator_1 = __nccwpck_require__(39534);
377
379
  const version = (__nccwpck_require__(8330)/* .version */ .rE);
378
380
  const description = (__nccwpck_require__(8330)/* .description */ .h_);
379
381
  // fix windows terminal encoding of simplified chinese text
380
382
  (0, encoding_1.setUTF8EncodingForWindows)();
381
383
  const program = new commander_1.Command();
382
384
  program.name('offckb').description(description).version(version).enablePositionalOptions();
385
+ let activeCommand = 'offckb';
386
+ function commandPath(command) {
387
+ const names = [];
388
+ let current = command;
389
+ while (current === null || current === void 0 ? void 0 : current.parent) {
390
+ names.unshift(current.name());
391
+ current = current.parent;
392
+ }
393
+ return names.join('.') || 'offckb';
394
+ }
383
395
  program.option('--json', 'Output logs in JSON format for agent/programmatic consumption');
384
- program.hook('preAction', (thisCommand) => {
385
- const opts = thisCommand.opts();
396
+ program.hook('preAction', (_thisCommand, actionCommand) => {
397
+ activeCommand = commandPath(actionCommand);
398
+ const opts = actionCommand.optsWithGlobals();
386
399
  if (opts.json) {
387
400
  logger_1.logger.setJsonMode(true);
388
401
  }
@@ -419,7 +432,8 @@ program
419
432
  .option('--target <target>', 'Specify the script binaries file/folder path to deploy', './')
420
433
  .option('-o, --output <output>', 'Specify the output folder path for the deployment record files', './deployment')
421
434
  .option('-t, --type-id', 'Specify if use upgradable type id to deploy the script')
422
- .option('--privkey <privkey>', 'Specify the private key to deploy scripts')
435
+ .option('--privkey <privkey>', 'Specify the private key to deploy scripts (visible in shell history)')
436
+ .option('--privkey-file <path>', 'Read the private key from a local file')
423
437
  .option('-y, --yes', 'Skip confirmation prompt and deploy immediately')
424
438
  .action((options) => (0, deploy_1.deploy)(options));
425
439
  program
@@ -432,8 +446,7 @@ program
432
446
  .action((option) => __awaiter(void 0, void 0, void 0, function* () {
433
447
  // For debugging, tx-hash is required
434
448
  if (!option.txHash) {
435
- logger_1.logger.error('Error: --tx-hash is required for debugging operations');
436
- process.exit(1);
449
+ throw new Error('--tx-hash is required for debugging operations');
437
450
  }
438
451
  const txHash = option.txHash;
439
452
  if (option.singleScript) {
@@ -463,34 +476,43 @@ program
463
476
  .description('Clean the devnet data, need to stop running the chain first')
464
477
  .option('-d, --data', 'Only remove chain data, keep devnet config files')
465
478
  .action((options) => (0, clean_1.clean)(options));
466
- program.command('accounts').description('Print account list info').action(accounts_1.accounts);
479
+ program
480
+ .command('accounts')
481
+ .description('Print account list info')
482
+ .option('--show-private-keys', 'Include built-in dev private keys (hidden by default)')
483
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
484
+ yield (0, accounts_1.accounts)(options);
485
+ }));
467
486
  program
468
487
  .command('deposit [toAddress] [amountInCKB]')
469
488
  .description('Deposit CKB tokens to address, only devnet and testnet')
470
489
  .option('--network <network>', 'Specify the network to deposit to', 'devnet')
471
490
  .option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain')
472
491
  .action((toAddress, amountInCKB, options) => __awaiter(void 0, void 0, void 0, function* () {
473
- return (0, deposit_1.deposit)(toAddress, amountInCKB, options);
492
+ yield (0, deposit_1.deposit)(toAddress, amountInCKB, options);
474
493
  }));
475
494
  program
476
495
  .command('transfer [toAddress] [amount]')
477
496
  .description('Transfer CKB or UDT tokens to address, only devnet and testnet')
478
497
  .option('--network <network>', 'Specify the network to transfer to', 'devnet')
479
- .option('--privkey <privkey>', 'Specify the private key to transfer')
480
- .addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt'))
498
+ .option('--privkey <privkey>', 'Specify the private key to transfer (visible in shell history)')
499
+ .option('--privkey-file <path>', 'Read the private key from a local file')
500
+ .addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']))
481
501
  .option('--udt-type-args <typeArgs>', 'Specify the UDT type script args to transfer UDT')
502
+ .option('--allow-mainnet-replay-risk', 'Allow a non-built-in key on a Mainnet fork (copied inputs remain blocked)')
482
503
  .option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain')
483
504
  .action((toAddress, amount, options) => __awaiter(void 0, void 0, void 0, function* () {
484
- return (0, transfer_1.transfer)(toAddress, amount, options);
505
+ yield (0, transfer_1.transfer)(toAddress, amount, options);
485
506
  }));
486
507
  program
487
508
  .command('transfer-all [toAddress]')
488
509
  .description('Transfer All CKB tokens to address, only devnet and testnet')
489
510
  .option('--network <network>', 'Specify the network to transfer to', 'devnet')
490
- .option('--privkey <privkey>', 'Specify the private key to deploy scripts')
511
+ .option('--privkey <privkey>', 'Specify the private key (visible in shell history)')
512
+ .option('--privkey-file <path>', 'Read the private key from a local file')
491
513
  .option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain')
492
514
  .action((toAddress, options) => __awaiter(void 0, void 0, void 0, function* () {
493
- return (0, transfer_all_1.transferAll)(toAddress, options);
515
+ yield (0, transfer_all_1.transferAll)(toAddress, options);
494
516
  }));
495
517
  program
496
518
  .command('balance [toAddress]')
@@ -500,7 +522,7 @@ program
500
522
  .option('--udt-type-args <typeArgs>', 'Filter by UDT type script args')
501
523
  .option('--no-udt', 'Skip UDT balance scan')
502
524
  .action((toAddress, options) => __awaiter(void 0, void 0, void 0, function* () {
503
- return (0, balance_1.balanceOf)(toAddress, options);
525
+ yield (0, balance_1.balanceOf)(toAddress, options);
504
526
  }));
505
527
  const udtCommand = program.command('udt').description('UDT token commands');
506
528
  udtCommand
@@ -510,9 +532,10 @@ udtCommand
510
532
  .addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt'))
511
533
  .option('--type-args <typeArgs>', 'Specify the UDT type script args (xudt only; defaults to signer lock hash)')
512
534
  .option('--to <toAddress>', 'Specify the receiver address (defaults to signer)')
513
- .option('--privkey <privkey>', 'Specify the private key to issue UDT')
535
+ .option('--privkey <privkey>', 'Specify the private key to issue UDT (visible in shell history)')
536
+ .option('--privkey-file <path>', 'Read the private key from a local file')
514
537
  .action((amount, options) => __awaiter(void 0, void 0, void 0, function* () {
515
- return (0, udt_1.udtIssue)(amount, options);
538
+ yield (0, udt_1.udtIssue)(amount, options);
516
539
  }));
517
540
  udtCommand
518
541
  .command('destroy <amount>')
@@ -520,9 +543,10 @@ udtCommand
520
543
  .option('--network <network>', 'Specify the network', 'devnet')
521
544
  .addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt'))
522
545
  .requiredOption('--type-args <typeArgs>', 'Specify the UDT type script args')
523
- .option('--privkey <privkey>', 'Specify the private key to destroy UDT')
546
+ .option('--privkey <privkey>', 'Specify the private key to destroy UDT (visible in shell history)')
547
+ .option('--privkey-file <path>', 'Read the private key from a local file')
524
548
  .action((amount, options) => __awaiter(void 0, void 0, void 0, function* () {
525
- return (0, udt_1.udtDestroy)(amount, options);
549
+ yield (0, udt_1.udtDestroy)(amount, options);
526
550
  }));
527
551
  program
528
552
  .command('debugger')
@@ -536,10 +560,11 @@ program
536
560
  program
537
561
  .command('status')
538
562
  .description('Show ckb-tui status interface')
539
- .option('--network <network>', 'Specify the network whose node status to monitor', 'devnet')
563
+ .addOption(new commander_1.Option('--network <network>', 'Specify the network whose node status to monitor')
564
+ .choices(['devnet', 'testnet', 'mainnet'])
565
+ .default('devnet'))
540
566
  .action((option) => __awaiter(void 0, void 0, void 0, function* () {
541
- (0, validator_1.validateNetworkOpt)(option.network);
542
- return yield (0, status_1.status)({ network: option.network });
567
+ yield (0, status_1.status)({ network: option.network });
543
568
  }));
544
569
  program
545
570
  .command('config <action> [item] [value]')
@@ -551,10 +576,77 @@ devnetCommand
551
576
  .description('Open a full-screen editor to tweak devnet config files')
552
577
  .option('-s, --set <key=value>', 'Set a devnet config field non-interactively (repeatable), e.g. --set ckb.logger.filter=info', (value, previous = []) => [...previous, value], [])
553
578
  .action(devnet_config_1.devnetConfig);
554
- program.parse(process.argv);
555
- // If no command is specified, display help
556
- if (!process.argv.slice(2).length) {
557
- program.outputHelp();
579
+ devnetCommand
580
+ .command('info')
581
+ .description('Show fork metadata and node/indexer readiness')
582
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
583
+ yield (0, devnet_info_1.devnetInfo)();
584
+ }));
585
+ devnetCommand
586
+ .command('fork')
587
+ .description('Fork an existing mainnet/testnet chain data directory into the local devnet')
588
+ .option('--from <dir>', 'Path to the source CKB node directory used with `ckb -C`')
589
+ .option('--source <source>', 'Source chain: mainnet or testnet (auto-detected from the source ckb.toml when omitted)')
590
+ .option('--spec-file <path>', 'Use a local chain spec file instead of downloading it')
591
+ .option('--force', 'Replace the existing devnet (or a previous fork)')
592
+ .option('--migrate', 'Migrate only the copied database when the selected CKB version requires it')
593
+ .option('--dry-run', 'Run source/spec/database preflight without replacing the current devnet')
594
+ .action(devnet_fork_1.devnetFork);
595
+ function normalizeGlobalJsonFlag(argv) {
596
+ const jsonRequested = argv.slice(2).includes('--json');
597
+ if (!jsonRequested)
598
+ return argv;
599
+ return [argv[0], argv[1], '--json', ...argv.slice(2).filter((arg) => arg !== '--json')];
600
+ }
601
+ function installBrokenPipeHandlers() {
602
+ for (const stream of [process.stdout, process.stderr]) {
603
+ stream.on('error', (error) => {
604
+ if (error.code === 'EPIPE') {
605
+ process.exit(0);
606
+ }
607
+ throw error;
608
+ });
609
+ }
610
+ }
611
+ function configureCommanderErrors(command) {
612
+ command.exitOverride();
613
+ command.configureOutput({
614
+ writeErr: (text) => {
615
+ if (!logger_1.logger.isJsonMode())
616
+ process.stderr.write(text);
617
+ },
618
+ });
619
+ command.commands.forEach(configureCommanderErrors);
620
+ }
621
+ function runCli() {
622
+ return __awaiter(this, arguments, void 0, function* (argv = process.argv) {
623
+ installBrokenPipeHandlers();
624
+ const normalizedArgv = normalizeGlobalJsonFlag(argv);
625
+ if (normalizedArgv.includes('--json'))
626
+ logger_1.logger.setJsonMode(true);
627
+ if (!normalizedArgv.slice(2).length) {
628
+ program.outputHelp();
629
+ return;
630
+ }
631
+ configureCommanderErrors(program);
632
+ try {
633
+ yield program.parseAsync(normalizedArgv);
634
+ if (logger_1.logger.isJsonMode() && !logger_1.logger.hasResult() && (process.exitCode == null || process.exitCode === 0)) {
635
+ logger_1.logger.result({ command: activeCommand, completed: true });
636
+ }
637
+ }
638
+ catch (error) {
639
+ if (error instanceof commander_1.CommanderError && error.exitCode === 0)
640
+ return;
641
+ const message = error instanceof Error ? error.message : String(error);
642
+ const code = error instanceof commander_1.CommanderError ? error.code : 'COMMAND_FAILED';
643
+ logger_1.logger.failure(code, message);
644
+ process.exitCode = error instanceof commander_1.CommanderError ? error.exitCode : 1;
645
+ }
646
+ });
647
+ }
648
+ if (require.main === require.cache[eval('__filename')]) {
649
+ void runCli();
558
650
  }
559
651
 
560
652
 
@@ -565,40 +657,90 @@ if (!process.argv.slice(2).length) {
565
657
 
566
658
  "use strict";
567
659
 
660
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
661
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
662
+ return new (P || (P = Promise))(function (resolve, reject) {
663
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
664
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
665
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
666
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
667
+ });
668
+ };
568
669
  var __importDefault = (this && this.__importDefault) || function (mod) {
569
670
  return (mod && mod.__esModule) ? mod : { "default": mod };
570
671
  };
571
672
  Object.defineProperty(exports, "__esModule", ({ value: true }));
572
673
  exports.accounts = accounts;
573
674
  const account_json_1 = __importDefault(__nccwpck_require__(13849));
675
+ const core_1 = __nccwpck_require__(39842);
676
+ const setting_1 = __nccwpck_require__(25546);
677
+ const fork_1 = __nccwpck_require__(77912);
678
+ const readiness_1 = __nccwpck_require__(19326);
679
+ const base_1 = __nccwpck_require__(69951);
574
680
  const logger_1 = __nccwpck_require__(65370);
575
681
  function accounts() {
576
- logger_1.logger.warn([
577
- '#### All Accounts are for test and develop only ####'.toUpperCase(),
578
- "#### DON'T use these accounts on Mainnet ####".toUpperCase(),
579
- '#### Otherwise You will loose your money ####'.toUpperCase(),
580
- '',
581
- ]);
582
- logger_1.logger.info([
583
- 'Print account list, each account is funded with 42_000_000_00000000 capacity in the devnet genesis block.',
584
- '',
585
- ]);
586
- const accountDetails = account_json_1.default.map((account, index) => {
587
- return [
588
- `- "#": ${index}`,
589
- `address: ${account.address}`,
590
- `privkey: ${account.privkey}`,
591
- `pubkey: ${account.pubkey}`,
592
- `lock_arg: ${account.lockScript.args}`,
593
- 'lockScript:',
594
- ` codeHash: ${account.lockScript.codeHash}`,
595
- ` hashType: ${account.lockScript.hashType}`,
596
- ` args: ${account.lockScript.args}`,
682
+ return __awaiter(this, arguments, void 0, function* (options = {}) {
683
+ const settings = (0, setting_1.readSettings)();
684
+ const fork = (0, fork_1.readForkState)(settings.devnet.configPath);
685
+ const isMainnetFork = (fork === null || fork === void 0 ? void 0 : fork.source) === 'mainnet';
686
+ const client = isMainnetFork
687
+ ? new core_1.ccc.ClientPublicMainnet({ url: settings.devnet.rpcUrl, fallbacks: [] })
688
+ : new core_1.ccc.ClientPublicTestnet({ url: settings.devnet.rpcUrl, fallbacks: [] });
689
+ const context = fork ? `DEVNET (fork of ${fork.source.toUpperCase()})` : 'DEVNET';
690
+ const readiness = fork ? yield (0, readiness_1.warnIfForkIndexerIsBehind)(base_1.Network.devnet) : undefined;
691
+ const canReadSpendableBalance = (readiness === null || readiness === void 0 ? void 0 : readiness.ready) === true && readiness.indexerTip != null && readiness.indexerLag === BigInt(0);
692
+ logger_1.logger.warn([
693
+ '#### All Accounts are for test and develop only ####'.toUpperCase(),
694
+ "#### DON'T use these accounts on Mainnet ####".toUpperCase(),
695
+ '#### Otherwise You will lose your money ####'.toUpperCase(),
597
696
  '',
598
- ];
599
- });
600
- accountDetails.forEach((details, _index) => {
601
- logger_1.logger.info(details);
697
+ ]);
698
+ if (fork) {
699
+ logger_1.logger.info([
700
+ `Print account list for ${context}. Addresses use the source-chain prefix.`,
701
+ 'Forked devnets do not include the standard offckb genesis allocation; funds come from fork-mined cellbase cells.',
702
+ 'Run `offckb devnet info` before trusting balances while the indexer catches up.',
703
+ '',
704
+ ]);
705
+ }
706
+ else {
707
+ logger_1.logger.info([
708
+ 'Print account list, each account is funded with 42_000_000_00000000 capacity in the devnet genesis block.',
709
+ '',
710
+ ]);
711
+ }
712
+ const resolvedAccounts = yield Promise.all(account_json_1.default.map((account, index) => __awaiter(this, void 0, void 0, function* () {
713
+ const script = core_1.ccc.Script.from(account.lockScript);
714
+ const address = core_1.ccc.Address.fromScript(script, client).toString();
715
+ const spendableCkb = canReadSpendableBalance
716
+ ? core_1.ccc.fixedPointToString(yield client.getBalanceSingle(script))
717
+ : undefined;
718
+ return Object.assign(Object.assign(Object.assign({ index,
719
+ address }, (spendableCkb == null ? {} : { spendableCkb })), (options.showPrivateKeys ? { privkey: account.privkey } : {})), { pubkey: account.pubkey, lockArg: account.lockScript.args, lockScript: account.lockScript });
720
+ })));
721
+ const accountDetails = resolvedAccounts.map((account) => {
722
+ return [
723
+ `- "#": ${account.index}`,
724
+ `address: ${account.address}`,
725
+ ...('spendableCkb' in account ? [`spendable_ckb: ${account.spendableCkb}`] : []),
726
+ ...(options.showPrivateKeys ? [`privkey: ${account.privkey}`] : []),
727
+ `pubkey: ${account.pubkey}`,
728
+ `lock_arg: ${account.lockArg}`,
729
+ 'lockScript:',
730
+ ` codeHash: ${account.lockScript.codeHash}`,
731
+ ` hashType: ${account.lockScript.hashType}`,
732
+ ` args: ${account.lockScript.args}`,
733
+ '',
734
+ ];
735
+ });
736
+ accountDetails.forEach((details, _index) => {
737
+ logger_1.logger.info(details);
738
+ });
739
+ if (!options.showPrivateKeys) {
740
+ logger_1.logger.info('Private keys are hidden by default. Use --show-private-keys only in a trusted local terminal.');
741
+ }
742
+ logger_1.logger.result({ command: 'accounts', context, forked: Boolean(fork), accounts: resolvedAccounts });
743
+ return resolvedAccounts;
602
744
  });
603
745
  }
604
746
 
@@ -625,10 +767,12 @@ const ckb_1 = __nccwpck_require__(83748);
625
767
  const validator_1 = __nccwpck_require__(39534);
626
768
  const base_1 = __nccwpck_require__(69951);
627
769
  const logger_1 = __nccwpck_require__(65370);
770
+ const readiness_1 = __nccwpck_require__(19326);
628
771
  function balanceOf(address_1) {
629
772
  return __awaiter(this, arguments, void 0, function* (address, opt = { network: base_1.Network.devnet }) {
630
773
  const network = opt.network;
631
774
  (0, validator_1.validateNetworkOpt)(network);
775
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
632
776
  const ckb = new ckb_1.CKB({ network });
633
777
  const [balanceInCKB, udtBalances] = yield Promise.all([
634
778
  ckb.balance(address),
@@ -642,7 +786,9 @@ function balanceOf(address_1) {
642
786
  logger_1.logger.info(` ${udt.kind} (args=${udt.args}): ${udt.balance}`);
643
787
  }
644
788
  }
645
- process.exit(0);
789
+ const result = { command: 'balance', network, address, ckb: balanceInCKB, udt: filtered };
790
+ logger_1.logger.result(result);
791
+ return result;
646
792
  });
647
793
  }
648
794
  function filterUdtBalances(balances, opt) {
@@ -686,8 +832,7 @@ function clean(options) {
686
832
  logger_1.logger.info(`Chain data cleaned. Devnet config files preserved.`);
687
833
  }
688
834
  catch (error) {
689
- logger_1.logger.info(`Did you stop running the chain first?`);
690
- logger_1.logger.error(error.message);
835
+ throw new Error(`Failed to clean chain data. Did you stop the chain first? ${error.message}`);
691
836
  }
692
837
  }
693
838
  else {
@@ -703,8 +848,7 @@ function clean(options) {
703
848
  logger_1.logger.info(`Chain data cleaned.`);
704
849
  }
705
850
  catch (error) {
706
- logger_1.logger.info(`Did you stop running the chain first?`);
707
- logger_1.logger.error(error.message);
851
+ throw new Error(`Failed to clean devnet data. Did you stop the chain first? ${error.message}`);
708
852
  }
709
853
  }
710
854
  else {
@@ -761,8 +905,7 @@ function Config(action, item, value) {
761
905
  const settings = (0, setting_1.readSettings)();
762
906
  const proxy = settings.proxy;
763
907
  if (proxy == null) {
764
- logger_1.logger.info(`No Proxy.`);
765
- process.exit(0);
908
+ return logger_1.logger.info(`No Proxy.`);
766
909
  }
767
910
  return logger_1.logger.info(`${request_1.Request.proxyConfigToUrl(proxy)}`);
768
911
  }
@@ -787,7 +930,7 @@ function Config(action, item, value) {
787
930
  return (0, setting_1.writeSettings)(settings);
788
931
  }
789
932
  catch (error) {
790
- return logger_1.logger.error(`invalid proxyURL, `, error.message);
933
+ throw new Error(`invalid proxyURL: ${error.message}`);
791
934
  }
792
935
  }
793
936
  case ConfigItem.ckbVersion: {
@@ -799,11 +942,11 @@ function Config(action, item, value) {
799
942
  return (0, setting_1.writeSettings)(settings);
800
943
  }
801
944
  else {
802
- return logger_1.logger.error(`invalid version value, ${value}. Check available versions on https://github.com/nervosnetwork/ckb/tags`);
945
+ throw new Error(`invalid version value, ${value}. Check available versions on https://github.com/nervosnetwork/ckb/tags`);
803
946
  }
804
947
  }
805
948
  catch (error) {
806
- return logger_1.logger.error(`invalid version value, `, error.message);
949
+ throw new Error(`invalid version value: ${error.message}`);
807
950
  }
808
951
  }
809
952
  default:
@@ -923,8 +1066,7 @@ function createScriptProject(name_1) {
923
1066
  const fullProjectPath = path.resolve(projectPath);
924
1067
  // Check if directory already exists
925
1068
  if (fs.existsSync(fullProjectPath)) {
926
- logger_1.logger.error(`❌ Directory '${projectPath}' already exists!`);
927
- process.exit(1);
1069
+ throw new Error(`Directory '${projectPath}' already exists!`);
928
1070
  }
929
1071
  logger_1.logger.info([
930
1072
  '📝 Project details:',
@@ -945,8 +1087,7 @@ function createScriptProject(name_1) {
945
1087
  // Get template directory (adjust path based on whether we're running from source or built)
946
1088
  const templateDir = possiblePaths.find((p) => fs.existsSync(p)) || possiblePaths[0];
947
1089
  if (!fs.existsSync(templateDir)) {
948
- logger_1.logger.error(`❌ Template directory not found: ${templateDir}`);
949
- process.exit(1);
1090
+ throw new Error(`Template directory not found: ${templateDir}`);
950
1091
  }
951
1092
  // Initialize template processor
952
1093
  const processor = new processor_1.TemplateProcessor(templateDir);
@@ -1022,8 +1163,7 @@ function createScriptProject(name_1) {
1022
1163
  }
1023
1164
  }
1024
1165
  catch (error) {
1025
- logger_1.logger.error(`\n❌ Failed to create project: ${error.message}`);
1026
- process.exit(1);
1166
+ throw new Error(`Failed to create project: ${error.message}`);
1027
1167
  }
1028
1168
  });
1029
1169
  }
@@ -1089,6 +1229,7 @@ const path_1 = __importDefault(__nccwpck_require__(16928));
1089
1229
  const advanced_1 = __nccwpck_require__(98486);
1090
1230
  const base_1 = __nccwpck_require__(69951);
1091
1231
  const encoding_1 = __nccwpck_require__(6851);
1232
+ const json_rpc_1 = __nccwpck_require__(51726);
1092
1233
  const logger_1 = __nccwpck_require__(65370);
1093
1234
  function debugTransaction(txHash, network) {
1094
1235
  return __awaiter(this, void 0, void 0, function* () {
@@ -1159,8 +1300,8 @@ function buildTxFileOptionBy(txHash, network) {
1159
1300
  if (!fs_1.default.existsSync(outputFilePath)) {
1160
1301
  const rpc = settings[network].rpcUrl;
1161
1302
  const txJsonFilePath = buildTransactionJsonFilePath(network, txHash);
1162
- if (!fs_1.default.existsSync(outputFilePath)) {
1163
- fs_1.default.mkdirSync(path_1.default.dirname(outputFilePath), { recursive: true });
1303
+ if (!fs_1.default.existsSync(txJsonFilePath)) {
1304
+ yield fetchTransactionIntoCache(rpc, txHash, txJsonFilePath);
1164
1305
  }
1165
1306
  yield (0, ckb_tx_dumper_1.dumpTransaction)({ rpc, txJsonFilePath, outputFilePath });
1166
1307
  }
@@ -1168,6 +1309,23 @@ function buildTxFileOptionBy(txHash, network) {
1168
1309
  return opt;
1169
1310
  });
1170
1311
  }
1312
+ // Fallback for transactions that never went through the local RPC proxy
1313
+ // (e.g. historical transactions on a forked devnet): pull the transaction
1314
+ // from the node and cache it in the same JSON-RPC format the proxy stores.
1315
+ function fetchTransactionIntoCache(rpc, txHash, txJsonFilePath) {
1316
+ return __awaiter(this, void 0, void 0, function* () {
1317
+ logger_1.logger.info(`Transaction ${txHash} not found in local cache, fetching from ${rpc} ..`);
1318
+ const result = yield (0, json_rpc_1.callJsonRpc)(rpc, 'get_transaction', [txHash]).catch((error) => {
1319
+ throw new Error(`Failed to fetch transaction ${txHash} from ${rpc}: ${error.message}`);
1320
+ });
1321
+ if (!(result === null || result === void 0 ? void 0 : result.transaction)) {
1322
+ throw new Error(`Transaction ${txHash} not found on ${rpc}. ` +
1323
+ `Check the hash and the --network option, or send the transaction through the offckb RPC proxy first.`);
1324
+ }
1325
+ fs_1.default.mkdirSync(path_1.default.dirname(txJsonFilePath), { recursive: true });
1326
+ fs_1.default.writeFileSync(txJsonFilePath, JSON.stringify(result.transaction, null, 2));
1327
+ });
1328
+ }
1171
1329
  function buildTransactionJsonFilePath(network, txHash) {
1172
1330
  const settings = (0, setting_1.readSettings)();
1173
1331
  if (network === base_1.Network.devnet) {
@@ -1230,8 +1388,7 @@ function buildContract(jsFile, outputFile, jsVmPath) {
1230
1388
  logger_1.logger.success(`✅ Contract built successfully: ${outputFile}`);
1231
1389
  }
1232
1390
  catch (error) {
1233
- logger_1.logger.error(`❌ Build failed: ${error}`);
1234
- process.exit(1);
1391
+ throw new Error(`Build failed: ${error instanceof Error ? error.message : String(error)}`);
1235
1392
  }
1236
1393
  });
1237
1394
  }
@@ -1267,14 +1424,19 @@ const deploy_1 = __nccwpck_require__(45615);
1267
1424
  const ckb_1 = __nccwpck_require__(83748);
1268
1425
  const prompts_1 = __nccwpck_require__(18167);
1269
1426
  const logger_1 = __nccwpck_require__(65370);
1427
+ const private_key_1 = __nccwpck_require__(26241);
1428
+ const readiness_1 = __nccwpck_require__(19326);
1429
+ const fork_safety_1 = __nccwpck_require__(47625);
1270
1430
  function deploy() {
1271
1431
  return __awaiter(this, arguments, void 0, function* (opt = { network: base_1.Network.devnet, typeId: false, target: './', output: './deployment' }) {
1272
1432
  var _a;
1273
1433
  const network = opt.network;
1274
1434
  (0, validator_1.validateNetworkOpt)(network);
1275
- const ckb = new ckb_1.CKB({ network });
1276
1435
  // we use deployerAccount to deploy contract by default
1277
- const privateKey = opt.privkey || account_1.deployerAccount.privkey;
1436
+ const privateKey = (0, private_key_1.resolvePrivateKey)(opt, account_1.deployerAccount.privkey);
1437
+ (0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
1438
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
1439
+ const ckb = new ckb_1.CKB({ network });
1278
1440
  const enableTypeId = (_a = opt.typeId) !== null && _a !== void 0 ? _a : false;
1279
1441
  const targetFolder = opt.target;
1280
1442
  const output = opt.output;
@@ -1298,7 +1460,7 @@ function deploy() {
1298
1460
  '',
1299
1461
  ` 📁 Deployment artifacts will be saved to: ${outputFolder}`,
1300
1462
  ` 🌐 Network: ${network}`,
1301
- ` 🔑 Using ${opt.privkey ? 'custom' : 'default'} private key`,
1463
+ ` 🔑 Using ${opt.privkey || opt.privkeyFile || process.env.OFFCKB_PRIVATE_KEY ? 'custom' : 'default'} private key`,
1302
1464
  ` 🔄 Type ID: ${enableTypeId ? 'enabled (upgradable)' : 'disabled (immutable)'}`,
1303
1465
  ]);
1304
1466
  // Skip confirmation if -y flag is provided
@@ -1344,22 +1506,40 @@ const link_1 = __nccwpck_require__(27814);
1344
1506
  const validator_1 = __nccwpck_require__(39534);
1345
1507
  const request_1 = __nccwpck_require__(65897);
1346
1508
  const logger_1 = __nccwpck_require__(65370);
1509
+ const readiness_1 = __nccwpck_require__(19326);
1510
+ const fork_safety_1 = __nccwpck_require__(47625);
1511
+ const TESTNET_FAUCET_CLAIM_AMOUNT = '10000';
1347
1512
  function deposit(toAddress_1, amountInCKB_1) {
1348
1513
  return __awaiter(this, arguments, void 0, function* (toAddress, amountInCKB, opt = { network: base_1.Network.devnet }) {
1349
1514
  const network = opt.network;
1350
1515
  (0, validator_1.validateNetworkOpt)(network);
1351
1516
  const ckb = new ckb_1.CKB({ network });
1352
1517
  if (network === 'testnet') {
1353
- return yield depositFromTestnetFaucet(toAddress, ckb);
1518
+ const txHash = yield depositFromTestnetFaucet(toAddress, ckb);
1519
+ logger_1.logger.result({
1520
+ command: 'deposit',
1521
+ network,
1522
+ source: 'fixed-testnet-faucet-claim',
1523
+ requestedAmount: amountInCKB,
1524
+ faucetClaimAmount: TESTNET_FAUCET_CLAIM_AMOUNT,
1525
+ toAddress,
1526
+ txHash,
1527
+ });
1528
+ return txHash;
1354
1529
  }
1355
1530
  // deposit from devnet miner
1356
1531
  const privateKey = account_1.ckbDevnetMinerAccount.privkey;
1532
+ const rejectInputsAtOrBeforeBlock = (0, fork_safety_1.validateMainnetForkSigning)(network, privateKey);
1533
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
1357
1534
  const txHash = yield ckb.transfer({
1358
1535
  toAddress,
1359
1536
  privateKey,
1360
1537
  amountInCKB,
1538
+ rejectInputsAtOrBeforeBlock,
1361
1539
  });
1362
1540
  logger_1.logger.info('tx hash: ', txHash);
1541
+ logger_1.logger.result({ command: 'deposit', network, amount: amountInCKB, toAddress, txHash });
1542
+ return txHash;
1363
1543
  });
1364
1544
  }
1365
1545
  function depositFromTestnetFaucet(ckbAddress, ckb) {
@@ -1385,6 +1565,7 @@ function depositFromTestnetFaucet(ckbAddress, ckb) {
1385
1565
  }
1386
1566
  const txHash = yield ckb.transferAll({ privateKey: randomAccountPrivateKey, toAddress: ckbAddress });
1387
1567
  logger_1.logger.info(`Done, check ${(0, link_1.buildTestnetTxLink)(txHash)} for details.`);
1568
+ return txHash;
1388
1569
  });
1389
1570
  }
1390
1571
  function sendClaimRequest(toAddress) {
@@ -1400,7 +1581,7 @@ function sendClaimRequest(toAddress) {
1400
1581
  const body = JSON.stringify({
1401
1582
  claim_event: {
1402
1583
  address_hash: toAddress,
1403
- amount: '10000', // unit: CKB
1584
+ amount: TESTNET_FAUCET_CLAIM_AMOUNT, // unit: CKB
1404
1585
  },
1405
1586
  });
1406
1587
  const config = {
@@ -1482,12 +1663,8 @@ function devnetConfig() {
1482
1663
  return;
1483
1664
  }
1484
1665
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
1485
- logger_1.logger.error('Interactive devnet config editor requires a TTY terminal.');
1486
- logger_1.logger.info('Use non-interactive mode instead, e.g.:');
1487
- logger_1.logger.info(' offckb devnet config --set ckb.logger.filter=info');
1488
- logger_1.logger.info(' offckb devnet config --set miner.client.poll_interval=1500');
1489
- process.exitCode = 1;
1490
- return;
1666
+ throw new Error('Interactive devnet config editor requires a TTY terminal. Use non-interactive mode, e.g. ' +
1667
+ '`offckb devnet config --set ckb.logger.filter=info`.');
1491
1668
  }
1492
1669
  const isSaved = yield (0, devnet_config_tui_1.runDevnetConfigTui)(editor, configPath);
1493
1670
  if (isSaved) {
@@ -1498,13 +1675,121 @@ function devnetConfig() {
1498
1675
  logger_1.logger.info('No changes saved.');
1499
1676
  }
1500
1677
  catch (error) {
1501
- const message = error instanceof Error ? error.message : String(error);
1502
- logger_1.logger.error(message);
1678
+ let message = error instanceof Error ? error.message : String(error);
1503
1679
  if (error instanceof config_editor_1.InitializationError) {
1504
- logger_1.logger.info('Tip: run `offckb node` once to initialize devnet config files first.');
1680
+ message += ' Tip: run `offckb node` once to initialize devnet config files first.';
1505
1681
  }
1506
- process.exitCode = 1;
1682
+ throw new Error(message);
1683
+ }
1684
+ });
1685
+ }
1686
+
1687
+
1688
+ /***/ }),
1689
+
1690
+ /***/ 36709:
1691
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
1692
+
1693
+ "use strict";
1694
+
1695
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1696
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1697
+ return new (P || (P = Promise))(function (resolve, reject) {
1698
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1699
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1700
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1701
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1702
+ });
1703
+ };
1704
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1705
+ exports.devnetFork = devnetFork;
1706
+ const fork_1 = __nccwpck_require__(77912);
1707
+ function devnetFork(options) {
1708
+ return __awaiter(this, void 0, void 0, function* () {
1709
+ if (options.source && options.source !== 'mainnet' && options.source !== 'testnet') {
1710
+ throw new Error(`Invalid --source value: ${options.source}. Expected mainnet or testnet.`);
1507
1711
  }
1712
+ yield (0, fork_1.forkDevnet)(options);
1713
+ });
1714
+ }
1715
+
1716
+
1717
+ /***/ }),
1718
+
1719
+ /***/ 60905:
1720
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
1721
+
1722
+ "use strict";
1723
+
1724
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1725
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1726
+ return new (P || (P = Promise))(function (resolve, reject) {
1727
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1728
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1729
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1730
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1731
+ });
1732
+ };
1733
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1734
+ exports.devnetInfo = devnetInfo;
1735
+ const setting_1 = __nccwpck_require__(25546);
1736
+ const fork_1 = __nccwpck_require__(77912);
1737
+ const readiness_1 = __nccwpck_require__(19326);
1738
+ const logger_1 = __nccwpck_require__(65370);
1739
+ function devnetInfo() {
1740
+ return __awaiter(this, void 0, void 0, function* () {
1741
+ var _a, _b, _c;
1742
+ const settings = (0, setting_1.readSettings)();
1743
+ const fork = (0, fork_1.readForkState)(settings.devnet.configPath);
1744
+ const readiness = yield (0, readiness_1.checkNodeReadiness)(settings.devnet.rpcUrl);
1745
+ const indexerReady = readiness.indexerTip != null && readiness.indexerLag === BigInt(0);
1746
+ const networkIsolated = fork && readiness.peers != null ? readiness.peers === 0 : undefined;
1747
+ const result = {
1748
+ command: 'devnet.info',
1749
+ kind: fork ? `fork-of-${fork.source}` : 'pure-devnet',
1750
+ configPath: settings.devnet.configPath,
1751
+ rpcUrl: settings.devnet.rpcUrl,
1752
+ proxyUrl: `http://127.0.0.1:${settings.devnet.rpcProxyPort}`,
1753
+ ready: readiness.ready,
1754
+ nodeTip: (_a = readiness.nodeTip) === null || _a === void 0 ? void 0 : _a.toString(),
1755
+ indexerTip: (_b = readiness.indexerTip) === null || _b === void 0 ? void 0 : _b.toString(),
1756
+ indexerLag: (_c = readiness.indexerLag) === null || _c === void 0 ? void 0 : _c.toString(),
1757
+ indexerReady,
1758
+ peers: readiness.peers,
1759
+ networkIsolated,
1760
+ error: readiness.error,
1761
+ fork,
1762
+ };
1763
+ logger_1.logger.info(`Devnet: ${result.kind}`);
1764
+ logger_1.logger.info(`RPC: ${result.rpcUrl}`);
1765
+ logger_1.logger.info(`Proxy RPC: ${result.proxyUrl}`);
1766
+ logger_1.logger.info(`Node ready: ${result.ready ? 'yes' : 'no'}`);
1767
+ if (readiness.nodeTip != null)
1768
+ logger_1.logger.info(`Node tip: ${readiness.nodeTip}`);
1769
+ if (readiness.indexerTip != null)
1770
+ logger_1.logger.info(`Indexer tip: ${readiness.indexerTip}`);
1771
+ if (readiness.indexerLag != null) {
1772
+ const message = `Indexer lag: ${readiness.indexerLag}`;
1773
+ if (readiness.indexerLag > BigInt(0))
1774
+ logger_1.logger.warn(`${message}; indexed queries may be stale.`);
1775
+ else
1776
+ logger_1.logger.info(message);
1777
+ }
1778
+ logger_1.logger.info(`Indexer ready: ${indexerReady ? 'yes' : 'no'}`);
1779
+ if (readiness.peers != null)
1780
+ logger_1.logger.info(`Peers: ${readiness.peers}`);
1781
+ if (fork)
1782
+ logger_1.logger.info(`Public network isolated: ${networkIsolated == null ? 'unknown' : networkIsolated ? 'yes' : 'NO'}`);
1783
+ if (fork && readiness.peers != null && readiness.peers > 0) {
1784
+ logger_1.logger.warn('A forked devnet has connected peers. Stop it and inspect ckb.toml before signing or mining.');
1785
+ }
1786
+ if ((fork === null || fork === void 0 ? void 0 : fork.source) === 'mainnet') {
1787
+ logger_1.logger.warn('MAINNET FORK REPLAY RISK: only sign with built-in dev keys and fork-mined cells.');
1788
+ }
1789
+ if (readiness.error)
1790
+ logger_1.logger.warn(readiness.error);
1791
+ logger_1.logger.result(result);
1792
+ return result;
1508
1793
  });
1509
1794
  }
1510
1795
 
@@ -1570,14 +1855,23 @@ const path = __importStar(__nccwpck_require__(16928));
1570
1855
  const init_chain_1 = __nccwpck_require__(1360);
1571
1856
  const install_1 = __nccwpck_require__(65611);
1572
1857
  const setting_1 = __nccwpck_require__(25546);
1573
- const encoding_1 = __nccwpck_require__(6851);
1574
1858
  const rpc_proxy_1 = __nccwpck_require__(46589);
1859
+ const fork_1 = __nccwpck_require__(77912);
1860
+ const json_rpc_1 = __nccwpck_require__(51726);
1575
1861
  const base_1 = __nccwpck_require__(69951);
1576
1862
  const logger_1 = __nccwpck_require__(65370);
1863
+ const readiness_1 = __nccwpck_require__(19326);
1577
1864
  const DAEMON_LOG_DIR = 'logs';
1578
1865
  const DAEMON_LOG_FILE = 'daemon.log';
1579
1866
  const DAEMON_PID_FILE = 'daemon.pid';
1580
1867
  const DAEMON_CHILD_ENV = 'OFFCKB_DAEMON_CHILD';
1868
+ const NODE_READY_TIMEOUT_MS = 90000;
1869
+ const FORK_NODE_READY_TIMEOUT_MS = 10 * 60000;
1870
+ function cleanChildOutput(data) {
1871
+ // CKB colors its output even when it is redirected. Strip ANSI control
1872
+ // sequences so JSON logs stay machine-readable.
1873
+ return String(data).replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '');
1874
+ }
1581
1875
  function startNode({ version, network = base_1.Network.devnet, binaryPath, daemon }) {
1582
1876
  if (binaryPath && network !== base_1.Network.devnet) {
1583
1877
  logger_1.logger.warn('Custom binaryPath is only supported for devnet. The provided binaryPath will be ignored.');
@@ -1598,7 +1892,7 @@ function startNode({ version, network = base_1.Network.devnet, binaryPath, daemo
1598
1892
  }
1599
1893
  function nodeDevnet(_a) {
1600
1894
  return __awaiter(this, arguments, void 0, function* ({ version, binaryPath, daemon }) {
1601
- var _b, _c;
1895
+ var _b, _c, _d, _e, _f;
1602
1896
  if (daemon) {
1603
1897
  return startDaemon();
1604
1898
  }
@@ -1606,54 +1900,114 @@ function nodeDevnet(_a) {
1606
1900
  const ckbVersion = version || settings.bins.defaultCKBVersion;
1607
1901
  let ckbBinPath = '';
1608
1902
  if (binaryPath) {
1609
- ckbBinPath = (0, encoding_1.encodeBinPathForTerminal)(binaryPath);
1903
+ ckbBinPath = binaryPath;
1610
1904
  logger_1.logger.info(`Using custom CKB binary path: ${ckbBinPath}`);
1611
1905
  }
1612
1906
  else {
1613
1907
  yield (0, install_1.installCKBBinary)(ckbVersion);
1614
- ckbBinPath = (0, encoding_1.encodeBinPathForTerminal)((0, setting_1.getCKBBinaryPath)(ckbVersion));
1908
+ ckbBinPath = (0, setting_1.getCKBBinaryPath)(ckbVersion);
1615
1909
  }
1616
1910
  yield (0, init_chain_1.initChainIfNeeded)();
1617
- const devnetConfigPath = (0, encoding_1.encodeBinPathForTerminal)(settings.devnet.configPath);
1618
- const ckbCmd = `${ckbBinPath} run -C ${devnetConfigPath}`;
1619
- const minerCmd = `${ckbBinPath} miner -C ${devnetConfigPath}`;
1911
+ const devnetConfigPath = settings.devnet.configPath;
1912
+ // A forked devnet must boot once with --skip-spec-check --overwrite-spec so
1913
+ // the imported (and patched) spec replaces the source chain's stored spec.
1914
+ const forkState = (0, fork_1.readForkState)(settings.devnet.configPath);
1915
+ const firstRunFlags = (forkState === null || forkState === void 0 ? void 0 : forkState.firstRunPending) ? ' --skip-spec-check --overwrite-spec' : '';
1916
+ if (forkState === null || forkState === void 0 ? void 0 : forkState.firstRunPending) {
1917
+ logger_1.logger.info(`Forked devnet (${forkState.source}) detected, first run uses --skip-spec-check --overwrite-spec.`);
1918
+ }
1620
1919
  logger_1.logger.info(`Launching CKB devnet Node...`);
1920
+ const runArgs = ['run', '-C', devnetConfigPath];
1921
+ if (firstRunFlags)
1922
+ runArgs.push('--skip-spec-check', '--overwrite-spec');
1923
+ const ckbProcess = (0, child_process_1.spawn)(ckbBinPath, runArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
1924
+ (_b = ckbProcess.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => logger_1.logger.info(['CKB:', cleanChildOutput(data)]));
1925
+ (_c = ckbProcess.stderr) === null || _c === void 0 ? void 0 : _c.on('data', (data) => logger_1.logger.error(['CKB error:', cleanChildOutput(data)]));
1926
+ let ckbExited = false;
1927
+ ckbProcess.once('exit', () => {
1928
+ ckbExited = true;
1929
+ });
1930
+ ckbProcess.once('error', () => {
1931
+ ckbExited = true;
1932
+ });
1933
+ const timeoutMs = forkState ? FORK_NODE_READY_TIMEOUT_MS : NODE_READY_TIMEOUT_MS;
1934
+ const readiness = yield (0, readiness_1.waitForNodeReady)(settings.devnet.rpcUrl, timeoutMs, () => !ckbExited);
1935
+ if (!readiness.ready) {
1936
+ if (!ckbExited)
1937
+ ckbProcess.kill('SIGTERM');
1938
+ throw new Error(`CKB devnet failed to become ready: ${(_d = readiness.error) !== null && _d !== void 0 ? _d : 'CKB process exited'}`);
1939
+ }
1940
+ if (ckbExited) {
1941
+ throw new Error('CKB devnet exited immediately after its readiness check.');
1942
+ }
1943
+ if (forkState === null || forkState === void 0 ? void 0 : forkState.firstRunPending) {
1944
+ yield clearForkFirstRunWhenNodeUp(ckbProcess, settings.devnet.rpcUrl, settings.devnet.configPath, forkState.genesisHash);
1945
+ }
1946
+ let minerProcess;
1621
1947
  try {
1622
- // Run first command
1623
- const ckbProcess = (0, child_process_1.exec)(ckbCmd);
1624
- // Log first command's output
1625
- (_b = ckbProcess.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
1626
- logger_1.logger.info(['CKB:', data.toString()]);
1627
- });
1628
- (_c = ckbProcess.stderr) === null || _c === void 0 ? void 0 : _c.on('data', (data) => {
1629
- logger_1.logger.error(['CKB error:', data.toString()]);
1630
- });
1631
- // Start the second command after 3 seconds
1632
- setTimeout(() => __awaiter(this, void 0, void 0, function* () {
1633
- var _a, _b;
1634
- try {
1635
- // Run second command
1636
- const minerProcess = (0, child_process_1.exec)(minerCmd);
1637
- (_a = minerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
1638
- logger_1.logger.info(['CKB-Miner:', data.toString()]);
1639
- });
1640
- (_b = minerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
1641
- logger_1.logger.error(['CKB-Miner error:', data.toString()]);
1642
- });
1643
- // by default we start the proxy server
1644
- const ckbRpc = settings.devnet.rpcUrl;
1645
- const port = settings.devnet.rpcProxyPort;
1646
- const proxy = (0, rpc_proxy_1.createRPCProxy)(base_1.Network.devnet, ckbRpc, port);
1647
- proxy.start();
1648
- }
1649
- catch (error) {
1650
- logger_1.logger.error('Error running CKB-Miner:', error);
1651
- }
1652
- }), 3000);
1948
+ minerProcess = (0, child_process_1.spawn)(ckbBinPath, ['miner', '-C', devnetConfigPath], { stdio: ['ignore', 'pipe', 'pipe'] });
1653
1949
  }
1654
1950
  catch (error) {
1655
- logger_1.logger.error('Error:', error);
1951
+ ckbProcess.kill('SIGTERM');
1952
+ throw new Error(`CKB miner failed to start: ${error.message}`);
1656
1953
  }
1954
+ (_e = minerProcess.stdout) === null || _e === void 0 ? void 0 : _e.on('data', (data) => logger_1.logger.info(['CKB-Miner:', cleanChildOutput(data)]));
1955
+ (_f = minerProcess.stderr) === null || _f === void 0 ? void 0 : _f.on('data', (data) => logger_1.logger.error(['CKB-Miner error:', cleanChildOutput(data)]));
1956
+ try {
1957
+ yield waitForChildSpawn(minerProcess, 'CKB miner');
1958
+ }
1959
+ catch (error) {
1960
+ ckbProcess.kill('SIGTERM');
1961
+ throw error;
1962
+ }
1963
+ if (ckbExited) {
1964
+ if (!minerProcess.killed)
1965
+ minerProcess.kill('SIGTERM');
1966
+ throw new Error('CKB devnet exited while the miner was starting.');
1967
+ }
1968
+ const proxy = (0, rpc_proxy_1.createRPCProxy)(base_1.Network.devnet, settings.devnet.rpcUrl, settings.devnet.rpcProxyPort);
1969
+ proxy.start();
1970
+ logger_1.logger.success(`CKB devnet is ready at ${settings.devnet.rpcUrl}.`);
1971
+ logger_1.logger.result({
1972
+ command: 'node',
1973
+ network: base_1.Network.devnet,
1974
+ daemon: false,
1975
+ rpcUrl: settings.devnet.rpcUrl,
1976
+ proxyUrl: `http://127.0.0.1:${settings.devnet.rpcProxyPort}`,
1977
+ });
1978
+ // Treat CKB, miner and proxy as one service. A dead CKB must not leave a
1979
+ // healthy-looking proxy and a miner that retries forever.
1980
+ let serviceStopping = false;
1981
+ const stopService = (component, code, signal) => {
1982
+ if (serviceStopping)
1983
+ return;
1984
+ serviceStopping = true;
1985
+ if (component !== 'CKB node' && !ckbProcess.killed)
1986
+ ckbProcess.kill('SIGTERM');
1987
+ if (component !== 'CKB miner' && !minerProcess.killed)
1988
+ minerProcess.kill('SIGTERM');
1989
+ proxy.stop();
1990
+ if (process.env[DAEMON_CHILD_ENV] === '1')
1991
+ cleanupPidFile(resolveDaemonPaths().pidFile);
1992
+ logger_1.logger.error(`${component} exited unexpectedly (code=${code !== null && code !== void 0 ? code : 'null'}, signal=${signal !== null && signal !== void 0 ? signal : 'none'}).`);
1993
+ process.exitCode = typeof code === 'number' && code > 0 ? code : 1;
1994
+ };
1995
+ ckbProcess.once('exit', (code, signal) => stopService('CKB node', code, signal));
1996
+ minerProcess.once('exit', (code, signal) => stopService('CKB miner', code, signal));
1997
+ });
1998
+ }
1999
+ function waitForChildSpawn(child, label) {
2000
+ return new Promise((resolve, reject) => {
2001
+ const onSpawn = () => {
2002
+ child.removeListener('error', onError);
2003
+ resolve();
2004
+ };
2005
+ const onError = (error) => {
2006
+ child.removeListener('spawn', onSpawn);
2007
+ reject(new Error(`${label} failed to start: ${error.message}`));
2008
+ };
2009
+ child.once('spawn', onSpawn);
2010
+ child.once('error', onError);
1657
2011
  });
1658
2012
  }
1659
2013
  function resolveDaemonPaths() {
@@ -1663,6 +2017,49 @@ function resolveDaemonPaths() {
1663
2017
  const pidFile = path.join(logDir, DAEMON_PID_FILE);
1664
2018
  return { logDir, logFile, pidFile };
1665
2019
  }
2020
+ // Poll the devnet RPC until the spawned node answers with the fork's genesis
2021
+ // hash, then mark the first run as done so subsequent `offckb node` runs boot
2022
+ // normally. Two guards against clearing the flag on the wrong signal:
2023
+ // - the poll aborts when the spawned ckb process exits (e.g. failed boot),
2024
+ // - an answering node is only trusted when its genesis matches the fork
2025
+ // state — an unrelated node occupying the port must not clear the flag.
2026
+ function clearForkFirstRunWhenNodeUp(ckbProcess, rpcUrl, configPath, expectedGenesisHash) {
2027
+ return __awaiter(this, void 0, void 0, function* () {
2028
+ let processExited = false;
2029
+ const markExited = () => {
2030
+ processExited = true;
2031
+ };
2032
+ ckbProcess.once('exit', markExited);
2033
+ ckbProcess.once('error', markExited);
2034
+ const timeoutMs = 10 * 60 * 1000; // large forks take a while to boot
2035
+ const start = Date.now();
2036
+ while (!processExited && Date.now() - start < timeoutMs) {
2037
+ try {
2038
+ const genesisHash = String(yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_block_hash', ['0x0'], 5000)).toLowerCase();
2039
+ if (genesisHash !== expectedGenesisHash.toLowerCase()) {
2040
+ logger_1.logger.warn(`A node is answering at ${rpcUrl} but reports a different genesis (${genesisHash}); ` +
2041
+ 'leaving the first-run flags in place.');
2042
+ return;
2043
+ }
2044
+ // The miner has not started yet, so this tip is the exact boundary
2045
+ // between copied public-chain state and cells mined on the local fork.
2046
+ const forkBlockNumber = BigInt(String(yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_tip_block_number', [], 5000))).toString();
2047
+ (0, fork_1.markForkFirstRunComplete)(configPath, forkBlockNumber);
2048
+ logger_1.logger.success('Forked devnet is up; first-run spec flags cleared.');
2049
+ return;
2050
+ }
2051
+ catch (_a) {
2052
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
2053
+ }
2054
+ }
2055
+ if (processExited) {
2056
+ logger_1.logger.warn('The CKB process exited before the forked devnet came up; first-run flags will be retried next time.');
2057
+ }
2058
+ else {
2059
+ logger_1.logger.warn('Timed out waiting for the forked devnet to start; first-run flags will be retried next time.');
2060
+ }
2061
+ });
2062
+ }
1666
2063
  function readPidFile(pidFile) {
1667
2064
  var _a, _b;
1668
2065
  let raw;
@@ -1689,6 +2086,7 @@ function readPidFile(pidFile) {
1689
2086
  pid,
1690
2087
  scriptPath: parsed.scriptPath,
1691
2088
  startedAt: (_b = parsed.startedAt) !== null && _b !== void 0 ? _b : new Date(0).toISOString(),
2089
+ status: parsed.status,
1692
2090
  };
1693
2091
  }
1694
2092
  }
@@ -1702,6 +2100,39 @@ function readPidFile(pidFile) {
1702
2100
  function writePidFile(pidFile, metadata) {
1703
2101
  fs.writeFileSync(pidFile, JSON.stringify(metadata, null, 2));
1704
2102
  }
2103
+ function reservePidFile(pidFile, scriptPath) {
2104
+ let fd;
2105
+ try {
2106
+ fd = fs.openSync(pidFile, 'wx');
2107
+ }
2108
+ catch (error) {
2109
+ const err = error;
2110
+ if (err.code === 'EEXIST') {
2111
+ throw new Error('A CKB devnet daemon startup is already in progress. Try again after it completes.');
2112
+ }
2113
+ throw new Error(`Failed to reserve daemon PID file ${pidFile}: ${err.message}`);
2114
+ }
2115
+ let writeError;
2116
+ try {
2117
+ const reservation = {
2118
+ pid: process.pid,
2119
+ scriptPath,
2120
+ startedAt: new Date().toISOString(),
2121
+ status: 'starting',
2122
+ };
2123
+ fs.writeFileSync(fd, JSON.stringify(reservation, null, 2));
2124
+ }
2125
+ catch (error) {
2126
+ writeError = error;
2127
+ }
2128
+ finally {
2129
+ fs.closeSync(fd);
2130
+ }
2131
+ if (writeError) {
2132
+ cleanupPidFile(pidFile);
2133
+ throw new Error(`Failed to initialize daemon PID reservation ${pidFile}: ${writeError.message}`);
2134
+ }
2135
+ }
1705
2136
  function resolveCliEntry() {
1706
2137
  var _a;
1707
2138
  // In priority order. process.argv[1] is the most reliable for a Node CLI.
@@ -1723,12 +2154,19 @@ function resolveCliEntry() {
1723
2154
  return null;
1724
2155
  }
1725
2156
  function isProcessAlive(pid) {
2157
+ if (!Number.isInteger(pid) || pid <= 0)
2158
+ return false;
1726
2159
  try {
1727
2160
  process.kill(pid, 0);
1728
2161
  return true;
1729
2162
  }
1730
- catch (_a) {
1731
- return false;
2163
+ catch (error) {
2164
+ const err = error;
2165
+ if (err.code === 'ESRCH')
2166
+ return false;
2167
+ if (err.code === 'EPERM')
2168
+ throw new Error(`Permission denied when checking daemon process ${pid}.`);
2169
+ throw error;
1732
2170
  }
1733
2171
  }
1734
2172
  function cleanupPidFile(pidFile) {
@@ -1741,10 +2179,16 @@ function cleanupPidFile(pidFile) {
1741
2179
  }
1742
2180
  function waitForProcessExit(pid, timeoutMs) {
1743
2181
  const start = Date.now();
1744
- return new Promise((resolve) => {
2182
+ return new Promise((resolve, reject) => {
1745
2183
  const check = () => {
1746
- if (!isProcessAlive(pid)) {
1747
- resolve(true);
2184
+ try {
2185
+ if (!isProcessAlive(pid)) {
2186
+ resolve(true);
2187
+ return;
2188
+ }
2189
+ }
2190
+ catch (error) {
2191
+ reject(error);
1748
2192
  return;
1749
2193
  }
1750
2194
  if (Date.now() - start >= timeoutMs) {
@@ -1824,73 +2268,159 @@ function terminateProcess(pid, signal) {
1824
2268
  }
1825
2269
  });
1826
2270
  }
2271
+ function failDaemonStartup(error, pid, pidFile) {
2272
+ return __awaiter(this, void 0, void 0, function* () {
2273
+ let exited = false;
2274
+ try {
2275
+ exited = !isProcessAlive(pid);
2276
+ if (!exited) {
2277
+ yield terminateProcess(pid, 'SIGTERM');
2278
+ exited = yield waitForProcessExit(pid, 5000);
2279
+ if (!exited) {
2280
+ yield terminateProcess(pid, 'SIGKILL');
2281
+ exited = yield waitForProcessExit(pid, 5000);
2282
+ }
2283
+ }
2284
+ }
2285
+ catch (_a) {
2286
+ // The child may have exited while cleanup signals were sent. If liveness
2287
+ // cannot be checked, preserve the PID file for a later explicit stop.
2288
+ try {
2289
+ exited = !isProcessAlive(pid);
2290
+ }
2291
+ catch (_b) {
2292
+ exited = false;
2293
+ }
2294
+ }
2295
+ if (exited) {
2296
+ cleanupPidFile(pidFile);
2297
+ }
2298
+ else {
2299
+ error.message += ` Process ${pid} is still running; PID file was preserved.`;
2300
+ }
2301
+ throw error;
2302
+ });
2303
+ }
1827
2304
  function startDaemon() {
1828
- const { logDir, logFile, pidFile } = resolveDaemonPaths();
1829
- // Prevent duplicate daemon starts. If a daemon is already running, refuse
1830
- // to overwrite its PID file.
1831
- const existing = readPidFile(pidFile);
1832
- if (existing && isProcessAlive(existing.pid)) {
1833
- logger_1.logger.error(`A CKB devnet daemon is already running (PID ${existing.pid}). Stop it first with: offckb node stop`);
1834
- return;
1835
- }
1836
- if (existing && !isProcessAlive(existing.pid)) {
1837
- // Stale PID file from a crashed daemon; clean it up before starting anew.
1838
- cleanupPidFile(pidFile);
1839
- }
1840
- let out;
1841
- let err;
1842
- try {
1843
- fs.mkdirSync(logDir, { recursive: true });
1844
- out = fs.openSync(logFile, 'a');
1845
- err = fs.openSync(logFile, 'a');
1846
- }
1847
- catch (error) {
1848
- logger_1.logger.error(`Failed to prepare daemon log directory or log file at ${logFile}:`, error);
1849
- return;
1850
- }
1851
- const scriptPath = resolveCliEntry();
1852
- if (!scriptPath) {
1853
- logger_1.logger.error('Unable to determine the CLI entry point for daemon mode. Set OFFCKB_CLI_PATH to the offckb script.');
1854
- closeFileDescriptors(out, err);
1855
- return;
1856
- }
1857
- const childArgs = process.argv.slice(2).filter((arg) => arg !== '--daemon');
1858
- const childEnv = Object.assign(Object.assign({}, process.env), { [DAEMON_CHILD_ENV]: '1' });
1859
- let child;
1860
- try {
1861
- child = (0, child_process_1.spawn)(process.execPath, [scriptPath, ...childArgs], {
1862
- detached: true,
1863
- stdio: ['ignore', out, err],
1864
- env: childEnv,
2305
+ return __awaiter(this, void 0, void 0, function* () {
2306
+ var _a;
2307
+ const { logDir, logFile, pidFile } = resolveDaemonPaths();
2308
+ try {
2309
+ fs.mkdirSync(logDir, { recursive: true });
2310
+ }
2311
+ catch (error) {
2312
+ throw new Error(`Failed to prepare daemon log directory at ${logDir}: ${error.message}`);
2313
+ }
2314
+ const settings = (0, setting_1.readSettings)();
2315
+ const activeNode = yield (0, readiness_1.checkNodeReadiness)(settings.devnet.rpcUrl, 1000);
2316
+ if (activeNode.ready) {
2317
+ throw new Error(`A CKB node is already answering at ${settings.devnet.rpcUrl}. Stop it before starting daemon mode.`);
2318
+ }
2319
+ // Prevent duplicate daemon starts. If a daemon is already running, refuse
2320
+ // to overwrite its PID file.
2321
+ const existing = readPidFile(pidFile);
2322
+ if (existing) {
2323
+ if (isProcessAlive(existing.pid)) {
2324
+ const identityOk = yield verifyDaemonIdentity(existing.pid, existing);
2325
+ if (identityOk) {
2326
+ if (existing.status === 'starting') {
2327
+ throw new Error(`Another CKB devnet daemon startup is already in progress (PID ${existing.pid}).`);
2328
+ }
2329
+ throw new Error(`A CKB devnet daemon is already running (PID ${existing.pid}). Stop it first with: offckb node stop`);
2330
+ }
2331
+ logger_1.logger.warn(`PID ${existing.pid} from ${pidFile} belongs to another process; removing stale daemon metadata without signaling it.`);
2332
+ }
2333
+ // Stale PID file from a crashed daemon; clean it up before atomically
2334
+ // reserving the same control file for this startup attempt.
2335
+ cleanupPidFile(pidFile);
2336
+ }
2337
+ const scriptPath = resolveCliEntry();
2338
+ if (!scriptPath) {
2339
+ throw new Error('Unable to determine the CLI entry point for daemon mode. Set OFFCKB_CLI_PATH to the offckb script.');
2340
+ }
2341
+ reservePidFile(pidFile, scriptPath);
2342
+ let out;
2343
+ let err;
2344
+ try {
2345
+ out = fs.openSync(logFile, 'a');
2346
+ err = fs.openSync(logFile, 'a');
2347
+ }
2348
+ catch (error) {
2349
+ closeFileDescriptors(out, err);
2350
+ cleanupPidFile(pidFile);
2351
+ throw new Error(`Failed to prepare daemon log directory or log file at ${logFile}: ${error.message}`);
2352
+ }
2353
+ const childArgs = process.argv.slice(2).filter((arg) => arg !== '--daemon');
2354
+ const childEnv = Object.assign(Object.assign({}, process.env), { [DAEMON_CHILD_ENV]: '1' });
2355
+ let child;
2356
+ try {
2357
+ child = (0, child_process_1.spawn)(process.execPath, [scriptPath, ...childArgs], {
2358
+ detached: true,
2359
+ stdio: ['ignore', out, err],
2360
+ env: childEnv,
2361
+ });
2362
+ }
2363
+ catch (error) {
2364
+ closeFileDescriptors(out, err);
2365
+ cleanupPidFile(pidFile);
2366
+ throw new Error(`Failed to spawn daemon process: ${error.message}`);
2367
+ }
2368
+ if (!child.pid) {
2369
+ closeFileDescriptors(out, err);
2370
+ cleanupPidFile(pidFile);
2371
+ throw new Error('Failed to spawn daemon process: no PID returned.');
2372
+ }
2373
+ child.unref();
2374
+ child.on('error', (error) => {
2375
+ logger_1.logger.error('Daemon child process failed to start:', error);
2376
+ cleanupPidFile(pidFile);
1865
2377
  });
1866
- }
1867
- catch (error) {
1868
- logger_1.logger.error('Failed to spawn daemon process:', error);
1869
- closeFileDescriptors(out, err);
1870
- return;
1871
- }
1872
- if (!child.pid) {
1873
- logger_1.logger.error('Failed to spawn daemon process: no PID returned.');
2378
+ const metadata = {
2379
+ pid: child.pid,
2380
+ scriptPath,
2381
+ startedAt: new Date().toISOString(),
2382
+ status: 'starting',
2383
+ };
2384
+ try {
2385
+ writePidFile(pidFile, metadata);
2386
+ }
2387
+ catch (error) {
2388
+ closeFileDescriptors(out, err);
2389
+ return failDaemonStartup(error, child.pid, pidFile);
2390
+ }
2391
+ // File descriptors are now owned by the spawned child; close our copies.
1874
2392
  closeFileDescriptors(out, err);
1875
- return;
1876
- }
1877
- child.unref();
1878
- child.on('error', (error) => {
1879
- logger_1.logger.error('Daemon child process failed to start:', error);
1880
- cleanupPidFile(pidFile);
2393
+ const proxyUrl = `http://127.0.0.1:${settings.devnet.rpcProxyPort}`;
2394
+ try {
2395
+ const forkState = (0, fork_1.readForkState)(settings.devnet.configPath);
2396
+ const timeoutMs = forkState ? FORK_NODE_READY_TIMEOUT_MS : NODE_READY_TIMEOUT_MS;
2397
+ // The proxy only starts after the child has a healthy CKB RPC and has
2398
+ // successfully spawned the miner, so this is the daemon's service-level
2399
+ // readiness check rather than a port/process check.
2400
+ const readiness = yield (0, readiness_1.waitForNodeReady)(proxyUrl, timeoutMs, () => isProcessAlive(child.pid));
2401
+ if (!readiness.ready) {
2402
+ throw new Error(`CKB devnet daemon failed to become ready. See ${logFile}. ${(_a = readiness.error) !== null && _a !== void 0 ? _a : 'Daemon process exited.'}`);
2403
+ }
2404
+ writePidFile(pidFile, Object.assign(Object.assign({}, metadata), { status: 'running' }));
2405
+ }
2406
+ catch (error) {
2407
+ return failDaemonStartup(error, child.pid, pidFile);
2408
+ }
2409
+ logger_1.logger.success(`CKB devnet daemon started with PID ${child.pid} and passed its RPC/proxy health check.`);
2410
+ logger_1.logger.info(`Logs: ${logFile}`);
2411
+ logger_1.logger.info(`PID file: ${pidFile}`);
2412
+ logger_1.logger.info('Stop the daemon with: offckb node stop');
2413
+ logger_1.logger.result({
2414
+ command: 'node',
2415
+ network: base_1.Network.devnet,
2416
+ daemon: true,
2417
+ pid: child.pid,
2418
+ rpcUrl: settings.devnet.rpcUrl,
2419
+ proxyUrl,
2420
+ logFile,
2421
+ pidFile,
2422
+ });
1881
2423
  });
1882
- const metadata = {
1883
- pid: child.pid,
1884
- scriptPath,
1885
- startedAt: new Date().toISOString(),
1886
- };
1887
- writePidFile(pidFile, metadata);
1888
- // File descriptors are now owned by the spawned child; close our copies.
1889
- closeFileDescriptors(out, err);
1890
- logger_1.logger.success(`CKB devnet daemon started with PID ${child.pid}.`);
1891
- logger_1.logger.info(`Logs: ${logFile}`);
1892
- logger_1.logger.info(`PID file: ${pidFile}`);
1893
- logger_1.logger.info('Stop the daemon with: offckb node stop');
1894
2424
  }
1895
2425
  function closeFileDescriptors(...fds) {
1896
2426
  for (const fd of fds) {
@@ -1910,24 +2440,28 @@ function stopNode() {
1910
2440
  const metadata = readPidFile(pidFile);
1911
2441
  if (!metadata) {
1912
2442
  logger_1.logger.warn(`No daemon PID file found at ${pidFile}. Is the devnet daemon running?`);
2443
+ logger_1.logger.result({ command: 'node.stop', stopped: false, reason: 'not-running' });
1913
2444
  return;
1914
2445
  }
1915
2446
  const pid = metadata.pid;
1916
2447
  if (!Number.isInteger(pid) || pid <= 0) {
1917
- logger_1.logger.error(`Invalid PID in ${pidFile}: ${pid}`);
1918
2448
  cleanupPidFile(pidFile);
1919
- return;
2449
+ throw new Error(`Invalid PID in ${pidFile}: ${pid}`);
1920
2450
  }
1921
- if (!isProcessAlive(pid)) {
2451
+ const processAlive = isProcessAlive(pid);
2452
+ if (!processAlive) {
1922
2453
  logger_1.logger.warn(`Daemon process ${pid} is not running.`);
1923
2454
  cleanupPidFile(pidFile);
2455
+ logger_1.logger.result({ command: 'node.stop', stopped: false, reason: 'stale-pid', pid });
1924
2456
  return;
1925
2457
  }
2458
+ if (metadata.status === 'starting') {
2459
+ throw new Error(`CKB devnet daemon startup is still in progress (PID ${pid}). Try stopping it again shortly.`);
2460
+ }
1926
2461
  const identityOk = yield verifyDaemonIdentity(pid, metadata);
1927
2462
  if (!identityOk) {
1928
- logger_1.logger.error(`Process ${pid} does not appear to be the offckb daemon. Refusing to send signals to avoid killing an unrelated process. ` +
2463
+ throw new Error(`Process ${pid} does not appear to be the offckb daemon. Refusing to send signals to avoid killing an unrelated process. ` +
1929
2464
  `If you are sure this is the daemon, stop it manually and remove ${pidFile}.`);
1930
- return;
1931
2465
  }
1932
2466
  logger_1.logger.info(`Stopping CKB devnet daemon (PID ${pid})...`);
1933
2467
  try {
@@ -1938,17 +2472,13 @@ function stopNode() {
1938
2472
  if (err.code === 'ESRCH') {
1939
2473
  logger_1.logger.warn(`Daemon process ${pid} is not running.`);
1940
2474
  cleanupPidFile(pidFile);
2475
+ logger_1.logger.result({ command: 'node.stop', stopped: false, reason: 'already-exited', pid });
1941
2476
  return;
1942
2477
  }
1943
2478
  if (err.code === 'EPERM') {
1944
- logger_1.logger.error(`Permission denied when sending SIGTERM to daemon process ${pid}.`);
2479
+ throw new Error(`Permission denied when sending SIGTERM to daemon process ${pid}.`);
1945
2480
  }
1946
- else {
1947
- logger_1.logger.error(`Failed to send SIGTERM to daemon process ${pid}:`, error);
1948
- }
1949
- // Still try to clean up the PID file so the user can recover.
1950
- cleanupPidFile(pidFile);
1951
- return;
2481
+ throw new Error(`Failed to send SIGTERM to daemon process ${pid}: ${err.message}`);
1952
2482
  }
1953
2483
  const exited = yield waitForProcessExit(pid, 5000);
1954
2484
  if (!exited) {
@@ -1957,11 +2487,12 @@ function stopNode() {
1957
2487
  yield terminateProcess(pid, 'SIGKILL');
1958
2488
  }
1959
2489
  catch (error) {
1960
- logger_1.logger.error(`Failed to send SIGKILL to daemon process ${pid}:`, error);
2490
+ throw new Error(`Failed to send SIGKILL to daemon process ${pid}: ${error.message}`);
1961
2491
  }
1962
2492
  }
1963
2493
  cleanupPidFile(pidFile);
1964
2494
  logger_1.logger.success('CKB devnet daemon stopped.');
2495
+ logger_1.logger.result({ command: 'node.stop', stopped: true, pid });
1965
2496
  });
1966
2497
  }
1967
2498
  function nodeTestnet() {
@@ -1995,39 +2526,6 @@ function nodeMainnet() {
1995
2526
 
1996
2527
  "use strict";
1997
2528
 
1998
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
1999
- if (k2 === undefined) k2 = k;
2000
- var desc = Object.getOwnPropertyDescriptor(m, k);
2001
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2002
- desc = { enumerable: true, get: function() { return m[k]; } };
2003
- }
2004
- Object.defineProperty(o, k2, desc);
2005
- }) : (function(o, m, k, k2) {
2006
- if (k2 === undefined) k2 = k;
2007
- o[k2] = m[k];
2008
- }));
2009
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
2010
- Object.defineProperty(o, "default", { enumerable: true, value: v });
2011
- }) : function(o, v) {
2012
- o["default"] = v;
2013
- });
2014
- var __importStar = (this && this.__importStar) || (function () {
2015
- var ownKeys = function(o) {
2016
- ownKeys = Object.getOwnPropertyNames || function (o) {
2017
- var ar = [];
2018
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
2019
- return ar;
2020
- };
2021
- return ownKeys(o);
2022
- };
2023
- return function (mod) {
2024
- if (mod && mod.__esModule) return mod;
2025
- var result = {};
2026
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
2027
- __setModuleDefault(result, mod);
2028
- return result;
2029
- };
2030
- })();
2031
2529
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2032
2530
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
2033
2531
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -2042,8 +2540,7 @@ exports.status = status;
2042
2540
  const setting_1 = __nccwpck_require__(25546);
2043
2541
  const ckb_tui_1 = __nccwpck_require__(7020);
2044
2542
  const base_1 = __nccwpck_require__(69951);
2045
- const logger_1 = __nccwpck_require__(65370);
2046
- const net = __importStar(__nccwpck_require__(69278));
2543
+ const readiness_1 = __nccwpck_require__(19326);
2047
2544
  const NETWORK_SETTINGS_KEY = {
2048
2545
  [base_1.Network.devnet]: 'devnet',
2049
2546
  [base_1.Network.testnet]: 'testnet',
@@ -2051,65 +2548,27 @@ const NETWORK_SETTINGS_KEY = {
2051
2548
  };
2052
2549
  function status(_a) {
2053
2550
  return __awaiter(this, arguments, void 0, function* ({ network }) {
2054
- var _b;
2551
+ var _b, _c;
2055
2552
  // ckb-tui is an interactive terminal UI. Running it without a TTY
2056
2553
  // (pipe, redirect, CI) would hang or produce garbage output.
2057
2554
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
2058
- logger_1.logger.error('The status command requires an interactive terminal (TTY). ' +
2059
- 'It cannot be used in pipes, redirects, or non-interactive environments like CI.');
2060
- process.exit(1);
2555
+ throw new Error('The status command requires an interactive terminal (TTY). It cannot be used in pipes, redirects, or CI.');
2061
2556
  }
2062
2557
  const settings = (0, setting_1.readSettings)();
2063
2558
  const networkKey = NETWORK_SETTINGS_KEY[network];
2064
2559
  const port = settings[networkKey].rpcProxyPort;
2065
2560
  const url = `http://127.0.0.1:${port}`;
2066
- const isListening = yield isRPCPortListening(port);
2067
- if (!isListening) {
2068
- logger_1.logger.error(`RPC port ${port} is not listening. Please make sure the ${network} node is running and Proxy RPC is enabled.`);
2069
- return;
2561
+ const readiness = yield (0, readiness_1.checkNodeReadiness)(url);
2562
+ if (!readiness.ready) {
2563
+ throw new Error(`RPC proxy ${url} is not connected to a healthy ${network} node: ${(_b = readiness.error) !== null && _b !== void 0 ? _b : 'health check failed'}`);
2070
2564
  }
2071
2565
  const result = ckb_tui_1.CKBTui.run(['-r', url]);
2072
2566
  // Propagate ckb-tui exit code so scripts can detect TUI failure
2073
2567
  if (result.status !== 0) {
2074
- process.exitCode = (_b = result.status) !== null && _b !== void 0 ? _b : 1;
2568
+ throw new Error(`ckb-tui exited with code ${(_c = result.status) !== null && _c !== void 0 ? _c : 'unknown'}`);
2075
2569
  }
2076
2570
  });
2077
2571
  }
2078
- function isRPCPortListening(port) {
2079
- return __awaiter(this, void 0, void 0, function* () {
2080
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
2081
- return false;
2082
- }
2083
- const client = new net.Socket();
2084
- return new Promise((resolve) => {
2085
- let settled = false;
2086
- const TIMEOUT_MS = 2000;
2087
- const timeout = setTimeout(() => {
2088
- if (!settled) {
2089
- settled = true;
2090
- client.destroy();
2091
- resolve(false);
2092
- }
2093
- }, TIMEOUT_MS);
2094
- client.once('error', () => {
2095
- if (!settled) {
2096
- settled = true;
2097
- clearTimeout(timeout);
2098
- resolve(false);
2099
- }
2100
- });
2101
- client.once('connect', () => {
2102
- if (!settled) {
2103
- settled = true;
2104
- clearTimeout(timeout);
2105
- client.end();
2106
- resolve(true);
2107
- }
2108
- });
2109
- client.connect(port, '127.0.0.1');
2110
- });
2111
- });
2112
- }
2113
2572
 
2114
2573
 
2115
2574
  /***/ }),
@@ -2134,7 +2593,6 @@ exports.printSystemScripts = printSystemScripts;
2134
2593
  exports.printInSystemStyle = printInSystemStyle;
2135
2594
  exports.printInLumosConfigStyle = printInLumosConfigStyle;
2136
2595
  exports.printInCCCStyle = printInCCCStyle;
2137
- exports.systemCellToScriptInfo = systemCellToScriptInfo;
2138
2596
  exports.toLumosConfig = toLumosConfig;
2139
2597
  const base_1 = __nccwpck_require__(69951);
2140
2598
  const public_1 = __nccwpck_require__(84535);
@@ -2148,26 +2606,41 @@ var PrintStyle;
2148
2606
  })(PrintStyle || (exports.PrintStyle = PrintStyle = {}));
2149
2607
  function printSystemScripts(_a) {
2150
2608
  return __awaiter(this, arguments, void 0, function* ({ style = PrintStyle.system, network = base_1.Network.devnet }) {
2151
- const systemScripts = network === base_1.Network.mainnet
2152
- ? public_1.MAINNET_SYSTEM_SCRIPTS
2153
- : network === base_1.Network.testnet
2154
- ? public_1.TESTNET_SYSTEM_SCRIPTS
2155
- : (0, private_1.getDevnetSystemScriptsFromListHashes)();
2609
+ var _b;
2610
+ let systemScripts;
2611
+ // Display label and address prefix follow the chain the devnet actually
2612
+ // runs: a fork carries the source chain's genesis, scripts and prefix.
2613
+ let label = network.toUpperCase();
2614
+ let addressPrefix = network === base_1.Network.mainnet ? 'ckb' : 'ckt';
2615
+ if (network === base_1.Network.mainnet) {
2616
+ systemScripts = public_1.MAINNET_SYSTEM_SCRIPTS;
2617
+ }
2618
+ else if (network === base_1.Network.testnet) {
2619
+ systemScripts = public_1.TESTNET_SYSTEM_SCRIPTS;
2620
+ }
2621
+ else {
2622
+ const resolved = (0, private_1.resolveDevnetSystemScripts)();
2623
+ systemScripts = (_b = resolved === null || resolved === void 0 ? void 0 : resolved.scripts) !== null && _b !== void 0 ? _b : null;
2624
+ if (resolved === null || resolved === void 0 ? void 0 : resolved.forkedFrom) {
2625
+ label = `DEVNET (fork of ${resolved.forkedFrom.toUpperCase()})`;
2626
+ addressPrefix = resolved.forkedFrom === 'mainnet' ? 'ckb' : 'ckt';
2627
+ }
2628
+ }
2156
2629
  if (!systemScripts)
2157
2630
  return logger_1.logger.info(`SystemScripts is null, ${network}`);
2158
2631
  if (style === PrintStyle.system) {
2159
- return printInSystemStyle(systemScripts, network);
2632
+ return printInSystemStyle(systemScripts, label);
2160
2633
  }
2161
2634
  if (style === PrintStyle.lumos) {
2162
- return printInLumosConfigStyle(systemScripts, network);
2635
+ return printInLumosConfigStyle(systemScripts, label, addressPrefix);
2163
2636
  }
2164
2637
  if (style === PrintStyle.ccc) {
2165
- return printInCCCStyle(systemScripts, network);
2638
+ return printInCCCStyle(systemScripts, label);
2166
2639
  }
2167
2640
  });
2168
2641
  }
2169
- function printInSystemStyle(systemScripts, network) {
2170
- logger_1.logger.info(`*** CKB ${network.toUpperCase()} System Scripts ***\n`);
2642
+ function printInSystemStyle(systemScripts, label) {
2643
+ logger_1.logger.info(`*** CKB ${label} System Scripts ***\n`);
2171
2644
  for (const [name, script] of Object.entries(systemScripts)) {
2172
2645
  logger_1.logger.info(`- name: ${name}`);
2173
2646
  if (script == null) {
@@ -2180,67 +2653,16 @@ function printInSystemStyle(systemScripts, network) {
2180
2653
  logger_1.logger.info(` cellDeps: ${JSON.stringify(script.script.cellDeps, null, 2)}\n`);
2181
2654
  }
2182
2655
  }
2183
- function printInLumosConfigStyle(scripts, network) {
2184
- const config = toLumosConfig(scripts, network === base_1.Network.mainnet ? 'ckb' : 'ckt');
2185
- logger_1.logger.info(`*** CKB ${network.toUpperCase()} System Scripts As LumosConfig ***\n`);
2656
+ function printInLumosConfigStyle(scripts, label, addressPrefix) {
2657
+ const config = toLumosConfig(scripts, addressPrefix);
2658
+ logger_1.logger.info(`*** CKB ${label} System Scripts As LumosConfig ***\n`);
2186
2659
  logger_1.logger.info(JSON.stringify(config, null, 2));
2187
2660
  }
2188
- function printInCCCStyle(scripts, network) {
2661
+ function printInCCCStyle(scripts, label) {
2189
2662
  const knownsScripts = (0, private_1.toCCCKnownScripts)(scripts);
2190
- logger_1.logger.info(`*** CKB ${network.toUpperCase()} System Scripts As CCC KnownScripts ***\n`);
2663
+ logger_1.logger.info(`*** CKB ${label} System Scripts As CCC KnownScripts ***\n`);
2191
2664
  logger_1.logger.info(JSON.stringify(knownsScripts, null, 2));
2192
2665
  }
2193
- function systemCellToScriptInfo({ cell, depType, depGroup, extraCellDeps, }) {
2194
- // todo: we left the type in cellDepsInfo since it requires async fetching and
2195
- // chain running to get the full type script of the type-id deps.
2196
- // Also, in devnet there is no real need to auto upgrade the system scripts with type-id
2197
- if (depType === 'code') {
2198
- let cellDeps = [
2199
- {
2200
- cellDep: {
2201
- outPoint: {
2202
- txHash: cell.tx_hash,
2203
- index: cell.index,
2204
- },
2205
- depType,
2206
- },
2207
- },
2208
- ];
2209
- if (extraCellDeps && extraCellDeps.length > 0) {
2210
- cellDeps = [...extraCellDeps, ...cellDeps];
2211
- }
2212
- return {
2213
- codeHash: (cell.type_hash || cell.data_hash),
2214
- hashType: cell.type_hash ? 'type' : 'data2',
2215
- cellDeps,
2216
- };
2217
- }
2218
- if (depType === 'depGroup') {
2219
- if (!depGroup) {
2220
- throw new Error('require depGroup info since the dep type is depGroup');
2221
- }
2222
- let cellDeps = [
2223
- {
2224
- cellDep: {
2225
- outPoint: {
2226
- txHash: depGroup.txHash,
2227
- index: depGroup.index,
2228
- },
2229
- depType,
2230
- },
2231
- },
2232
- ];
2233
- if (extraCellDeps && extraCellDeps.length > 0) {
2234
- cellDeps = [...extraCellDeps, ...cellDeps];
2235
- }
2236
- return {
2237
- codeHash: (cell.type_hash || cell.data_hash),
2238
- hashType: cell.type_hash ? 'type' : 'data2',
2239
- cellDeps,
2240
- };
2241
- }
2242
- throw new Error(`unknown DepType ${depType}`);
2243
- }
2244
2666
  function toLumosConfig(scripts, addressPrefix = 'ckt') {
2245
2667
  const config = {
2246
2668
  PREFIX: addressPrefix,
@@ -2379,14 +2801,16 @@ const base_1 = __nccwpck_require__(69951);
2379
2801
  const link_1 = __nccwpck_require__(27814);
2380
2802
  const validator_1 = __nccwpck_require__(39534);
2381
2803
  const logger_1 = __nccwpck_require__(65370);
2804
+ const private_key_1 = __nccwpck_require__(26241);
2805
+ const readiness_1 = __nccwpck_require__(19326);
2806
+ const fork_safety_1 = __nccwpck_require__(47625);
2382
2807
  function transferAll(toAddress_1) {
2383
2808
  return __awaiter(this, arguments, void 0, function* (toAddress, opt = { network: base_1.Network.devnet }) {
2384
2809
  const network = opt.network;
2385
2810
  (0, validator_1.validateNetworkOpt)(network);
2386
- if (opt.privkey == null) {
2387
- throw new Error('--privkey is required!');
2388
- }
2389
- const privateKey = opt.privkey;
2811
+ const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
2812
+ (0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
2813
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
2390
2814
  const ckb = new ckb_1.CKB({ network });
2391
2815
  const txHash = yield ckb.transferAll({
2392
2816
  toAddress,
@@ -2394,9 +2818,12 @@ function transferAll(toAddress_1) {
2394
2818
  });
2395
2819
  if (network === 'testnet') {
2396
2820
  logger_1.logger.info(`Successfully transfer, check ${(0, link_1.buildTestnetTxLink)(txHash)} for details.`);
2397
- return;
2821
+ logger_1.logger.result({ command: 'transfer-all', network, toAddress, txHash });
2822
+ return txHash;
2398
2823
  }
2399
2824
  logger_1.logger.info('Successfully transfer, txHash:', txHash);
2825
+ logger_1.logger.result({ command: 'transfer-all', network, toAddress, txHash });
2826
+ return txHash;
2400
2827
  });
2401
2828
  }
2402
2829
 
@@ -2423,37 +2850,61 @@ const ckb_1 = __nccwpck_require__(83748);
2423
2850
  const base_1 = __nccwpck_require__(69951);
2424
2851
  const link_1 = __nccwpck_require__(27814);
2425
2852
  const validator_1 = __nccwpck_require__(39534);
2853
+ const private_key_1 = __nccwpck_require__(26241);
2854
+ const logger_1 = __nccwpck_require__(65370);
2855
+ const readiness_1 = __nccwpck_require__(19326);
2856
+ const fork_safety_1 = __nccwpck_require__(47625);
2426
2857
  function transfer(toAddress_1, amount_1) {
2427
2858
  return __awaiter(this, arguments, void 0, function* (toAddress, amount, opt = { network: base_1.Network.devnet }) {
2428
2859
  var _a;
2429
2860
  const network = opt.network;
2430
2861
  (0, validator_1.validateNetworkOpt)(network);
2431
- if (opt.privkey == null) {
2432
- throw new Error('--privkey is required!');
2433
- }
2434
- const privateKey = opt.privkey;
2862
+ let udtKind;
2863
+ let udtTypeArgs;
2864
+ if (opt.udtKind != null || opt.udtTypeArgs != null) {
2865
+ if (!opt.udtTypeArgs) {
2866
+ throw new Error('UDT type args are required for a UDT transfer');
2867
+ }
2868
+ (0, validator_1.validateUdtAmount)(amount);
2869
+ udtKind = (_a = opt.udtKind) !== null && _a !== void 0 ? _a : 'sudt';
2870
+ (0, validator_1.validateUdtKind)(udtKind);
2871
+ udtTypeArgs = (0, validator_1.validateUdtTypeArgs)(udtKind, opt.udtTypeArgs);
2872
+ }
2873
+ const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
2874
+ const rejectInputsAtOrBeforeBlock = (0, fork_safety_1.validateMainnetForkSigning)(network, privateKey, opt.allowMainnetReplayRisk);
2875
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
2435
2876
  const ckb = new ckb_1.CKB({ network });
2436
- if (opt.udtTypeArgs) {
2437
- const kind = (_a = opt.udtKind) !== null && _a !== void 0 ? _a : 'sudt';
2438
- (0, validator_1.validateUdtKind)(kind);
2439
- const udtTypeArgs = (0, validator_1.validateUdtTypeArgs)(kind, opt.udtTypeArgs);
2440
- const udtType = yield ckb.buildUdtTypeScript(kind, udtTypeArgs);
2877
+ if (udtKind && udtTypeArgs) {
2878
+ const udtType = yield ckb.buildUdtTypeScript(udtKind, udtTypeArgs);
2441
2879
  const txHash = yield ckb.udtTransfer({
2442
2880
  toAddress,
2443
2881
  amount,
2444
2882
  privateKey,
2445
2883
  udtType,
2446
- kind,
2884
+ kind: udtKind,
2885
+ rejectInputsAtOrBeforeBlock,
2447
2886
  });
2448
2887
  (0, link_1.logTxSuccess)(network, txHash, 'transfer UDT');
2449
- return;
2888
+ logger_1.logger.result({
2889
+ command: 'udt.transfer',
2890
+ network,
2891
+ kind: udtKind,
2892
+ amount,
2893
+ typeArgs: udtTypeArgs,
2894
+ toAddress,
2895
+ txHash,
2896
+ });
2897
+ return txHash;
2450
2898
  }
2451
2899
  const txHash = yield ckb.transfer({
2452
2900
  toAddress,
2453
2901
  amountInCKB: amount,
2454
2902
  privateKey,
2903
+ rejectInputsAtOrBeforeBlock,
2455
2904
  });
2456
2905
  (0, link_1.logTxSuccess)(network, txHash, 'transfer');
2906
+ logger_1.logger.result({ command: 'transfer', network, amount, toAddress, txHash });
2907
+ return txHash;
2457
2908
  });
2458
2909
  }
2459
2910
 
@@ -2481,41 +2932,65 @@ const ckb_1 = __nccwpck_require__(83748);
2481
2932
  const base_1 = __nccwpck_require__(69951);
2482
2933
  const link_1 = __nccwpck_require__(27814);
2483
2934
  const validator_1 = __nccwpck_require__(39534);
2935
+ const private_key_1 = __nccwpck_require__(26241);
2936
+ const logger_1 = __nccwpck_require__(65370);
2937
+ const readiness_1 = __nccwpck_require__(19326);
2938
+ const fork_safety_1 = __nccwpck_require__(47625);
2484
2939
  function udtIssue(amount_1) {
2485
- return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt', privkey: '' }) {
2940
+ return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt' }) {
2486
2941
  const network = opt.network;
2487
2942
  (0, validator_1.validateNetworkOpt)(network);
2488
2943
  (0, validator_1.validateUdtKind)(opt.udtKind);
2489
- if (!opt.privkey) {
2490
- throw new Error('--privkey is required!');
2491
- }
2944
+ (0, validator_1.validateUdtAmount)(amount);
2945
+ const typeArgs = opt.typeArgs ? (0, validator_1.validateUdtTypeArgs)(opt.udtKind, opt.typeArgs) : undefined;
2946
+ const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
2947
+ (0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
2948
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
2492
2949
  const ckb = new ckb_1.CKB({ network });
2493
- const txHash = yield ckb.udtIssue({
2494
- privateKey: opt.privkey,
2950
+ const result = yield ckb.udtIssue({
2951
+ privateKey,
2495
2952
  kind: opt.udtKind,
2496
2953
  amount,
2497
- typeArgs: opt.typeArgs ? (0, validator_1.validateUdtTypeArgs)(opt.udtKind, opt.typeArgs) : undefined,
2954
+ typeArgs,
2498
2955
  toAddress: opt.to,
2499
2956
  });
2500
- (0, link_1.logTxSuccess)(network, txHash, 'issued UDT');
2957
+ (0, link_1.logTxSuccess)(network, result.txHash, 'issued UDT');
2958
+ logger_1.logger.info(`UDT kind: ${opt.udtKind}`);
2959
+ logger_1.logger.info(`UDT type args: ${result.typeArgs}`);
2960
+ logger_1.logger.info(`Receiver: ${result.receiver}`);
2961
+ logger_1.logger.info(`Next: offckb balance ${result.receiver} --udt-kind ${opt.udtKind} --udt-type-args ${result.typeArgs}`);
2962
+ logger_1.logger.result({
2963
+ command: 'udt.issue',
2964
+ network,
2965
+ kind: opt.udtKind,
2966
+ amount,
2967
+ receiver: result.receiver,
2968
+ typeArgs: result.typeArgs,
2969
+ txHash: result.txHash,
2970
+ });
2971
+ return result;
2501
2972
  });
2502
2973
  }
2503
2974
  function udtDestroy(amount_1) {
2504
- return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt', typeArgs: '', privkey: '' }) {
2975
+ return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt', typeArgs: '' }) {
2505
2976
  const network = opt.network;
2506
2977
  (0, validator_1.validateNetworkOpt)(network);
2507
2978
  (0, validator_1.validateUdtKind)(opt.udtKind);
2508
- if (!opt.privkey) {
2509
- throw new Error('--privkey is required!');
2510
- }
2979
+ (0, validator_1.validateUdtAmount)(amount);
2980
+ const typeArgs = (0, validator_1.validateUdtTypeArgs)(opt.udtKind, opt.typeArgs);
2981
+ const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
2982
+ (0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
2983
+ yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
2511
2984
  const ckb = new ckb_1.CKB({ network });
2512
2985
  const txHash = yield ckb.udtDestroy({
2513
- privateKey: opt.privkey,
2986
+ privateKey,
2514
2987
  kind: opt.udtKind,
2515
2988
  amount,
2516
- typeArgs: (0, validator_1.validateUdtTypeArgs)(opt.udtKind, opt.typeArgs),
2989
+ typeArgs,
2517
2990
  });
2518
2991
  (0, link_1.logTxSuccess)(network, txHash, 'destroyed UDT');
2992
+ logger_1.logger.result({ command: 'udt.destroy', network, kind: opt.udtKind, amount, typeArgs, txHash });
2993
+ return txHash;
2519
2994
  });
2520
2995
  }
2521
2996
 
@@ -3487,6 +3962,640 @@ class InitializationError extends Error {
3487
3962
  exports.InitializationError = InitializationError;
3488
3963
 
3489
3964
 
3965
+ /***/ }),
3966
+
3967
+ /***/ 77912:
3968
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
3969
+
3970
+ "use strict";
3971
+
3972
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3973
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3974
+ return new (P || (P = Promise))(function (resolve, reject) {
3975
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
3976
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
3977
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
3978
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
3979
+ });
3980
+ };
3981
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3982
+ return (mod && mod.__esModule) ? mod : { "default": mod };
3983
+ };
3984
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3985
+ exports.FORK_STATE_FILE = void 0;
3986
+ exports.getForkStatePath = getForkStatePath;
3987
+ exports.readForkState = readForkState;
3988
+ exports.writeForkState = writeForkState;
3989
+ exports.markForkFirstRunComplete = markForkFirstRunComplete;
3990
+ exports.detectSourceFromCkbToml = detectSourceFromCkbToml;
3991
+ exports.parseGenesisHashFromInitOutput = parseGenesisHashFromInitOutput;
3992
+ exports.parseGenesisHashFromListHashes = parseGenesisHashFromListHashes;
3993
+ exports.expectedGenesisHash = expectedGenesisHash;
3994
+ exports.patchDevSpecForFork = patchDevSpecForFork;
3995
+ exports.copySourceData = copySourceData;
3996
+ exports.isolateForkCkbConfig = isolateForkCkbConfig;
3997
+ exports.migrationNeededFromExitCode = migrationNeededFromExitCode;
3998
+ exports.forkDevnet = forkDevnet;
3999
+ const child_process_1 = __nccwpck_require__(35317);
4000
+ const fs_1 = __importDefault(__nccwpck_require__(79896));
4001
+ const os_1 = __importDefault(__nccwpck_require__(70857));
4002
+ const path_1 = __importDefault(__nccwpck_require__(16928));
4003
+ const toml_1 = __importDefault(__nccwpck_require__(2811));
4004
+ const setting_1 = __nccwpck_require__(25546);
4005
+ const install_1 = __nccwpck_require__(65611);
4006
+ const fs_2 = __nccwpck_require__(37293);
4007
+ const request_1 = __nccwpck_require__(65897);
4008
+ const logger_1 = __nccwpck_require__(65370);
4009
+ const const_1 = __nccwpck_require__(75587);
4010
+ exports.FORK_STATE_FILE = 'fork.json';
4011
+ // The official guide requires genesis_epoch_length to stay at 1743 for a
4012
+ // mainnet fork, and to fall back to the default 1000 for a testnet fork
4013
+ // (i.e. the key must be absent). Getting this wrong produces a different
4014
+ // genesis hash and the node refuses to start. See nervosnetwork/ckb#5205.
4015
+ const MAINNET_GENESIS_EPOCH_LENGTH = 1743;
4016
+ function getForkStatePath(configPath) {
4017
+ return path_1.default.join(configPath, exports.FORK_STATE_FILE);
4018
+ }
4019
+ function readForkState(configPath) {
4020
+ const statePath = getForkStatePath(configPath);
4021
+ let raw;
4022
+ try {
4023
+ raw = fs_1.default.readFileSync(statePath, 'utf8');
4024
+ }
4025
+ catch (_a) {
4026
+ return null;
4027
+ }
4028
+ try {
4029
+ const parsed = JSON.parse(raw);
4030
+ if (typeof parsed.source !== 'string' || typeof parsed.firstRunPending !== 'boolean') {
4031
+ return null;
4032
+ }
4033
+ return parsed;
4034
+ }
4035
+ catch (_b) {
4036
+ return null;
4037
+ }
4038
+ }
4039
+ function writeForkState(configPath, state) {
4040
+ const statePath = getForkStatePath(configPath);
4041
+ const tempPath = `${statePath}.tmp`;
4042
+ fs_1.default.writeFileSync(tempPath, JSON.stringify(state, null, 2));
4043
+ fs_1.default.renameSync(tempPath, statePath);
4044
+ }
4045
+ function markForkFirstRunComplete(configPath, forkBlockNumber) {
4046
+ const state = readForkState(configPath);
4047
+ if (!state || !state.firstRunPending)
4048
+ return;
4049
+ writeForkState(configPath, Object.assign(Object.assign(Object.assign({}, state), { firstRunPending: false }), (forkBlockNumber == null ? {} : { forkBlockNumber })));
4050
+ }
4051
+ function detectSourceFromCkbToml(ckbTomlContent) {
4052
+ let parsed;
4053
+ try {
4054
+ parsed = toml_1.default.parse(ckbTomlContent);
4055
+ }
4056
+ catch (_a) {
4057
+ return null;
4058
+ }
4059
+ const chain = parsed.chain;
4060
+ if (chain == null || typeof chain !== 'object')
4061
+ return null;
4062
+ const spec = chain.spec;
4063
+ if (spec == null || typeof spec !== 'object')
4064
+ return null;
4065
+ const bundled = spec.bundled;
4066
+ if (typeof bundled !== 'string')
4067
+ return null;
4068
+ if (bundled.includes('mainnet'))
4069
+ return 'mainnet';
4070
+ if (bundled.includes('testnet'))
4071
+ return 'testnet';
4072
+ return null;
4073
+ }
4074
+ function parseGenesisHashFromInitOutput(output) {
4075
+ const match = output.match(/Genesis Hash:\s*(0x[0-9a-fA-F]{64})/);
4076
+ return match ? match[1].toLowerCase() : null;
4077
+ }
4078
+ // `ckb list-hashes` prints TOML with a single table whose `genesis` field is
4079
+ // the chain's genesis hash.
4080
+ function parseGenesisHashFromListHashes(output) {
4081
+ const match = output.match(/^\s*genesis\s*=\s*"(0x[0-9a-fA-F]{64})"\s*$/m);
4082
+ return match ? match[1].toLowerCase() : null;
4083
+ }
4084
+ function expectedGenesisHash(source) {
4085
+ return source === 'mainnet' ? const_1.MAINNET_GENESIS_HASH : const_1.TESTNET_GENESIS_HASH;
4086
+ }
4087
+ // Patch the imported chain spec into a minable dev chain, following
4088
+ // https://docs.nervos.org/docs/node/devnet-from-existing-data
4089
+ function patchDevSpecForFork(spec, source) {
4090
+ var _a, _b;
4091
+ const pow = Object.assign(Object.assign({}, ((_a = spec.pow) !== null && _a !== void 0 ? _a : {})), { func: 'Dummy' });
4092
+ const params = Object.assign({}, ((_b = spec.params) !== null && _b !== void 0 ? _b : {}));
4093
+ params.cellbase_maturity = 0;
4094
+ params.permanent_difficulty_in_dummy = true;
4095
+ if (source === 'mainnet') {
4096
+ params.genesis_epoch_length = MAINNET_GENESIS_EPOCH_LENGTH;
4097
+ }
4098
+ else if (source === 'testnet') {
4099
+ // testnet derives genesis_epoch_length from the default (1000); a leftover
4100
+ // mainnet value here is exactly the nervosnetwork/ckb#5205 trap.
4101
+ delete params.genesis_epoch_length;
4102
+ }
4103
+ // custom specs keep whatever genesis_epoch_length they declared
4104
+ return Object.assign(Object.assign({}, spec), { pow, params });
4105
+ }
4106
+ function validateSourceDir(sourceDir) {
4107
+ if (!(0, fs_2.isFolderExists)(sourceDir)) {
4108
+ throw new Error(`Source directory does not exist: ${sourceDir}`);
4109
+ }
4110
+ if (!(0, fs_2.isFolderExists)(path_1.default.join(sourceDir, 'data', 'db'))) {
4111
+ throw new Error(`${sourceDir} does not look like a CKB node directory (missing data/db). ` +
4112
+ `Point --from at the directory the source node runs with (-C).`);
4113
+ }
4114
+ }
4115
+ // Best-effort detection of a running ckb process using the given directory.
4116
+ // Returns null when the check cannot be performed (Windows, no ps).
4117
+ function isCkbNodeRunningOn(dir) {
4118
+ if (process.platform === 'win32')
4119
+ return null;
4120
+ let processes = '';
4121
+ try {
4122
+ processes = (0, child_process_1.execSync)('ps -eo args', { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
4123
+ }
4124
+ catch (_a) {
4125
+ return null;
4126
+ }
4127
+ return processes.split('\n').some((line) => {
4128
+ var _a;
4129
+ const tokens = line.trim().split(/\s+/);
4130
+ // The program itself must be a ckb binary — a looser match would flag
4131
+ // unrelated processes that merely mention the directory.
4132
+ const program = path_1.default.basename((_a = tokens[0]) !== null && _a !== void 0 ? _a : '');
4133
+ if (program !== 'ckb' && program !== 'ckb.exe')
4134
+ return false;
4135
+ return tokens.includes('run') && line.includes(dir);
4136
+ });
4137
+ }
4138
+ function assertSourceNodeStopped(sourceDir) {
4139
+ const running = isCkbNodeRunningOn(sourceDir);
4140
+ if (running === null) {
4141
+ logger_1.logger.warn('Make sure the source CKB node is stopped; copying a live database produces corrupted data.');
4142
+ return;
4143
+ }
4144
+ if (running) {
4145
+ throw new Error(`A CKB node appears to be running on ${sourceDir}. Stop it first; ` +
4146
+ `copying a live database produces corrupted data.`);
4147
+ }
4148
+ }
4149
+ function assertOffckbDevnetStopped() {
4150
+ const settings = (0, setting_1.readSettings)();
4151
+ // Foreground `offckb node` (no daemon pid file): look for the ckb process.
4152
+ if (isCkbNodeRunningOn(settings.devnet.configPath) === true) {
4153
+ throw new Error('An offckb devnet node appears to be running. Stop it before forking.');
4154
+ }
4155
+ // Daemon mode: check the pid file.
4156
+ const pidFile = path_1.default.join(settings.devnet.dataPath, 'logs', 'daemon.pid');
4157
+ let raw;
4158
+ try {
4159
+ raw = fs_1.default.readFileSync(pidFile, 'utf8').trim();
4160
+ }
4161
+ catch (_a) {
4162
+ return;
4163
+ }
4164
+ let pid = Number(raw);
4165
+ if (!Number.isInteger(pid) || pid <= 0) {
4166
+ try {
4167
+ pid = Number(JSON.parse(raw).pid);
4168
+ }
4169
+ catch (_b) {
4170
+ return;
4171
+ }
4172
+ }
4173
+ if (!Number.isInteger(pid) || pid <= 0)
4174
+ return;
4175
+ let alive = true;
4176
+ try {
4177
+ process.kill(pid, 0);
4178
+ }
4179
+ catch (_c) {
4180
+ alive = false; // ESRCH: stale pid file, no daemon running
4181
+ }
4182
+ if (alive) {
4183
+ throw new Error(`The offckb devnet daemon is running (PID ${pid}). Stop it first with: offckb node stop`);
4184
+ }
4185
+ }
4186
+ function resolveSpecFile(options, source, ckbVersion) {
4187
+ return __awaiter(this, void 0, void 0, function* () {
4188
+ if (options.specFile) {
4189
+ const specFile = path_1.default.resolve(options.specFile);
4190
+ if (!fs_1.default.existsSync(specFile)) {
4191
+ throw new Error(`Spec file not found: ${specFile}`);
4192
+ }
4193
+ return specFile;
4194
+ }
4195
+ const cacheDir = path_1.default.join(setting_1.cachePath, 'specs', ckbVersion);
4196
+ const cachedSpec = path_1.default.join(cacheDir, `${source}.toml`);
4197
+ if (fs_1.default.existsSync(cachedSpec)) {
4198
+ logger_1.logger.debug(`Using cached ${source} spec: ${cachedSpec}`);
4199
+ return cachedSpec;
4200
+ }
4201
+ const url = `https://raw.githubusercontent.com/nervosnetwork/ckb/v${ckbVersion}/resource/specs/${source}.toml`;
4202
+ logger_1.logger.info(`Downloading ${source} chain spec from ${url} ..`);
4203
+ try {
4204
+ const response = yield request_1.Request.send(url);
4205
+ const content = yield response.text();
4206
+ fs_1.default.mkdirSync(cacheDir, { recursive: true });
4207
+ fs_1.default.writeFileSync(cachedSpec, content);
4208
+ return cachedSpec;
4209
+ }
4210
+ catch (error) {
4211
+ throw new Error(`Failed to download the ${source} chain spec for CKB v${ckbVersion}: ${error.message}. ` +
4212
+ `Pass a local copy with --spec-file.`);
4213
+ }
4214
+ });
4215
+ }
4216
+ function copySourceData(sourceDir, configPath) {
4217
+ const sourceData = path_1.default.join(sourceDir, 'data');
4218
+ const targetData = path_1.default.join(configPath, 'data');
4219
+ logger_1.logger.info(`Copying chain data from ${sourceData} to ${targetData} ..`);
4220
+ logger_1.logger.info('This can take a while for large chains.');
4221
+ fs_1.default.mkdirSync(configPath, { recursive: true });
4222
+ // Full copy on purpose: never hardlink — RocksDB appends to WAL/MANIFEST in
4223
+ // place, and linked files would corrupt the source chain.
4224
+ const excludedTopLevelEntries = new Set(['network', 'logs', 'tmp']);
4225
+ fs_1.default.mkdirSync(targetData, { recursive: true });
4226
+ // Enumerate top-level entries instead of relying on fs.cp's filter paths,
4227
+ // which may use Windows extended-length prefixes and bypass relative-path
4228
+ // comparisons.
4229
+ for (const entry of fs_1.default.readdirSync(sourceData)) {
4230
+ if (excludedTopLevelEntries.has(entry))
4231
+ continue;
4232
+ fs_1.default.cpSync(path_1.default.join(sourceData, entry), path_1.default.join(targetData, entry), { recursive: true });
4233
+ }
4234
+ logger_1.logger.info('Excluded source network peers and transient logs/tmp data from the fork.');
4235
+ }
4236
+ function isolateForkCkbConfig(config) {
4237
+ var _a, _b;
4238
+ const network = Object.assign({}, ((_a = config.network) !== null && _a !== void 0 ? _a : {}));
4239
+ network.bootnodes = [];
4240
+ network.max_outbound_peers = 0;
4241
+ network.whitelist_only = true;
4242
+ network.discovery_local_address = false;
4243
+ const loggerConfig = Object.assign({}, ((_b = config.logger) !== null && _b !== void 0 ? _b : {}));
4244
+ loggerConfig.filter = 'warn';
4245
+ return Object.assign(Object.assign({}, config), { network, logger: loggerConfig });
4246
+ }
4247
+ function runCkbInit(ckbBinPath, configPath, specFile) {
4248
+ // argv form, never a shell: --spec-file is user input and quote-wrapping
4249
+ // alone does not stop shell expansion (command substitution still runs
4250
+ // inside double quotes).
4251
+ const args = ['init', '-C', configPath, '--chain', 'dev', '--import-spec', specFile, '--force'];
4252
+ logger_1.logger.debug(`Running: ${ckbBinPath} ${args.join(' ')}`);
4253
+ return (0, child_process_1.execFileSync)(ckbBinPath, args, {
4254
+ encoding: 'utf8',
4255
+ maxBuffer: 16 * 1024 * 1024,
4256
+ stdio: ['ignore', 'pipe', 'pipe'],
4257
+ });
4258
+ }
4259
+ function migrationNeededFromExitCode(status) {
4260
+ if (status === 0)
4261
+ return true;
4262
+ if (status === 64)
4263
+ return false;
4264
+ throw new Error(`ckb migrate --check failed with exit code ${status !== null && status !== void 0 ? status : 'unknown'}`);
4265
+ }
4266
+ function isDatabaseMigrationNeeded(ckbBinPath, configPath) {
4267
+ const result = (0, child_process_1.spawnSync)(ckbBinPath, ['migrate', '--check', '-C', configPath], {
4268
+ encoding: 'utf8',
4269
+ stdio: ['ignore', 'pipe', 'pipe'],
4270
+ });
4271
+ if (result.error) {
4272
+ throw new Error(`Could not check the CKB database version: ${result.error.message}`);
4273
+ }
4274
+ try {
4275
+ return migrationNeededFromExitCode(result.status);
4276
+ }
4277
+ catch (error) {
4278
+ const details = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
4279
+ throw new Error(`${error.message}${details ? `: ${details}` : ''}`);
4280
+ }
4281
+ }
4282
+ function migrateDatabaseCopy(ckbBinPath, configPath) {
4283
+ var _a, _b;
4284
+ logger_1.logger.info('Migrating the copied database (the source directory remains untouched) ..');
4285
+ const result = (0, child_process_1.spawnSync)(ckbBinPath, ['migrate', '--force', '-C', configPath], {
4286
+ encoding: 'utf8',
4287
+ stdio: ['ignore', 'pipe', 'pipe'],
4288
+ });
4289
+ if (result.error || result.status !== 0) {
4290
+ const details = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
4291
+ throw new Error(`Failed to migrate the copied database: ${(_b = (_a = result.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : `exit code ${result.status}`}` +
4292
+ `${details ? `\n${details}` : ''}`);
4293
+ }
4294
+ logger_1.logger.success('Copied database migration completed.');
4295
+ }
4296
+ // Best-effort read of the source directory's own chain identity. `ckb
4297
+ // list-hashes` resolves the chain spec of the given config dir (no database
4298
+ // access needed), which tells us which chain the source node was configured
4299
+ // for. Returns null when the source is not a standard config dir.
4300
+ function readSourceGenesisHash(ckbBinPath, sourceDir) {
4301
+ try {
4302
+ const output = (0, child_process_1.execFileSync)(ckbBinPath, ['list-hashes', '-C', sourceDir], {
4303
+ encoding: 'utf8',
4304
+ maxBuffer: 16 * 1024 * 1024,
4305
+ stdio: ['ignore', 'pipe', 'ignore'],
4306
+ });
4307
+ return parseGenesisHashFromListHashes(output);
4308
+ }
4309
+ catch (_a) {
4310
+ return null;
4311
+ }
4312
+ }
4313
+ function validateForkSpec(ckbBinPath, sourceDir, specFile, declaredSource) {
4314
+ var _a;
4315
+ const tempConfigPath = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'offckb-fork-preflight-'));
4316
+ try {
4317
+ const initOutput = runCkbInit(ckbBinPath, tempConfigPath, specFile);
4318
+ const genesisHash = parseGenesisHashFromInitOutput(initOutput);
4319
+ if (!genesisHash) {
4320
+ throw new Error(`Could not parse the genesis hash from ckb init output:\n${initOutput}`);
4321
+ }
4322
+ const source = (_a = declaredSource !== null && declaredSource !== void 0 ? declaredSource : (0, const_1.identifyPublicChainByGenesisHash)(genesisHash)) !== null && _a !== void 0 ? _a : 'custom';
4323
+ if (source !== 'custom') {
4324
+ const expected = expectedGenesisHash(source);
4325
+ if (genesisHash !== expected) {
4326
+ throw new Error(`Genesis hash mismatch: expected ${expected} for ${source}, got ${genesisHash}. ` +
4327
+ 'This usually means the chain spec does not match the source data.');
4328
+ }
4329
+ }
4330
+ const sourceGenesisHash = readSourceGenesisHash(ckbBinPath, sourceDir);
4331
+ if (sourceGenesisHash && sourceGenesisHash !== genesisHash) {
4332
+ throw new Error(`The source directory is configured for a different chain (genesis ${sourceGenesisHash}) ` +
4333
+ `than the imported spec (genesis ${genesisHash}). Pass a matching --source or --spec-file.`);
4334
+ }
4335
+ return { source, genesisHash };
4336
+ }
4337
+ finally {
4338
+ fs_1.default.rmSync(tempConfigPath, { recursive: true, force: true });
4339
+ }
4340
+ }
4341
+ // Overwrite the ckb-init-generated configs with offckb's own devnet configs so
4342
+ // the fork behaves like a normal offckb devnet (miner account as block
4343
+ // assembler, RPC on 8114 with the Indexer module, proxy on 28114). The
4344
+ // imported specs/dev.toml is kept.
4345
+ function alignConfigsWithOffckb(configPath) {
4346
+ const settings = (0, setting_1.readSettings)();
4347
+ const devnetSourcePath = path_1.default.resolve(setting_1.packageRootPath, './ckb/devnet');
4348
+ const ckbConfigPath = path_1.default.join(configPath, 'ckb.toml');
4349
+ const parsedCkbConfig = toml_1.default.parse(fs_1.default.readFileSync(path_1.default.join(devnetSourcePath, 'ckb.toml'), 'utf8'));
4350
+ const ckbConfig = isolateForkCkbConfig(parsedCkbConfig);
4351
+ fs_1.default.writeFileSync(ckbConfigPath, toml_1.default.stringify(ckbConfig));
4352
+ fs_1.default.copyFileSync(path_1.default.join(devnetSourcePath, 'default.db-options'), path_1.default.join(configPath, 'default.db-options'));
4353
+ const minerToml = fs_1.default.readFileSync(path_1.default.join(devnetSourcePath, 'ckb-miner.toml'), 'utf8');
4354
+ fs_1.default.writeFileSync(path_1.default.join(configPath, 'ckb-miner.toml'), minerToml.replace('http://ckb:8114/', settings.devnet.rpcUrl));
4355
+ }
4356
+ function forkDevnet(options) {
4357
+ return __awaiter(this, void 0, void 0, function* () {
4358
+ var _a;
4359
+ if (!options.from) {
4360
+ throw new Error('Database fork requires a source CKB directory: --from <dir>.');
4361
+ }
4362
+ const settings = (0, setting_1.readSettings)();
4363
+ const configPath = settings.devnet.configPath;
4364
+ const ckbVersion = settings.bins.defaultCKBVersion;
4365
+ const sourceDir = path_1.default.resolve(options.from);
4366
+ validateSourceDir(sourceDir);
4367
+ assertSourceNodeStopped(sourceDir);
4368
+ // Identify the source chain: explicit flag > ckb.toml bundled spec.
4369
+ let source = (_a = options.source) !== null && _a !== void 0 ? _a : null;
4370
+ if (source == null) {
4371
+ const sourceCkbToml = path_1.default.join(sourceDir, 'ckb.toml');
4372
+ if (fs_1.default.existsSync(sourceCkbToml)) {
4373
+ source = detectSourceFromCkbToml(fs_1.default.readFileSync(sourceCkbToml, 'utf8'));
4374
+ }
4375
+ }
4376
+ if (source == null && !options.specFile) {
4377
+ throw new Error('Could not identify the source chain from the source directory. ' +
4378
+ 'Pass --source mainnet|testnet, or provide the chain spec with --spec-file.');
4379
+ }
4380
+ yield (0, install_1.installCKBBinary)(ckbVersion);
4381
+ const ckbBinPath = (0, setting_1.getCKBBinaryPath)(ckbVersion);
4382
+ const databaseMigrationNeeded = isDatabaseMigrationNeeded(ckbBinPath, sourceDir);
4383
+ const sourcePeerStoreDetected = fs_1.default.existsSync(path_1.default.join(sourceDir, 'data', 'network', 'peer_store'));
4384
+ const specFile = yield resolveSpecFile(options, source !== null && source !== void 0 ? source : 'mainnet', ckbVersion);
4385
+ const specPreflight = validateForkSpec(ckbBinPath, sourceDir, specFile, source);
4386
+ source = specPreflight.source;
4387
+ logger_1.logger.info(`Fork preflight: source=${source}, genesis=${specPreflight.genesisHash}, CKB=${ckbVersion}, migration=${databaseMigrationNeeded ? 'required' : 'not required'}.`);
4388
+ if (sourcePeerStoreDetected) {
4389
+ logger_1.logger.info('A persisted source peer store was detected and will be excluded from the fork.');
4390
+ }
4391
+ if (options.dryRun) {
4392
+ logger_1.logger.success('Fork preflight passed. No devnet files were changed.');
4393
+ logger_1.logger.result({
4394
+ command: 'devnet.fork.preflight',
4395
+ sourceDir,
4396
+ source,
4397
+ genesisHash: specPreflight.genesisHash,
4398
+ ckbVersion,
4399
+ databaseMigrationNeeded,
4400
+ sourcePeerStoreDetected,
4401
+ sourcePeerStoreWillBeExcluded: true,
4402
+ targetDir: configPath,
4403
+ });
4404
+ return;
4405
+ }
4406
+ assertOffckbDevnetStopped();
4407
+ if (databaseMigrationNeeded && !options.migrate) {
4408
+ throw new Error(`The source database requires migration for CKB v${ckbVersion}. ` +
4409
+ 'Re-run with --migrate to migrate only the copied devnet, leaving the source untouched.');
4410
+ }
4411
+ if ((0, fs_2.isFolderExists)(configPath)) {
4412
+ if (!options.force) {
4413
+ throw new Error(`A devnet already exists at ${configPath}. Re-run with --force to replace it, ` +
4414
+ `or reset it first with: offckb clean`);
4415
+ }
4416
+ logger_1.logger.info(`Removing existing devnet at ${configPath} ..`);
4417
+ fs_1.default.rmSync(configPath, { recursive: true, force: true });
4418
+ }
4419
+ try {
4420
+ // Inside the try so a failed copy (disk full, permissions, I/O) gets the
4421
+ // same rollback: a partial configPath would otherwise block the next
4422
+ // attempt as an "existing devnet".
4423
+ copySourceData(sourceDir, configPath);
4424
+ const initOutput = runCkbInit(ckbBinPath, configPath, specFile);
4425
+ const genesisHash = parseGenesisHashFromInitOutput(initOutput);
4426
+ if (!genesisHash) {
4427
+ throw new Error(`Could not parse the genesis hash from ckb init output:\n${initOutput}`);
4428
+ }
4429
+ if (genesisHash !== specPreflight.genesisHash) {
4430
+ throw new Error(`Chain spec changed after preflight: expected genesis ${specPreflight.genesisHash}, got ${genesisHash}.`);
4431
+ }
4432
+ const devSpecPath = path_1.default.join(configPath, 'specs', 'dev.toml');
4433
+ const spec = toml_1.default.parse(fs_1.default.readFileSync(devSpecPath, 'utf8'));
4434
+ const patched = patchDevSpecForFork(spec, source);
4435
+ const tempSpecPath = `${devSpecPath}.tmp`;
4436
+ fs_1.default.writeFileSync(tempSpecPath, toml_1.default.stringify(patched));
4437
+ fs_1.default.renameSync(tempSpecPath, devSpecPath);
4438
+ alignConfigsWithOffckb(configPath);
4439
+ if (databaseMigrationNeeded) {
4440
+ migrateDatabaseCopy(ckbBinPath, configPath);
4441
+ }
4442
+ const state = {
4443
+ source,
4444
+ sourceDir,
4445
+ ckbVersion,
4446
+ genesisHash,
4447
+ forkedAt: new Date().toISOString(),
4448
+ firstRunPending: true,
4449
+ databaseMigrated: databaseMigrationNeeded,
4450
+ networkIsolated: true,
4451
+ };
4452
+ writeForkState(configPath, state);
4453
+ logger_1.logger.success(`Devnet forked from ${sourceDir} (${source}, genesis ${genesisHash}).`);
4454
+ logger_1.logger.info('Start it with: offckb node --daemon');
4455
+ logger_1.logger.info('The first run applies --skip-spec-check --overwrite-spec automatically.');
4456
+ logger_1.logger.info('Then inspect node, Indexer, and network isolation with: offckb devnet info');
4457
+ logger_1.logger.info('List source-prefix dev addresses with: offckb accounts');
4458
+ if (source === 'mainnet') {
4459
+ logger_1.logger.warn('MAINNET FORK REPLAY RISK: only sign with built-in dev keys and fork-mined cells.');
4460
+ }
4461
+ logger_1.logger.result({
4462
+ command: 'devnet.fork',
4463
+ sourceDir,
4464
+ source,
4465
+ genesisHash,
4466
+ ckbVersion,
4467
+ databaseMigrated: databaseMigrationNeeded,
4468
+ networkIsolated: true,
4469
+ next: ['offckb node --daemon', 'offckb devnet info', 'offckb accounts'],
4470
+ });
4471
+ }
4472
+ catch (error) {
4473
+ // Leave no half-forked devnet behind.
4474
+ fs_1.default.rmSync(configPath, { recursive: true, force: true });
4475
+ throw error;
4476
+ }
4477
+ });
4478
+ }
4479
+
4480
+
4481
+ /***/ }),
4482
+
4483
+ /***/ 19326:
4484
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
4485
+
4486
+ "use strict";
4487
+
4488
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4489
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4490
+ return new (P || (P = Promise))(function (resolve, reject) {
4491
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
4492
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
4493
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
4494
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
4495
+ });
4496
+ };
4497
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
4498
+ exports.checkNodeReadiness = checkNodeReadiness;
4499
+ exports.waitForNodeReady = waitForNodeReady;
4500
+ exports.checkConfiguredDevnetReadiness = checkConfiguredDevnetReadiness;
4501
+ exports.warnIfForkIndexerIsBehind = warnIfForkIndexerIsBehind;
4502
+ const setting_1 = __nccwpck_require__(25546);
4503
+ const base_1 = __nccwpck_require__(69951);
4504
+ const logger_1 = __nccwpck_require__(65370);
4505
+ const json_rpc_1 = __nccwpck_require__(51726);
4506
+ const fork_1 = __nccwpck_require__(77912);
4507
+ function parseHexNumber(value) {
4508
+ if (typeof value !== 'string' || !/^0x[0-9a-f]+$/i.test(value))
4509
+ return undefined;
4510
+ return BigInt(value);
4511
+ }
4512
+ function checkNodeReadiness(rpcUrl_1) {
4513
+ return __awaiter(this, arguments, void 0, function* (rpcUrl, timeoutMs = 3000) {
4514
+ try {
4515
+ const [nodeInfo, tipValue] = yield Promise.all([
4516
+ (0, json_rpc_1.callJsonRpc)(rpcUrl, 'local_node_info', [], timeoutMs),
4517
+ (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_tip_block_number', [], timeoutMs),
4518
+ ]);
4519
+ const nodeTip = parseHexNumber(tipValue);
4520
+ if (nodeTip == null) {
4521
+ throw new Error(`Invalid node tip returned by ${rpcUrl}`);
4522
+ }
4523
+ let indexerTip;
4524
+ try {
4525
+ const value = yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_indexer_tip', [], timeoutMs);
4526
+ indexerTip = parseHexNumber(value === null || value === void 0 ? void 0 : value.block_number);
4527
+ }
4528
+ catch (_a) {
4529
+ // Indexer readiness is reported separately and does not make the node RPC unhealthy.
4530
+ }
4531
+ let peers;
4532
+ try {
4533
+ const value = yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_peers', [], timeoutMs);
4534
+ peers = Array.isArray(value) ? value.length : undefined;
4535
+ }
4536
+ catch (_b) {
4537
+ // The Net RPC module can be disabled on custom nodes.
4538
+ }
4539
+ const indexerLag = indexerTip == null ? undefined : indexerTip >= nodeTip ? BigInt(0) : nodeTip - indexerTip;
4540
+ return {
4541
+ ready: true,
4542
+ rpcUrl,
4543
+ version: typeof (nodeInfo === null || nodeInfo === void 0 ? void 0 : nodeInfo.version) === 'string' ? nodeInfo.version : undefined,
4544
+ nodeTip,
4545
+ indexerTip,
4546
+ indexerLag,
4547
+ peers,
4548
+ };
4549
+ }
4550
+ catch (error) {
4551
+ return {
4552
+ ready: false,
4553
+ rpcUrl,
4554
+ error: error instanceof Error ? error.message : String(error),
4555
+ };
4556
+ }
4557
+ });
4558
+ }
4559
+ function waitForNodeReady(rpcUrl_1, timeoutMs_1) {
4560
+ return __awaiter(this, arguments, void 0, function* (rpcUrl, timeoutMs, isProcessAlive = () => true) {
4561
+ const start = Date.now();
4562
+ let last = yield checkNodeReadiness(rpcUrl);
4563
+ while (!last.ready && isProcessAlive() && Date.now() - start < timeoutMs) {
4564
+ yield new Promise((resolve) => setTimeout(resolve, 500));
4565
+ last = yield checkNodeReadiness(rpcUrl);
4566
+ }
4567
+ return last;
4568
+ });
4569
+ }
4570
+ function checkConfiguredDevnetReadiness() {
4571
+ return __awaiter(this, void 0, void 0, function* () {
4572
+ return checkNodeReadiness((0, setting_1.readSettings)().devnet.rpcUrl);
4573
+ });
4574
+ }
4575
+ function warnIfForkIndexerIsBehind(network) {
4576
+ return __awaiter(this, void 0, void 0, function* () {
4577
+ var _a;
4578
+ if (network !== base_1.Network.devnet)
4579
+ return undefined;
4580
+ const settings = (0, setting_1.readSettings)();
4581
+ if (!(0, fork_1.readForkState)(settings.devnet.configPath))
4582
+ return undefined;
4583
+ const readiness = yield checkNodeReadiness(settings.devnet.rpcUrl);
4584
+ if (!readiness.ready) {
4585
+ logger_1.logger.warn(`The forked devnet is not RPC-ready: ${(_a = readiness.error) !== null && _a !== void 0 ? _a : 'health check failed'}`);
4586
+ }
4587
+ else if (readiness.indexerTip == null) {
4588
+ logger_1.logger.warn('The CKB indexer is not ready yet; cell and balance lookups may be incomplete. Check `offckb devnet info`.');
4589
+ }
4590
+ else if (readiness.indexerLag && readiness.indexerLag > BigInt(0)) {
4591
+ logger_1.logger.warn(`The CKB indexer is ${readiness.indexerLag} blocks behind the node; cell and balance lookups may be incomplete. ` +
4592
+ 'Check `offckb devnet info`.');
4593
+ }
4594
+ return readiness;
4595
+ });
4596
+ }
4597
+
4598
+
3490
4599
  /***/ }),
3491
4600
 
3492
4601
  /***/ 1360:
@@ -3518,19 +4627,27 @@ function initChainIfNeeded() {
3518
4627
  const settings = (0, setting_1.readSettings)();
3519
4628
  const devnetSourcePath = path_1.default.resolve(setting_1.packageRootPath, './ckb/devnet');
3520
4629
  const devnetConfigPath = settings.devnet.configPath;
3521
- if (!(0, fs_2.isFolderExists)(devnetConfigPath)) {
3522
- const devnetConfigPath = settings.devnet.configPath;
3523
- yield (0, fs_2.copyFilesWithExclusion)(devnetSourcePath, devnetConfigPath, ['data']);
4630
+ const requiredConfigFiles = ['ckb.toml', 'ckb-miner.toml', path_1.default.join('specs', 'dev.toml')];
4631
+ const isInitialized = (0, fs_2.isFolderExists)(devnetConfigPath) &&
4632
+ requiredConfigFiles.every((relativePath) => fs_1.default.existsSync(path_1.default.join(devnetConfigPath, relativePath)));
4633
+ const minerConfigPath = path_1.default.join(devnetConfigPath, 'ckb-miner.toml');
4634
+ const minerConfigWasMissing = !fs_1.default.existsSync(minerConfigPath);
4635
+ // Daemon mode creates data/logs before the child starts. A directory-only
4636
+ // check therefore mistakes a fresh install for an initialized chain. Check
4637
+ // the files CKB actually needs instead, and repair an incomplete directory.
4638
+ if (!isInitialized) {
4639
+ yield (0, fs_2.copyFilesWithExclusion)(devnetSourcePath, devnetConfigPath, ['data'], false);
3524
4640
  logger_1.logger.debug(`init devnet config folder: ${devnetConfigPath}`);
3525
4641
  // copy and edit ckb-miner.toml
3526
4642
  const minerToml = path_1.default.join(devnetSourcePath, 'ckb-miner.toml');
3527
- const newMinerToml = path_1.default.join(devnetConfigPath, 'ckb-miner.toml');
3528
- // Read the content of the ckb-miner.toml file
3529
- const data = fs_1.default.readFileSync(minerToml, 'utf8');
3530
- // Replace the URL
3531
- const modifiedData = data.replace('http://ckb:8114/', settings.devnet.rpcUrl);
3532
- // Write the modified content back to the file
3533
- fs_1.default.writeFileSync(newMinerToml, modifiedData, 'utf8');
4643
+ if (minerConfigWasMissing) {
4644
+ // Read the content of the ckb-miner.toml file
4645
+ const data = fs_1.default.readFileSync(minerToml, 'utf8');
4646
+ // Replace the URL
4647
+ const modifiedData = data.replace('http://ckb:8114/', settings.devnet.rpcUrl);
4648
+ // Write the modified content back to the file
4649
+ fs_1.default.writeFileSync(minerConfigPath, modifiedData, 'utf8');
4650
+ }
3534
4651
  }
3535
4652
  });
3536
4653
  }
@@ -3601,7 +4718,7 @@ const fs = __importStar(__nccwpck_require__(79896));
3601
4718
  const path = __importStar(__nccwpck_require__(16928));
3602
4719
  const semver_1 = __importDefault(__nccwpck_require__(89939));
3603
4720
  const os_1 = __importDefault(__nccwpck_require__(70857));
3604
- const adm_zip_1 = __importDefault(__nccwpck_require__(83746));
4721
+ const adm_zip_1 = __importDefault(__nccwpck_require__(3858));
3605
4722
  const tar = __importStar(__nccwpck_require__(80228));
3606
4723
  const request_1 = __nccwpck_require__(65897);
3607
4724
  const setting_1 = __nccwpck_require__(25546);
@@ -3660,6 +4777,7 @@ function downloadCKBBinaryAndUnzip(version) {
3660
4777
  }
3661
4778
  catch (error) {
3662
4779
  logger_1.logger.error('Error installing dependency binary:', error.message);
4780
+ throw error;
3663
4781
  }
3664
4782
  });
3665
4783
  }
@@ -3821,7 +4939,8 @@ function buildDownloadUrl(version, opt = {}) {
3821
4939
  "use strict";
3822
4940
 
3823
4941
  Object.defineProperty(exports, "__esModule", ({ value: true }));
3824
- exports.TYPE_ID_SCRIPT = void 0;
4942
+ exports.TESTNET_GENESIS_HASH = exports.MAINNET_GENESIS_HASH = exports.TYPE_ID_SCRIPT = void 0;
4943
+ exports.identifyPublicChainByGenesisHash = identifyPublicChainByGenesisHash;
3825
4944
  exports.TYPE_ID_SCRIPT = {
3826
4945
  name: 'type_id',
3827
4946
  script: {
@@ -3830,6 +4949,18 @@ exports.TYPE_ID_SCRIPT = {
3830
4949
  cellDeps: [],
3831
4950
  },
3832
4951
  };
4952
+ // Well-known genesis block hashes, used to identify which chain a devnet
4953
+ // actually runs (a forked devnet carries the source chain's genesis).
4954
+ // https://github.com/nervosnetwork/ckb/tree/master/resource/specs
4955
+ exports.MAINNET_GENESIS_HASH = '0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5';
4956
+ exports.TESTNET_GENESIS_HASH = '0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606';
4957
+ function identifyPublicChainByGenesisHash(genesisHash) {
4958
+ if (genesisHash === exports.MAINNET_GENESIS_HASH)
4959
+ return 'mainnet';
4960
+ if (genesisHash === exports.TESTNET_GENESIS_HASH)
4961
+ return 'testnet';
4962
+ return null;
4963
+ }
3833
4964
 
3834
4965
 
3835
4966
  /***/ }),
@@ -3917,30 +5048,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3917
5048
  return (mod && mod.__esModule) ? mod : { "default": mod };
3918
5049
  };
3919
5050
  Object.defineProperty(exports, "__esModule", ({ value: true }));
5051
+ exports.resolveDevnetSystemScripts = resolveDevnetSystemScripts;
3920
5052
  exports.getDevnetSystemScriptsFromListHashes = getDevnetSystemScriptsFromListHashes;
3921
5053
  exports.toCCCKnownScripts = toCCCKnownScripts;
3922
5054
  exports.buildCCCDevnetKnownScripts = buildCCCDevnetKnownScripts;
5055
+ exports.buildDevnetCCCClient = buildDevnetCCCClient;
3923
5056
  const core_1 = __nccwpck_require__(39842);
3924
5057
  const setting_1 = __nccwpck_require__(25546);
3925
- const system_scripts_1 = __nccwpck_require__(86198);
3926
5058
  const list_hashes_1 = __nccwpck_require__(31603);
3927
5059
  const logger_1 = __nccwpck_require__(65370);
3928
5060
  const const_1 = __nccwpck_require__(75587);
5061
+ const public_1 = __nccwpck_require__(84535);
3929
5062
  const toml_1 = __importDefault(__nccwpck_require__(2811));
3930
5063
  const util_1 = __nccwpck_require__(58642);
3931
- function getDevnetSystemScriptsFromListHashes() {
5064
+ function buildSystemScriptsFromSpecHashes(chainSpecHashes) {
3932
5065
  var _a;
3933
- const settings = (0, setting_1.readSettings)();
3934
- const listHashesString = (0, list_hashes_1.getDevnetListHashes)(settings.bins.defaultCKBVersion);
3935
- if (!listHashesString) {
3936
- logger_1.logger.info(`list-hashes not found!`);
3937
- return null;
3938
- }
3939
- const listHashes = toml_1.default.parse(listHashesString);
3940
- const chainSpecHashes = Object.values(listHashes)[0];
3941
- if (chainSpecHashes == null) {
3942
- throw new Error(`invalid chain spec hashes file ${listHashesString}`);
3943
- }
3944
5066
  const systemScriptArray = chainSpecHashes.system_cells
3945
5067
  .map((cell) => {
3946
5068
  // Extract the file name from the path using the helper function
@@ -3953,7 +5075,7 @@ function getDevnetSystemScriptsFromListHashes() {
3953
5075
  txHash: chainSpecHashes.dep_groups[depGroupIndex].tx_hash,
3954
5076
  index: chainSpecHashes.dep_groups[depGroupIndex].index,
3955
5077
  };
3956
- const scriptInfo = (0, system_scripts_1.systemCellToScriptInfo)({ cell, depType, depGroup });
5078
+ const scriptInfo = (0, util_1.systemCellToScriptInfo)({ cell, depType, depGroup });
3957
5079
  return {
3958
5080
  name,
3959
5081
  file: cell.path,
@@ -3973,6 +5095,46 @@ function getDevnetSystemScriptsFromListHashes() {
3973
5095
  systemScripts.type_id = const_1.TYPE_ID_SCRIPT;
3974
5096
  return systemScripts;
3975
5097
  }
5098
+ // `ckb list-hashes` only reports what the chain spec declares in genesis.
5099
+ // Post-genesis deployments (sudt/xudt/omnilock/spore on mainnet and testnet)
5100
+ // can never appear there, so for a forked devnet we fill the gaps from the
5101
+ // well-known static records of the chain the genesis belongs to. Genesis
5102
+ // scripts always come from list-hashes — the chain's own spec wins.
5103
+ function supplementFromStaticRecord(scripts, staticRecord) {
5104
+ for (const [name, script] of Object.entries(staticRecord)) {
5105
+ const key = name;
5106
+ if (scripts[key] == null && script != null) {
5107
+ // deep clone so callers can never mutate the shared static record
5108
+ scripts[key] = JSON.parse(JSON.stringify(script));
5109
+ }
5110
+ }
5111
+ }
5112
+ function resolveDevnetSystemScripts() {
5113
+ const settings = (0, setting_1.readSettings)();
5114
+ const listHashesString = (0, list_hashes_1.getDevnetListHashes)(settings.bins.defaultCKBVersion);
5115
+ if (!listHashesString) {
5116
+ logger_1.logger.info(`list-hashes not found!`);
5117
+ return null;
5118
+ }
5119
+ const listHashes = toml_1.default.parse(listHashesString);
5120
+ const chainSpecHashes = Object.values(listHashes)[0];
5121
+ if (chainSpecHashes == null) {
5122
+ throw new Error(`invalid chain spec hashes file ${listHashesString}`);
5123
+ }
5124
+ const scripts = buildSystemScriptsFromSpecHashes(chainSpecHashes);
5125
+ const forkedFrom = (0, const_1.identifyPublicChainByGenesisHash)(chainSpecHashes.genesis);
5126
+ if (forkedFrom === 'mainnet') {
5127
+ supplementFromStaticRecord(scripts, public_1.MAINNET_SYSTEM_SCRIPTS);
5128
+ }
5129
+ else if (forkedFrom === 'testnet') {
5130
+ supplementFromStaticRecord(scripts, public_1.TESTNET_SYSTEM_SCRIPTS);
5131
+ }
5132
+ return { scripts, forkedFrom, genesisHash: chainSpecHashes.genesis };
5133
+ }
5134
+ function getDevnetSystemScriptsFromListHashes() {
5135
+ var _a, _b;
5136
+ return (_b = (_a = resolveDevnetSystemScripts()) === null || _a === void 0 ? void 0 : _a.scripts) !== null && _b !== void 0 ? _b : null;
5137
+ }
3976
5138
  function toCCCKnownScripts(scripts) {
3977
5139
  const DEVNET_SCRIPTS = {
3978
5140
  [core_1.KnownScript.Secp256k1Blake160]: scripts.secp256k1_blake160_sighash_all.script,
@@ -4000,6 +5162,19 @@ function buildCCCDevnetKnownScripts() {
4000
5162
  const devnetKnownScripts = toCCCKnownScripts(devnetSystemScripts);
4001
5163
  return devnetKnownScripts;
4002
5164
  }
5165
+ // Build the ccc client for the devnet. A forked devnet carries the source
5166
+ // chain's genesis, so a mainnet fork must use the `ckb` address prefix
5167
+ // (ClientPublicMainnet); everything else uses the `ckt` prefix. Known scripts
5168
+ // always come from the fork-aware resolver above.
5169
+ function buildDevnetCCCClient(url, fallbacks = []) {
5170
+ const resolved = resolveDevnetSystemScripts();
5171
+ if (resolved == null) {
5172
+ throw new Error('can not getSystemScriptsFromListHashes in devnet');
5173
+ }
5174
+ const scripts = toCCCKnownScripts(resolved.scripts);
5175
+ const ClientCtor = resolved.forkedFrom === 'mainnet' ? core_1.ccc.ClientPublicMainnet : core_1.ccc.ClientPublicTestnet;
5176
+ return new ClientCtor({ url, scripts, fallbacks });
5177
+ }
4003
5178
 
4004
5179
 
4005
5180
  /***/ }),
@@ -4384,6 +5559,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
4384
5559
  exports.readDeployedScriptInfoFrom = readDeployedScriptInfoFrom;
4385
5560
  exports.getScriptInfoFrom = getScriptInfoFrom;
4386
5561
  exports.extractScriptNameFromPath = extractScriptNameFromPath;
5562
+ exports.systemCellToScriptInfo = systemCellToScriptInfo;
4387
5563
  const fs = __importStar(__nccwpck_require__(79896));
4388
5564
  const migration_1 = __nccwpck_require__(76679);
4389
5565
  const path_1 = __importDefault(__nccwpck_require__(16928));
@@ -4474,6 +5650,57 @@ function extractScriptNameFromPath(pathString) {
4474
5650
  // On Windows, it handles backslashes; on Unix, it handles forward slashes
4475
5651
  return path_1.default.basename(pathString);
4476
5652
  }
5653
+ function systemCellToScriptInfo({ cell, depType, depGroup, extraCellDeps, }) {
5654
+ // todo: we left the type in cellDepsInfo since it requires async fetching and
5655
+ // chain running to get the full type script of the type-id deps.
5656
+ // Also, in devnet there is no real need to auto upgrade the system scripts with type-id
5657
+ if (depType === 'code') {
5658
+ let cellDeps = [
5659
+ {
5660
+ cellDep: {
5661
+ outPoint: {
5662
+ txHash: cell.tx_hash,
5663
+ index: cell.index,
5664
+ },
5665
+ depType,
5666
+ },
5667
+ },
5668
+ ];
5669
+ if (extraCellDeps && extraCellDeps.length > 0) {
5670
+ cellDeps = [...extraCellDeps, ...cellDeps];
5671
+ }
5672
+ return {
5673
+ codeHash: (cell.type_hash || cell.data_hash),
5674
+ hashType: cell.type_hash ? 'type' : 'data2',
5675
+ cellDeps,
5676
+ };
5677
+ }
5678
+ if (depType === 'depGroup') {
5679
+ if (!depGroup) {
5680
+ throw new Error('require depGroup info since the dep type is depGroup');
5681
+ }
5682
+ let cellDeps = [
5683
+ {
5684
+ cellDep: {
5685
+ outPoint: {
5686
+ txHash: depGroup.txHash,
5687
+ index: depGroup.index,
5688
+ },
5689
+ depType,
5690
+ },
5691
+ },
5692
+ ];
5693
+ if (extraCellDeps && extraCellDeps.length > 0) {
5694
+ cellDeps = [...extraCellDeps, ...cellDeps];
5695
+ }
5696
+ return {
5697
+ codeHash: (cell.type_hash || cell.data_hash),
5698
+ hashType: cell.type_hash ? 'type' : 'data2',
5699
+ cellDeps,
5700
+ };
5701
+ }
5702
+ throw new Error(`unknown DepType ${depType}`);
5703
+ }
4477
5704
 
4478
5705
 
4479
5706
  /***/ }),
@@ -4541,11 +5768,7 @@ class CKB {
4541
5768
  url: network_1.networks.testnet.proxy_rpc_url,
4542
5769
  fallbacks: [network_1.networks.testnet.rpc_url],
4543
5770
  }) // we keep the fallbacks in case the proxy rpc is not started
4544
- : new core_1.ccc.ClientPublicTestnet({
4545
- url: network_1.networks.devnet.proxy_rpc_url,
4546
- scripts: (0, private_1.buildCCCDevnetKnownScripts)(),
4547
- fallbacks: [network_1.networks.devnet.rpc_url],
4548
- });
5771
+ : (0, private_1.buildDevnetCCCClient)(network_1.networks.devnet.proxy_rpc_url, [network_1.networks.devnet.rpc_url]);
4549
5772
  }
4550
5773
  else {
4551
5774
  this.client =
@@ -4559,11 +5782,7 @@ class CKB {
4559
5782
  url: network_1.networks.testnet.rpc_url,
4560
5783
  fallbacks: [],
4561
5784
  }) // pass it to avoid using websocket and fallback RPCs
4562
- : new core_1.ccc.ClientPublicTestnet({
4563
- url: network_1.networks.devnet.rpc_url,
4564
- scripts: (0, private_1.buildCCCDevnetKnownScripts)(),
4565
- fallbacks: [], // pass it to avoid using websocket and fallback RPCs
4566
- });
5785
+ : (0, private_1.buildDevnetCCCClient)(network_1.networks.devnet.rpc_url);
4567
5786
  }
4568
5787
  }
4569
5788
  buildSigner(privateKey) {
@@ -4620,7 +5839,7 @@ class CKB {
4620
5839
  });
4621
5840
  }
4622
5841
  transfer(_a) {
4623
- return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amountInCKB }) {
5842
+ return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amountInCKB, rejectInputsAtOrBeforeBlock, }) {
4624
5843
  const signer = this.buildSigner(privateKey);
4625
5844
  const to = yield core_1.ccc.Address.fromString(toAddress, this.client);
4626
5845
  const tx = core_1.ccc.Transaction.from({
@@ -4633,6 +5852,7 @@ class CKB {
4633
5852
  });
4634
5853
  yield tx.completeInputsByCapacity(signer);
4635
5854
  yield tx.completeFeeBy(signer, this.feeRate);
5855
+ yield this.assertInputsCreatedAfter(tx, rejectInputsAtOrBeforeBlock);
4636
5856
  const txHash = yield signer.sendTransaction(tx);
4637
5857
  return txHash;
4638
5858
  });
@@ -4802,7 +6022,7 @@ class CKB {
4802
6022
  });
4803
6023
  }
4804
6024
  udtTransfer(_a) {
4805
- return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amount, udtType, kind }) {
6025
+ return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amount, udtType, kind, rejectInputsAtOrBeforeBlock, }) {
4806
6026
  const signer = this.buildSigner(privateKey);
4807
6027
  const to = yield core_1.ccc.Address.fromString(toAddress, this.client);
4808
6028
  const amountBigInt = (0, validator_1.validateUdtAmount)(amount);
@@ -4833,10 +6053,28 @@ class CKB {
4833
6053
  tx.outputsData.push(core_1.ccc.hexFrom(core_1.ccc.numToBytes(changeAmount, 16)));
4834
6054
  }
4835
6055
  yield tx.completeFeeBy(signer, this.feeRate);
6056
+ yield this.assertInputsCreatedAfter(tx, rejectInputsAtOrBeforeBlock);
4836
6057
  const txHash = yield signer.sendTransaction(tx);
4837
6058
  return txHash;
4838
6059
  });
4839
6060
  }
6061
+ assertInputsCreatedAfter(tx, blockNumber) {
6062
+ return __awaiter(this, void 0, void 0, function* () {
6063
+ if (blockNumber == null)
6064
+ return;
6065
+ for (const input of tx.inputs) {
6066
+ const outPoint = input.previousOutput;
6067
+ const origin = yield this.client.getTransactionNoCache(outPoint.txHash);
6068
+ if ((origin === null || origin === void 0 ? void 0 : origin.blockNumber) == null) {
6069
+ throw new Error(`Refusing to sign: could not verify the origin block of input ${outPoint.txHash}:${outPoint.index}.`);
6070
+ }
6071
+ if (origin.blockNumber <= blockNumber) {
6072
+ throw new Error(`Refusing to sign: input ${outPoint.txHash}:${outPoint.index} was created at block ${origin.blockNumber}, ` +
6073
+ `at or before the Mainnet fork boundary ${blockNumber}.`);
6074
+ }
6075
+ }
6076
+ });
6077
+ }
4840
6078
  udtIssue(_a) {
4841
6079
  return __awaiter(this, arguments, void 0, function* ({ privateKey, kind, amount, typeArgs, toAddress }) {
4842
6080
  const signer = this.buildSigner(privateKey);
@@ -4849,7 +6087,7 @@ class CKB {
4849
6087
  logger_1.logger.warn('SUDT type args are derived from the issuer lock hash; --type-args is ignored');
4850
6088
  }
4851
6089
  const issuerLockHash = signerAddress.script.hash();
4852
- resolvedTypeArgs = ('0x' + issuerLockHash.slice(2, 42));
6090
+ resolvedTypeArgs = issuerLockHash;
4853
6091
  }
4854
6092
  else {
4855
6093
  if (typeArgs) {
@@ -4877,7 +6115,7 @@ class CKB {
4877
6115
  yield tx.completeInputsByCapacity(signer);
4878
6116
  yield tx.completeFeeBy(signer, this.feeRate);
4879
6117
  const txHash = yield signer.sendTransaction(tx);
4880
- return txHash;
6118
+ return { txHash, typeArgs: resolvedTypeArgs, receiver: to.toString() };
4881
6119
  });
4882
6120
  }
4883
6121
  udtDestroy(_a) {
@@ -6336,7 +7574,7 @@ const path = __importStar(__nccwpck_require__(16928));
6336
7574
  const fs = __importStar(__nccwpck_require__(79896));
6337
7575
  const os = __importStar(__nccwpck_require__(70857));
6338
7576
  const crypto = __importStar(__nccwpck_require__(76982));
6339
- const adm_zip_1 = __importDefault(__nccwpck_require__(83746));
7577
+ const adm_zip_1 = __importDefault(__nccwpck_require__(3858));
6340
7578
  const setting_1 = __nccwpck_require__(25546);
6341
7579
  const logger_1 = __nccwpck_require__(65370);
6342
7580
  const fs_1 = __nccwpck_require__(37293);
@@ -6344,6 +7582,16 @@ const DOWNLOAD_TIMEOUT_MS = 120000;
6344
7582
  const EXTRACT_TIMEOUT_MS = 60000;
6345
7583
  // Strict semver regex: v<major>.<minor>.<patch> (no leading zeros on digits)
6346
7584
  const STRICT_VERSION_REGEX = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
7585
+ // Independently pinned digests for the default release. Keeping these in
7586
+ // offckb makes the default installation verifiable even though ckb-tui v0.1.3
7587
+ // did not upload a checksums-sha256.txt asset.
7588
+ const KNOWN_SHA256 = {
7589
+ 'v0.1.3': {
7590
+ 'ckb-tui-with-node-linux-amd64.tar.gz': '33455cefe2c016149fa8fa3abde7960b348d4606afef9279d787ac8a8b59956f',
7591
+ 'ckb-tui-with-node-macos-aarch64.tar.gz': 'de18107ec179ced03608da956013e38ae82e6c1fae588f12c17d138ee6ee072c',
7592
+ 'ckb-tui-with-node-windows-amd64.zip': '749d8e09fd5d23fc8af12892b7d197add5aae004f7438678023e4a973f3fd58b',
7593
+ },
7594
+ };
6347
7595
  class CKBTui {
6348
7596
  /**
6349
7597
  * Pure lookup — returns the expected binary path without triggering any
@@ -6460,7 +7708,7 @@ class CKBTui {
6460
7708
  if (curlResult.status !== 0) {
6461
7709
  throw new Error(`curl exited with code ${curlResult.status}`);
6462
7710
  }
6463
- // 2. Verify checksum (best-effort: warns if checksum file is unavailable)
7711
+ // 2. Verify checksum. Installation fails closed if no trusted digest exists.
6464
7712
  this.verifyChecksum(version, assetName, archivePath);
6465
7713
  // 3. Extract to temp directory
6466
7714
  logger_1.logger.info('Extracting...');
@@ -6498,64 +7746,24 @@ class CKBTui {
6498
7746
  }
6499
7747
  }
6500
7748
  }
6501
- /**
6502
- * Best-effort SHA-256 checksum verification.
6503
- * Downloads the checksum file published alongside the release asset and
6504
- * verifies the downloaded archive. Logs a warning (but does not fail) if
6505
- * the checksum file is unavailable — this maintains compatibility while
6506
- * the upstream project adopts checksum publishing.
6507
- */
7749
+ /** Verify against an independently pinned digest. */
6508
7750
  static verifyChecksum(version, assetName, archivePath) {
6509
- const checksumUrl = `https://github.com/Officeyutong/ckb-tui/releases/download/${version}/checksums-sha256.txt`;
6510
- const checksumPath = path.join(path.dirname(archivePath), 'checksums-sha256.txt');
6511
- const fetchResult = (0, child_process_1.spawnSync)('curl', ['-fsSL', '--max-time', '30', '-o', checksumPath, checksumUrl], {
6512
- stdio: 'pipe',
6513
- timeout: 30000,
6514
- });
6515
- if (fetchResult.status !== 0) {
6516
- logger_1.logger.warn(`SHA-256 checksum file not available for version ${version}. ` +
6517
- 'Skipping integrity verification. Consider asking the upstream maintainer to publish checksum files.');
6518
- return;
6519
- }
6520
- try {
6521
- const checksumContent = fs.readFileSync(checksumPath, 'utf8');
6522
- const expectedHash = this.parseChecksumFile(checksumContent, assetName);
6523
- if (!expectedHash) {
6524
- logger_1.logger.warn(`Checksum entry for "${assetName}" not found in checksums file. Skipping verification.`);
6525
- return;
6526
- }
6527
- const actualHash = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
6528
- if (actualHash !== expectedHash) {
6529
- throw new Error(`SHA-256 checksum mismatch for ${assetName}.\n` +
6530
- `Expected: ${expectedHash}\nActual: ${actualHash}\n` +
6531
- 'The downloaded file may be corrupted or tampered with.');
6532
- }
6533
- logger_1.logger.info('SHA-256 checksum verified successfully.');
6534
- }
6535
- finally {
6536
- try {
6537
- fs.unlinkSync(checksumPath);
6538
- }
6539
- catch (_a) {
6540
- // Best effort
6541
- }
7751
+ var _a;
7752
+ const pinnedHash = (_a = KNOWN_SHA256[version]) === null || _a === void 0 ? void 0 : _a[assetName];
7753
+ if (!pinnedHash) {
7754
+ throw new Error(`No trusted SHA-256 checksum is pinned for ckb-tui ${version} (${assetName}). ` +
7755
+ 'Refusing to install an unverified binary.');
6542
7756
  }
7757
+ this.assertChecksum(archivePath, assetName, pinnedHash);
6543
7758
  }
6544
- /**
6545
- * Parse a standard SHA-256 checksum file (format: "<hash> <filename>" per line)
6546
- * and return the hex hash for the given asset name, or null if not found.
6547
- */
6548
- static parseChecksumFile(content, assetName) {
6549
- for (const line of content.split('\n')) {
6550
- const trimmed = line.trim();
6551
- if (!trimmed || trimmed.startsWith('#'))
6552
- continue;
6553
- const match = trimmed.match(/^([0-9a-fA-F]{64})\s+[*]?(.+)$/);
6554
- if (match && match[2] === assetName) {
6555
- return match[1].toLowerCase();
6556
- }
7759
+ static assertChecksum(archivePath, assetName, expectedHash) {
7760
+ const actualHash = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
7761
+ if (actualHash !== expectedHash) {
7762
+ throw new Error(`SHA-256 checksum mismatch for ${assetName}.\n` +
7763
+ `Expected: ${expectedHash}\nActual: ${actualHash}\n` +
7764
+ 'The downloaded file may be corrupted or tampered with.');
6557
7765
  }
6558
- return null;
7766
+ logger_1.logger.info('SHA-256 checksum verified successfully.');
6559
7767
  }
6560
7768
  /**
6561
7769
  * Extract a downloaded archive to the given directory.
@@ -6619,6 +7827,7 @@ const fs_1 = __importDefault(__nccwpck_require__(79896));
6619
7827
  const path_1 = __importDefault(__nccwpck_require__(16928));
6620
7828
  const core_1 = __nccwpck_require__(39842);
6621
7829
  const advanced_1 = __nccwpck_require__(98486);
7830
+ const json_rpc_1 = __nccwpck_require__(51726);
6622
7831
  const logger_1 = __nccwpck_require__(65370);
6623
7832
  const OutPointCodec = core_1.ccc.mol.struct({
6624
7833
  txHash: core_1.ccc.mol.Byte32,
@@ -6743,6 +7952,19 @@ function resolveInputs(client, inputs) {
6743
7952
  return resolved;
6744
7953
  });
6745
7954
  }
7955
+ function resolveHeaderDeps(rpc, headerDeps) {
7956
+ return __awaiter(this, void 0, void 0, function* () {
7957
+ const headers = [];
7958
+ for (const hash of headerDeps) {
7959
+ const header = yield (0, json_rpc_1.callJsonRpc)(rpc, 'get_header', [hash]);
7960
+ if (!header) {
7961
+ throw new Error(`Header not found: ${hash}`);
7962
+ }
7963
+ headers.push(header);
7964
+ }
7965
+ return headers;
7966
+ });
7967
+ }
6746
7968
  function dumpTransaction(_a) {
6747
7969
  return __awaiter(this, arguments, void 0, function* ({ rpc, txJsonFilePath, outputFilePath }) {
6748
7970
  try {
@@ -6758,15 +7980,16 @@ function dumpTransaction(_a) {
6758
7980
  });
6759
7981
  const txJson = JSON.parse(fs_1.default.readFileSync(txJsonFilePath, 'utf-8'));
6760
7982
  const tx = advanced_1.cccA.JsonRpcTransformers.transactionTo(txJson);
6761
- const [cell_deps, inputs] = yield Promise.all([
7983
+ const [cell_deps, inputs, header_deps] = yield Promise.all([
6762
7984
  resolveCellDeps(client, tx.cellDeps),
6763
7985
  resolveInputs(client, tx.inputs),
7986
+ resolveHeaderDeps(rpc, tx.headerDeps),
6764
7987
  ]);
6765
7988
  const mockTx = {
6766
7989
  mock_info: {
6767
7990
  inputs,
6768
7991
  cell_deps,
6769
- header_deps: tx.headerDeps.map((h) => h.toString()),
7992
+ header_deps,
6770
7993
  },
6771
7994
  tx: {
6772
7995
  version: '0x' + tx.version.toString(16),
@@ -8792,6 +10015,77 @@ function encodeBinPathForTerminal(path) {
8792
10015
  }
8793
10016
 
8794
10017
 
10018
+ /***/ }),
10019
+
10020
+ /***/ 47625:
10021
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
10022
+
10023
+ "use strict";
10024
+
10025
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10026
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10027
+ };
10028
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10029
+ exports.warnIfMainnetForkSigning = warnIfMainnetForkSigning;
10030
+ exports.validateMainnetForkSigning = validateMainnetForkSigning;
10031
+ const account_json_1 = __importDefault(__nccwpck_require__(13849));
10032
+ const account_1 = __nccwpck_require__(59489);
10033
+ const setting_1 = __nccwpck_require__(25546);
10034
+ const fork_1 = __nccwpck_require__(77912);
10035
+ const base_1 = __nccwpck_require__(69951);
10036
+ const logger_1 = __nccwpck_require__(65370);
10037
+ const BUILT_IN_DEV_KEYS = new Set([...account_json_1.default.map((account) => account.privkey), account_1.ckbDevnetMinerAccount.privkey].map((key) => key.toLowerCase()));
10038
+ function warnIfMainnetForkSigning(network, privateKey) {
10039
+ if (!readMainnetForkState(network))
10040
+ return;
10041
+ logMainnetForkSigningWarning(privateKey);
10042
+ }
10043
+ /**
10044
+ * Fail closed before a Mainnet-fork transfer is constructed or signed.
10045
+ * Returns the copied-chain tip so the transaction layer can reject inputs
10046
+ * created at or before the fork boundary after input selection.
10047
+ */
10048
+ function validateMainnetForkSigning(network, privateKey, allowMainnetReplayRisk = false) {
10049
+ const fork = readMainnetForkState(network);
10050
+ if (!fork)
10051
+ return undefined;
10052
+ logMainnetForkSigningWarning(privateKey);
10053
+ if (!BUILT_IN_DEV_KEYS.has(privateKey.trim().toLowerCase()) && !allowMainnetReplayRisk) {
10054
+ throw new Error('Refusing to sign with a non-built-in private key on a Mainnet fork. ' +
10055
+ 'Use --allow-mainnet-replay-risk only after verifying that no copied Mainnet input will be selected.');
10056
+ }
10057
+ if (fork.forkBlockNumber == null) {
10058
+ throw new Error('Mainnet fork boundary metadata is missing. Restart or recreate the fork before signing so input origins can be verified.');
10059
+ }
10060
+ try {
10061
+ const blockNumber = BigInt(fork.forkBlockNumber);
10062
+ if (blockNumber < BigInt(0))
10063
+ throw new Error('negative block number');
10064
+ return blockNumber;
10065
+ }
10066
+ catch (_a) {
10067
+ throw new Error(`Invalid Mainnet fork boundary metadata: ${fork.forkBlockNumber}`);
10068
+ }
10069
+ }
10070
+ function readMainnetForkState(network) {
10071
+ if (network !== base_1.Network.devnet)
10072
+ return null;
10073
+ const settings = (0, setting_1.readSettings)();
10074
+ const fork = (0, fork_1.readForkState)(settings.devnet.configPath);
10075
+ return (fork === null || fork === void 0 ? void 0 : fork.source) === 'mainnet' ? fork : null;
10076
+ }
10077
+ function logMainnetForkSigningWarning(privateKey) {
10078
+ logger_1.logger.warn([
10079
+ 'MAINNET FORK REPLAY RISK: CKB transactions have no chain id.',
10080
+ 'A transaction spending cells copied from Mainnet can also be valid on Mainnet.',
10081
+ 'Use only built-in dev keys and fork-mined cells unless you explicitly accept that risk.',
10082
+ ]);
10083
+ if (!BUILT_IN_DEV_KEYS.has(privateKey.trim().toLowerCase())) {
10084
+ logger_1.logger.warn('A non-built-in private key is being used on a Mainnet fork. Verify every input before signing.');
10085
+ }
10086
+ }
10087
+
10088
+
8795
10089
  /***/ }),
8796
10090
 
8797
10091
  /***/ 37293:
@@ -8897,22 +10191,23 @@ function copyFileSync(source, target) {
8897
10191
  }
8898
10192
  fs.writeFileSync(targetFile, fs.readFileSync(source));
8899
10193
  }
8900
- function copyFilesWithExclusion(sourceDir, destinationDir, excludedFolders) {
8901
- return __awaiter(this, void 0, void 0, function* () {
10194
+ function copyFilesWithExclusion(sourceDir_1, destinationDir_1, excludedFolders_1) {
10195
+ return __awaiter(this, arguments, void 0, function* (sourceDir, destinationDir, excludedFolders, overwrite = true) {
8902
10196
  try {
8903
10197
  // Ensure the destination directory exists
8904
10198
  yield fs.promises.mkdir(destinationDir, { recursive: true });
8905
10199
  // Start copying recursively from the source directory
8906
- yield copyRecursive(sourceDir, destinationDir, excludedFolders);
10200
+ yield copyRecursive(sourceDir, destinationDir, excludedFolders, overwrite);
8907
10201
  }
8908
10202
  catch (error) {
8909
10203
  logger_1.logger.error('An error occurred during copying files:', error);
10204
+ throw error;
8910
10205
  }
8911
10206
  });
8912
10207
  }
8913
10208
  // Function to recursively copy files and directories
8914
- function copyRecursive(source, destination, excludedFolders) {
8915
- return __awaiter(this, void 0, void 0, function* () {
10209
+ function copyRecursive(source_1, destination_1, excludedFolders_1) {
10210
+ return __awaiter(this, arguments, void 0, function* (source, destination, excludedFolders, overwrite = true) {
8916
10211
  // Get a list of all files and directories in the source directory
8917
10212
  const files = yield fs.promises.readdir(source);
8918
10213
  // Iterate through each file or directory
@@ -8929,12 +10224,14 @@ function copyRecursive(source, destination, excludedFolders) {
8929
10224
  else {
8930
10225
  // Ensure destination directory exists before copying
8931
10226
  yield fs.promises.mkdir(destPath, { recursive: true });
8932
- yield copyRecursive(sourcePath, destPath, excludedFolders);
10227
+ yield copyRecursive(sourcePath, destPath, excludedFolders, overwrite);
8933
10228
  }
8934
10229
  }
8935
10230
  else {
8936
10231
  // Otherwise, copy the file
8937
- yield fs.promises.copyFile(sourcePath, destPath);
10232
+ if (overwrite || !fs.existsSync(destPath)) {
10233
+ yield fs.promises.copyFile(sourcePath, destPath);
10234
+ }
8938
10235
  }
8939
10236
  }
8940
10237
  });
@@ -9019,6 +10316,85 @@ function getSubfolders(folderPath) {
9019
10316
  }
9020
10317
 
9021
10318
 
10319
+ /***/ }),
10320
+
10321
+ /***/ 51726:
10322
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
10323
+
10324
+ "use strict";
10325
+
10326
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
10327
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10328
+ return new (P || (P = Promise))(function (resolve, reject) {
10329
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10330
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10331
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10332
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10333
+ });
10334
+ };
10335
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10336
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10337
+ };
10338
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10339
+ exports.callJsonRpc = callJsonRpc;
10340
+ const http_1 = __importDefault(__nccwpck_require__(58611));
10341
+ const https_1 = __importDefault(__nccwpck_require__(65692));
10342
+ // Minimal raw JSON-RPC caller. Unlike Request.send it never goes through the
10343
+ // configured proxy, which makes it safe for talking to the local devnet node.
10344
+ function callJsonRpc(rpcUrl_1, method_1, params_1) {
10345
+ return __awaiter(this, arguments, void 0, function* (rpcUrl, method, params, timeoutMs = 30000) {
10346
+ const body = JSON.stringify({ id: 1, jsonrpc: '2.0', method, params });
10347
+ const url = new URL(rpcUrl);
10348
+ const transport = url.protocol === 'https:' ? https_1.default : http_1.default;
10349
+ return new Promise((resolve, reject) => {
10350
+ const req = transport.request({
10351
+ hostname: url.hostname,
10352
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
10353
+ path: url.pathname + url.search,
10354
+ method: 'POST',
10355
+ headers: {
10356
+ 'content-type': 'application/json',
10357
+ 'content-length': Buffer.byteLength(body),
10358
+ },
10359
+ timeout: timeoutMs,
10360
+ }, (res) => {
10361
+ const chunks = [];
10362
+ res.on('data', (chunk) => chunks.push(chunk));
10363
+ // The response stream can error or close before `end` (connection
10364
+ // reset, truncated body). Without these listeners the promise would
10365
+ // hang until the request timeout — or the process would crash on an
10366
+ // unhandled stream error.
10367
+ res.once('error', reject);
10368
+ res.on('close', () => {
10369
+ if (!res.complete) {
10370
+ reject(new Error(`JSON-RPC ${method} to ${rpcUrl} returned a truncated response`));
10371
+ }
10372
+ });
10373
+ res.on('end', () => {
10374
+ try {
10375
+ const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
10376
+ if (parsed.error) {
10377
+ reject(new Error(`JSON-RPC ${method} failed: ${JSON.stringify(parsed.error)}`));
10378
+ return;
10379
+ }
10380
+ resolve(parsed.result);
10381
+ }
10382
+ catch (error) {
10383
+ reject(new Error(`Invalid JSON-RPC response from ${rpcUrl}: ${error.message}`));
10384
+ }
10385
+ });
10386
+ });
10387
+ req.on('timeout', () => {
10388
+ req.destroy(new Error(`JSON-RPC ${method} to ${rpcUrl} timed out`));
10389
+ });
10390
+ req.on('error', reject);
10391
+ req.write(body);
10392
+ req.end();
10393
+ });
10394
+ });
10395
+ }
10396
+
10397
+
9022
10398
  /***/ }),
9023
10399
 
9024
10400
  /***/ 27814:
@@ -9100,6 +10476,7 @@ const levelColors = {
9100
10476
  };
9101
10477
  class UnifiedLogger {
9102
10478
  constructor(options = {}) {
10479
+ this.resultEmitted = false;
9103
10480
  this.enableColors = options.enableColors !== false;
9104
10481
  this.showLevel = options.showLevel !== false;
9105
10482
  this.jsonMode = options.jsonMode === true;
@@ -9109,8 +10486,8 @@ class UnifiedLogger {
9109
10486
  levels: {
9110
10487
  error: 0,
9111
10488
  warn: 1,
9112
- info: 2,
9113
- success: 3,
10489
+ success: 2,
10490
+ info: 3,
9114
10491
  debug: 4,
9115
10492
  },
9116
10493
  format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.printf(({ level, message, timestamp }) => {
@@ -9122,6 +10499,7 @@ class UnifiedLogger {
9122
10499
  }),
9123
10500
  ],
9124
10501
  });
10502
+ this.setJsonMode(this.jsonMode);
9125
10503
  }
9126
10504
  /**
9127
10505
  * Toggle JSON output mode. When enabled, every log line is emitted as a
@@ -9129,6 +10507,36 @@ class UnifiedLogger {
9129
10507
  */
9130
10508
  setJsonMode(enabled) {
9131
10509
  this.jsonMode = enabled;
10510
+ // In machine mode stdout is reserved for command result records. Progress
10511
+ // and diagnostics remain NDJSON, but go to stderr so callers can parse one
10512
+ // stable stdout object without filtering human-oriented logs.
10513
+ for (const transport of this.logger.transports) {
10514
+ if (transport instanceof winston_1.default.transports.Console) {
10515
+ const consoleTransport = transport;
10516
+ consoleTransport.stderrLevels = Object.fromEntries((enabled ? Object.keys(levelColors) : ['error', 'warn']).map((level) => [level, true]));
10517
+ }
10518
+ }
10519
+ }
10520
+ isJsonMode() {
10521
+ return this.jsonMode;
10522
+ }
10523
+ /** Emit one stable, command-level result record for programmatic callers. */
10524
+ result(result) {
10525
+ if (this.jsonMode && !this.resultEmitted) {
10526
+ this.resultEmitted = true;
10527
+ node_process_1.default.stdout.write(`${JSON.stringify(Object.assign({ ok: true }, result))}\n`);
10528
+ }
10529
+ }
10530
+ hasResult() {
10531
+ return this.resultEmitted;
10532
+ }
10533
+ /** Emit a stable error record without exposing internal stack traces. */
10534
+ failure(code, message, details) {
10535
+ if (this.jsonMode) {
10536
+ node_process_1.default.stderr.write(`${JSON.stringify(Object.assign({ ok: false, code, message }, (details == null ? {} : { details })))}\n`);
10537
+ return;
10538
+ }
10539
+ this.error(message);
9132
10540
  }
9133
10541
  /**
9134
10542
  * Format the message with appropriate colors and structure
@@ -9311,6 +10719,43 @@ const logger = UnifiedLogger.create({ showLevel: false });
9311
10719
  exports.logger = logger;
9312
10720
 
9313
10721
 
10722
+ /***/ }),
10723
+
10724
+ /***/ 26241:
10725
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
10726
+
10727
+ "use strict";
10728
+
10729
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10730
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10731
+ };
10732
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10733
+ exports.resolvePrivateKey = resolvePrivateKey;
10734
+ const fs_1 = __importDefault(__nccwpck_require__(79896));
10735
+ const path_1 = __importDefault(__nccwpck_require__(16928));
10736
+ function resolvePrivateKey(input, defaultKey) {
10737
+ if (input.privkey && input.privkeyFile) {
10738
+ throw new Error('Use only one of --privkey or --privkey-file.');
10739
+ }
10740
+ if (input.privkey)
10741
+ return input.privkey;
10742
+ if (input.privkeyFile) {
10743
+ const filePath = path_1.default.resolve(input.privkeyFile);
10744
+ try {
10745
+ return fs_1.default.readFileSync(filePath, 'utf8').trim();
10746
+ }
10747
+ catch (error) {
10748
+ throw new Error(`Could not read private key file ${filePath}: ${error.message}`);
10749
+ }
10750
+ }
10751
+ if (process.env.OFFCKB_PRIVATE_KEY)
10752
+ return process.env.OFFCKB_PRIVATE_KEY;
10753
+ if (defaultKey)
10754
+ return defaultKey;
10755
+ throw new Error('--privkey, --privkey-file, or OFFCKB_PRIVATE_KEY is required!');
10756
+ }
10757
+
10758
+
9314
10759
  /***/ }),
9315
10760
 
9316
10761
  /***/ 65897:
@@ -9405,7 +10850,6 @@ exports.validateUdtTypeArgs = validateUdtTypeArgs;
9405
10850
  const path_1 = __importDefault(__nccwpck_require__(16928));
9406
10851
  const fs_1 = __importDefault(__nccwpck_require__(79896));
9407
10852
  const base_1 = __nccwpck_require__(69951);
9408
- const logger_1 = __nccwpck_require__(65370);
9409
10853
  function validateTypescriptWorkspace() {
9410
10854
  const cwd = process.cwd();
9411
10855
  // Check if package.json exists
@@ -9447,8 +10891,7 @@ function validateNetworkOpt(network) {
9447
10891
  throw new Error('invalid network option, ' + network);
9448
10892
  }
9449
10893
  if (network === base_1.Network.mainnet) {
9450
- logger_1.logger.info('Mainnet not support yet. Please use CKB-CLI to operate on mainnet for better security. Check https://github.com/nervosnetwork/ckb-cli');
9451
- process.exit(1);
10894
+ throw new Error('Mainnet not support yet. Please use CKB-CLI to operate on mainnet for better security. Check https://github.com/nervosnetwork/ckb-cli');
9452
10895
  }
9453
10896
  }
9454
10897
  function isValidVersion(version) {
@@ -9504,9 +10947,12 @@ function validateUdtKind(kind) {
9504
10947
  const U128_MAX = (BigInt(1) << BigInt(128)) - BigInt(1);
9505
10948
  function validateUdtAmount(amount) {
9506
10949
  if (!/^\d+$/.test(amount)) {
9507
- throw new Error(`invalid UDT amount "${amount}", must be a non-negative decimal integer`);
10950
+ throw new Error(`invalid UDT amount "${amount}", must be a positive decimal integer`);
9508
10951
  }
9509
10952
  const value = BigInt(amount);
10953
+ if (value === BigInt(0)) {
10954
+ throw new Error('invalid UDT amount "0", must be greater than zero');
10955
+ }
9510
10956
  if (value > U128_MAX) {
9511
10957
  throw new Error(`UDT amount exceeds 128-bit max: ${amount}`);
9512
10958
  }
@@ -9522,8 +10968,8 @@ function validateHexString(value, name) {
9522
10968
  function validateUdtTypeArgs(kind, typeArgs) {
9523
10969
  const hex = validateHexString(typeArgs, 'type args');
9524
10970
  const byteLength = (hex.length - 2) / 2;
9525
- if (kind === 'sudt' && byteLength !== 20) {
9526
- throw new Error(`invalid SUDT type args length: expected 20 bytes, got ${byteLength}`);
10971
+ if (kind === 'sudt' && byteLength !== 32) {
10972
+ throw new Error(`invalid SUDT type args length: expected 32 bytes, got ${byteLength}`);
9527
10973
  }
9528
10974
  if (kind === 'xudt' && byteLength !== 32) {
9529
10975
  throw new Error(`invalid xUDT type args length: expected 32 bytes, got ${byteLength}`);
@@ -17273,13 +18719,13 @@ exports.randomBytes = randomBytes;
17273
18719
 
17274
18720
  /***/ }),
17275
18721
 
17276
- /***/ 83746:
18722
+ /***/ 3858:
17277
18723
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
17278
18724
 
17279
- const Utils = __nccwpck_require__(28605);
18725
+ const Utils = __nccwpck_require__(91373);
17280
18726
  const pth = __nccwpck_require__(16928);
17281
- const ZipEntry = __nccwpck_require__(59155);
17282
- const ZipFile = __nccwpck_require__(43985);
18727
+ const ZipEntry = __nccwpck_require__(58851);
18728
+ const ZipFile = __nccwpck_require__(46593);
17283
18729
 
17284
18730
  const get_Bool = (...val) => Utils.findLast(val, (c) => typeof c === "boolean");
17285
18731
  const get_Str = (...val) => Utils.findLast(val, (c) => typeof c === "string");
@@ -17325,6 +18771,18 @@ module.exports = function (/**String*/ input, /** object */ options) {
17325
18771
  // instanciate utils filesystem
17326
18772
  const filetools = new Utils(opts);
17327
18773
 
18774
+ // Restore the archived permissions on extracted directories. This has to run
18775
+ // after a directory's contents are written: applying a restrictive mode
18776
+ // (e.g. 0o500) up front would stop us writing the files it contains. Applying
18777
+ // the deepest paths first keeps parent directories traversable while their
18778
+ // children are updated (issue #530).
18779
+ const applyDirAttributes = (dirEntries) => {
18780
+ dirEntries
18781
+ .filter((d) => d.attr)
18782
+ .sort((a, b) => b.path.length - a.path.length)
18783
+ .forEach((d) => filetools.fs.chmodSync(d.path, d.attr));
18784
+ };
18785
+
17328
18786
  if (typeof opts.decoder !== "object" || typeof opts.decoder.encode !== "function" || typeof opts.decoder.decode !== "function") {
17329
18787
  opts.decoder = Utils.decoder;
17330
18788
  }
@@ -17364,7 +18822,7 @@ module.exports = function (/**String*/ input, /** object */ options) {
17364
18822
  function fixPath(zipPath) {
17365
18823
  const { join, normalize, sep } = pth.posix;
17366
18824
  // convert windows file separators and normalize
17367
- return join(".", normalize(sep + zipPath.split("\\").join(sep) + sep));
18825
+ return join(pth.isAbsolute(zipPath) ? "/": '.', normalize(sep + zipPath.split("\\").join(sep) + sep));
17368
18826
  }
17369
18827
 
17370
18828
  function filenameFilter(filterfn) {
@@ -17479,6 +18937,7 @@ module.exports = function (/**String*/ input, /** object */ options) {
17479
18937
  * Remove the entry from the file or the entry and all it's nested directories and files if the given entry is a directory
17480
18938
  *
17481
18939
  * @param {ZipEntry|string} entry
18940
+ * @param {boolean} withsubfolders
17482
18941
  * @returns {void}
17483
18942
  */
17484
18943
  deleteFile: function (entry, withsubfolders = true) {
@@ -17763,7 +19222,7 @@ module.exports = function (/**String*/ input, /** object */ options) {
17763
19222
  addLocalFolderAsync2: function (options, callback) {
17764
19223
  const self = this;
17765
19224
  options = typeof options === "object" ? options : { localPath: options };
17766
- localPath = pth.resolve(fixPath(options.localPath));
19225
+ const localPath = pth.resolve(fixPath(options.localPath));
17767
19226
  let { zipPath, filter, namefix } = options;
17768
19227
 
17769
19228
  if (filter instanceof RegExp) {
@@ -17782,7 +19241,7 @@ module.exports = function (/**String*/ input, /** object */ options) {
17782
19241
  zipPath = zipPath ? fixPath(zipPath) : "";
17783
19242
 
17784
19243
  // Check Namefix function
17785
- if (namefix == "latin1") {
19244
+ if (namefix === "latin1") {
17786
19245
  namefix = (str) =>
17787
19246
  str
17788
19247
  .normalize("NFD")
@@ -17958,7 +19417,7 @@ module.exports = function (/**String*/ input, /** object */ options) {
17958
19417
 
17959
19418
  var entryName = canonical(item.entryName);
17960
19419
 
17961
- var target = sanitize(targetPath, outFileName && !item.isDirectory ? outFileName : maintainEntryPath ? entryName : pth.basename(entryName));
19420
+ var target = sanitize(targetPath, outFileName && !item.isDirectory ? canonical(outFileName) : maintainEntryPath ? entryName : pth.basename(entryName));
17962
19421
 
17963
19422
  if (item.isDirectory) {
17964
19423
  var children = _zip.getEntryChildren(item);
@@ -17968,8 +19427,12 @@ module.exports = function (/**String*/ input, /** object */ options) {
17968
19427
  if (!content) {
17969
19428
  throw Utils.Errors.CANT_EXTRACT_FILE();
17970
19429
  }
17971
- var name = canonical(child.entryName);
17972
- var childName = sanitize(targetPath, maintainEntryPath ? name : pth.basename(name));
19430
+ // When not maintaining the full entry path, keep each child's path
19431
+ // relative to the extracted directory (drop the directory's own
19432
+ // prefix) instead of flattening every file to its basename, which
19433
+ // collapsed subdirectories together (issue #306).
19434
+ var name = canonical(maintainEntryPath ? child.entryName : child.entryName.substring(item.entryName.length));
19435
+ var childName = sanitize(targetPath, name);
17973
19436
  // The reverse operation for attr depend on method addFile()
17974
19437
  const fileAttr = keepOriginalPermission ? child.header.fileAttr : undefined;
17975
19438
  filetools.writeFileTo(childName, content, overwrite, fileAttr);
@@ -17999,12 +19462,14 @@ module.exports = function (/**String*/ input, /** object */ options) {
17999
19462
  return false;
18000
19463
  }
18001
19464
 
18002
- for (var entry in _zip.entries) {
19465
+ for (var entry of _zip.entries) {
18003
19466
  try {
18004
19467
  if (entry.isDirectory) {
18005
19468
  continue;
18006
19469
  }
18007
- var content = _zip.entries[entry].getData(pass);
19470
+ // was `_zip.entries[entry]` (indexing the array with an entry
19471
+ // object -> undefined -> threw -> test() always returned false)
19472
+ var content = entry.getData(pass);
18008
19473
  if (!content) {
18009
19474
  return false;
18010
19475
  }
@@ -18031,10 +19496,13 @@ module.exports = function (/**String*/ input, /** object */ options) {
18031
19496
  overwrite = get_Bool(false, overwrite);
18032
19497
  if (!_zip) throw Utils.Errors.NO_ZIP();
18033
19498
 
19499
+ const dirEntries = [];
18034
19500
  _zip.entries.forEach(function (entry) {
18035
19501
  var entryName = sanitize(targetPath, canonical(entry.entryName));
18036
19502
  if (entry.isDirectory) {
18037
19503
  filetools.makeDir(entryName);
19504
+ // defer restoring the directory permission until its files are written
19505
+ if (keepOriginalPermission) dirEntries.push({ path: entryName, attr: entry.header.fileAttr });
18038
19506
  return;
18039
19507
  }
18040
19508
  var content = entry.getData(pass);
@@ -18045,11 +19513,15 @@ module.exports = function (/**String*/ input, /** object */ options) {
18045
19513
  const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
18046
19514
  filetools.writeFileTo(entryName, content, overwrite, fileAttr);
18047
19515
  try {
19516
+ // best-effort: an invalid date in the archive or a filesystem that
19517
+ // rejects utimes must not fail extraction of already-written content (issue #379)
18048
19518
  filetools.fs.utimesSync(entryName, entry.header.time, entry.header.time);
18049
19519
  } catch (err) {
18050
- throw Utils.Errors.CANT_EXTRACT_FILE();
19520
+ /* ignore timestamp failures */
18051
19521
  }
18052
19522
  });
19523
+
19524
+ applyDirAttributes(dirEntries);
18053
19525
  },
18054
19526
 
18055
19527
  /**
@@ -18100,20 +19572,41 @@ module.exports = function (/**String*/ input, /** object */ options) {
18100
19572
 
18101
19573
  // Create directory entries first synchronously
18102
19574
  // this prevents race condition and assures folders are there before writing files
19575
+ const deferredDirAttr = [];
18103
19576
  for (const entry of dirEntries) {
18104
19577
  const dirPath = getPath(entry);
18105
19578
  // The reverse operation for attr depend on method addFile()
18106
19579
  const dirAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
18107
19580
  try {
18108
19581
  filetools.makeDir(dirPath);
18109
- if (dirAttr) filetools.fs.chmodSync(dirPath, dirAttr);
18110
- // in unix timestamp will change if files are later added to folder, but still
18111
- filetools.fs.utimesSync(dirPath, entry.header.time, entry.header.time);
18112
19582
  } catch (er) {
18113
19583
  callback(getError("Unable to create folder", dirPath));
19584
+ continue;
19585
+ }
19586
+ // defer restoring the directory permission until its files are written:
19587
+ // a restrictive mode applied now would block writing them
19588
+ if (dirAttr) deferredDirAttr.push({ path: dirPath, attr: dirAttr });
19589
+ try {
19590
+ // in unix timestamp will change if files are later added to folder, but still.
19591
+ // best-effort: a utimes failure must not abort extraction (issue #379)
19592
+ filetools.fs.utimesSync(dirPath, entry.header.time, entry.header.time);
19593
+ } catch (er) {
19594
+ /* ignore timestamp failures */
18114
19595
  }
18115
19596
  }
18116
19597
 
19598
+ // restore directory permissions once every file has been extracted
19599
+ const done = (err) => {
19600
+ if (!err) {
19601
+ try {
19602
+ applyDirAttributes(deferredDirAttr);
19603
+ } catch (er) {
19604
+ return callback(getError("Unable to set folder permissions", er.path || ""));
19605
+ }
19606
+ }
19607
+ callback(err);
19608
+ };
19609
+
18117
19610
  fileEntries.reverse().reduce(function (next, entry) {
18118
19611
  return function (err) {
18119
19612
  if (err) {
@@ -18131,21 +19624,19 @@ module.exports = function (/**String*/ input, /** object */ options) {
18131
19624
  const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
18132
19625
  filetools.writeFileToAsync(filePath, content, overwrite, fileAttr, function (succ) {
18133
19626
  if (!succ) {
18134
- next(getError("Unable to write file", filePath));
19627
+ return next(getError("Unable to write file", filePath));
18135
19628
  }
18136
- filetools.fs.utimes(filePath, entry.header.time, entry.header.time, function (err_2) {
18137
- if (err_2) {
18138
- next(getError("Unable to set times", filePath));
18139
- } else {
18140
- next();
18141
- }
19629
+ filetools.fs.utimes(filePath, entry.header.time, entry.header.time, function () {
19630
+ // best-effort: a utimes failure must not abort extraction
19631
+ // of already-written content (issue #379)
19632
+ next();
18142
19633
  });
18143
19634
  });
18144
19635
  }
18145
19636
  });
18146
19637
  }
18147
19638
  };
18148
- }, callback)();
19639
+ }, done)();
18149
19640
  },
18150
19641
 
18151
19642
  /**
@@ -18229,10 +19720,10 @@ module.exports = function (/**String*/ input, /** object */ options) {
18229
19720
 
18230
19721
  /***/ }),
18231
19722
 
18232
- /***/ 2916:
19723
+ /***/ 88436:
18233
19724
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
18234
19725
 
18235
- var Utils = __nccwpck_require__(28605),
19726
+ var Utils = __nccwpck_require__(91373),
18236
19727
  Constants = Utils.Constants;
18237
19728
 
18238
19729
  /* The central directory file header */
@@ -18321,6 +19812,7 @@ module.exports = function () {
18321
19812
  switch (val) {
18322
19813
  case Constants.STORED:
18323
19814
  this.version = 10;
19815
+ break;
18324
19816
  case Constants.DEFLATED:
18325
19817
  default:
18326
19818
  this.version = 20;
@@ -18332,6 +19824,7 @@ module.exports = function () {
18332
19824
  return Utils.fromDOS2Date(this.timeval);
18333
19825
  },
18334
19826
  set time(val) {
19827
+ val = new Date(val);
18335
19828
  this.timeval = Utils.fromDate2DOS(val);
18336
19829
  },
18337
19830
 
@@ -18454,6 +19947,8 @@ module.exports = function () {
18454
19947
  _localHeader.version = data.readUInt16LE(Constants.LOCVER);
18455
19948
  // general purpose bit flag
18456
19949
  _localHeader.flags = data.readUInt16LE(Constants.LOCFLG);
19950
+ // desc flag
19951
+ _localHeader.flags_desc = (_localHeader.flags & Constants.FLG_DESC) > 0;
18457
19952
  // compression method
18458
19953
  _localHeader.method = data.readUInt16LE(Constants.LOCHOW);
18459
19954
  // modification time (2 bytes time, 2 bytes date)
@@ -18520,7 +20015,11 @@ module.exports = function () {
18520
20015
  // version needed to extract
18521
20016
  data.writeUInt16LE(_version, Constants.LOCVER);
18522
20017
  // general purpose bit flag
18523
- data.writeUInt16LE(_flags, Constants.LOCFLG);
20018
+ // clear bit 3 (data descriptor): we always write the real crc-32
20019
+ // and sizes into this local header, so no trailing descriptor is
20020
+ // emitted. Leaving the flag set would make the output unreadable
20021
+ // (see issue #555).
20022
+ data.writeUInt16LE(_flags & ~Constants.FLG_DESC, Constants.LOCFLG);
18524
20023
  // compression method
18525
20024
  data.writeUInt16LE(_method, Constants.LOCHOW);
18526
20025
  // modification time (2 bytes time, 2 bytes date)
@@ -18548,7 +20047,9 @@ module.exports = function () {
18548
20047
  // version needed to extract
18549
20048
  data.writeUInt16LE(_version, Constants.CENVER);
18550
20049
  // encrypt, decrypt flags
18551
- data.writeUInt16LE(_flags, Constants.CENFLG);
20050
+ // clear bit 3 (data descriptor) to match the local header we emit
20051
+ // (real crc/sizes are written, no descriptor follows the data) — issue #555
20052
+ data.writeUInt16LE(_flags & ~Constants.FLG_DESC, Constants.CENFLG);
18552
20053
  // compression method
18553
20054
  data.writeUInt16LE(_method, Constants.CENHOW);
18554
20055
  // modification time (2 bytes time, 2 bytes date)
@@ -18610,19 +20111,19 @@ module.exports = function () {
18610
20111
 
18611
20112
  /***/ }),
18612
20113
 
18613
- /***/ 57405:
20114
+ /***/ 77837:
18614
20115
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
18615
20116
 
18616
- exports.EntryHeader = __nccwpck_require__(2916);
18617
- exports.MainHeader = __nccwpck_require__(81289);
20117
+ exports.EntryHeader = __nccwpck_require__(88436);
20118
+ exports.MainHeader = __nccwpck_require__(88889);
18618
20119
 
18619
20120
 
18620
20121
  /***/ }),
18621
20122
 
18622
- /***/ 81289:
20123
+ /***/ 88889:
18623
20124
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
18624
20125
 
18625
- var Utils = __nccwpck_require__(28605),
20126
+ var Utils = __nccwpck_require__(91373),
18626
20127
  Constants = Utils.Constants;
18627
20128
 
18628
20129
  /* The entries in the end of central directory */
@@ -18633,6 +20134,8 @@ module.exports = function () {
18633
20134
  _offset = 0,
18634
20135
  _commentLength = 0;
18635
20136
 
20137
+ const needsZip64 = () => _volumeEntries > Constants.EF_ZIP64_OR_16 || _totalEntries > Constants.EF_ZIP64_OR_16 || _size > Constants.EF_ZIP64_OR_32 || _offset > Constants.EF_ZIP64_OR_32;
20138
+
18636
20139
  return {
18637
20140
  get diskEntries() {
18638
20141
  return _volumeEntries;
@@ -18670,7 +20173,7 @@ module.exports = function () {
18670
20173
  },
18671
20174
 
18672
20175
  get mainHeaderSize() {
18673
- return Constants.ENDHDR + _commentLength;
20176
+ return (needsZip64() ? Constants.ZIP64HDR + Constants.END64HDR : 0) + Constants.ENDHDR + _commentLength;
18674
20177
  },
18675
20178
 
18676
20179
  loadFromBinary: function (/*Buffer*/ data) {
@@ -18700,7 +20203,7 @@ module.exports = function () {
18700
20203
  // total number of entries
18701
20204
  _totalEntries = Utils.readBigUInt64LE(data, Constants.ZIP64TOT);
18702
20205
  // central directory size in bytes
18703
- _size = Utils.readBigUInt64LE(data, Constants.ZIP64SIZE);
20206
+ _size = Utils.readBigUInt64LE(data, Constants.ZIP64SIZB);
18704
20207
  // offset of first CEN header
18705
20208
  _offset = Utils.readBigUInt64LE(data, Constants.ZIP64OFF);
18706
20209
 
@@ -18709,22 +20212,67 @@ module.exports = function () {
18709
20212
  },
18710
20213
 
18711
20214
  toBinary: function () {
18712
- var b = Buffer.alloc(Constants.ENDHDR + _commentLength);
20215
+ if (!needsZip64()) {
20216
+ var b = Buffer.alloc(Constants.ENDHDR + _commentLength);
20217
+ // "PK 05 06" signature
20218
+ b.writeUInt32LE(Constants.ENDSIG, 0);
20219
+ b.writeUInt32LE(0, 4);
20220
+ // number of entries on this volume
20221
+ b.writeUInt16LE(_volumeEntries, Constants.ENDSUB);
20222
+ // total number of entries
20223
+ b.writeUInt16LE(_totalEntries, Constants.ENDTOT);
20224
+ // central directory size in bytes
20225
+ b.writeUInt32LE(_size, Constants.ENDSIZ);
20226
+ // offset of first CEN header
20227
+ b.writeUInt32LE(_offset, Constants.ENDOFF);
20228
+ // zip file comment length
20229
+ b.writeUInt16LE(_commentLength, Constants.ENDCOM);
20230
+ // fill comment memory with spaces so no garbage is left there
20231
+ b.fill(" ", Constants.ENDHDR);
20232
+
20233
+ return b;
20234
+ }
20235
+
20236
+ var b = Buffer.alloc(this.mainHeaderSize);
20237
+ let offset = 0;
20238
+
20239
+ // Zip64 end of central directory record.
20240
+ b.writeUInt32LE(Constants.ZIP64SIG, offset);
20241
+ Utils.writeBigUInt64LE(b, Constants.ZIP64HDR - Constants.ZIP64LEAD, offset + Constants.ZIP64SIZE);
20242
+ b.writeUInt16LE(45, offset + Constants.ZIP64VEM);
20243
+ b.writeUInt16LE(45, offset + Constants.ZIP64VER);
20244
+ b.writeUInt32LE(0, offset + Constants.ZIP64DSK);
20245
+ b.writeUInt32LE(0, offset + Constants.ZIP64DSKDIR);
20246
+ Utils.writeBigUInt64LE(b, _volumeEntries, offset + Constants.ZIP64SUB);
20247
+ Utils.writeBigUInt64LE(b, _totalEntries, offset + Constants.ZIP64TOT);
20248
+ Utils.writeBigUInt64LE(b, _size, offset + Constants.ZIP64SIZB);
20249
+ Utils.writeBigUInt64LE(b, _offset, offset + Constants.ZIP64OFF);
20250
+
20251
+ const zip64EndOffset = _offset + _size;
20252
+ offset += Constants.ZIP64HDR;
20253
+
20254
+ // Zip64 end of central directory locator.
20255
+ b.writeUInt32LE(Constants.END64SIG, offset);
20256
+ b.writeUInt32LE(0, offset + Constants.END64START);
20257
+ Utils.writeBigUInt64LE(b, zip64EndOffset, offset + Constants.END64OFF);
20258
+ b.writeUInt32LE(1, offset + Constants.END64NUMDISKS);
20259
+ offset += Constants.END64HDR;
20260
+
18713
20261
  // "PK 05 06" signature
18714
- b.writeUInt32LE(Constants.ENDSIG, 0);
18715
- b.writeUInt32LE(0, 4);
20262
+ b.writeUInt32LE(Constants.ENDSIG, offset);
20263
+ b.writeUInt32LE(0, offset + 4);
18716
20264
  // number of entries on this volume
18717
- b.writeUInt16LE(_volumeEntries, Constants.ENDSUB);
20265
+ b.writeUInt16LE(Math.min(_volumeEntries, Constants.EF_ZIP64_OR_16), offset + Constants.ENDSUB);
18718
20266
  // total number of entries
18719
- b.writeUInt16LE(_totalEntries, Constants.ENDTOT);
20267
+ b.writeUInt16LE(Math.min(_totalEntries, Constants.EF_ZIP64_OR_16), offset + Constants.ENDTOT);
18720
20268
  // central directory size in bytes
18721
- b.writeUInt32LE(_size, Constants.ENDSIZ);
20269
+ b.writeUInt32LE(Math.min(_size, Constants.EF_ZIP64_OR_32), offset + Constants.ENDSIZ);
18722
20270
  // offset of first CEN header
18723
- b.writeUInt32LE(_offset, Constants.ENDOFF);
20271
+ b.writeUInt32LE(Math.min(_offset, Constants.EF_ZIP64_OR_32), offset + Constants.ENDOFF);
18724
20272
  // zip file comment length
18725
- b.writeUInt16LE(_commentLength, Constants.ENDCOM);
20273
+ b.writeUInt16LE(_commentLength, offset + Constants.ENDCOM);
18726
20274
  // fill comment memory with spaces so no garbage is left there
18727
- b.fill(" ", Constants.ENDHDR);
20275
+ b.fill(" ", offset + Constants.ENDHDR);
18728
20276
 
18729
20277
  return b;
18730
20278
  },
@@ -18756,7 +20304,7 @@ module.exports = function () {
18756
20304
 
18757
20305
  /***/ }),
18758
20306
 
18759
- /***/ 90858:
20307
+ /***/ 49466:
18760
20308
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
18761
20309
 
18762
20310
  module.exports = function (/*Buffer*/ inbuf) {
@@ -18796,20 +20344,20 @@ module.exports = function (/*Buffer*/ inbuf) {
18796
20344
 
18797
20345
  /***/ }),
18798
20346
 
18799
- /***/ 93133:
20347
+ /***/ 70493:
18800
20348
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
18801
20349
 
18802
- exports.Deflater = __nccwpck_require__(90858);
18803
- exports.Inflater = __nccwpck_require__(33938);
18804
- exports.ZipCrypto = __nccwpck_require__(12759);
20350
+ exports.Deflater = __nccwpck_require__(49466);
20351
+ exports.Inflater = __nccwpck_require__(27298);
20352
+ exports.ZipCrypto = __nccwpck_require__(19687);
18805
20353
 
18806
20354
 
18807
20355
  /***/ }),
18808
20356
 
18809
- /***/ 33938:
20357
+ /***/ 27298:
18810
20358
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
18811
20359
 
18812
- const version = +(process.versions ? process.versions.node : "").split(".")[0] || 0;
20360
+ const version = +(process?.versions?.node ?? "").split(".")[0] || 0;
18813
20361
 
18814
20362
  module.exports = function (/*Buffer*/ inbuf, /*number*/ expectedLength) {
18815
20363
  var zlib = __nccwpck_require__(43106);
@@ -18847,7 +20395,7 @@ module.exports = function (/*Buffer*/ inbuf, /*number*/ expectedLength) {
18847
20395
 
18848
20396
  /***/ }),
18849
20397
 
18850
- /***/ 12759:
20398
+ /***/ 19687:
18851
20399
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
18852
20400
 
18853
20401
  "use strict";
@@ -18856,7 +20404,7 @@ module.exports = function (/*Buffer*/ inbuf, /*number*/ expectedLength) {
18856
20404
  // node crypt, we use it for generate salt
18857
20405
  // eslint-disable-next-line node/no-unsupported-features/node-builtins
18858
20406
  const { randomFillSync } = __nccwpck_require__(76982);
18859
- const Errors = __nccwpck_require__(68978);
20407
+ const Errors = __nccwpck_require__(9314);
18860
20408
 
18861
20409
  // generate CRC32 lookup table
18862
20410
  const crctable = new Uint32Array(256).map((t, crc) => {
@@ -19030,7 +20578,7 @@ module.exports = { decrypt, encrypt, _salter };
19030
20578
 
19031
20579
  /***/ }),
19032
20580
 
19033
- /***/ 44436:
20581
+ /***/ 85604:
19034
20582
  /***/ ((module) => {
19035
20583
 
19036
20584
  module.exports = {
@@ -19179,7 +20727,7 @@ module.exports = {
19179
20727
 
19180
20728
  /***/ }),
19181
20729
 
19182
- /***/ 40801:
20730
+ /***/ 95825:
19183
20731
  /***/ ((module) => {
19184
20732
 
19185
20733
  module.exports = {
@@ -19191,7 +20739,7 @@ module.exports = {
19191
20739
 
19192
20740
  /***/ }),
19193
20741
 
19194
- /***/ 68978:
20742
+ /***/ 9314:
19195
20743
  /***/ ((__unused_webpack_module, exports) => {
19196
20744
 
19197
20745
  const errors = {
@@ -19261,7 +20809,7 @@ for (const msg of Object.keys(errors)) {
19261
20809
 
19262
20810
  /***/ }),
19263
20811
 
19264
- /***/ 62328:
20812
+ /***/ 71560:
19265
20813
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
19266
20814
 
19267
20815
  const pth = __nccwpck_require__(16928);
@@ -19344,25 +20892,25 @@ module.exports = function (/*String*/ path, /*Utils object*/ { fs }) {
19344
20892
 
19345
20893
  /***/ }),
19346
20894
 
19347
- /***/ 28605:
20895
+ /***/ 91373:
19348
20896
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
19349
20897
 
19350
- module.exports = __nccwpck_require__(32904);
19351
- module.exports.Constants = __nccwpck_require__(44436);
19352
- module.exports.Errors = __nccwpck_require__(68978);
19353
- module.exports.FileAttr = __nccwpck_require__(62328);
19354
- module.exports.decoder = __nccwpck_require__(40801);
20898
+ module.exports = __nccwpck_require__(12056);
20899
+ module.exports.Constants = __nccwpck_require__(85604);
20900
+ module.exports.Errors = __nccwpck_require__(9314);
20901
+ module.exports.FileAttr = __nccwpck_require__(71560);
20902
+ module.exports.decoder = __nccwpck_require__(95825);
19355
20903
 
19356
20904
 
19357
20905
  /***/ }),
19358
20906
 
19359
- /***/ 32904:
20907
+ /***/ 12056:
19360
20908
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
19361
20909
 
19362
20910
  const fsystem = __nccwpck_require__(79896);
19363
20911
  const pth = __nccwpck_require__(16928);
19364
- const Constants = __nccwpck_require__(44436);
19365
- const Errors = __nccwpck_require__(68978);
20912
+ const Constants = __nccwpck_require__(85604);
20913
+ const Errors = __nccwpck_require__(9314);
19366
20914
  const isWin = typeof process === "object" && "win32" === process.platform;
19367
20915
 
19368
20916
  const is_Obj = (obj) => typeof obj === "object" && obj !== null;
@@ -19410,7 +20958,11 @@ Utils.prototype.makeDir = function (/*String*/ folder) {
19410
20958
  try {
19411
20959
  stat = self.fs.statSync(resolvedPath);
19412
20960
  } catch (e) {
19413
- self.fs.mkdirSync(resolvedPath);
20961
+ if (e.message && e.message.startsWith('ENOENT')) {
20962
+ self.fs.mkdirSync(resolvedPath);
20963
+ } else {
20964
+ throw e;
20965
+ }
19414
20966
  }
19415
20967
  if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
19416
20968
  });
@@ -19464,39 +21016,51 @@ Utils.prototype.writeFileToAsync = function (/*String*/ path, /*Buffer*/ content
19464
21016
  if (exist && !overwrite) return callback(false);
19465
21017
 
19466
21018
  self.fs.stat(path, function (err, stat) {
19467
- if (exist && stat.isDirectory()) {
21019
+ if (exist && stat && stat.isDirectory()) {
19468
21020
  return callback(false);
19469
21021
  }
19470
21022
 
19471
21023
  var folder = pth.dirname(path);
19472
21024
  self.fs.exists(folder, function (exists) {
19473
- if (!exists) self.makeDir(folder);
21025
+ if (!exists) {
21026
+ // makeDir is synchronous and can throw (e.g. EACCES); report failure
21027
+ // rather than letting it escape this callback as an uncaught exception
21028
+ try {
21029
+ self.makeDir(folder);
21030
+ } catch (e) {
21031
+ return callback(false);
21032
+ }
21033
+ }
21034
+
21035
+ // write the content to an open descriptor, then apply the attributes
21036
+ const writeToFd = function (fd) {
21037
+ self.fs.write(fd, content, 0, content.length, 0, function (writeErr) {
21038
+ self.fs.close(fd, function () {
21039
+ // surface write failures instead of silently reporting success (issue #402)
21040
+ if (writeErr) return callback(false);
21041
+ self.fs.chmod(path, attr || 0o666, function () {
21042
+ callback(true);
21043
+ });
21044
+ });
21045
+ });
21046
+ };
19474
21047
 
19475
21048
  self.fs.open(path, "w", 0o666, function (err, fd) {
19476
21049
  if (err) {
21050
+ // the target may exist but be read-only: make it writable and retry once
19477
21051
  self.fs.chmod(path, 0o666, function () {
19478
- self.fs.open(path, "w", 0o666, function (err, fd) {
19479
- self.fs.write(fd, content, 0, content.length, 0, function () {
19480
- self.fs.close(fd, function () {
19481
- self.fs.chmod(path, attr || 0o666, function () {
19482
- callback(true);
19483
- });
19484
- });
19485
- });
21052
+ self.fs.open(path, "w", 0o666, function (retryErr, fd) {
21053
+ // Previously the retry error was ignored and an undefined fd was
21054
+ // passed to fs.write, throwing an uncaught ERR_INVALID_ARG_TYPE that
21055
+ // crashed the process (issues #470, #459, #402). Report failure instead.
21056
+ if (retryErr || !fd) return callback(false);
21057
+ writeToFd(fd);
19486
21058
  });
19487
21059
  });
19488
21060
  } else if (fd) {
19489
- self.fs.write(fd, content, 0, content.length, 0, function () {
19490
- self.fs.close(fd, function () {
19491
- self.fs.chmod(path, attr || 0o666, function () {
19492
- callback(true);
19493
- });
19494
- });
19495
- });
21061
+ writeToFd(fd);
19496
21062
  } else {
19497
- self.fs.chmod(path, attr || 0o666, function () {
19498
- callback(true);
19499
- });
21063
+ callback(false);
19500
21064
  }
19501
21065
  });
19502
21066
  });
@@ -19507,7 +21071,7 @@ Utils.prototype.writeFileToAsync = function (/*String*/ path, /*Buffer*/ content
19507
21071
  Utils.prototype.findFiles = function (/*String*/ path) {
19508
21072
  const self = this;
19509
21073
 
19510
- function findSync(/*String*/ dir, /*RegExp*/ pattern, /*Boolean*/ recursive) {
21074
+ function findSync(/*String*/ dir, /*RegExp*/ pattern, /*Boolean*/ recursive, /*Set*/ visited) {
19511
21075
  if (typeof pattern === "boolean") {
19512
21076
  recursive = pattern;
19513
21077
  pattern = undefined;
@@ -19521,12 +21085,22 @@ Utils.prototype.findFiles = function (/*String*/ path) {
19521
21085
  files.push(pth.normalize(path) + (stat.isDirectory() ? self.sep : ""));
19522
21086
  }
19523
21087
 
19524
- if (stat.isDirectory() && recursive) files = files.concat(findSync(path, pattern, recursive));
21088
+ if (stat.isDirectory() && recursive) {
21089
+ // Descend by resolved real path and skip directories we have already
21090
+ // visited. This stops a symlink that points back to an ancestor from
21091
+ // recursing forever until the path fails with ELOOP / ENAMETOOLONG
21092
+ // (issue #541).
21093
+ const realDir = self.fs.realpathSync(path);
21094
+ if (!visited.has(realDir)) {
21095
+ visited.add(realDir);
21096
+ files = files.concat(findSync(path, pattern, recursive, visited));
21097
+ }
21098
+ }
19525
21099
  });
19526
21100
  return files;
19527
21101
  }
19528
21102
 
19529
- return findSync(path, undefined, true);
21103
+ return findSync(path, undefined, true, new Set([self.fs.realpathSync(path)]));
19530
21104
  };
19531
21105
 
19532
21106
  /**
@@ -19544,29 +21118,54 @@ Utils.prototype.findFiles = function (/*String*/ path) {
19544
21118
  */
19545
21119
  Utils.prototype.findFilesAsync = function (dir, cb) {
19546
21120
  const self = this;
19547
- let results = [];
19548
- self.fs.readdir(dir, function (err, list) {
19549
- if (err) return cb(err);
19550
- let list_length = list.length;
19551
- if (!list_length) return cb(null, results);
19552
- list.forEach(function (file) {
19553
- file = pth.join(dir, file);
19554
- self.fs.stat(file, function (err, stat) {
19555
- if (err) return cb(err);
19556
- if (stat) {
21121
+ const results = [];
21122
+ let finished = false;
21123
+ const finish = function (err) {
21124
+ if (finished) return;
21125
+ finished = true;
21126
+ cb(err, err ? undefined : results);
21127
+ };
21128
+
21129
+ // Descend by resolved real path and skip directories already visited, so a
21130
+ // symlink pointing back to an ancestor cannot recurse forever (issue #541).
21131
+ const walk = function (dir, visited, done) {
21132
+ self.fs.readdir(dir, function (err, list) {
21133
+ if (err) return done(err);
21134
+ let pending = list.length;
21135
+ if (!pending) return done();
21136
+ list.forEach(function (name) {
21137
+ const file = pth.join(dir, name);
21138
+ self.fs.stat(file, function (err, stat) {
21139
+ if (err) return done(err);
21140
+ if (!stat) {
21141
+ if (!--pending) done();
21142
+ return;
21143
+ }
19557
21144
  results.push(pth.normalize(file) + (stat.isDirectory() ? self.sep : ""));
19558
- if (stat.isDirectory()) {
19559
- self.findFilesAsync(file, function (err, res) {
19560
- if (err) return cb(err);
19561
- results = results.concat(res);
19562
- if (!--list_length) cb(null, results);
19563
- });
19564
- } else {
19565
- if (!--list_length) cb(null, results);
21145
+ if (!stat.isDirectory()) {
21146
+ if (!--pending) done();
21147
+ return;
19566
21148
  }
19567
- }
21149
+ self.fs.realpath(file, function (err, realDir) {
21150
+ if (err) return done(err);
21151
+ if (visited.has(realDir)) {
21152
+ if (!--pending) done();
21153
+ return;
21154
+ }
21155
+ visited.add(realDir);
21156
+ walk(file, visited, function (err) {
21157
+ if (err) return done(err);
21158
+ if (!--pending) done();
21159
+ });
21160
+ });
21161
+ });
19568
21162
  });
19569
21163
  });
21164
+ };
21165
+
21166
+ self.fs.realpath(dir, function (err, realDir) {
21167
+ if (err) return finish(err);
21168
+ walk(dir, new Set([realDir]), finish);
19570
21169
  });
19571
21170
  };
19572
21171
 
@@ -19647,13 +21246,13 @@ Utils.findLast = function (arr, callback) {
19647
21246
  return void 0;
19648
21247
  };
19649
21248
 
19650
- // make abolute paths taking prefix as root folder
21249
+ // make absolute paths taking prefix as root folder
19651
21250
  Utils.sanitize = function (/*string*/ prefix, /*string*/ name) {
19652
21251
  prefix = pth.resolve(pth.normalize(prefix));
19653
21252
  var parts = name.split("/");
19654
21253
  for (var i = 0, l = parts.length; i < l; i++) {
19655
21254
  var path = pth.normalize(pth.join(prefix, parts.slice(i, l).join(pth.sep)));
19656
- if (path.indexOf(prefix) === 0) {
21255
+ if (path === prefix || path.startsWith(prefix + pth.sep)) {
19657
21256
  return path;
19658
21257
  }
19659
21258
  }
@@ -19673,10 +21272,16 @@ Utils.toBuffer = function toBuffer(/*buffer, Uint8Array, string*/ input, /* func
19673
21272
  };
19674
21273
 
19675
21274
  Utils.readBigUInt64LE = function (/*Buffer*/ buffer, /*int*/ index) {
19676
- var slice = Buffer.from(buffer.slice(index, index + 8));
19677
- slice.swap64();
21275
+ const lo = buffer.readUInt32LE(index);
21276
+ const hi = buffer.readUInt32LE(index + 4);
21277
+ return hi * 0x100000000 + lo;
21278
+ };
19678
21279
 
19679
- return parseInt(`0x${slice.toString("hex")}`);
21280
+ Utils.writeBigUInt64LE = function (/*Buffer*/ buffer, /*Number*/ value, /*int*/ index) {
21281
+ const lo = value >>> 0;
21282
+ const hi = Math.floor(value / 0x100000000) >>> 0;
21283
+ buffer.writeUInt32LE(lo, index);
21284
+ buffer.writeUInt32LE(hi, index + 4);
19680
21285
  };
19681
21286
 
19682
21287
  Utils.fromDOS2Date = function (val) {
@@ -19699,13 +21304,13 @@ Utils.crcTable = crcTable;
19699
21304
 
19700
21305
  /***/ }),
19701
21306
 
19702
- /***/ 59155:
21307
+ /***/ 58851:
19703
21308
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
19704
21309
 
19705
- var Utils = __nccwpck_require__(28605),
19706
- Headers = __nccwpck_require__(57405),
21310
+ var Utils = __nccwpck_require__(91373),
21311
+ Headers = __nccwpck_require__(77837),
19707
21312
  Constants = Utils.Constants,
19708
- Methods = __nccwpck_require__(93133);
21313
+ Methods = __nccwpck_require__(70493);
19709
21314
 
19710
21315
  module.exports = function (/** object */ options, /*Buffer*/ input) {
19711
21316
  var _centralHeader = new Headers.EntryHeader(),
@@ -19733,48 +21338,23 @@ module.exports = function (/** object */ options, /*Buffer*/ input) {
19733
21338
  }
19734
21339
 
19735
21340
  function crc32OK(data) {
19736
- // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the local header is written
19737
- if (!_centralHeader.flags_desc) {
19738
- if (Utils.crc32(data) !== _centralHeader.localHeader.crc) {
19739
- return false;
19740
- }
19741
- } else {
19742
- const descriptor = {};
19743
- const dataEndOffset = _centralHeader.realDataOffset + _centralHeader.compressedSize;
19744
- // no descriptor after compressed data, instead new local header
19745
- if (input.readUInt32LE(dataEndOffset) == Constants.LOCSIG || input.readUInt32LE(dataEndOffset) == Constants.CENSIG) {
19746
- throw Utils.Errors.DESCRIPTOR_NOT_EXIST();
19747
- }
19748
-
19749
- // get decriptor data
19750
- if (input.readUInt32LE(dataEndOffset) == Constants.EXTSIG) {
19751
- // descriptor with signature
19752
- descriptor.crc = input.readUInt32LE(dataEndOffset + Constants.EXTCRC);
19753
- descriptor.compressedSize = input.readUInt32LE(dataEndOffset + Constants.EXTSIZ);
19754
- descriptor.size = input.readUInt32LE(dataEndOffset + Constants.EXTLEN);
19755
- } else if (input.readUInt16LE(dataEndOffset + 12) === 0x4b50) {
19756
- // descriptor without signature (we check is new header starting where we expect)
19757
- descriptor.crc = input.readUInt32LE(dataEndOffset + Constants.EXTCRC - 4);
19758
- descriptor.compressedSize = input.readUInt32LE(dataEndOffset + Constants.EXTSIZ - 4);
19759
- descriptor.size = input.readUInt32LE(dataEndOffset + Constants.EXTLEN - 4);
19760
- } else {
19761
- throw Utils.Errors.DESCRIPTOR_UNKNOWN();
19762
- }
19763
-
19764
- // check data integrity
19765
- if (descriptor.compressedSize !== _centralHeader.compressedSize || descriptor.size !== _centralHeader.size || descriptor.crc !== _centralHeader.crc) {
19766
- throw Utils.Errors.DESCRIPTOR_FAULTY();
19767
- }
19768
- if (Utils.crc32(data) !== descriptor.crc) {
19769
- return false;
19770
- }
21341
+ // When bit 3 (0x08) of the general-purpose flags is set, the crc-32 and
21342
+ // sizes were unknown when the local file header was written, so that
21343
+ // header carries placeholder zeros and the real values are repeated in a
21344
+ // data descriptor after the compressed data. adm-zip always parses the
21345
+ // central directory, whose header holds the authoritative crc-32 and
21346
+ // sizes, so we validate the payload against that value.
21347
+ //
21348
+ // Earlier versions instead located and parsed the trailing descriptor and
21349
+ // threw when it was absent or in an unexpected shape. Many valid archives
21350
+ // set the descriptor flag but write the real crc/sizes into the local and
21351
+ // central headers without emitting a descriptor (or write one we did not
21352
+ // recognise), so that strict handling rejected readable zips
21353
+ // (issues #533, #548, #554). Trusting the central-directory crc keeps the
21354
+ // integrity check while accepting those archives.
21355
+ const expectedCrc = _centralHeader.flags_desc || _centralHeader.localHeader.flags_desc ? _centralHeader.crc : _centralHeader.localHeader.crc;
19771
21356
 
19772
- // @TODO: zip64 bit descriptor fields
19773
- // if bit 3 is set and any value in local header "zip64 Extended information" extra field are set 0 (place holder)
19774
- // then 64-bit descriptor format is used instead of 32-bit
19775
- // central header - "zip64 Extended information" extra field should store real values and not place holders
19776
- }
19777
- return true;
21357
+ return Utils.crc32(data) === expectedCrc;
19778
21358
  }
19779
21359
 
19780
21360
  function decompress(/*Boolean*/ async, /*Function*/ callback, /*String, Buffer*/ pass) {
@@ -19804,10 +21384,16 @@ module.exports = function (/** object */ options, /*Buffer*/ input) {
19804
21384
  compressedData = Methods.ZipCrypto.decrypt(compressedData, _centralHeader, pass);
19805
21385
  }
19806
21386
 
19807
- var data = Buffer.alloc(_centralHeader.size);
21387
+ var data;
19808
21388
 
19809
21389
  switch (_centralHeader.method) {
19810
21390
  case Utils.Constants.STORED:
21391
+ // STORED entries are not compressed, so the uncompressed output is
21392
+ // exactly the bytes present in the archive. Allocate from the real
21393
+ // data length rather than the attacker-declared central-directory
21394
+ // size, otherwise a tiny archive can declare a huge size and force a
21395
+ // multi-gigabyte allocation before any validation (CVE-2026-39244).
21396
+ data = Buffer.alloc(compressedData.length);
19811
21397
  compressedData.copy(data);
19812
21398
  if (!crc32OK(data)) {
19813
21399
  if (async && callback) callback(data, Utils.Errors.BAD_CRC()); //si added error
@@ -19818,17 +21404,19 @@ module.exports = function (/** object */ options, /*Buffer*/ input) {
19818
21404
  return data;
19819
21405
  }
19820
21406
  case Utils.Constants.DEFLATED:
21407
+ // Do not pre-allocate the declared uncompressed size. The inflater
21408
+ // grows its output buffer as zlib emits data and caps the total at
21409
+ // the declared size (maxOutputLength), so a bogus size can no longer
21410
+ // trigger an eager allocation before the data is read (CVE-2026-39244).
19821
21411
  var inflater = new Methods.Inflater(compressedData, _centralHeader.size);
19822
21412
  if (!async) {
19823
- const result = inflater.inflate(data);
19824
- result.copy(data, 0);
21413
+ data = inflater.inflate();
19825
21414
  if (!crc32OK(data)) {
19826
21415
  throw Utils.Errors.BAD_CRC(`"${decoder.decode(_entryName)}"`);
19827
21416
  }
19828
21417
  return data;
19829
21418
  } else {
19830
21419
  inflater.inflateAsync(function (result) {
19831
- result.copy(result, 0);
19832
21420
  if (callback) {
19833
21421
  if (!crc32OK(result)) {
19834
21422
  callback(result, Utils.Errors.BAD_CRC()); //si added error
@@ -19890,7 +21478,7 @@ module.exports = function (/** object */ options, /*Buffer*/ input) {
19890
21478
  }
19891
21479
 
19892
21480
  function readUInt64LE(buffer, offset) {
19893
- return (buffer.readUInt32LE(offset + 4) << 4) + buffer.readUInt32LE(offset);
21481
+ return Utils.readBigUInt64LE(buffer, offset);
19894
21482
  }
19895
21483
 
19896
21484
  function parseExtra(data) {
@@ -19984,10 +21572,12 @@ module.exports = function (/** object */ options, /*Buffer*/ input) {
19984
21572
  },
19985
21573
 
19986
21574
  get name() {
19987
- var n = decoder.decode(_entryName);
21575
+ const n = decoder.decode(_entryName);
21576
+ // For directories the name is the last path segment; drop the trailing
21577
+ // separator first so "a/b/c/" yields "c" and not "" (issue #466).
19988
21578
  return _isDirectory
19989
21579
  ? n
19990
- .substr(n.length - 1)
21580
+ .replace(/[/\\]$/, "")
19991
21581
  .split("/")
19992
21582
  .pop()
19993
21583
  : n.split("/").pop();
@@ -20111,16 +21701,19 @@ module.exports = function (/** object */ options, /*Buffer*/ input) {
20111
21701
 
20112
21702
  /***/ }),
20113
21703
 
20114
- /***/ 43985:
21704
+ /***/ 46593:
20115
21705
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
20116
21706
 
20117
- const ZipEntry = __nccwpck_require__(59155);
20118
- const Headers = __nccwpck_require__(57405);
20119
- const Utils = __nccwpck_require__(28605);
21707
+ const ZipEntry = __nccwpck_require__(58851);
21708
+ const Headers = __nccwpck_require__(77837);
21709
+ const Utils = __nccwpck_require__(91373);
20120
21710
 
20121
21711
  module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
20122
21712
  var entryList = [],
20123
- entryTable = {},
21713
+ // prototype-less: entry names come from untrusted input, so keys like
21714
+ // "__proto__" or "constructor" must be plain data, not touch the prototype
21715
+ // chain (which otherwise crashes addFile and hides such entries)
21716
+ entryTable = Object.create(null),
20124
21717
  _comment = Buffer.alloc(0),
20125
21718
  mainHeader = new Headers.MainHeader(),
20126
21719
  loadedEntries = false;
@@ -20170,7 +21763,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
20170
21763
 
20171
21764
  function readEntries() {
20172
21765
  loadedEntries = true;
20173
- entryTable = {};
21766
+ entryTable = Object.create(null);
20174
21767
  if (mainHeader.diskEntries > (inBuffer.length - mainHeader.offset) / Utils.Constants.CENHDR) {
20175
21768
  throw Utils.Errors.DISK_ENTRY_TOO_LARGE();
20176
21769
  }
@@ -20247,7 +21840,14 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
20247
21840
 
20248
21841
  function sortEntries() {
20249
21842
  if (entryList.length > 1 && !noSort) {
20250
- entryList.sort((a, b) => a.entryName.toLowerCase().localeCompare(b.entryName.toLowerCase()));
21843
+ // Decode + lowercase each name once rather than on every comparison:
21844
+ // the entryName getter re-decodes the underlying buffer on each access,
21845
+ // so the previous inline comparator did O(n log n) redundant decoding.
21846
+ // Ordering is unchanged (same localeCompare on the same keys).
21847
+ entryList = entryList
21848
+ .map((entry) => ({ entry, key: entry.entryName.toLowerCase() }))
21849
+ .sort((a, b) => a.key.localeCompare(b.key))
21850
+ .map((pair) => pair.entry);
20251
21851
  }
20252
21852
  }
20253
21853
 
@@ -20459,7 +22059,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
20459
22059
  // write main header
20460
22060
  const mh = mainHeader.toBinary();
20461
22061
  if (_comment) {
20462
- _comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment
22062
+ _comment.copy(mh, mh.length - _comment.length); // add zip file comment
20463
22063
  }
20464
22064
  mh.copy(outBuffer, dindex);
20465
22065
 
@@ -20537,7 +22137,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
20537
22137
 
20538
22138
  const mh = mainHeader.toBinary();
20539
22139
  if (_comment) {
20540
- _comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment
22140
+ _comment.copy(mh, mh.length - _comment.length); // add zip file comment
20541
22141
  }
20542
22142
 
20543
22143
  mh.copy(outBuffer, dindex); // write main header
@@ -129501,7 +131101,7 @@ module.exports = {"version":"3.17.0"};
129501
131101
  /***/ ((module) => {
129502
131102
 
129503
131103
  "use strict";
129504
- module.exports = /*#__PURE__*/JSON.parse('{"rE":"0.4.9-canary-2b6b3af.0","h_":"ckb development network for your first try"}');
131104
+ module.exports = /*#__PURE__*/JSON.parse('{"rE":"0.4.9","h_":"ckb development network for your first try"}');
129505
131105
 
129506
131106
  /***/ })
129507
131107