@veil-cash/sdk 0.2.0 → 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}`);
@@ -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,7 +20,7 @@ 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')
20
- .argument('<asset>', 'Asset to transfer (ETH)')
23
+ .argument('<asset>', 'Asset to transfer (ETH, USDC, or CBBTC)')
21
24
  .argument('<amount>', 'Amount to transfer (e.g., 0.1)')
22
25
  .argument('<recipient>', 'Recipient address (must be registered)')
23
26
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
@@ -25,9 +28,11 @@ export function createTransferCommand(): Command {
25
28
  .option('--quiet', 'Suppress progress output')
26
29
  .action(async (asset: string, amount: string, recipient: string, options) => {
27
30
  try {
28
- // Validate asset is ETH
29
- if (asset.toUpperCase() !== 'ETH') {
30
- throw new CLIError(ErrorCode.INVALID_AMOUNT, 'Only ETH is supported');
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(', ')}`);
31
36
  }
32
37
 
33
38
  // Validate recipient
@@ -43,6 +48,7 @@ export function createTransferCommand(): Command {
43
48
 
44
49
  const senderKeypair = new Keypair(veilKey);
45
50
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
51
+ const pool = assetUpper.toLowerCase() as RelayPool;
46
52
 
47
53
  // Progress callback
48
54
  const onProgress = options.quiet
@@ -52,13 +58,14 @@ export function createTransferCommand(): Command {
52
58
  progress(msg, options.quiet);
53
59
  };
54
60
 
55
- progress('Starting transfer...', options.quiet);
61
+ progress(`Starting ${assetUpper} transfer...`, options.quiet);
56
62
 
57
63
  // Execute transfer
58
64
  const result = await transfer({
59
65
  amount,
60
66
  recipientAddress: recipient as `0x${string}`,
61
67
  senderKeypair,
68
+ pool,
62
69
  rpcUrl,
63
70
  onProgress,
64
71
  });
@@ -71,6 +78,7 @@ export function createTransferCommand(): Command {
71
78
  success: result.success,
72
79
  transactionHash: result.transactionHash,
73
80
  blockNumber: result.blockNumber,
81
+ asset: assetUpper,
74
82
  amount: result.amount,
75
83
  recipient: result.recipient,
76
84
  type: 'transfer',
@@ -88,16 +96,18 @@ export function createTransferCommand(): Command {
88
96
  export function createMergeCommand(): Command {
89
97
  const mergeCmd = new Command('merge')
90
98
  .description('Merge UTXOs by self-transfer (consolidate small UTXOs)')
91
- .argument('<asset>', 'Asset to merge (ETH)')
99
+ .argument('<asset>', 'Asset to merge (ETH, USDC, or CBBTC)')
92
100
  .argument('<amount>', 'Amount to merge (e.g., 0.5)')
93
101
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
94
102
  .option('--rpc-url <url>', 'RPC URL (or set RPC_URL env)')
95
103
  .option('--quiet', 'Suppress progress output')
96
104
  .action(async (asset: string, amount: string, options) => {
97
105
  try {
98
- // Validate asset is ETH
99
- if (asset.toUpperCase() !== 'ETH') {
100
- throw new CLIError(ErrorCode.INVALID_AMOUNT, 'Only ETH is supported');
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(', ')}`);
101
111
  }
102
112
 
103
113
  // Get keypair
@@ -108,6 +118,7 @@ export function createMergeCommand(): Command {
108
118
 
109
119
  const keypair = new Keypair(veilKey);
110
120
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
121
+ const pool = assetUpper.toLowerCase() as RelayPool;
111
122
 
112
123
  // Progress callback
113
124
  const onProgress = options.quiet
@@ -117,12 +128,13 @@ export function createMergeCommand(): Command {
117
128
  progress(msg, options.quiet);
118
129
  };
119
130
 
120
- progress('Starting merge (self-transfer)...', options.quiet);
131
+ progress(`Starting ${assetUpper} merge (self-transfer)...`, options.quiet);
121
132
 
122
133
  // Execute merge
123
134
  const result = await mergeUtxos({
124
135
  amount,
125
136
  keypair,
137
+ pool,
126
138
  rpcUrl,
127
139
  onProgress,
128
140
  });
@@ -135,6 +147,7 @@ export function createMergeCommand(): Command {
135
147
  success: result.success,
136
148
  transactionHash: result.transactionHash,
137
149
  blockNumber: result.blockNumber,
150
+ asset: assetUpper,
138
151
  amount: result.amount,
139
152
  type: 'merge',
140
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,7 +20,7 @@ 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')
20
- .argument('<asset>', 'Asset to withdraw (ETH)')
23
+ .argument('<asset>', 'Asset to withdraw (ETH, USDC, or CBBTC)')
21
24
  .argument('<amount>', 'Amount to withdraw (e.g., 0.1)')
22
25
  .argument('<recipient>', 'Recipient address (e.g., 0x...)')
23
26
  .option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
@@ -25,9 +28,11 @@ export function createWithdrawCommand(): Command {
25
28
  .option('--quiet', 'Suppress progress output')
26
29
  .action(async (asset: string, amount: string, recipient: string, options) => {
27
30
  try {
28
- // Validate asset is ETH
29
- if (asset.toUpperCase() !== 'ETH') {
30
- throw new CLIError(ErrorCode.INVALID_AMOUNT, 'Only ETH is supported');
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(', ')}`);
31
36
  }
32
37
 
33
38
  // Validate recipient
@@ -43,6 +48,7 @@ export function createWithdrawCommand(): Command {
43
48
 
44
49
  const keypair = new Keypair(veilKey);
45
50
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
51
+ const pool = assetUpper.toLowerCase() as RelayPool;
46
52
 
47
53
  // Progress callback
48
54
  const onProgress = options.quiet
@@ -52,13 +58,14 @@ export function createWithdrawCommand(): Command {
52
58
  progress(msg, options.quiet);
53
59
  };
54
60
 
55
- progress('Starting withdrawal...', options.quiet);
61
+ progress(`Starting ${assetUpper} withdrawal...`, options.quiet);
56
62
 
57
63
  // Execute withdrawal
58
64
  const result = await withdraw({
59
65
  amount,
60
66
  recipient: recipient as `0x${string}`,
61
67
  keypair,
68
+ pool,
62
69
  rpcUrl,
63
70
  onProgress,
64
71
  });
@@ -71,6 +78,7 @@ export function createWithdrawCommand(): Command {
71
78
  success: result.success,
72
79
  transactionHash: result.transactionHash,
73
80
  blockNumber: result.blockNumber,
81
+ asset: assetUpper,
74
82
  amount: result.amount,
75
83
  recipient: result.recipient,
76
84
  }, null, 2));
package/src/cli/index.ts CHANGED
@@ -36,7 +36,7 @@ const program = new Command();
36
36
  program
37
37
  .name('veil')
38
38
  .description('CLI for Veil Cash privacy pools on Base')
39
- .version('0.1.0');
39
+ .version('0.3.0');
40
40
 
41
41
  // Add commands
42
42
  program.addCommand(createInitCommand());
package/src/deposit.ts CHANGED
@@ -47,6 +47,42 @@ export function buildRegisterTx(
47
47
  };
48
48
  }
49
49
 
50
+ /**
51
+ * Build a transaction to change an existing deposit key
52
+ * The caller must already be registered on-chain
53
+ *
54
+ * @param depositKey - New deposit key from Keypair.depositKey()
55
+ * @param ownerAddress - Address that owns the current deposit key (must be msg.sender)
56
+ * @returns Transaction data to send
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const newKeypair = await Keypair.fromWalletKey('0x...');
61
+ * const tx = buildChangeDepositKeyTx(newKeypair.depositKey(), '0x...');
62
+ * // Send tx using your wallet
63
+ * ```
64
+ */
65
+ export function buildChangeDepositKeyTx(
66
+ depositKey: string,
67
+ ownerAddress: `0x${string}`
68
+ ): TransactionData {
69
+ const addresses = getAddresses();
70
+
71
+ const data = encodeFunctionData({
72
+ abi: ENTRY_ABI,
73
+ functionName: 'changeDepositKey',
74
+ args: [{
75
+ owner: ownerAddress,
76
+ depositKey: depositKey as `0x${string}`,
77
+ }],
78
+ });
79
+
80
+ return {
81
+ to: addresses.entry,
82
+ data,
83
+ };
84
+ }
85
+
50
86
  /**
51
87
  * Build a transaction to deposit ETH
52
88
  *
@@ -145,7 +181,65 @@ export function buildDepositUSDCTx(options: {
145
181
  }
146
182
 
147
183
  /**
148
- * Build a deposit transaction (ETH or USDC)
184
+ * Build a transaction to approve cbBTC for deposit
185
+ * Must be called before depositCBBTC if allowance is insufficient
186
+ *
187
+ * @param options - Approval options
188
+ * @param options.amount - Amount to approve (human readable, e.g., '0.5')
189
+ * @returns Transaction data
190
+ */
191
+ export function buildApproveCBBTCTx(options: {
192
+ amount: string;
193
+ }): TransactionData {
194
+ const { amount } = options;
195
+ const addresses = getAddresses();
196
+
197
+ const amountWei = parseUnits(amount, POOL_CONFIG.cbbtc.decimals);
198
+
199
+ const data = encodeFunctionData({
200
+ abi: ERC20_ABI,
201
+ functionName: 'approve',
202
+ args: [addresses.entry, amountWei],
203
+ });
204
+
205
+ return {
206
+ to: addresses.cbbtcToken,
207
+ data,
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Build a transaction to deposit cbBTC
213
+ * Note: You must approve cbBTC first using buildApproveCBBTCTx
214
+ *
215
+ * @param options - Deposit options
216
+ * @param options.depositKey - Deposit key from Keypair.depositKey()
217
+ * @param options.amount - Amount to deposit (human readable, e.g., '0.5')
218
+ * @returns Transaction data
219
+ */
220
+ export function buildDepositCBBTCTx(options: {
221
+ depositKey: string;
222
+ amount: string;
223
+ }): TransactionData {
224
+ const { depositKey, amount } = options;
225
+ const addresses = getAddresses();
226
+
227
+ const amountWei = parseUnits(amount, POOL_CONFIG.cbbtc.decimals);
228
+
229
+ const data = encodeFunctionData({
230
+ abi: ENTRY_ABI,
231
+ functionName: 'queueBTC',
232
+ args: [amountWei, depositKey as `0x${string}`],
233
+ });
234
+
235
+ return {
236
+ to: addresses.entry,
237
+ data,
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Build a deposit transaction (ETH, USDC, or cbBTC)
149
243
  * Convenience function that routes to the correct builder
150
244
  *
151
245
  * @param options - Deposit options
@@ -166,6 +260,13 @@ export function buildDepositUSDCTx(options: {
166
260
  * amount: '100',
167
261
  * token: 'USDC',
168
262
  * });
263
+ *
264
+ * // cbBTC deposit (remember to approve first!)
265
+ * const cbbtcTx = buildDepositTx({
266
+ * depositKey: keypair.depositKey(),
267
+ * amount: '0.5',
268
+ * token: 'CBBTC',
269
+ * });
169
270
  * ```
170
271
  */
171
272
  export function buildDepositTx(options: {
@@ -178,6 +279,10 @@ export function buildDepositTx(options: {
178
279
  if (token === 'USDC') {
179
280
  return buildDepositUSDCTx(rest);
180
281
  }
282
+
283
+ if (token === 'CBBTC') {
284
+ return buildDepositCBBTCTx(rest);
285
+ }
181
286
 
182
287
  return buildDepositETHTx(rest);
183
288
  }
package/src/index.ts CHANGED
@@ -32,7 +32,8 @@
32
32
  */
33
33
 
34
34
  // Keypair
35
- export { Keypair, packEncryptedMessage, unpackEncryptedMessage } from './keypair.js';
35
+ export { Keypair, packEncryptedMessage, unpackEncryptedMessage, VEIL_SIGNED_MESSAGE } from './keypair.js';
36
+ export type { MessageSigner } from './keypair.js';
36
37
 
37
38
  // UTXO
38
39
  export { Utxo } from './utxo.js';
@@ -41,9 +42,12 @@ export type { UtxoParams } from './utxo.js';
41
42
  // Deposit functions
42
43
  export {
43
44
  buildRegisterTx,
45
+ buildChangeDepositKeyTx,
44
46
  buildDepositETHTx,
45
47
  buildDepositUSDCTx,
46
48
  buildApproveUSDCTx,
49
+ buildDepositCBBTCTx,
50
+ buildApproveCBBTCTx,
47
51
  buildDepositTx,
48
52
  } from './deposit.js';
49
53
 
@@ -99,7 +103,9 @@ export type { ProofInput } from './prover.js';
99
103
  export {
100
104
  ADDRESSES,
101
105
  POOL_CONFIG,
102
- getAddresses,
106
+ getAddresses,
107
+ getPoolAddress,
108
+ getQueueAddress,
103
109
  getRelayUrl,
104
110
  } from './addresses.js';
105
111