@veil-cash/sdk 0.4.0 → 0.6.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.
@@ -5,57 +5,63 @@
5
5
  import { Command } from 'commander';
6
6
  import { buildRegisterTx, buildChangeDepositKeyTx } from '../../deposit.js';
7
7
  import { sendTransaction, getAddress, isRegistered } from '../wallet.js';
8
- import { getConfig } from '../config.js';
8
+ import { getConfig, resolveAddress } from '../config.js';
9
9
  import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
10
+ import { maskValue, printFields, printHeader, printJson, printLine, txUrl } from '../output.js';
10
11
 
11
12
  export function createRegisterCommand(): Command {
12
13
  const register = new Command('register')
13
14
  .description('Register or update your deposit key on-chain')
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('--force', 'Change deposit key even if already registered')
20
- .option('--json', 'Output as JSON')
15
+ .option('--address <address>', 'Signer address (optional if SIGNER_ADDRESS or WALLET_KEY is set in --unsigned mode)')
16
+ .option('--unsigned', 'Output unsigned transaction payload instead of sending')
17
+ .option('--force', 'Change deposit key even if already registered')
18
+ .option('--json', 'Output as JSON')
19
+ .addHelpText('after', `
20
+ Examples:
21
+ veil register
22
+ veil register --force
23
+ veil register --unsigned --address 0x...
24
+ SIGNER_ADDRESS=0x... veil register --unsigned
25
+ veil register --json
26
+ `)
21
27
  .action(async (options) => {
22
28
  const jsonOutput = options.json;
23
29
 
24
30
  try {
25
31
  // Get deposit key from option or env
26
- const depositKey = options.depositKey || process.env.DEPOSIT_KEY;
32
+ const depositKey = process.env.DEPOSIT_KEY;
27
33
  if (!depositKey) {
28
- throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'Deposit key required. Run "veil init" first or use --deposit-key');
34
+ throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'DEPOSIT_KEY not set. Run "veil init" first.');
29
35
  }
30
36
 
31
37
  // Handle --unsigned mode (no wallet required, just build payload)
32
38
  if (options.unsigned) {
33
- let address: `0x${string}`;
34
- if (options.address) {
35
- address = options.address as `0x${string}`;
36
- } else {
37
- const walletKey = options.walletKey || process.env.WALLET_KEY;
38
- if (!walletKey) {
39
- throw new CLIError(ErrorCode.WALLET_KEY_MISSING, 'Must provide --address or --wallet-key for --unsigned mode');
40
- }
41
- address = getAddress(walletKey as `0x${string}`);
39
+ const resolvedAddress = resolveAddress({ address: options.address }, { required: true });
40
+ if (!resolvedAddress) {
41
+ throw new CLIError(
42
+ ErrorCode.WALLET_KEY_MISSING,
43
+ 'Must provide --address, set SIGNER_ADDRESS, or set WALLET_KEY env.',
44
+ );
42
45
  }
46
+ const address = resolvedAddress.address;
43
47
 
44
- // Use changeDepositKey if --force is set, otherwise register
45
- const tx = options.force
48
+ const rpcUrl = process.env.RPC_URL;
49
+ const isChange = options.force
50
+ ? (await isRegistered(address, rpcUrl)).registered
51
+ : false;
52
+ const tx = isChange
46
53
  ? buildChangeDepositKeyTx(depositKey, address)
47
54
  : buildRegisterTx(depositKey, address);
48
55
 
49
56
  const payload = {
50
- action: options.force ? 'changeDepositKey' : 'register',
57
+ action: isChange ? 'changeDepositKey' : 'register',
51
58
  to: tx.to,
52
59
  data: tx.data,
53
60
  value: '0',
54
61
  chainId: 8453,
55
62
  };
56
63
 
57
- console.log(JSON.stringify(payload, null, 2));
58
- process.exit(0);
64
+ printJson(payload);
59
65
  return;
60
66
  }
61
67
 
@@ -64,35 +70,35 @@ export function createRegisterCommand(): Command {
64
70
  const address = getAddress(config.privateKey);
65
71
 
66
72
  // Check if already registered
67
- if (!jsonOutput) console.log('\nChecking registration status...');
73
+ if (!jsonOutput) printLine('Checking registration status...');
68
74
  const { registered, depositKey: existingKey } = await isRegistered(address, config.rpcUrl);
69
75
 
70
76
  // Determine if this is a new registration or a key change
71
77
  const keysMatch = registered && existingKey === depositKey;
72
- const isChange = registered && !keysMatch;
73
78
 
74
- if (registered && keysMatch) {
75
- // Already registered with the same key -- nothing to do
79
+ if (registered && keysMatch && !options.force) {
76
80
  if (jsonOutput) {
77
- console.log(JSON.stringify({
81
+ printJson({
78
82
  success: true,
79
83
  alreadyRegistered: true,
80
84
  keysMatch: true,
81
85
  address,
82
86
  depositKey: existingKey,
83
- }, null, 2));
87
+ });
84
88
  } else {
85
- console.log(`\nAddress ${address} is already registered with this deposit key.`);
86
- console.log(`\nDeposit key: ${existingKey!.slice(0, 40)}...`);
89
+ printHeader('Already Registered');
90
+ printFields([
91
+ { label: 'Address', value: address },
92
+ { label: 'Deposit key', value: maskValue(existingKey!) },
93
+ ]);
94
+ printLine();
87
95
  }
88
- process.exit(0);
89
96
  return;
90
97
  }
91
98
 
92
- if (isChange && !options.force) {
93
- // Registered with a different key but --force not provided
99
+ if (registered && !keysMatch && !options.force) {
94
100
  if (jsonOutput) {
95
- console.log(JSON.stringify({
101
+ printJson({
96
102
  success: false,
97
103
  alreadyRegistered: true,
98
104
  keysMatch: false,
@@ -100,23 +106,31 @@ export function createRegisterCommand(): Command {
100
106
  onChainKey: existingKey,
101
107
  localKey: depositKey.slice(0, 40) + '...',
102
108
  hint: 'Use --force to change deposit key',
103
- }, null, 2));
109
+ });
104
110
  } 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.`);
111
+ printLine('A different deposit key is already registered');
112
+ printFields([
113
+ { label: 'Address', value: address },
114
+ { label: 'On-chain key', value: maskValue(existingKey!) },
115
+ { label: 'Local key', value: maskValue(depositKey) },
116
+ ]);
117
+ printLine('');
118
+ printLine('Use `veil register --force` to change your deposit key on-chain.');
109
119
  }
110
120
  process.exit(1);
111
121
  return;
112
122
  }
113
123
 
124
+ const isChange = registered && options.force;
125
+
114
126
  // Build the appropriate transaction
115
127
  const action = isChange ? 'Changing deposit key' : 'Registering deposit key';
116
128
  if (!jsonOutput) {
117
- console.log(`${action}...`);
118
- console.log(` Address: ${address}`);
119
- console.log(` Deposit Key: ${depositKey.slice(0, 40)}...`);
129
+ printLine(`${action}...`);
130
+ printFields([
131
+ { label: 'Address', value: address },
132
+ { label: 'Deposit key', value: maskValue(depositKey) },
133
+ ]);
120
134
  }
121
135
 
122
136
  const tx = isChange
@@ -126,22 +140,22 @@ export function createRegisterCommand(): Command {
126
140
 
127
141
  if (result.receipt.status === 'success') {
128
142
  if (jsonOutput) {
129
- console.log(JSON.stringify({
143
+ printJson({
130
144
  success: true,
131
145
  action: isChange ? 'changed' : 'registered',
132
146
  address,
133
147
  transactionHash: result.hash,
134
148
  blockNumber: result.receipt.blockNumber.toString(),
135
- gasUsed: result.receipt.gasUsed.toString(),
136
- }, null, 2));
149
+ });
137
150
  } else {
138
- console.log(isChange ? '\nDeposit key changed successfully!' : '\nRegistration successful!');
139
- console.log(` Transaction: ${result.hash}`);
140
- console.log(` Block: ${result.receipt.blockNumber}`);
141
- console.log(` Gas used: ${result.receipt.gasUsed}`);
142
- console.log('\nNext step: veil deposit <amount>');
151
+ printLine(isChange ? 'Deposit key changed successfully' : 'Registration successful');
152
+ printFields([
153
+ { label: 'Transaction', value: txUrl(result.hash) },
154
+ { label: 'Block', value: result.receipt.blockNumber },
155
+ ]);
156
+ printLine('');
157
+ printLine('Next step: veil deposit <asset> <amount>');
143
158
  }
144
- process.exit(0);
145
159
  } else {
146
160
  throw new CLIError(ErrorCode.CONTRACT_ERROR, `Transaction reverted: ${result.hash}`);
147
161
  }
@@ -3,13 +3,28 @@
3
3
  */
4
4
 
5
5
  import { Command } from 'commander';
6
- import { isRegistered, getAddress } from '../wallet.js';
6
+ import { isRegistered, getAddress, getWalletBalances } from '../wallet.js';
7
+ import { resolveAddress } from '../config.js';
7
8
  import { checkRelayHealth } from '../../relay.js';
9
+ import { handleCLIError } from '../errors.js';
10
+ import { maskValue, printFields, printHeader, printJson, printSection } from '../output.js';
8
11
 
9
12
  interface StatusResult {
10
13
  walletKey: {
11
14
  found: boolean;
12
15
  address?: string;
16
+ valid?: boolean;
17
+ ethBalance?: string;
18
+ error?: string;
19
+ };
20
+ signerAddress: {
21
+ found: boolean;
22
+ address?: string;
23
+ valid?: boolean;
24
+ };
25
+ resolvedAddress: {
26
+ address?: string;
27
+ source?: string;
13
28
  };
14
29
  veilKey: {
15
30
  found: boolean;
@@ -41,89 +56,200 @@ interface StatusResult {
41
56
  export function createStatusCommand(): Command {
42
57
  const status = new Command('status')
43
58
  .description('Check configuration and service status')
44
- .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
59
+ .option('--json', 'Output as JSON')
60
+ .addHelpText('after', `
61
+ Examples:
62
+ veil status
63
+ veil status --json
64
+ `)
45
65
  .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
66
+ try {
67
+ const result: StatusResult = {
68
+ walletKey: { found: false },
69
+ signerAddress: { found: false },
70
+ resolvedAddress: {},
71
+ veilKey: { found: false },
72
+ depositKey: { found: false },
73
+ rpcUrl: { found: false, url: 'https://mainnet.base.org' },
74
+ registration: { checked: false },
75
+ relay: { checked: false },
76
+ };
77
+
78
+ // Check WALLET_KEY
79
+ const walletKey = process.env.WALLET_KEY;
80
+ if (walletKey) {
81
+ result.walletKey.found = true;
82
+ try {
83
+ result.walletKey.address = getAddress(walletKey as `0x${string}`);
84
+ result.walletKey.valid = true;
85
+ } catch {
86
+ result.walletKey.valid = false;
87
+ result.walletKey.error = 'invalid format';
88
+ }
63
89
  }
64
- }
65
90
 
66
- // Check VEIL_KEY
67
- const veilKey = process.env.VEIL_KEY;
68
- if (veilKey) {
69
- result.veilKey.found = true;
70
- }
91
+ // Check SIGNER_ADDRESS
92
+ const signerAddress = process.env.SIGNER_ADDRESS;
93
+ if (signerAddress) {
94
+ result.signerAddress.found = true;
95
+ if (/^0x[a-fA-F0-9]{40}$/.test(signerAddress.trim())) {
96
+ result.signerAddress.valid = true;
97
+ result.signerAddress.address = signerAddress.trim();
98
+ } else {
99
+ result.signerAddress.valid = false;
100
+ }
101
+ }
71
102
 
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;
103
+ // Check VEIL_KEY
104
+ const veilKey = process.env.VEIL_KEY;
105
+ if (veilKey) {
106
+ result.veilKey.found = true;
81
107
  }
82
- }
83
108
 
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';
109
+ // Check DEPOSIT_KEY
110
+ const depositKey = process.env.DEPOSIT_KEY;
111
+ if (depositKey) {
112
+ result.depositKey.found = true;
113
+ if (depositKey.length > 20) {
114
+ result.depositKey.key = `${depositKey.slice(0, 10)}...${depositKey.slice(-8)}`;
115
+ } else {
116
+ result.depositKey.key = depositKey;
117
+ }
118
+ }
91
119
 
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();
120
+ // Check RPC_URL
121
+ const rpcUrl = process.env.RPC_URL;
122
+ if (rpcUrl) {
123
+ result.rpcUrl.found = true;
124
+ result.rpcUrl.url = rpcUrl;
125
+ }
126
+ const effectiveRpcUrl = rpcUrl || 'https://mainnet.base.org';
127
+
128
+ const resolvedAddress = resolveAddress({}, { required: false, allowInvalidWalletKey: true });
129
+ if (resolvedAddress) {
130
+ result.resolvedAddress.address = resolvedAddress.address;
131
+ result.resolvedAddress.source =
132
+ resolvedAddress.source === 'wallet-key'
133
+ ? 'WALLET_KEY'
134
+ : resolvedAddress.source === 'signer-address'
135
+ ? 'SIGNER_ADDRESS'
136
+ : '--address';
137
+
138
+ try {
139
+ const walletBalances = await getWalletBalances(
140
+ resolvedAddress.address,
141
+ effectiveRpcUrl
142
+ );
143
+ result.walletKey.ethBalance = walletBalances.eth;
144
+ } catch {
145
+ // Ignore wallet balance lookup errors so status can still report config/registration
146
+ }
147
+
148
+ result.registration.checked = true;
149
+ try {
150
+ const regStatus = await isRegistered(
151
+ resolvedAddress.address,
152
+ effectiveRpcUrl
153
+ );
154
+ result.registration.registered = regStatus.registered;
155
+ result.registration.onChainKey = regStatus.depositKey;
156
+
157
+ if (regStatus.registered && depositKey && regStatus.depositKey) {
158
+ result.registration.matches =
159
+ regStatus.depositKey.toLowerCase() === depositKey.toLowerCase();
160
+ }
161
+ } catch (error) {
162
+ result.registration.error = error instanceof Error ? error.message : 'Unknown error';
107
163
  }
164
+ }
165
+
166
+ // Check relay health
167
+ const relayUrl = process.env.RELAY_URL;
168
+ result.relay.checked = true;
169
+ try {
170
+ const health = await checkRelayHealth(relayUrl);
171
+ result.relay.healthy = health.status === 'ok';
172
+ result.relay.status = health.status;
173
+ result.relay.network = health.network;
108
174
  } catch (error) {
109
- result.registration.error = error instanceof Error ? error.message : 'Unknown error';
175
+ result.relay.healthy = false;
176
+ result.relay.error = error instanceof Error ? error.message : 'Unknown error';
110
177
  }
111
- }
112
178
 
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;
179
+ if (options.json) {
180
+ printJson(result);
181
+ return;
182
+ }
183
+
184
+ const signingValue = result.signerAddress.found
185
+ ? result.signerAddress.valid === false
186
+ ? 'external (SIGNER_ADDRESS invalid)'
187
+ : 'external (SIGNER_ADDRESS)'
188
+ : result.walletKey.found
189
+ ? result.walletKey.valid === false
190
+ ? 'local (WALLET_KEY invalid)'
191
+ : 'local (WALLET_KEY)'
192
+ : 'not configured';
193
+
194
+ printHeader('Veil CLI Status');
195
+ printSection('Configuration');
196
+ printFields([
197
+ { label: 'Signing', value: signingValue },
198
+ { label: 'Address', value: result.resolvedAddress.address || 'n/a' },
199
+ { label: 'ETH balance', value: result.walletKey.ethBalance ? `${result.walletKey.ethBalance} ETH` : 'n/a' },
200
+ { label: 'Veil key', value: result.veilKey.found ? 'configured' : 'missing' },
201
+ { label: 'Deposit key', value: result.depositKey.found ? maskValue(result.depositKey.key || '') : 'missing' },
202
+ { label: 'RPC URL', value: maskUrl(result.rpcUrl.url) },
203
+ ]);
204
+
205
+ printSection('Registration');
206
+ if (!result.registration.checked) {
207
+ printFields([
208
+ {
209
+ label: 'Status',
210
+ value: result.walletKey.found && result.walletKey.valid === false
211
+ ? 'skipped (invalid WALLET_KEY)'
212
+ : 'skipped (no WALLET_KEY or SIGNER_ADDRESS)'
213
+ },
214
+ ]);
215
+ } else if (result.registration.error) {
216
+ printFields([
217
+ { label: 'Error', value: result.registration.error },
218
+ ]);
219
+ } else {
220
+ printFields([
221
+ { label: 'Registered', value: result.registration.registered },
222
+ { label: 'Keys match', value: result.registration.matches ?? 'n/a' },
223
+ { label: 'On-chain key', value: result.registration.onChainKey ? maskValue(result.registration.onChainKey) : 'n/a' },
224
+ ]);
225
+ }
226
+
227
+ printSection('Relay');
228
+ if (result.relay.error) {
229
+ printFields([
230
+ { label: 'Healthy', value: false },
231
+ { label: 'Error', value: result.relay.error },
232
+ ]);
233
+ } else {
234
+ printFields([
235
+ { label: 'Healthy', value: result.relay.healthy },
236
+ { label: 'Status', value: result.relay.status || 'n/a' },
237
+ { label: 'Network', value: result.relay.network || 'n/a' },
238
+ ]);
239
+ }
120
240
  } catch (error) {
121
- result.relay.healthy = false;
122
- result.relay.error = error instanceof Error ? error.message : 'Unknown error';
241
+ handleCLIError(error);
123
242
  }
124
-
125
- console.log(JSON.stringify(result, null, 2));
126
243
  });
127
244
 
128
245
  return status;
129
246
  }
247
+
248
+ function maskUrl(url: string): string {
249
+ try {
250
+ const parsed = new URL(url);
251
+ return parsed.origin;
252
+ } catch {
253
+ return maskValue(url);
254
+ }
255
+ }