@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.
@@ -5,9 +5,9 @@ var readline = require('readline');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
7
7
  var ethers = require('ethers');
8
+ var accounts = require('viem/accounts');
8
9
  var crypto = require('crypto');
9
10
  var viem = require('viem');
10
- var accounts = require('viem/accounts');
11
11
  var chains = require('viem/chains');
12
12
  var MerkleTree = require('fixed-merkle-tree-legacy');
13
13
  var snarkjs = require('snarkjs');
@@ -3996,6 +3996,7 @@ function shuffle(array) {
3996
3996
  }
3997
3997
 
3998
3998
  // src/keypair.ts
3999
+ var VEIL_SIGNED_MESSAGE = "Sign this message to create your Veil Wallet private key. This will be used to decrypt your balances. Ensure you are signing this message on the Veil Cash website.";
3999
4000
  var ethSigUtil = __require("eth-sig-util");
4000
4001
  function packEncryptedMessage(encryptedMessage) {
4001
4002
  const nonceBuf = Buffer.from(encryptedMessage.nonce, "base64");
@@ -4075,6 +4076,69 @@ var Keypair = class _Keypair {
4075
4076
  encryptionKey: Buffer.from(str.slice(64, 128), "hex").toString("base64")
4076
4077
  });
4077
4078
  }
4079
+ /**
4080
+ * Derive a Keypair from an EIP-191 personal_sign signature.
4081
+ * The private key is keccak256(signature) -- matching the frontend derivation.
4082
+ *
4083
+ * @param signature - Raw ECDSA signature (0x-prefixed, 132 hex chars)
4084
+ * @returns Keypair derived from the signature
4085
+ *
4086
+ * @example
4087
+ * ```typescript
4088
+ * const keypair = Keypair.fromSignature(signature);
4089
+ * ```
4090
+ */
4091
+ static fromSignature(signature) {
4092
+ const privkey = ethers.ethers.keccak256(signature);
4093
+ return new _Keypair(privkey);
4094
+ }
4095
+ /**
4096
+ * Derive a Keypair from an Ethereum wallet private key.
4097
+ * Signs VEIL_SIGNED_MESSAGE with the wallet, then derives via keccak256(signature).
4098
+ * Produces the same keypair as the frontend for the same wallet.
4099
+ *
4100
+ * @param walletPrivateKey - Ethereum EOA private key (0x-prefixed)
4101
+ * @returns Promise resolving to the derived Keypair
4102
+ *
4103
+ * @example
4104
+ * ```typescript
4105
+ * const keypair = await Keypair.fromWalletKey('0xYOUR_WALLET_PRIVATE_KEY');
4106
+ * console.log(keypair.depositKey()); // Same as frontend login with this wallet
4107
+ * ```
4108
+ */
4109
+ static async fromWalletKey(walletPrivateKey) {
4110
+ const account = accounts.privateKeyToAccount(walletPrivateKey);
4111
+ const signature = await account.signMessage({ message: VEIL_SIGNED_MESSAGE });
4112
+ return _Keypair.fromSignature(signature);
4113
+ }
4114
+ /**
4115
+ * Derive a Keypair using any external signer that supports personal_sign (EIP-191).
4116
+ * The signer function receives VEIL_SIGNED_MESSAGE and must return a 0x-prefixed signature.
4117
+ * Works with any signing backend: Bankr, MPC wallets, custodial services, hardware wallets, etc.
4118
+ *
4119
+ * @param signer - Async function that signs a message and returns a 0x-prefixed signature
4120
+ * @returns Promise resolving to the derived Keypair
4121
+ *
4122
+ * @example
4123
+ * ```typescript
4124
+ * // With Bankr
4125
+ * const keypair = await Keypair.fromSigner(async (message) => {
4126
+ * const res = await fetch('https://api.bankr.bot/agent/sign', {
4127
+ * method: 'POST',
4128
+ * headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
4129
+ * body: JSON.stringify({ signatureType: 'personal_sign', message }),
4130
+ * });
4131
+ * return (await res.json()).signature;
4132
+ * });
4133
+ *
4134
+ * // With any custom signer
4135
+ * const keypair = await Keypair.fromSigner(async (msg) => myService.personalSign(msg));
4136
+ * ```
4137
+ */
4138
+ static async fromSigner(signer) {
4139
+ const signature = await signer(VEIL_SIGNED_MESSAGE);
4140
+ return _Keypair.fromSignature(signature);
4141
+ }
4078
4142
  /**
4079
4143
  * Sign a message using the private key
4080
4144
  * @param commitment - Commitment hash
@@ -4165,11 +4229,33 @@ function saveVeilKeypair(veilKey, depositKey, envPath) {
4165
4229
  content = updateEnvVar(content, "DEPOSIT_KEY", depositKey);
4166
4230
  fs.writeFileSync(envPath, content);
4167
4231
  }
4232
+ function resolveWalletKey(options) {
4233
+ const raw = options.walletKey || process.env.WALLET_KEY;
4234
+ if (!raw) {
4235
+ throw new Error("Wallet key required for --sign-message. Use --wallet-key <key> or set WALLET_KEY env var.");
4236
+ }
4237
+ const key = raw.startsWith("0x") ? raw : `0x${raw}`;
4238
+ if (key.length !== 66) {
4239
+ throw new Error("Invalid wallet key format. Must be a 0x-prefixed 64-character hex string.");
4240
+ }
4241
+ return key;
4242
+ }
4168
4243
  function createInitCommand() {
4169
- const init = new Command("init").description("Generate a new Veil keypair").option("--force", "Overwrite existing keypair without prompting").option("--json", "Output as JSON (no prompts, no file save)").option("--out <path>", "Save to custom path instead of .env.veil").option("--no-save", "Print keypair without saving to file").action(async (options) => {
4170
- const envPath = options.out || getDefaultEnvPath();
4244
+ const init = new Command("init").description("Generate a new Veil keypair").option("--force", "Overwrite existing keypair without prompting").option("--json", "Output as JSON (no prompts, no file save)").option("--no-save", "Print keypair without saving to file").option("--sign-message", "Derive keypair from wallet signature (same as frontend login)").option("--wallet-key <key>", "Ethereum wallet private key (or set WALLET_KEY env var)").option("--signature <sig>", "Derive keypair from a pre-computed EIP-191 personal_sign signature").action(async (options) => {
4245
+ const envPath = getDefaultEnvPath();
4246
+ async function createKp() {
4247
+ if (options.signMessage) {
4248
+ const walletKey = resolveWalletKey(options);
4249
+ return Keypair.fromWalletKey(walletKey);
4250
+ }
4251
+ if (options.signature) {
4252
+ return Keypair.fromSignature(options.signature);
4253
+ }
4254
+ return new Keypair();
4255
+ }
4256
+ const derivationLabel = options.signMessage ? "Derived Veil keypair from wallet signature" : options.signature ? "Derived Veil keypair from provided signature" : "Generated new Veil keypair";
4171
4257
  if (options.json) {
4172
- const kp2 = new Keypair();
4258
+ const kp2 = await createKp();
4173
4259
  console.log(JSON.stringify({
4174
4260
  veilKey: kp2.privkey,
4175
4261
  depositKey: kp2.depositKey()
@@ -4178,15 +4264,17 @@ function createInitCommand() {
4178
4264
  return;
4179
4265
  }
4180
4266
  if (!options.save) {
4181
- const kp2 = new Keypair();
4182
- console.log("\nGenerated new Veil keypair:\n");
4267
+ const kp2 = await createKp();
4268
+ console.log(`
4269
+ ${derivationLabel}:
4270
+ `);
4183
4271
  console.log("Veil Private Key:");
4184
4272
  console.log(` ${kp2.privkey}
4185
4273
  `);
4186
4274
  console.log("Deposit Key (register this on-chain):");
4187
4275
  console.log(` ${kp2.depositKey()}
4188
4276
  `);
4189
- console.log("(Not saved - use --out <path> to save to a file)");
4277
+ console.log("(Not saved - run without --no-save to save to .env.veil)");
4190
4278
  process.exit(0);
4191
4279
  return;
4192
4280
  }
@@ -4201,8 +4289,10 @@ WARNING: A Veil key already exists in ${envPath}`);
4201
4289
  return;
4202
4290
  }
4203
4291
  }
4204
- const kp = new Keypair();
4205
- console.log("\nGenerated new Veil keypair:\n");
4292
+ const kp = await createKp();
4293
+ console.log(`
4294
+ ${derivationLabel}:
4295
+ `);
4206
4296
  console.log("Veil Private Key:");
4207
4297
  console.log(` ${kp.privkey}
4208
4298
  `);
@@ -4245,15 +4335,61 @@ var ADDRESSES = {
4245
4335
  usdcPool: "0x5c50d58E49C59d112680c187De2Bf989d2a91242",
4246
4336
  usdcQueue: "0x5530241b24504bF05C9a22e95A1F5458888e6a9B",
4247
4337
  usdcToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
4338
+ cbbtcPool: "0x51A021da774b4bBB59B47f7CB4ccd631337680BA",
4339
+ cbbtcQueue: "0x977741CaDF8D1431c4816C0993D32b02094cD35C",
4340
+ cbbtcToken: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
4248
4341
  chainId: 8453,
4249
4342
  relayUrl: "https://veil-relay.up.railway.app"
4250
4343
  };
4251
4344
  var POOL_CONFIG = {
4252
4345
  eth: {
4253
- decimals: 18}};
4346
+ decimals: 18,
4347
+ displayDecimals: 4,
4348
+ symbol: "ETH",
4349
+ name: "Ethereum"
4350
+ },
4351
+ usdc: {
4352
+ decimals: 6,
4353
+ displayDecimals: 2,
4354
+ symbol: "USDC",
4355
+ name: "USD Coin"
4356
+ },
4357
+ cbbtc: {
4358
+ decimals: 8,
4359
+ displayDecimals: 6,
4360
+ symbol: "cbBTC",
4361
+ name: "Coinbase Bitcoin"
4362
+ }
4363
+ };
4254
4364
  function getAddresses() {
4255
4365
  return ADDRESSES;
4256
4366
  }
4367
+ function getPoolAddress(pool) {
4368
+ const addresses = getAddresses();
4369
+ switch (pool) {
4370
+ case "eth":
4371
+ return addresses.ethPool;
4372
+ case "usdc":
4373
+ return addresses.usdcPool;
4374
+ case "cbbtc":
4375
+ return addresses.cbbtcPool;
4376
+ default:
4377
+ throw new Error(`Unknown pool: ${pool}`);
4378
+ }
4379
+ }
4380
+ function getQueueAddress(pool) {
4381
+ const addresses = getAddresses();
4382
+ switch (pool) {
4383
+ case "eth":
4384
+ return addresses.ethQueue;
4385
+ case "usdc":
4386
+ return addresses.usdcQueue;
4387
+ case "cbbtc":
4388
+ return addresses.cbbtcQueue;
4389
+ default:
4390
+ throw new Error(`Unknown pool: ${pool}`);
4391
+ }
4392
+ }
4257
4393
  function getRelayUrl() {
4258
4394
  return ADDRESSES.relayUrl;
4259
4395
  }
@@ -4315,6 +4451,23 @@ var ENTRY_ABI = [
4315
4451
  stateMutability: "nonpayable",
4316
4452
  type: "function"
4317
4453
  },
4454
+ // Change deposit key (must already be registered)
4455
+ {
4456
+ inputs: [
4457
+ {
4458
+ components: [
4459
+ { name: "owner", type: "address" },
4460
+ { name: "depositKey", type: "bytes" }
4461
+ ],
4462
+ name: "_account",
4463
+ type: "tuple"
4464
+ }
4465
+ ],
4466
+ name: "changeDepositKey",
4467
+ outputs: [],
4468
+ stateMutability: "nonpayable",
4469
+ type: "function"
4470
+ },
4318
4471
  // Queue ETH deposit
4319
4472
  {
4320
4473
  inputs: [{ name: "_depositKey", type: "bytes" }],
@@ -4334,6 +4487,17 @@ var ENTRY_ABI = [
4334
4487
  stateMutability: "nonpayable",
4335
4488
  type: "function"
4336
4489
  },
4490
+ // Queue cbBTC deposit
4491
+ {
4492
+ inputs: [
4493
+ { name: "_amount", type: "uint256" },
4494
+ { name: "_depositKey", type: "bytes" }
4495
+ ],
4496
+ name: "queueBTC",
4497
+ outputs: [],
4498
+ stateMutability: "nonpayable",
4499
+ type: "function"
4500
+ },
4337
4501
  // Read deposit keys
4338
4502
  {
4339
4503
  inputs: [{ name: "", type: "address" }],
@@ -4828,6 +4992,35 @@ var POOL_ABI = [
4828
4992
  // ============ RECEIVE ============
4829
4993
  { stateMutability: "payable", type: "receive" }
4830
4994
  ];
4995
+ var ERC20_ABI = [
4996
+ {
4997
+ inputs: [
4998
+ { name: "spender", type: "address" },
4999
+ { name: "amount", type: "uint256" }
5000
+ ],
5001
+ name: "approve",
5002
+ outputs: [{ name: "", type: "bool" }],
5003
+ stateMutability: "nonpayable",
5004
+ type: "function"
5005
+ },
5006
+ {
5007
+ inputs: [{ name: "account", type: "address" }],
5008
+ name: "balanceOf",
5009
+ outputs: [{ name: "", type: "uint256" }],
5010
+ stateMutability: "view",
5011
+ type: "function"
5012
+ },
5013
+ {
5014
+ inputs: [
5015
+ { name: "owner", type: "address" },
5016
+ { name: "spender", type: "address" }
5017
+ ],
5018
+ name: "allowance",
5019
+ outputs: [{ name: "", type: "uint256" }],
5020
+ stateMutability: "view",
5021
+ type: "function"
5022
+ }
5023
+ ];
4831
5024
 
4832
5025
  // src/deposit.ts
4833
5026
  function buildRegisterTx(depositKey, ownerAddress) {
@@ -4845,6 +5038,21 @@ function buildRegisterTx(depositKey, ownerAddress) {
4845
5038
  data
4846
5039
  };
4847
5040
  }
5041
+ function buildChangeDepositKeyTx(depositKey, ownerAddress) {
5042
+ const addresses = getAddresses();
5043
+ const data = viem.encodeFunctionData({
5044
+ abi: ENTRY_ABI,
5045
+ functionName: "changeDepositKey",
5046
+ args: [{
5047
+ owner: ownerAddress,
5048
+ depositKey
5049
+ }]
5050
+ });
5051
+ return {
5052
+ to: addresses.entry,
5053
+ data
5054
+ };
5055
+ }
4848
5056
  function buildDepositETHTx(options) {
4849
5057
  const { depositKey, amount } = options;
4850
5058
  const addresses = getAddresses();
@@ -4860,6 +5068,62 @@ function buildDepositETHTx(options) {
4860
5068
  value
4861
5069
  };
4862
5070
  }
5071
+ function buildApproveUSDCTx(options) {
5072
+ const { amount } = options;
5073
+ const addresses = getAddresses();
5074
+ const amountWei = viem.parseUnits(amount, POOL_CONFIG.usdc.decimals);
5075
+ const data = viem.encodeFunctionData({
5076
+ abi: ERC20_ABI,
5077
+ functionName: "approve",
5078
+ args: [addresses.entry, amountWei]
5079
+ });
5080
+ return {
5081
+ to: addresses.usdcToken,
5082
+ data
5083
+ };
5084
+ }
5085
+ function buildDepositUSDCTx(options) {
5086
+ const { depositKey, amount } = options;
5087
+ const addresses = getAddresses();
5088
+ const amountWei = viem.parseUnits(amount, POOL_CONFIG.usdc.decimals);
5089
+ const data = viem.encodeFunctionData({
5090
+ abi: ENTRY_ABI,
5091
+ functionName: "queueUSDC",
5092
+ args: [amountWei, depositKey]
5093
+ });
5094
+ return {
5095
+ to: addresses.entry,
5096
+ data
5097
+ };
5098
+ }
5099
+ function buildApproveCBBTCTx(options) {
5100
+ const { amount } = options;
5101
+ const addresses = getAddresses();
5102
+ const amountWei = viem.parseUnits(amount, POOL_CONFIG.cbbtc.decimals);
5103
+ const data = viem.encodeFunctionData({
5104
+ abi: ERC20_ABI,
5105
+ functionName: "approve",
5106
+ args: [addresses.entry, amountWei]
5107
+ });
5108
+ return {
5109
+ to: addresses.cbbtcToken,
5110
+ data
5111
+ };
5112
+ }
5113
+ function buildDepositCBBTCTx(options) {
5114
+ const { depositKey, amount } = options;
5115
+ const addresses = getAddresses();
5116
+ const amountWei = viem.parseUnits(amount, POOL_CONFIG.cbbtc.decimals);
5117
+ const data = viem.encodeFunctionData({
5118
+ abi: ENTRY_ABI,
5119
+ functionName: "queueBTC",
5120
+ args: [amountWei, depositKey]
5121
+ });
5122
+ return {
5123
+ to: addresses.entry,
5124
+ data
5125
+ };
5126
+ }
4863
5127
  function createWallet(config) {
4864
5128
  const { privateKey, rpcUrl } = config;
4865
5129
  const account = accounts.privateKeyToAccount(privateKey);
@@ -5076,7 +5340,7 @@ function handleCLIError(error) {
5076
5340
 
5077
5341
  // src/cli/commands/register.ts
5078
5342
  function createRegisterCommand() {
5079
- const register = new Command("register").description("Register your deposit key on-chain (one-time)").option("--deposit-key <key>", "Your Veil deposit key (or set DEPOSIT_KEY env)").option("--wallet-key <key>", "Ethereum wallet key for signing (or set WALLET_KEY env)").option("--address <address>", "Owner address (required with --unsigned)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--unsigned", "Output unsigned transaction payload (Bankr-compatible format)").option("--json", "Output as JSON").action(async (options) => {
5343
+ const register = new Command("register").description("Register or update your deposit key on-chain").option("--deposit-key <key>", "Your Veil deposit key (or set DEPOSIT_KEY env)").option("--wallet-key <key>", "Ethereum wallet key for signing (or set WALLET_KEY env)").option("--address <address>", "Owner address (required with --unsigned)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--unsigned", "Output unsigned transaction payload (Bankr-compatible format)").option("--force", "Change deposit key even if already registered").option("--json", "Output as JSON").action(async (options) => {
5080
5344
  const jsonOutput = options.json;
5081
5345
  try {
5082
5346
  const depositKey = options.depositKey || process.env.DEPOSIT_KEY;
@@ -5094,8 +5358,9 @@ function createRegisterCommand() {
5094
5358
  }
5095
5359
  address2 = getAddress(walletKey);
5096
5360
  }
5097
- const tx2 = buildRegisterTx(depositKey, address2);
5361
+ const tx2 = options.force ? buildChangeDepositKeyTx(depositKey, address2) : buildRegisterTx(depositKey, address2);
5098
5362
  const payload = {
5363
+ action: options.force ? "changeDepositKey" : "register",
5099
5364
  to: tx2.to,
5100
5365
  data: tx2.data,
5101
5366
  value: "0",
@@ -5109,43 +5374,69 @@ function createRegisterCommand() {
5109
5374
  const address = getAddress(config.privateKey);
5110
5375
  if (!jsonOutput) console.log("\nChecking registration status...");
5111
5376
  const { registered, depositKey: existingKey } = await isRegistered(address, config.rpcUrl);
5112
- if (registered) {
5377
+ const keysMatch = registered && existingKey === depositKey;
5378
+ const isChange = registered && !keysMatch;
5379
+ if (registered && keysMatch) {
5113
5380
  if (jsonOutput) {
5114
5381
  console.log(JSON.stringify({
5115
5382
  success: true,
5116
5383
  alreadyRegistered: true,
5384
+ keysMatch: true,
5117
5385
  address,
5118
5386
  depositKey: existingKey
5119
5387
  }, null, 2));
5120
5388
  } else {
5121
5389
  console.log(`
5122
- Address ${address} is already registered.`);
5390
+ Address ${address} is already registered with this deposit key.`);
5123
5391
  console.log(`
5124
- Existing deposit key:`);
5125
- console.log(` ${existingKey}`);
5392
+ Deposit key: ${existingKey.slice(0, 40)}...`);
5126
5393
  }
5127
5394
  process.exit(0);
5128
5395
  return;
5129
5396
  }
5397
+ if (isChange && !options.force) {
5398
+ if (jsonOutput) {
5399
+ console.log(JSON.stringify({
5400
+ success: false,
5401
+ alreadyRegistered: true,
5402
+ keysMatch: false,
5403
+ address,
5404
+ onChainKey: existingKey,
5405
+ localKey: depositKey.slice(0, 40) + "...",
5406
+ hint: "Use --force to change deposit key"
5407
+ }, null, 2));
5408
+ } else {
5409
+ console.log(`
5410
+ Address ${address} is already registered with a different deposit key.`);
5411
+ console.log(`
5412
+ On-chain key: ${existingKey.slice(0, 40)}...`);
5413
+ console.log(` Local key: ${depositKey.slice(0, 40)}...`);
5414
+ console.log(`
5415
+ Use --force to change your deposit key on-chain.`);
5416
+ }
5417
+ process.exit(1);
5418
+ return;
5419
+ }
5420
+ const action = isChange ? "Changing deposit key" : "Registering deposit key";
5130
5421
  if (!jsonOutput) {
5131
- console.log("Registering deposit key...");
5422
+ console.log(`${action}...`);
5132
5423
  console.log(` Address: ${address}`);
5133
5424
  console.log(` Deposit Key: ${depositKey.slice(0, 40)}...`);
5134
5425
  }
5135
- const tx = buildRegisterTx(depositKey, address);
5426
+ const tx = isChange ? buildChangeDepositKeyTx(depositKey, address) : buildRegisterTx(depositKey, address);
5136
5427
  const result = await sendTransaction(config, tx);
5137
5428
  if (result.receipt.status === "success") {
5138
5429
  if (jsonOutput) {
5139
5430
  console.log(JSON.stringify({
5140
5431
  success: true,
5141
- alreadyRegistered: false,
5432
+ action: isChange ? "changed" : "registered",
5142
5433
  address,
5143
5434
  transactionHash: result.hash,
5144
5435
  blockNumber: result.receipt.blockNumber.toString(),
5145
5436
  gasUsed: result.receipt.gasUsed.toString()
5146
5437
  }, null, 2));
5147
5438
  } else {
5148
- console.log("\nRegistration successful!");
5439
+ console.log(isChange ? "\nDeposit key changed successfully!" : "\nRegistration successful!");
5149
5440
  console.log(` Transaction: ${result.hash}`);
5150
5441
  console.log(` Block: ${result.receipt.blockNumber}`);
5151
5442
  console.log(` Gas used: ${result.receipt.gasUsed}`);
@@ -5161,25 +5452,36 @@ Existing deposit key:`);
5161
5452
  });
5162
5453
  return register;
5163
5454
  }
5164
- var MINIMUM_DEPOSIT_ETH = 0.01;
5165
5455
  var DEPOSIT_FEE_PERCENT = 0.3;
5166
- var MINIMUM_DEPOSIT_WITH_FEE = MINIMUM_DEPOSIT_ETH / (1 - DEPOSIT_FEE_PERCENT / 100);
5456
+ var MINIMUM_DEPOSITS = {
5457
+ ETH: 0.01,
5458
+ USDC: 10,
5459
+ CBBTC: 1e-4
5460
+ };
5461
+ function getMinimumWithFee(asset) {
5462
+ const min = MINIMUM_DEPOSITS[asset] || 0;
5463
+ return min / (1 - DEPOSIT_FEE_PERCENT / 100);
5464
+ }
5465
+ var SUPPORTED_ASSETS = ["ETH", "USDC", "CBBTC"];
5167
5466
  function progress(msg, quiet) {
5168
5467
  if (!quiet) {
5169
5468
  process.stderr.write(`\r\x1B[K${msg}`);
5170
5469
  }
5171
5470
  }
5172
5471
  function createDepositCommand() {
5173
- const deposit = new Command("deposit").description("Deposit ETH into Veil").argument("<asset>", "Asset to deposit (ETH)").argument("<amount>", "Amount to deposit (e.g., 0.1)").option("--deposit-key <key>", "Your Veil deposit key (or set DEPOSIT_KEY env)").option("--wallet-key <key>", "Ethereum wallet key for signing (or set WALLET_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--unsigned", "Output unsigned transaction payload (Bankr-compatible format)").option("--quiet", "Suppress progress output").action(async (asset, amount, options) => {
5472
+ const deposit = new Command("deposit").description("Deposit ETH, USDC, or cbBTC into Veil").argument("<asset>", "Asset to deposit (ETH, USDC, or CBBTC)").argument("<amount>", "Amount to deposit (e.g., 0.1)").option("--deposit-key <key>", "Your Veil deposit key (or set DEPOSIT_KEY env)").option("--wallet-key <key>", "Ethereum wallet key for signing (or set WALLET_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--unsigned", "Output unsigned transaction payload (Bankr-compatible format)").option("--quiet", "Suppress progress output").action(async (asset, amount, options) => {
5174
5473
  try {
5175
- if (asset.toUpperCase() !== "ETH") {
5176
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
5474
+ const assetUpper = asset.toUpperCase();
5475
+ if (!SUPPORTED_ASSETS.includes(assetUpper)) {
5476
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(", ")}`);
5177
5477
  }
5178
5478
  const amountNum = parseFloat(amount);
5179
- if (amountNum < MINIMUM_DEPOSIT_WITH_FEE) {
5479
+ const minimumWithFee = getMinimumWithFee(assetUpper);
5480
+ const minimumNet = MINIMUM_DEPOSITS[assetUpper];
5481
+ if (amountNum < minimumWithFee) {
5180
5482
  throw new CLIError(
5181
5483
  ErrorCode.INVALID_AMOUNT,
5182
- `Minimum deposit is ${MINIMUM_DEPOSIT_ETH} ETH (net). With ${DEPOSIT_FEE_PERCENT}% fee, send at least ${MINIMUM_DEPOSIT_WITH_FEE.toFixed(5)} ETH.`
5484
+ `Minimum deposit is ${minimumNet} ${assetUpper} (net). With ${DEPOSIT_FEE_PERCENT}% fee, send at least ${minimumWithFee.toFixed(assetUpper === "ETH" ? 5 : 8)} ${assetUpper}.`
5183
5485
  );
5184
5486
  }
5185
5487
  const depositKey = options.depositKey || process.env.DEPOSIT_KEY;
@@ -5187,38 +5489,62 @@ function createDepositCommand() {
5187
5489
  throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, "Deposit key required. Use --deposit-key or set DEPOSIT_KEY in .env (run: veil init)");
5188
5490
  }
5189
5491
  progress("Building transaction...", options.quiet);
5190
- const tx = buildDepositETHTx({
5191
- depositKey,
5192
- amount
5193
- });
5492
+ let tx;
5493
+ let approveTx = null;
5494
+ if (assetUpper === "USDC") {
5495
+ approveTx = buildApproveUSDCTx({ amount });
5496
+ tx = buildDepositUSDCTx({ depositKey, amount });
5497
+ } else if (assetUpper === "CBBTC") {
5498
+ approveTx = buildApproveCBBTCTx({ amount });
5499
+ tx = buildDepositCBBTCTx({ depositKey, amount });
5500
+ } else {
5501
+ tx = buildDepositETHTx({ depositKey, amount });
5502
+ }
5194
5503
  if (options.unsigned) {
5195
5504
  progress("", options.quiet);
5196
- const payload = {
5505
+ const payloads = [];
5506
+ if (approveTx) {
5507
+ payloads.push({
5508
+ step: "approve",
5509
+ to: approveTx.to,
5510
+ data: approveTx.data,
5511
+ value: "0",
5512
+ chainId: 8453
5513
+ });
5514
+ }
5515
+ payloads.push({
5516
+ step: "deposit",
5197
5517
  to: tx.to,
5198
5518
  data: tx.data,
5199
5519
  value: tx.value ? tx.value.toString() : "0",
5200
5520
  chainId: 8453
5201
- // Base mainnet
5202
- };
5203
- console.log(JSON.stringify(payload, null, 2));
5521
+ });
5522
+ console.log(JSON.stringify(payloads.length === 1 ? payloads[0] : payloads, null, 2));
5204
5523
  return;
5205
5524
  }
5206
5525
  const config = getConfig(options);
5207
5526
  const address = getAddress(config.privateKey);
5208
- progress("Checking balance...", options.quiet);
5209
- const balance = await getBalance(address, config.rpcUrl);
5210
- const amountWei = viem.parseEther(amount);
5211
- if (balance < amountWei) {
5212
- progress("", options.quiet);
5213
- throw new CLIError(ErrorCode.INSUFFICIENT_BALANCE, `Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${amount} ETH`);
5527
+ if (assetUpper === "ETH") {
5528
+ progress("Checking balance...", options.quiet);
5529
+ const balance = await getBalance(address, config.rpcUrl);
5530
+ const amountWei = viem.parseEther(amount);
5531
+ if (balance < amountWei) {
5532
+ progress("", options.quiet);
5533
+ throw new CLIError(ErrorCode.INSUFFICIENT_BALANCE, `Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${amount} ETH`);
5534
+ }
5535
+ }
5536
+ if (approveTx) {
5537
+ progress(`Approving ${assetUpper}...`, options.quiet);
5538
+ await sendTransaction(config, approveTx);
5214
5539
  }
5215
- progress("Sending transaction...", options.quiet);
5540
+ progress("Sending deposit transaction...", options.quiet);
5216
5541
  const result = await sendTransaction(config, tx);
5217
5542
  progress("Confirming...", options.quiet);
5218
5543
  progress("", options.quiet);
5219
5544
  console.log(JSON.stringify({
5220
5545
  success: result.receipt.status === "success",
5221
5546
  hash: result.hash,
5547
+ asset: assetUpper,
5222
5548
  amount,
5223
5549
  blockNumber: result.receipt.blockNumber.toString(),
5224
5550
  gasUsed: result.receipt.gasUsed.toString()
@@ -5315,15 +5641,16 @@ var DEPOSIT_STATUS_MAP = {
5315
5641
  3: "refunded"
5316
5642
  };
5317
5643
  async function getQueueBalance(options) {
5318
- const { address, rpcUrl, onProgress } = options;
5319
- const addresses = getAddresses();
5644
+ const { address, pool = "eth", rpcUrl, onProgress } = options;
5645
+ const queueAddress = getQueueAddress(pool);
5646
+ const poolConfig = POOL_CONFIG[pool];
5320
5647
  const publicClient = viem.createPublicClient({
5321
5648
  chain: chains.base,
5322
5649
  transport: viem.http(rpcUrl)
5323
5650
  });
5324
5651
  onProgress?.("Fetching pending deposits...");
5325
5652
  const pendingNonces = await publicClient.readContract({
5326
- address: addresses.ethQueue,
5653
+ address: queueAddress,
5327
5654
  abi: QUEUE_ABI,
5328
5655
  functionName: "getPendingDeposits"
5329
5656
  });
@@ -5334,7 +5661,7 @@ async function getQueueBalance(options) {
5334
5661
  const nonce = pendingNonces[i];
5335
5662
  onProgress?.("Checking deposit", `${i + 1}/${pendingNonces.length}`);
5336
5663
  const deposit = await publicClient.readContract({
5337
- address: addresses.ethQueue,
5664
+ address: queueAddress,
5338
5665
  abi: QUEUE_ABI,
5339
5666
  functionName: "getDeposit",
5340
5667
  args: [nonce]
@@ -5344,7 +5671,7 @@ async function getQueueBalance(options) {
5344
5671
  pendingDeposits.push({
5345
5672
  nonce: nonce.toString(),
5346
5673
  status: DEPOSIT_STATUS_MAP[deposit.status] || "pending",
5347
- amount: viem.formatEther(deposit.amountIn),
5674
+ amount: viem.formatUnits(deposit.amountIn, poolConfig.decimals),
5348
5675
  amountWei: deposit.amountIn.toString(),
5349
5676
  timestamp: new Date(Number(deposit.timestamp) * 1e3).toISOString()
5350
5677
  });
@@ -5355,15 +5682,16 @@ async function getQueueBalance(options) {
5355
5682
  }
5356
5683
  return {
5357
5684
  address,
5358
- queueBalance: viem.formatEther(totalQueueBalance),
5685
+ queueBalance: viem.formatUnits(totalQueueBalance, poolConfig.decimals),
5359
5686
  queueBalanceWei: totalQueueBalance.toString(),
5360
5687
  pendingDeposits,
5361
5688
  pendingCount: pendingDeposits.length
5362
5689
  };
5363
5690
  }
5364
5691
  async function getPrivateBalance(options) {
5365
- const { keypair, rpcUrl, onProgress } = options;
5366
- const addresses = getAddresses();
5692
+ const { keypair, pool = "eth", rpcUrl, onProgress } = options;
5693
+ const poolAddress = getPoolAddress(pool);
5694
+ const poolConfig = POOL_CONFIG[pool];
5367
5695
  if (!keypair.privkey) {
5368
5696
  throw new Error("Keypair must have a private key to calculate private balance");
5369
5697
  }
@@ -5373,7 +5701,7 @@ async function getPrivateBalance(options) {
5373
5701
  });
5374
5702
  onProgress?.("Fetching pool index...");
5375
5703
  const nextIndex = await publicClient.readContract({
5376
- address: addresses.ethPool,
5704
+ address: poolAddress,
5377
5705
  abi: POOL_ABI,
5378
5706
  functionName: "nextIndex"
5379
5707
  });
@@ -5396,7 +5724,7 @@ async function getPrivateBalance(options) {
5396
5724
  const batchNum = Math.floor(start / BATCH_SIZE) + 1;
5397
5725
  onProgress?.("Fetching encrypted outputs", `batch ${batchNum}/${totalBatches} (${start}-${end})`);
5398
5726
  const batch = await publicClient.readContract({
5399
- address: addresses.ethPool,
5727
+ address: poolAddress,
5400
5728
  abi: POOL_ABI,
5401
5729
  functionName: "getEncryptedOutputs",
5402
5730
  args: [BigInt(start), BigInt(end)]
@@ -5426,14 +5754,14 @@ async function getPrivateBalance(options) {
5426
5754
  const nullifier = utxo.getNullifier();
5427
5755
  const nullifierHex = toFixedHex(nullifier);
5428
5756
  const isSpent = await publicClient.readContract({
5429
- address: addresses.ethPool,
5757
+ address: poolAddress,
5430
5758
  abi: POOL_ABI,
5431
5759
  functionName: "isSpent",
5432
5760
  args: [nullifierHex]
5433
5761
  });
5434
5762
  utxoInfos.push({
5435
5763
  index,
5436
- amount: viem.formatEther(utxo.amount),
5764
+ amount: viem.formatUnits(utxo.amount, poolConfig.decimals),
5437
5765
  amountWei: utxo.amount.toString(),
5438
5766
  isSpent
5439
5767
  });
@@ -5445,7 +5773,7 @@ async function getPrivateBalance(options) {
5445
5773
  }
5446
5774
  }
5447
5775
  return {
5448
- privateBalance: viem.formatEther(totalBalance),
5776
+ privateBalance: viem.formatUnits(totalBalance, poolConfig.decimals),
5449
5777
  privateBalanceWei: totalBalance.toString(),
5450
5778
  utxoCount: decryptedUtxos.length,
5451
5779
  spentCount,
@@ -5453,9 +5781,62 @@ async function getPrivateBalance(options) {
5453
5781
  utxos: utxoInfos
5454
5782
  };
5455
5783
  }
5784
+ var SUPPORTED_POOLS = ["eth", "usdc", "cbbtc"];
5785
+ async function fetchPoolBalance(pool, address, keypair, rpcUrl, onProgress) {
5786
+ const poolConfig = POOL_CONFIG[pool];
5787
+ const poolProgress = onProgress ? (stage, detail) => onProgress(`[${pool.toUpperCase()}] ${stage}`, detail) : void 0;
5788
+ const queueResult = await getQueueBalance({ address, pool, rpcUrl, onProgress: poolProgress });
5789
+ let privateResult = null;
5790
+ if (keypair) {
5791
+ privateResult = await getPrivateBalance({ keypair, pool, rpcUrl, onProgress: poolProgress });
5792
+ }
5793
+ const queueBalanceWei = BigInt(queueResult.queueBalanceWei);
5794
+ const privateBalanceWei = privateResult ? BigInt(privateResult.privateBalanceWei) : 0n;
5795
+ const totalBalanceWei = queueBalanceWei + privateBalanceWei;
5796
+ const result = {
5797
+ pool: pool.toUpperCase(),
5798
+ symbol: poolConfig.symbol,
5799
+ totalBalance: viem.formatUnits(totalBalanceWei, poolConfig.decimals),
5800
+ totalBalanceWei: totalBalanceWei.toString()
5801
+ };
5802
+ if (privateResult) {
5803
+ const unspentUtxos = privateResult.utxos.filter((u) => !u.isSpent);
5804
+ result.private = {
5805
+ balance: privateResult.privateBalance,
5806
+ balanceWei: privateResult.privateBalanceWei,
5807
+ utxoCount: privateResult.unspentCount,
5808
+ utxos: unspentUtxos.map((u) => ({
5809
+ index: u.index,
5810
+ amount: u.amount
5811
+ }))
5812
+ };
5813
+ } else {
5814
+ result.private = {
5815
+ balance: null,
5816
+ note: "Set VEIL_KEY to see private balance"
5817
+ };
5818
+ }
5819
+ result.queue = {
5820
+ balance: queueResult.queueBalance,
5821
+ balanceWei: queueResult.queueBalanceWei,
5822
+ count: queueResult.pendingCount,
5823
+ deposits: queueResult.pendingDeposits.map((d) => ({
5824
+ nonce: d.nonce,
5825
+ amount: d.amount,
5826
+ status: d.status,
5827
+ timestamp: d.timestamp
5828
+ }))
5829
+ };
5830
+ return result;
5831
+ }
5456
5832
  function createBalanceCommand() {
5457
- const balance = new Command("balance").description("Show queue and private balances").option("--wallet-key <key>", "Ethereum wallet key (or set WALLET_KEY env)").option("--address <address>", "Address to check (or derived from wallet key)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (options) => {
5833
+ const balance = new Command("balance").description("Show queue and private balances (all pools by default)").option("--pool <pool>", "Pool to check (eth, usdc, cbbtc, or all)", "all").option("--wallet-key <key>", "Ethereum wallet key (or set WALLET_KEY env)").option("--address <address>", "Address to check (or derived from wallet key)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (options) => {
5458
5834
  try {
5835
+ const poolArg = (options.pool || "all").toLowerCase();
5836
+ if (poolArg !== "all" && !SUPPORTED_POOLS.includes(poolArg)) {
5837
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported pool: ${options.pool}. Supported: ${SUPPORTED_POOLS.join(", ")}, all`);
5838
+ }
5839
+ const poolsToQuery = poolArg === "all" ? [...SUPPORTED_POOLS] : [poolArg];
5459
5840
  let address;
5460
5841
  if (options.address) {
5461
5842
  address = options.address;
@@ -5473,51 +5854,28 @@ function createBalanceCommand() {
5473
5854
  const msg = detail ? `${stage}: ${detail}` : stage;
5474
5855
  process.stderr.write(`\r\x1B[K${msg}`);
5475
5856
  };
5476
- const queueResult = await getQueueBalance({ address, rpcUrl, onProgress });
5477
- let privateResult = null;
5478
- if (keypair) {
5479
- privateResult = await getPrivateBalance({ keypair, rpcUrl, onProgress });
5857
+ const depositKey = process.env.DEPOSIT_KEY || (keypair ? keypair.depositKey() : null);
5858
+ if (poolsToQuery.length === 1) {
5859
+ const poolResult = await fetchPoolBalance(poolsToQuery[0], address, keypair, rpcUrl, onProgress);
5860
+ if (!options.quiet) process.stderr.write("\r\x1B[K");
5861
+ const output2 = {
5862
+ address,
5863
+ depositKey: depositKey || null,
5864
+ ...poolResult
5865
+ };
5866
+ console.log(JSON.stringify(output2, null, 2));
5867
+ return;
5480
5868
  }
5481
- if (!options.quiet) {
5482
- process.stderr.write("\r\x1B[K");
5869
+ const pools = [];
5870
+ for (const pool of poolsToQuery) {
5871
+ const poolResult = await fetchPoolBalance(pool, address, keypair, rpcUrl, onProgress);
5872
+ pools.push(poolResult);
5483
5873
  }
5484
- const queueBalanceWei = BigInt(queueResult.queueBalanceWei);
5485
- const privateBalanceWei = privateResult ? BigInt(privateResult.privateBalanceWei) : 0n;
5486
- const totalBalanceWei = queueBalanceWei + privateBalanceWei;
5487
- const depositKey = process.env.DEPOSIT_KEY || (keypair ? keypair.depositKey() : null);
5874
+ if (!options.quiet) process.stderr.write("\r\x1B[K");
5488
5875
  const output = {
5489
5876
  address,
5490
5877
  depositKey: depositKey || null,
5491
- totalBalance: viem.formatEther(totalBalanceWei),
5492
- totalBalanceWei: totalBalanceWei.toString()
5493
- };
5494
- if (privateResult) {
5495
- const unspentUtxos = privateResult.utxos.filter((u) => !u.isSpent);
5496
- output.private = {
5497
- balance: privateResult.privateBalance,
5498
- balanceWei: privateResult.privateBalanceWei,
5499
- utxoCount: privateResult.unspentCount,
5500
- utxos: unspentUtxos.map((u) => ({
5501
- index: u.index,
5502
- amount: u.amount
5503
- }))
5504
- };
5505
- } else {
5506
- output.private = {
5507
- balance: null,
5508
- note: "Set VEIL_KEY to see private balance"
5509
- };
5510
- }
5511
- output.queue = {
5512
- balance: queueResult.queueBalance,
5513
- balanceWei: queueResult.queueBalanceWei,
5514
- count: queueResult.pendingCount,
5515
- deposits: queueResult.pendingDeposits.map((d) => ({
5516
- nonce: d.nonce,
5517
- amount: d.amount,
5518
- status: d.status,
5519
- timestamp: d.timestamp
5520
- }))
5878
+ pools
5521
5879
  };
5522
5880
  console.log(JSON.stringify(output, null, 2));
5523
5881
  } catch (error) {
@@ -5530,8 +5888,9 @@ function createBalanceCommand() {
5530
5888
 
5531
5889
  // src/cli/commands/queue-balance.ts
5532
5890
  function createQueueBalanceCommand() {
5533
- const balance = new Command("queue-balance").description("Show queue balance and pending deposits").option("--wallet-key <key>", "Ethereum wallet key (or set WALLET_KEY env)").option("--address <address>", "Address to check (or derived from wallet key)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (options) => {
5891
+ const balance = new Command("queue-balance").description("Show queue balance and pending deposits").option("--pool <pool>", "Pool to check (eth, usdc, or cbbtc)", "eth").option("--wallet-key <key>", "Ethereum wallet key (or set WALLET_KEY env)").option("--address <address>", "Address to check (or derived from wallet key)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (options) => {
5534
5892
  try {
5893
+ const pool = (options.pool || "eth").toLowerCase();
5535
5894
  let address;
5536
5895
  if (options.address) {
5537
5896
  address = options.address;
@@ -5547,7 +5906,7 @@ function createQueueBalanceCommand() {
5547
5906
  process.stderr.write(`\r\x1B[K${msg}`);
5548
5907
  };
5549
5908
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
5550
- const result = await getQueueBalance({ address, rpcUrl, onProgress });
5909
+ const result = await getQueueBalance({ address, pool, rpcUrl, onProgress });
5551
5910
  if (!options.quiet) {
5552
5911
  process.stderr.write("\r\x1B[K");
5553
5912
  }
@@ -5564,8 +5923,9 @@ function createQueueBalanceCommand() {
5564
5923
 
5565
5924
  // src/cli/commands/private-balance.ts
5566
5925
  function createPrivateBalanceCommand() {
5567
- const privateBalance = new Command("private-balance").description("Show private balance (requires VEIL_KEY)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--show-utxos", "Show individual UTXO details").option("--quiet", "Suppress progress output").action(async (options) => {
5926
+ const privateBalance = new Command("private-balance").description("Show private balance (requires VEIL_KEY)").option("--pool <pool>", "Pool to check (eth, usdc, or cbbtc)", "eth").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--show-utxos", "Show individual UTXO details").option("--quiet", "Suppress progress output").action(async (options) => {
5568
5927
  try {
5928
+ const pool = (options.pool || "eth").toLowerCase();
5569
5929
  const veilKey = options.veilKey || process.env.VEIL_KEY;
5570
5930
  if (!veilKey) {
5571
5931
  throw new Error("Must provide --veil-key or set VEIL_KEY env");
@@ -5576,11 +5936,12 @@ function createPrivateBalanceCommand() {
5576
5936
  const msg = detail ? `${stage}: ${detail}` : stage;
5577
5937
  process.stderr.write(`\r\x1B[K${msg}`);
5578
5938
  };
5579
- const result = await getPrivateBalance({ keypair, rpcUrl, onProgress });
5939
+ const result = await getPrivateBalance({ keypair, pool, rpcUrl, onProgress });
5580
5940
  if (!options.quiet) {
5581
5941
  process.stderr.write("\r\x1B[K");
5582
5942
  }
5583
5943
  const output = {
5944
+ pool: pool.toUpperCase(),
5584
5945
  privateBalance: result.privateBalance,
5585
5946
  privateBalanceWei: result.privateBalanceWei,
5586
5947
  utxoCount: result.utxoCount,
@@ -5818,8 +6179,8 @@ async function submitRelay(options) {
5818
6179
  if (type !== "withdraw" && type !== "transfer") {
5819
6180
  throw new RelayError('Invalid type. Must be "withdraw" or "transfer"', 400);
5820
6181
  }
5821
- if (pool !== "eth" && pool !== "usdc") {
5822
- throw new RelayError('Invalid pool. Must be "eth" or "usdc"', 400);
6182
+ if (pool !== "eth" && pool !== "usdc" && pool !== "cbbtc") {
6183
+ throw new RelayError('Invalid pool. Must be "eth", "usdc", or "cbbtc"', 400);
5823
6184
  }
5824
6185
  if (!proofArgs || !extData) {
5825
6186
  throw new RelayError("Missing proofArgs or extData", 400);
@@ -5916,15 +6277,16 @@ async function buildWithdrawProof(options) {
5916
6277
  amount,
5917
6278
  recipient,
5918
6279
  keypair,
6280
+ pool = "eth",
5919
6281
  rpcUrl,
5920
6282
  onProgress
5921
6283
  } = options;
5922
- const addresses = getAddresses();
5923
- const poolConfig = POOL_CONFIG.eth;
5924
- const poolAddress = addresses.ethPool;
6284
+ const poolConfig = POOL_CONFIG[pool];
6285
+ const poolAddress = getPoolAddress(pool);
5925
6286
  onProgress?.("Fetching your UTXOs...");
5926
6287
  const balanceResult = await getPrivateBalance({
5927
6288
  keypair,
6289
+ pool,
5928
6290
  rpcUrl,
5929
6291
  onProgress
5930
6292
  });
@@ -5998,12 +6360,12 @@ async function buildWithdrawProof(options) {
5998
6360
  };
5999
6361
  }
6000
6362
  async function withdraw(options) {
6001
- const { amount, recipient, onProgress } = options;
6363
+ const { amount, recipient, pool = "eth", onProgress } = options;
6002
6364
  const proof = await buildWithdrawProof(options);
6003
6365
  onProgress?.("Submitting to relay...");
6004
6366
  const relayResult = await submitRelay({
6005
6367
  type: "withdraw",
6006
- pool: "eth",
6368
+ pool,
6007
6369
  proofArgs: proof.proofArgs,
6008
6370
  extData: proof.extData,
6009
6371
  metadata: {
@@ -6023,16 +6385,18 @@ async function withdraw(options) {
6023
6385
  }
6024
6386
 
6025
6387
  // src/cli/commands/withdraw.ts
6388
+ var SUPPORTED_ASSETS2 = ["ETH", "USDC", "CBBTC"];
6026
6389
  function progress2(msg, quiet) {
6027
6390
  if (!quiet) {
6028
6391
  process.stderr.write(`\r\x1B[K${msg}`);
6029
6392
  }
6030
6393
  }
6031
6394
  function createWithdrawCommand() {
6032
- const withdrawCmd = new Command("withdraw").description("Withdraw from private pool to a public address").argument("<asset>", "Asset to withdraw (ETH)").argument("<amount>", "Amount to withdraw (e.g., 0.1)").argument("<recipient>", "Recipient address (e.g., 0x...)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (asset, amount, recipient, options) => {
6395
+ const withdrawCmd = new Command("withdraw").description("Withdraw from private pool to a public address").argument("<asset>", "Asset to withdraw (ETH, USDC, or CBBTC)").argument("<amount>", "Amount to withdraw (e.g., 0.1)").argument("<recipient>", "Recipient address (e.g., 0x...)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (asset, amount, recipient, options) => {
6033
6396
  try {
6034
- if (asset.toUpperCase() !== "ETH") {
6035
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
6397
+ const assetUpper = asset.toUpperCase();
6398
+ if (!SUPPORTED_ASSETS2.includes(assetUpper)) {
6399
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS2.join(", ")}`);
6036
6400
  }
6037
6401
  if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
6038
6402
  throw new CLIError(ErrorCode.INVALID_ADDRESS, "Invalid recipient address format");
@@ -6043,15 +6407,17 @@ function createWithdrawCommand() {
6043
6407
  }
6044
6408
  const keypair = new Keypair(veilKey);
6045
6409
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
6410
+ const pool = assetUpper.toLowerCase();
6046
6411
  const onProgress = options.quiet ? void 0 : (stage, detail) => {
6047
6412
  const msg = detail ? `${stage}: ${detail}` : stage;
6048
6413
  progress2(msg, options.quiet);
6049
6414
  };
6050
- progress2("Starting withdrawal...", options.quiet);
6415
+ progress2(`Starting ${assetUpper} withdrawal...`, options.quiet);
6051
6416
  const result = await withdraw({
6052
6417
  amount,
6053
6418
  recipient,
6054
6419
  keypair,
6420
+ pool,
6055
6421
  rpcUrl,
6056
6422
  onProgress
6057
6423
  });
@@ -6060,6 +6426,7 @@ function createWithdrawCommand() {
6060
6426
  success: result.success,
6061
6427
  transactionHash: result.transactionHash,
6062
6428
  blockNumber: result.blockNumber,
6429
+ asset: assetUpper,
6063
6430
  amount: result.amount,
6064
6431
  recipient: result.recipient
6065
6432
  }, null, 2));
@@ -6125,12 +6492,12 @@ async function buildTransferProof(options) {
6125
6492
  amount,
6126
6493
  recipientAddress,
6127
6494
  senderKeypair,
6495
+ pool = "eth",
6128
6496
  rpcUrl,
6129
6497
  onProgress
6130
6498
  } = options;
6131
- const addresses = getAddresses();
6132
- const poolConfig = POOL_CONFIG.eth;
6133
- const poolAddress = addresses.ethPool;
6499
+ const poolConfig = POOL_CONFIG[pool];
6500
+ const poolAddress = getPoolAddress(pool);
6134
6501
  onProgress?.("Checking recipient registration...");
6135
6502
  const { isRegistered: isRegistered2, depositKey } = await checkRecipientRegistration(
6136
6503
  recipientAddress,
@@ -6142,6 +6509,7 @@ async function buildTransferProof(options) {
6142
6509
  onProgress?.("Fetching your UTXOs...");
6143
6510
  const balanceResult = await getPrivateBalance({
6144
6511
  keypair: senderKeypair,
6512
+ pool,
6145
6513
  rpcUrl,
6146
6514
  onProgress
6147
6515
  });
@@ -6222,12 +6590,12 @@ async function buildTransferProof(options) {
6222
6590
  };
6223
6591
  }
6224
6592
  async function transfer(options) {
6225
- const { amount, recipientAddress, onProgress } = options;
6593
+ const { amount, recipientAddress, pool = "eth", onProgress } = options;
6226
6594
  const proof = await buildTransferProof(options);
6227
6595
  onProgress?.("Submitting to relay...");
6228
6596
  const relayResult = await submitRelay({
6229
6597
  type: "transfer",
6230
- pool: "eth",
6598
+ pool,
6231
6599
  proofArgs: proof.proofArgs,
6232
6600
  extData: proof.extData,
6233
6601
  metadata: {
@@ -6246,13 +6614,13 @@ async function transfer(options) {
6246
6614
  };
6247
6615
  }
6248
6616
  async function mergeUtxos(options) {
6249
- const { amount, keypair, rpcUrl, onProgress } = options;
6250
- const addresses = getAddresses();
6251
- const poolConfig = POOL_CONFIG.eth;
6252
- const poolAddress = addresses.ethPool;
6617
+ const { amount, keypair, pool = "eth", rpcUrl, onProgress } = options;
6618
+ const poolConfig = POOL_CONFIG[pool];
6619
+ const poolAddress = getPoolAddress(pool);
6253
6620
  onProgress?.("Fetching your UTXOs...");
6254
6621
  const balanceResult = await getPrivateBalance({
6255
6622
  keypair,
6623
+ pool,
6256
6624
  rpcUrl,
6257
6625
  onProgress
6258
6626
  });
@@ -6319,7 +6687,7 @@ async function mergeUtxos(options) {
6319
6687
  onProgress?.("Submitting to relay...");
6320
6688
  const relayResult = await submitRelay({
6321
6689
  type: "transfer",
6322
- pool: "eth",
6690
+ pool,
6323
6691
  proofArgs: {
6324
6692
  proof: result.args.proof,
6325
6693
  root: result.args.root,
@@ -6345,16 +6713,18 @@ async function mergeUtxos(options) {
6345
6713
  }
6346
6714
 
6347
6715
  // src/cli/commands/transfer.ts
6716
+ var SUPPORTED_ASSETS3 = ["ETH", "USDC", "CBBTC"];
6348
6717
  function progress3(msg, quiet) {
6349
6718
  if (!quiet) {
6350
6719
  process.stderr.write(`\r\x1B[K${msg}`);
6351
6720
  }
6352
6721
  }
6353
6722
  function createTransferCommand() {
6354
- const transferCmd = new Command("transfer").description("Transfer privately within the pool to another registered address").argument("<asset>", "Asset to transfer (ETH)").argument("<amount>", "Amount to transfer (e.g., 0.1)").argument("<recipient>", "Recipient address (must be registered)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (asset, amount, recipient, options) => {
6723
+ const transferCmd = new Command("transfer").description("Transfer privately within the pool to another registered address").argument("<asset>", "Asset to transfer (ETH, USDC, or CBBTC)").argument("<amount>", "Amount to transfer (e.g., 0.1)").argument("<recipient>", "Recipient address (must be registered)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (asset, amount, recipient, options) => {
6355
6724
  try {
6356
- if (asset.toUpperCase() !== "ETH") {
6357
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
6725
+ const assetUpper = asset.toUpperCase();
6726
+ if (!SUPPORTED_ASSETS3.includes(assetUpper)) {
6727
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS3.join(", ")}`);
6358
6728
  }
6359
6729
  if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
6360
6730
  throw new CLIError(ErrorCode.INVALID_ADDRESS, "Invalid recipient address format");
@@ -6365,15 +6735,17 @@ function createTransferCommand() {
6365
6735
  }
6366
6736
  const senderKeypair = new Keypair(veilKey);
6367
6737
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
6738
+ const pool = assetUpper.toLowerCase();
6368
6739
  const onProgress = options.quiet ? void 0 : (stage, detail) => {
6369
6740
  const msg = detail ? `${stage}: ${detail}` : stage;
6370
6741
  progress3(msg, options.quiet);
6371
6742
  };
6372
- progress3("Starting transfer...", options.quiet);
6743
+ progress3(`Starting ${assetUpper} transfer...`, options.quiet);
6373
6744
  const result = await transfer({
6374
6745
  amount,
6375
6746
  recipientAddress: recipient,
6376
6747
  senderKeypair,
6748
+ pool,
6377
6749
  rpcUrl,
6378
6750
  onProgress
6379
6751
  });
@@ -6382,6 +6754,7 @@ function createTransferCommand() {
6382
6754
  success: result.success,
6383
6755
  transactionHash: result.transactionHash,
6384
6756
  blockNumber: result.blockNumber,
6757
+ asset: assetUpper,
6385
6758
  amount: result.amount,
6386
6759
  recipient: result.recipient,
6387
6760
  type: "transfer"
@@ -6395,10 +6768,11 @@ function createTransferCommand() {
6395
6768
  return transferCmd;
6396
6769
  }
6397
6770
  function createMergeCommand() {
6398
- const mergeCmd = new Command("merge").description("Merge UTXOs by self-transfer (consolidate small UTXOs)").argument("<asset>", "Asset to merge (ETH)").argument("<amount>", "Amount to merge (e.g., 0.5)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (asset, amount, options) => {
6771
+ const mergeCmd = new Command("merge").description("Merge UTXOs by self-transfer (consolidate small UTXOs)").argument("<asset>", "Asset to merge (ETH, USDC, or CBBTC)").argument("<amount>", "Amount to merge (e.g., 0.5)").option("--veil-key <key>", "Veil private key (or set VEIL_KEY env)").option("--rpc-url <url>", "RPC URL (or set RPC_URL env)").option("--quiet", "Suppress progress output").action(async (asset, amount, options) => {
6399
6772
  try {
6400
- if (asset.toUpperCase() !== "ETH") {
6401
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
6773
+ const assetUpper = asset.toUpperCase();
6774
+ if (!SUPPORTED_ASSETS3.includes(assetUpper)) {
6775
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS3.join(", ")}`);
6402
6776
  }
6403
6777
  const veilKey = options.veilKey || process.env.VEIL_KEY;
6404
6778
  if (!veilKey) {
@@ -6406,14 +6780,16 @@ function createMergeCommand() {
6406
6780
  }
6407
6781
  const keypair = new Keypair(veilKey);
6408
6782
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
6783
+ const pool = assetUpper.toLowerCase();
6409
6784
  const onProgress = options.quiet ? void 0 : (stage, detail) => {
6410
6785
  const msg = detail ? `${stage}: ${detail}` : stage;
6411
6786
  progress3(msg, options.quiet);
6412
6787
  };
6413
- progress3("Starting merge (self-transfer)...", options.quiet);
6788
+ progress3(`Starting ${assetUpper} merge (self-transfer)...`, options.quiet);
6414
6789
  const result = await mergeUtxos({
6415
6790
  amount,
6416
6791
  keypair,
6792
+ pool,
6417
6793
  rpcUrl,
6418
6794
  onProgress
6419
6795
  });
@@ -6422,6 +6798,7 @@ function createMergeCommand() {
6422
6798
  success: result.success,
6423
6799
  transactionHash: result.transactionHash,
6424
6800
  blockNumber: result.blockNumber,
6801
+ asset: assetUpper,
6425
6802
  amount: result.amount,
6426
6803
  type: "merge"
6427
6804
  }, null, 2));
@@ -6506,7 +6883,7 @@ function createStatusCommand() {
6506
6883
  // src/cli/index.ts
6507
6884
  loadEnv();
6508
6885
  var program2 = new Command();
6509
- program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.1.0");
6886
+ program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.3.0");
6510
6887
  program2.addCommand(createInitCommand());
6511
6888
  program2.addCommand(createKeypairCommand());
6512
6889
  program2.addCommand(createRegisterCommand());