@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 +5 -1
- package/SDK.md +20 -2
- package/dist/cli/index.cjs +257 -13
- package/dist/index.cjs +185 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -2
- package/dist/index.d.ts +84 -2
- package/dist/index.js +184 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/veil/SKILL.md +26 -8
- package/skills/veil/reference.md +23 -3
- package/src/cli/commands/subaccount.ts +82 -2
- package/src/cli/index.ts +1 -1
- package/src/index.ts +6 -0
- package/src/subaccount.ts +244 -5
- package/src/types.ts +56 -0
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
|
|
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,
|
|
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
|
-
|
|
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;
|