@veil-cash/sdk 0.6.1 → 0.6.3

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
@@ -625,6 +625,14 @@ var QUEUE_ABI = [
625
625
  outputs: [{ name: "count", type: "uint256" }],
626
626
  stateMutability: "view",
627
627
  type: "function"
628
+ },
629
+ // Get remaining daily free deposits for an address (V3+)
630
+ {
631
+ inputs: [{ name: "_depositor", type: "address" }],
632
+ name: "getDailyFreeRemaining",
633
+ outputs: [{ name: "remaining", type: "uint256" }],
634
+ stateMutability: "view",
635
+ type: "function"
628
636
  }
629
637
  ];
630
638
  var POOL_ABI = [
@@ -1342,6 +1350,25 @@ async function getQueueBalance(options) {
1342
1350
  pendingCount: pendingDeposits.length
1343
1351
  };
1344
1352
  }
1353
+ async function getDailyFreeRemaining(options) {
1354
+ const { address, pool = "eth", rpcUrl } = options;
1355
+ const queueAddress = getQueueAddress(pool);
1356
+ const publicClient = viem.createPublicClient({
1357
+ chain: chains.base,
1358
+ transport: viem.http(rpcUrl)
1359
+ });
1360
+ try {
1361
+ const remaining = await publicClient.readContract({
1362
+ address: queueAddress,
1363
+ abi: QUEUE_ABI,
1364
+ functionName: "getDailyFreeRemaining",
1365
+ args: [address]
1366
+ });
1367
+ return Number(remaining);
1368
+ } catch {
1369
+ return 0;
1370
+ }
1371
+ }
1345
1372
  async function getPrivateBalance(options) {
1346
1373
  const { keypair, pool = "eth", rpcUrl, onProgress } = options;
1347
1374
  const poolAddress = getPoolAddress(pool);
@@ -1669,7 +1696,16 @@ async function postRelayJson(endpoint, body, relayUrl) {
1669
1696
  },
1670
1697
  body: JSON.stringify(body)
1671
1698
  });
1672
- const data = await response.json();
1699
+ const text = await response.text();
1700
+ let data;
1701
+ try {
1702
+ data = JSON.parse(text);
1703
+ } catch {
1704
+ throw new RelayError(
1705
+ `Relay returned non-JSON response (HTTP ${response.status})`,
1706
+ response.status
1707
+ );
1708
+ }
1673
1709
  if (!response.ok) {
1674
1710
  const errorData = data;
1675
1711
  throw new RelayError(
@@ -2241,11 +2277,12 @@ function deriveSubaccountChildDepositKey(childPrivateKey) {
2241
2277
  }
2242
2278
  async function predictSubaccountForwarder(options) {
2243
2279
  const publicClient = createBaseClient(options.rpcUrl);
2280
+ const depositKeyBytes = options.childDepositKey.startsWith("0x") ? options.childDepositKey : `0x${options.childDepositKey}`;
2244
2281
  return publicClient.readContract({
2245
2282
  abi: FORWARDER_FACTORY_ABI,
2246
2283
  address: getForwarderFactoryAddress(),
2247
2284
  functionName: "computeAddress",
2248
- args: [options.salt, options.childDepositKey, options.childOwner]
2285
+ args: [options.salt, depositKeyBytes, options.childOwner]
2249
2286
  });
2250
2287
  }
2251
2288
  async function deriveSubaccountSlot(options) {
@@ -2282,7 +2319,7 @@ async function deploySubaccountForwarder(options) {
2282
2319
  slot: options.slot,
2283
2320
  rpcUrl: options.rpcUrl
2284
2321
  });
2285
- return postRelayJson(
2322
+ const result = await postRelayJson(
2286
2323
  "/stealth/deploy",
2287
2324
  {
2288
2325
  salt: slot.salt,
@@ -2292,6 +2329,7 @@ async function deploySubaccountForwarder(options) {
2292
2329
  },
2293
2330
  options.relayUrl
2294
2331
  );
2332
+ return { ...result, slot };
2295
2333
  }
2296
2334
  async function sweepSubaccountForwarder(options) {
2297
2335
  const asset = normalizeAsset(options.asset);
@@ -2316,11 +2354,32 @@ function toQueueStatus(asset, result) {
2316
2354
  pendingDeposits: result.pendingDeposits
2317
2355
  };
2318
2356
  }
2357
+ function toPrivateBalanceStatus(result) {
2358
+ return {
2359
+ privateBalance: result.privateBalance,
2360
+ privateBalanceWei: result.privateBalanceWei,
2361
+ utxoCount: result.utxoCount,
2362
+ spentCount: result.spentCount,
2363
+ unspentCount: result.unspentCount
2364
+ };
2365
+ }
2366
+ async function getSubaccountPrivateBalance(options) {
2367
+ const normalizedSlot = normalizeSlot(options.slot);
2368
+ assertPrivateKey(options.rootPrivateKey, "rootPrivateKey");
2369
+ const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, normalizedSlot);
2370
+ const childKeypair = new Keypair(childPrivateKey);
2371
+ return getPrivateBalance({
2372
+ keypair: childKeypair,
2373
+ pool: options.pool,
2374
+ rpcUrl: options.rpcUrl,
2375
+ onProgress: options.onProgress
2376
+ });
2377
+ }
2319
2378
  async function getSubaccountStatus(options) {
2320
2379
  const slot = await deriveSubaccountSlot(options);
2321
2380
  const publicClient = createBaseClient(options.rpcUrl);
2322
2381
  const addresses = getAddresses();
2323
- const [deployed, ethWei, usdcWei, ethQueue, usdcQueue] = await Promise.all([
2382
+ const [deployed, ethWei, usdcWei, ethQueue, usdcQueue, ethPrivate, usdcPrivate] = await Promise.all([
2324
2383
  isSubaccountForwarderDeployed({
2325
2384
  forwarderAddress: slot.forwarderAddress,
2326
2385
  rpcUrl: options.rpcUrl
@@ -2341,6 +2400,18 @@ async function getSubaccountStatus(options) {
2341
2400
  address: slot.forwarderAddress,
2342
2401
  pool: "usdc",
2343
2402
  rpcUrl: options.rpcUrl
2403
+ }),
2404
+ getSubaccountPrivateBalance({
2405
+ rootPrivateKey: options.rootPrivateKey,
2406
+ slot: options.slot,
2407
+ pool: "eth",
2408
+ rpcUrl: options.rpcUrl
2409
+ }),
2410
+ getSubaccountPrivateBalance({
2411
+ rootPrivateKey: options.rootPrivateKey,
2412
+ slot: options.slot,
2413
+ pool: "usdc",
2414
+ rpcUrl: options.rpcUrl
2344
2415
  })
2345
2416
  ]);
2346
2417
  return {
@@ -2356,6 +2427,10 @@ async function getSubaccountStatus(options) {
2356
2427
  balanceWei: usdcWei.toString()
2357
2428
  }
2358
2429
  },
2430
+ privateBalances: {
2431
+ eth: toPrivateBalanceStatus(ethPrivate),
2432
+ usdc: toPrivateBalanceStatus(usdcPrivate)
2433
+ },
2359
2434
  queues: {
2360
2435
  eth: toQueueStatus("eth", ethQueue),
2361
2436
  usdc: toQueueStatus("usdc", usdcQueue)
@@ -2502,6 +2577,137 @@ async function buildSubaccountRecoveryTx(options) {
2502
2577
  signature
2503
2578
  };
2504
2579
  }
2580
+ async function mergeSubaccount(options) {
2581
+ const {
2582
+ rootPrivateKey,
2583
+ slot,
2584
+ pool = "eth",
2585
+ rpcUrl,
2586
+ relayUrl,
2587
+ onProgress
2588
+ } = options;
2589
+ const normalizedSlot = normalizeSlot(slot);
2590
+ assertPrivateKey(rootPrivateKey, "rootPrivateKey");
2591
+ const poolConfig = POOL_CONFIG[pool];
2592
+ const poolAddress = getPoolAddress(pool);
2593
+ const childPrivateKey = deriveSubaccountChildPrivateKey(rootPrivateKey, normalizedSlot);
2594
+ const childKeypair = new Keypair(childPrivateKey);
2595
+ const parentKeypair = new Keypair(rootPrivateKey);
2596
+ onProgress?.("Fetching subaccount balance...");
2597
+ const balanceResult = await getPrivateBalance({
2598
+ keypair: childKeypair,
2599
+ pool,
2600
+ rpcUrl,
2601
+ onProgress
2602
+ });
2603
+ const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
2604
+ if (unspentUtxoInfos.length === 0) {
2605
+ throw new Error("Subaccount has no unspent UTXOs to merge");
2606
+ }
2607
+ if (unspentUtxoInfos.length > 16) {
2608
+ throw new Error(
2609
+ `Subaccount has ${unspentUtxoInfos.length} unspent UTXOs which exceeds the 16-input circuit limit. Consolidate UTXOs on the subaccount first before merging.`
2610
+ );
2611
+ }
2612
+ onProgress?.("Preparing UTXOs...");
2613
+ const publicClient = viem.createPublicClient({
2614
+ chain: chains.base,
2615
+ transport: viem.http(rpcUrl)
2616
+ });
2617
+ const utxos = [];
2618
+ for (const utxoInfo of unspentUtxoInfos) {
2619
+ const encryptedOutputs = await publicClient.readContract({
2620
+ address: poolAddress,
2621
+ abi: POOL_ABI,
2622
+ functionName: "getEncryptedOutputs",
2623
+ args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
2624
+ });
2625
+ if (encryptedOutputs.length > 0) {
2626
+ try {
2627
+ const utxo = Utxo.decrypt(encryptedOutputs[0], childKeypair);
2628
+ utxo.index = utxoInfo.index;
2629
+ utxos.push(utxo);
2630
+ } catch {
2631
+ }
2632
+ }
2633
+ }
2634
+ if (utxos.length === 0) {
2635
+ throw new Error("Failed to decrypt subaccount UTXOs");
2636
+ }
2637
+ onProgress?.("Selecting UTXOs...");
2638
+ const amount = balanceResult.privateBalance;
2639
+ const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
2640
+ utxos,
2641
+ amount,
2642
+ poolConfig.decimals
2643
+ );
2644
+ const outputs = [];
2645
+ const mergeWei = viem.parseUnits(amount, poolConfig.decimals);
2646
+ outputs.push(new Utxo({ amount: mergeWei, keypair: parentKeypair }));
2647
+ if (changeAmount > 0n) {
2648
+ outputs.push(new Utxo({ amount: changeAmount, keypair: parentKeypair }));
2649
+ }
2650
+ onProgress?.("Fetching commitments...");
2651
+ const nextIndex = await publicClient.readContract({
2652
+ address: poolAddress,
2653
+ abi: POOL_ABI,
2654
+ functionName: "nextIndex"
2655
+ });
2656
+ const BATCH_SIZE = 5e3;
2657
+ const commitments = [];
2658
+ const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
2659
+ for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
2660
+ const end = Math.min(start + BATCH_SIZE, nextIndex);
2661
+ const batchNum = Math.floor(start / BATCH_SIZE) + 1;
2662
+ onProgress?.("Fetching commitments", `batch ${batchNum}/${totalBatches}`);
2663
+ const batch = await publicClient.readContract({
2664
+ address: poolAddress,
2665
+ abi: POOL_ABI,
2666
+ functionName: "getCommitments",
2667
+ args: [BigInt(start), BigInt(end)]
2668
+ });
2669
+ commitments.push(...batch.map((c) => c.toString()));
2670
+ }
2671
+ onProgress?.("Building ZK proof...");
2672
+ const result = await prepareTransaction({
2673
+ commitments,
2674
+ inputs: selectedUtxos,
2675
+ outputs,
2676
+ fee: 0,
2677
+ recipient: "0x0000000000000000000000000000000000000000",
2678
+ relayer: "0x0000000000000000000000000000000000000000",
2679
+ onProgress
2680
+ });
2681
+ onProgress?.("Submitting to relay...");
2682
+ const relayResult = await submitRelay({
2683
+ type: "transfer",
2684
+ pool,
2685
+ relayUrl,
2686
+ proofArgs: {
2687
+ proof: result.args.proof,
2688
+ root: result.args.root,
2689
+ inputNullifiers: result.args.inputNullifiers,
2690
+ outputCommitments: result.args.outputCommitments,
2691
+ publicAmount: result.args.publicAmount,
2692
+ extDataHash: result.args.extDataHash
2693
+ },
2694
+ extData: result.extData,
2695
+ metadata: {
2696
+ amount,
2697
+ recipient: "self",
2698
+ inputUtxoCount: selectedUtxos.length,
2699
+ outputUtxoCount: outputs.length
2700
+ }
2701
+ });
2702
+ return {
2703
+ success: relayResult.success,
2704
+ transactionHash: relayResult.transactionHash,
2705
+ blockNumber: relayResult.blockNumber,
2706
+ amount,
2707
+ slot: normalizedSlot,
2708
+ pool
2709
+ };
2710
+ }
2505
2711
 
2506
2712
  exports.ADDRESSES = ADDRESSES;
2507
2713
  exports.CIRCUIT_CONFIG = CIRCUIT_CONFIG;
@@ -2541,6 +2747,7 @@ exports.deriveSubaccountSalt = deriveSubaccountSalt;
2541
2747
  exports.deriveSubaccountSlot = deriveSubaccountSlot;
2542
2748
  exports.findNextSubaccountWithdrawNonce = findNextSubaccountWithdrawNonce;
2543
2749
  exports.getAddresses = getAddresses;
2750
+ exports.getDailyFreeRemaining = getDailyFreeRemaining;
2544
2751
  exports.getExtDataHash = getExtDataHash;
2545
2752
  exports.getForwarderFactoryAddress = getForwarderFactoryAddress;
2546
2753
  exports.getMerklePath = getMerklePath;
@@ -2550,9 +2757,11 @@ exports.getQueueAddress = getQueueAddress;
2550
2757
  exports.getQueueBalance = getQueueBalance;
2551
2758
  exports.getRelayInfo = getRelayInfo;
2552
2759
  exports.getRelayUrl = getRelayUrl;
2760
+ exports.getSubaccountPrivateBalance = getSubaccountPrivateBalance;
2553
2761
  exports.getSubaccountStatus = getSubaccountStatus;
2554
2762
  exports.isSubaccountForwarderDeployed = isSubaccountForwarderDeployed;
2555
2763
  exports.isSubaccountWithdrawNonceUsed = isSubaccountWithdrawNonceUsed;
2764
+ exports.mergeSubaccount = mergeSubaccount;
2556
2765
  exports.mergeUtxos = mergeUtxos;
2557
2766
  exports.packEncryptedMessage = packEncryptedMessage;
2558
2767
  exports.poseidonHash = poseidonHash;