@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.
- package/dist/index.js +508 -23
- package/dist/index.mjs +524 -23
- 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
|
|
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
|
-
|
|
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.
|
|
360
|
-
|
|
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:
|
|
418
|
-
//
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
|
529
|
-
program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("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
|
|
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
|
-
|
|
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.
|
|
342
|
-
|
|
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:
|
|
400
|
-
//
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
|
511
|
-
program.name("sip").description("Shielded Intents Protocol (SIP) - Privacy layer for cross-chain transactions").version("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.
|
|
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
|
-
"
|
|
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
|
|
57
|
+
"test": "vitest"
|
|
56
58
|
}
|
|
57
59
|
}
|