@sip-protocol/cli 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1402 -53
  2. package/dist/index.mjs +1436 -54
  3. package/package.json +14 -10
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 Command14 } 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
  }
@@ -64,6 +63,13 @@ ${message}
64
63
  function keyValue(key, value) {
65
64
  console.log(chalk.gray(` ${key}:`), chalk.white(String(value)));
66
65
  }
66
+ function json(data) {
67
+ console.log(JSON.stringify(
68
+ data,
69
+ (_, value) => typeof value === "bigint" ? value.toString() : value,
70
+ 2
71
+ ));
72
+ }
67
73
  function spinner(text) {
68
74
  return ora(text).start();
69
75
  }
@@ -92,6 +98,9 @@ function formatHash(hash, length = 8) {
92
98
  if (hash.length <= length * 2) return hash;
93
99
  return `${hash.slice(0, length)}...${hash.slice(-length)}`;
94
100
  }
101
+ function divider() {
102
+ 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"));
103
+ }
95
104
 
96
105
  // src/commands/init.ts
97
106
  function createInitCommand() {
@@ -127,35 +136,85 @@ import {
127
136
  isEd25519Chain,
128
137
  encodeStealthMetaAddress
129
138
  } from "@sip-protocol/sdk";
139
+ import * as fs from "fs";
140
+ import * as path from "path";
141
+ function formatKeysAsText(data) {
142
+ return [
143
+ `# SIP Stealth Meta-Address Keys`,
144
+ `# Generated: ${data.generatedAt}`,
145
+ `# Chain: ${data.chain}`,
146
+ ``,
147
+ `## Public Keys (safe to share)`,
148
+ `SPENDING_PUBLIC_KEY=${data.spendingPublicKey}`,
149
+ `VIEWING_PUBLIC_KEY=${data.viewingPublicKey}`,
150
+ `META_ADDRESS=${data.metaAddress}`,
151
+ ``,
152
+ `## Private Keys (KEEP SECRET!)`,
153
+ `SPENDING_PRIVATE_KEY=${data.spendingPrivateKey}`,
154
+ `VIEWING_PRIVATE_KEY=${data.viewingPrivateKey}`,
155
+ ``,
156
+ `# WARNING: Delete this file after securely storing the keys!`
157
+ ].join("\n");
158
+ }
130
159
  function createKeygenCommand() {
131
- return new Command2("keygen").description("Generate stealth meta-address").option("-c, --chain <chain>", "Target chain (ethereum, solana, near)", "ethereum").option("--spending-key <key>", "Spending private key (hex)").option("--viewing-key <key>", "Viewing private key (hex)").action(async (options) => {
160
+ return new Command2("keygen").description("Generate stealth meta-address").option("-c, --chain <chain>", "Target chain (ethereum, solana, near)", "ethereum").option("--spending-key <key>", "Spending private key (hex)").option("--viewing-key <key>", "Viewing private key (hex)").option("-o, --output-file <path>", "Output file for keys (enables secure export)").option("-f, --format <format>", "Output format: json or text", "json").action(async (options) => {
132
161
  try {
133
162
  heading("Generate Stealth Meta-Address");
134
163
  const chain = options.chain;
135
164
  const useEd25519 = isEd25519Chain(chain);
136
- let metaAddress;
165
+ const format = options.format || "json";
166
+ let result;
137
167
  if (useEd25519) {
138
168
  if (options.spendingKey || options.viewingKey) {
139
169
  warning("Ed25519 chains do not support custom keys in CLI yet");
140
170
  }
141
- metaAddress = generateEd25519StealthMetaAddress(chain);
171
+ result = generateEd25519StealthMetaAddress(chain);
142
172
  } else {
143
- metaAddress = generateStealthMetaAddress(chain);
173
+ result = generateStealthMetaAddress(chain);
144
174
  }
145
175
  success("Stealth meta-address generated");
146
176
  keyValue("Chain", chain);
147
- const spendingPubKey = metaAddress.metaAddress.spendingKey;
148
- const viewingPubKey = metaAddress.metaAddress.viewingKey;
149
- const spendingPrivKey = metaAddress.spendingPrivateKey;
150
- const viewingPrivKey = metaAddress.viewingPrivateKey;
177
+ const spendingPubKey = result.metaAddress.spendingKey;
178
+ const viewingPubKey = result.metaAddress.viewingKey;
179
+ const spendingPrivKey = result.spendingPrivateKey;
180
+ const viewingPrivKey = result.viewingPrivateKey;
181
+ const encoded = encodeStealthMetaAddress(result.metaAddress);
151
182
  keyValue("Spending Public Key", spendingPubKey);
152
183
  keyValue("Viewing Public Key", viewingPubKey);
153
- const encoded = encodeStealthMetaAddress(metaAddress.metaAddress);
154
184
  keyValue("Encoded Address", encoded);
155
185
  console.log();
156
- warning("PRIVATE KEYS - Keep these secure!");
157
- keyValue("Spending Private Key", spendingPrivKey);
158
- keyValue("Viewing Private Key", viewingPrivKey);
186
+ if (options.outputFile) {
187
+ const outputPath = path.resolve(options.outputFile);
188
+ const dir = path.dirname(outputPath);
189
+ if (dir !== "." && dir !== "/") {
190
+ fs.mkdirSync(dir, { recursive: true });
191
+ }
192
+ const exportData = {
193
+ chain,
194
+ spendingPublicKey: spendingPubKey,
195
+ viewingPublicKey: viewingPubKey,
196
+ spendingPrivateKey: spendingPrivKey,
197
+ viewingPrivateKey: viewingPrivKey,
198
+ metaAddress: encoded,
199
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
200
+ };
201
+ const content = format === "json" ? JSON.stringify(exportData, null, 2) : formatKeysAsText(exportData);
202
+ fs.writeFileSync(outputPath, content, {
203
+ mode: 384,
204
+ encoding: "utf-8"
205
+ });
206
+ success(`Keys exported to: ${outputPath}`);
207
+ warning("SECURITY: File permissions set to 600 (owner only)");
208
+ warning("SECURITY: Delete this file after securely storing the keys!");
209
+ info("Private keys NOT displayed in terminal for security");
210
+ } else {
211
+ warning("PRIVATE KEYS - Keep these secure!");
212
+ keyValue("Spending Private Key", spendingPrivKey);
213
+ keyValue("Viewing Private Key", viewingPrivKey);
214
+ console.log();
215
+ info("TIP: Use --output-file for secure key export:");
216
+ info(" sip keygen --chain solana --output-file ./keys.json");
217
+ }
159
218
  } catch (err) {
160
219
  console.error("Failed to generate keys:", err);
161
220
  process.exit(1);
@@ -338,8 +397,8 @@ function createQuoteCommand() {
338
397
  const quotes = await sip.getQuotes(intent);
339
398
  spin.succeed(`Found ${quotes.length} quote(s)`);
340
399
  if (quotes.length === 0) {
341
- console.log("\nNo quotes available");
342
- return;
400
+ console.error("\nNo quotes available");
401
+ process.exit(1);
343
402
  }
344
403
  console.log();
345
404
  const headers = ["Solver", "Output Amount", "Fee", "Time (s)"];
@@ -367,7 +426,7 @@ function createQuoteCommand() {
367
426
  import { Command as Command7 } from "commander";
368
427
  import { createSIP as createSIP2, NATIVE_TOKENS as NATIVE_TOKENS2 } from "@sip-protocol/sdk";
369
428
  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) => {
429
+ 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
430
  try {
372
431
  heading("Execute Swap");
373
432
  const config = getConfig();
@@ -386,7 +445,10 @@ function createSwapCommand() {
386
445
  address: null,
387
446
  decimals: 18
388
447
  };
448
+ const slippagePercent = Math.min(Math.max(options.slippage ?? 5, 0), 100);
449
+ const slippageTolerance = slippagePercent / 100;
389
450
  info("Creating shielded intent...");
451
+ info(`Slippage tolerance: ${slippagePercent}%`);
390
452
  const intent = await sip.createIntent({
391
453
  input: {
392
454
  asset: inputAsset,
@@ -396,8 +458,8 @@ function createSwapCommand() {
396
458
  asset: outputAsset,
397
459
  minAmount: 0n,
398
460
  // Accept any amount
399
- maxSlippage: 0.05
400
- // 5% slippage tolerance
461
+ maxSlippage: slippageTolerance
462
+ // User-configurable slippage
401
463
  },
402
464
  privacy,
403
465
  recipientMetaAddress: options.recipient
@@ -439,9 +501,40 @@ Solver not found: ${options.solver}`);
439
501
 
440
502
  // src/commands/scan.ts
441
503
  import { Command as Command8 } from "commander";
442
- import { checkStealthAddress, checkEd25519StealthAddress, isEd25519Chain as isEd25519Chain2 } from "@sip-protocol/sdk";
504
+ import {
505
+ checkStealthAddress,
506
+ checkEd25519StealthAddressV1,
507
+ checkSecp256k1StealthAddressV1,
508
+ deriveStealthPrivateKey,
509
+ deriveStealthPrivateKeyV1,
510
+ isEd25519Chain as isEd25519Chain2,
511
+ parseStealthAddress
512
+ } from "@sip-protocol/sdk";
513
+ import { ed25519 } from "@noble/curves/ed25519";
514
+ import { secp256k1 } from "@noble/curves/secp256k1";
515
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
516
+ import * as fs2 from "fs";
517
+ import * as path2 from "path";
518
+ function formatScanResultsAsText(results, exportedAt) {
519
+ const lines = [
520
+ `# SIP Scan Results`,
521
+ `# Exported: ${exportedAt}`,
522
+ `# Found: ${results.length} stealth payment(s)`,
523
+ ``
524
+ ];
525
+ for (const r of results) {
526
+ lines.push(`## Address: ${r.address}`);
527
+ lines.push(`CHAIN=${r.chain}`);
528
+ if (r.privateKey) {
529
+ lines.push(`PRIVATE_KEY=${r.privateKey}`);
530
+ }
531
+ lines.push(``);
532
+ }
533
+ lines.push(`# WARNING: Delete this file after importing keys to your wallet!`);
534
+ return lines.join("\n");
535
+ }
443
536
  function createScanCommand() {
444
- return new Command8("scan").description("Scan for stealth payments").requiredOption("-c, --chain <chain>", "Chain to scan (ethereum, solana, near)").requiredOption("-s, --spending-key <key>", "Your spending private key (hex)").requiredOption("-v, --viewing-key <key>", "Your viewing private key (hex)").option("-a, --addresses <addresses...>", "Specific addresses to check").action(async (options) => {
537
+ return new Command8("scan").description("Scan for stealth payments").requiredOption("-c, --chain <chain>", "Chain to scan (ethereum, solana, near)").requiredOption("-s, --spending-key <key>", "Your spending private key (hex)").requiredOption("-v, --viewing-key <key>", "Your viewing private key (hex)").option("-a, --addresses <addresses...>", "Specific addresses to check").option("-o, --output-file <path>", "Output file for private keys (required to export keys)").option("-f, --format <format>", "Output format: json or text", "json").action(async (options) => {
445
538
  try {
446
539
  heading("Scan for Stealth Payments");
447
540
  const chain = options.chain;
@@ -449,31 +542,35 @@ function createScanCommand() {
449
542
  info(`Scanning ${chain} for stealth payments...`);
450
543
  info(`Using ${useEd25519 ? "ed25519" : "secp256k1"} curve`);
451
544
  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;
545
+ console.error("No addresses provided. Specify addresses with -a flag.");
546
+ console.error("Example: sip scan -c ethereum -s 0x... -v 0x... -a 0xabc... 0xdef...");
547
+ process.exit(1);
455
548
  }
456
549
  const results = [];
457
550
  for (const address of options.addresses) {
458
551
  try {
552
+ const stealthAddr = parseStealthAddress(address);
459
553
  let result;
460
- if (useEd25519) {
461
- result = checkEd25519StealthAddress(
462
- address,
463
- options.spendingKey,
464
- options.viewingKey
465
- );
554
+ const spendingPubKey = useEd25519 ? `0x${bytesToHex(ed25519.getPublicKey(hexToBytes(options.spendingKey.slice(2))))}` : `0x${bytesToHex(secp256k1.getPublicKey(hexToBytes(options.spendingKey.slice(2)), true))}`;
555
+ let isMine = checkStealthAddress(stealthAddr, options.viewingKey, spendingPubKey);
556
+ let isLegacy = false;
557
+ if (!isMine) {
558
+ const legacyMatch = useEd25519 ? checkEd25519StealthAddressV1(stealthAddr, options.spendingKey, options.viewingKey) : checkSecp256k1StealthAddressV1(stealthAddr, options.spendingKey, options.viewingKey);
559
+ if (legacyMatch) {
560
+ isMine = true;
561
+ isLegacy = true;
562
+ }
563
+ }
564
+ if (isMine) {
565
+ const derivedKey = isLegacy ? deriveStealthPrivateKeyV1(stealthAddr, options.spendingKey, options.viewingKey) : deriveStealthPrivateKey(stealthAddr, options.spendingKey, options.viewingKey);
566
+ result = { isMine: true, stealthPrivateKey: derivedKey.privateKey };
466
567
  } else {
467
- result = checkStealthAddress(
468
- address,
469
- options.spendingKey,
470
- options.viewingKey
471
- );
568
+ result = { isMine: false };
472
569
  }
473
570
  results.push({
474
571
  address,
475
572
  isMine: result.isMine,
476
- privateKey: result.isMine ? result.stealthPrivateKey : void 0
573
+ privateKey: result.stealthPrivateKey
477
574
  });
478
575
  } catch (err) {
479
576
  results.push({
@@ -487,15 +584,34 @@ function createScanCommand() {
487
584
  success(`Scanned ${results.length} address(es), found ${foundCount} stealth payment(s)`);
488
585
  if (foundCount > 0) {
489
586
  console.log();
490
- const headers = ["Address", "Match", "Private Key"];
587
+ const headers = ["Address", "Match"];
491
588
  const rows = results.filter((r) => r.isMine).map((r) => [
492
- r.address.slice(0, 10) + "...",
493
- "Yes",
494
- r.privateKey ? r.privateKey.slice(0, 10) + "..." : "N/A"
589
+ r.address.slice(0, 16) + "..." + r.address.slice(-8),
590
+ "Yes"
495
591
  ]);
496
592
  table(headers, rows);
497
593
  console.log();
498
- warning("Store the private keys securely to access these funds");
594
+ if (options.outputFile) {
595
+ const outputPath = path2.resolve(options.outputFile);
596
+ const format = options.format || "json";
597
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
598
+ const exportData = results.filter((r) => r.isMine).map((r) => ({
599
+ address: r.address,
600
+ privateKey: r.privateKey,
601
+ chain: options.chain
602
+ }));
603
+ const content = format === "json" ? JSON.stringify(exportData.map((d) => ({ ...d, exportedAt })), null, 2) : formatScanResultsAsText(exportData, exportedAt);
604
+ fs2.writeFileSync(outputPath, content, {
605
+ mode: 384,
606
+ encoding: "utf-8"
607
+ });
608
+ success(`Private keys exported to: ${outputPath}`);
609
+ warning("SECURITY: Delete this file after importing keys to your wallet!");
610
+ warning("SECURITY: File permissions set to 600 (owner only)");
611
+ } else {
612
+ info("Private keys not exported (use --output-file to export securely)");
613
+ warning("Keys are NOT displayed in terminal for security reasons");
614
+ }
499
615
  } else {
500
616
  info("No stealth payments found");
501
617
  }
@@ -506,15 +622,1281 @@ function createScanCommand() {
506
622
  });
507
623
  }
508
624
 
625
+ // src/commands/setup.ts
626
+ import { Command as Command9 } from "commander";
627
+ import prompts from "prompts";
628
+ import chalk2 from "chalk";
629
+ import ora2 from "ora";
630
+ import {
631
+ generateStealthMetaAddress as generateStealthMetaAddress2,
632
+ generateEd25519StealthMetaAddress as generateEd25519StealthMetaAddress2,
633
+ encodeStealthMetaAddress as encodeStealthMetaAddress2,
634
+ isEd25519Chain as isEd25519Chain3
635
+ } from "@sip-protocol/sdk";
636
+ import { PrivacyLevel as PrivacyLevel2 } from "@sip-protocol/types";
637
+ var CHAINS = [
638
+ { title: "Solana", value: "solana", description: "Fast, low-cost transactions" },
639
+ { title: "Ethereum", value: "ethereum", description: "EVM mainnet" },
640
+ { title: "NEAR", value: "near", description: "Sharded, scalable" },
641
+ { title: "Arbitrum", value: "arbitrum", description: "Ethereum L2" },
642
+ { title: "Base", value: "base", description: "Coinbase L2" }
643
+ ];
644
+ var PRIVACY_LEVELS = [
645
+ {
646
+ title: "Transparent",
647
+ value: PrivacyLevel2.TRANSPARENT,
648
+ description: "No privacy (like standard transactions)"
649
+ },
650
+ {
651
+ title: "Shielded",
652
+ value: PrivacyLevel2.SHIELDED,
653
+ description: "Full privacy - hidden sender, amount, recipient"
654
+ },
655
+ {
656
+ title: "Compliant",
657
+ value: PrivacyLevel2.COMPLIANT,
658
+ description: "Privacy with viewing keys for auditors"
659
+ }
660
+ ];
661
+ function createSetupCommand() {
662
+ return new Command9("setup").description("Interactive setup wizard for SIP Protocol").option("--quick", "Quick setup with defaults").action(async (options) => {
663
+ console.clear();
664
+ console.log();
665
+ 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"));
666
+ console.log(chalk2.bold.magenta(" \u2551 \u2551"));
667
+ console.log(chalk2.bold.magenta(" \u2551") + chalk2.bold.white(" \u{1F6E1}\uFE0F SIP Protocol Setup Wizard \u{1F6E1}\uFE0F ") + chalk2.bold.magenta("\u2551"));
668
+ console.log(chalk2.bold.magenta(" \u2551 \u2551"));
669
+ 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"));
670
+ console.log();
671
+ console.log(chalk2.gray(" Privacy layer for cross-chain transactions"));
672
+ console.log();
673
+ if (options.quick) {
674
+ await quickSetup();
675
+ return;
676
+ }
677
+ console.log(chalk2.cyan.bold(" Step 1 of 4: ") + chalk2.white("Network Configuration"));
678
+ console.log();
679
+ const networkResponse = await prompts([
680
+ {
681
+ type: "select",
682
+ name: "network",
683
+ message: "Which network do you want to use?",
684
+ choices: [
685
+ { title: "Testnet / Devnet", value: "testnet", description: "For development and testing" },
686
+ { title: "Mainnet", value: "mainnet", description: "Production network" }
687
+ ],
688
+ initial: 0
689
+ }
690
+ ]);
691
+ if (!networkResponse.network) {
692
+ console.log(chalk2.yellow("\n Setup cancelled."));
693
+ return;
694
+ }
695
+ console.log();
696
+ console.log(chalk2.cyan.bold(" Step 2 of 4: ") + chalk2.white("Primary Chain"));
697
+ console.log();
698
+ const chainResponse = await prompts([
699
+ {
700
+ type: "select",
701
+ name: "chain",
702
+ message: "Which chain will you use primarily?",
703
+ choices: CHAINS,
704
+ initial: 0
705
+ }
706
+ ]);
707
+ if (!chainResponse.chain) {
708
+ console.log(chalk2.yellow("\n Setup cancelled."));
709
+ return;
710
+ }
711
+ console.log();
712
+ console.log(chalk2.cyan.bold(" Step 3 of 4: ") + chalk2.white("Default Privacy Level"));
713
+ console.log();
714
+ const privacyResponse = await prompts([
715
+ {
716
+ type: "select",
717
+ name: "privacy",
718
+ message: "What privacy level do you want by default?",
719
+ choices: PRIVACY_LEVELS,
720
+ initial: 1
721
+ // Default to shielded
722
+ }
723
+ ]);
724
+ if (!privacyResponse.privacy) {
725
+ console.log(chalk2.yellow("\n Setup cancelled."));
726
+ return;
727
+ }
728
+ console.log();
729
+ console.log(chalk2.cyan.bold(" Step 4 of 4: ") + chalk2.white("Key Generation"));
730
+ console.log();
731
+ const keyResponse = await prompts([
732
+ {
733
+ type: "confirm",
734
+ name: "generateKeys",
735
+ message: "Generate stealth meta-address now?",
736
+ initial: true
737
+ }
738
+ ]);
739
+ const spinner2 = ora2("Saving configuration...").start();
740
+ try {
741
+ setConfig("network", networkResponse.network);
742
+ setConfig("primaryChain", chainResponse.chain);
743
+ setConfig("defaultPrivacy", privacyResponse.privacy);
744
+ spinner2.succeed("Configuration saved");
745
+ } catch (err) {
746
+ spinner2.fail("Failed to save configuration");
747
+ console.error(err);
748
+ process.exit(1);
749
+ }
750
+ if (keyResponse.generateKeys) {
751
+ console.log();
752
+ const keySpinner = ora2("Generating stealth meta-address...").start();
753
+ try {
754
+ const chain = chainResponse.chain;
755
+ const useEd25519 = isEd25519Chain3(chain);
756
+ const metaAddress = useEd25519 ? generateEd25519StealthMetaAddress2(chain) : generateStealthMetaAddress2(chain);
757
+ keySpinner.succeed("Keys generated");
758
+ console.log();
759
+ console.log(chalk2.bold.green(" \u2713 Stealth Meta-Address Generated"));
760
+ console.log();
761
+ 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"));
762
+ console.log(chalk2.gray(" \u2502 ") + chalk2.cyan("Chain: ") + chalk2.white(chain.padEnd(30)) + chalk2.gray(" \u2502"));
763
+ console.log(chalk2.gray(" \u2502 ") + chalk2.cyan("Curve: ") + chalk2.white((useEd25519 ? "ed25519" : "secp256k1").padEnd(30)) + chalk2.gray(" \u2502"));
764
+ 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"));
765
+ console.log();
766
+ const encoded = encodeStealthMetaAddress2(metaAddress.metaAddress);
767
+ console.log(chalk2.bold(" Encoded Meta-Address (share this):"));
768
+ console.log(chalk2.green(` ${encoded}`));
769
+ console.log();
770
+ console.log(chalk2.bold.yellow(" \u26A0\uFE0F PRIVATE KEYS - Store securely!"));
771
+ console.log();
772
+ console.log(chalk2.gray(" Spending Key: ") + chalk2.dim(metaAddress.spendingPrivateKey));
773
+ console.log(chalk2.gray(" Viewing Key: ") + chalk2.dim(metaAddress.viewingPrivateKey));
774
+ console.log();
775
+ setConfig("metaAddress", encoded);
776
+ } catch (err) {
777
+ keySpinner.fail("Failed to generate keys");
778
+ console.error(err instanceof Error ? err.message : err);
779
+ }
780
+ }
781
+ console.log();
782
+ divider();
783
+ console.log();
784
+ console.log(chalk2.bold.green(" \u{1F389} Setup Complete!"));
785
+ console.log();
786
+ console.log(chalk2.gray(" Configuration:"));
787
+ console.log(chalk2.gray(" \u251C\u2500 Network: ") + chalk2.white(networkResponse.network));
788
+ console.log(chalk2.gray(" \u251C\u2500 Chain: ") + chalk2.white(chainResponse.chain));
789
+ console.log(chalk2.gray(" \u251C\u2500 Privacy: ") + chalk2.white(privacyResponse.privacy));
790
+ console.log(chalk2.gray(" \u2514\u2500 Config: ") + chalk2.dim(getConfigPath()));
791
+ console.log();
792
+ console.log(chalk2.cyan(" Next steps:"));
793
+ console.log(chalk2.gray(" 1. ") + chalk2.white("sip keygen") + chalk2.gray(" - Generate more stealth addresses"));
794
+ console.log(chalk2.gray(" 2. ") + chalk2.white("sip quote") + chalk2.gray(" - Get a swap quote"));
795
+ console.log(chalk2.gray(" 3. ") + chalk2.white("sip scan") + chalk2.gray(" - Scan for incoming payments"));
796
+ console.log();
797
+ });
798
+ }
799
+ async function quickSetup() {
800
+ const spinner2 = ora2("Quick setup with defaults...").start();
801
+ try {
802
+ setConfig("network", "testnet");
803
+ setConfig("primaryChain", "solana");
804
+ setConfig("defaultPrivacy", PrivacyLevel2.SHIELDED);
805
+ const metaAddress = generateEd25519StealthMetaAddress2("solana");
806
+ const encoded = encodeStealthMetaAddress2(metaAddress.metaAddress);
807
+ setConfig("metaAddress", encoded);
808
+ spinner2.succeed("Quick setup complete");
809
+ console.log();
810
+ console.log(chalk2.green(" \u2713 Network: testnet"));
811
+ console.log(chalk2.green(" \u2713 Chain: solana"));
812
+ console.log(chalk2.green(" \u2713 Privacy: shielded"));
813
+ console.log(chalk2.green(" \u2713 Keys generated"));
814
+ console.log();
815
+ console.log(chalk2.bold(" Meta-Address:"));
816
+ console.log(chalk2.cyan(` ${encoded}`));
817
+ console.log();
818
+ console.log(chalk2.yellow(" \u26A0\uFE0F Run ") + chalk2.white("sip setup") + chalk2.yellow(" for full interactive setup"));
819
+ console.log();
820
+ } catch (err) {
821
+ spinner2.fail("Quick setup failed");
822
+ console.error(err);
823
+ process.exit(1);
824
+ }
825
+ }
826
+
827
+ // src/commands/stealth.ts
828
+ import { Command as Command10 } from "commander";
829
+ import prompts2 from "prompts";
830
+ import chalk3 from "chalk";
831
+ import ora3 from "ora";
832
+ import {
833
+ generateStealthAddress,
834
+ generateEd25519StealthAddress,
835
+ decodeStealthMetaAddress,
836
+ isEd25519Chain as isEd25519Chain4,
837
+ ed25519PublicKeyToSolanaAddress,
838
+ ed25519PublicKeyToNearAddress,
839
+ publicKeyToEthAddress,
840
+ deriveStealthPrivateKey as deriveStealthPrivateKey2,
841
+ deriveEd25519StealthPrivateKey,
842
+ solanaAddressToEd25519PublicKey
843
+ } from "@sip-protocol/sdk";
844
+ function createStealthCommand() {
845
+ const cmd = new Command10("stealth").description("Generate one-time stealth addresses");
846
+ 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) => {
847
+ heading("Generate Stealth Address");
848
+ let metaAddressStr = options.meta;
849
+ if (options.interactive || !metaAddressStr) {
850
+ const savedMeta = getConfig("metaAddress");
851
+ const response = await prompts2([
852
+ {
853
+ type: "text",
854
+ name: "meta",
855
+ message: "Enter recipient meta-address:",
856
+ initial: savedMeta || "",
857
+ validate: (value) => value.startsWith("sip:") ? true : "Must be a valid SIP meta-address (sip:...)"
858
+ }
859
+ ]);
860
+ if (!response.meta) {
861
+ console.log(chalk3.yellow("Cancelled."));
862
+ return;
863
+ }
864
+ metaAddressStr = response.meta;
865
+ }
866
+ const spinner2 = ora3("Generating stealth address...").start();
867
+ try {
868
+ const metaAddress = decodeStealthMetaAddress(metaAddressStr);
869
+ const chain = metaAddress.chain;
870
+ const useEd25519 = isEd25519Chain4(chain);
871
+ const result = useEd25519 ? generateEd25519StealthAddress(metaAddress) : generateStealthAddress(metaAddress);
872
+ const stealthPubKey = result.stealthAddress.address;
873
+ const ephemeralPubKey = result.stealthAddress.ephemeralPublicKey;
874
+ let chainAddress;
875
+ if (useEd25519) {
876
+ if (chain === "solana") {
877
+ chainAddress = ed25519PublicKeyToSolanaAddress(stealthPubKey);
878
+ } else if (chain === "near") {
879
+ chainAddress = ed25519PublicKeyToNearAddress(stealthPubKey);
880
+ } else {
881
+ chainAddress = stealthPubKey;
882
+ }
883
+ } else {
884
+ chainAddress = publicKeyToEthAddress(stealthPubKey);
885
+ }
886
+ spinner2.succeed("Stealth address generated");
887
+ console.log();
888
+ keyValue("Chain", chain);
889
+ keyValue("Curve", useEd25519 ? "ed25519" : "secp256k1");
890
+ console.log();
891
+ console.log(chalk3.bold.green(" One-Time Address (send funds here):"));
892
+ console.log(chalk3.cyan(` ${chainAddress}`));
893
+ console.log();
894
+ console.log(chalk3.bold(" Ephemeral Public Key (publish for recipient):"));
895
+ console.log(chalk3.gray(` ${ephemeralPubKey}`));
896
+ console.log();
897
+ warning("The ephemeral key must be published so the recipient can find and claim funds.");
898
+ } catch (err) {
899
+ spinner2.fail("Failed to generate stealth address");
900
+ console.error(err instanceof Error ? err.message : err);
901
+ process.exit(1);
902
+ }
903
+ });
904
+ 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) => {
905
+ heading("Derive Stealth Spending Key");
906
+ warning("This is for advanced users. Keep your derived private key secure!");
907
+ console.log();
908
+ const spinner2 = ora3("Deriving stealth private key...").start();
909
+ try {
910
+ const chain = options.chain;
911
+ const useEd25519 = isEd25519Chain4(chain);
912
+ const spendingKey = normalizeHexKey(options.spendingKey);
913
+ const viewingKey = normalizeHexKey(options.viewingKey);
914
+ const ephemeralKey = normalizeHexKey(options.ephemeral);
915
+ let stealthPubKeyHex;
916
+ if (useEd25519 && chain === "solana") {
917
+ stealthPubKeyHex = solanaAddressToEd25519PublicKey(options.stealthAddress);
918
+ } else if (options.stealthAddress.startsWith("0x")) {
919
+ stealthPubKeyHex = options.stealthAddress;
920
+ } else {
921
+ throw new Error("Stealth address must be base58 (Solana) or hex (0x...)");
922
+ }
923
+ const stealthAddressObj = {
924
+ address: stealthPubKeyHex,
925
+ ephemeralPublicKey: ephemeralKey,
926
+ viewTag: 0
927
+ // Not needed for derivation
928
+ };
929
+ const recovery = useEd25519 ? deriveEd25519StealthPrivateKey(
930
+ stealthAddressObj,
931
+ spendingKey,
932
+ viewingKey
933
+ ) : deriveStealthPrivateKey2(
934
+ stealthAddressObj,
935
+ spendingKey,
936
+ viewingKey
937
+ );
938
+ spinner2.succeed("Stealth private key derived");
939
+ console.log();
940
+ keyValue("Chain", chain);
941
+ keyValue("Curve", useEd25519 ? "ed25519" : "secp256k1");
942
+ console.log();
943
+ console.log(chalk3.bold.green(" Derived Private Key (use to claim funds):"));
944
+ console.log(chalk3.cyan(` ${recovery.privateKey}`));
945
+ console.log();
946
+ console.log(chalk3.bold(" Stealth Address:"));
947
+ console.log(chalk3.gray(` ${recovery.stealthAddress}`));
948
+ console.log();
949
+ warning("Never share your private key! Use it to sign transactions claiming your funds.");
950
+ console.log();
951
+ info3("Next steps:");
952
+ console.log(chalk3.gray(" 1. Import this key into a wallet or use SDK to claim"));
953
+ console.log(chalk3.gray(" 2. Transfer funds from stealth address to your main wallet"));
954
+ console.log(chalk3.gray(" 3. Securely delete this private key after claiming"));
955
+ console.log();
956
+ } catch (err) {
957
+ spinner2.fail("Failed to derive stealth key");
958
+ console.error(err instanceof Error ? err.message : err);
959
+ process.exit(1);
960
+ }
961
+ });
962
+ return cmd;
963
+ }
964
+ function info3(message) {
965
+ console.log(chalk3.blue("\u2139"), message);
966
+ }
967
+ function normalizeHexKey(key) {
968
+ if (key.startsWith("0x")) {
969
+ return key;
970
+ }
971
+ return `0x${key}`;
972
+ }
973
+
974
+ // src/commands/viewing-key.ts
975
+ import { Command as Command11 } from "commander";
976
+ import prompts3 from "prompts";
977
+ import chalk4 from "chalk";
978
+ import ora4 from "ora";
979
+ import { generateViewingKey } from "@sip-protocol/sdk";
980
+ function createViewingKeyCommand() {
981
+ const cmd = new Command11("viewing-key").alias("vk").description("Manage viewing keys for selective disclosure");
982
+ 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) => {
983
+ heading("Generate Viewing Key");
984
+ let path3 = options.path;
985
+ if (options.interactive || !path3) {
986
+ const response = await prompts3([
987
+ {
988
+ type: "text",
989
+ name: "path",
990
+ message: "Enter a label or path for this viewing key:",
991
+ initial: `audit/${Date.now()}`,
992
+ validate: (value) => value.length > 0 ? true : "Path is required"
993
+ },
994
+ {
995
+ type: "text",
996
+ name: "description",
997
+ message: "Description (optional):"
998
+ }
999
+ ]);
1000
+ if (!response.path) {
1001
+ console.log(chalk4.yellow("Cancelled."));
1002
+ return;
1003
+ }
1004
+ path3 = response.path;
1005
+ }
1006
+ const spinner2 = ora4("Generating viewing key...").start();
1007
+ try {
1008
+ const viewingKey = generateViewingKey(path3);
1009
+ spinner2.succeed("Viewing key generated");
1010
+ console.log();
1011
+ keyValue("Path", viewingKey.path);
1012
+ keyValue("Hash", viewingKey.hash);
1013
+ console.log();
1014
+ console.log(chalk4.bold.green(" Viewing Key (share with auditors):"));
1015
+ console.log(chalk4.cyan(` ${viewingKey.key}`));
1016
+ console.log();
1017
+ console.log(chalk4.bold(" Key Hash (for verification):"));
1018
+ console.log(chalk4.gray(` ${viewingKey.hash}`));
1019
+ console.log();
1020
+ warning("Share the viewing key with authorized parties only.");
1021
+ console.log(chalk4.gray(" They can view transactions but cannot spend funds."));
1022
+ console.log();
1023
+ const saveResponse = await prompts3([
1024
+ {
1025
+ type: "confirm",
1026
+ name: "save",
1027
+ message: "Save this viewing key to config?",
1028
+ initial: false
1029
+ }
1030
+ ]);
1031
+ if (saveResponse.save) {
1032
+ const existingKeys = getConfig("viewingKeys") || {};
1033
+ existingKeys[path3] = viewingKey.key;
1034
+ setConfig("viewingKeys", existingKeys);
1035
+ success("Viewing key saved to config");
1036
+ }
1037
+ } catch (err) {
1038
+ spinner2.fail("Failed to generate viewing key");
1039
+ console.error(err instanceof Error ? err.message : err);
1040
+ process.exit(1);
1041
+ }
1042
+ });
1043
+ cmd.command("list").alias("ls").description("List saved viewing keys").action(async () => {
1044
+ heading("Saved Viewing Keys");
1045
+ const keys = getConfig("viewingKeys") || {};
1046
+ const entries = Object.entries(keys);
1047
+ if (entries.length === 0) {
1048
+ console.log(chalk4.gray(" No viewing keys saved."));
1049
+ console.log(chalk4.gray(" Run: sip viewing-key generate -i"));
1050
+ console.log();
1051
+ return;
1052
+ }
1053
+ console.log();
1054
+ entries.forEach(([path3, key]) => {
1055
+ console.log(chalk4.cyan(` ${path3}`));
1056
+ console.log(chalk4.gray(` ${key.slice(0, 20)}...${key.slice(-10)}`));
1057
+ console.log();
1058
+ });
1059
+ });
1060
+ 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) => {
1061
+ heading("Create Viewing Key Disclosure");
1062
+ const keys = getConfig("viewingKeys") || {};
1063
+ const entries = Object.entries(keys);
1064
+ if (entries.length === 0) {
1065
+ console.log(chalk4.yellow(" No viewing keys saved."));
1066
+ console.log(chalk4.gray(" Run: sip viewing-key generate -i first"));
1067
+ console.log();
1068
+ return;
1069
+ }
1070
+ const response = await prompts3([
1071
+ {
1072
+ type: "select",
1073
+ name: "path",
1074
+ message: "Select viewing key to share:",
1075
+ choices: entries.map(([path3]) => ({ title: path3, value: path3 }))
1076
+ },
1077
+ {
1078
+ type: "select",
1079
+ name: "scope",
1080
+ message: "Disclosure scope:",
1081
+ choices: [
1082
+ { title: "All transactions", value: "all" },
1083
+ { title: "Treasury only", value: "treasury" },
1084
+ { title: "Payments only", value: "payments" },
1085
+ { title: "Custom time range", value: "custom" }
1086
+ ]
1087
+ },
1088
+ {
1089
+ type: "text",
1090
+ name: "recipient",
1091
+ message: "Recipient (auditor, regulator, etc.):"
1092
+ }
1093
+ ]);
1094
+ if (!response.path) {
1095
+ console.log(chalk4.yellow("Cancelled."));
1096
+ return;
1097
+ }
1098
+ const key = keys[response.path];
1099
+ console.log();
1100
+ console.log(chalk4.bold.green(" \u{1F4CB} Viewing Key Disclosure"));
1101
+ console.log();
1102
+ 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"));
1103
+ console.log(chalk4.gray(" \u2502 ") + chalk4.cyan("Path: ") + chalk4.white(response.path.padEnd(36)) + chalk4.gray(" \u2502"));
1104
+ console.log(chalk4.gray(" \u2502 ") + chalk4.cyan("Scope: ") + chalk4.white(response.scope.padEnd(36)) + chalk4.gray(" \u2502"));
1105
+ console.log(chalk4.gray(" \u2502 ") + chalk4.cyan("Recipient: ") + chalk4.white((response.recipient || "Not specified").padEnd(36)) + chalk4.gray(" \u2502"));
1106
+ 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"));
1107
+ console.log();
1108
+ console.log(chalk4.bold(" Viewing Key:"));
1109
+ console.log(chalk4.cyan(` ${key}`));
1110
+ console.log();
1111
+ console.log(chalk4.gray(" The recipient can use this key to view transactions"));
1112
+ console.log(chalk4.gray(" matching the specified scope, but cannot spend funds."));
1113
+ console.log();
1114
+ });
1115
+ return cmd;
1116
+ }
1117
+
1118
+ // src/commands/backends.ts
1119
+ import { Command as Command12 } from "commander";
1120
+ import {
1121
+ PrivacyBackendRegistry,
1122
+ SIPNativeBackend
1123
+ } from "@sip-protocol/sdk";
1124
+ import chalk5 from "chalk";
1125
+ function createDefaultRegistry() {
1126
+ const registry = new PrivacyBackendRegistry({ enableHealthTracking: true });
1127
+ registry.register(new SIPNativeBackend());
1128
+ return registry;
1129
+ }
1130
+ function collectBackendInfo(registry) {
1131
+ const entries = registry.getAllEntries();
1132
+ const healthTracker = registry.getHealthTracker();
1133
+ const results = [];
1134
+ for (const entry of entries) {
1135
+ const backend = entry.backend;
1136
+ const caps = backend.getCapabilities();
1137
+ let healthy = true;
1138
+ let failures = 0;
1139
+ if (healthTracker) {
1140
+ const health = healthTracker.getHealth(backend.name);
1141
+ if (health) {
1142
+ healthy = health.isHealthy;
1143
+ failures = health.consecutiveFailures;
1144
+ }
1145
+ }
1146
+ results.push({
1147
+ name: backend.name,
1148
+ type: backend.type,
1149
+ chains: [...backend.chains],
1150
+ healthy,
1151
+ failures,
1152
+ enabled: entry.enabled,
1153
+ compliance: caps.complianceSupport
1154
+ });
1155
+ }
1156
+ return results;
1157
+ }
1158
+ function createBackendsCommand() {
1159
+ const cmd = new Command12("backends").description("Manage and list privacy backends");
1160
+ cmd.command("list").description("List all registered privacy backends").option("--health", "Show health status").option("--metrics", "Show detailed metrics").option("--json", "Output as JSON").option("--type <type>", "Filter by type (transaction, compute, both)").option("--chain <chain>", "Filter by chain support").action(async (options) => {
1161
+ try {
1162
+ const registry = createDefaultRegistry();
1163
+ let backends = collectBackendInfo(registry);
1164
+ if (options.type) {
1165
+ backends = backends.filter((b) => b.type === options.type || b.type === "both");
1166
+ }
1167
+ if (options.chain) {
1168
+ backends = backends.filter((b) => b.chains.includes(options.chain));
1169
+ }
1170
+ if (options.json) {
1171
+ json({
1172
+ backends: backends.map((b) => ({
1173
+ ...b,
1174
+ chains: b.chains
1175
+ })),
1176
+ total: backends.length,
1177
+ healthy: backends.filter((b) => b.healthy).length
1178
+ });
1179
+ return;
1180
+ }
1181
+ heading("Privacy Backends");
1182
+ if (backends.length === 0) {
1183
+ warning("No backends match the specified filters");
1184
+ return;
1185
+ }
1186
+ const headers = ["NAME", "TYPE", "CHAINS", "COMPLIANCE"];
1187
+ if (options.health || options.metrics) {
1188
+ headers.push("HEALTHY", "FAILURES");
1189
+ }
1190
+ const rows = backends.map((b) => {
1191
+ const row = [
1192
+ b.enabled ? b.name : chalk5.gray(b.name + " (disabled)"),
1193
+ b.type,
1194
+ b.chains.join(", "),
1195
+ b.compliance ? chalk5.green("\u2713") : chalk5.gray("\u2717")
1196
+ ];
1197
+ if (options.health || options.metrics) {
1198
+ row.push(
1199
+ b.healthy ? chalk5.green("\u2713") : chalk5.red("\u2717"),
1200
+ b.failures
1201
+ );
1202
+ }
1203
+ return row;
1204
+ });
1205
+ table(headers, rows);
1206
+ console.log();
1207
+ const healthyCount = backends.filter((b) => b.healthy).length;
1208
+ const complianceCount = backends.filter((b) => b.compliance).length;
1209
+ success(`${backends.length} backend(s) registered`);
1210
+ if (options.health || options.metrics) {
1211
+ if (healthyCount === backends.length) {
1212
+ info(`All backends healthy`);
1213
+ } else {
1214
+ warning(`${healthyCount}/${backends.length} backends healthy`);
1215
+ }
1216
+ }
1217
+ info(`${complianceCount} backend(s) support compliance (viewing keys)`);
1218
+ } catch (err) {
1219
+ console.error("Failed to list backends:", err);
1220
+ process.exit(1);
1221
+ }
1222
+ });
1223
+ cmd.command("info <name>").description("Show detailed information about a specific backend").option("--json", "Output as JSON").action(async (name, options) => {
1224
+ try {
1225
+ const registry = createDefaultRegistry();
1226
+ const backend = registry.get(name);
1227
+ if (!backend) {
1228
+ console.error(`Backend '${name}' not found`);
1229
+ console.error("Available backends:", registry.getNames().join(", "));
1230
+ process.exit(1);
1231
+ }
1232
+ const caps = backend.getCapabilities();
1233
+ const healthTracker = registry.getHealthTracker();
1234
+ const health = healthTracker?.getHealth(name);
1235
+ const metrics = healthTracker?.getMetrics(name);
1236
+ const backendInfo = {
1237
+ name: backend.name,
1238
+ type: backend.type,
1239
+ chains: [...backend.chains],
1240
+ capabilities: {
1241
+ complianceSupport: caps.complianceSupport,
1242
+ anonymitySet: caps.anonymitySet,
1243
+ latency: caps.latencyEstimate,
1244
+ setupRequired: caps.setupRequired
1245
+ },
1246
+ health: health ? {
1247
+ state: health.circuitState,
1248
+ isHealthy: health.isHealthy,
1249
+ consecutiveFailures: health.consecutiveFailures,
1250
+ consecutiveSuccesses: health.consecutiveSuccesses,
1251
+ lastFailureTime: health.lastFailureTime ? new Date(health.lastFailureTime).toISOString() : null,
1252
+ lastFailureReason: health.lastFailureReason ?? null
1253
+ } : null,
1254
+ metrics: metrics ? {
1255
+ totalRequests: metrics.totalRequests,
1256
+ successfulRequests: metrics.successfulRequests,
1257
+ failedRequests: metrics.failedRequests,
1258
+ averageLatencyMs: Math.round(metrics.averageLatencyMs),
1259
+ successRate: metrics.totalRequests > 0 ? Math.round(metrics.successfulRequests / metrics.totalRequests * 100) : 0
1260
+ } : null
1261
+ };
1262
+ if (options.json) {
1263
+ json(backendInfo);
1264
+ return;
1265
+ }
1266
+ heading(`Backend: ${backend.name}`);
1267
+ console.log(chalk5.bold("General"));
1268
+ console.log(` Type: ${backend.type}`);
1269
+ console.log(` Chains: ${backend.chains.join(", ")}`);
1270
+ console.log();
1271
+ console.log(chalk5.bold("Capabilities"));
1272
+ console.log(` Compliance: ${caps.complianceSupport ? chalk5.green("Yes") : chalk5.gray("No")}`);
1273
+ console.log(` Anonymity Set: ${caps.anonymitySet ?? "N/A"}`);
1274
+ console.log(` Est. Latency: ${caps.latencyEstimate}`);
1275
+ console.log(` Setup Required: ${caps.setupRequired ? "Yes" : "No"}`);
1276
+ if (health) {
1277
+ console.log();
1278
+ console.log(chalk5.bold("Health"));
1279
+ console.log(` State: ${health.circuitState === "closed" ? chalk5.green("Healthy") : chalk5.red(health.circuitState)}`);
1280
+ console.log(` Healthy: ${health.isHealthy ? chalk5.green("Yes") : chalk5.red("No")}`);
1281
+ console.log(` Failures: ${health.consecutiveFailures}`);
1282
+ console.log(` Successes: ${health.consecutiveSuccesses}`);
1283
+ if (health.lastFailureReason) {
1284
+ console.log(` Last Error: ${health.lastFailureReason}`);
1285
+ }
1286
+ }
1287
+ if (metrics) {
1288
+ console.log();
1289
+ console.log(chalk5.bold("Metrics"));
1290
+ console.log(` Total Requests: ${metrics.totalRequests}`);
1291
+ console.log(` Success Rate: ${backendInfo.metrics?.successRate}%`);
1292
+ console.log(` Avg Latency: ${backendInfo.metrics?.averageLatencyMs}ms`);
1293
+ }
1294
+ } catch (err) {
1295
+ console.error("Failed to get backend info:", err);
1296
+ process.exit(1);
1297
+ }
1298
+ });
1299
+ return cmd;
1300
+ }
1301
+
1302
+ // src/commands/proof.ts
1303
+ import { Command as Command13 } from "commander";
1304
+ import { readFileSync, writeFileSync as writeFileSync3, existsSync } from "fs";
1305
+ import {
1306
+ MockProofProvider as MockProofProvider3,
1307
+ createProofAggregator,
1308
+ createVerificationPipeline,
1309
+ createCrossSystemValidator,
1310
+ UnifiedProofConverter
1311
+ } from "@sip-protocol/sdk";
1312
+ function readProofFile(path3) {
1313
+ if (!existsSync(path3)) {
1314
+ throw new Error(`File not found: ${path3}`);
1315
+ }
1316
+ const content = readFileSync(path3, "utf-8");
1317
+ return JSON.parse(content);
1318
+ }
1319
+ function writeProofFile(path3, data) {
1320
+ writeFileSync3(path3, JSON.stringify(data, null, 2));
1321
+ }
1322
+ function readFromStdin() {
1323
+ return new Promise((resolve3, reject) => {
1324
+ let data = "";
1325
+ process.stdin.setEncoding("utf8");
1326
+ process.stdin.on("data", (chunk) => {
1327
+ data += chunk;
1328
+ });
1329
+ process.stdin.on("end", () => resolve3(data));
1330
+ process.stdin.on("error", reject);
1331
+ setTimeout(() => {
1332
+ if (data === "") {
1333
+ reject(new Error("No input received from stdin"));
1334
+ }
1335
+ }, 1e3);
1336
+ });
1337
+ }
1338
+ var PROOF_SYSTEM_NAMES = {
1339
+ noir: "Noir (Aztec)",
1340
+ halo2: "Halo2 (Zcash)",
1341
+ kimchi: "Kimchi (Mina)",
1342
+ groth16: "Groth16",
1343
+ plonk: "PLONK",
1344
+ stark: "STARK"
1345
+ };
1346
+ function formatProofSystem(system) {
1347
+ return PROOF_SYSTEM_NAMES[system] || system;
1348
+ }
1349
+ function getSystemFromProof(proof) {
1350
+ return proof.metadata.system;
1351
+ }
1352
+ function createProofCommand() {
1353
+ const proof = new Command13("proof").description("Proof composition operations (M20)");
1354
+ proof.command("generate").description("Generate a ZK proof").requiredOption("-s, --system <system>", "Proof system (noir|halo2|kimchi|mock)").requiredOption("-c, --circuit <id>", "Circuit identifier").option("-i, --inputs <json>", "Public inputs as JSON string").option("-f, --inputs-file <path>", "Public inputs from JSON file").option("-w, --witness <json>", "Private witness as JSON string").option("--witness-file <path>", "Private witness from JSON file").option("-o, --output <path>", "Output file path (default: stdout)").option("--json", "Output as JSON", false).action(async (options) => {
1355
+ const format = options.json ? "json" : "human";
1356
+ try {
1357
+ if (format === "human") {
1358
+ heading("Generate ZK Proof");
1359
+ }
1360
+ let publicInputs = {};
1361
+ if (options.inputs) {
1362
+ publicInputs = JSON.parse(options.inputs);
1363
+ } else if (options.inputsFile) {
1364
+ publicInputs = JSON.parse(readFileSync(options.inputsFile, "utf-8"));
1365
+ }
1366
+ const spin = format === "human" ? spinner(`Generating ${options.system} proof...`) : null;
1367
+ const systemLower = options.system.toLowerCase();
1368
+ const provider = new MockProofProvider3({ silent: true });
1369
+ await provider.initialize();
1370
+ const result = await provider.generateFundingProof({
1371
+ balance: BigInt(publicInputs.balance || "1000000"),
1372
+ minimumRequired: BigInt(publicInputs.minimum || "100"),
1373
+ blindingFactor: new Uint8Array(32),
1374
+ assetId: publicInputs.asset || "ETH",
1375
+ userAddress: publicInputs.user || "0x0000000000000000000000000000000000000000",
1376
+ ownershipSignature: new Uint8Array(64)
1377
+ });
1378
+ spin?.succeed("Proof generated");
1379
+ const proofMetadata = {
1380
+ system: systemLower,
1381
+ systemVersion: "1.0.0",
1382
+ circuitId: options.circuit,
1383
+ circuitVersion: "1.0.0",
1384
+ generatedAt: Date.now(),
1385
+ proofSizeBytes: result.proof.proof.length / 2
1386
+ // hex string to bytes
1387
+ };
1388
+ const proofData = {
1389
+ proof: {
1390
+ id: `proof_${Date.now()}`,
1391
+ proof: result.proof.proof,
1392
+ publicInputs: result.publicInputs.map(String),
1393
+ metadata: proofMetadata
1394
+ },
1395
+ metadata: {
1396
+ system: options.system,
1397
+ circuit: options.circuit,
1398
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1399
+ }
1400
+ };
1401
+ if (options.output) {
1402
+ writeProofFile(options.output, proofData);
1403
+ if (format === "human") {
1404
+ success(`Proof written to ${options.output}`);
1405
+ }
1406
+ }
1407
+ if (format === "json") {
1408
+ json(proofData);
1409
+ } else if (!options.output) {
1410
+ keyValue("System", formatProofSystem(systemLower));
1411
+ keyValue("Circuit", options.circuit);
1412
+ keyValue("Proof", formatHash(result.proof.proof, 16));
1413
+ keyValue("Public Inputs", JSON.stringify(result.publicInputs));
1414
+ }
1415
+ } catch (err) {
1416
+ if (format === "json") {
1417
+ json({ error: String(err) });
1418
+ } else {
1419
+ error(`Failed to generate proof: ${err}`);
1420
+ }
1421
+ process.exit(1);
1422
+ }
1423
+ });
1424
+ proof.command("compose").description("Compose multiple proofs together").requiredOption("-p, --proofs <paths...>", "Proof file paths to compose").option("-t, --template <name>", "Composition template (sequential|parallel|recursive)", "sequential").option("-o, --output <path>", "Output file path (default: stdout)").option("--json", "Output as JSON", false).option("--validate", "Validate compatibility before composing", true).action(async (options) => {
1425
+ const format = options.json ? "json" : "human";
1426
+ try {
1427
+ if (format === "human") {
1428
+ heading("Compose Proofs");
1429
+ }
1430
+ const proofs = [];
1431
+ for (const path3 of options.proofs) {
1432
+ const proofFile = readProofFile(path3);
1433
+ proofs.push(proofFile.proof);
1434
+ }
1435
+ if (format === "human") {
1436
+ info(`Loaded ${proofs.length} proofs`);
1437
+ proofs.forEach((p, i) => {
1438
+ keyValue(` Proof ${i + 1}`, `${formatProofSystem(getSystemFromProof(p))} - ${formatHash(p.id)}`);
1439
+ });
1440
+ divider();
1441
+ }
1442
+ if (options.validate) {
1443
+ const validator = createCrossSystemValidator();
1444
+ const systems = [...new Set(proofs.map((p) => getSystemFromProof(p)))];
1445
+ if (systems.length > 1) {
1446
+ const spin2 = format === "human" ? spinner("Validating cross-system compatibility...") : null;
1447
+ const report = validator.validate(proofs, {
1448
+ skipFieldCheck: false,
1449
+ skipCurveCheck: false
1450
+ });
1451
+ const errors = report.checks.filter((c) => !c.passed && c.severity === "error");
1452
+ if (errors.length > 0) {
1453
+ spin2?.fail("Compatibility check failed");
1454
+ if (format === "json") {
1455
+ json({ error: "Incompatible proof systems", report });
1456
+ } else {
1457
+ error("Proof systems are not compatible for composition");
1458
+ errors.forEach((e) => {
1459
+ error(` - ${e.name}: ${e.message}`);
1460
+ });
1461
+ }
1462
+ process.exit(1);
1463
+ }
1464
+ spin2?.succeed("Compatibility validated");
1465
+ }
1466
+ }
1467
+ const spin = format === "human" ? spinner(`Composing proofs (${options.template})...`) : null;
1468
+ const startTime = Date.now();
1469
+ const aggregator = createProofAggregator();
1470
+ const mockProvider = new MockProofProvider3({ silent: true });
1471
+ await mockProvider.initialize();
1472
+ const getProvider = (_system) => {
1473
+ return mockProvider;
1474
+ };
1475
+ let result;
1476
+ if (options.template === "parallel") {
1477
+ result = await aggregator.aggregateParallel({
1478
+ proofs,
1479
+ getProvider,
1480
+ verifyBefore: options.validate,
1481
+ maxConcurrent: 4,
1482
+ onProgress: (event) => {
1483
+ if (format === "human" && spin) {
1484
+ const progress = Math.round(event.step / event.totalSteps * 100);
1485
+ spin.text = `${event.operation} (${progress}%)`;
1486
+ }
1487
+ }
1488
+ });
1489
+ } else {
1490
+ result = await aggregator.aggregateSequential({
1491
+ proofs,
1492
+ getProvider,
1493
+ verifyBefore: options.validate,
1494
+ onProgress: (event) => {
1495
+ if (format === "human" && spin) {
1496
+ const progress = Math.round(event.step / event.totalSteps * 100);
1497
+ spin.text = `${event.operation} (${progress}%)`;
1498
+ }
1499
+ }
1500
+ });
1501
+ }
1502
+ const timeMs = Date.now() - startTime;
1503
+ if (!result.success || !result.composedProof) {
1504
+ spin?.fail("Composition failed");
1505
+ if (format === "json") {
1506
+ json({ error: result.error || "Unknown error" });
1507
+ } else {
1508
+ error(`Composition failed: ${result.error || "Unknown error"}`);
1509
+ }
1510
+ process.exit(1);
1511
+ }
1512
+ spin?.succeed("Composition complete");
1513
+ const outputData = {
1514
+ proof: result.composedProof,
1515
+ metadata: {
1516
+ template: options.template,
1517
+ inputProofs: proofs.map((p) => p.id),
1518
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1519
+ timeMs
1520
+ }
1521
+ };
1522
+ if (options.output) {
1523
+ writeProofFile(options.output, outputData);
1524
+ if (format === "human") {
1525
+ success(`Composed proof written to ${options.output}`);
1526
+ }
1527
+ }
1528
+ if (format === "json") {
1529
+ json(outputData);
1530
+ } else if (!options.output) {
1531
+ keyValue("Composed Proof ID", result.composedProof.id);
1532
+ keyValue("Component Proofs", result.composedProof.proofs.length.toString());
1533
+ keyValue("Strategy", result.composedProof.strategy);
1534
+ keyValue("Time", `${timeMs}ms`);
1535
+ }
1536
+ } catch (err) {
1537
+ if (format === "json") {
1538
+ json({ error: String(err) });
1539
+ } else {
1540
+ error(`Failed to compose proofs: ${err}`);
1541
+ }
1542
+ process.exit(1);
1543
+ }
1544
+ });
1545
+ proof.command("verify").description("Verify a proof or composed proof").argument("<path>", "Proof file path (or - for stdin)").option("--strict", "Enable strict verification mode", false).option("--json", "Output as JSON", false).action(async (path3, options) => {
1546
+ const format = options.json ? "json" : "human";
1547
+ try {
1548
+ if (format === "human") {
1549
+ heading("Verify Proof");
1550
+ }
1551
+ let proofData;
1552
+ if (path3 === "-") {
1553
+ const stdinData = await readFromStdin();
1554
+ proofData = JSON.parse(stdinData);
1555
+ } else {
1556
+ proofData = readProofFile(path3);
1557
+ }
1558
+ const spin = format === "human" ? spinner("Verifying proof...") : null;
1559
+ const pipeline = createVerificationPipeline();
1560
+ const isComposed = "proofs" in proofData.proof && Array.isArray(proofData.proof.proofs);
1561
+ const mockProvider = new MockProofProvider3({ silent: true });
1562
+ await mockProvider.initialize();
1563
+ const getProvider = (_system) => {
1564
+ return mockProvider;
1565
+ };
1566
+ let result;
1567
+ if (isComposed) {
1568
+ result = await pipeline.verify(proofData.proof, {
1569
+ getProvider,
1570
+ config: {}
1571
+ });
1572
+ } else {
1573
+ result = await pipeline.verifySingle(proofData.proof, getProvider);
1574
+ }
1575
+ if (result.valid) {
1576
+ spin?.succeed("Proof is valid");
1577
+ } else {
1578
+ spin?.fail("Proof verification failed");
1579
+ }
1580
+ if (format === "json") {
1581
+ json({
1582
+ valid: result.valid,
1583
+ isComposed,
1584
+ details: result
1585
+ });
1586
+ } else {
1587
+ keyValue("Valid", result.valid ? "Yes" : "No");
1588
+ keyValue("Type", isComposed ? "Composed" : "Single");
1589
+ if (isComposed) {
1590
+ const composed = proofData.proof;
1591
+ keyValue("Component Proofs", composed.proofs.length.toString());
1592
+ }
1593
+ if (!result.valid && "error" in result) {
1594
+ error(`Reason: ${result.error}`);
1595
+ }
1596
+ }
1597
+ if (!result.valid) {
1598
+ process.exit(1);
1599
+ }
1600
+ } catch (err) {
1601
+ if (format === "json") {
1602
+ json({ valid: false, error: String(err) });
1603
+ } else {
1604
+ error(`Failed to verify proof: ${err}`);
1605
+ }
1606
+ process.exit(1);
1607
+ }
1608
+ });
1609
+ proof.command("inspect").description("Analyze and display proof details").argument("<path>", "Proof file path (or - for stdin)").option("--json", "Output as JSON", false).option("--full", "Show full proof data (not truncated)", false).action(async (path3, options) => {
1610
+ const format = options.json ? "json" : "human";
1611
+ try {
1612
+ if (format === "human") {
1613
+ heading("Proof Inspection");
1614
+ }
1615
+ let proofData;
1616
+ if (path3 === "-") {
1617
+ const stdinData = await readFromStdin();
1618
+ proofData = JSON.parse(stdinData);
1619
+ } else {
1620
+ proofData = readProofFile(path3);
1621
+ }
1622
+ const proof2 = proofData.proof;
1623
+ const isComposed = "proofs" in proof2 && Array.isArray(proof2.proofs);
1624
+ if (format === "json") {
1625
+ json({
1626
+ type: isComposed ? "composed" : "single",
1627
+ ...proofData
1628
+ });
1629
+ return;
1630
+ }
1631
+ keyValue("Type", isComposed ? "Composed Proof" : "Single Proof");
1632
+ keyValue("ID", proof2.id);
1633
+ divider();
1634
+ if (isComposed) {
1635
+ const composed = proof2;
1636
+ keyValue("Strategy", composed.strategy);
1637
+ keyValue("Status", composed.status);
1638
+ keyValue("Component Proofs", composed.proofs.length.toString());
1639
+ info("\nComponent Proofs:");
1640
+ table(
1641
+ ["#", "System", "ID", "Public Inputs"],
1642
+ composed.proofs.map((p, i) => [
1643
+ (i + 1).toString(),
1644
+ formatProofSystem(getSystemFromProof(p)),
1645
+ formatHash(p.id),
1646
+ p.publicInputs.length.toString()
1647
+ ])
1648
+ );
1649
+ if (composed.compositionMetadata) {
1650
+ divider();
1651
+ info("Composition Metadata:");
1652
+ keyValue(" Proof Count", composed.compositionMetadata.proofCount.toString());
1653
+ keyValue(" Systems", composed.compositionMetadata.systems.join(", "));
1654
+ keyValue(" Composition Time", `${composed.compositionMetadata.compositionTimeMs}ms`);
1655
+ }
1656
+ } else {
1657
+ const single = proof2;
1658
+ keyValue("System", formatProofSystem(getSystemFromProof(single)));
1659
+ if (single.metadata) {
1660
+ keyValue("Circuit ID", single.metadata.circuitId || "N/A");
1661
+ keyValue("Circuit Version", single.metadata.circuitVersion || "N/A");
1662
+ keyValue("System Version", single.metadata.systemVersion || "N/A");
1663
+ keyValue("Proof Size", `${single.metadata.proofSizeBytes} bytes`);
1664
+ if (single.metadata.generatedAt) {
1665
+ keyValue("Generated At", new Date(single.metadata.generatedAt).toISOString());
1666
+ }
1667
+ }
1668
+ divider();
1669
+ info("Proof Data:");
1670
+ if (options.full) {
1671
+ keyValue("Proof", single.proof);
1672
+ } else {
1673
+ keyValue("Proof", formatHash(single.proof, 32));
1674
+ }
1675
+ info("\nPublic Inputs:");
1676
+ if (single.publicInputs && single.publicInputs.length > 0) {
1677
+ single.publicInputs.forEach((input, i) => {
1678
+ keyValue(` [${i}]`, formatHash(input, 16));
1679
+ });
1680
+ } else {
1681
+ info(" (none)");
1682
+ }
1683
+ }
1684
+ if (proofData.metadata) {
1685
+ divider();
1686
+ info("File Metadata:");
1687
+ Object.entries(proofData.metadata).forEach(([key, value]) => {
1688
+ keyValue(` ${key}`, typeof value === "object" ? JSON.stringify(value) : String(value));
1689
+ });
1690
+ }
1691
+ } catch (err) {
1692
+ if (format === "json") {
1693
+ json({ error: String(err) });
1694
+ } else {
1695
+ error(`Failed to inspect proof: ${err}`);
1696
+ }
1697
+ process.exit(1);
1698
+ }
1699
+ });
1700
+ proof.command("convert").description("Convert proof between formats").argument("<path>", "Source proof file path (or - for stdin)").requiredOption("-t, --to <format>", "Target format (sip|noir|halo2|kimchi|json)").option("-o, --output <path>", "Output file path (default: stdout)").option("--json", "Output as JSON", false).action(async (path3, options) => {
1701
+ const format = options.json ? "json" : "human";
1702
+ try {
1703
+ if (format === "human") {
1704
+ heading("Convert Proof Format");
1705
+ }
1706
+ let proofData;
1707
+ if (path3 === "-") {
1708
+ const stdinData = await readFromStdin();
1709
+ proofData = JSON.parse(stdinData);
1710
+ } else {
1711
+ proofData = readProofFile(path3);
1712
+ }
1713
+ const spin = format === "human" ? spinner(`Converting to ${options.to} format...`) : null;
1714
+ const converter = new UnifiedProofConverter();
1715
+ const targetFormat = options.to.toLowerCase();
1716
+ const single = proofData.proof;
1717
+ let result;
1718
+ if (targetFormat === "json") {
1719
+ result = proofData;
1720
+ } else if (targetFormat === "sip") {
1721
+ result = proofData;
1722
+ } else {
1723
+ const converted = converter.fromSIP(single);
1724
+ if (!converted.success || !converted.result) {
1725
+ throw new Error(converted.error || "Conversion failed");
1726
+ }
1727
+ result = converted.result;
1728
+ }
1729
+ spin?.succeed("Conversion complete");
1730
+ const outputData = {
1731
+ originalFormat: getSystemFromProof(single),
1732
+ targetFormat,
1733
+ proof: result,
1734
+ convertedAt: (/* @__PURE__ */ new Date()).toISOString()
1735
+ };
1736
+ if (options.output) {
1737
+ writeFileSync3(options.output, JSON.stringify(outputData, null, 2));
1738
+ if (format === "human") {
1739
+ success(`Converted proof written to ${options.output}`);
1740
+ }
1741
+ }
1742
+ if (format === "json" || !options.output) {
1743
+ json(outputData);
1744
+ }
1745
+ } catch (err) {
1746
+ if (format === "json") {
1747
+ json({ error: String(err) });
1748
+ } else {
1749
+ error(`Failed to convert proof: ${err}`);
1750
+ }
1751
+ process.exit(1);
1752
+ }
1753
+ });
1754
+ proof.command("systems").description("List supported proof systems and their capabilities").option("--json", "Output as JSON", false).action(async (options) => {
1755
+ const format = options.json ? "json" : "human";
1756
+ const systems = [
1757
+ {
1758
+ id: "noir",
1759
+ name: "Noir (Aztec)",
1760
+ curve: "BN254",
1761
+ features: ["SNARK", "Universal Setup", "Browser Support"],
1762
+ status: "Production"
1763
+ },
1764
+ {
1765
+ id: "halo2",
1766
+ name: "Halo2 (Zcash)",
1767
+ curve: "Pallas/Vesta",
1768
+ features: ["SNARK", "No Trusted Setup", "Recursive"],
1769
+ status: "Production"
1770
+ },
1771
+ {
1772
+ id: "kimchi",
1773
+ name: "Kimchi (Mina)",
1774
+ curve: "Pasta",
1775
+ features: ["SNARK", "Recursive", "Succinct"],
1776
+ status: "Production"
1777
+ },
1778
+ {
1779
+ id: "groth16",
1780
+ name: "Groth16",
1781
+ curve: "BN254",
1782
+ features: ["SNARK", "Smallest Proofs", "Fastest Verification"],
1783
+ status: "Supported"
1784
+ },
1785
+ {
1786
+ id: "plonk",
1787
+ name: "PLONK",
1788
+ curve: "BN254",
1789
+ features: ["SNARK", "Universal Setup", "Flexible"],
1790
+ status: "Supported"
1791
+ },
1792
+ {
1793
+ id: "stark",
1794
+ name: "STARK",
1795
+ curve: "None (Hash-based)",
1796
+ features: ["No Trusted Setup", "Post-Quantum", "Large Proofs"],
1797
+ status: "Experimental"
1798
+ }
1799
+ ];
1800
+ if (format === "json") {
1801
+ json({ systems });
1802
+ return;
1803
+ }
1804
+ heading("Supported Proof Systems");
1805
+ table(
1806
+ ["System", "Curve", "Status"],
1807
+ systems.map((s) => [s.name, s.curve, s.status])
1808
+ );
1809
+ divider();
1810
+ info("Features by System:");
1811
+ systems.forEach((s) => {
1812
+ keyValue(` ${s.name}`, s.features.join(", "));
1813
+ });
1814
+ });
1815
+ proof.command("compat").description("Check compatibility between proof systems").argument("<systems...>", "Proof systems to check (e.g., noir halo2)").option("--json", "Output as JSON", false).action(async (systems, options) => {
1816
+ const format = options.json ? "json" : "human";
1817
+ try {
1818
+ if (format === "human") {
1819
+ heading("Proof System Compatibility");
1820
+ }
1821
+ const mockProofs = systems.map((sys, i) => ({
1822
+ id: `mock_${sys}_${i}`,
1823
+ proof: "0x00",
1824
+ publicInputs: ["0x01"],
1825
+ metadata: {
1826
+ system: sys,
1827
+ systemVersion: "1.0.0",
1828
+ circuitId: "test",
1829
+ circuitVersion: "1.0.0",
1830
+ generatedAt: Date.now(),
1831
+ proofSizeBytes: 1
1832
+ }
1833
+ }));
1834
+ const validator = createCrossSystemValidator();
1835
+ const report = validator.validate(mockProofs, {
1836
+ skipFieldCheck: false,
1837
+ skipCurveCheck: false
1838
+ });
1839
+ const errors = report.checks.filter((c) => !c.passed && c.severity === "error");
1840
+ const warnings = report.checks.filter((c) => !c.passed && c.severity === "warning");
1841
+ const compatible = errors.length === 0;
1842
+ if (format === "json") {
1843
+ json({
1844
+ systems,
1845
+ compatible,
1846
+ errors: errors.map((e) => ({ check: e.name, message: e.message })),
1847
+ warnings: warnings.map((w) => ({ check: w.name, message: w.message })),
1848
+ report
1849
+ });
1850
+ return;
1851
+ }
1852
+ keyValue("Systems", systems.join(", "));
1853
+ keyValue("Compatible", compatible ? "Yes" : "No");
1854
+ if (compatible) {
1855
+ success("These proof systems can be composed together");
1856
+ } else {
1857
+ error("These proof systems are not directly compatible");
1858
+ divider();
1859
+ info("Issues:");
1860
+ errors.forEach((e) => {
1861
+ error(` - ${e.name}: ${e.message}`);
1862
+ });
1863
+ }
1864
+ if (warnings.length > 0) {
1865
+ divider();
1866
+ info("Warnings:");
1867
+ warnings.forEach((w) => {
1868
+ info(` \u26A0 ${w.name}: ${w.message}`);
1869
+ });
1870
+ }
1871
+ if (!compatible) {
1872
+ process.exit(1);
1873
+ }
1874
+ } catch (err) {
1875
+ if (format === "json") {
1876
+ json({ error: String(err) });
1877
+ } else {
1878
+ error(`Failed to check compatibility: ${err}`);
1879
+ }
1880
+ process.exit(1);
1881
+ }
1882
+ });
1883
+ return proof;
1884
+ }
1885
+
509
1886
  // 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");
1887
+ var program = new Command14();
1888
+ program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("0.2.0");
1889
+ program.addCommand(createSetupCommand());
512
1890
  program.addCommand(createInitCommand());
513
1891
  program.addCommand(createKeygenCommand());
1892
+ program.addCommand(createStealthCommand());
1893
+ program.addCommand(createViewingKeyCommand());
514
1894
  program.addCommand(createCommitCommand());
515
1895
  program.addCommand(createProveCommand());
516
1896
  program.addCommand(createVerifyCommand());
517
1897
  program.addCommand(createQuoteCommand());
518
1898
  program.addCommand(createSwapCommand());
519
1899
  program.addCommand(createScanCommand());
1900
+ program.addCommand(createBackendsCommand());
1901
+ program.addCommand(createProofCommand());
520
1902
  program.parse();