@veil-cash/sdk 0.1.1 → 0.3.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.
@@ -82,19 +82,56 @@ function saveVeilKeypair(veilKey: string, depositKey: string, envPath: string):
82
82
  writeFileSync(envPath, content);
83
83
  }
84
84
 
85
+ /**
86
+ * Resolve wallet key from CLI flag or WALLET_KEY env var
87
+ */
88
+ function resolveWalletKey(options: { walletKey?: string }): `0x${string}` {
89
+ const raw = options.walletKey || process.env.WALLET_KEY;
90
+ if (!raw) {
91
+ throw new Error('Wallet key required for --sign-message. Use --wallet-key <key> or set WALLET_KEY env var.');
92
+ }
93
+ const key = raw.startsWith('0x') ? raw : `0x${raw}`;
94
+ if (key.length !== 66) {
95
+ throw new Error('Invalid wallet key format. Must be a 0x-prefixed 64-character hex string.');
96
+ }
97
+ return key as `0x${string}`;
98
+ }
99
+
85
100
  export function createInitCommand(): Command {
86
101
  const init = new Command('init')
87
102
  .description('Generate a new Veil keypair')
88
103
  .option('--force', 'Overwrite existing keypair without prompting')
89
104
  .option('--json', 'Output as JSON (no prompts, no file save)')
90
- .option('--out <path>', 'Save to custom path instead of .env.veil')
91
105
  .option('--no-save', 'Print keypair without saving to file')
106
+ .option('--sign-message', 'Derive keypair from wallet signature (same as frontend login)')
107
+ .option('--wallet-key <key>', 'Ethereum wallet private key (or set WALLET_KEY env var)')
108
+ .option('--signature <sig>', 'Derive keypair from a pre-computed EIP-191 personal_sign signature')
92
109
  .action(async (options) => {
93
- const envPath = options.out || getDefaultEnvPath();
110
+ const envPath = getDefaultEnvPath();
94
111
 
112
+ /**
113
+ * Create keypair: derived from wallet, from signature, or random
114
+ */
115
+ async function createKp(): Promise<Keypair> {
116
+ if (options.signMessage) {
117
+ const walletKey = resolveWalletKey(options);
118
+ return Keypair.fromWalletKey(walletKey);
119
+ }
120
+ if (options.signature) {
121
+ return Keypair.fromSignature(options.signature);
122
+ }
123
+ return new Keypair();
124
+ }
125
+
126
+ const derivationLabel = options.signMessage
127
+ ? 'Derived Veil keypair from wallet signature'
128
+ : options.signature
129
+ ? 'Derived Veil keypair from provided signature'
130
+ : 'Generated new Veil keypair';
131
+
95
132
  // JSON mode: no prompts, no save, just output JSON
96
133
  if (options.json) {
97
- const kp = new Keypair();
134
+ const kp = await createKp();
98
135
  console.log(JSON.stringify({
99
136
  veilKey: kp.privkey,
100
137
  depositKey: kp.depositKey(),
@@ -105,13 +142,13 @@ export function createInitCommand(): Command {
105
142
 
106
143
  // No-save mode: print but don't save
107
144
  if (!options.save) {
108
- const kp = new Keypair();
109
- console.log('\nGenerated new Veil keypair:\n');
145
+ const kp = await createKp();
146
+ console.log(`\n${derivationLabel}:\n`);
110
147
  console.log('Veil Private Key:');
111
148
  console.log(` ${kp.privkey}\n`);
112
149
  console.log('Deposit Key (register this on-chain):');
113
150
  console.log(` ${kp.depositKey()}\n`);
114
- console.log('(Not saved - use --out <path> to save to a file)');
151
+ console.log('(Not saved - run without --no-save to save to .env.veil)');
115
152
  process.exit(0);
116
153
  return;
117
154
  }
@@ -129,9 +166,9 @@ export function createInitCommand(): Command {
129
166
  }
130
167
  }
131
168
 
132
- const kp = new Keypair();
169
+ const kp = await createKp();
133
170
 
134
- console.log('\nGenerated new Veil keypair:\n');
171
+ console.log(`\n${derivationLabel}:\n`);
135
172
  console.log('Veil Private Key:');
136
173
  console.log(` ${kp.privkey}\n`);
137
174
  console.log('Deposit Key (register this on-chain):');
@@ -5,16 +5,20 @@
5
5
  import { Command } from 'commander';
6
6
  import { getPrivateBalance } from '../../balance.js';
7
7
  import { Keypair } from '../../keypair.js';
8
+ import type { RelayPool } from '../../types.js';
8
9
 
9
10
  export function createPrivateBalanceCommand(): Command {
10
11
  const privateBalance = new Command('private-balance')
11
12
  .description('Show private balance (requires VEIL_KEY)')
13
+ .option('--pool <pool>', 'Pool to check (eth, usdc, or cbbtc)', 'eth')
12
14
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
13
15
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
14
16
  .option('--show-utxos', 'Show individual UTXO details')
15
17
  .option('--quiet', 'Suppress progress output')
16
18
  .action(async (options) => {
17
19
  try {
20
+ const pool = (options.pool || 'eth').toLowerCase() as RelayPool;
21
+
18
22
  // Get keypair
19
23
  const veilKey = options.veilKey || process.env.VEIL_KEY;
20
24
  if (!veilKey) {
@@ -33,7 +37,7 @@ export function createPrivateBalanceCommand(): Command {
33
37
  };
34
38
 
35
39
  // Get private balance from SDK
36
- const result = await getPrivateBalance({ keypair, rpcUrl, onProgress });
40
+ const result = await getPrivateBalance({ keypair, pool, rpcUrl, onProgress });
37
41
 
38
42
  // Clear progress line
39
43
  if (!options.quiet) {
@@ -42,6 +46,7 @@ export function createPrivateBalanceCommand(): Command {
42
46
 
43
47
  // Format output
44
48
  const output: Record<string, unknown> = {
49
+ pool: pool.toUpperCase(),
45
50
  privateBalance: result.privateBalance,
46
51
  privateBalanceWei: result.privateBalanceWei,
47
52
  utxoCount: result.utxoCount,
@@ -5,16 +5,20 @@
5
5
  import { Command } from 'commander';
6
6
  import { getQueueBalance } from '../../balance.js';
7
7
  import { getAddress } from '../wallet.js';
8
+ import type { RelayPool } from '../../types.js';
8
9
 
9
10
  export function createQueueBalanceCommand(): Command {
10
11
  const balance = new Command('queue-balance')
11
12
  .description('Show queue balance and pending deposits')
13
+ .option('--pool <pool>', 'Pool to check (eth, usdc, or cbbtc)', 'eth')
12
14
  .option('--wallet-key <key>', 'Ethereum wallet key (or set WALLET_KEY env)')
13
15
  .option('--address <address>', 'Address to check (or derived from wallet key)')
14
16
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
15
17
  .option('--quiet', 'Suppress progress output')
16
18
  .action(async (options) => {
17
19
  try {
20
+ const pool = (options.pool || 'eth').toLowerCase() as RelayPool;
21
+
18
22
  // Get address
19
23
  let address: `0x${string}`;
20
24
  if (options.address) {
@@ -37,7 +41,7 @@ export function createQueueBalanceCommand(): Command {
37
41
 
38
42
  // Get queue balance from SDK
39
43
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
40
- const result = await getQueueBalance({ address, rpcUrl, onProgress });
44
+ const result = await getQueueBalance({ address, pool, rpcUrl, onProgress });
41
45
 
42
46
  // Clear progress line
43
47
  if (!options.quiet) {
@@ -3,19 +3,20 @@
3
3
  */
4
4
 
5
5
  import { Command } from 'commander';
6
- import { buildRegisterTx } from '../../deposit.js';
6
+ import { buildRegisterTx, buildChangeDepositKeyTx } from '../../deposit.js';
7
7
  import { sendTransaction, getAddress, isRegistered } from '../wallet.js';
8
8
  import { getConfig } from '../config.js';
9
9
  import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
10
10
 
11
11
  export function createRegisterCommand(): Command {
12
12
  const register = new Command('register')
13
- .description('Register your deposit key on-chain (one-time)')
13
+ .description('Register or update your deposit key on-chain')
14
14
  .option('--deposit-key <key>', 'Your Veil deposit key (or set DEPOSIT_KEY env)')
15
15
  .option('--wallet-key <key>', 'Ethereum wallet key for signing (or set WALLET_KEY env)')
16
16
  .option('--address <address>', 'Owner address (required with --unsigned)')
17
17
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
18
18
  .option('--unsigned', 'Output unsigned transaction payload (Bankr-compatible format)')
19
+ .option('--force', 'Change deposit key even if already registered')
19
20
  .option('--json', 'Output as JSON')
20
21
  .action(async (options) => {
21
22
  const jsonOutput = options.json;
@@ -40,9 +41,13 @@ export function createRegisterCommand(): Command {
40
41
  address = getAddress(walletKey as `0x${string}`);
41
42
  }
42
43
 
43
- const tx = buildRegisterTx(depositKey, address);
44
+ // Use changeDepositKey if --force is set, otherwise register
45
+ const tx = options.force
46
+ ? buildChangeDepositKeyTx(depositKey, address)
47
+ : buildRegisterTx(depositKey, address);
44
48
 
45
49
  const payload = {
50
+ action: options.force ? 'changeDepositKey' : 'register',
46
51
  to: tx.to,
47
52
  data: tx.data,
48
53
  value: '0',
@@ -62,45 +67,75 @@ export function createRegisterCommand(): Command {
62
67
  if (!jsonOutput) console.log('\nChecking registration status...');
63
68
  const { registered, depositKey: existingKey } = await isRegistered(address, config.rpcUrl);
64
69
 
65
- if (registered) {
70
+ // Determine if this is a new registration or a key change
71
+ const keysMatch = registered && existingKey === depositKey;
72
+ const isChange = registered && !keysMatch;
73
+
74
+ if (registered && keysMatch) {
75
+ // Already registered with the same key -- nothing to do
66
76
  if (jsonOutput) {
67
77
  console.log(JSON.stringify({
68
78
  success: true,
69
79
  alreadyRegistered: true,
80
+ keysMatch: true,
70
81
  address,
71
82
  depositKey: existingKey,
72
83
  }, null, 2));
73
84
  } else {
74
- console.log(`\nAddress ${address} is already registered.`);
75
- console.log(`\nExisting deposit key:`);
76
- console.log(` ${existingKey}`);
85
+ console.log(`\nAddress ${address} is already registered with this deposit key.`);
86
+ console.log(`\nDeposit key: ${existingKey!.slice(0, 40)}...`);
77
87
  }
78
88
  process.exit(0);
79
89
  return;
80
90
  }
81
91
 
92
+ if (isChange && !options.force) {
93
+ // Registered with a different key but --force not provided
94
+ if (jsonOutput) {
95
+ console.log(JSON.stringify({
96
+ success: false,
97
+ alreadyRegistered: true,
98
+ keysMatch: false,
99
+ address,
100
+ onChainKey: existingKey,
101
+ localKey: depositKey.slice(0, 40) + '...',
102
+ hint: 'Use --force to change deposit key',
103
+ }, null, 2));
104
+ } else {
105
+ console.log(`\nAddress ${address} is already registered with a different deposit key.`);
106
+ console.log(`\n On-chain key: ${existingKey!.slice(0, 40)}...`);
107
+ console.log(` Local key: ${depositKey.slice(0, 40)}...`);
108
+ console.log(`\nUse --force to change your deposit key on-chain.`);
109
+ }
110
+ process.exit(1);
111
+ return;
112
+ }
113
+
114
+ // Build the appropriate transaction
115
+ const action = isChange ? 'Changing deposit key' : 'Registering deposit key';
82
116
  if (!jsonOutput) {
83
- console.log('Registering deposit key...');
117
+ console.log(`${action}...`);
84
118
  console.log(` Address: ${address}`);
85
119
  console.log(` Deposit Key: ${depositKey.slice(0, 40)}...`);
86
120
  }
87
121
 
88
- // Build and send
89
- const tx = buildRegisterTx(depositKey, address);
122
+ const tx = isChange
123
+ ? buildChangeDepositKeyTx(depositKey, address)
124
+ : buildRegisterTx(depositKey, address);
90
125
  const result = await sendTransaction(config, tx);
91
126
 
92
127
  if (result.receipt.status === 'success') {
93
128
  if (jsonOutput) {
94
129
  console.log(JSON.stringify({
95
130
  success: true,
96
- alreadyRegistered: false,
131
+ action: isChange ? 'changed' : 'registered',
97
132
  address,
98
133
  transactionHash: result.hash,
99
134
  blockNumber: result.receipt.blockNumber.toString(),
100
135
  gasUsed: result.receipt.gasUsed.toString(),
101
136
  }, null, 2));
102
137
  } else {
103
- console.log('\nRegistration successful!');
138
+ console.log(isChange ? '\nDeposit key changed successfully!' : '\nRegistration successful!');
104
139
  console.log(` Transaction: ${result.hash}`);
105
140
  console.log(` Block: ${result.receipt.blockNumber}`);
106
141
  console.log(` Gas used: ${result.receipt.gasUsed}`);
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Status CLI command - Check configuration and service status
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { isRegistered, getAddress } from '../wallet.js';
7
+ import { checkRelayHealth } from '../../relay.js';
8
+
9
+ interface StatusResult {
10
+ walletKey: {
11
+ found: boolean;
12
+ address?: string;
13
+ };
14
+ veilKey: {
15
+ found: boolean;
16
+ };
17
+ depositKey: {
18
+ found: boolean;
19
+ key?: string;
20
+ };
21
+ rpcUrl: {
22
+ found: boolean;
23
+ url: string;
24
+ };
25
+ registration: {
26
+ checked: boolean;
27
+ registered?: boolean;
28
+ matches?: boolean;
29
+ onChainKey?: string | null;
30
+ error?: string;
31
+ };
32
+ relay: {
33
+ checked: boolean;
34
+ healthy?: boolean;
35
+ status?: string;
36
+ network?: string;
37
+ error?: string;
38
+ };
39
+ }
40
+
41
+ export function createStatusCommand(): Command {
42
+ const status = new Command('status')
43
+ .description('Check configuration and service status')
44
+ .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
45
+ .action(async (options) => {
46
+ const result: StatusResult = {
47
+ walletKey: { found: false },
48
+ veilKey: { found: false },
49
+ depositKey: { found: false },
50
+ rpcUrl: { found: false, url: 'https://mainnet.base.org' },
51
+ registration: { checked: false },
52
+ relay: { checked: false },
53
+ };
54
+
55
+ // Check WALLET_KEY
56
+ const walletKey = process.env.WALLET_KEY;
57
+ if (walletKey) {
58
+ result.walletKey.found = true;
59
+ try {
60
+ result.walletKey.address = getAddress(walletKey as `0x${string}`);
61
+ } catch {
62
+ // Invalid key format, but it was found
63
+ }
64
+ }
65
+
66
+ // Check VEIL_KEY
67
+ const veilKey = process.env.VEIL_KEY;
68
+ if (veilKey) {
69
+ result.veilKey.found = true;
70
+ }
71
+
72
+ // Check DEPOSIT_KEY
73
+ const depositKey = process.env.DEPOSIT_KEY;
74
+ if (depositKey) {
75
+ result.depositKey.found = true;
76
+ // Show truncated key
77
+ if (depositKey.length > 20) {
78
+ result.depositKey.key = `${depositKey.slice(0, 10)}...${depositKey.slice(-8)}`;
79
+ } else {
80
+ result.depositKey.key = depositKey;
81
+ }
82
+ }
83
+
84
+ // Check RPC_URL
85
+ const rpcUrl = options.rpcUrl || process.env.RPC_URL;
86
+ if (rpcUrl) {
87
+ result.rpcUrl.found = true;
88
+ result.rpcUrl.url = rpcUrl;
89
+ }
90
+ const effectiveRpcUrl = rpcUrl || 'https://mainnet.base.org';
91
+
92
+ // Check registration status (requires wallet address)
93
+ if (result.walletKey.found && result.walletKey.address) {
94
+ result.registration.checked = true;
95
+ try {
96
+ const regStatus = await isRegistered(
97
+ result.walletKey.address as `0x${string}`,
98
+ effectiveRpcUrl
99
+ );
100
+ result.registration.registered = regStatus.registered;
101
+ result.registration.onChainKey = regStatus.depositKey;
102
+
103
+ // Check if on-chain key matches env DEPOSIT_KEY
104
+ if (regStatus.registered && depositKey && regStatus.depositKey) {
105
+ result.registration.matches =
106
+ regStatus.depositKey.toLowerCase() === depositKey.toLowerCase();
107
+ }
108
+ } catch (error) {
109
+ result.registration.error = error instanceof Error ? error.message : 'Unknown error';
110
+ }
111
+ }
112
+
113
+ // Check relay health
114
+ result.relay.checked = true;
115
+ try {
116
+ const health = await checkRelayHealth();
117
+ result.relay.healthy = health.status === 'ok';
118
+ result.relay.status = health.status;
119
+ result.relay.network = health.network;
120
+ } catch (error) {
121
+ result.relay.healthy = false;
122
+ result.relay.error = error instanceof Error ? error.message : 'Unknown error';
123
+ }
124
+
125
+ console.log(JSON.stringify(result, null, 2));
126
+ });
127
+
128
+ return status;
129
+ }
@@ -6,6 +6,9 @@ import { Command } from 'commander';
6
6
  import { Keypair } from '../../keypair.js';
7
7
  import { transfer, mergeUtxos } from '../../transfer.js';
8
8
  import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
9
+ import type { RelayPool } from '../../types.js';
10
+
11
+ const SUPPORTED_ASSETS = ['ETH', 'USDC', 'CBBTC'];
9
12
 
10
13
  // Progress helper - writes to stderr so JSON output stays clean
11
14
  function progress(msg: string, quiet?: boolean) {
@@ -17,13 +20,21 @@ function progress(msg: string, quiet?: boolean) {
17
20
  export function createTransferCommand(): Command {
18
21
  const transferCmd = new Command('transfer')
19
22
  .description('Transfer privately within the pool to another registered address')
23
+ .argument('<asset>', 'Asset to transfer (ETH, USDC, or CBBTC)')
20
24
  .argument('<amount>', 'Amount to transfer (e.g., 0.1)')
21
25
  .argument('<recipient>', 'Recipient address (must be registered)')
22
26
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
23
27
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
24
28
  .option('--quiet', 'Suppress progress output')
25
- .action(async (amount: string, recipient: string, options) => {
29
+ .action(async (asset: string, amount: string, recipient: string, options) => {
26
30
  try {
31
+ const assetUpper = asset.toUpperCase();
32
+
33
+ // Validate asset
34
+ if (!SUPPORTED_ASSETS.includes(assetUpper)) {
35
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(', ')}`);
36
+ }
37
+
27
38
  // Validate recipient
28
39
  if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
29
40
  throw new CLIError(ErrorCode.INVALID_ADDRESS, 'Invalid recipient address format');
@@ -37,6 +48,7 @@ export function createTransferCommand(): Command {
37
48
 
38
49
  const senderKeypair = new Keypair(veilKey);
39
50
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
51
+ const pool = assetUpper.toLowerCase() as RelayPool;
40
52
 
41
53
  // Progress callback
42
54
  const onProgress = options.quiet
@@ -46,13 +58,14 @@ export function createTransferCommand(): Command {
46
58
  progress(msg, options.quiet);
47
59
  };
48
60
 
49
- progress('Starting transfer...', options.quiet);
61
+ progress(`Starting ${assetUpper} transfer...`, options.quiet);
50
62
 
51
63
  // Execute transfer
52
64
  const result = await transfer({
53
65
  amount,
54
66
  recipientAddress: recipient as `0x${string}`,
55
67
  senderKeypair,
68
+ pool,
56
69
  rpcUrl,
57
70
  onProgress,
58
71
  });
@@ -65,6 +78,7 @@ export function createTransferCommand(): Command {
65
78
  success: result.success,
66
79
  transactionHash: result.transactionHash,
67
80
  blockNumber: result.blockNumber,
81
+ asset: assetUpper,
68
82
  amount: result.amount,
69
83
  recipient: result.recipient,
70
84
  type: 'transfer',
@@ -82,12 +96,20 @@ export function createTransferCommand(): Command {
82
96
  export function createMergeCommand(): Command {
83
97
  const mergeCmd = new Command('merge')
84
98
  .description('Merge UTXOs by self-transfer (consolidate small UTXOs)')
99
+ .argument('<asset>', 'Asset to merge (ETH, USDC, or CBBTC)')
85
100
  .argument('<amount>', 'Amount to merge (e.g., 0.5)')
86
101
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
87
102
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
88
103
  .option('--quiet', 'Suppress progress output')
89
- .action(async (amount: string, options) => {
104
+ .action(async (asset: string, amount: string, options) => {
90
105
  try {
106
+ const assetUpper = asset.toUpperCase();
107
+
108
+ // Validate asset
109
+ if (!SUPPORTED_ASSETS.includes(assetUpper)) {
110
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(', ')}`);
111
+ }
112
+
91
113
  // Get keypair
92
114
  const veilKey = options.veilKey || process.env.VEIL_KEY;
93
115
  if (!veilKey) {
@@ -96,6 +118,7 @@ export function createMergeCommand(): Command {
96
118
 
97
119
  const keypair = new Keypair(veilKey);
98
120
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
121
+ const pool = assetUpper.toLowerCase() as RelayPool;
99
122
 
100
123
  // Progress callback
101
124
  const onProgress = options.quiet
@@ -105,12 +128,13 @@ export function createMergeCommand(): Command {
105
128
  progress(msg, options.quiet);
106
129
  };
107
130
 
108
- progress('Starting merge (self-transfer)...', options.quiet);
131
+ progress(`Starting ${assetUpper} merge (self-transfer)...`, options.quiet);
109
132
 
110
133
  // Execute merge
111
134
  const result = await mergeUtxos({
112
135
  amount,
113
136
  keypair,
137
+ pool,
114
138
  rpcUrl,
115
139
  onProgress,
116
140
  });
@@ -123,6 +147,7 @@ export function createMergeCommand(): Command {
123
147
  success: result.success,
124
148
  transactionHash: result.transactionHash,
125
149
  blockNumber: result.blockNumber,
150
+ asset: assetUpper,
126
151
  amount: result.amount,
127
152
  type: 'merge',
128
153
  }, null, 2));
@@ -6,6 +6,9 @@ import { Command } from 'commander';
6
6
  import { Keypair } from '../../keypair.js';
7
7
  import { withdraw } from '../../withdraw.js';
8
8
  import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
9
+ import type { RelayPool } from '../../types.js';
10
+
11
+ const SUPPORTED_ASSETS = ['ETH', 'USDC', 'CBBTC'];
9
12
 
10
13
  // Progress helper - writes to stderr so JSON output stays clean
11
14
  function progress(msg: string, quiet?: boolean) {
@@ -17,13 +20,21 @@ function progress(msg: string, quiet?: boolean) {
17
20
  export function createWithdrawCommand(): Command {
18
21
  const withdrawCmd = new Command('withdraw')
19
22
  .description('Withdraw from private pool to a public address')
23
+ .argument('<asset>', 'Asset to withdraw (ETH, USDC, or CBBTC)')
20
24
  .argument('<amount>', 'Amount to withdraw (e.g., 0.1)')
21
25
  .argument('<recipient>', 'Recipient address (e.g., 0x...)')
22
26
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
23
27
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
24
28
  .option('--quiet', 'Suppress progress output')
25
- .action(async (amount: string, recipient: string, options) => {
29
+ .action(async (asset: string, amount: string, recipient: string, options) => {
26
30
  try {
31
+ const assetUpper = asset.toUpperCase();
32
+
33
+ // Validate asset
34
+ if (!SUPPORTED_ASSETS.includes(assetUpper)) {
35
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(', ')}`);
36
+ }
37
+
27
38
  // Validate recipient
28
39
  if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
29
40
  throw new CLIError(ErrorCode.INVALID_ADDRESS, 'Invalid recipient address format');
@@ -37,6 +48,7 @@ export function createWithdrawCommand(): Command {
37
48
 
38
49
  const keypair = new Keypair(veilKey);
39
50
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
51
+ const pool = assetUpper.toLowerCase() as RelayPool;
40
52
 
41
53
  // Progress callback
42
54
  const onProgress = options.quiet
@@ -46,13 +58,14 @@ export function createWithdrawCommand(): Command {
46
58
  progress(msg, options.quiet);
47
59
  };
48
60
 
49
- progress('Starting withdrawal...', options.quiet);
61
+ progress(`Starting ${assetUpper} withdrawal...`, options.quiet);
50
62
 
51
63
  // Execute withdrawal
52
64
  const result = await withdraw({
53
65
  amount,
54
66
  recipient: recipient as `0x${string}`,
55
67
  keypair,
68
+ pool,
56
69
  rpcUrl,
57
70
  onProgress,
58
71
  });
@@ -65,6 +78,7 @@ export function createWithdrawCommand(): Command {
65
78
  success: result.success,
66
79
  transactionHash: result.transactionHash,
67
80
  blockNumber: result.blockNumber,
81
+ asset: assetUpper,
68
82
  amount: result.amount,
69
83
  recipient: result.recipient,
70
84
  }, null, 2));
package/src/cli/index.ts CHANGED
@@ -2,16 +2,17 @@
2
2
  * Veil CLI - Command-line interface for Veil Cash
3
3
  *
4
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)
5
+ * veil init # Generate keypair
6
+ * veil keypair # Show keypair (JSON)
7
+ * veil status # Check config and service status
8
+ * veil register # Register on-chain
9
+ * veil deposit ETH 0.1 # Deposit ETH
10
+ * veil balance # Show all balances
11
+ * veil queue-balance # Show pending queue deposits
12
+ * veil private-balance # Show private balance
13
+ * veil withdraw ETH 0.1 0x... # Withdraw to public address
14
+ * veil transfer ETH 0.1 0x... # Transfer privately
15
+ * veil merge ETH 0.5 # Merge UTXOs (self-transfer)
15
16
  */
16
17
 
17
18
  import { Command } from 'commander';
@@ -25,6 +26,7 @@ import { createQueueBalanceCommand } from './commands/queue-balance.js';
25
26
  import { createPrivateBalanceCommand } from './commands/private-balance.js';
26
27
  import { createWithdrawCommand } from './commands/withdraw.js';
27
28
  import { createTransferCommand, createMergeCommand } from './commands/transfer.js';
29
+ import { createStatusCommand } from './commands/status.js';
28
30
 
29
31
  // Load environment variables
30
32
  loadEnv();
@@ -34,7 +36,7 @@ const program = new Command();
34
36
  program
35
37
  .name('veil')
36
38
  .description('CLI for Veil Cash privacy pools on Base')
37
- .version('0.1.0');
39
+ .version('0.3.0');
38
40
 
39
41
  // Add commands
40
42
  program.addCommand(createInitCommand());
@@ -47,6 +49,7 @@ program.addCommand(createPrivateBalanceCommand());
47
49
  program.addCommand(createWithdrawCommand());
48
50
  program.addCommand(createTransferCommand());
49
51
  program.addCommand(createMergeCommand());
52
+ program.addCommand(createStatusCommand());
50
53
 
51
54
  // Parse and execute
52
55
  program.parse();