@veil-cash/sdk 0.1.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.
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Private Balance CLI command - Show private balance from Pool contract
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { getPrivateBalance } from '../../balance.js';
7
+ import { Keypair } from '../../keypair.js';
8
+
9
+ export function createPrivateBalanceCommand(): Command {
10
+ const privateBalance = new Command('private-balance')
11
+ .description('Show private balance (requires VEIL_KEY)')
12
+ .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
13
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
14
+ .option('--show-utxos', 'Show individual UTXO details')
15
+ .option('--quiet', 'Suppress progress output')
16
+ .action(async (options) => {
17
+ try {
18
+ // Get keypair
19
+ const veilKey = options.veilKey || process.env.VEIL_KEY;
20
+ if (!veilKey) {
21
+ throw new Error('Must provide --veil-key or set VEIL_KEY env');
22
+ }
23
+
24
+ const keypair = new Keypair(veilKey);
25
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL;
26
+
27
+ // Progress callback - writes to stderr so JSON output is clean
28
+ const onProgress = options.quiet
29
+ ? undefined
30
+ : (stage: string, detail?: string) => {
31
+ const msg = detail ? `${stage}: ${detail}` : stage;
32
+ process.stderr.write(`\r\x1b[K${msg}`);
33
+ };
34
+
35
+ // Get private balance from SDK
36
+ const result = await getPrivateBalance({ keypair, rpcUrl, onProgress });
37
+
38
+ // Clear progress line
39
+ if (!options.quiet) {
40
+ process.stderr.write('\r\x1b[K');
41
+ }
42
+
43
+ // Format output
44
+ const output: Record<string, unknown> = {
45
+ privateBalance: result.privateBalance,
46
+ privateBalanceWei: result.privateBalanceWei,
47
+ utxoCount: result.utxoCount,
48
+ unspentCount: result.unspentCount,
49
+ spentCount: result.spentCount,
50
+ };
51
+
52
+ // Optionally include UTXO details
53
+ if (options.showUtxos) {
54
+ output.utxos = result.utxos;
55
+ }
56
+
57
+ console.log(JSON.stringify(output, null, 2));
58
+ } catch (error) {
59
+ // Clear progress line on error
60
+ process.stderr.write('\r\x1b[K');
61
+ const errorMessage = error instanceof Error ? error.message : String(error);
62
+ console.log(JSON.stringify({ success: false, error: errorMessage }));
63
+ process.exit(1);
64
+ }
65
+ });
66
+
67
+ return privateBalance;
68
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Queue Balance CLI command - Show queue balance and pending deposits
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { getQueueBalance } from '../../balance.js';
7
+ import { getAddress } from '../wallet.js';
8
+
9
+ export function createQueueBalanceCommand(): Command {
10
+ const balance = new Command('queue-balance')
11
+ .description('Show queue balance and pending deposits')
12
+ .option('--wallet-key <key>', 'Ethereum wallet key (or set WALLET_KEY env)')
13
+ .option('--address <address>', 'Address to check (or derived from wallet key)')
14
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
15
+ .option('--quiet', 'Suppress progress output')
16
+ .action(async (options) => {
17
+ try {
18
+ // Get address
19
+ let address: `0x${string}`;
20
+ if (options.address) {
21
+ address = options.address as `0x${string}`;
22
+ } else {
23
+ const walletKey = options.walletKey || process.env.WALLET_KEY;
24
+ if (!walletKey) {
25
+ throw new Error('Must provide --address or --wallet-key (or set WALLET_KEY env)');
26
+ }
27
+ address = getAddress(walletKey as `0x${string}`);
28
+ }
29
+
30
+ // Progress callback - writes to stderr so JSON output is clean
31
+ const onProgress = options.quiet
32
+ ? undefined
33
+ : (stage: string, detail?: string) => {
34
+ const msg = detail ? `${stage}: ${detail}` : stage;
35
+ process.stderr.write(`\r\x1b[K${msg}`);
36
+ };
37
+
38
+ // Get queue balance from SDK
39
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL;
40
+ const result = await getQueueBalance({ address, rpcUrl, onProgress });
41
+
42
+ // Clear progress line
43
+ if (!options.quiet) {
44
+ process.stderr.write('\r\x1b[K');
45
+ }
46
+
47
+ console.log(JSON.stringify(result, null, 2));
48
+ } catch (error) {
49
+ // Clear progress line on error
50
+ process.stderr.write('\r\x1b[K');
51
+ const errorMessage = error instanceof Error ? error.message : String(error);
52
+ console.log(JSON.stringify({ success: false, error: errorMessage }));
53
+ process.exit(1);
54
+ }
55
+ });
56
+
57
+ return balance;
58
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Register CLI command - Register your deposit key on-chain
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { buildRegisterTx } from '../../deposit.js';
7
+ import { sendTransaction, getAddress, isRegistered } from '../wallet.js';
8
+ import { getConfig } from '../config.js';
9
+ import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
10
+
11
+ export function createRegisterCommand(): Command {
12
+ const register = new Command('register')
13
+ .description('Register your deposit key on-chain (one-time)')
14
+ .option('--deposit-key <key>', 'Your Veil deposit key (or set DEPOSIT_KEY env)')
15
+ .option('--wallet-key <key>', 'Ethereum wallet key for signing (or set WALLET_KEY env)')
16
+ .option('--address <address>', 'Owner address (required with --unsigned)')
17
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
18
+ .option('--unsigned', 'Output unsigned transaction payload (Bankr-compatible format)')
19
+ .option('--json', 'Output as JSON')
20
+ .action(async (options) => {
21
+ const jsonOutput = options.json;
22
+
23
+ try {
24
+ // Get deposit key from option or env
25
+ const depositKey = options.depositKey || process.env.DEPOSIT_KEY;
26
+ if (!depositKey) {
27
+ throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'Deposit key required. Run "veil init" first or use --deposit-key');
28
+ }
29
+
30
+ // Handle --unsigned mode (no wallet required, just build payload)
31
+ if (options.unsigned) {
32
+ let address: `0x${string}`;
33
+ if (options.address) {
34
+ address = options.address as `0x${string}`;
35
+ } else {
36
+ const walletKey = options.walletKey || process.env.WALLET_KEY;
37
+ if (!walletKey) {
38
+ throw new CLIError(ErrorCode.WALLET_KEY_MISSING, 'Must provide --address or --wallet-key for --unsigned mode');
39
+ }
40
+ address = getAddress(walletKey as `0x${string}`);
41
+ }
42
+
43
+ const tx = buildRegisterTx(depositKey, address);
44
+
45
+ const payload = {
46
+ to: tx.to,
47
+ data: tx.data,
48
+ value: '0',
49
+ chainId: 8453,
50
+ };
51
+
52
+ console.log(JSON.stringify(payload, null, 2));
53
+ process.exit(0);
54
+ return;
55
+ }
56
+
57
+ // Regular mode: sign and send
58
+ const config = getConfig(options);
59
+ const address = getAddress(config.privateKey);
60
+
61
+ // Check if already registered
62
+ if (!jsonOutput) console.log('\nChecking registration status...');
63
+ const { registered, depositKey: existingKey } = await isRegistered(address, config.rpcUrl);
64
+
65
+ if (registered) {
66
+ if (jsonOutput) {
67
+ console.log(JSON.stringify({
68
+ success: true,
69
+ alreadyRegistered: true,
70
+ address,
71
+ depositKey: existingKey,
72
+ }, null, 2));
73
+ } else {
74
+ console.log(`\nAddress ${address} is already registered.`);
75
+ console.log(`\nExisting deposit key:`);
76
+ console.log(` ${existingKey}`);
77
+ }
78
+ process.exit(0);
79
+ return;
80
+ }
81
+
82
+ if (!jsonOutput) {
83
+ console.log('Registering deposit key...');
84
+ console.log(` Address: ${address}`);
85
+ console.log(` Deposit Key: ${depositKey.slice(0, 40)}...`);
86
+ }
87
+
88
+ // Build and send
89
+ const tx = buildRegisterTx(depositKey, address);
90
+ const result = await sendTransaction(config, tx);
91
+
92
+ if (result.receipt.status === 'success') {
93
+ if (jsonOutput) {
94
+ console.log(JSON.stringify({
95
+ success: true,
96
+ alreadyRegistered: false,
97
+ address,
98
+ transactionHash: result.hash,
99
+ blockNumber: result.receipt.blockNumber.toString(),
100
+ gasUsed: result.receipt.gasUsed.toString(),
101
+ }, null, 2));
102
+ } else {
103
+ console.log('\nRegistration successful!');
104
+ console.log(` Transaction: ${result.hash}`);
105
+ console.log(` Block: ${result.receipt.blockNumber}`);
106
+ console.log(` Gas used: ${result.receipt.gasUsed}`);
107
+ console.log('\nNext step: veil deposit <amount>');
108
+ }
109
+ process.exit(0);
110
+ } else {
111
+ throw new CLIError(ErrorCode.CONTRACT_ERROR, `Transaction reverted: ${result.hash}`);
112
+ }
113
+ } catch (error) {
114
+ handleCLIError(error);
115
+ }
116
+ });
117
+
118
+ return register;
119
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Transfer CLI command
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { Keypair } from '../../keypair.js';
7
+ import { transfer, mergeUtxos } from '../../transfer.js';
8
+ import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
9
+
10
+ // Progress helper - writes to stderr so JSON output stays clean
11
+ function progress(msg: string, quiet?: boolean) {
12
+ if (!quiet) {
13
+ process.stderr.write(`\r\x1b[K${msg}`);
14
+ }
15
+ }
16
+
17
+ export function createTransferCommand(): Command {
18
+ const transferCmd = new Command('transfer')
19
+ .description('Transfer privately within the pool to another registered address')
20
+ .argument('<amount>', 'Amount to transfer (e.g., 0.1)')
21
+ .argument('<recipient>', 'Recipient address (must be registered)')
22
+ .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
23
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
24
+ .option('--quiet', 'Suppress progress output')
25
+ .action(async (amount: string, recipient: string, options) => {
26
+ try {
27
+ // Validate recipient
28
+ if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
29
+ throw new CLIError(ErrorCode.INVALID_ADDRESS, 'Invalid recipient address format');
30
+ }
31
+
32
+ // Get keypair
33
+ const veilKey = options.veilKey || process.env.VEIL_KEY;
34
+ if (!veilKey) {
35
+ throw new CLIError(ErrorCode.VEIL_KEY_MISSING, 'VEIL_KEY required. Use --veil-key or set VEIL_KEY env');
36
+ }
37
+
38
+ const senderKeypair = new Keypair(veilKey);
39
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL;
40
+
41
+ // Progress callback
42
+ const onProgress = options.quiet
43
+ ? undefined
44
+ : (stage: string, detail?: string) => {
45
+ const msg = detail ? `${stage}: ${detail}` : stage;
46
+ progress(msg, options.quiet);
47
+ };
48
+
49
+ progress('Starting transfer...', options.quiet);
50
+
51
+ // Execute transfer
52
+ const result = await transfer({
53
+ amount,
54
+ recipientAddress: recipient as `0x${string}`,
55
+ senderKeypair,
56
+ rpcUrl,
57
+ onProgress,
58
+ });
59
+
60
+ // Clear progress line
61
+ progress('', options.quiet);
62
+
63
+ // Output result
64
+ console.log(JSON.stringify({
65
+ success: result.success,
66
+ transactionHash: result.transactionHash,
67
+ blockNumber: result.blockNumber,
68
+ amount: result.amount,
69
+ recipient: result.recipient,
70
+ type: 'transfer',
71
+ }, null, 2));
72
+ process.exit(0);
73
+ } catch (error) {
74
+ progress('', options.quiet);
75
+ handleCLIError(error);
76
+ }
77
+ });
78
+
79
+ return transferCmd;
80
+ }
81
+
82
+ export function createMergeCommand(): Command {
83
+ const mergeCmd = new Command('merge')
84
+ .description('Merge UTXOs by self-transfer (consolidate small UTXOs)')
85
+ .argument('<amount>', 'Amount to merge (e.g., 0.5)')
86
+ .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
87
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
88
+ .option('--quiet', 'Suppress progress output')
89
+ .action(async (amount: string, options) => {
90
+ try {
91
+ // Get keypair
92
+ const veilKey = options.veilKey || process.env.VEIL_KEY;
93
+ if (!veilKey) {
94
+ throw new CLIError(ErrorCode.VEIL_KEY_MISSING, 'VEIL_KEY required. Use --veil-key or set VEIL_KEY env');
95
+ }
96
+
97
+ const keypair = new Keypair(veilKey);
98
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL;
99
+
100
+ // Progress callback
101
+ const onProgress = options.quiet
102
+ ? undefined
103
+ : (stage: string, detail?: string) => {
104
+ const msg = detail ? `${stage}: ${detail}` : stage;
105
+ progress(msg, options.quiet);
106
+ };
107
+
108
+ progress('Starting merge (self-transfer)...', options.quiet);
109
+
110
+ // Execute merge
111
+ const result = await mergeUtxos({
112
+ amount,
113
+ keypair,
114
+ rpcUrl,
115
+ onProgress,
116
+ });
117
+
118
+ // Clear progress line
119
+ progress('', options.quiet);
120
+
121
+ // Output result
122
+ console.log(JSON.stringify({
123
+ success: result.success,
124
+ transactionHash: result.transactionHash,
125
+ blockNumber: result.blockNumber,
126
+ amount: result.amount,
127
+ type: 'merge',
128
+ }, null, 2));
129
+ process.exit(0);
130
+ } catch (error) {
131
+ progress('', options.quiet);
132
+ handleCLIError(error);
133
+ }
134
+ });
135
+
136
+ return mergeCmd;
137
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Withdraw CLI command
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { Keypair } from '../../keypair.js';
7
+ import { withdraw } from '../../withdraw.js';
8
+ import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
9
+
10
+ // Progress helper - writes to stderr so JSON output stays clean
11
+ function progress(msg: string, quiet?: boolean) {
12
+ if (!quiet) {
13
+ process.stderr.write(`\r\x1b[K${msg}`);
14
+ }
15
+ }
16
+
17
+ export function createWithdrawCommand(): Command {
18
+ const withdrawCmd = new Command('withdraw')
19
+ .description('Withdraw from private pool to a public address')
20
+ .argument('<amount>', 'Amount to withdraw (e.g., 0.1)')
21
+ .argument('<recipient>', 'Recipient address (e.g., 0x...)')
22
+ .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
23
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
24
+ .option('--quiet', 'Suppress progress output')
25
+ .action(async (amount: string, recipient: string, options) => {
26
+ try {
27
+ // Validate recipient
28
+ if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
29
+ throw new CLIError(ErrorCode.INVALID_ADDRESS, 'Invalid recipient address format');
30
+ }
31
+
32
+ // Get keypair
33
+ const veilKey = options.veilKey || process.env.VEIL_KEY;
34
+ if (!veilKey) {
35
+ throw new CLIError(ErrorCode.VEIL_KEY_MISSING, 'VEIL_KEY required. Use --veil-key or set VEIL_KEY env');
36
+ }
37
+
38
+ const keypair = new Keypair(veilKey);
39
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL;
40
+
41
+ // Progress callback
42
+ const onProgress = options.quiet
43
+ ? undefined
44
+ : (stage: string, detail?: string) => {
45
+ const msg = detail ? `${stage}: ${detail}` : stage;
46
+ progress(msg, options.quiet);
47
+ };
48
+
49
+ progress('Starting withdrawal...', options.quiet);
50
+
51
+ // Execute withdrawal
52
+ const result = await withdraw({
53
+ amount,
54
+ recipient: recipient as `0x${string}`,
55
+ keypair,
56
+ rpcUrl,
57
+ onProgress,
58
+ });
59
+
60
+ // Clear progress line
61
+ progress('', options.quiet);
62
+
63
+ // Output result
64
+ console.log(JSON.stringify({
65
+ success: result.success,
66
+ transactionHash: result.transactionHash,
67
+ blockNumber: result.blockNumber,
68
+ amount: result.amount,
69
+ recipient: result.recipient,
70
+ }, null, 2));
71
+ process.exit(0);
72
+ } catch (error) {
73
+ progress('', options.quiet);
74
+ handleCLIError(error);
75
+ }
76
+ });
77
+
78
+ return withdrawCmd;
79
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * CLI configuration handling
3
+ */
4
+
5
+ import type { WalletConfig } from './wallet.js';
6
+
7
+ export interface CLIOptions {
8
+ walletKey?: string;
9
+ rpcUrl?: string;
10
+ }
11
+
12
+ /**
13
+ * Get wallet configuration from CLI options and environment variables
14
+ */
15
+ export function getConfig(options: CLIOptions): WalletConfig {
16
+ const walletKey = options.walletKey || process.env.WALLET_KEY;
17
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL || 'https://mainnet.base.org';
18
+
19
+ if (!walletKey) {
20
+ throw new Error('Wallet key required. Use --wallet-key or set WALLET_KEY environment variable.');
21
+ }
22
+
23
+ // Validate wallet key format
24
+ if (!walletKey.startsWith('0x') || walletKey.length !== 66) {
25
+ throw new Error('Invalid wallet key format. Must be a 0x-prefixed 64-character hex string.');
26
+ }
27
+
28
+ return {
29
+ privateKey: walletKey as `0x${string}`,
30
+ rpcUrl,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Get Veil private key from CLI options or environment
36
+ */
37
+ export function getVeilKey(options: { veilKey?: string }): string | undefined {
38
+ return options.veilKey || process.env.VEIL_KEY;
39
+ }
40
+
41
+ /**
42
+ * Load environment variables from .env.veil and .env files
43
+ */
44
+ export function loadEnv(): void {
45
+ try {
46
+ // Dynamic import to avoid bundling issues
47
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
48
+ const dotenv = require('dotenv');
49
+
50
+ // Load .env.veil first (Veil-specific config)
51
+ dotenv.config({ path: '.env.veil', quiet: true });
52
+
53
+ // Then load .env (for WALLET_KEY, RPC_URL if not in .env.veil)
54
+ dotenv.config({ quiet: true });
55
+ } catch {
56
+ // dotenv not available, skip
57
+ }
58
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Standardized CLI error handling
3
+ */
4
+
5
+ /**
6
+ * Error codes for machine-readable error handling
7
+ */
8
+ export const ErrorCode = {
9
+ VEIL_KEY_MISSING: 'VEIL_KEY_MISSING',
10
+ WALLET_KEY_MISSING: 'WALLET_KEY_MISSING',
11
+ DEPOSIT_KEY_MISSING: 'DEPOSIT_KEY_MISSING',
12
+ INVALID_ADDRESS: 'INVALID_ADDRESS',
13
+ INVALID_AMOUNT: 'INVALID_AMOUNT',
14
+ INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
15
+ USER_NOT_REGISTERED: 'USER_NOT_REGISTERED',
16
+ USER_ALREADY_REGISTERED: 'USER_ALREADY_REGISTERED',
17
+ NO_UTXOS: 'NO_UTXOS',
18
+ RELAY_ERROR: 'RELAY_ERROR',
19
+ RPC_ERROR: 'RPC_ERROR',
20
+ CONTRACT_ERROR: 'CONTRACT_ERROR',
21
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
22
+ } as const;
23
+
24
+ export type ErrorCodeType = typeof ErrorCode[keyof typeof ErrorCode];
25
+
26
+ /**
27
+ * CLI Error with error code
28
+ */
29
+ export class CLIError extends Error {
30
+ code: ErrorCodeType;
31
+
32
+ constructor(code: ErrorCodeType, message: string) {
33
+ super(message);
34
+ this.code = code;
35
+ this.name = 'CLIError';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Map common error messages to error codes
41
+ */
42
+ function inferErrorCode(message: string): ErrorCodeType {
43
+ const msg = message.toLowerCase();
44
+
45
+ if (msg.includes('veil_key') || msg.includes('veil key')) {
46
+ return ErrorCode.VEIL_KEY_MISSING;
47
+ }
48
+ if (msg.includes('wallet_key') || msg.includes('wallet key')) {
49
+ return ErrorCode.WALLET_KEY_MISSING;
50
+ }
51
+ if (msg.includes('deposit_key') || msg.includes('deposit key')) {
52
+ return ErrorCode.DEPOSIT_KEY_MISSING;
53
+ }
54
+ if (msg.includes('invalid') && msg.includes('address')) {
55
+ return ErrorCode.INVALID_ADDRESS;
56
+ }
57
+ if (msg.includes('insufficient balance') || msg.includes('not enough')) {
58
+ return ErrorCode.INSUFFICIENT_BALANCE;
59
+ }
60
+ if (msg.includes('not registered')) {
61
+ return ErrorCode.USER_NOT_REGISTERED;
62
+ }
63
+ if (msg.includes('already registered')) {
64
+ return ErrorCode.USER_ALREADY_REGISTERED;
65
+ }
66
+ if (msg.includes('no unspent') || msg.includes('no utxo')) {
67
+ return ErrorCode.NO_UTXOS;
68
+ }
69
+ if (msg.includes('relay')) {
70
+ return ErrorCode.RELAY_ERROR;
71
+ }
72
+ if (msg.includes('rpc') || msg.includes('network') || msg.includes('connection')) {
73
+ return ErrorCode.RPC_ERROR;
74
+ }
75
+ if (msg.includes('revert') || msg.includes('contract')) {
76
+ return ErrorCode.CONTRACT_ERROR;
77
+ }
78
+
79
+ return ErrorCode.UNKNOWN_ERROR;
80
+ }
81
+
82
+ /**
83
+ * Format error for JSON output
84
+ */
85
+ export function formatErrorOutput(error: unknown): { success: false; errorCode: ErrorCodeType; error: string } {
86
+ let code: ErrorCodeType;
87
+ let message: string;
88
+
89
+ if (error instanceof CLIError) {
90
+ code = error.code;
91
+ message = error.message;
92
+ } else if (error instanceof Error) {
93
+ message = error.message;
94
+ code = inferErrorCode(message);
95
+ } else {
96
+ message = String(error);
97
+ code = inferErrorCode(message);
98
+ }
99
+
100
+ return {
101
+ success: false,
102
+ errorCode: code,
103
+ error: message,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Handle CLI error: print JSON and exit
109
+ */
110
+ export function handleCLIError(error: unknown): never {
111
+ const output = formatErrorOutput(error);
112
+ console.log(JSON.stringify(output, null, 2));
113
+ process.exit(1);
114
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Veil CLI - Command-line interface for Veil Cash
3
+ *
4
+ * Usage:
5
+ * veil init # Generate keypair
6
+ * veil keypair # Show keypair (JSON)
7
+ * veil register # Register on-chain
8
+ * veil deposit 0.1 # Deposit ETH
9
+ * veil balance # Show all balances
10
+ * veil queue-balance # Show pending queue deposits
11
+ * veil private-balance # Show private balance
12
+ * veil withdraw 0.1 --recipient 0x... # Withdraw to public address
13
+ * veil transfer 0.1 --to 0x... # Transfer privately
14
+ * veil merge 0.5 # Merge UTXOs (self-transfer)
15
+ */
16
+
17
+ import { Command } from 'commander';
18
+ import { loadEnv } from './config.js';
19
+ import { createInitCommand } from './commands/init.js';
20
+ import { createKeypairCommand } from './commands/keypair.js';
21
+ import { createRegisterCommand } from './commands/register.js';
22
+ import { createDepositCommand } from './commands/deposit.js';
23
+ import { createBalanceCommand } from './commands/balance.js';
24
+ import { createQueueBalanceCommand } from './commands/queue-balance.js';
25
+ import { createPrivateBalanceCommand } from './commands/private-balance.js';
26
+ import { createWithdrawCommand } from './commands/withdraw.js';
27
+ import { createTransferCommand, createMergeCommand } from './commands/transfer.js';
28
+
29
+ // Load environment variables
30
+ loadEnv();
31
+
32
+ const program = new Command();
33
+
34
+ program
35
+ .name('veil')
36
+ .description('CLI for Veil Cash privacy pools on Base')
37
+ .version('0.1.0');
38
+
39
+ // Add commands
40
+ program.addCommand(createInitCommand());
41
+ program.addCommand(createKeypairCommand());
42
+ program.addCommand(createRegisterCommand());
43
+ program.addCommand(createDepositCommand());
44
+ program.addCommand(createBalanceCommand());
45
+ program.addCommand(createQueueBalanceCommand());
46
+ program.addCommand(createPrivateBalanceCommand());
47
+ program.addCommand(createWithdrawCommand());
48
+ program.addCommand(createTransferCommand());
49
+ program.addCommand(createMergeCommand());
50
+
51
+ // Parse and execute
52
+ program.parse();