@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/dist/index.cjs CHANGED
@@ -1669,7 +1669,16 @@ async function postRelayJson(endpoint, body, relayUrl) {
1669
1669
  },
1670
1670
  body: JSON.stringify(body)
1671
1671
  });
1672
- const data = await response.json();
1672
+ const text = await response.text();
1673
+ let data;
1674
+ try {
1675
+ data = JSON.parse(text);
1676
+ } catch {
1677
+ throw new RelayError(
1678
+ `Relay returned non-JSON response (HTTP ${response.status})`,
1679
+ response.status
1680
+ );
1681
+ }
1673
1682
  if (!response.ok) {
1674
1683
  const errorData = data;
1675
1684
  throw new RelayError(
@@ -2241,11 +2250,12 @@ function deriveSubaccountChildDepositKey(childPrivateKey) {
2241
2250
  }
2242
2251
  async function predictSubaccountForwarder(options) {
2243
2252
  const publicClient = createBaseClient(options.rpcUrl);
2253
+ const depositKeyBytes = options.childDepositKey.startsWith("0x") ? options.childDepositKey : `0x${options.childDepositKey}`;
2244
2254
  return publicClient.readContract({
2245
2255
  abi: FORWARDER_FACTORY_ABI,
2246
2256
  address: getForwarderFactoryAddress(),
2247
2257
  functionName: "computeAddress",
2248
- args: [options.salt, options.childDepositKey, options.childOwner]
2258
+ args: [options.salt, depositKeyBytes, options.childOwner]
2249
2259
  });
2250
2260
  }
2251
2261
  async function deriveSubaccountSlot(options) {
@@ -2282,7 +2292,7 @@ async function deploySubaccountForwarder(options) {
2282
2292
  slot: options.slot,
2283
2293
  rpcUrl: options.rpcUrl
2284
2294
  });
2285
- return postRelayJson(
2295
+ const result = await postRelayJson(
2286
2296
  "/stealth/deploy",
2287
2297
  {
2288
2298
  salt: slot.salt,
@@ -2292,6 +2302,7 @@ async function deploySubaccountForwarder(options) {
2292
2302
  },
2293
2303
  options.relayUrl
2294
2304
  );
2305
+ return { ...result, slot };
2295
2306
  }
2296
2307
  async function sweepSubaccountForwarder(options) {
2297
2308
  const asset = normalizeAsset(options.asset);
@@ -2316,11 +2327,32 @@ function toQueueStatus(asset, result) {
2316
2327
  pendingDeposits: result.pendingDeposits
2317
2328
  };
2318
2329
  }
2330
+ function toPrivateBalanceStatus(result) {
2331
+ return {
2332
+ privateBalance: result.privateBalance,
2333
+ privateBalanceWei: result.privateBalanceWei,
2334
+ utxoCount: result.utxoCount,
2335
+ spentCount: result.spentCount,
2336
+ unspentCount: result.unspentCount
2337
+ };
2338
+ }
2339
+ async function getSubaccountPrivateBalance(options) {
2340
+ const normalizedSlot = normalizeSlot(options.slot);
2341
+ assertPrivateKey(options.rootPrivateKey, "rootPrivateKey");
2342
+ const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, normalizedSlot);
2343
+ const childKeypair = new Keypair(childPrivateKey);
2344
+ return getPrivateBalance({
2345
+ keypair: childKeypair,
2346
+ pool: options.pool,
2347
+ rpcUrl: options.rpcUrl,
2348
+ onProgress: options.onProgress
2349
+ });
2350
+ }
2319
2351
  async function getSubaccountStatus(options) {
2320
2352
  const slot = await deriveSubaccountSlot(options);
2321
2353
  const publicClient = createBaseClient(options.rpcUrl);
2322
2354
  const addresses = getAddresses();
2323
- const [deployed, ethWei, usdcWei, ethQueue, usdcQueue] = await Promise.all([
2355
+ const [deployed, ethWei, usdcWei, ethQueue, usdcQueue, ethPrivate, usdcPrivate] = await Promise.all([
2324
2356
  isSubaccountForwarderDeployed({
2325
2357
  forwarderAddress: slot.forwarderAddress,
2326
2358
  rpcUrl: options.rpcUrl
@@ -2341,6 +2373,18 @@ async function getSubaccountStatus(options) {
2341
2373
  address: slot.forwarderAddress,
2342
2374
  pool: "usdc",
2343
2375
  rpcUrl: options.rpcUrl
2376
+ }),
2377
+ getSubaccountPrivateBalance({
2378
+ rootPrivateKey: options.rootPrivateKey,
2379
+ slot: options.slot,
2380
+ pool: "eth",
2381
+ rpcUrl: options.rpcUrl
2382
+ }),
2383
+ getSubaccountPrivateBalance({
2384
+ rootPrivateKey: options.rootPrivateKey,
2385
+ slot: options.slot,
2386
+ pool: "usdc",
2387
+ rpcUrl: options.rpcUrl
2344
2388
  })
2345
2389
  ]);
2346
2390
  return {
@@ -2356,6 +2400,10 @@ async function getSubaccountStatus(options) {
2356
2400
  balanceWei: usdcWei.toString()
2357
2401
  }
2358
2402
  },
2403
+ privateBalances: {
2404
+ eth: toPrivateBalanceStatus(ethPrivate),
2405
+ usdc: toPrivateBalanceStatus(usdcPrivate)
2406
+ },
2359
2407
  queues: {
2360
2408
  eth: toQueueStatus("eth", ethQueue),
2361
2409
  usdc: toQueueStatus("usdc", usdcQueue)
@@ -2502,6 +2550,137 @@ async function buildSubaccountRecoveryTx(options) {
2502
2550
  signature
2503
2551
  };
2504
2552
  }
2553
+ async function mergeSubaccount(options) {
2554
+ const {
2555
+ rootPrivateKey,
2556
+ slot,
2557
+ pool = "eth",
2558
+ rpcUrl,
2559
+ relayUrl,
2560
+ onProgress
2561
+ } = options;
2562
+ const normalizedSlot = normalizeSlot(slot);
2563
+ assertPrivateKey(rootPrivateKey, "rootPrivateKey");
2564
+ const poolConfig = POOL_CONFIG[pool];
2565
+ const poolAddress = getPoolAddress(pool);
2566
+ const childPrivateKey = deriveSubaccountChildPrivateKey(rootPrivateKey, normalizedSlot);
2567
+ const childKeypair = new Keypair(childPrivateKey);
2568
+ const parentKeypair = new Keypair(rootPrivateKey);
2569
+ onProgress?.("Fetching subaccount balance...");
2570
+ const balanceResult = await getPrivateBalance({
2571
+ keypair: childKeypair,
2572
+ pool,
2573
+ rpcUrl,
2574
+ onProgress
2575
+ });
2576
+ const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
2577
+ if (unspentUtxoInfos.length === 0) {
2578
+ throw new Error("Subaccount has no unspent UTXOs to merge");
2579
+ }
2580
+ if (unspentUtxoInfos.length > 16) {
2581
+ throw new Error(
2582
+ `Subaccount has ${unspentUtxoInfos.length} unspent UTXOs which exceeds the 16-input circuit limit. Consolidate UTXOs on the subaccount first before merging.`
2583
+ );
2584
+ }
2585
+ onProgress?.("Preparing UTXOs...");
2586
+ const publicClient = viem.createPublicClient({
2587
+ chain: chains.base,
2588
+ transport: viem.http(rpcUrl)
2589
+ });
2590
+ const utxos = [];
2591
+ for (const utxoInfo of unspentUtxoInfos) {
2592
+ const encryptedOutputs = await publicClient.readContract({
2593
+ address: poolAddress,
2594
+ abi: POOL_ABI,
2595
+ functionName: "getEncryptedOutputs",
2596
+ args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
2597
+ });
2598
+ if (encryptedOutputs.length > 0) {
2599
+ try {
2600
+ const utxo = Utxo.decrypt(encryptedOutputs[0], childKeypair);
2601
+ utxo.index = utxoInfo.index;
2602
+ utxos.push(utxo);
2603
+ } catch {
2604
+ }
2605
+ }
2606
+ }
2607
+ if (utxos.length === 0) {
2608
+ throw new Error("Failed to decrypt subaccount UTXOs");
2609
+ }
2610
+ onProgress?.("Selecting UTXOs...");
2611
+ const amount = balanceResult.privateBalance;
2612
+ const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
2613
+ utxos,
2614
+ amount,
2615
+ poolConfig.decimals
2616
+ );
2617
+ const outputs = [];
2618
+ const mergeWei = viem.parseUnits(amount, poolConfig.decimals);
2619
+ outputs.push(new Utxo({ amount: mergeWei, keypair: parentKeypair }));
2620
+ if (changeAmount > 0n) {
2621
+ outputs.push(new Utxo({ amount: changeAmount, keypair: parentKeypair }));
2622
+ }
2623
+ onProgress?.("Fetching commitments...");
2624
+ const nextIndex = await publicClient.readContract({
2625
+ address: poolAddress,
2626
+ abi: POOL_ABI,
2627
+ functionName: "nextIndex"
2628
+ });
2629
+ const BATCH_SIZE = 5e3;
2630
+ const commitments = [];
2631
+ const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
2632
+ for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
2633
+ const end = Math.min(start + BATCH_SIZE, nextIndex);
2634
+ const batchNum = Math.floor(start / BATCH_SIZE) + 1;
2635
+ onProgress?.("Fetching commitments", `batch ${batchNum}/${totalBatches}`);
2636
+ const batch = await publicClient.readContract({
2637
+ address: poolAddress,
2638
+ abi: POOL_ABI,
2639
+ functionName: "getCommitments",
2640
+ args: [BigInt(start), BigInt(end)]
2641
+ });
2642
+ commitments.push(...batch.map((c) => c.toString()));
2643
+ }
2644
+ onProgress?.("Building ZK proof...");
2645
+ const result = await prepareTransaction({
2646
+ commitments,
2647
+ inputs: selectedUtxos,
2648
+ outputs,
2649
+ fee: 0,
2650
+ recipient: "0x0000000000000000000000000000000000000000",
2651
+ relayer: "0x0000000000000000000000000000000000000000",
2652
+ onProgress
2653
+ });
2654
+ onProgress?.("Submitting to relay...");
2655
+ const relayResult = await submitRelay({
2656
+ type: "transfer",
2657
+ pool,
2658
+ relayUrl,
2659
+ proofArgs: {
2660
+ proof: result.args.proof,
2661
+ root: result.args.root,
2662
+ inputNullifiers: result.args.inputNullifiers,
2663
+ outputCommitments: result.args.outputCommitments,
2664
+ publicAmount: result.args.publicAmount,
2665
+ extDataHash: result.args.extDataHash
2666
+ },
2667
+ extData: result.extData,
2668
+ metadata: {
2669
+ amount,
2670
+ recipient: "self",
2671
+ inputUtxoCount: selectedUtxos.length,
2672
+ outputUtxoCount: outputs.length
2673
+ }
2674
+ });
2675
+ return {
2676
+ success: relayResult.success,
2677
+ transactionHash: relayResult.transactionHash,
2678
+ blockNumber: relayResult.blockNumber,
2679
+ amount,
2680
+ slot: normalizedSlot,
2681
+ pool
2682
+ };
2683
+ }
2505
2684
 
2506
2685
  exports.ADDRESSES = ADDRESSES;
2507
2686
  exports.CIRCUIT_CONFIG = CIRCUIT_CONFIG;
@@ -2550,9 +2729,11 @@ exports.getQueueAddress = getQueueAddress;
2550
2729
  exports.getQueueBalance = getQueueBalance;
2551
2730
  exports.getRelayInfo = getRelayInfo;
2552
2731
  exports.getRelayUrl = getRelayUrl;
2732
+ exports.getSubaccountPrivateBalance = getSubaccountPrivateBalance;
2553
2733
  exports.getSubaccountStatus = getSubaccountStatus;
2554
2734
  exports.isSubaccountForwarderDeployed = isSubaccountForwarderDeployed;
2555
2735
  exports.isSubaccountWithdrawNonceUsed = isSubaccountWithdrawNonceUsed;
2736
+ exports.mergeSubaccount = mergeSubaccount;
2556
2737
  exports.mergeUtxos = mergeUtxos;
2557
2738
  exports.packEncryptedMessage = packEncryptedMessage;
2558
2739
  exports.poseidonHash = poseidonHash;