@sip-protocol/cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +508 -23
  2. package/dist/index.mjs +524 -23
  3. package/package.json +8 -6
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_commander9 = require("commander");
27
+ var import_commander12 = require("commander");
28
28
 
29
29
  // src/commands/init.ts
30
30
  var import_commander = require("commander");
@@ -32,25 +32,24 @@ var import_types = require("@sip-protocol/types");
32
32
 
33
33
  // src/utils/config.ts
34
34
  var import_conf = __toESM(require("conf"));
35
- var schema = {
36
- network: {
37
- type: "string",
38
- default: "testnet"
39
- },
40
- defaultPrivacy: {
41
- type: "string",
42
- default: "transparent"
43
- }
44
- };
45
35
  var store = new import_conf.default({
46
36
  projectName: "sip-protocol",
47
- schema
37
+ defaults: {
38
+ network: "testnet",
39
+ defaultPrivacy: "transparent"
40
+ }
48
41
  });
49
- function getConfig() {
42
+ function getConfig(key) {
43
+ if (key) {
44
+ return store.get(key);
45
+ }
50
46
  return {
51
47
  network: store.get("network"),
52
48
  defaultPrivacy: store.get("defaultPrivacy"),
53
49
  defaultChain: store.get("defaultChain"),
50
+ primaryChain: store.get("primaryChain"),
51
+ metaAddress: store.get("metaAddress"),
52
+ viewingKeys: store.get("viewingKeys"),
54
53
  rpcEndpoints: store.get("rpcEndpoints")
55
54
  };
56
55
  }
@@ -115,6 +114,9 @@ function formatHash(hash, length = 8) {
115
114
  if (hash.length <= length * 2) return hash;
116
115
  return `${hash.slice(0, length)}...${hash.slice(-length)}`;
117
116
  }
117
+ function divider() {
118
+ console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
119
+ }
118
120
 
119
121
  // src/commands/init.ts
120
122
  function createInitCommand() {
@@ -356,8 +358,8 @@ function createQuoteCommand() {
356
358
  const quotes = await sip.getQuotes(intent);
357
359
  spin.succeed(`Found ${quotes.length} quote(s)`);
358
360
  if (quotes.length === 0) {
359
- console.log("\nNo quotes available");
360
- return;
361
+ console.error("\nNo quotes available");
362
+ process.exit(1);
361
363
  }
362
364
  console.log();
363
365
  const headers = ["Solver", "Output Amount", "Fee", "Time (s)"];
@@ -385,7 +387,7 @@ function createQuoteCommand() {
385
387
  var import_commander7 = require("commander");
386
388
  var import_sdk6 = require("@sip-protocol/sdk");
387
389
  function createSwapCommand() {
388
- return new import_commander7.Command("swap").description("Execute swap").argument("<from-chain>", "Source chain (e.g., ethereum, solana)").argument("<to-chain>", "Destination chain").argument("<amount>", "Amount to swap").option("-t, --token <symbol>", "Token symbol (default: native token)").option("-p, --privacy <level>", "Privacy level (transparent|shielded|compliant)").option("-r, --recipient <address>", "Recipient address (optional)").option("--solver <id>", "Specific solver to use").action(async (fromChain, toChain, amountStr, options) => {
390
+ return new import_commander7.Command("swap").description("Execute swap").argument("<from-chain>", "Source chain (e.g., ethereum, solana)").argument("<to-chain>", "Destination chain").argument("<amount>", "Amount to swap").option("-t, --token <symbol>", "Token symbol (default: native token)").option("-p, --privacy <level>", "Privacy level (transparent|shielded|compliant)").option("-r, --recipient <address>", "Recipient address (optional)").option("-s, --slippage <percent>", "Slippage tolerance in percent (default: 5)", parseFloat).option("--solver <id>", "Specific solver to use").action(async (fromChain, toChain, amountStr, options) => {
389
391
  try {
390
392
  heading("Execute Swap");
391
393
  const config = getConfig();
@@ -404,7 +406,10 @@ function createSwapCommand() {
404
406
  address: null,
405
407
  decimals: 18
406
408
  };
409
+ const slippagePercent = Math.min(Math.max(options.slippage ?? 5, 0), 100);
410
+ const slippageTolerance = slippagePercent / 100;
407
411
  info("Creating shielded intent...");
412
+ info(`Slippage tolerance: ${slippagePercent}%`);
408
413
  const intent = await sip.createIntent({
409
414
  input: {
410
415
  asset: inputAsset,
@@ -414,8 +419,8 @@ function createSwapCommand() {
414
419
  asset: outputAsset,
415
420
  minAmount: 0n,
416
421
  // Accept any amount
417
- maxSlippage: 0.05
418
- // 5% slippage tolerance
422
+ maxSlippage: slippageTolerance
423
+ // User-configurable slippage
419
424
  },
420
425
  privacy,
421
426
  recipientMetaAddress: options.recipient
@@ -467,9 +472,9 @@ function createScanCommand() {
467
472
  info(`Scanning ${chain} for stealth payments...`);
468
473
  info(`Using ${useEd25519 ? "ed25519" : "secp256k1"} curve`);
469
474
  if (!options.addresses || options.addresses.length === 0) {
470
- warning("No addresses provided. Specify addresses with -a flag.");
471
- info("Example: sip scan -c ethereum -s 0x... -v 0x... -a 0xabc... 0xdef...");
472
- return;
475
+ console.error("No addresses provided. Specify addresses with -a flag.");
476
+ console.error("Example: sip scan -c ethereum -s 0x... -v 0x... -a 0xabc... 0xdef...");
477
+ process.exit(1);
473
478
  }
474
479
  const results = [];
475
480
  for (const address of options.addresses) {
@@ -524,11 +529,491 @@ function createScanCommand() {
524
529
  });
525
530
  }
526
531
 
532
+ // src/commands/setup.ts
533
+ var import_commander9 = require("commander");
534
+ var import_prompts = __toESM(require("prompts"));
535
+ var import_chalk2 = __toESM(require("chalk"));
536
+ var import_ora2 = __toESM(require("ora"));
537
+ var import_sdk8 = require("@sip-protocol/sdk");
538
+ var import_types2 = require("@sip-protocol/types");
539
+ var CHAINS = [
540
+ { title: "Solana", value: "solana", description: "Fast, low-cost transactions" },
541
+ { title: "Ethereum", value: "ethereum", description: "EVM mainnet" },
542
+ { title: "NEAR", value: "near", description: "Sharded, scalable" },
543
+ { title: "Arbitrum", value: "arbitrum", description: "Ethereum L2" },
544
+ { title: "Base", value: "base", description: "Coinbase L2" }
545
+ ];
546
+ var PRIVACY_LEVELS = [
547
+ {
548
+ title: "Transparent",
549
+ value: import_types2.PrivacyLevel.TRANSPARENT,
550
+ description: "No privacy (like standard transactions)"
551
+ },
552
+ {
553
+ title: "Shielded",
554
+ value: import_types2.PrivacyLevel.SHIELDED,
555
+ description: "Full privacy - hidden sender, amount, recipient"
556
+ },
557
+ {
558
+ title: "Compliant",
559
+ value: import_types2.PrivacyLevel.COMPLIANT,
560
+ description: "Privacy with viewing keys for auditors"
561
+ }
562
+ ];
563
+ function createSetupCommand() {
564
+ return new import_commander9.Command("setup").description("Interactive setup wizard for SIP Protocol").option("--quick", "Quick setup with defaults").action(async (options) => {
565
+ console.clear();
566
+ console.log();
567
+ console.log(import_chalk2.default.bold.magenta(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
568
+ console.log(import_chalk2.default.bold.magenta(" \u2551 \u2551"));
569
+ console.log(import_chalk2.default.bold.magenta(" \u2551") + import_chalk2.default.bold.white(" \u{1F6E1}\uFE0F SIP Protocol Setup Wizard \u{1F6E1}\uFE0F ") + import_chalk2.default.bold.magenta("\u2551"));
570
+ console.log(import_chalk2.default.bold.magenta(" \u2551 \u2551"));
571
+ console.log(import_chalk2.default.bold.magenta(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
572
+ console.log();
573
+ console.log(import_chalk2.default.gray(" Privacy layer for cross-chain transactions"));
574
+ console.log();
575
+ if (options.quick) {
576
+ await quickSetup();
577
+ return;
578
+ }
579
+ console.log(import_chalk2.default.cyan.bold(" Step 1 of 4: ") + import_chalk2.default.white("Network Configuration"));
580
+ console.log();
581
+ const networkResponse = await (0, import_prompts.default)([
582
+ {
583
+ type: "select",
584
+ name: "network",
585
+ message: "Which network do you want to use?",
586
+ choices: [
587
+ { title: "Testnet / Devnet", value: "testnet", description: "For development and testing" },
588
+ { title: "Mainnet", value: "mainnet", description: "Production network" }
589
+ ],
590
+ initial: 0
591
+ }
592
+ ]);
593
+ if (!networkResponse.network) {
594
+ console.log(import_chalk2.default.yellow("\n Setup cancelled."));
595
+ return;
596
+ }
597
+ console.log();
598
+ console.log(import_chalk2.default.cyan.bold(" Step 2 of 4: ") + import_chalk2.default.white("Primary Chain"));
599
+ console.log();
600
+ const chainResponse = await (0, import_prompts.default)([
601
+ {
602
+ type: "select",
603
+ name: "chain",
604
+ message: "Which chain will you use primarily?",
605
+ choices: CHAINS,
606
+ initial: 0
607
+ }
608
+ ]);
609
+ if (!chainResponse.chain) {
610
+ console.log(import_chalk2.default.yellow("\n Setup cancelled."));
611
+ return;
612
+ }
613
+ console.log();
614
+ console.log(import_chalk2.default.cyan.bold(" Step 3 of 4: ") + import_chalk2.default.white("Default Privacy Level"));
615
+ console.log();
616
+ const privacyResponse = await (0, import_prompts.default)([
617
+ {
618
+ type: "select",
619
+ name: "privacy",
620
+ message: "What privacy level do you want by default?",
621
+ choices: PRIVACY_LEVELS,
622
+ initial: 1
623
+ // Default to shielded
624
+ }
625
+ ]);
626
+ if (!privacyResponse.privacy) {
627
+ console.log(import_chalk2.default.yellow("\n Setup cancelled."));
628
+ return;
629
+ }
630
+ console.log();
631
+ console.log(import_chalk2.default.cyan.bold(" Step 4 of 4: ") + import_chalk2.default.white("Key Generation"));
632
+ console.log();
633
+ const keyResponse = await (0, import_prompts.default)([
634
+ {
635
+ type: "confirm",
636
+ name: "generateKeys",
637
+ message: "Generate stealth meta-address now?",
638
+ initial: true
639
+ }
640
+ ]);
641
+ const spinner2 = (0, import_ora2.default)("Saving configuration...").start();
642
+ try {
643
+ setConfig("network", networkResponse.network);
644
+ setConfig("primaryChain", chainResponse.chain);
645
+ setConfig("defaultPrivacy", privacyResponse.privacy);
646
+ spinner2.succeed("Configuration saved");
647
+ } catch (err) {
648
+ spinner2.fail("Failed to save configuration");
649
+ console.error(err);
650
+ process.exit(1);
651
+ }
652
+ if (keyResponse.generateKeys) {
653
+ console.log();
654
+ const keySpinner = (0, import_ora2.default)("Generating stealth meta-address...").start();
655
+ try {
656
+ const chain = chainResponse.chain;
657
+ const useEd25519 = (0, import_sdk8.isEd25519Chain)(chain);
658
+ const metaAddress = useEd25519 ? (0, import_sdk8.generateEd25519StealthMetaAddress)(chain) : (0, import_sdk8.generateStealthMetaAddress)(chain);
659
+ keySpinner.succeed("Keys generated");
660
+ console.log();
661
+ console.log(import_chalk2.default.bold.green(" \u2713 Stealth Meta-Address Generated"));
662
+ console.log();
663
+ console.log(import_chalk2.default.gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
664
+ console.log(import_chalk2.default.gray(" \u2502 ") + import_chalk2.default.cyan("Chain: ") + import_chalk2.default.white(chain.padEnd(30)) + import_chalk2.default.gray(" \u2502"));
665
+ console.log(import_chalk2.default.gray(" \u2502 ") + import_chalk2.default.cyan("Curve: ") + import_chalk2.default.white((useEd25519 ? "ed25519" : "secp256k1").padEnd(30)) + import_chalk2.default.gray(" \u2502"));
666
+ console.log(import_chalk2.default.gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
667
+ console.log();
668
+ const encoded = (0, import_sdk8.encodeStealthMetaAddress)(metaAddress.metaAddress);
669
+ console.log(import_chalk2.default.bold(" Encoded Meta-Address (share this):"));
670
+ console.log(import_chalk2.default.green(` ${encoded}`));
671
+ console.log();
672
+ console.log(import_chalk2.default.bold.yellow(" \u26A0\uFE0F PRIVATE KEYS - Store securely!"));
673
+ console.log();
674
+ console.log(import_chalk2.default.gray(" Spending Key: ") + import_chalk2.default.dim(metaAddress.spendingPrivateKey));
675
+ console.log(import_chalk2.default.gray(" Viewing Key: ") + import_chalk2.default.dim(metaAddress.viewingPrivateKey));
676
+ console.log();
677
+ setConfig("metaAddress", encoded);
678
+ } catch (err) {
679
+ keySpinner.fail("Failed to generate keys");
680
+ console.error(err instanceof Error ? err.message : err);
681
+ }
682
+ }
683
+ console.log();
684
+ divider();
685
+ console.log();
686
+ console.log(import_chalk2.default.bold.green(" \u{1F389} Setup Complete!"));
687
+ console.log();
688
+ console.log(import_chalk2.default.gray(" Configuration:"));
689
+ console.log(import_chalk2.default.gray(" \u251C\u2500 Network: ") + import_chalk2.default.white(networkResponse.network));
690
+ console.log(import_chalk2.default.gray(" \u251C\u2500 Chain: ") + import_chalk2.default.white(chainResponse.chain));
691
+ console.log(import_chalk2.default.gray(" \u251C\u2500 Privacy: ") + import_chalk2.default.white(privacyResponse.privacy));
692
+ console.log(import_chalk2.default.gray(" \u2514\u2500 Config: ") + import_chalk2.default.dim(getConfigPath()));
693
+ console.log();
694
+ console.log(import_chalk2.default.cyan(" Next steps:"));
695
+ console.log(import_chalk2.default.gray(" 1. ") + import_chalk2.default.white("sip keygen") + import_chalk2.default.gray(" - Generate more stealth addresses"));
696
+ console.log(import_chalk2.default.gray(" 2. ") + import_chalk2.default.white("sip quote") + import_chalk2.default.gray(" - Get a swap quote"));
697
+ console.log(import_chalk2.default.gray(" 3. ") + import_chalk2.default.white("sip scan") + import_chalk2.default.gray(" - Scan for incoming payments"));
698
+ console.log();
699
+ });
700
+ }
701
+ async function quickSetup() {
702
+ const spinner2 = (0, import_ora2.default)("Quick setup with defaults...").start();
703
+ try {
704
+ setConfig("network", "testnet");
705
+ setConfig("primaryChain", "solana");
706
+ setConfig("defaultPrivacy", import_types2.PrivacyLevel.SHIELDED);
707
+ const metaAddress = (0, import_sdk8.generateEd25519StealthMetaAddress)("solana");
708
+ const encoded = (0, import_sdk8.encodeStealthMetaAddress)(metaAddress.metaAddress);
709
+ setConfig("metaAddress", encoded);
710
+ spinner2.succeed("Quick setup complete");
711
+ console.log();
712
+ console.log(import_chalk2.default.green(" \u2713 Network: testnet"));
713
+ console.log(import_chalk2.default.green(" \u2713 Chain: solana"));
714
+ console.log(import_chalk2.default.green(" \u2713 Privacy: shielded"));
715
+ console.log(import_chalk2.default.green(" \u2713 Keys generated"));
716
+ console.log();
717
+ console.log(import_chalk2.default.bold(" Meta-Address:"));
718
+ console.log(import_chalk2.default.cyan(` ${encoded}`));
719
+ console.log();
720
+ console.log(import_chalk2.default.yellow(" \u26A0\uFE0F Run ") + import_chalk2.default.white("sip setup") + import_chalk2.default.yellow(" for full interactive setup"));
721
+ console.log();
722
+ } catch (err) {
723
+ spinner2.fail("Quick setup failed");
724
+ console.error(err);
725
+ process.exit(1);
726
+ }
727
+ }
728
+
729
+ // src/commands/stealth.ts
730
+ var import_commander10 = require("commander");
731
+ var import_prompts2 = __toESM(require("prompts"));
732
+ var import_chalk3 = __toESM(require("chalk"));
733
+ var import_ora3 = __toESM(require("ora"));
734
+ var import_sdk9 = require("@sip-protocol/sdk");
735
+ function createStealthCommand() {
736
+ const cmd = new import_commander10.Command("stealth").description("Generate one-time stealth addresses");
737
+ cmd.command("generate").alias("gen").description("Generate a one-time stealth address from a meta-address").option("-m, --meta <address>", "Recipient meta-address (or use saved)").option("-c, --chain <chain>", "Target chain (auto-detected from meta-address)").option("-i, --interactive", "Interactive mode").action(async (options) => {
738
+ heading("Generate Stealth Address");
739
+ let metaAddressStr = options.meta;
740
+ if (options.interactive || !metaAddressStr) {
741
+ const savedMeta = getConfig("metaAddress");
742
+ const response = await (0, import_prompts2.default)([
743
+ {
744
+ type: "text",
745
+ name: "meta",
746
+ message: "Enter recipient meta-address:",
747
+ initial: savedMeta || "",
748
+ validate: (value) => value.startsWith("sip:") ? true : "Must be a valid SIP meta-address (sip:...)"
749
+ }
750
+ ]);
751
+ if (!response.meta) {
752
+ console.log(import_chalk3.default.yellow("Cancelled."));
753
+ return;
754
+ }
755
+ metaAddressStr = response.meta;
756
+ }
757
+ const spinner2 = (0, import_ora3.default)("Generating stealth address...").start();
758
+ try {
759
+ const metaAddress = (0, import_sdk9.decodeStealthMetaAddress)(metaAddressStr);
760
+ const chain = metaAddress.chain;
761
+ const useEd25519 = (0, import_sdk9.isEd25519Chain)(chain);
762
+ const result = useEd25519 ? (0, import_sdk9.generateEd25519StealthAddress)(metaAddress) : (0, import_sdk9.generateStealthAddress)(metaAddress);
763
+ const stealthPubKey = result.stealthAddress.address;
764
+ const ephemeralPubKey = result.stealthAddress.ephemeralPublicKey;
765
+ let chainAddress;
766
+ if (useEd25519) {
767
+ if (chain === "solana") {
768
+ chainAddress = (0, import_sdk9.ed25519PublicKeyToSolanaAddress)(stealthPubKey);
769
+ } else if (chain === "near") {
770
+ chainAddress = (0, import_sdk9.ed25519PublicKeyToNearAddress)(stealthPubKey);
771
+ } else {
772
+ chainAddress = stealthPubKey;
773
+ }
774
+ } else {
775
+ chainAddress = (0, import_sdk9.publicKeyToEthAddress)(stealthPubKey);
776
+ }
777
+ spinner2.succeed("Stealth address generated");
778
+ console.log();
779
+ keyValue("Chain", chain);
780
+ keyValue("Curve", useEd25519 ? "ed25519" : "secp256k1");
781
+ console.log();
782
+ console.log(import_chalk3.default.bold.green(" One-Time Address (send funds here):"));
783
+ console.log(import_chalk3.default.cyan(` ${chainAddress}`));
784
+ console.log();
785
+ console.log(import_chalk3.default.bold(" Ephemeral Public Key (publish for recipient):"));
786
+ console.log(import_chalk3.default.gray(` ${ephemeralPubKey}`));
787
+ console.log();
788
+ warning("The ephemeral key must be published so the recipient can find and claim funds.");
789
+ } catch (err) {
790
+ spinner2.fail("Failed to generate stealth address");
791
+ console.error(err instanceof Error ? err.message : err);
792
+ process.exit(1);
793
+ }
794
+ });
795
+ cmd.command("derive").description("Derive spending key from stealth address (to claim funds)").requiredOption("-a, --stealth-address <address>", "Stealth address where funds were sent").requiredOption("-e, --ephemeral <key>", "Ephemeral public key from sender announcement").requiredOption("-s, --spending-key <key>", "Your spending private key (hex)").requiredOption("-v, --viewing-key <key>", "Your viewing private key (hex)").option("-c, --chain <chain>", "Chain (solana, ethereum, near)", "solana").action(async (options) => {
796
+ heading("Derive Stealth Spending Key");
797
+ warning("This is for advanced users. Keep your derived private key secure!");
798
+ console.log();
799
+ const spinner2 = (0, import_ora3.default)("Deriving stealth private key...").start();
800
+ try {
801
+ const chain = options.chain;
802
+ const useEd25519 = (0, import_sdk9.isEd25519Chain)(chain);
803
+ const spendingKey = normalizeHexKey(options.spendingKey);
804
+ const viewingKey = normalizeHexKey(options.viewingKey);
805
+ const ephemeralKey = normalizeHexKey(options.ephemeral);
806
+ let stealthPubKeyHex;
807
+ if (useEd25519 && chain === "solana") {
808
+ stealthPubKeyHex = (0, import_sdk9.solanaAddressToEd25519PublicKey)(options.stealthAddress);
809
+ } else if (options.stealthAddress.startsWith("0x")) {
810
+ stealthPubKeyHex = options.stealthAddress;
811
+ } else {
812
+ throw new Error("Stealth address must be base58 (Solana) or hex (0x...)");
813
+ }
814
+ const stealthAddressObj = {
815
+ address: stealthPubKeyHex,
816
+ ephemeralPublicKey: ephemeralKey,
817
+ viewTag: 0
818
+ // Not needed for derivation
819
+ };
820
+ const recovery = useEd25519 ? (0, import_sdk9.deriveEd25519StealthPrivateKey)(
821
+ stealthAddressObj,
822
+ spendingKey,
823
+ viewingKey
824
+ ) : (0, import_sdk9.deriveStealthPrivateKey)(
825
+ stealthAddressObj,
826
+ spendingKey,
827
+ viewingKey
828
+ );
829
+ spinner2.succeed("Stealth private key derived");
830
+ console.log();
831
+ keyValue("Chain", chain);
832
+ keyValue("Curve", useEd25519 ? "ed25519" : "secp256k1");
833
+ console.log();
834
+ console.log(import_chalk3.default.bold.green(" Derived Private Key (use to claim funds):"));
835
+ console.log(import_chalk3.default.cyan(` ${recovery.privateKey}`));
836
+ console.log();
837
+ console.log(import_chalk3.default.bold(" Stealth Address:"));
838
+ console.log(import_chalk3.default.gray(` ${recovery.stealthAddress}`));
839
+ console.log();
840
+ warning("Never share your private key! Use it to sign transactions claiming your funds.");
841
+ console.log();
842
+ info3("Next steps:");
843
+ console.log(import_chalk3.default.gray(" 1. Import this key into a wallet or use SDK to claim"));
844
+ console.log(import_chalk3.default.gray(" 2. Transfer funds from stealth address to your main wallet"));
845
+ console.log(import_chalk3.default.gray(" 3. Securely delete this private key after claiming"));
846
+ console.log();
847
+ } catch (err) {
848
+ spinner2.fail("Failed to derive stealth key");
849
+ console.error(err instanceof Error ? err.message : err);
850
+ process.exit(1);
851
+ }
852
+ });
853
+ return cmd;
854
+ }
855
+ function info3(message) {
856
+ console.log(import_chalk3.default.blue("\u2139"), message);
857
+ }
858
+ function normalizeHexKey(key) {
859
+ if (key.startsWith("0x")) {
860
+ return key;
861
+ }
862
+ return `0x${key}`;
863
+ }
864
+
865
+ // src/commands/viewing-key.ts
866
+ var import_commander11 = require("commander");
867
+ var import_prompts3 = __toESM(require("prompts"));
868
+ var import_chalk4 = __toESM(require("chalk"));
869
+ var import_ora4 = __toESM(require("ora"));
870
+ var import_sdk10 = require("@sip-protocol/sdk");
871
+ function createViewingKeyCommand() {
872
+ const cmd = new import_commander11.Command("viewing-key").alias("vk").description("Manage viewing keys for selective disclosure");
873
+ cmd.command("generate").alias("gen").description("Generate a new viewing key").option("-p, --path <path>", 'Key derivation path (e.g., "payments/2024")').option("-i, --interactive", "Interactive mode").action(async (options) => {
874
+ heading("Generate Viewing Key");
875
+ let path = options.path;
876
+ if (options.interactive || !path) {
877
+ const response = await (0, import_prompts3.default)([
878
+ {
879
+ type: "text",
880
+ name: "path",
881
+ message: "Enter a label or path for this viewing key:",
882
+ initial: `audit/${Date.now()}`,
883
+ validate: (value) => value.length > 0 ? true : "Path is required"
884
+ },
885
+ {
886
+ type: "text",
887
+ name: "description",
888
+ message: "Description (optional):"
889
+ }
890
+ ]);
891
+ if (!response.path) {
892
+ console.log(import_chalk4.default.yellow("Cancelled."));
893
+ return;
894
+ }
895
+ path = response.path;
896
+ }
897
+ const spinner2 = (0, import_ora4.default)("Generating viewing key...").start();
898
+ try {
899
+ const viewingKey = (0, import_sdk10.generateViewingKey)(path);
900
+ spinner2.succeed("Viewing key generated");
901
+ console.log();
902
+ keyValue("Path", viewingKey.path);
903
+ keyValue("Hash", viewingKey.hash);
904
+ console.log();
905
+ console.log(import_chalk4.default.bold.green(" Viewing Key (share with auditors):"));
906
+ console.log(import_chalk4.default.cyan(` ${viewingKey.key}`));
907
+ console.log();
908
+ console.log(import_chalk4.default.bold(" Key Hash (for verification):"));
909
+ console.log(import_chalk4.default.gray(` ${viewingKey.hash}`));
910
+ console.log();
911
+ warning("Share the viewing key with authorized parties only.");
912
+ console.log(import_chalk4.default.gray(" They can view transactions but cannot spend funds."));
913
+ console.log();
914
+ const saveResponse = await (0, import_prompts3.default)([
915
+ {
916
+ type: "confirm",
917
+ name: "save",
918
+ message: "Save this viewing key to config?",
919
+ initial: false
920
+ }
921
+ ]);
922
+ if (saveResponse.save) {
923
+ const existingKeys = getConfig("viewingKeys") || {};
924
+ existingKeys[path] = viewingKey.key;
925
+ setConfig("viewingKeys", existingKeys);
926
+ success("Viewing key saved to config");
927
+ }
928
+ } catch (err) {
929
+ spinner2.fail("Failed to generate viewing key");
930
+ console.error(err instanceof Error ? err.message : err);
931
+ process.exit(1);
932
+ }
933
+ });
934
+ cmd.command("list").alias("ls").description("List saved viewing keys").action(async () => {
935
+ heading("Saved Viewing Keys");
936
+ const keys = getConfig("viewingKeys") || {};
937
+ const entries = Object.entries(keys);
938
+ if (entries.length === 0) {
939
+ console.log(import_chalk4.default.gray(" No viewing keys saved."));
940
+ console.log(import_chalk4.default.gray(" Run: sip viewing-key generate -i"));
941
+ console.log();
942
+ return;
943
+ }
944
+ console.log();
945
+ entries.forEach(([path, key]) => {
946
+ console.log(import_chalk4.default.cyan(` ${path}`));
947
+ console.log(import_chalk4.default.gray(` ${key.slice(0, 20)}...${key.slice(-10)}`));
948
+ console.log();
949
+ });
950
+ });
951
+ cmd.command("share").description("Create a shareable viewing key disclosure").option("-p, --path <path>", "Viewing key path to share").option("-e, --expires <date>", "Expiration date (ISO format)").option("-s, --scope <scope>", "Scope of disclosure (all, treasury, payments)").action(async (options) => {
952
+ heading("Create Viewing Key Disclosure");
953
+ const keys = getConfig("viewingKeys") || {};
954
+ const entries = Object.entries(keys);
955
+ if (entries.length === 0) {
956
+ console.log(import_chalk4.default.yellow(" No viewing keys saved."));
957
+ console.log(import_chalk4.default.gray(" Run: sip viewing-key generate -i first"));
958
+ console.log();
959
+ return;
960
+ }
961
+ const response = await (0, import_prompts3.default)([
962
+ {
963
+ type: "select",
964
+ name: "path",
965
+ message: "Select viewing key to share:",
966
+ choices: entries.map(([path]) => ({ title: path, value: path }))
967
+ },
968
+ {
969
+ type: "select",
970
+ name: "scope",
971
+ message: "Disclosure scope:",
972
+ choices: [
973
+ { title: "All transactions", value: "all" },
974
+ { title: "Treasury only", value: "treasury" },
975
+ { title: "Payments only", value: "payments" },
976
+ { title: "Custom time range", value: "custom" }
977
+ ]
978
+ },
979
+ {
980
+ type: "text",
981
+ name: "recipient",
982
+ message: "Recipient (auditor, regulator, etc.):"
983
+ }
984
+ ]);
985
+ if (!response.path) {
986
+ console.log(import_chalk4.default.yellow("Cancelled."));
987
+ return;
988
+ }
989
+ const key = keys[response.path];
990
+ console.log();
991
+ console.log(import_chalk4.default.bold.green(" \u{1F4CB} Viewing Key Disclosure"));
992
+ console.log();
993
+ console.log(import_chalk4.default.gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
994
+ console.log(import_chalk4.default.gray(" \u2502 ") + import_chalk4.default.cyan("Path: ") + import_chalk4.default.white(response.path.padEnd(36)) + import_chalk4.default.gray(" \u2502"));
995
+ console.log(import_chalk4.default.gray(" \u2502 ") + import_chalk4.default.cyan("Scope: ") + import_chalk4.default.white(response.scope.padEnd(36)) + import_chalk4.default.gray(" \u2502"));
996
+ console.log(import_chalk4.default.gray(" \u2502 ") + import_chalk4.default.cyan("Recipient: ") + import_chalk4.default.white((response.recipient || "Not specified").padEnd(36)) + import_chalk4.default.gray(" \u2502"));
997
+ console.log(import_chalk4.default.gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
998
+ console.log();
999
+ console.log(import_chalk4.default.bold(" Viewing Key:"));
1000
+ console.log(import_chalk4.default.cyan(` ${key}`));
1001
+ console.log();
1002
+ console.log(import_chalk4.default.gray(" The recipient can use this key to view transactions"));
1003
+ console.log(import_chalk4.default.gray(" matching the specified scope, but cannot spend funds."));
1004
+ console.log();
1005
+ });
1006
+ return cmd;
1007
+ }
1008
+
527
1009
  // src/index.ts
528
- var program = new import_commander9.Command();
529
- program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("0.1.0");
1010
+ var program = new import_commander12.Command();
1011
+ program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("0.2.0");
1012
+ program.addCommand(createSetupCommand());
530
1013
  program.addCommand(createInitCommand());
531
1014
  program.addCommand(createKeygenCommand());
1015
+ program.addCommand(createStealthCommand());
1016
+ program.addCommand(createViewingKeyCommand());
532
1017
  program.addCommand(createCommitCommand());
533
1018
  program.addCommand(createProveCommand());
534
1019
  program.addCommand(createVerifyCommand());
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command9 } from "commander";
4
+ import { Command as Command12 } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import { Command } from "commander";
@@ -9,25 +9,24 @@ import { PrivacyLevel } from "@sip-protocol/types";
9
9
 
10
10
  // src/utils/config.ts
11
11
  import Conf from "conf";
12
- var schema = {
13
- network: {
14
- type: "string",
15
- default: "testnet"
16
- },
17
- defaultPrivacy: {
18
- type: "string",
19
- default: "transparent"
20
- }
21
- };
22
12
  var store = new Conf({
23
13
  projectName: "sip-protocol",
24
- schema
14
+ defaults: {
15
+ network: "testnet",
16
+ defaultPrivacy: "transparent"
17
+ }
25
18
  });
26
- function getConfig() {
19
+ function getConfig(key) {
20
+ if (key) {
21
+ return store.get(key);
22
+ }
27
23
  return {
28
24
  network: store.get("network"),
29
25
  defaultPrivacy: store.get("defaultPrivacy"),
30
26
  defaultChain: store.get("defaultChain"),
27
+ primaryChain: store.get("primaryChain"),
28
+ metaAddress: store.get("metaAddress"),
29
+ viewingKeys: store.get("viewingKeys"),
31
30
  rpcEndpoints: store.get("rpcEndpoints")
32
31
  };
33
32
  }
@@ -92,6 +91,9 @@ function formatHash(hash, length = 8) {
92
91
  if (hash.length <= length * 2) return hash;
93
92
  return `${hash.slice(0, length)}...${hash.slice(-length)}`;
94
93
  }
94
+ function divider() {
95
+ console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
96
+ }
95
97
 
96
98
  // src/commands/init.ts
97
99
  function createInitCommand() {
@@ -338,8 +340,8 @@ function createQuoteCommand() {
338
340
  const quotes = await sip.getQuotes(intent);
339
341
  spin.succeed(`Found ${quotes.length} quote(s)`);
340
342
  if (quotes.length === 0) {
341
- console.log("\nNo quotes available");
342
- return;
343
+ console.error("\nNo quotes available");
344
+ process.exit(1);
343
345
  }
344
346
  console.log();
345
347
  const headers = ["Solver", "Output Amount", "Fee", "Time (s)"];
@@ -367,7 +369,7 @@ function createQuoteCommand() {
367
369
  import { Command as Command7 } from "commander";
368
370
  import { createSIP as createSIP2, NATIVE_TOKENS as NATIVE_TOKENS2 } from "@sip-protocol/sdk";
369
371
  function createSwapCommand() {
370
- return new Command7("swap").description("Execute swap").argument("<from-chain>", "Source chain (e.g., ethereum, solana)").argument("<to-chain>", "Destination chain").argument("<amount>", "Amount to swap").option("-t, --token <symbol>", "Token symbol (default: native token)").option("-p, --privacy <level>", "Privacy level (transparent|shielded|compliant)").option("-r, --recipient <address>", "Recipient address (optional)").option("--solver <id>", "Specific solver to use").action(async (fromChain, toChain, amountStr, options) => {
372
+ return new Command7("swap").description("Execute swap").argument("<from-chain>", "Source chain (e.g., ethereum, solana)").argument("<to-chain>", "Destination chain").argument("<amount>", "Amount to swap").option("-t, --token <symbol>", "Token symbol (default: native token)").option("-p, --privacy <level>", "Privacy level (transparent|shielded|compliant)").option("-r, --recipient <address>", "Recipient address (optional)").option("-s, --slippage <percent>", "Slippage tolerance in percent (default: 5)", parseFloat).option("--solver <id>", "Specific solver to use").action(async (fromChain, toChain, amountStr, options) => {
371
373
  try {
372
374
  heading("Execute Swap");
373
375
  const config = getConfig();
@@ -386,7 +388,10 @@ function createSwapCommand() {
386
388
  address: null,
387
389
  decimals: 18
388
390
  };
391
+ const slippagePercent = Math.min(Math.max(options.slippage ?? 5, 0), 100);
392
+ const slippageTolerance = slippagePercent / 100;
389
393
  info("Creating shielded intent...");
394
+ info(`Slippage tolerance: ${slippagePercent}%`);
390
395
  const intent = await sip.createIntent({
391
396
  input: {
392
397
  asset: inputAsset,
@@ -396,8 +401,8 @@ function createSwapCommand() {
396
401
  asset: outputAsset,
397
402
  minAmount: 0n,
398
403
  // Accept any amount
399
- maxSlippage: 0.05
400
- // 5% slippage tolerance
404
+ maxSlippage: slippageTolerance
405
+ // User-configurable slippage
401
406
  },
402
407
  privacy,
403
408
  recipientMetaAddress: options.recipient
@@ -449,9 +454,9 @@ function createScanCommand() {
449
454
  info(`Scanning ${chain} for stealth payments...`);
450
455
  info(`Using ${useEd25519 ? "ed25519" : "secp256k1"} curve`);
451
456
  if (!options.addresses || options.addresses.length === 0) {
452
- warning("No addresses provided. Specify addresses with -a flag.");
453
- info("Example: sip scan -c ethereum -s 0x... -v 0x... -a 0xabc... 0xdef...");
454
- return;
457
+ console.error("No addresses provided. Specify addresses with -a flag.");
458
+ console.error("Example: sip scan -c ethereum -s 0x... -v 0x... -a 0xabc... 0xdef...");
459
+ process.exit(1);
455
460
  }
456
461
  const results = [];
457
462
  for (const address of options.addresses) {
@@ -506,11 +511,507 @@ function createScanCommand() {
506
511
  });
507
512
  }
508
513
 
514
+ // src/commands/setup.ts
515
+ import { Command as Command9 } from "commander";
516
+ import prompts from "prompts";
517
+ import chalk2 from "chalk";
518
+ import ora2 from "ora";
519
+ import {
520
+ generateStealthMetaAddress as generateStealthMetaAddress2,
521
+ generateEd25519StealthMetaAddress as generateEd25519StealthMetaAddress2,
522
+ encodeStealthMetaAddress as encodeStealthMetaAddress2,
523
+ isEd25519Chain as isEd25519Chain3
524
+ } from "@sip-protocol/sdk";
525
+ import { PrivacyLevel as PrivacyLevel2 } from "@sip-protocol/types";
526
+ var CHAINS = [
527
+ { title: "Solana", value: "solana", description: "Fast, low-cost transactions" },
528
+ { title: "Ethereum", value: "ethereum", description: "EVM mainnet" },
529
+ { title: "NEAR", value: "near", description: "Sharded, scalable" },
530
+ { title: "Arbitrum", value: "arbitrum", description: "Ethereum L2" },
531
+ { title: "Base", value: "base", description: "Coinbase L2" }
532
+ ];
533
+ var PRIVACY_LEVELS = [
534
+ {
535
+ title: "Transparent",
536
+ value: PrivacyLevel2.TRANSPARENT,
537
+ description: "No privacy (like standard transactions)"
538
+ },
539
+ {
540
+ title: "Shielded",
541
+ value: PrivacyLevel2.SHIELDED,
542
+ description: "Full privacy - hidden sender, amount, recipient"
543
+ },
544
+ {
545
+ title: "Compliant",
546
+ value: PrivacyLevel2.COMPLIANT,
547
+ description: "Privacy with viewing keys for auditors"
548
+ }
549
+ ];
550
+ function createSetupCommand() {
551
+ return new Command9("setup").description("Interactive setup wizard for SIP Protocol").option("--quick", "Quick setup with defaults").action(async (options) => {
552
+ console.clear();
553
+ console.log();
554
+ console.log(chalk2.bold.magenta(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
555
+ console.log(chalk2.bold.magenta(" \u2551 \u2551"));
556
+ console.log(chalk2.bold.magenta(" \u2551") + chalk2.bold.white(" \u{1F6E1}\uFE0F SIP Protocol Setup Wizard \u{1F6E1}\uFE0F ") + chalk2.bold.magenta("\u2551"));
557
+ console.log(chalk2.bold.magenta(" \u2551 \u2551"));
558
+ console.log(chalk2.bold.magenta(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
559
+ console.log();
560
+ console.log(chalk2.gray(" Privacy layer for cross-chain transactions"));
561
+ console.log();
562
+ if (options.quick) {
563
+ await quickSetup();
564
+ return;
565
+ }
566
+ console.log(chalk2.cyan.bold(" Step 1 of 4: ") + chalk2.white("Network Configuration"));
567
+ console.log();
568
+ const networkResponse = await prompts([
569
+ {
570
+ type: "select",
571
+ name: "network",
572
+ message: "Which network do you want to use?",
573
+ choices: [
574
+ { title: "Testnet / Devnet", value: "testnet", description: "For development and testing" },
575
+ { title: "Mainnet", value: "mainnet", description: "Production network" }
576
+ ],
577
+ initial: 0
578
+ }
579
+ ]);
580
+ if (!networkResponse.network) {
581
+ console.log(chalk2.yellow("\n Setup cancelled."));
582
+ return;
583
+ }
584
+ console.log();
585
+ console.log(chalk2.cyan.bold(" Step 2 of 4: ") + chalk2.white("Primary Chain"));
586
+ console.log();
587
+ const chainResponse = await prompts([
588
+ {
589
+ type: "select",
590
+ name: "chain",
591
+ message: "Which chain will you use primarily?",
592
+ choices: CHAINS,
593
+ initial: 0
594
+ }
595
+ ]);
596
+ if (!chainResponse.chain) {
597
+ console.log(chalk2.yellow("\n Setup cancelled."));
598
+ return;
599
+ }
600
+ console.log();
601
+ console.log(chalk2.cyan.bold(" Step 3 of 4: ") + chalk2.white("Default Privacy Level"));
602
+ console.log();
603
+ const privacyResponse = await prompts([
604
+ {
605
+ type: "select",
606
+ name: "privacy",
607
+ message: "What privacy level do you want by default?",
608
+ choices: PRIVACY_LEVELS,
609
+ initial: 1
610
+ // Default to shielded
611
+ }
612
+ ]);
613
+ if (!privacyResponse.privacy) {
614
+ console.log(chalk2.yellow("\n Setup cancelled."));
615
+ return;
616
+ }
617
+ console.log();
618
+ console.log(chalk2.cyan.bold(" Step 4 of 4: ") + chalk2.white("Key Generation"));
619
+ console.log();
620
+ const keyResponse = await prompts([
621
+ {
622
+ type: "confirm",
623
+ name: "generateKeys",
624
+ message: "Generate stealth meta-address now?",
625
+ initial: true
626
+ }
627
+ ]);
628
+ const spinner2 = ora2("Saving configuration...").start();
629
+ try {
630
+ setConfig("network", networkResponse.network);
631
+ setConfig("primaryChain", chainResponse.chain);
632
+ setConfig("defaultPrivacy", privacyResponse.privacy);
633
+ spinner2.succeed("Configuration saved");
634
+ } catch (err) {
635
+ spinner2.fail("Failed to save configuration");
636
+ console.error(err);
637
+ process.exit(1);
638
+ }
639
+ if (keyResponse.generateKeys) {
640
+ console.log();
641
+ const keySpinner = ora2("Generating stealth meta-address...").start();
642
+ try {
643
+ const chain = chainResponse.chain;
644
+ const useEd25519 = isEd25519Chain3(chain);
645
+ const metaAddress = useEd25519 ? generateEd25519StealthMetaAddress2(chain) : generateStealthMetaAddress2(chain);
646
+ keySpinner.succeed("Keys generated");
647
+ console.log();
648
+ console.log(chalk2.bold.green(" \u2713 Stealth Meta-Address Generated"));
649
+ console.log();
650
+ console.log(chalk2.gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
651
+ console.log(chalk2.gray(" \u2502 ") + chalk2.cyan("Chain: ") + chalk2.white(chain.padEnd(30)) + chalk2.gray(" \u2502"));
652
+ console.log(chalk2.gray(" \u2502 ") + chalk2.cyan("Curve: ") + chalk2.white((useEd25519 ? "ed25519" : "secp256k1").padEnd(30)) + chalk2.gray(" \u2502"));
653
+ console.log(chalk2.gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
654
+ console.log();
655
+ const encoded = encodeStealthMetaAddress2(metaAddress.metaAddress);
656
+ console.log(chalk2.bold(" Encoded Meta-Address (share this):"));
657
+ console.log(chalk2.green(` ${encoded}`));
658
+ console.log();
659
+ console.log(chalk2.bold.yellow(" \u26A0\uFE0F PRIVATE KEYS - Store securely!"));
660
+ console.log();
661
+ console.log(chalk2.gray(" Spending Key: ") + chalk2.dim(metaAddress.spendingPrivateKey));
662
+ console.log(chalk2.gray(" Viewing Key: ") + chalk2.dim(metaAddress.viewingPrivateKey));
663
+ console.log();
664
+ setConfig("metaAddress", encoded);
665
+ } catch (err) {
666
+ keySpinner.fail("Failed to generate keys");
667
+ console.error(err instanceof Error ? err.message : err);
668
+ }
669
+ }
670
+ console.log();
671
+ divider();
672
+ console.log();
673
+ console.log(chalk2.bold.green(" \u{1F389} Setup Complete!"));
674
+ console.log();
675
+ console.log(chalk2.gray(" Configuration:"));
676
+ console.log(chalk2.gray(" \u251C\u2500 Network: ") + chalk2.white(networkResponse.network));
677
+ console.log(chalk2.gray(" \u251C\u2500 Chain: ") + chalk2.white(chainResponse.chain));
678
+ console.log(chalk2.gray(" \u251C\u2500 Privacy: ") + chalk2.white(privacyResponse.privacy));
679
+ console.log(chalk2.gray(" \u2514\u2500 Config: ") + chalk2.dim(getConfigPath()));
680
+ console.log();
681
+ console.log(chalk2.cyan(" Next steps:"));
682
+ console.log(chalk2.gray(" 1. ") + chalk2.white("sip keygen") + chalk2.gray(" - Generate more stealth addresses"));
683
+ console.log(chalk2.gray(" 2. ") + chalk2.white("sip quote") + chalk2.gray(" - Get a swap quote"));
684
+ console.log(chalk2.gray(" 3. ") + chalk2.white("sip scan") + chalk2.gray(" - Scan for incoming payments"));
685
+ console.log();
686
+ });
687
+ }
688
+ async function quickSetup() {
689
+ const spinner2 = ora2("Quick setup with defaults...").start();
690
+ try {
691
+ setConfig("network", "testnet");
692
+ setConfig("primaryChain", "solana");
693
+ setConfig("defaultPrivacy", PrivacyLevel2.SHIELDED);
694
+ const metaAddress = generateEd25519StealthMetaAddress2("solana");
695
+ const encoded = encodeStealthMetaAddress2(metaAddress.metaAddress);
696
+ setConfig("metaAddress", encoded);
697
+ spinner2.succeed("Quick setup complete");
698
+ console.log();
699
+ console.log(chalk2.green(" \u2713 Network: testnet"));
700
+ console.log(chalk2.green(" \u2713 Chain: solana"));
701
+ console.log(chalk2.green(" \u2713 Privacy: shielded"));
702
+ console.log(chalk2.green(" \u2713 Keys generated"));
703
+ console.log();
704
+ console.log(chalk2.bold(" Meta-Address:"));
705
+ console.log(chalk2.cyan(` ${encoded}`));
706
+ console.log();
707
+ console.log(chalk2.yellow(" \u26A0\uFE0F Run ") + chalk2.white("sip setup") + chalk2.yellow(" for full interactive setup"));
708
+ console.log();
709
+ } catch (err) {
710
+ spinner2.fail("Quick setup failed");
711
+ console.error(err);
712
+ process.exit(1);
713
+ }
714
+ }
715
+
716
+ // src/commands/stealth.ts
717
+ import { Command as Command10 } from "commander";
718
+ import prompts2 from "prompts";
719
+ import chalk3 from "chalk";
720
+ import ora3 from "ora";
721
+ import {
722
+ generateStealthAddress,
723
+ generateEd25519StealthAddress,
724
+ decodeStealthMetaAddress,
725
+ isEd25519Chain as isEd25519Chain4,
726
+ ed25519PublicKeyToSolanaAddress,
727
+ ed25519PublicKeyToNearAddress,
728
+ publicKeyToEthAddress,
729
+ deriveStealthPrivateKey,
730
+ deriveEd25519StealthPrivateKey,
731
+ solanaAddressToEd25519PublicKey
732
+ } from "@sip-protocol/sdk";
733
+ function createStealthCommand() {
734
+ const cmd = new Command10("stealth").description("Generate one-time stealth addresses");
735
+ cmd.command("generate").alias("gen").description("Generate a one-time stealth address from a meta-address").option("-m, --meta <address>", "Recipient meta-address (or use saved)").option("-c, --chain <chain>", "Target chain (auto-detected from meta-address)").option("-i, --interactive", "Interactive mode").action(async (options) => {
736
+ heading("Generate Stealth Address");
737
+ let metaAddressStr = options.meta;
738
+ if (options.interactive || !metaAddressStr) {
739
+ const savedMeta = getConfig("metaAddress");
740
+ const response = await prompts2([
741
+ {
742
+ type: "text",
743
+ name: "meta",
744
+ message: "Enter recipient meta-address:",
745
+ initial: savedMeta || "",
746
+ validate: (value) => value.startsWith("sip:") ? true : "Must be a valid SIP meta-address (sip:...)"
747
+ }
748
+ ]);
749
+ if (!response.meta) {
750
+ console.log(chalk3.yellow("Cancelled."));
751
+ return;
752
+ }
753
+ metaAddressStr = response.meta;
754
+ }
755
+ const spinner2 = ora3("Generating stealth address...").start();
756
+ try {
757
+ const metaAddress = decodeStealthMetaAddress(metaAddressStr);
758
+ const chain = metaAddress.chain;
759
+ const useEd25519 = isEd25519Chain4(chain);
760
+ const result = useEd25519 ? generateEd25519StealthAddress(metaAddress) : generateStealthAddress(metaAddress);
761
+ const stealthPubKey = result.stealthAddress.address;
762
+ const ephemeralPubKey = result.stealthAddress.ephemeralPublicKey;
763
+ let chainAddress;
764
+ if (useEd25519) {
765
+ if (chain === "solana") {
766
+ chainAddress = ed25519PublicKeyToSolanaAddress(stealthPubKey);
767
+ } else if (chain === "near") {
768
+ chainAddress = ed25519PublicKeyToNearAddress(stealthPubKey);
769
+ } else {
770
+ chainAddress = stealthPubKey;
771
+ }
772
+ } else {
773
+ chainAddress = publicKeyToEthAddress(stealthPubKey);
774
+ }
775
+ spinner2.succeed("Stealth address generated");
776
+ console.log();
777
+ keyValue("Chain", chain);
778
+ keyValue("Curve", useEd25519 ? "ed25519" : "secp256k1");
779
+ console.log();
780
+ console.log(chalk3.bold.green(" One-Time Address (send funds here):"));
781
+ console.log(chalk3.cyan(` ${chainAddress}`));
782
+ console.log();
783
+ console.log(chalk3.bold(" Ephemeral Public Key (publish for recipient):"));
784
+ console.log(chalk3.gray(` ${ephemeralPubKey}`));
785
+ console.log();
786
+ warning("The ephemeral key must be published so the recipient can find and claim funds.");
787
+ } catch (err) {
788
+ spinner2.fail("Failed to generate stealth address");
789
+ console.error(err instanceof Error ? err.message : err);
790
+ process.exit(1);
791
+ }
792
+ });
793
+ cmd.command("derive").description("Derive spending key from stealth address (to claim funds)").requiredOption("-a, --stealth-address <address>", "Stealth address where funds were sent").requiredOption("-e, --ephemeral <key>", "Ephemeral public key from sender announcement").requiredOption("-s, --spending-key <key>", "Your spending private key (hex)").requiredOption("-v, --viewing-key <key>", "Your viewing private key (hex)").option("-c, --chain <chain>", "Chain (solana, ethereum, near)", "solana").action(async (options) => {
794
+ heading("Derive Stealth Spending Key");
795
+ warning("This is for advanced users. Keep your derived private key secure!");
796
+ console.log();
797
+ const spinner2 = ora3("Deriving stealth private key...").start();
798
+ try {
799
+ const chain = options.chain;
800
+ const useEd25519 = isEd25519Chain4(chain);
801
+ const spendingKey = normalizeHexKey(options.spendingKey);
802
+ const viewingKey = normalizeHexKey(options.viewingKey);
803
+ const ephemeralKey = normalizeHexKey(options.ephemeral);
804
+ let stealthPubKeyHex;
805
+ if (useEd25519 && chain === "solana") {
806
+ stealthPubKeyHex = solanaAddressToEd25519PublicKey(options.stealthAddress);
807
+ } else if (options.stealthAddress.startsWith("0x")) {
808
+ stealthPubKeyHex = options.stealthAddress;
809
+ } else {
810
+ throw new Error("Stealth address must be base58 (Solana) or hex (0x...)");
811
+ }
812
+ const stealthAddressObj = {
813
+ address: stealthPubKeyHex,
814
+ ephemeralPublicKey: ephemeralKey,
815
+ viewTag: 0
816
+ // Not needed for derivation
817
+ };
818
+ const recovery = useEd25519 ? deriveEd25519StealthPrivateKey(
819
+ stealthAddressObj,
820
+ spendingKey,
821
+ viewingKey
822
+ ) : deriveStealthPrivateKey(
823
+ stealthAddressObj,
824
+ spendingKey,
825
+ viewingKey
826
+ );
827
+ spinner2.succeed("Stealth private key derived");
828
+ console.log();
829
+ keyValue("Chain", chain);
830
+ keyValue("Curve", useEd25519 ? "ed25519" : "secp256k1");
831
+ console.log();
832
+ console.log(chalk3.bold.green(" Derived Private Key (use to claim funds):"));
833
+ console.log(chalk3.cyan(` ${recovery.privateKey}`));
834
+ console.log();
835
+ console.log(chalk3.bold(" Stealth Address:"));
836
+ console.log(chalk3.gray(` ${recovery.stealthAddress}`));
837
+ console.log();
838
+ warning("Never share your private key! Use it to sign transactions claiming your funds.");
839
+ console.log();
840
+ info3("Next steps:");
841
+ console.log(chalk3.gray(" 1. Import this key into a wallet or use SDK to claim"));
842
+ console.log(chalk3.gray(" 2. Transfer funds from stealth address to your main wallet"));
843
+ console.log(chalk3.gray(" 3. Securely delete this private key after claiming"));
844
+ console.log();
845
+ } catch (err) {
846
+ spinner2.fail("Failed to derive stealth key");
847
+ console.error(err instanceof Error ? err.message : err);
848
+ process.exit(1);
849
+ }
850
+ });
851
+ return cmd;
852
+ }
853
+ function info3(message) {
854
+ console.log(chalk3.blue("\u2139"), message);
855
+ }
856
+ function normalizeHexKey(key) {
857
+ if (key.startsWith("0x")) {
858
+ return key;
859
+ }
860
+ return `0x${key}`;
861
+ }
862
+
863
+ // src/commands/viewing-key.ts
864
+ import { Command as Command11 } from "commander";
865
+ import prompts3 from "prompts";
866
+ import chalk4 from "chalk";
867
+ import ora4 from "ora";
868
+ import { generateViewingKey } from "@sip-protocol/sdk";
869
+ function createViewingKeyCommand() {
870
+ const cmd = new Command11("viewing-key").alias("vk").description("Manage viewing keys for selective disclosure");
871
+ cmd.command("generate").alias("gen").description("Generate a new viewing key").option("-p, --path <path>", 'Key derivation path (e.g., "payments/2024")').option("-i, --interactive", "Interactive mode").action(async (options) => {
872
+ heading("Generate Viewing Key");
873
+ let path = options.path;
874
+ if (options.interactive || !path) {
875
+ const response = await prompts3([
876
+ {
877
+ type: "text",
878
+ name: "path",
879
+ message: "Enter a label or path for this viewing key:",
880
+ initial: `audit/${Date.now()}`,
881
+ validate: (value) => value.length > 0 ? true : "Path is required"
882
+ },
883
+ {
884
+ type: "text",
885
+ name: "description",
886
+ message: "Description (optional):"
887
+ }
888
+ ]);
889
+ if (!response.path) {
890
+ console.log(chalk4.yellow("Cancelled."));
891
+ return;
892
+ }
893
+ path = response.path;
894
+ }
895
+ const spinner2 = ora4("Generating viewing key...").start();
896
+ try {
897
+ const viewingKey = generateViewingKey(path);
898
+ spinner2.succeed("Viewing key generated");
899
+ console.log();
900
+ keyValue("Path", viewingKey.path);
901
+ keyValue("Hash", viewingKey.hash);
902
+ console.log();
903
+ console.log(chalk4.bold.green(" Viewing Key (share with auditors):"));
904
+ console.log(chalk4.cyan(` ${viewingKey.key}`));
905
+ console.log();
906
+ console.log(chalk4.bold(" Key Hash (for verification):"));
907
+ console.log(chalk4.gray(` ${viewingKey.hash}`));
908
+ console.log();
909
+ warning("Share the viewing key with authorized parties only.");
910
+ console.log(chalk4.gray(" They can view transactions but cannot spend funds."));
911
+ console.log();
912
+ const saveResponse = await prompts3([
913
+ {
914
+ type: "confirm",
915
+ name: "save",
916
+ message: "Save this viewing key to config?",
917
+ initial: false
918
+ }
919
+ ]);
920
+ if (saveResponse.save) {
921
+ const existingKeys = getConfig("viewingKeys") || {};
922
+ existingKeys[path] = viewingKey.key;
923
+ setConfig("viewingKeys", existingKeys);
924
+ success("Viewing key saved to config");
925
+ }
926
+ } catch (err) {
927
+ spinner2.fail("Failed to generate viewing key");
928
+ console.error(err instanceof Error ? err.message : err);
929
+ process.exit(1);
930
+ }
931
+ });
932
+ cmd.command("list").alias("ls").description("List saved viewing keys").action(async () => {
933
+ heading("Saved Viewing Keys");
934
+ const keys = getConfig("viewingKeys") || {};
935
+ const entries = Object.entries(keys);
936
+ if (entries.length === 0) {
937
+ console.log(chalk4.gray(" No viewing keys saved."));
938
+ console.log(chalk4.gray(" Run: sip viewing-key generate -i"));
939
+ console.log();
940
+ return;
941
+ }
942
+ console.log();
943
+ entries.forEach(([path, key]) => {
944
+ console.log(chalk4.cyan(` ${path}`));
945
+ console.log(chalk4.gray(` ${key.slice(0, 20)}...${key.slice(-10)}`));
946
+ console.log();
947
+ });
948
+ });
949
+ cmd.command("share").description("Create a shareable viewing key disclosure").option("-p, --path <path>", "Viewing key path to share").option("-e, --expires <date>", "Expiration date (ISO format)").option("-s, --scope <scope>", "Scope of disclosure (all, treasury, payments)").action(async (options) => {
950
+ heading("Create Viewing Key Disclosure");
951
+ const keys = getConfig("viewingKeys") || {};
952
+ const entries = Object.entries(keys);
953
+ if (entries.length === 0) {
954
+ console.log(chalk4.yellow(" No viewing keys saved."));
955
+ console.log(chalk4.gray(" Run: sip viewing-key generate -i first"));
956
+ console.log();
957
+ return;
958
+ }
959
+ const response = await prompts3([
960
+ {
961
+ type: "select",
962
+ name: "path",
963
+ message: "Select viewing key to share:",
964
+ choices: entries.map(([path]) => ({ title: path, value: path }))
965
+ },
966
+ {
967
+ type: "select",
968
+ name: "scope",
969
+ message: "Disclosure scope:",
970
+ choices: [
971
+ { title: "All transactions", value: "all" },
972
+ { title: "Treasury only", value: "treasury" },
973
+ { title: "Payments only", value: "payments" },
974
+ { title: "Custom time range", value: "custom" }
975
+ ]
976
+ },
977
+ {
978
+ type: "text",
979
+ name: "recipient",
980
+ message: "Recipient (auditor, regulator, etc.):"
981
+ }
982
+ ]);
983
+ if (!response.path) {
984
+ console.log(chalk4.yellow("Cancelled."));
985
+ return;
986
+ }
987
+ const key = keys[response.path];
988
+ console.log();
989
+ console.log(chalk4.bold.green(" \u{1F4CB} Viewing Key Disclosure"));
990
+ console.log();
991
+ console.log(chalk4.gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
992
+ console.log(chalk4.gray(" \u2502 ") + chalk4.cyan("Path: ") + chalk4.white(response.path.padEnd(36)) + chalk4.gray(" \u2502"));
993
+ console.log(chalk4.gray(" \u2502 ") + chalk4.cyan("Scope: ") + chalk4.white(response.scope.padEnd(36)) + chalk4.gray(" \u2502"));
994
+ console.log(chalk4.gray(" \u2502 ") + chalk4.cyan("Recipient: ") + chalk4.white((response.recipient || "Not specified").padEnd(36)) + chalk4.gray(" \u2502"));
995
+ console.log(chalk4.gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
996
+ console.log();
997
+ console.log(chalk4.bold(" Viewing Key:"));
998
+ console.log(chalk4.cyan(` ${key}`));
999
+ console.log();
1000
+ console.log(chalk4.gray(" The recipient can use this key to view transactions"));
1001
+ console.log(chalk4.gray(" matching the specified scope, but cannot spend funds."));
1002
+ console.log();
1003
+ });
1004
+ return cmd;
1005
+ }
1006
+
509
1007
  // src/index.ts
510
- var program = new Command9();
511
- program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("0.1.0");
1008
+ var program = new Command12();
1009
+ program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("0.2.0");
1010
+ program.addCommand(createSetupCommand());
512
1011
  program.addCommand(createInitCommand());
513
1012
  program.addCommand(createKeygenCommand());
1013
+ program.addCommand(createStealthCommand());
1014
+ program.addCommand(createViewingKeyCommand());
514
1015
  program.addCommand(createCommitCommand());
515
1016
  program.addCommand(createProveCommand());
516
1017
  program.addCommand(createVerifyCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sip-protocol/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Command-line tool for Shielded Intents Protocol (SIP) operations",
5
5
  "author": "SIP Protocol <hello@sip-protocol.org>",
6
6
  "homepage": "https://sip-protocol.org",
@@ -23,15 +23,17 @@
23
23
  "dist"
24
24
  ],
25
25
  "dependencies": {
26
- "@sip-protocol/sdk": "^0.6.0",
27
- "@sip-protocol/types": "^0.2.0",
28
- "commander": "^12.0.0",
29
26
  "chalk": "^4.1.2",
27
+ "commander": "^12.0.0",
28
+ "conf": "^10.2.0",
30
29
  "ora": "^5.4.1",
31
- "conf": "^10.2.0"
30
+ "prompts": "^2.4.2",
31
+ "@sip-protocol/sdk": "0.7.2",
32
+ "@sip-protocol/types": "0.2.1"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^20.10.0",
36
+ "@types/prompts": "^2.4.9",
35
37
  "tsup": "^8.0.0",
36
38
  "typescript": "^5.3.0",
37
39
  "vitest": "^1.1.0"
@@ -52,6 +54,6 @@
52
54
  "lint": "eslint --ext .ts src/",
53
55
  "typecheck": "tsc --noEmit",
54
56
  "clean": "rm -rf dist",
55
- "test": "vitest --passWithNoTests"
57
+ "test": "vitest"
56
58
  }
57
59
  }