@veil-cash/sdk 0.2.0 → 0.4.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,28 +4229,55 @@ 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 derivation = options.signMessage ? "wallet-signature" : options.signature ? "provided-signature" : "random";
4257
+ const derivationLabel = options.signMessage ? "Derived Veil keypair from wallet signature" : options.signature ? "Derived Veil keypair from provided signature" : "Generated new Veil keypair";
4171
4258
  if (options.json) {
4172
- const kp2 = new Keypair();
4259
+ const kp2 = await createKp();
4173
4260
  console.log(JSON.stringify({
4174
4261
  veilKey: kp2.privkey,
4175
- depositKey: kp2.depositKey()
4262
+ veilPrivateKey: kp2.privkey,
4263
+ depositKey: kp2.depositKey(),
4264
+ derivation
4176
4265
  }, null, 2));
4177
4266
  process.exit(0);
4178
4267
  return;
4179
4268
  }
4180
4269
  if (!options.save) {
4181
- const kp2 = new Keypair();
4182
- console.log("\nGenerated new Veil keypair:\n");
4270
+ const kp2 = await createKp();
4271
+ console.log(`
4272
+ ${derivationLabel}:
4273
+ `);
4183
4274
  console.log("Veil Private Key:");
4184
4275
  console.log(` ${kp2.privkey}
4185
4276
  `);
4186
4277
  console.log("Deposit Key (register this on-chain):");
4187
4278
  console.log(` ${kp2.depositKey()}
4188
4279
  `);
4189
- console.log("(Not saved - use --out <path> to save to a file)");
4280
+ console.log("(Not saved - run without --no-save to save to .env.veil)");
4190
4281
  process.exit(0);
4191
4282
  return;
4192
4283
  }
@@ -4201,8 +4292,10 @@ WARNING: A Veil key already exists in ${envPath}`);
4201
4292
  return;
4202
4293
  }
4203
4294
  }
4204
- const kp = new Keypair();
4205
- console.log("\nGenerated new Veil keypair:\n");
4295
+ const kp = await createKp();
4296
+ console.log(`
4297
+ ${derivationLabel}:
4298
+ `);
4206
4299
  console.log("Veil Private Key:");
4207
4300
  console.log(` ${kp.privkey}
4208
4301
  `);
@@ -4250,10 +4343,43 @@ var ADDRESSES = {
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
+ };
4254
4358
  function getAddresses() {
4255
4359
  return ADDRESSES;
4256
4360
  }
4361
+ function getPoolAddress(pool) {
4362
+ const addresses = getAddresses();
4363
+ switch (pool) {
4364
+ case "eth":
4365
+ return addresses.ethPool;
4366
+ case "usdc":
4367
+ return addresses.usdcPool;
4368
+ default:
4369
+ throw new Error(`Unknown pool: ${pool}`);
4370
+ }
4371
+ }
4372
+ function getQueueAddress(pool) {
4373
+ const addresses = getAddresses();
4374
+ switch (pool) {
4375
+ case "eth":
4376
+ return addresses.ethQueue;
4377
+ case "usdc":
4378
+ return addresses.usdcQueue;
4379
+ default:
4380
+ throw new Error(`Unknown pool: ${pool}`);
4381
+ }
4382
+ }
4257
4383
  function getRelayUrl() {
4258
4384
  return ADDRESSES.relayUrl;
4259
4385
  }
@@ -4315,6 +4441,23 @@ var ENTRY_ABI = [
4315
4441
  stateMutability: "nonpayable",
4316
4442
  type: "function"
4317
4443
  },
4444
+ // Change deposit key (must already be registered)
4445
+ {
4446
+ inputs: [
4447
+ {
4448
+ components: [
4449
+ { name: "owner", type: "address" },
4450
+ { name: "depositKey", type: "bytes" }
4451
+ ],
4452
+ name: "_account",
4453
+ type: "tuple"
4454
+ }
4455
+ ],
4456
+ name: "changeDepositKey",
4457
+ outputs: [],
4458
+ stateMutability: "nonpayable",
4459
+ type: "function"
4460
+ },
4318
4461
  // Queue ETH deposit
4319
4462
  {
4320
4463
  inputs: [{ name: "_depositKey", type: "bytes" }],
@@ -4828,6 +4971,35 @@ var POOL_ABI = [
4828
4971
  // ============ RECEIVE ============
4829
4972
  { stateMutability: "payable", type: "receive" }
4830
4973
  ];
4974
+ var ERC20_ABI = [
4975
+ {
4976
+ inputs: [
4977
+ { name: "spender", type: "address" },
4978
+ { name: "amount", type: "uint256" }
4979
+ ],
4980
+ name: "approve",
4981
+ outputs: [{ name: "", type: "bool" }],
4982
+ stateMutability: "nonpayable",
4983
+ type: "function"
4984
+ },
4985
+ {
4986
+ inputs: [{ name: "account", type: "address" }],
4987
+ name: "balanceOf",
4988
+ outputs: [{ name: "", type: "uint256" }],
4989
+ stateMutability: "view",
4990
+ type: "function"
4991
+ },
4992
+ {
4993
+ inputs: [
4994
+ { name: "owner", type: "address" },
4995
+ { name: "spender", type: "address" }
4996
+ ],
4997
+ name: "allowance",
4998
+ outputs: [{ name: "", type: "uint256" }],
4999
+ stateMutability: "view",
5000
+ type: "function"
5001
+ }
5002
+ ];
4831
5003
 
4832
5004
  // src/deposit.ts
4833
5005
  function buildRegisterTx(depositKey, ownerAddress) {
@@ -4845,6 +5017,21 @@ function buildRegisterTx(depositKey, ownerAddress) {
4845
5017
  data
4846
5018
  };
4847
5019
  }
5020
+ function buildChangeDepositKeyTx(depositKey, ownerAddress) {
5021
+ const addresses = getAddresses();
5022
+ const data = viem.encodeFunctionData({
5023
+ abi: ENTRY_ABI,
5024
+ functionName: "changeDepositKey",
5025
+ args: [{
5026
+ owner: ownerAddress,
5027
+ depositKey
5028
+ }]
5029
+ });
5030
+ return {
5031
+ to: addresses.entry,
5032
+ data
5033
+ };
5034
+ }
4848
5035
  function buildDepositETHTx(options) {
4849
5036
  const { depositKey, amount } = options;
4850
5037
  const addresses = getAddresses();
@@ -4860,6 +5047,34 @@ function buildDepositETHTx(options) {
4860
5047
  value
4861
5048
  };
4862
5049
  }
5050
+ function buildApproveUSDCTx(options) {
5051
+ const { amount } = options;
5052
+ const addresses = getAddresses();
5053
+ const amountWei = viem.parseUnits(amount, POOL_CONFIG.usdc.decimals);
5054
+ const data = viem.encodeFunctionData({
5055
+ abi: ERC20_ABI,
5056
+ functionName: "approve",
5057
+ args: [addresses.entry, amountWei]
5058
+ });
5059
+ return {
5060
+ to: addresses.usdcToken,
5061
+ data
5062
+ };
5063
+ }
5064
+ function buildDepositUSDCTx(options) {
5065
+ const { depositKey, amount } = options;
5066
+ const addresses = getAddresses();
5067
+ const amountWei = viem.parseUnits(amount, POOL_CONFIG.usdc.decimals);
5068
+ const data = viem.encodeFunctionData({
5069
+ abi: ENTRY_ABI,
5070
+ functionName: "queueUSDC",
5071
+ args: [amountWei, depositKey]
5072
+ });
5073
+ return {
5074
+ to: addresses.entry,
5075
+ data
5076
+ };
5077
+ }
4863
5078
  function createWallet(config) {
4864
5079
  const { privateKey, rpcUrl } = config;
4865
5080
  const account = accounts.privateKeyToAccount(privateKey);
@@ -5076,7 +5291,7 @@ function handleCLIError(error) {
5076
5291
 
5077
5292
  // src/cli/commands/register.ts
5078
5293
  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) => {
5294
+ 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
5295
  const jsonOutput = options.json;
5081
5296
  try {
5082
5297
  const depositKey = options.depositKey || process.env.DEPOSIT_KEY;
@@ -5094,8 +5309,9 @@ function createRegisterCommand() {
5094
5309
  }
5095
5310
  address2 = getAddress(walletKey);
5096
5311
  }
5097
- const tx2 = buildRegisterTx(depositKey, address2);
5312
+ const tx2 = options.force ? buildChangeDepositKeyTx(depositKey, address2) : buildRegisterTx(depositKey, address2);
5098
5313
  const payload = {
5314
+ action: options.force ? "changeDepositKey" : "register",
5099
5315
  to: tx2.to,
5100
5316
  data: tx2.data,
5101
5317
  value: "0",
@@ -5109,43 +5325,69 @@ function createRegisterCommand() {
5109
5325
  const address = getAddress(config.privateKey);
5110
5326
  if (!jsonOutput) console.log("\nChecking registration status...");
5111
5327
  const { registered, depositKey: existingKey } = await isRegistered(address, config.rpcUrl);
5112
- if (registered) {
5328
+ const keysMatch = registered && existingKey === depositKey;
5329
+ const isChange = registered && !keysMatch;
5330
+ if (registered && keysMatch) {
5113
5331
  if (jsonOutput) {
5114
5332
  console.log(JSON.stringify({
5115
5333
  success: true,
5116
5334
  alreadyRegistered: true,
5335
+ keysMatch: true,
5117
5336
  address,
5118
5337
  depositKey: existingKey
5119
5338
  }, null, 2));
5120
5339
  } else {
5121
5340
  console.log(`
5122
- Address ${address} is already registered.`);
5341
+ Address ${address} is already registered with this deposit key.`);
5123
5342
  console.log(`
5124
- Existing deposit key:`);
5125
- console.log(` ${existingKey}`);
5343
+ Deposit key: ${existingKey.slice(0, 40)}...`);
5126
5344
  }
5127
5345
  process.exit(0);
5128
5346
  return;
5129
5347
  }
5348
+ if (isChange && !options.force) {
5349
+ if (jsonOutput) {
5350
+ console.log(JSON.stringify({
5351
+ success: false,
5352
+ alreadyRegistered: true,
5353
+ keysMatch: false,
5354
+ address,
5355
+ onChainKey: existingKey,
5356
+ localKey: depositKey.slice(0, 40) + "...",
5357
+ hint: "Use --force to change deposit key"
5358
+ }, null, 2));
5359
+ } else {
5360
+ console.log(`
5361
+ Address ${address} is already registered with a different deposit key.`);
5362
+ console.log(`
5363
+ On-chain key: ${existingKey.slice(0, 40)}...`);
5364
+ console.log(` Local key: ${depositKey.slice(0, 40)}...`);
5365
+ console.log(`
5366
+ Use --force to change your deposit key on-chain.`);
5367
+ }
5368
+ process.exit(1);
5369
+ return;
5370
+ }
5371
+ const action = isChange ? "Changing deposit key" : "Registering deposit key";
5130
5372
  if (!jsonOutput) {
5131
- console.log("Registering deposit key...");
5373
+ console.log(`${action}...`);
5132
5374
  console.log(` Address: ${address}`);
5133
5375
  console.log(` Deposit Key: ${depositKey.slice(0, 40)}...`);
5134
5376
  }
5135
- const tx = buildRegisterTx(depositKey, address);
5377
+ const tx = isChange ? buildChangeDepositKeyTx(depositKey, address) : buildRegisterTx(depositKey, address);
5136
5378
  const result = await sendTransaction(config, tx);
5137
5379
  if (result.receipt.status === "success") {
5138
5380
  if (jsonOutput) {
5139
5381
  console.log(JSON.stringify({
5140
5382
  success: true,
5141
- alreadyRegistered: false,
5383
+ action: isChange ? "changed" : "registered",
5142
5384
  address,
5143
5385
  transactionHash: result.hash,
5144
5386
  blockNumber: result.receipt.blockNumber.toString(),
5145
5387
  gasUsed: result.receipt.gasUsed.toString()
5146
5388
  }, null, 2));
5147
5389
  } else {
5148
- console.log("\nRegistration successful!");
5390
+ console.log(isChange ? "\nDeposit key changed successfully!" : "\nRegistration successful!");
5149
5391
  console.log(` Transaction: ${result.hash}`);
5150
5392
  console.log(` Block: ${result.receipt.blockNumber}`);
5151
5393
  console.log(` Gas used: ${result.receipt.gasUsed}`);
@@ -5161,25 +5403,35 @@ Existing deposit key:`);
5161
5403
  });
5162
5404
  return register;
5163
5405
  }
5164
- var MINIMUM_DEPOSIT_ETH = 0.01;
5165
5406
  var DEPOSIT_FEE_PERCENT = 0.3;
5166
- var MINIMUM_DEPOSIT_WITH_FEE = MINIMUM_DEPOSIT_ETH / (1 - DEPOSIT_FEE_PERCENT / 100);
5407
+ var MINIMUM_DEPOSITS = {
5408
+ ETH: 0.01,
5409
+ USDC: 10
5410
+ };
5411
+ function getMinimumWithFee(asset) {
5412
+ const min = MINIMUM_DEPOSITS[asset] || 0;
5413
+ return min / (1 - DEPOSIT_FEE_PERCENT / 100);
5414
+ }
5415
+ var SUPPORTED_ASSETS = ["ETH", "USDC"];
5167
5416
  function progress(msg, quiet) {
5168
5417
  if (!quiet) {
5169
5418
  process.stderr.write(`\r\x1B[K${msg}`);
5170
5419
  }
5171
5420
  }
5172
5421
  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) => {
5422
+ const deposit = new Command("deposit").description("Deposit ETH or USDC into Veil").argument("<asset>", "Asset to deposit (ETH or USDC)").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
5423
  try {
5175
- if (asset.toUpperCase() !== "ETH") {
5176
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
5424
+ const assetUpper = asset.toUpperCase();
5425
+ if (!SUPPORTED_ASSETS.includes(assetUpper)) {
5426
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(", ")}`);
5177
5427
  }
5178
5428
  const amountNum = parseFloat(amount);
5179
- if (amountNum < MINIMUM_DEPOSIT_WITH_FEE) {
5429
+ const minimumWithFee = getMinimumWithFee(assetUpper);
5430
+ const minimumNet = MINIMUM_DEPOSITS[assetUpper];
5431
+ if (amountNum < minimumWithFee) {
5180
5432
  throw new CLIError(
5181
5433
  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.`
5434
+ `Minimum deposit is ${minimumNet} ${assetUpper} (net). With ${DEPOSIT_FEE_PERCENT}% fee, send at least ${minimumWithFee.toFixed(assetUpper === "ETH" ? 5 : 8)} ${assetUpper}.`
5183
5435
  );
5184
5436
  }
5185
5437
  const depositKey = options.depositKey || process.env.DEPOSIT_KEY;
@@ -5187,38 +5439,59 @@ function createDepositCommand() {
5187
5439
  throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, "Deposit key required. Use --deposit-key or set DEPOSIT_KEY in .env (run: veil init)");
5188
5440
  }
5189
5441
  progress("Building transaction...", options.quiet);
5190
- const tx = buildDepositETHTx({
5191
- depositKey,
5192
- amount
5193
- });
5442
+ let tx;
5443
+ let approveTx = null;
5444
+ if (assetUpper === "USDC") {
5445
+ approveTx = buildApproveUSDCTx({ amount });
5446
+ tx = buildDepositUSDCTx({ depositKey, amount });
5447
+ } else {
5448
+ tx = buildDepositETHTx({ depositKey, amount });
5449
+ }
5194
5450
  if (options.unsigned) {
5195
5451
  progress("", options.quiet);
5196
- const payload = {
5452
+ const payloads = [];
5453
+ if (approveTx) {
5454
+ payloads.push({
5455
+ step: "approve",
5456
+ to: approveTx.to,
5457
+ data: approveTx.data,
5458
+ value: "0",
5459
+ chainId: 8453
5460
+ });
5461
+ }
5462
+ payloads.push({
5463
+ step: "deposit",
5197
5464
  to: tx.to,
5198
5465
  data: tx.data,
5199
5466
  value: tx.value ? tx.value.toString() : "0",
5200
5467
  chainId: 8453
5201
- // Base mainnet
5202
- };
5203
- console.log(JSON.stringify(payload, null, 2));
5468
+ });
5469
+ console.log(JSON.stringify(payloads.length === 1 ? payloads[0] : payloads, null, 2));
5204
5470
  return;
5205
5471
  }
5206
5472
  const config = getConfig(options);
5207
5473
  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`);
5474
+ if (assetUpper === "ETH") {
5475
+ progress("Checking balance...", options.quiet);
5476
+ const balance = await getBalance(address, config.rpcUrl);
5477
+ const amountWei = viem.parseEther(amount);
5478
+ if (balance < amountWei) {
5479
+ progress("", options.quiet);
5480
+ throw new CLIError(ErrorCode.INSUFFICIENT_BALANCE, `Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${amount} ETH`);
5481
+ }
5482
+ }
5483
+ if (approveTx) {
5484
+ progress(`Approving ${assetUpper}...`, options.quiet);
5485
+ await sendTransaction(config, approveTx);
5214
5486
  }
5215
- progress("Sending transaction...", options.quiet);
5487
+ progress("Sending deposit transaction...", options.quiet);
5216
5488
  const result = await sendTransaction(config, tx);
5217
5489
  progress("Confirming...", options.quiet);
5218
5490
  progress("", options.quiet);
5219
5491
  console.log(JSON.stringify({
5220
5492
  success: result.receipt.status === "success",
5221
5493
  hash: result.hash,
5494
+ asset: assetUpper,
5222
5495
  amount,
5223
5496
  blockNumber: result.receipt.blockNumber.toString(),
5224
5497
  gasUsed: result.receipt.gasUsed.toString()
@@ -5315,15 +5588,16 @@ var DEPOSIT_STATUS_MAP = {
5315
5588
  3: "refunded"
5316
5589
  };
5317
5590
  async function getQueueBalance(options) {
5318
- const { address, rpcUrl, onProgress } = options;
5319
- const addresses = getAddresses();
5591
+ const { address, pool = "eth", rpcUrl, onProgress } = options;
5592
+ const queueAddress = getQueueAddress(pool);
5593
+ const poolConfig = POOL_CONFIG[pool];
5320
5594
  const publicClient = viem.createPublicClient({
5321
5595
  chain: chains.base,
5322
5596
  transport: viem.http(rpcUrl)
5323
5597
  });
5324
5598
  onProgress?.("Fetching pending deposits...");
5325
5599
  const pendingNonces = await publicClient.readContract({
5326
- address: addresses.ethQueue,
5600
+ address: queueAddress,
5327
5601
  abi: QUEUE_ABI,
5328
5602
  functionName: "getPendingDeposits"
5329
5603
  });
@@ -5334,7 +5608,7 @@ async function getQueueBalance(options) {
5334
5608
  const nonce = pendingNonces[i];
5335
5609
  onProgress?.("Checking deposit", `${i + 1}/${pendingNonces.length}`);
5336
5610
  const deposit = await publicClient.readContract({
5337
- address: addresses.ethQueue,
5611
+ address: queueAddress,
5338
5612
  abi: QUEUE_ABI,
5339
5613
  functionName: "getDeposit",
5340
5614
  args: [nonce]
@@ -5344,7 +5618,7 @@ async function getQueueBalance(options) {
5344
5618
  pendingDeposits.push({
5345
5619
  nonce: nonce.toString(),
5346
5620
  status: DEPOSIT_STATUS_MAP[deposit.status] || "pending",
5347
- amount: viem.formatEther(deposit.amountIn),
5621
+ amount: viem.formatUnits(deposit.amountIn, poolConfig.decimals),
5348
5622
  amountWei: deposit.amountIn.toString(),
5349
5623
  timestamp: new Date(Number(deposit.timestamp) * 1e3).toISOString()
5350
5624
  });
@@ -5355,15 +5629,16 @@ async function getQueueBalance(options) {
5355
5629
  }
5356
5630
  return {
5357
5631
  address,
5358
- queueBalance: viem.formatEther(totalQueueBalance),
5632
+ queueBalance: viem.formatUnits(totalQueueBalance, poolConfig.decimals),
5359
5633
  queueBalanceWei: totalQueueBalance.toString(),
5360
5634
  pendingDeposits,
5361
5635
  pendingCount: pendingDeposits.length
5362
5636
  };
5363
5637
  }
5364
5638
  async function getPrivateBalance(options) {
5365
- const { keypair, rpcUrl, onProgress } = options;
5366
- const addresses = getAddresses();
5639
+ const { keypair, pool = "eth", rpcUrl, onProgress } = options;
5640
+ const poolAddress = getPoolAddress(pool);
5641
+ const poolConfig = POOL_CONFIG[pool];
5367
5642
  if (!keypair.privkey) {
5368
5643
  throw new Error("Keypair must have a private key to calculate private balance");
5369
5644
  }
@@ -5373,7 +5648,7 @@ async function getPrivateBalance(options) {
5373
5648
  });
5374
5649
  onProgress?.("Fetching pool index...");
5375
5650
  const nextIndex = await publicClient.readContract({
5376
- address: addresses.ethPool,
5651
+ address: poolAddress,
5377
5652
  abi: POOL_ABI,
5378
5653
  functionName: "nextIndex"
5379
5654
  });
@@ -5396,7 +5671,7 @@ async function getPrivateBalance(options) {
5396
5671
  const batchNum = Math.floor(start / BATCH_SIZE) + 1;
5397
5672
  onProgress?.("Fetching encrypted outputs", `batch ${batchNum}/${totalBatches} (${start}-${end})`);
5398
5673
  const batch = await publicClient.readContract({
5399
- address: addresses.ethPool,
5674
+ address: poolAddress,
5400
5675
  abi: POOL_ABI,
5401
5676
  functionName: "getEncryptedOutputs",
5402
5677
  args: [BigInt(start), BigInt(end)]
@@ -5426,14 +5701,14 @@ async function getPrivateBalance(options) {
5426
5701
  const nullifier = utxo.getNullifier();
5427
5702
  const nullifierHex = toFixedHex(nullifier);
5428
5703
  const isSpent = await publicClient.readContract({
5429
- address: addresses.ethPool,
5704
+ address: poolAddress,
5430
5705
  abi: POOL_ABI,
5431
5706
  functionName: "isSpent",
5432
5707
  args: [nullifierHex]
5433
5708
  });
5434
5709
  utxoInfos.push({
5435
5710
  index,
5436
- amount: viem.formatEther(utxo.amount),
5711
+ amount: viem.formatUnits(utxo.amount, poolConfig.decimals),
5437
5712
  amountWei: utxo.amount.toString(),
5438
5713
  isSpent
5439
5714
  });
@@ -5445,7 +5720,7 @@ async function getPrivateBalance(options) {
5445
5720
  }
5446
5721
  }
5447
5722
  return {
5448
- privateBalance: viem.formatEther(totalBalance),
5723
+ privateBalance: viem.formatUnits(totalBalance, poolConfig.decimals),
5449
5724
  privateBalanceWei: totalBalance.toString(),
5450
5725
  utxoCount: decryptedUtxos.length,
5451
5726
  spentCount,
@@ -5453,9 +5728,62 @@ async function getPrivateBalance(options) {
5453
5728
  utxos: utxoInfos
5454
5729
  };
5455
5730
  }
5731
+ var SUPPORTED_POOLS = ["eth", "usdc"];
5732
+ async function fetchPoolBalance(pool, address, keypair, rpcUrl, onProgress) {
5733
+ const poolConfig = POOL_CONFIG[pool];
5734
+ const poolProgress = onProgress ? (stage, detail) => onProgress(`[${pool.toUpperCase()}] ${stage}`, detail) : void 0;
5735
+ const queueResult = await getQueueBalance({ address, pool, rpcUrl, onProgress: poolProgress });
5736
+ let privateResult = null;
5737
+ if (keypair) {
5738
+ privateResult = await getPrivateBalance({ keypair, pool, rpcUrl, onProgress: poolProgress });
5739
+ }
5740
+ const queueBalanceWei = BigInt(queueResult.queueBalanceWei);
5741
+ const privateBalanceWei = privateResult ? BigInt(privateResult.privateBalanceWei) : 0n;
5742
+ const totalBalanceWei = queueBalanceWei + privateBalanceWei;
5743
+ const result = {
5744
+ pool: pool.toUpperCase(),
5745
+ symbol: poolConfig.symbol,
5746
+ totalBalance: viem.formatUnits(totalBalanceWei, poolConfig.decimals),
5747
+ totalBalanceWei: totalBalanceWei.toString()
5748
+ };
5749
+ if (privateResult) {
5750
+ const unspentUtxos = privateResult.utxos.filter((u) => !u.isSpent);
5751
+ result.private = {
5752
+ balance: privateResult.privateBalance,
5753
+ balanceWei: privateResult.privateBalanceWei,
5754
+ utxoCount: privateResult.unspentCount,
5755
+ utxos: unspentUtxos.map((u) => ({
5756
+ index: u.index,
5757
+ amount: u.amount
5758
+ }))
5759
+ };
5760
+ } else {
5761
+ result.private = {
5762
+ balance: null,
5763
+ note: "Set VEIL_KEY to see private balance"
5764
+ };
5765
+ }
5766
+ result.queue = {
5767
+ balance: queueResult.queueBalance,
5768
+ balanceWei: queueResult.queueBalanceWei,
5769
+ count: queueResult.pendingCount,
5770
+ deposits: queueResult.pendingDeposits.map((d) => ({
5771
+ nonce: d.nonce,
5772
+ amount: d.amount,
5773
+ status: d.status,
5774
+ timestamp: d.timestamp
5775
+ }))
5776
+ };
5777
+ return result;
5778
+ }
5456
5779
  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) => {
5780
+ const balance = new Command("balance").description("Show queue and private balances (all pools by default)").option("--pool <pool>", "Pool to check (eth, usdc, 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
5781
  try {
5782
+ const poolArg = (options.pool || "all").toLowerCase();
5783
+ if (poolArg !== "all" && !SUPPORTED_POOLS.includes(poolArg)) {
5784
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported pool: ${options.pool}. Supported: ${SUPPORTED_POOLS.join(", ")}, all`);
5785
+ }
5786
+ const poolsToQuery = poolArg === "all" ? [...SUPPORTED_POOLS] : [poolArg];
5459
5787
  let address;
5460
5788
  if (options.address) {
5461
5789
  address = options.address;
@@ -5473,51 +5801,28 @@ function createBalanceCommand() {
5473
5801
  const msg = detail ? `${stage}: ${detail}` : stage;
5474
5802
  process.stderr.write(`\r\x1B[K${msg}`);
5475
5803
  };
5476
- const queueResult = await getQueueBalance({ address, rpcUrl, onProgress });
5477
- let privateResult = null;
5478
- if (keypair) {
5479
- privateResult = await getPrivateBalance({ keypair, rpcUrl, onProgress });
5804
+ const depositKey = process.env.DEPOSIT_KEY || (keypair ? keypair.depositKey() : null);
5805
+ if (poolsToQuery.length === 1) {
5806
+ const poolResult = await fetchPoolBalance(poolsToQuery[0], address, keypair, rpcUrl, onProgress);
5807
+ if (!options.quiet) process.stderr.write("\r\x1B[K");
5808
+ const output2 = {
5809
+ address,
5810
+ depositKey: depositKey || null,
5811
+ ...poolResult
5812
+ };
5813
+ console.log(JSON.stringify(output2, null, 2));
5814
+ return;
5480
5815
  }
5481
- if (!options.quiet) {
5482
- process.stderr.write("\r\x1B[K");
5816
+ const pools = [];
5817
+ for (const pool of poolsToQuery) {
5818
+ const poolResult = await fetchPoolBalance(pool, address, keypair, rpcUrl, onProgress);
5819
+ pools.push(poolResult);
5483
5820
  }
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);
5821
+ if (!options.quiet) process.stderr.write("\r\x1B[K");
5488
5822
  const output = {
5489
5823
  address,
5490
5824
  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
- }))
5825
+ pools
5521
5826
  };
5522
5827
  console.log(JSON.stringify(output, null, 2));
5523
5828
  } catch (error) {
@@ -5530,8 +5835,9 @@ function createBalanceCommand() {
5530
5835
 
5531
5836
  // src/cli/commands/queue-balance.ts
5532
5837
  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) => {
5838
+ const balance = new Command("queue-balance").description("Show queue balance and pending deposits").option("--pool <pool>", "Pool to check (eth or usdc)", "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
5839
  try {
5840
+ const pool = (options.pool || "eth").toLowerCase();
5535
5841
  let address;
5536
5842
  if (options.address) {
5537
5843
  address = options.address;
@@ -5547,7 +5853,7 @@ function createQueueBalanceCommand() {
5547
5853
  process.stderr.write(`\r\x1B[K${msg}`);
5548
5854
  };
5549
5855
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
5550
- const result = await getQueueBalance({ address, rpcUrl, onProgress });
5856
+ const result = await getQueueBalance({ address, pool, rpcUrl, onProgress });
5551
5857
  if (!options.quiet) {
5552
5858
  process.stderr.write("\r\x1B[K");
5553
5859
  }
@@ -5564,8 +5870,9 @@ function createQueueBalanceCommand() {
5564
5870
 
5565
5871
  // src/cli/commands/private-balance.ts
5566
5872
  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) => {
5873
+ const privateBalance = new Command("private-balance").description("Show private balance (requires VEIL_KEY)").option("--pool <pool>", "Pool to check (eth or usdc)", "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
5874
  try {
5875
+ const pool = (options.pool || "eth").toLowerCase();
5569
5876
  const veilKey = options.veilKey || process.env.VEIL_KEY;
5570
5877
  if (!veilKey) {
5571
5878
  throw new Error("Must provide --veil-key or set VEIL_KEY env");
@@ -5576,11 +5883,12 @@ function createPrivateBalanceCommand() {
5576
5883
  const msg = detail ? `${stage}: ${detail}` : stage;
5577
5884
  process.stderr.write(`\r\x1B[K${msg}`);
5578
5885
  };
5579
- const result = await getPrivateBalance({ keypair, rpcUrl, onProgress });
5886
+ const result = await getPrivateBalance({ keypair, pool, rpcUrl, onProgress });
5580
5887
  if (!options.quiet) {
5581
5888
  process.stderr.write("\r\x1B[K");
5582
5889
  }
5583
5890
  const output = {
5891
+ pool: pool.toUpperCase(),
5584
5892
  privateBalance: result.privateBalance,
5585
5893
  privateBalanceWei: result.privateBalanceWei,
5586
5894
  utxoCount: result.utxoCount,
@@ -5916,15 +6224,16 @@ async function buildWithdrawProof(options) {
5916
6224
  amount,
5917
6225
  recipient,
5918
6226
  keypair,
6227
+ pool = "eth",
5919
6228
  rpcUrl,
5920
6229
  onProgress
5921
6230
  } = options;
5922
- const addresses = getAddresses();
5923
- const poolConfig = POOL_CONFIG.eth;
5924
- const poolAddress = addresses.ethPool;
6231
+ const poolConfig = POOL_CONFIG[pool];
6232
+ const poolAddress = getPoolAddress(pool);
5925
6233
  onProgress?.("Fetching your UTXOs...");
5926
6234
  const balanceResult = await getPrivateBalance({
5927
6235
  keypair,
6236
+ pool,
5928
6237
  rpcUrl,
5929
6238
  onProgress
5930
6239
  });
@@ -5998,12 +6307,12 @@ async function buildWithdrawProof(options) {
5998
6307
  };
5999
6308
  }
6000
6309
  async function withdraw(options) {
6001
- const { amount, recipient, onProgress } = options;
6310
+ const { amount, recipient, pool = "eth", onProgress } = options;
6002
6311
  const proof = await buildWithdrawProof(options);
6003
6312
  onProgress?.("Submitting to relay...");
6004
6313
  const relayResult = await submitRelay({
6005
6314
  type: "withdraw",
6006
- pool: "eth",
6315
+ pool,
6007
6316
  proofArgs: proof.proofArgs,
6008
6317
  extData: proof.extData,
6009
6318
  metadata: {
@@ -6023,16 +6332,18 @@ async function withdraw(options) {
6023
6332
  }
6024
6333
 
6025
6334
  // src/cli/commands/withdraw.ts
6335
+ var SUPPORTED_ASSETS2 = ["ETH", "USDC"];
6026
6336
  function progress2(msg, quiet) {
6027
6337
  if (!quiet) {
6028
6338
  process.stderr.write(`\r\x1B[K${msg}`);
6029
6339
  }
6030
6340
  }
6031
6341
  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) => {
6342
+ const withdrawCmd = new Command("withdraw").description("Withdraw from private pool to a public address").argument("<asset>", "Asset to withdraw (ETH or USDC)").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
6343
  try {
6034
- if (asset.toUpperCase() !== "ETH") {
6035
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
6344
+ const assetUpper = asset.toUpperCase();
6345
+ if (!SUPPORTED_ASSETS2.includes(assetUpper)) {
6346
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS2.join(", ")}`);
6036
6347
  }
6037
6348
  if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
6038
6349
  throw new CLIError(ErrorCode.INVALID_ADDRESS, "Invalid recipient address format");
@@ -6043,15 +6354,17 @@ function createWithdrawCommand() {
6043
6354
  }
6044
6355
  const keypair = new Keypair(veilKey);
6045
6356
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
6357
+ const pool = assetUpper.toLowerCase();
6046
6358
  const onProgress = options.quiet ? void 0 : (stage, detail) => {
6047
6359
  const msg = detail ? `${stage}: ${detail}` : stage;
6048
6360
  progress2(msg, options.quiet);
6049
6361
  };
6050
- progress2("Starting withdrawal...", options.quiet);
6362
+ progress2(`Starting ${assetUpper} withdrawal...`, options.quiet);
6051
6363
  const result = await withdraw({
6052
6364
  amount,
6053
6365
  recipient,
6054
6366
  keypair,
6367
+ pool,
6055
6368
  rpcUrl,
6056
6369
  onProgress
6057
6370
  });
@@ -6060,6 +6373,7 @@ function createWithdrawCommand() {
6060
6373
  success: result.success,
6061
6374
  transactionHash: result.transactionHash,
6062
6375
  blockNumber: result.blockNumber,
6376
+ asset: assetUpper,
6063
6377
  amount: result.amount,
6064
6378
  recipient: result.recipient
6065
6379
  }, null, 2));
@@ -6125,12 +6439,12 @@ async function buildTransferProof(options) {
6125
6439
  amount,
6126
6440
  recipientAddress,
6127
6441
  senderKeypair,
6442
+ pool = "eth",
6128
6443
  rpcUrl,
6129
6444
  onProgress
6130
6445
  } = options;
6131
- const addresses = getAddresses();
6132
- const poolConfig = POOL_CONFIG.eth;
6133
- const poolAddress = addresses.ethPool;
6446
+ const poolConfig = POOL_CONFIG[pool];
6447
+ const poolAddress = getPoolAddress(pool);
6134
6448
  onProgress?.("Checking recipient registration...");
6135
6449
  const { isRegistered: isRegistered2, depositKey } = await checkRecipientRegistration(
6136
6450
  recipientAddress,
@@ -6142,6 +6456,7 @@ async function buildTransferProof(options) {
6142
6456
  onProgress?.("Fetching your UTXOs...");
6143
6457
  const balanceResult = await getPrivateBalance({
6144
6458
  keypair: senderKeypair,
6459
+ pool,
6145
6460
  rpcUrl,
6146
6461
  onProgress
6147
6462
  });
@@ -6222,12 +6537,12 @@ async function buildTransferProof(options) {
6222
6537
  };
6223
6538
  }
6224
6539
  async function transfer(options) {
6225
- const { amount, recipientAddress, onProgress } = options;
6540
+ const { amount, recipientAddress, pool = "eth", onProgress } = options;
6226
6541
  const proof = await buildTransferProof(options);
6227
6542
  onProgress?.("Submitting to relay...");
6228
6543
  const relayResult = await submitRelay({
6229
6544
  type: "transfer",
6230
- pool: "eth",
6545
+ pool,
6231
6546
  proofArgs: proof.proofArgs,
6232
6547
  extData: proof.extData,
6233
6548
  metadata: {
@@ -6246,13 +6561,13 @@ async function transfer(options) {
6246
6561
  };
6247
6562
  }
6248
6563
  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;
6564
+ const { amount, keypair, pool = "eth", rpcUrl, onProgress } = options;
6565
+ const poolConfig = POOL_CONFIG[pool];
6566
+ const poolAddress = getPoolAddress(pool);
6253
6567
  onProgress?.("Fetching your UTXOs...");
6254
6568
  const balanceResult = await getPrivateBalance({
6255
6569
  keypair,
6570
+ pool,
6256
6571
  rpcUrl,
6257
6572
  onProgress
6258
6573
  });
@@ -6319,7 +6634,7 @@ async function mergeUtxos(options) {
6319
6634
  onProgress?.("Submitting to relay...");
6320
6635
  const relayResult = await submitRelay({
6321
6636
  type: "transfer",
6322
- pool: "eth",
6637
+ pool,
6323
6638
  proofArgs: {
6324
6639
  proof: result.args.proof,
6325
6640
  root: result.args.root,
@@ -6345,16 +6660,18 @@ async function mergeUtxos(options) {
6345
6660
  }
6346
6661
 
6347
6662
  // src/cli/commands/transfer.ts
6663
+ var SUPPORTED_ASSETS3 = ["ETH", "USDC"];
6348
6664
  function progress3(msg, quiet) {
6349
6665
  if (!quiet) {
6350
6666
  process.stderr.write(`\r\x1B[K${msg}`);
6351
6667
  }
6352
6668
  }
6353
6669
  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) => {
6670
+ const transferCmd = new Command("transfer").description("Transfer privately within the pool to another registered address").argument("<asset>", "Asset to transfer (ETH or USDC)").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
6671
  try {
6356
- if (asset.toUpperCase() !== "ETH") {
6357
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
6672
+ const assetUpper = asset.toUpperCase();
6673
+ if (!SUPPORTED_ASSETS3.includes(assetUpper)) {
6674
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS3.join(", ")}`);
6358
6675
  }
6359
6676
  if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
6360
6677
  throw new CLIError(ErrorCode.INVALID_ADDRESS, "Invalid recipient address format");
@@ -6365,15 +6682,17 @@ function createTransferCommand() {
6365
6682
  }
6366
6683
  const senderKeypair = new Keypair(veilKey);
6367
6684
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
6685
+ const pool = assetUpper.toLowerCase();
6368
6686
  const onProgress = options.quiet ? void 0 : (stage, detail) => {
6369
6687
  const msg = detail ? `${stage}: ${detail}` : stage;
6370
6688
  progress3(msg, options.quiet);
6371
6689
  };
6372
- progress3("Starting transfer...", options.quiet);
6690
+ progress3(`Starting ${assetUpper} transfer...`, options.quiet);
6373
6691
  const result = await transfer({
6374
6692
  amount,
6375
6693
  recipientAddress: recipient,
6376
6694
  senderKeypair,
6695
+ pool,
6377
6696
  rpcUrl,
6378
6697
  onProgress
6379
6698
  });
@@ -6382,6 +6701,7 @@ function createTransferCommand() {
6382
6701
  success: result.success,
6383
6702
  transactionHash: result.transactionHash,
6384
6703
  blockNumber: result.blockNumber,
6704
+ asset: assetUpper,
6385
6705
  amount: result.amount,
6386
6706
  recipient: result.recipient,
6387
6707
  type: "transfer"
@@ -6395,10 +6715,11 @@ function createTransferCommand() {
6395
6715
  return transferCmd;
6396
6716
  }
6397
6717
  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) => {
6718
+ const mergeCmd = new Command("merge").description("Merge UTXOs by self-transfer (consolidate small UTXOs)").argument("<asset>", "Asset to merge (ETH or USDC)").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
6719
  try {
6400
- if (asset.toUpperCase() !== "ETH") {
6401
- throw new CLIError(ErrorCode.INVALID_AMOUNT, "Only ETH is supported");
6720
+ const assetUpper = asset.toUpperCase();
6721
+ if (!SUPPORTED_ASSETS3.includes(assetUpper)) {
6722
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS3.join(", ")}`);
6402
6723
  }
6403
6724
  const veilKey = options.veilKey || process.env.VEIL_KEY;
6404
6725
  if (!veilKey) {
@@ -6406,14 +6727,16 @@ function createMergeCommand() {
6406
6727
  }
6407
6728
  const keypair = new Keypair(veilKey);
6408
6729
  const rpcUrl = options.rpcUrl || process.env.RPC_URL;
6730
+ const pool = assetUpper.toLowerCase();
6409
6731
  const onProgress = options.quiet ? void 0 : (stage, detail) => {
6410
6732
  const msg = detail ? `${stage}: ${detail}` : stage;
6411
6733
  progress3(msg, options.quiet);
6412
6734
  };
6413
- progress3("Starting merge (self-transfer)...", options.quiet);
6735
+ progress3(`Starting ${assetUpper} merge (self-transfer)...`, options.quiet);
6414
6736
  const result = await mergeUtxos({
6415
6737
  amount,
6416
6738
  keypair,
6739
+ pool,
6417
6740
  rpcUrl,
6418
6741
  onProgress
6419
6742
  });
@@ -6422,6 +6745,7 @@ function createMergeCommand() {
6422
6745
  success: result.success,
6423
6746
  transactionHash: result.transactionHash,
6424
6747
  blockNumber: result.blockNumber,
6748
+ asset: assetUpper,
6425
6749
  amount: result.amount,
6426
6750
  type: "merge"
6427
6751
  }, null, 2));
@@ -6506,7 +6830,7 @@ function createStatusCommand() {
6506
6830
  // src/cli/index.ts
6507
6831
  loadEnv();
6508
6832
  var program2 = new Command();
6509
- program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.1.0");
6833
+ program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.3.0");
6510
6834
  program2.addCommand(createInitCommand());
6511
6835
  program2.addCommand(createKeypairCommand());
6512
6836
  program2.addCommand(createRegisterCommand());