@veil-cash/sdk 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,8 @@ SDK and CLI for interacting with [Veil Cash](https://veil.cash) privacy pools on
8
8
 
9
9
  Generate keypairs, register, deposit, withdraw, transfer, and merge ETH and USDC privately.
10
10
 
11
+ `0.6.2` adds `mergeSubaccount` — transfer a subaccount's private pool balance back to the main wallet via a ZK proof. Also adds `veil subaccount merge` CLI command.
12
+
11
13
  `0.6.0` adds SDK-first subaccount support for deterministic slot derivation, forwarder status, relay-backed deploy/sweep, and direct recovery.
12
14
 
13
15
  ## Installation
@@ -86,6 +88,7 @@ veil subaccount derive --slot 0
86
88
  veil subaccount status --slot 0
87
89
  veil subaccount deploy --slot 0
88
90
  veil subaccount sweep --slot 0 --asset eth
91
+ veil subaccount merge --slot 0 --pool eth
89
92
  veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
90
93
 
91
94
  # 9. Use JSON or unsigned modes when you need automation
@@ -207,7 +210,7 @@ Subaccounts are deterministic child slots derived from your main `VEIL_KEY`:
207
210
 
208
211
  `root key -> slot -> child key -> child deposit key -> forwarder`
209
212
 
210
- Base mainnet only. Deploy and sweep are relay-backed. Status reports the forwarder wallet and queue state only, not private pool attribution after queued funds are accepted. Recovery is for assets still sitting on the forwarder after refund or rejection, and is submitted directly on-chain by your CLI gas payer.
213
+ Base mainnet only. Deploy and sweep are relay-backed. Merge transfers the subaccount's private pool balance back to the main wallet via a ZK proof. Status reports forwarder wallet balances, private pool balances, and queue state for the child slot. Recovery is for assets still sitting on the forwarder after refund or rejection, and is submitted directly on-chain by your CLI gas payer.
211
214
 
212
215
  ```bash
213
216
  veil subaccount derive --slot 0
@@ -215,6 +218,7 @@ veil subaccount status --slot 0
215
218
  veil subaccount address --slot 0
216
219
  veil subaccount deploy --slot 0
217
220
  veil subaccount sweep --slot 0 --asset eth
221
+ veil subaccount merge --slot 0 --pool eth
218
222
  veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
219
223
  veil subaccount status --slot 0 --json
220
224
  ```
package/SDK.md CHANGED
@@ -10,7 +10,7 @@ If you are looking for the CLI first-run flow, go back to the main [README](./RE
10
10
  import {
11
11
  Keypair, buildRegisterTx, buildDepositETHTx,
12
12
  buildDepositUSDCTx, buildApproveUSDCTx,
13
- withdraw, transfer, getSubaccountStatus,
13
+ withdraw, transfer, getSubaccountStatus, mergeSubaccount,
14
14
  } from '@veil-cash/sdk';
15
15
  import { createWalletClient, http } from 'viem';
16
16
  import { base } from 'viem/chains';
@@ -203,14 +203,16 @@ Subaccounts are deterministic child slots derived from your main Veil key:
203
203
 
204
204
  `root key -> slot -> child key -> child deposit key -> forwarder`
205
205
 
206
- Base mainnet only. Status shows the forwarder wallet and queue state only. Deploy and sweep are relay-backed. Recovery signs a forwarder withdraw request with the child key and returns a direct transaction for your gas payer to submit.
206
+ Base mainnet only. Status shows the child slot's forwarder wallet balances, private pool balances, and queue state. Deploy and sweep are relay-backed. Merge transfers the subaccount's private pool balance back to the main wallet via a ZK proof. Recovery signs a forwarder withdraw request with the child key and returns a direct transaction for your gas payer to submit.
207
207
 
208
208
  ```typescript
209
209
  import {
210
210
  deriveSubaccountSlot,
211
+ getSubaccountPrivateBalance,
211
212
  getSubaccountStatus,
212
213
  deploySubaccountForwarder,
213
214
  sweepSubaccountForwarder,
215
+ mergeSubaccount,
214
216
  buildSubaccountRecoveryTx,
215
217
  } from '@veil-cash/sdk';
216
218
 
@@ -223,6 +225,14 @@ const status = await getSubaccountStatus({
223
225
  rootPrivateKey: veilKey,
224
226
  slot: 0,
225
227
  });
228
+ // status.privateBalances.eth.privateBalance, status.privateBalances.usdc.privateBalance
229
+
230
+ const privateBalance = await getSubaccountPrivateBalance({
231
+ rootPrivateKey: veilKey,
232
+ slot: 0,
233
+ pool: 'eth',
234
+ });
235
+ // privateBalance.privateBalance, privateBalance.unspentCount
226
236
 
227
237
  await deploySubaccountForwarder({
228
238
  rootPrivateKey: veilKey,
@@ -234,6 +244,14 @@ await sweepSubaccountForwarder({
234
244
  asset: 'eth',
235
245
  });
236
246
 
247
+ // Merge subaccount's private pool balance back to the main wallet
248
+ const mergeResult = await mergeSubaccount({
249
+ rootPrivateKey: veilKey,
250
+ slot: 0,
251
+ pool: 'eth', // 'eth' | 'usdc' (default: 'eth')
252
+ });
253
+ // mergeResult.amount, mergeResult.transactionHash
254
+
237
255
  const recovery = await buildSubaccountRecoveryTx({
238
256
  rootPrivateKey: veilKey,
239
257
  slot: 0,
@@ -6682,7 +6682,16 @@ async function postRelayJson(endpoint, body, relayUrl) {
6682
6682
  },
6683
6683
  body: JSON.stringify(body)
6684
6684
  });
6685
- const data = await response.json();
6685
+ const text = await response.text();
6686
+ let data;
6687
+ try {
6688
+ data = JSON.parse(text);
6689
+ } catch {
6690
+ throw new RelayError(
6691
+ `Relay returned non-JSON response (HTTP ${response.status})`,
6692
+ response.status
6693
+ );
6694
+ }
6686
6695
  if (!response.ok) {
6687
6696
  const errorData = data;
6688
6697
  throw new RelayError(
@@ -7589,11 +7598,12 @@ function deriveSubaccountChildDepositKey(childPrivateKey) {
7589
7598
  }
7590
7599
  async function predictSubaccountForwarder(options) {
7591
7600
  const publicClient = createBaseClient(options.rpcUrl);
7601
+ const depositKeyBytes = options.childDepositKey.startsWith("0x") ? options.childDepositKey : `0x${options.childDepositKey}`;
7592
7602
  return publicClient.readContract({
7593
7603
  abi: FORWARDER_FACTORY_ABI,
7594
7604
  address: getForwarderFactoryAddress(),
7595
7605
  functionName: "computeAddress",
7596
- args: [options.salt, options.childDepositKey, options.childOwner]
7606
+ args: [options.salt, depositKeyBytes, options.childOwner]
7597
7607
  });
7598
7608
  }
7599
7609
  async function deriveSubaccountSlot(options) {
@@ -7630,7 +7640,7 @@ async function deploySubaccountForwarder(options) {
7630
7640
  slot: options.slot,
7631
7641
  rpcUrl: options.rpcUrl
7632
7642
  });
7633
- return postRelayJson(
7643
+ const result = await postRelayJson(
7634
7644
  "/stealth/deploy",
7635
7645
  {
7636
7646
  salt: slot.salt,
@@ -7640,6 +7650,7 @@ async function deploySubaccountForwarder(options) {
7640
7650
  },
7641
7651
  options.relayUrl
7642
7652
  );
7653
+ return { ...result, slot };
7643
7654
  }
7644
7655
  async function sweepSubaccountForwarder(options) {
7645
7656
  const asset = normalizeAsset(options.asset);
@@ -7664,11 +7675,32 @@ function toQueueStatus(asset, result) {
7664
7675
  pendingDeposits: result.pendingDeposits
7665
7676
  };
7666
7677
  }
7678
+ function toPrivateBalanceStatus(result) {
7679
+ return {
7680
+ privateBalance: result.privateBalance,
7681
+ privateBalanceWei: result.privateBalanceWei,
7682
+ utxoCount: result.utxoCount,
7683
+ spentCount: result.spentCount,
7684
+ unspentCount: result.unspentCount
7685
+ };
7686
+ }
7687
+ async function getSubaccountPrivateBalance(options) {
7688
+ const normalizedSlot = normalizeSlot(options.slot);
7689
+ assertPrivateKey(options.rootPrivateKey, "rootPrivateKey");
7690
+ const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, normalizedSlot);
7691
+ const childKeypair = new Keypair(childPrivateKey);
7692
+ return getPrivateBalance({
7693
+ keypair: childKeypair,
7694
+ pool: options.pool,
7695
+ rpcUrl: options.rpcUrl,
7696
+ onProgress: options.onProgress
7697
+ });
7698
+ }
7667
7699
  async function getSubaccountStatus(options) {
7668
7700
  const slot = await deriveSubaccountSlot(options);
7669
7701
  const publicClient = createBaseClient(options.rpcUrl);
7670
7702
  const addresses = getAddresses();
7671
- const [deployed, ethWei, usdcWei, ethQueue, usdcQueue] = await Promise.all([
7703
+ const [deployed, ethWei, usdcWei, ethQueue, usdcQueue, ethPrivate, usdcPrivate] = await Promise.all([
7672
7704
  isSubaccountForwarderDeployed({
7673
7705
  forwarderAddress: slot.forwarderAddress,
7674
7706
  rpcUrl: options.rpcUrl
@@ -7689,6 +7721,18 @@ async function getSubaccountStatus(options) {
7689
7721
  address: slot.forwarderAddress,
7690
7722
  pool: "usdc",
7691
7723
  rpcUrl: options.rpcUrl
7724
+ }),
7725
+ getSubaccountPrivateBalance({
7726
+ rootPrivateKey: options.rootPrivateKey,
7727
+ slot: options.slot,
7728
+ pool: "eth",
7729
+ rpcUrl: options.rpcUrl
7730
+ }),
7731
+ getSubaccountPrivateBalance({
7732
+ rootPrivateKey: options.rootPrivateKey,
7733
+ slot: options.slot,
7734
+ pool: "usdc",
7735
+ rpcUrl: options.rpcUrl
7692
7736
  })
7693
7737
  ]);
7694
7738
  return {
@@ -7704,6 +7748,10 @@ async function getSubaccountStatus(options) {
7704
7748
  balanceWei: usdcWei.toString()
7705
7749
  }
7706
7750
  },
7751
+ privateBalances: {
7752
+ eth: toPrivateBalanceStatus(ethPrivate),
7753
+ usdc: toPrivateBalanceStatus(usdcPrivate)
7754
+ },
7707
7755
  queues: {
7708
7756
  eth: toQueueStatus("eth", ethQueue),
7709
7757
  usdc: toQueueStatus("usdc", usdcQueue)
@@ -7850,6 +7898,137 @@ async function buildSubaccountRecoveryTx(options) {
7850
7898
  signature
7851
7899
  };
7852
7900
  }
7901
+ async function mergeSubaccount(options) {
7902
+ const {
7903
+ rootPrivateKey,
7904
+ slot,
7905
+ pool = "eth",
7906
+ rpcUrl,
7907
+ relayUrl,
7908
+ onProgress
7909
+ } = options;
7910
+ const normalizedSlot = normalizeSlot(slot);
7911
+ assertPrivateKey(rootPrivateKey, "rootPrivateKey");
7912
+ const poolConfig = POOL_CONFIG[pool];
7913
+ const poolAddress = getPoolAddress(pool);
7914
+ const childPrivateKey = deriveSubaccountChildPrivateKey(rootPrivateKey, normalizedSlot);
7915
+ const childKeypair = new Keypair(childPrivateKey);
7916
+ const parentKeypair = new Keypair(rootPrivateKey);
7917
+ onProgress?.("Fetching subaccount balance...");
7918
+ const balanceResult = await getPrivateBalance({
7919
+ keypair: childKeypair,
7920
+ pool,
7921
+ rpcUrl,
7922
+ onProgress
7923
+ });
7924
+ const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
7925
+ if (unspentUtxoInfos.length === 0) {
7926
+ throw new Error("Subaccount has no unspent UTXOs to merge");
7927
+ }
7928
+ if (unspentUtxoInfos.length > 16) {
7929
+ throw new Error(
7930
+ `Subaccount has ${unspentUtxoInfos.length} unspent UTXOs which exceeds the 16-input circuit limit. Consolidate UTXOs on the subaccount first before merging.`
7931
+ );
7932
+ }
7933
+ onProgress?.("Preparing UTXOs...");
7934
+ const publicClient = viem.createPublicClient({
7935
+ chain: chains.base,
7936
+ transport: viem.http(rpcUrl)
7937
+ });
7938
+ const utxos = [];
7939
+ for (const utxoInfo of unspentUtxoInfos) {
7940
+ const encryptedOutputs = await publicClient.readContract({
7941
+ address: poolAddress,
7942
+ abi: POOL_ABI,
7943
+ functionName: "getEncryptedOutputs",
7944
+ args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
7945
+ });
7946
+ if (encryptedOutputs.length > 0) {
7947
+ try {
7948
+ const utxo = Utxo.decrypt(encryptedOutputs[0], childKeypair);
7949
+ utxo.index = utxoInfo.index;
7950
+ utxos.push(utxo);
7951
+ } catch {
7952
+ }
7953
+ }
7954
+ }
7955
+ if (utxos.length === 0) {
7956
+ throw new Error("Failed to decrypt subaccount UTXOs");
7957
+ }
7958
+ onProgress?.("Selecting UTXOs...");
7959
+ const amount = balanceResult.privateBalance;
7960
+ const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
7961
+ utxos,
7962
+ amount,
7963
+ poolConfig.decimals
7964
+ );
7965
+ const outputs = [];
7966
+ const mergeWei = viem.parseUnits(amount, poolConfig.decimals);
7967
+ outputs.push(new Utxo({ amount: mergeWei, keypair: parentKeypair }));
7968
+ if (changeAmount > 0n) {
7969
+ outputs.push(new Utxo({ amount: changeAmount, keypair: parentKeypair }));
7970
+ }
7971
+ onProgress?.("Fetching commitments...");
7972
+ const nextIndex = await publicClient.readContract({
7973
+ address: poolAddress,
7974
+ abi: POOL_ABI,
7975
+ functionName: "nextIndex"
7976
+ });
7977
+ const BATCH_SIZE = 5e3;
7978
+ const commitments = [];
7979
+ const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
7980
+ for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
7981
+ const end = Math.min(start + BATCH_SIZE, nextIndex);
7982
+ const batchNum = Math.floor(start / BATCH_SIZE) + 1;
7983
+ onProgress?.("Fetching commitments", `batch ${batchNum}/${totalBatches}`);
7984
+ const batch = await publicClient.readContract({
7985
+ address: poolAddress,
7986
+ abi: POOL_ABI,
7987
+ functionName: "getCommitments",
7988
+ args: [BigInt(start), BigInt(end)]
7989
+ });
7990
+ commitments.push(...batch.map((c) => c.toString()));
7991
+ }
7992
+ onProgress?.("Building ZK proof...");
7993
+ const result = await prepareTransaction({
7994
+ commitments,
7995
+ inputs: selectedUtxos,
7996
+ outputs,
7997
+ fee: 0,
7998
+ recipient: "0x0000000000000000000000000000000000000000",
7999
+ relayer: "0x0000000000000000000000000000000000000000",
8000
+ onProgress
8001
+ });
8002
+ onProgress?.("Submitting to relay...");
8003
+ const relayResult = await submitRelay({
8004
+ type: "transfer",
8005
+ pool,
8006
+ relayUrl,
8007
+ proofArgs: {
8008
+ proof: result.args.proof,
8009
+ root: result.args.root,
8010
+ inputNullifiers: result.args.inputNullifiers,
8011
+ outputCommitments: result.args.outputCommitments,
8012
+ publicAmount: result.args.publicAmount,
8013
+ extDataHash: result.args.extDataHash
8014
+ },
8015
+ extData: result.extData,
8016
+ metadata: {
8017
+ amount,
8018
+ recipient: "self",
8019
+ inputUtxoCount: selectedUtxos.length,
8020
+ outputUtxoCount: outputs.length
8021
+ }
8022
+ });
8023
+ return {
8024
+ success: relayResult.success,
8025
+ transactionHash: relayResult.transactionHash,
8026
+ blockNumber: relayResult.blockNumber,
8027
+ amount,
8028
+ slot: normalizedSlot,
8029
+ pool
8030
+ };
8031
+ }
7853
8032
 
7854
8033
  // src/cli/commands/subaccount.ts
7855
8034
  function parseSlotValue(raw) {
@@ -7883,6 +8062,13 @@ function parseAsset(raw) {
7883
8062
  }
7884
8063
  return asset;
7885
8064
  }
8065
+ function parsePool(raw) {
8066
+ const pool = raw.toLowerCase();
8067
+ if (pool !== "eth" && pool !== "usdc") {
8068
+ throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported pool: ${raw}. Supported: eth, usdc`);
8069
+ }
8070
+ return pool;
8071
+ }
7886
8072
  function printQueueHuman(title, queue) {
7887
8073
  printSection(title);
7888
8074
  printFields([
@@ -7902,6 +8088,7 @@ Examples:
7902
8088
  veil subaccount status --slot 0
7903
8089
  veil subaccount deploy --slot 0
7904
8090
  veil subaccount sweep --slot 0 --asset eth
8091
+ veil subaccount merge --slot 0 --pool eth
7905
8092
  veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
7906
8093
  veil subaccount address --slot 0
7907
8094
  `);
@@ -7939,7 +8126,7 @@ Examples:
7939
8126
  handleCLIError(error);
7940
8127
  }
7941
8128
  });
7942
- subaccount.command("status").description("Show subaccount deployment, balances, and queue state").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
8129
+ subaccount.command("status").description("Show subaccount deployment, forwarder balances, private balances, and queue state").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
7943
8130
  try {
7944
8131
  const rootPrivateKey = getRequiredVeilKey();
7945
8132
  const status = await getSubaccountStatus({
@@ -7964,6 +8151,17 @@ Examples:
7964
8151
  { label: "ETH", value: `${status.balances.eth.balance} ETH` },
7965
8152
  { label: "USDC", value: `${status.balances.usdc.balance} USDC` }
7966
8153
  ]);
8154
+ printSection("Private Pool Balances");
8155
+ printFields([
8156
+ {
8157
+ label: "ETH",
8158
+ value: `${status.privateBalances.eth.privateBalance} ETH (${status.privateBalances.eth.unspentCount} unspent / ${status.privateBalances.eth.spentCount} spent / ${status.privateBalances.eth.utxoCount} total UTXOs)`
8159
+ },
8160
+ {
8161
+ label: "USDC",
8162
+ value: `${status.privateBalances.usdc.privateBalance} USDC (${status.privateBalances.usdc.unspentCount} unspent / ${status.privateBalances.usdc.spentCount} spent / ${status.privateBalances.usdc.utxoCount} total UTXOs)`
8163
+ }
8164
+ ]);
7967
8165
  printQueueHuman("ETH Queue", status.queues.eth);
7968
8166
  printQueueHuman("USDC Queue", status.queues.usdc);
7969
8167
  printLine();
@@ -7974,11 +8172,6 @@ Examples:
7974
8172
  subaccount.command("deploy").description("Deploy a subaccount forwarder through the relay").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
7975
8173
  try {
7976
8174
  const rootPrivateKey = getRequiredVeilKey();
7977
- const slot = await deriveSubaccountSlot({
7978
- rootPrivateKey,
7979
- slot: options.slot,
7980
- rpcUrl: process.env.RPC_URL
7981
- });
7982
8175
  const result = await deploySubaccountForwarder({
7983
8176
  rootPrivateKey,
7984
8177
  slot: options.slot,
@@ -7988,7 +8181,7 @@ Examples:
7988
8181
  const output = {
7989
8182
  ...result,
7990
8183
  slot: options.slot,
7991
- forwarderAddress: slot.forwarderAddress
8184
+ forwarderAddress: result.slot.forwarderAddress
7992
8185
  };
7993
8186
  if (options.json) {
7994
8187
  printJson(output);
@@ -7997,7 +8190,7 @@ Examples:
7997
8190
  printHeader("Subaccount Deploy Submitted");
7998
8191
  printFields([
7999
8192
  { label: "Slot", value: options.slot },
8000
- { label: "Forwarder", value: slot.forwarderAddress },
8193
+ { label: "Forwarder", value: result.slot.forwarderAddress },
8001
8194
  { label: "Transaction", value: txUrl(result.transactionHash) },
8002
8195
  { label: "Block", value: result.blockNumber }
8003
8196
  ]);
@@ -8042,12 +8235,63 @@ Examples:
8042
8235
  handleCLIError(error);
8043
8236
  }
8044
8237
  });
8238
+ subaccount.command("merge").description("Merge a subaccount's private pool balance back to the main wallet").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--pool <pool>", "Pool to merge (eth or usdc)", parsePool, "eth").option("--json", "Output as JSON").action(async (options) => {
8239
+ try {
8240
+ const rootPrivateKey = getRequiredVeilKey();
8241
+ const result = await mergeSubaccount({
8242
+ rootPrivateKey,
8243
+ slot: options.slot,
8244
+ pool: options.pool,
8245
+ rpcUrl: process.env.RPC_URL,
8246
+ relayUrl: process.env.RELAY_URL,
8247
+ onProgress: options.json ? void 0 : (stage, detail) => {
8248
+ const msg = detail ? `${stage} ${detail}` : stage;
8249
+ process.stderr.write(`\r\x1B[K${msg}`);
8250
+ }
8251
+ });
8252
+ if (!options.json) {
8253
+ process.stderr.write("\r\x1B[K");
8254
+ }
8255
+ const output = {
8256
+ success: result.success,
8257
+ slot: result.slot,
8258
+ pool: result.pool,
8259
+ amount: result.amount,
8260
+ transactionHash: result.transactionHash,
8261
+ blockNumber: result.blockNumber
8262
+ };
8263
+ if (options.json) {
8264
+ printJson(output);
8265
+ return;
8266
+ }
8267
+ printHeader("Subaccount Merge Submitted");
8268
+ printFields([
8269
+ { label: "Slot", value: result.slot },
8270
+ { label: "Pool", value: result.pool.toUpperCase() },
8271
+ { label: "Amount", value: result.amount },
8272
+ { label: "Transaction", value: txUrl(result.transactionHash) },
8273
+ { label: "Block", value: result.blockNumber }
8274
+ ]);
8275
+ printLine();
8276
+ } catch (error) {
8277
+ if (!options.json) {
8278
+ process.stderr.write("\r\x1B[K");
8279
+ }
8280
+ handleCLIError(error);
8281
+ }
8282
+ });
8045
8283
  subaccount.command("recover").description("Recover assets sitting on the subaccount forwarder with a direct withdraw transaction").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).requiredOption("--asset <asset>", "Asset to recover (eth or usdc)", parseAsset).requiredOption("--to <address>", "Recipient address").requiredOption("--amount <value>", "Amount to recover").option("--json", "Output as JSON").action(async (options) => {
8046
8284
  try {
8047
8285
  const rootPrivateKey = getRequiredVeilKey();
8048
8286
  if (!viem.isAddress(options.to)) {
8049
8287
  throw new CLIError(ErrorCode.INVALID_ADDRESS, `Invalid recipient address: ${options.to}`);
8050
8288
  }
8289
+ if (!process.env.WALLET_KEY) {
8290
+ throw new CLIError(
8291
+ ErrorCode.WALLET_KEY_MISSING,
8292
+ "WALLET_KEY required for recovery. Recovery submits a transaction on-chain and needs a gas payer."
8293
+ );
8294
+ }
8051
8295
  const config = getConfig({});
8052
8296
  const recovery = await buildSubaccountRecoveryTx({
8053
8297
  rootPrivateKey,
@@ -8118,7 +8362,7 @@ Examples:
8118
8362
  // src/cli/index.ts
8119
8363
  loadEnv();
8120
8364
  var program2 = new Command();
8121
- program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.6.0").addHelpText("after", `
8365
+ program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.6.2").addHelpText("after", `
8122
8366
  Getting started:
8123
8367
  veil init
8124
8368
  veil register