@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.
- package/README.md +446 -0
- package/dist/cli/index.cjs +6431 -0
- package/dist/index.cjs +1912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2099 -0
- package/dist/index.d.ts +2099 -0
- package/dist/index.js +1840 -0
- package/dist/index.js.map +1 -0
- package/keys/transaction16.wasm +0 -0
- package/keys/transaction16.zkey +0 -0
- package/keys/transaction2.wasm +0 -0
- package/keys/transaction2.zkey +0 -0
- package/package.json +70 -0
- package/src/abi.ts +631 -0
- package/src/addresses.ts +53 -0
- package/src/balance.ts +266 -0
- package/src/cli/commands/balance.ts +118 -0
- package/src/cli/commands/deposit.ts +115 -0
- package/src/cli/commands/init.ts +147 -0
- package/src/cli/commands/keypair.ts +31 -0
- package/src/cli/commands/private-balance.ts +68 -0
- package/src/cli/commands/queue-balance.ts +58 -0
- package/src/cli/commands/register.ts +119 -0
- package/src/cli/commands/transfer.ts +137 -0
- package/src/cli/commands/withdraw.ts +79 -0
- package/src/cli/config.ts +58 -0
- package/src/cli/errors.ts +114 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/wallet.ts +228 -0
- package/src/deposit.ts +183 -0
- package/src/index.ts +160 -0
- package/src/keypair.ts +170 -0
- package/src/merkle.ts +71 -0
- package/src/prover.ts +176 -0
- package/src/relay.ts +216 -0
- package/src/transaction.ts +260 -0
- package/src/transfer.ts +462 -0
- package/src/types.ts +306 -0
- package/src/utils.ts +151 -0
- package/src/utxo.ts +119 -0
- package/src/withdraw.ts +299 -0
|
@@ -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
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -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();
|