@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.
- package/README.md +285 -60
- package/dist/cli/index.cjs +589 -122
- package/dist/index.cjs +214 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +172 -4
- package/dist/index.d.ts +172 -4
- package/dist/index.js +210 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/abi.ts +30 -0
- package/src/addresses.ts +40 -1
- package/src/balance.ts +22 -15
- package/src/cli/commands/balance.ts +114 -54
- package/src/cli/commands/deposit.ts +79 -30
- package/src/cli/commands/init.ts +45 -8
- package/src/cli/commands/private-balance.ts +6 -1
- package/src/cli/commands/queue-balance.ts +5 -1
- package/src/cli/commands/register.ts +47 -12
- package/src/cli/commands/status.ts +129 -0
- package/src/cli/commands/transfer.ts +29 -4
- package/src/cli/commands/withdraw.ts +16 -2
- package/src/cli/index.ts +14 -11
- package/src/deposit.ts +106 -1
- package/src/index.ts +8 -2
- package/src/keypair.ts +79 -0
- package/src/relay.ts +2 -2
- package/src/transfer.ts +14 -11
- package/src/types.ts +9 -2
- package/src/withdraw.ts +7 -6
package/src/cli/commands/init.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
109
|
-
console.log(
|
|
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 -
|
|
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 =
|
|
169
|
+
const kp = await createKp();
|
|
133
170
|
|
|
134
|
-
console.log(
|
|
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
|
|
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
|
-
|
|
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
|
|
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(`\
|
|
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(
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
6
|
-
* veil keypair
|
|
7
|
-
* veil
|
|
8
|
-
* veil
|
|
9
|
-
* veil
|
|
10
|
-
* veil
|
|
11
|
-
* veil
|
|
12
|
-
* veil
|
|
13
|
-
* veil
|
|
14
|
-
* veil
|
|
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.
|
|
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();
|