@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/README.md +12 -7
- package/SDK.md +38 -7
- package/dist/cli/index.cjs +523 -220
- package/dist/index.cjs +213 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -2
- package/dist/index.d.ts +121 -2
- package/dist/index.js +211 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/veil/SKILL.md +32 -12
- package/skills/veil/reference.md +28 -8
- package/src/abi.ts +8 -0
- package/src/balance.ts +48 -0
- package/src/cli/commands/deposit.ts +61 -15
- package/src/cli/commands/subaccount.ts +82 -2
- package/src/cli/index.ts +1 -1
- package/src/index.ts +7 -0
- package/src/subaccount.ts +244 -5
- package/src/types.ts +56 -0
package/dist/cli/index.cjs
CHANGED
|
@@ -4203,6 +4203,14 @@ var QUEUE_ABI = [
|
|
|
4203
4203
|
outputs: [{ name: "count", type: "uint256" }],
|
|
4204
4204
|
stateMutability: "view",
|
|
4205
4205
|
type: "function"
|
|
4206
|
+
},
|
|
4207
|
+
// Get remaining daily free deposits for an address (V3+)
|
|
4208
|
+
{
|
|
4209
|
+
inputs: [{ name: "_depositor", type: "address" }],
|
|
4210
|
+
name: "getDailyFreeRemaining",
|
|
4211
|
+
outputs: [{ name: "remaining", type: "uint256" }],
|
|
4212
|
+
stateMutability: "view",
|
|
4213
|
+
type: "function"
|
|
4206
4214
|
}
|
|
4207
4215
|
];
|
|
4208
4216
|
var POOL_ABI = [
|
|
@@ -5784,174 +5792,6 @@ function createRegisterCommand() {
|
|
|
5784
5792
|
});
|
|
5785
5793
|
return register;
|
|
5786
5794
|
}
|
|
5787
|
-
var MINIMUM_NET = {
|
|
5788
|
-
ETH: 0.01,
|
|
5789
|
-
USDC: 10
|
|
5790
|
-
};
|
|
5791
|
-
async function getGrossAmount(netWei, rpcUrl) {
|
|
5792
|
-
const publicClient = viem.createPublicClient({
|
|
5793
|
-
chain: chains.base,
|
|
5794
|
-
transport: viem.http(rpcUrl)
|
|
5795
|
-
});
|
|
5796
|
-
const grossWei = await publicClient.readContract({
|
|
5797
|
-
address: getAddresses().entry,
|
|
5798
|
-
abi: ENTRY_ABI,
|
|
5799
|
-
functionName: "getDepositAmountWithFee",
|
|
5800
|
-
args: [netWei]
|
|
5801
|
-
});
|
|
5802
|
-
return { grossWei, feeWei: grossWei - netWei };
|
|
5803
|
-
}
|
|
5804
|
-
var SUPPORTED_ASSETS = ["ETH", "USDC"];
|
|
5805
|
-
function createDepositCommand() {
|
|
5806
|
-
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 \u2014 this is what arrives in your Veil balance").option("--unsigned", "Output unsigned transaction payload instead of sending").option("--json", "Output as JSON").addHelpText("after", `
|
|
5807
|
-
The amount you specify is the net amount that lands in your Veil balance.
|
|
5808
|
-
The 0.3% protocol fee is automatically added on top.
|
|
5809
|
-
|
|
5810
|
-
Examples:
|
|
5811
|
-
veil deposit ETH 0.1 # deposits 0.1 ETH (sends ~0.1003 ETH)
|
|
5812
|
-
veil deposit USDC 100 # deposits 100 USDC (sends ~100.30 USDC)
|
|
5813
|
-
veil deposit ETH 0.1 --unsigned
|
|
5814
|
-
veil deposit ETH 0.1 --json
|
|
5815
|
-
`).action(async (asset, amount, options) => {
|
|
5816
|
-
try {
|
|
5817
|
-
const assetUpper = asset.toUpperCase();
|
|
5818
|
-
if (!SUPPORTED_ASSETS.includes(assetUpper)) {
|
|
5819
|
-
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(", ")}`);
|
|
5820
|
-
}
|
|
5821
|
-
const amountNum = parseFloat(amount);
|
|
5822
|
-
const minimumNet = MINIMUM_NET[assetUpper];
|
|
5823
|
-
if (amountNum < minimumNet) {
|
|
5824
|
-
throw new CLIError(
|
|
5825
|
-
ErrorCode.INVALID_AMOUNT,
|
|
5826
|
-
`Minimum deposit is ${minimumNet} ${assetUpper}.`
|
|
5827
|
-
);
|
|
5828
|
-
}
|
|
5829
|
-
const rpcUrl = process.env.RPC_URL;
|
|
5830
|
-
const poolConfig = POOL_CONFIG[assetUpper.toLowerCase()];
|
|
5831
|
-
const netWei = assetUpper === "ETH" ? viem.parseEther(amount) : viem.parseUnits(amount, poolConfig.decimals);
|
|
5832
|
-
const progress = createProgressReporter();
|
|
5833
|
-
progress("Calculating fee...");
|
|
5834
|
-
const { grossWei, feeWei } = await getGrossAmount(netWei, rpcUrl);
|
|
5835
|
-
const grossStr = assetUpper === "ETH" ? viem.formatEther(grossWei) : viem.formatUnits(grossWei, poolConfig.decimals);
|
|
5836
|
-
const feeStr = assetUpper === "ETH" ? viem.formatEther(feeWei) : viem.formatUnits(feeWei, poolConfig.decimals);
|
|
5837
|
-
const depositKey = process.env.DEPOSIT_KEY;
|
|
5838
|
-
if (!depositKey) {
|
|
5839
|
-
throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'DEPOSIT_KEY not set. Run "veil init" first.');
|
|
5840
|
-
}
|
|
5841
|
-
progress("Building transaction...");
|
|
5842
|
-
let tx;
|
|
5843
|
-
let approveTx = null;
|
|
5844
|
-
if (assetUpper === "USDC") {
|
|
5845
|
-
approveTx = buildApproveUSDCTx({ amount: grossStr });
|
|
5846
|
-
tx = buildDepositUSDCTx({ depositKey, amount: grossStr });
|
|
5847
|
-
} else {
|
|
5848
|
-
tx = buildDepositETHTx({ depositKey, amount: grossStr });
|
|
5849
|
-
}
|
|
5850
|
-
if (options.unsigned) {
|
|
5851
|
-
clearProgress();
|
|
5852
|
-
const payloads = [];
|
|
5853
|
-
if (approveTx) {
|
|
5854
|
-
payloads.push({
|
|
5855
|
-
step: "approve",
|
|
5856
|
-
to: approveTx.to,
|
|
5857
|
-
data: approveTx.data,
|
|
5858
|
-
value: "0",
|
|
5859
|
-
chainId: 8453
|
|
5860
|
-
});
|
|
5861
|
-
}
|
|
5862
|
-
payloads.push({
|
|
5863
|
-
step: "deposit",
|
|
5864
|
-
to: tx.to,
|
|
5865
|
-
data: tx.data,
|
|
5866
|
-
value: tx.value ? tx.value.toString() : "0",
|
|
5867
|
-
chainId: 8453
|
|
5868
|
-
});
|
|
5869
|
-
printJson(payloads.length === 1 ? payloads[0] : payloads);
|
|
5870
|
-
return;
|
|
5871
|
-
}
|
|
5872
|
-
const config = getConfig(options);
|
|
5873
|
-
const address = getAddress(config.privateKey);
|
|
5874
|
-
if (assetUpper === "ETH") {
|
|
5875
|
-
progress("Checking balance...");
|
|
5876
|
-
const balance = await getBalance(address, config.rpcUrl);
|
|
5877
|
-
if (balance < grossWei) {
|
|
5878
|
-
clearProgress();
|
|
5879
|
-
throw new CLIError(
|
|
5880
|
-
ErrorCode.INSUFFICIENT_BALANCE,
|
|
5881
|
-
`Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${grossStr} ETH (${amount} + fee)`
|
|
5882
|
-
);
|
|
5883
|
-
}
|
|
5884
|
-
}
|
|
5885
|
-
if (approveTx) {
|
|
5886
|
-
progress(`Approving ${assetUpper}...`);
|
|
5887
|
-
const approvalResult = await sendTransaction(config, approveTx);
|
|
5888
|
-
if (assetUpper === "USDC") {
|
|
5889
|
-
const publicClient = viem.createPublicClient({
|
|
5890
|
-
chain: chains.base,
|
|
5891
|
-
transport: viem.http(config.rpcUrl)
|
|
5892
|
-
});
|
|
5893
|
-
const addresses = getAddresses();
|
|
5894
|
-
let allowance = await publicClient.readContract({
|
|
5895
|
-
address: getAddresses().usdcToken,
|
|
5896
|
-
abi: ERC20_ABI,
|
|
5897
|
-
functionName: "allowance",
|
|
5898
|
-
args: [address, addresses.entry]
|
|
5899
|
-
});
|
|
5900
|
-
for (let confirmations = 2; allowance < grossWei && confirmations <= 3; confirmations++) {
|
|
5901
|
-
await publicClient.waitForTransactionReceipt({
|
|
5902
|
-
hash: approvalResult.hash,
|
|
5903
|
-
confirmations
|
|
5904
|
-
});
|
|
5905
|
-
allowance = await publicClient.readContract({
|
|
5906
|
-
address: addresses.usdcToken,
|
|
5907
|
-
abi: ERC20_ABI,
|
|
5908
|
-
functionName: "allowance",
|
|
5909
|
-
args: [address, addresses.entry]
|
|
5910
|
-
});
|
|
5911
|
-
}
|
|
5912
|
-
if (allowance < grossWei) {
|
|
5913
|
-
throw new CLIError(
|
|
5914
|
-
ErrorCode.CONTRACT_ERROR,
|
|
5915
|
-
`USDC approval is not yet visible on RPC after confirmation. Allowance ${allowance.toString()} < required ${grossWei.toString()}.`
|
|
5916
|
-
);
|
|
5917
|
-
}
|
|
5918
|
-
}
|
|
5919
|
-
}
|
|
5920
|
-
progress("Sending deposit transaction...");
|
|
5921
|
-
const result = await sendTransaction(config, tx);
|
|
5922
|
-
progress("Confirming...");
|
|
5923
|
-
clearProgress();
|
|
5924
|
-
const output = {
|
|
5925
|
-
success: result.receipt.status === "success",
|
|
5926
|
-
hash: result.hash,
|
|
5927
|
-
asset: assetUpper,
|
|
5928
|
-
amount,
|
|
5929
|
-
fee: feeStr,
|
|
5930
|
-
totalSent: grossStr,
|
|
5931
|
-
blockNumber: result.receipt.blockNumber.toString()
|
|
5932
|
-
};
|
|
5933
|
-
if (options.json) {
|
|
5934
|
-
printJson(output);
|
|
5935
|
-
return;
|
|
5936
|
-
}
|
|
5937
|
-
printHeader("Deposit Submitted");
|
|
5938
|
-
printFields([
|
|
5939
|
-
{ label: "Asset", value: assetUpper },
|
|
5940
|
-
{ label: "Amount", value: `${amount} ${assetUpper}` },
|
|
5941
|
-
{ label: "Fee", value: `${feeStr} ${assetUpper} (0.3%)` },
|
|
5942
|
-
{ label: "Total sent", value: `${grossStr} ${assetUpper}` },
|
|
5943
|
-
{ label: "From", value: address },
|
|
5944
|
-
{ label: "Transaction", value: txUrl(result.hash) },
|
|
5945
|
-
{ label: "Block", value: result.receipt.blockNumber }
|
|
5946
|
-
]);
|
|
5947
|
-
printLine();
|
|
5948
|
-
} catch (error) {
|
|
5949
|
-
clearProgress();
|
|
5950
|
-
handleCLIError(error);
|
|
5951
|
-
}
|
|
5952
|
-
});
|
|
5953
|
-
return deposit;
|
|
5954
|
-
}
|
|
5955
5795
|
|
|
5956
5796
|
// src/utxo.ts
|
|
5957
5797
|
var Utxo = class _Utxo {
|
|
@@ -6084,6 +5924,25 @@ async function getQueueBalance(options) {
|
|
|
6084
5924
|
pendingCount: pendingDeposits.length
|
|
6085
5925
|
};
|
|
6086
5926
|
}
|
|
5927
|
+
async function getDailyFreeRemaining(options) {
|
|
5928
|
+
const { address, pool = "eth", rpcUrl } = options;
|
|
5929
|
+
const queueAddress = getQueueAddress(pool);
|
|
5930
|
+
const publicClient = viem.createPublicClient({
|
|
5931
|
+
chain: chains.base,
|
|
5932
|
+
transport: viem.http(rpcUrl)
|
|
5933
|
+
});
|
|
5934
|
+
try {
|
|
5935
|
+
const remaining = await publicClient.readContract({
|
|
5936
|
+
address: queueAddress,
|
|
5937
|
+
abi: QUEUE_ABI,
|
|
5938
|
+
functionName: "getDailyFreeRemaining",
|
|
5939
|
+
args: [address]
|
|
5940
|
+
});
|
|
5941
|
+
return Number(remaining);
|
|
5942
|
+
} catch {
|
|
5943
|
+
return 0;
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
6087
5946
|
async function getPrivateBalance(options) {
|
|
6088
5947
|
const { keypair, pool = "eth", rpcUrl, onProgress } = options;
|
|
6089
5948
|
const poolAddress = getPoolAddress(pool);
|
|
@@ -6136,46 +5995,246 @@ async function getPrivateBalance(options) {
|
|
|
6136
5995
|
if (utxo.amount > 0n) {
|
|
6137
5996
|
decryptedUtxos.push({ utxo, index: i });
|
|
6138
5997
|
}
|
|
6139
|
-
} catch {
|
|
6140
|
-
}
|
|
6141
|
-
}
|
|
6142
|
-
onProgress?.("Found UTXOs", `${decryptedUtxos.length} belonging to you`);
|
|
6143
|
-
const utxoInfos = [];
|
|
6144
|
-
let totalBalance = 0n;
|
|
6145
|
-
let spentCount = 0;
|
|
6146
|
-
let unspentCount = 0;
|
|
6147
|
-
for (let i = 0; i < decryptedUtxos.length; i++) {
|
|
6148
|
-
const { utxo, index } = decryptedUtxos[i];
|
|
6149
|
-
onProgress?.("Checking spent status", `UTXO ${i + 1}/${decryptedUtxos.length}`);
|
|
6150
|
-
const nullifier = utxo.getNullifier();
|
|
6151
|
-
const nullifierHex = toFixedHex(nullifier);
|
|
6152
|
-
const isSpent = await publicClient.readContract({
|
|
6153
|
-
address: poolAddress,
|
|
6154
|
-
abi: POOL_ABI,
|
|
6155
|
-
functionName: "isSpent",
|
|
6156
|
-
args: [nullifierHex]
|
|
6157
|
-
});
|
|
6158
|
-
utxoInfos.push({
|
|
6159
|
-
index,
|
|
6160
|
-
amount: viem.formatUnits(utxo.amount, poolConfig.decimals),
|
|
6161
|
-
amountWei: utxo.amount.toString(),
|
|
6162
|
-
isSpent
|
|
6163
|
-
});
|
|
6164
|
-
if (isSpent) {
|
|
6165
|
-
spentCount++;
|
|
6166
|
-
} else {
|
|
6167
|
-
unspentCount++;
|
|
6168
|
-
totalBalance += utxo.amount;
|
|
5998
|
+
} catch {
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
onProgress?.("Found UTXOs", `${decryptedUtxos.length} belonging to you`);
|
|
6002
|
+
const utxoInfos = [];
|
|
6003
|
+
let totalBalance = 0n;
|
|
6004
|
+
let spentCount = 0;
|
|
6005
|
+
let unspentCount = 0;
|
|
6006
|
+
for (let i = 0; i < decryptedUtxos.length; i++) {
|
|
6007
|
+
const { utxo, index } = decryptedUtxos[i];
|
|
6008
|
+
onProgress?.("Checking spent status", `UTXO ${i + 1}/${decryptedUtxos.length}`);
|
|
6009
|
+
const nullifier = utxo.getNullifier();
|
|
6010
|
+
const nullifierHex = toFixedHex(nullifier);
|
|
6011
|
+
const isSpent = await publicClient.readContract({
|
|
6012
|
+
address: poolAddress,
|
|
6013
|
+
abi: POOL_ABI,
|
|
6014
|
+
functionName: "isSpent",
|
|
6015
|
+
args: [nullifierHex]
|
|
6016
|
+
});
|
|
6017
|
+
utxoInfos.push({
|
|
6018
|
+
index,
|
|
6019
|
+
amount: viem.formatUnits(utxo.amount, poolConfig.decimals),
|
|
6020
|
+
amountWei: utxo.amount.toString(),
|
|
6021
|
+
isSpent
|
|
6022
|
+
});
|
|
6023
|
+
if (isSpent) {
|
|
6024
|
+
spentCount++;
|
|
6025
|
+
} else {
|
|
6026
|
+
unspentCount++;
|
|
6027
|
+
totalBalance += utxo.amount;
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
return {
|
|
6031
|
+
privateBalance: viem.formatUnits(totalBalance, poolConfig.decimals),
|
|
6032
|
+
privateBalanceWei: totalBalance.toString(),
|
|
6033
|
+
utxoCount: decryptedUtxos.length,
|
|
6034
|
+
spentCount,
|
|
6035
|
+
unspentCount,
|
|
6036
|
+
utxos: utxoInfos
|
|
6037
|
+
};
|
|
6038
|
+
}
|
|
6039
|
+
var MINIMUM_NET = {
|
|
6040
|
+
ETH: 0.01,
|
|
6041
|
+
USDC: 10
|
|
6042
|
+
};
|
|
6043
|
+
async function getGrossAmount(netWei, depositor, pool, rpcUrl) {
|
|
6044
|
+
const freeRemaining = await getDailyFreeRemaining({ address: depositor, pool, rpcUrl });
|
|
6045
|
+
if (freeRemaining > 0) {
|
|
6046
|
+
return { grossWei: netWei, feeWei: 0n, dailyFreeUsed: true, dailyFreeRemaining: freeRemaining - 1 };
|
|
6047
|
+
}
|
|
6048
|
+
const publicClient = viem.createPublicClient({
|
|
6049
|
+
chain: chains.base,
|
|
6050
|
+
transport: viem.http(rpcUrl)
|
|
6051
|
+
});
|
|
6052
|
+
const grossWei = await publicClient.readContract({
|
|
6053
|
+
address: getAddresses().entry,
|
|
6054
|
+
abi: ENTRY_ABI,
|
|
6055
|
+
functionName: "getDepositAmountWithFee",
|
|
6056
|
+
args: [netWei]
|
|
6057
|
+
});
|
|
6058
|
+
return { grossWei, feeWei: grossWei - netWei, dailyFreeUsed: false, dailyFreeRemaining: 0 };
|
|
6059
|
+
}
|
|
6060
|
+
var SUPPORTED_ASSETS = ["ETH", "USDC"];
|
|
6061
|
+
function createDepositCommand() {
|
|
6062
|
+
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 \u2014 this is what arrives in your Veil balance").option("--address <address>", "Signer address (required in --unsigned mode unless SIGNER_ADDRESS or WALLET_KEY is set)").option("--unsigned", "Output unsigned transaction payload instead of sending").option("--json", "Output as JSON").addHelpText("after", `
|
|
6063
|
+
The amount you specify is the net amount that lands in your Veil balance.
|
|
6064
|
+
A 0.3% protocol fee is normally added on top, but each address gets
|
|
6065
|
+
free daily deposits (fee waived). The CLI checks automatically.
|
|
6066
|
+
|
|
6067
|
+
Examples:
|
|
6068
|
+
veil deposit ETH 0.1 # deposits 0.1 ETH (free or ~0.1003 ETH)
|
|
6069
|
+
veil deposit USDC 100 # deposits 100 USDC (free or ~100.30 USDC)
|
|
6070
|
+
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
6071
|
+
SIGNER_ADDRESS=0x... veil deposit ETH 0.1 --unsigned
|
|
6072
|
+
veil deposit ETH 0.1 --json
|
|
6073
|
+
`).action(async (asset, amount, options) => {
|
|
6074
|
+
try {
|
|
6075
|
+
const assetUpper = asset.toUpperCase();
|
|
6076
|
+
if (!SUPPORTED_ASSETS.includes(assetUpper)) {
|
|
6077
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(", ")}`);
|
|
6078
|
+
}
|
|
6079
|
+
const amountNum = parseFloat(amount);
|
|
6080
|
+
const minimumNet = MINIMUM_NET[assetUpper];
|
|
6081
|
+
if (amountNum < minimumNet) {
|
|
6082
|
+
throw new CLIError(
|
|
6083
|
+
ErrorCode.INVALID_AMOUNT,
|
|
6084
|
+
`Minimum deposit is ${minimumNet} ${assetUpper}.`
|
|
6085
|
+
);
|
|
6086
|
+
}
|
|
6087
|
+
const rpcUrl = process.env.RPC_URL;
|
|
6088
|
+
const pool = assetUpper.toLowerCase();
|
|
6089
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
6090
|
+
const netWei = assetUpper === "ETH" ? viem.parseEther(amount) : viem.parseUnits(amount, poolConfig.decimals);
|
|
6091
|
+
const progress = createProgressReporter();
|
|
6092
|
+
let config = null;
|
|
6093
|
+
let address;
|
|
6094
|
+
let feeRpcUrl = rpcUrl;
|
|
6095
|
+
if (options.unsigned) {
|
|
6096
|
+
const resolved = resolveAddress({ address: options.address }, { required: true });
|
|
6097
|
+
if (!resolved) {
|
|
6098
|
+
throw new CLIError(
|
|
6099
|
+
ErrorCode.WALLET_KEY_MISSING,
|
|
6100
|
+
"Must provide --address, set SIGNER_ADDRESS, or set WALLET_KEY env."
|
|
6101
|
+
);
|
|
6102
|
+
}
|
|
6103
|
+
address = resolved.address;
|
|
6104
|
+
} else {
|
|
6105
|
+
config = getConfig(options);
|
|
6106
|
+
address = getAddress(config.privateKey);
|
|
6107
|
+
feeRpcUrl = config.rpcUrl;
|
|
6108
|
+
}
|
|
6109
|
+
progress("Checking deposit fee...");
|
|
6110
|
+
const { grossWei, feeWei, dailyFreeUsed, dailyFreeRemaining } = await getGrossAmount(
|
|
6111
|
+
netWei,
|
|
6112
|
+
address,
|
|
6113
|
+
pool,
|
|
6114
|
+
feeRpcUrl
|
|
6115
|
+
);
|
|
6116
|
+
const grossStr = assetUpper === "ETH" ? viem.formatEther(grossWei) : viem.formatUnits(grossWei, poolConfig.decimals);
|
|
6117
|
+
const feeStr = assetUpper === "ETH" ? viem.formatEther(feeWei) : viem.formatUnits(feeWei, poolConfig.decimals);
|
|
6118
|
+
const depositKey = process.env.DEPOSIT_KEY;
|
|
6119
|
+
if (!depositKey) {
|
|
6120
|
+
throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'DEPOSIT_KEY not set. Run "veil init" first.');
|
|
6121
|
+
}
|
|
6122
|
+
progress("Building transaction...");
|
|
6123
|
+
let tx;
|
|
6124
|
+
let approveTx = null;
|
|
6125
|
+
if (assetUpper === "USDC") {
|
|
6126
|
+
approveTx = buildApproveUSDCTx({ amount: grossStr });
|
|
6127
|
+
tx = buildDepositUSDCTx({ depositKey, amount: grossStr });
|
|
6128
|
+
} else {
|
|
6129
|
+
tx = buildDepositETHTx({ depositKey, amount: grossStr });
|
|
6130
|
+
}
|
|
6131
|
+
if (options.unsigned) {
|
|
6132
|
+
clearProgress();
|
|
6133
|
+
const payloads = [];
|
|
6134
|
+
if (approveTx) {
|
|
6135
|
+
payloads.push({
|
|
6136
|
+
step: "approve",
|
|
6137
|
+
to: approveTx.to,
|
|
6138
|
+
data: approveTx.data,
|
|
6139
|
+
value: "0",
|
|
6140
|
+
chainId: 8453
|
|
6141
|
+
});
|
|
6142
|
+
}
|
|
6143
|
+
payloads.push({
|
|
6144
|
+
step: "deposit",
|
|
6145
|
+
to: tx.to,
|
|
6146
|
+
data: tx.data,
|
|
6147
|
+
value: tx.value ? tx.value.toString() : "0",
|
|
6148
|
+
chainId: 8453
|
|
6149
|
+
});
|
|
6150
|
+
printJson(payloads.length === 1 ? payloads[0] : payloads);
|
|
6151
|
+
return;
|
|
6152
|
+
}
|
|
6153
|
+
if (!config) {
|
|
6154
|
+
throw new CLIError(ErrorCode.WALLET_KEY_MISSING, "WALLET_KEY env var required. Set it before running this command.");
|
|
6155
|
+
}
|
|
6156
|
+
if (assetUpper === "ETH") {
|
|
6157
|
+
progress("Checking balance...");
|
|
6158
|
+
const balance = await getBalance(address, config.rpcUrl);
|
|
6159
|
+
if (balance < grossWei) {
|
|
6160
|
+
clearProgress();
|
|
6161
|
+
throw new CLIError(
|
|
6162
|
+
ErrorCode.INSUFFICIENT_BALANCE,
|
|
6163
|
+
`Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${grossStr} ETH (${amount} + fee)`
|
|
6164
|
+
);
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
if (approveTx) {
|
|
6168
|
+
progress(`Approving ${assetUpper}...`);
|
|
6169
|
+
const approvalResult = await sendTransaction(config, approveTx);
|
|
6170
|
+
if (assetUpper === "USDC") {
|
|
6171
|
+
const publicClient = viem.createPublicClient({
|
|
6172
|
+
chain: chains.base,
|
|
6173
|
+
transport: viem.http(config.rpcUrl)
|
|
6174
|
+
});
|
|
6175
|
+
const addresses = getAddresses();
|
|
6176
|
+
let allowance = await publicClient.readContract({
|
|
6177
|
+
address: getAddresses().usdcToken,
|
|
6178
|
+
abi: ERC20_ABI,
|
|
6179
|
+
functionName: "allowance",
|
|
6180
|
+
args: [address, addresses.entry]
|
|
6181
|
+
});
|
|
6182
|
+
for (let confirmations = 2; allowance < grossWei && confirmations <= 3; confirmations++) {
|
|
6183
|
+
await publicClient.waitForTransactionReceipt({
|
|
6184
|
+
hash: approvalResult.hash,
|
|
6185
|
+
confirmations
|
|
6186
|
+
});
|
|
6187
|
+
allowance = await publicClient.readContract({
|
|
6188
|
+
address: addresses.usdcToken,
|
|
6189
|
+
abi: ERC20_ABI,
|
|
6190
|
+
functionName: "allowance",
|
|
6191
|
+
args: [address, addresses.entry]
|
|
6192
|
+
});
|
|
6193
|
+
}
|
|
6194
|
+
if (allowance < grossWei) {
|
|
6195
|
+
throw new CLIError(
|
|
6196
|
+
ErrorCode.CONTRACT_ERROR,
|
|
6197
|
+
`USDC approval is not yet visible on RPC after confirmation. Allowance ${allowance.toString()} < required ${grossWei.toString()}.`
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
progress("Sending deposit transaction...");
|
|
6203
|
+
const result = await sendTransaction(config, tx);
|
|
6204
|
+
progress("Confirming...");
|
|
6205
|
+
clearProgress();
|
|
6206
|
+
const output = {
|
|
6207
|
+
success: result.receipt.status === "success",
|
|
6208
|
+
hash: result.hash,
|
|
6209
|
+
asset: assetUpper,
|
|
6210
|
+
amount,
|
|
6211
|
+
fee: feeStr,
|
|
6212
|
+
dailyFreeUsed,
|
|
6213
|
+
totalSent: grossStr,
|
|
6214
|
+
blockNumber: result.receipt.blockNumber.toString()
|
|
6215
|
+
};
|
|
6216
|
+
if (options.json) {
|
|
6217
|
+
printJson(output);
|
|
6218
|
+
return;
|
|
6219
|
+
}
|
|
6220
|
+
const feeLabel = dailyFreeUsed ? `0 ${assetUpper} (free \u2014 ${dailyFreeRemaining} remaining today)` : `${feeStr} ${assetUpper} (0.3%)`;
|
|
6221
|
+
printHeader("Deposit Submitted");
|
|
6222
|
+
printFields([
|
|
6223
|
+
{ label: "Asset", value: assetUpper },
|
|
6224
|
+
{ label: "Amount", value: `${amount} ${assetUpper}` },
|
|
6225
|
+
{ label: "Fee", value: feeLabel },
|
|
6226
|
+
{ label: "Total sent", value: `${grossStr} ${assetUpper}` },
|
|
6227
|
+
{ label: "From", value: address },
|
|
6228
|
+
{ label: "Transaction", value: txUrl(result.hash) },
|
|
6229
|
+
{ label: "Block", value: result.receipt.blockNumber }
|
|
6230
|
+
]);
|
|
6231
|
+
printLine();
|
|
6232
|
+
} catch (error) {
|
|
6233
|
+
clearProgress();
|
|
6234
|
+
handleCLIError(error);
|
|
6169
6235
|
}
|
|
6170
|
-
}
|
|
6171
|
-
return
|
|
6172
|
-
privateBalance: viem.formatUnits(totalBalance, poolConfig.decimals),
|
|
6173
|
-
privateBalanceWei: totalBalance.toString(),
|
|
6174
|
-
utxoCount: decryptedUtxos.length,
|
|
6175
|
-
spentCount,
|
|
6176
|
-
unspentCount,
|
|
6177
|
-
utxos: utxoInfos
|
|
6178
|
-
};
|
|
6236
|
+
});
|
|
6237
|
+
return deposit;
|
|
6179
6238
|
}
|
|
6180
6239
|
|
|
6181
6240
|
// src/cli/commands/private-balance.ts
|
|
@@ -6682,7 +6741,16 @@ async function postRelayJson(endpoint, body, relayUrl) {
|
|
|
6682
6741
|
},
|
|
6683
6742
|
body: JSON.stringify(body)
|
|
6684
6743
|
});
|
|
6685
|
-
const
|
|
6744
|
+
const text = await response.text();
|
|
6745
|
+
let data;
|
|
6746
|
+
try {
|
|
6747
|
+
data = JSON.parse(text);
|
|
6748
|
+
} catch {
|
|
6749
|
+
throw new RelayError(
|
|
6750
|
+
`Relay returned non-JSON response (HTTP ${response.status})`,
|
|
6751
|
+
response.status
|
|
6752
|
+
);
|
|
6753
|
+
}
|
|
6686
6754
|
if (!response.ok) {
|
|
6687
6755
|
const errorData = data;
|
|
6688
6756
|
throw new RelayError(
|
|
@@ -7589,11 +7657,12 @@ function deriveSubaccountChildDepositKey(childPrivateKey) {
|
|
|
7589
7657
|
}
|
|
7590
7658
|
async function predictSubaccountForwarder(options) {
|
|
7591
7659
|
const publicClient = createBaseClient(options.rpcUrl);
|
|
7660
|
+
const depositKeyBytes = options.childDepositKey.startsWith("0x") ? options.childDepositKey : `0x${options.childDepositKey}`;
|
|
7592
7661
|
return publicClient.readContract({
|
|
7593
7662
|
abi: FORWARDER_FACTORY_ABI,
|
|
7594
7663
|
address: getForwarderFactoryAddress(),
|
|
7595
7664
|
functionName: "computeAddress",
|
|
7596
|
-
args: [options.salt,
|
|
7665
|
+
args: [options.salt, depositKeyBytes, options.childOwner]
|
|
7597
7666
|
});
|
|
7598
7667
|
}
|
|
7599
7668
|
async function deriveSubaccountSlot(options) {
|
|
@@ -7630,7 +7699,7 @@ async function deploySubaccountForwarder(options) {
|
|
|
7630
7699
|
slot: options.slot,
|
|
7631
7700
|
rpcUrl: options.rpcUrl
|
|
7632
7701
|
});
|
|
7633
|
-
|
|
7702
|
+
const result = await postRelayJson(
|
|
7634
7703
|
"/stealth/deploy",
|
|
7635
7704
|
{
|
|
7636
7705
|
salt: slot.salt,
|
|
@@ -7640,6 +7709,7 @@ async function deploySubaccountForwarder(options) {
|
|
|
7640
7709
|
},
|
|
7641
7710
|
options.relayUrl
|
|
7642
7711
|
);
|
|
7712
|
+
return { ...result, slot };
|
|
7643
7713
|
}
|
|
7644
7714
|
async function sweepSubaccountForwarder(options) {
|
|
7645
7715
|
const asset = normalizeAsset(options.asset);
|
|
@@ -7664,11 +7734,32 @@ function toQueueStatus(asset, result) {
|
|
|
7664
7734
|
pendingDeposits: result.pendingDeposits
|
|
7665
7735
|
};
|
|
7666
7736
|
}
|
|
7737
|
+
function toPrivateBalanceStatus(result) {
|
|
7738
|
+
return {
|
|
7739
|
+
privateBalance: result.privateBalance,
|
|
7740
|
+
privateBalanceWei: result.privateBalanceWei,
|
|
7741
|
+
utxoCount: result.utxoCount,
|
|
7742
|
+
spentCount: result.spentCount,
|
|
7743
|
+
unspentCount: result.unspentCount
|
|
7744
|
+
};
|
|
7745
|
+
}
|
|
7746
|
+
async function getSubaccountPrivateBalance(options) {
|
|
7747
|
+
const normalizedSlot = normalizeSlot(options.slot);
|
|
7748
|
+
assertPrivateKey(options.rootPrivateKey, "rootPrivateKey");
|
|
7749
|
+
const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, normalizedSlot);
|
|
7750
|
+
const childKeypair = new Keypair(childPrivateKey);
|
|
7751
|
+
return getPrivateBalance({
|
|
7752
|
+
keypair: childKeypair,
|
|
7753
|
+
pool: options.pool,
|
|
7754
|
+
rpcUrl: options.rpcUrl,
|
|
7755
|
+
onProgress: options.onProgress
|
|
7756
|
+
});
|
|
7757
|
+
}
|
|
7667
7758
|
async function getSubaccountStatus(options) {
|
|
7668
7759
|
const slot = await deriveSubaccountSlot(options);
|
|
7669
7760
|
const publicClient = createBaseClient(options.rpcUrl);
|
|
7670
7761
|
const addresses = getAddresses();
|
|
7671
|
-
const [deployed, ethWei, usdcWei, ethQueue, usdcQueue] = await Promise.all([
|
|
7762
|
+
const [deployed, ethWei, usdcWei, ethQueue, usdcQueue, ethPrivate, usdcPrivate] = await Promise.all([
|
|
7672
7763
|
isSubaccountForwarderDeployed({
|
|
7673
7764
|
forwarderAddress: slot.forwarderAddress,
|
|
7674
7765
|
rpcUrl: options.rpcUrl
|
|
@@ -7689,6 +7780,18 @@ async function getSubaccountStatus(options) {
|
|
|
7689
7780
|
address: slot.forwarderAddress,
|
|
7690
7781
|
pool: "usdc",
|
|
7691
7782
|
rpcUrl: options.rpcUrl
|
|
7783
|
+
}),
|
|
7784
|
+
getSubaccountPrivateBalance({
|
|
7785
|
+
rootPrivateKey: options.rootPrivateKey,
|
|
7786
|
+
slot: options.slot,
|
|
7787
|
+
pool: "eth",
|
|
7788
|
+
rpcUrl: options.rpcUrl
|
|
7789
|
+
}),
|
|
7790
|
+
getSubaccountPrivateBalance({
|
|
7791
|
+
rootPrivateKey: options.rootPrivateKey,
|
|
7792
|
+
slot: options.slot,
|
|
7793
|
+
pool: "usdc",
|
|
7794
|
+
rpcUrl: options.rpcUrl
|
|
7692
7795
|
})
|
|
7693
7796
|
]);
|
|
7694
7797
|
return {
|
|
@@ -7704,6 +7807,10 @@ async function getSubaccountStatus(options) {
|
|
|
7704
7807
|
balanceWei: usdcWei.toString()
|
|
7705
7808
|
}
|
|
7706
7809
|
},
|
|
7810
|
+
privateBalances: {
|
|
7811
|
+
eth: toPrivateBalanceStatus(ethPrivate),
|
|
7812
|
+
usdc: toPrivateBalanceStatus(usdcPrivate)
|
|
7813
|
+
},
|
|
7707
7814
|
queues: {
|
|
7708
7815
|
eth: toQueueStatus("eth", ethQueue),
|
|
7709
7816
|
usdc: toQueueStatus("usdc", usdcQueue)
|
|
@@ -7850,6 +7957,137 @@ async function buildSubaccountRecoveryTx(options) {
|
|
|
7850
7957
|
signature
|
|
7851
7958
|
};
|
|
7852
7959
|
}
|
|
7960
|
+
async function mergeSubaccount(options) {
|
|
7961
|
+
const {
|
|
7962
|
+
rootPrivateKey,
|
|
7963
|
+
slot,
|
|
7964
|
+
pool = "eth",
|
|
7965
|
+
rpcUrl,
|
|
7966
|
+
relayUrl,
|
|
7967
|
+
onProgress
|
|
7968
|
+
} = options;
|
|
7969
|
+
const normalizedSlot = normalizeSlot(slot);
|
|
7970
|
+
assertPrivateKey(rootPrivateKey, "rootPrivateKey");
|
|
7971
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
7972
|
+
const poolAddress = getPoolAddress(pool);
|
|
7973
|
+
const childPrivateKey = deriveSubaccountChildPrivateKey(rootPrivateKey, normalizedSlot);
|
|
7974
|
+
const childKeypair = new Keypair(childPrivateKey);
|
|
7975
|
+
const parentKeypair = new Keypair(rootPrivateKey);
|
|
7976
|
+
onProgress?.("Fetching subaccount balance...");
|
|
7977
|
+
const balanceResult = await getPrivateBalance({
|
|
7978
|
+
keypair: childKeypair,
|
|
7979
|
+
pool,
|
|
7980
|
+
rpcUrl,
|
|
7981
|
+
onProgress
|
|
7982
|
+
});
|
|
7983
|
+
const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
|
|
7984
|
+
if (unspentUtxoInfos.length === 0) {
|
|
7985
|
+
throw new Error("Subaccount has no unspent UTXOs to merge");
|
|
7986
|
+
}
|
|
7987
|
+
if (unspentUtxoInfos.length > 16) {
|
|
7988
|
+
throw new Error(
|
|
7989
|
+
`Subaccount has ${unspentUtxoInfos.length} unspent UTXOs which exceeds the 16-input circuit limit. Consolidate UTXOs on the subaccount first before merging.`
|
|
7990
|
+
);
|
|
7991
|
+
}
|
|
7992
|
+
onProgress?.("Preparing UTXOs...");
|
|
7993
|
+
const publicClient = viem.createPublicClient({
|
|
7994
|
+
chain: chains.base,
|
|
7995
|
+
transport: viem.http(rpcUrl)
|
|
7996
|
+
});
|
|
7997
|
+
const utxos = [];
|
|
7998
|
+
for (const utxoInfo of unspentUtxoInfos) {
|
|
7999
|
+
const encryptedOutputs = await publicClient.readContract({
|
|
8000
|
+
address: poolAddress,
|
|
8001
|
+
abi: POOL_ABI,
|
|
8002
|
+
functionName: "getEncryptedOutputs",
|
|
8003
|
+
args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
|
|
8004
|
+
});
|
|
8005
|
+
if (encryptedOutputs.length > 0) {
|
|
8006
|
+
try {
|
|
8007
|
+
const utxo = Utxo.decrypt(encryptedOutputs[0], childKeypair);
|
|
8008
|
+
utxo.index = utxoInfo.index;
|
|
8009
|
+
utxos.push(utxo);
|
|
8010
|
+
} catch {
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
8014
|
+
if (utxos.length === 0) {
|
|
8015
|
+
throw new Error("Failed to decrypt subaccount UTXOs");
|
|
8016
|
+
}
|
|
8017
|
+
onProgress?.("Selecting UTXOs...");
|
|
8018
|
+
const amount = balanceResult.privateBalance;
|
|
8019
|
+
const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
|
|
8020
|
+
utxos,
|
|
8021
|
+
amount,
|
|
8022
|
+
poolConfig.decimals
|
|
8023
|
+
);
|
|
8024
|
+
const outputs = [];
|
|
8025
|
+
const mergeWei = viem.parseUnits(amount, poolConfig.decimals);
|
|
8026
|
+
outputs.push(new Utxo({ amount: mergeWei, keypair: parentKeypair }));
|
|
8027
|
+
if (changeAmount > 0n) {
|
|
8028
|
+
outputs.push(new Utxo({ amount: changeAmount, keypair: parentKeypair }));
|
|
8029
|
+
}
|
|
8030
|
+
onProgress?.("Fetching commitments...");
|
|
8031
|
+
const nextIndex = await publicClient.readContract({
|
|
8032
|
+
address: poolAddress,
|
|
8033
|
+
abi: POOL_ABI,
|
|
8034
|
+
functionName: "nextIndex"
|
|
8035
|
+
});
|
|
8036
|
+
const BATCH_SIZE = 5e3;
|
|
8037
|
+
const commitments = [];
|
|
8038
|
+
const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
|
|
8039
|
+
for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
|
|
8040
|
+
const end = Math.min(start + BATCH_SIZE, nextIndex);
|
|
8041
|
+
const batchNum = Math.floor(start / BATCH_SIZE) + 1;
|
|
8042
|
+
onProgress?.("Fetching commitments", `batch ${batchNum}/${totalBatches}`);
|
|
8043
|
+
const batch = await publicClient.readContract({
|
|
8044
|
+
address: poolAddress,
|
|
8045
|
+
abi: POOL_ABI,
|
|
8046
|
+
functionName: "getCommitments",
|
|
8047
|
+
args: [BigInt(start), BigInt(end)]
|
|
8048
|
+
});
|
|
8049
|
+
commitments.push(...batch.map((c) => c.toString()));
|
|
8050
|
+
}
|
|
8051
|
+
onProgress?.("Building ZK proof...");
|
|
8052
|
+
const result = await prepareTransaction({
|
|
8053
|
+
commitments,
|
|
8054
|
+
inputs: selectedUtxos,
|
|
8055
|
+
outputs,
|
|
8056
|
+
fee: 0,
|
|
8057
|
+
recipient: "0x0000000000000000000000000000000000000000",
|
|
8058
|
+
relayer: "0x0000000000000000000000000000000000000000",
|
|
8059
|
+
onProgress
|
|
8060
|
+
});
|
|
8061
|
+
onProgress?.("Submitting to relay...");
|
|
8062
|
+
const relayResult = await submitRelay({
|
|
8063
|
+
type: "transfer",
|
|
8064
|
+
pool,
|
|
8065
|
+
relayUrl,
|
|
8066
|
+
proofArgs: {
|
|
8067
|
+
proof: result.args.proof,
|
|
8068
|
+
root: result.args.root,
|
|
8069
|
+
inputNullifiers: result.args.inputNullifiers,
|
|
8070
|
+
outputCommitments: result.args.outputCommitments,
|
|
8071
|
+
publicAmount: result.args.publicAmount,
|
|
8072
|
+
extDataHash: result.args.extDataHash
|
|
8073
|
+
},
|
|
8074
|
+
extData: result.extData,
|
|
8075
|
+
metadata: {
|
|
8076
|
+
amount,
|
|
8077
|
+
recipient: "self",
|
|
8078
|
+
inputUtxoCount: selectedUtxos.length,
|
|
8079
|
+
outputUtxoCount: outputs.length
|
|
8080
|
+
}
|
|
8081
|
+
});
|
|
8082
|
+
return {
|
|
8083
|
+
success: relayResult.success,
|
|
8084
|
+
transactionHash: relayResult.transactionHash,
|
|
8085
|
+
blockNumber: relayResult.blockNumber,
|
|
8086
|
+
amount,
|
|
8087
|
+
slot: normalizedSlot,
|
|
8088
|
+
pool
|
|
8089
|
+
};
|
|
8090
|
+
}
|
|
7853
8091
|
|
|
7854
8092
|
// src/cli/commands/subaccount.ts
|
|
7855
8093
|
function parseSlotValue(raw) {
|
|
@@ -7883,6 +8121,13 @@ function parseAsset(raw) {
|
|
|
7883
8121
|
}
|
|
7884
8122
|
return asset;
|
|
7885
8123
|
}
|
|
8124
|
+
function parsePool(raw) {
|
|
8125
|
+
const pool = raw.toLowerCase();
|
|
8126
|
+
if (pool !== "eth" && pool !== "usdc") {
|
|
8127
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported pool: ${raw}. Supported: eth, usdc`);
|
|
8128
|
+
}
|
|
8129
|
+
return pool;
|
|
8130
|
+
}
|
|
7886
8131
|
function printQueueHuman(title, queue) {
|
|
7887
8132
|
printSection(title);
|
|
7888
8133
|
printFields([
|
|
@@ -7902,6 +8147,7 @@ Examples:
|
|
|
7902
8147
|
veil subaccount status --slot 0
|
|
7903
8148
|
veil subaccount deploy --slot 0
|
|
7904
8149
|
veil subaccount sweep --slot 0 --asset eth
|
|
8150
|
+
veil subaccount merge --slot 0 --pool eth
|
|
7905
8151
|
veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
|
|
7906
8152
|
veil subaccount address --slot 0
|
|
7907
8153
|
`);
|
|
@@ -7939,7 +8185,7 @@ Examples:
|
|
|
7939
8185
|
handleCLIError(error);
|
|
7940
8186
|
}
|
|
7941
8187
|
});
|
|
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) => {
|
|
8188
|
+
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
8189
|
try {
|
|
7944
8190
|
const rootPrivateKey = getRequiredVeilKey();
|
|
7945
8191
|
const status = await getSubaccountStatus({
|
|
@@ -7964,6 +8210,17 @@ Examples:
|
|
|
7964
8210
|
{ label: "ETH", value: `${status.balances.eth.balance} ETH` },
|
|
7965
8211
|
{ label: "USDC", value: `${status.balances.usdc.balance} USDC` }
|
|
7966
8212
|
]);
|
|
8213
|
+
printSection("Private Pool Balances");
|
|
8214
|
+
printFields([
|
|
8215
|
+
{
|
|
8216
|
+
label: "ETH",
|
|
8217
|
+
value: `${status.privateBalances.eth.privateBalance} ETH (${status.privateBalances.eth.unspentCount} unspent / ${status.privateBalances.eth.spentCount} spent / ${status.privateBalances.eth.utxoCount} total UTXOs)`
|
|
8218
|
+
},
|
|
8219
|
+
{
|
|
8220
|
+
label: "USDC",
|
|
8221
|
+
value: `${status.privateBalances.usdc.privateBalance} USDC (${status.privateBalances.usdc.unspentCount} unspent / ${status.privateBalances.usdc.spentCount} spent / ${status.privateBalances.usdc.utxoCount} total UTXOs)`
|
|
8222
|
+
}
|
|
8223
|
+
]);
|
|
7967
8224
|
printQueueHuman("ETH Queue", status.queues.eth);
|
|
7968
8225
|
printQueueHuman("USDC Queue", status.queues.usdc);
|
|
7969
8226
|
printLine();
|
|
@@ -7974,11 +8231,6 @@ Examples:
|
|
|
7974
8231
|
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
8232
|
try {
|
|
7976
8233
|
const rootPrivateKey = getRequiredVeilKey();
|
|
7977
|
-
const slot = await deriveSubaccountSlot({
|
|
7978
|
-
rootPrivateKey,
|
|
7979
|
-
slot: options.slot,
|
|
7980
|
-
rpcUrl: process.env.RPC_URL
|
|
7981
|
-
});
|
|
7982
8234
|
const result = await deploySubaccountForwarder({
|
|
7983
8235
|
rootPrivateKey,
|
|
7984
8236
|
slot: options.slot,
|
|
@@ -7988,7 +8240,7 @@ Examples:
|
|
|
7988
8240
|
const output = {
|
|
7989
8241
|
...result,
|
|
7990
8242
|
slot: options.slot,
|
|
7991
|
-
forwarderAddress: slot.forwarderAddress
|
|
8243
|
+
forwarderAddress: result.slot.forwarderAddress
|
|
7992
8244
|
};
|
|
7993
8245
|
if (options.json) {
|
|
7994
8246
|
printJson(output);
|
|
@@ -7997,7 +8249,7 @@ Examples:
|
|
|
7997
8249
|
printHeader("Subaccount Deploy Submitted");
|
|
7998
8250
|
printFields([
|
|
7999
8251
|
{ label: "Slot", value: options.slot },
|
|
8000
|
-
{ label: "Forwarder", value: slot.forwarderAddress },
|
|
8252
|
+
{ label: "Forwarder", value: result.slot.forwarderAddress },
|
|
8001
8253
|
{ label: "Transaction", value: txUrl(result.transactionHash) },
|
|
8002
8254
|
{ label: "Block", value: result.blockNumber }
|
|
8003
8255
|
]);
|
|
@@ -8042,12 +8294,63 @@ Examples:
|
|
|
8042
8294
|
handleCLIError(error);
|
|
8043
8295
|
}
|
|
8044
8296
|
});
|
|
8297
|
+
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) => {
|
|
8298
|
+
try {
|
|
8299
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
8300
|
+
const result = await mergeSubaccount({
|
|
8301
|
+
rootPrivateKey,
|
|
8302
|
+
slot: options.slot,
|
|
8303
|
+
pool: options.pool,
|
|
8304
|
+
rpcUrl: process.env.RPC_URL,
|
|
8305
|
+
relayUrl: process.env.RELAY_URL,
|
|
8306
|
+
onProgress: options.json ? void 0 : (stage, detail) => {
|
|
8307
|
+
const msg = detail ? `${stage} ${detail}` : stage;
|
|
8308
|
+
process.stderr.write(`\r\x1B[K${msg}`);
|
|
8309
|
+
}
|
|
8310
|
+
});
|
|
8311
|
+
if (!options.json) {
|
|
8312
|
+
process.stderr.write("\r\x1B[K");
|
|
8313
|
+
}
|
|
8314
|
+
const output = {
|
|
8315
|
+
success: result.success,
|
|
8316
|
+
slot: result.slot,
|
|
8317
|
+
pool: result.pool,
|
|
8318
|
+
amount: result.amount,
|
|
8319
|
+
transactionHash: result.transactionHash,
|
|
8320
|
+
blockNumber: result.blockNumber
|
|
8321
|
+
};
|
|
8322
|
+
if (options.json) {
|
|
8323
|
+
printJson(output);
|
|
8324
|
+
return;
|
|
8325
|
+
}
|
|
8326
|
+
printHeader("Subaccount Merge Submitted");
|
|
8327
|
+
printFields([
|
|
8328
|
+
{ label: "Slot", value: result.slot },
|
|
8329
|
+
{ label: "Pool", value: result.pool.toUpperCase() },
|
|
8330
|
+
{ label: "Amount", value: result.amount },
|
|
8331
|
+
{ label: "Transaction", value: txUrl(result.transactionHash) },
|
|
8332
|
+
{ label: "Block", value: result.blockNumber }
|
|
8333
|
+
]);
|
|
8334
|
+
printLine();
|
|
8335
|
+
} catch (error) {
|
|
8336
|
+
if (!options.json) {
|
|
8337
|
+
process.stderr.write("\r\x1B[K");
|
|
8338
|
+
}
|
|
8339
|
+
handleCLIError(error);
|
|
8340
|
+
}
|
|
8341
|
+
});
|
|
8045
8342
|
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
8343
|
try {
|
|
8047
8344
|
const rootPrivateKey = getRequiredVeilKey();
|
|
8048
8345
|
if (!viem.isAddress(options.to)) {
|
|
8049
8346
|
throw new CLIError(ErrorCode.INVALID_ADDRESS, `Invalid recipient address: ${options.to}`);
|
|
8050
8347
|
}
|
|
8348
|
+
if (!process.env.WALLET_KEY) {
|
|
8349
|
+
throw new CLIError(
|
|
8350
|
+
ErrorCode.WALLET_KEY_MISSING,
|
|
8351
|
+
"WALLET_KEY required for recovery. Recovery submits a transaction on-chain and needs a gas payer."
|
|
8352
|
+
);
|
|
8353
|
+
}
|
|
8051
8354
|
const config = getConfig({});
|
|
8052
8355
|
const recovery = await buildSubaccountRecoveryTx({
|
|
8053
8356
|
rootPrivateKey,
|
|
@@ -8118,7 +8421,7 @@ Examples:
|
|
|
8118
8421
|
// src/cli/index.ts
|
|
8119
8422
|
loadEnv();
|
|
8120
8423
|
var program2 = new Command();
|
|
8121
|
-
program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.6.
|
|
8424
|
+
program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.6.2").addHelpText("after", `
|
|
8122
8425
|
Getting started:
|
|
8123
8426
|
veil init
|
|
8124
8427
|
veil register
|