@veil-cash/sdk 0.6.5 → 0.7.0
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 +47 -5
- package/SDK.md +77 -11
- package/dist/cli/index.cjs +194 -229
- package/dist/index.cjs +351 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -38
- package/dist/index.d.ts +131 -38
- package/dist/index.js +344 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/skills/veil/SKILL.md +1 -3
- package/skills/veil/reference.md +3 -3
- package/src/abi.ts +0 -8
- package/src/balance.ts +0 -48
- package/src/cli/commands/deposit.ts +8 -27
- package/src/index.ts +21 -1
- package/src/types.ts +5 -0
- package/src/utils.ts +3 -1
- package/src/withdraw.ts +2 -1
- package/src/x402.ts +546 -0
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.7.0` adds Coinbase-compatible x402 payments from private USDC balances via deterministic fresh payer EOAs.
|
|
12
|
+
|
|
11
13
|
`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
14
|
|
|
13
15
|
`0.6.0` adds SDK-first subaccount support for deterministic slot derivation, forwarder status, relay-backed deploy/sweep, and direct recovery.
|
|
@@ -91,7 +93,10 @@ veil subaccount sweep --slot 0 --asset eth
|
|
|
91
93
|
veil subaccount merge --slot 0 --pool eth
|
|
92
94
|
veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
|
|
93
95
|
|
|
94
|
-
# 9.
|
|
96
|
+
# 9. Pay an x402 resource from private USDC in application code
|
|
97
|
+
# See SDK.md for payX402Resource().
|
|
98
|
+
|
|
99
|
+
# 10. Use JSON or unsigned modes when you need automation
|
|
95
100
|
veil status --json
|
|
96
101
|
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
97
102
|
veil subaccount status --slot 0 --json
|
|
@@ -142,11 +147,11 @@ SIGNER_ADDRESS=0x... veil register --unsigned
|
|
|
142
147
|
|
|
143
148
|
In `--unsigned` mode, `--address` is optional when `SIGNER_ADDRESS` is set. `veil register --force` checks on-chain state first and emits a `changeDepositKey` payload only if the address is already registered; otherwise it emits a normal `register` payload.
|
|
144
149
|
|
|
145
|
-
Deposit ETH or USDC into Veil. The amount you specify is the **net** amount that arrives in your Veil balance. A 0.3% protocol fee is
|
|
150
|
+
Deposit ETH or USDC into Veil. The amount you specify is the **net** amount that arrives in your Veil balance. A 0.3% protocol fee is added on top:
|
|
146
151
|
|
|
147
152
|
```bash
|
|
148
|
-
veil deposit ETH 0.1 # 0.1 ETH lands in pool (
|
|
149
|
-
veil deposit USDC 100 # 100 USDC lands in pool (
|
|
153
|
+
veil deposit ETH 0.1 # 0.1 ETH lands in pool (~0.1003 ETH sent)
|
|
154
|
+
veil deposit USDC 100 # 100 USDC lands in pool (~100.30 USDC sent)
|
|
150
155
|
veil deposit ETH 0.1 --json
|
|
151
156
|
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
152
157
|
SIGNER_ADDRESS=0x... veil deposit ETH 0.1 --unsigned
|
|
@@ -205,6 +210,43 @@ veil merge USDC 100
|
|
|
205
210
|
veil merge ETH 0.1 --json
|
|
206
211
|
```
|
|
207
212
|
|
|
213
|
+
Pay a Coinbase-compatible x402 resource from private USDC:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { payX402Resource } from '@veil-cash/sdk';
|
|
217
|
+
|
|
218
|
+
const result = await payX402Resource({
|
|
219
|
+
url: 'https://merchant.example/paid-resource',
|
|
220
|
+
rootPrivateKey: process.env.VEIL_KEY as `0x${string}`,
|
|
221
|
+
payerIndex: 0n,
|
|
222
|
+
maxPayment: '0.10', // reject if the resource demands more than 0.10 USDC
|
|
223
|
+
relayUrl: process.env.X402_RELAY_URL,
|
|
224
|
+
rpcUrl: process.env.RPC_URL,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
console.log(result.response.status, result.payerAddress);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The helper withdraws the exact x402 amount to a deterministic fresh payer EOA,
|
|
231
|
+
then signs a standard x402 v2 Base USDC `exact` payment from that EOA. Use a
|
|
232
|
+
new `payerIndex` per payment. Pass `maxPayment` (a decimal USDC string) to cap
|
|
233
|
+
exposure; the payment is rejected before any funds move if the requirement
|
|
234
|
+
exceeds the cap. The withdrawal and payment legs are public and can be correlated
|
|
235
|
+
by amount and timing, but the source private balance remains hidden.
|
|
236
|
+
|
|
237
|
+
Use `quoteX402Resource({ url, rpcUrl, maxPayment, init })` to probe a resource
|
|
238
|
+
for its price and payment requirement without funding a payer or paying. It
|
|
239
|
+
returns the parsed requirement for a supported `402` or the raw status/body
|
|
240
|
+
otherwise.
|
|
241
|
+
|
|
242
|
+
If a payment is funded but delivery fails, the USDC stays on the payer EOA. Retry
|
|
243
|
+
with `reuseExistingBalance: true` and the same `payerIndex` to pay from that
|
|
244
|
+
balance without a second withdrawal; it throws if the balance is insufficient.
|
|
245
|
+
|
|
246
|
+
To surface funds left on a payer after a failed payment, use
|
|
247
|
+
`getX402PayerBalances({ rootPrivateKey, startIndex, count, nonZeroOnly })`, which
|
|
248
|
+
returns the Base USDC balance of each derived payer EOA. It is read-only.
|
|
249
|
+
|
|
208
250
|
### Subaccounts
|
|
209
251
|
|
|
210
252
|
Subaccounts are deterministic child slots derived from your main `VEIL_KEY`:
|
|
@@ -297,7 +339,7 @@ The CLI is the main entrypoint for most users. If you are integrating Veil progr
|
|
|
297
339
|
2. **Derive Keypair**: Run `veil init` to derive and save your Veil keypair
|
|
298
340
|
3. **Register**: Run `veil register` to link your deposit key on-chain (one-time)
|
|
299
341
|
4. **Check Status**: Run `veil status` to verify your setup
|
|
300
|
-
5. **Deposit**: Run `veil deposit <asset> <amount>` — the amount is what lands in your balance (e.g., `veil deposit ETH 0.1` deposits 0.1 ETH;
|
|
342
|
+
5. **Deposit**: Run `veil deposit <asset> <amount>` — the amount is what lands in your balance (e.g., `veil deposit ETH 0.1` deposits 0.1 ETH; a 0.3% protocol fee is added on top automatically)
|
|
301
343
|
6. **Wait**: The Veil deposit engine processes your deposit
|
|
302
344
|
7. **Done**: Your deposit is accepted into the privacy pool
|
|
303
345
|
|
package/SDK.md
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Keypair, buildRegisterTx, buildDepositETHTx,
|
|
12
12
|
buildDepositUSDCTx, buildApproveUSDCTx,
|
|
13
13
|
withdraw, transfer, getSubaccountStatus, mergeSubaccount,
|
|
14
|
+
payX402Resource,
|
|
14
15
|
} from '@veil-cash/sdk';
|
|
15
16
|
import { createWalletClient, http } from 'viem';
|
|
16
17
|
import { base } from 'viem/chains';
|
|
@@ -72,6 +73,14 @@ const subaccount = await getSubaccountStatus({
|
|
|
72
73
|
});
|
|
73
74
|
console.log(subaccount.slot.forwarderAddress);
|
|
74
75
|
|
|
76
|
+
// 8. Pay an x402 resource from private USDC
|
|
77
|
+
const paid = await payX402Resource({
|
|
78
|
+
url: 'https://merchant.example/paid-resource',
|
|
79
|
+
rootPrivateKey: keypair.privkey as `0x${string}`,
|
|
80
|
+
payerIndex: 0n,
|
|
81
|
+
relayUrl: process.env.X402_RELAY_URL,
|
|
82
|
+
});
|
|
83
|
+
console.log(paid.response.status, paid.payerAddress);
|
|
75
84
|
```
|
|
76
85
|
|
|
77
86
|
## SDK API Reference
|
|
@@ -109,10 +118,9 @@ keypair.privkey; // '0x...'
|
|
|
109
118
|
|
|
110
119
|
### Transaction Builders
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
> user has free slots, pass the net amount directly (no fee markup needed).
|
|
121
|
+
The CLI treats deposit amounts as net amounts and adds the 0.3% protocol fee
|
|
122
|
+
automatically. The low-level transaction builders use the amount you pass as the
|
|
123
|
+
gross amount sent to the Entry contract.
|
|
116
124
|
|
|
117
125
|
```typescript
|
|
118
126
|
import {
|
|
@@ -182,6 +190,70 @@ const mergeResult = await mergeUtxos({
|
|
|
182
190
|
});
|
|
183
191
|
```
|
|
184
192
|
|
|
193
|
+
### x402 Payments
|
|
194
|
+
|
|
195
|
+
`payX402Resource()` pays standard x402 v2 Base USDC `exact` resources from a
|
|
196
|
+
private Veil USDC balance while remaining compatible with Coinbase-facilitated
|
|
197
|
+
merchants.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import {
|
|
201
|
+
deriveX402PayerAddress,
|
|
202
|
+
payX402Resource,
|
|
203
|
+
usdcAtomicToDecimalString,
|
|
204
|
+
} from '@veil-cash/sdk';
|
|
205
|
+
|
|
206
|
+
const payerAddress = deriveX402PayerAddress(
|
|
207
|
+
process.env.VEIL_KEY as `0x${string}`,
|
|
208
|
+
42n,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const amount = usdcAtomicToDecimalString('1000'); // "0.001"
|
|
212
|
+
|
|
213
|
+
const result = await payX402Resource({
|
|
214
|
+
url: 'https://merchant.example/paid-resource',
|
|
215
|
+
rootPrivateKey: process.env.VEIL_KEY as `0x${string}`,
|
|
216
|
+
payerIndex: 42n,
|
|
217
|
+
maxPayment: '0.10', // cap exposure; reject if the resource demands more
|
|
218
|
+
relayUrl: process.env.X402_RELAY_URL,
|
|
219
|
+
rpcUrl: process.env.RPC_URL,
|
|
220
|
+
onProgress: (stage, detail) => console.log(stage, detail),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
console.log({
|
|
224
|
+
status: result.response.status,
|
|
225
|
+
payerAddress: result.payerAddress,
|
|
226
|
+
relayTx: result.relayTransactionHash,
|
|
227
|
+
paymentTx: result.paymentTransactionHash,
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The payer key uses the `veil-x402-payer` derivation domain, separate from
|
|
232
|
+
subaccounts. Use a fresh, persisted `payerIndex` for each payment. Set
|
|
233
|
+
`maxPayment` (a decimal USDC string) to cap exposure; the payment is rejected
|
|
234
|
+
before any funds move if the requirement exceeds the cap. The helper currently
|
|
235
|
+
supports x402 v2 `exact` requirements on Base mainnet USDC only; it rejects other
|
|
236
|
+
assets, networks, and schemes.
|
|
237
|
+
|
|
238
|
+
`quoteX402Resource({ url, rpcUrl, maxPayment, init })` probes a resource without
|
|
239
|
+
funding a payer or signing a payment. For a supported `402` it returns the price
|
|
240
|
+
and requirement (`amount`, `amountAtomic`, `payTo`, `network`, `asset`, and
|
|
241
|
+
`exceedsMax` when `maxPayment` is set); for any other status it returns the raw
|
|
242
|
+
`status` and parsed `body`. Use it to validate a request and confirm cost before
|
|
243
|
+
committing a withdrawal. A merchant that validates the request body only after
|
|
244
|
+
payment will still return `402` here, so a quote cannot catch post-payment errors.
|
|
245
|
+
|
|
246
|
+
To retry a payment whose funding succeeded but whose delivery failed (the USDC is
|
|
247
|
+
still on the payer EOA), pass `reuseExistingBalance: true` with the same
|
|
248
|
+
`payerIndex`. When the payer already holds at least the required amount,
|
|
249
|
+
`payX402Resource` skips the withdrawal and pays directly from that balance
|
|
250
|
+
(`relayTransactionHash` is empty); it throws if the balance is insufficient.
|
|
251
|
+
|
|
252
|
+
`getX402PayerBalances({ rootPrivateKey, startIndex, count, nonZeroOnly })`
|
|
253
|
+
inspects the Base USDC balance held by each derived payer EOA, which is useful
|
|
254
|
+
for surfacing funds left on a payer after a failed payment. It is read-only and
|
|
255
|
+
does not move funds.
|
|
256
|
+
|
|
185
257
|
### Browser Proof Generation
|
|
186
258
|
|
|
187
259
|
`withdraw()`, `transfer()`, `mergeUtxos()`, `mergeSubaccount()`, `buildWithdrawProof()`,
|
|
@@ -232,7 +304,7 @@ such as `circomlib`, `eth-sig-util`, and `fixed-merkle-tree`.
|
|
|
232
304
|
Balance functions accept an optional `pool` parameter (`'eth'` | `'usdc'`), defaulting to `'eth'`.
|
|
233
305
|
|
|
234
306
|
```typescript
|
|
235
|
-
import { getQueueBalance, getPrivateBalance
|
|
307
|
+
import { getQueueBalance, getPrivateBalance } from '@veil-cash/sdk';
|
|
236
308
|
|
|
237
309
|
// Check ETH queue balance (pending deposits)
|
|
238
310
|
const queueBalance = await getQueueBalance({
|
|
@@ -246,12 +318,6 @@ const privateBalance = await getPrivateBalance({
|
|
|
246
318
|
pool: 'usdc',
|
|
247
319
|
});
|
|
248
320
|
|
|
249
|
-
// Check how many fee-free deposits the user has left today
|
|
250
|
-
const freeRemaining = await getDailyFreeRemaining({
|
|
251
|
-
address: '0x...',
|
|
252
|
-
pool: 'eth', // default
|
|
253
|
-
});
|
|
254
|
-
// freeRemaining: number — 0 when all free slots are used or the feature is disabled
|
|
255
321
|
```
|
|
256
322
|
|
|
257
323
|
### Subaccounts
|
package/dist/cli/index.cjs
CHANGED
|
@@ -4204,14 +4204,6 @@ var QUEUE_ABI = [
|
|
|
4204
4204
|
outputs: [{ name: "count", type: "uint256" }],
|
|
4205
4205
|
stateMutability: "view",
|
|
4206
4206
|
type: "function"
|
|
4207
|
-
},
|
|
4208
|
-
// Get remaining daily free deposits for an address (V3+)
|
|
4209
|
-
{
|
|
4210
|
-
inputs: [{ name: "_depositor", type: "address" }],
|
|
4211
|
-
name: "getDailyFreeRemaining",
|
|
4212
|
-
outputs: [{ name: "remaining", type: "uint256" }],
|
|
4213
|
-
stateMutability: "view",
|
|
4214
|
-
type: "function"
|
|
4215
4207
|
}
|
|
4216
4208
|
];
|
|
4217
4209
|
var POOL_ABI = [
|
|
@@ -5126,7 +5118,9 @@ var poseidonHash2 = (a, b) => poseidonHash([a, b]);
|
|
|
5126
5118
|
var randomBN = (nbytes = 31) => {
|
|
5127
5119
|
const cryptoApi = globalThis.crypto;
|
|
5128
5120
|
if (!cryptoApi?.getRandomValues) {
|
|
5129
|
-
throw new Error(
|
|
5121
|
+
throw new Error(
|
|
5122
|
+
"Secure random number generation is unavailable. Provide globalThis.crypto.getRandomValues in this runtime."
|
|
5123
|
+
);
|
|
5130
5124
|
}
|
|
5131
5125
|
const bytes = cryptoApi.getRandomValues(new Uint8Array(nbytes));
|
|
5132
5126
|
let hex = "0x";
|
|
@@ -5803,6 +5797,195 @@ function createRegisterCommand() {
|
|
|
5803
5797
|
});
|
|
5804
5798
|
return register;
|
|
5805
5799
|
}
|
|
5800
|
+
var MINIMUM_NET = {
|
|
5801
|
+
ETH: 0.01,
|
|
5802
|
+
USDC: 10
|
|
5803
|
+
};
|
|
5804
|
+
async function getGrossAmount(netWei, rpcUrl) {
|
|
5805
|
+
const publicClient = viem.createPublicClient({
|
|
5806
|
+
chain: chains.base,
|
|
5807
|
+
transport: viem.http(rpcUrl)
|
|
5808
|
+
});
|
|
5809
|
+
const grossWei = await publicClient.readContract({
|
|
5810
|
+
address: getAddresses().entry,
|
|
5811
|
+
abi: ENTRY_ABI,
|
|
5812
|
+
functionName: "getDepositAmountWithFee",
|
|
5813
|
+
args: [netWei]
|
|
5814
|
+
});
|
|
5815
|
+
return { grossWei, feeWei: grossWei - netWei };
|
|
5816
|
+
}
|
|
5817
|
+
var SUPPORTED_ASSETS = ["ETH", "USDC"];
|
|
5818
|
+
function createDepositCommand() {
|
|
5819
|
+
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", `
|
|
5820
|
+
The amount you specify is the net amount that lands in your Veil balance.
|
|
5821
|
+
A 0.3% protocol fee is added on top.
|
|
5822
|
+
|
|
5823
|
+
Examples:
|
|
5824
|
+
veil deposit ETH 0.1 # deposits 0.1 ETH (~0.1003 ETH sent)
|
|
5825
|
+
veil deposit USDC 100 # deposits 100 USDC (~100.30 USDC sent)
|
|
5826
|
+
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
5827
|
+
SIGNER_ADDRESS=0x... veil deposit ETH 0.1 --unsigned
|
|
5828
|
+
veil deposit ETH 0.1 --json
|
|
5829
|
+
`).action(async (asset, amount, options) => {
|
|
5830
|
+
try {
|
|
5831
|
+
const assetUpper = asset.toUpperCase();
|
|
5832
|
+
if (!SUPPORTED_ASSETS.includes(assetUpper)) {
|
|
5833
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(", ")}`);
|
|
5834
|
+
}
|
|
5835
|
+
const amountNum = parseFloat(amount);
|
|
5836
|
+
const minimumNet = MINIMUM_NET[assetUpper];
|
|
5837
|
+
if (amountNum < minimumNet) {
|
|
5838
|
+
throw new CLIError(
|
|
5839
|
+
ErrorCode.INVALID_AMOUNT,
|
|
5840
|
+
`Minimum deposit is ${minimumNet} ${assetUpper}.`
|
|
5841
|
+
);
|
|
5842
|
+
}
|
|
5843
|
+
const rpcUrl = process.env.RPC_URL;
|
|
5844
|
+
const pool = assetUpper.toLowerCase();
|
|
5845
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
5846
|
+
const netWei = assetUpper === "ETH" ? viem.parseEther(amount) : viem.parseUnits(amount, poolConfig.decimals);
|
|
5847
|
+
const progress = createProgressReporter();
|
|
5848
|
+
let config = null;
|
|
5849
|
+
let address;
|
|
5850
|
+
let feeRpcUrl = rpcUrl;
|
|
5851
|
+
if (options.unsigned) {
|
|
5852
|
+
const resolved = resolveAddress({ address: options.address }, { required: true });
|
|
5853
|
+
if (!resolved) {
|
|
5854
|
+
throw new CLIError(
|
|
5855
|
+
ErrorCode.WALLET_KEY_MISSING,
|
|
5856
|
+
"Must provide --address, set SIGNER_ADDRESS, or set WALLET_KEY env."
|
|
5857
|
+
);
|
|
5858
|
+
}
|
|
5859
|
+
address = resolved.address;
|
|
5860
|
+
} else {
|
|
5861
|
+
config = getConfig(options);
|
|
5862
|
+
address = getAddress(config.privateKey);
|
|
5863
|
+
feeRpcUrl = config.rpcUrl;
|
|
5864
|
+
}
|
|
5865
|
+
progress("Checking deposit fee...");
|
|
5866
|
+
const { grossWei, feeWei } = await getGrossAmount(netWei, feeRpcUrl);
|
|
5867
|
+
const grossStr = assetUpper === "ETH" ? viem.formatEther(grossWei) : viem.formatUnits(grossWei, poolConfig.decimals);
|
|
5868
|
+
const feeStr = assetUpper === "ETH" ? viem.formatEther(feeWei) : viem.formatUnits(feeWei, poolConfig.decimals);
|
|
5869
|
+
const depositKey = process.env.DEPOSIT_KEY;
|
|
5870
|
+
if (!depositKey) {
|
|
5871
|
+
throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'DEPOSIT_KEY not set. Run "veil init" first.');
|
|
5872
|
+
}
|
|
5873
|
+
progress("Building transaction...");
|
|
5874
|
+
let tx;
|
|
5875
|
+
let approveTx = null;
|
|
5876
|
+
if (assetUpper === "USDC") {
|
|
5877
|
+
approveTx = buildApproveUSDCTx({ amount: grossStr });
|
|
5878
|
+
tx = buildDepositUSDCTx({ depositKey, amount: grossStr });
|
|
5879
|
+
} else {
|
|
5880
|
+
tx = buildDepositETHTx({ depositKey, amount: grossStr });
|
|
5881
|
+
}
|
|
5882
|
+
if (options.unsigned) {
|
|
5883
|
+
clearProgress();
|
|
5884
|
+
const payloads = [];
|
|
5885
|
+
if (approveTx) {
|
|
5886
|
+
payloads.push({
|
|
5887
|
+
step: "approve",
|
|
5888
|
+
to: approveTx.to,
|
|
5889
|
+
data: approveTx.data,
|
|
5890
|
+
value: "0",
|
|
5891
|
+
chainId: 8453
|
|
5892
|
+
});
|
|
5893
|
+
}
|
|
5894
|
+
payloads.push({
|
|
5895
|
+
step: "deposit",
|
|
5896
|
+
to: tx.to,
|
|
5897
|
+
data: tx.data,
|
|
5898
|
+
value: tx.value ? tx.value.toString() : "0",
|
|
5899
|
+
chainId: 8453
|
|
5900
|
+
});
|
|
5901
|
+
printJson(payloads.length === 1 ? payloads[0] : payloads);
|
|
5902
|
+
return;
|
|
5903
|
+
}
|
|
5904
|
+
if (!config) {
|
|
5905
|
+
throw new CLIError(ErrorCode.WALLET_KEY_MISSING, "WALLET_KEY env var required. Set it before running this command.");
|
|
5906
|
+
}
|
|
5907
|
+
if (assetUpper === "ETH") {
|
|
5908
|
+
progress("Checking balance...");
|
|
5909
|
+
const balance = await getBalance(address, config.rpcUrl);
|
|
5910
|
+
if (balance < grossWei) {
|
|
5911
|
+
clearProgress();
|
|
5912
|
+
throw new CLIError(
|
|
5913
|
+
ErrorCode.INSUFFICIENT_BALANCE,
|
|
5914
|
+
`Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${grossStr} ETH (${amount} + fee)`
|
|
5915
|
+
);
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
if (approveTx) {
|
|
5919
|
+
progress(`Approving ${assetUpper}...`);
|
|
5920
|
+
const approvalResult = await sendTransaction(config, approveTx);
|
|
5921
|
+
if (assetUpper === "USDC") {
|
|
5922
|
+
const publicClient = viem.createPublicClient({
|
|
5923
|
+
chain: chains.base,
|
|
5924
|
+
transport: viem.http(config.rpcUrl)
|
|
5925
|
+
});
|
|
5926
|
+
const addresses = getAddresses();
|
|
5927
|
+
let allowance = await publicClient.readContract({
|
|
5928
|
+
address: getAddresses().usdcToken,
|
|
5929
|
+
abi: ERC20_ABI,
|
|
5930
|
+
functionName: "allowance",
|
|
5931
|
+
args: [address, addresses.entry]
|
|
5932
|
+
});
|
|
5933
|
+
for (let confirmations = 2; allowance < grossWei && confirmations <= 3; confirmations++) {
|
|
5934
|
+
await publicClient.waitForTransactionReceipt({
|
|
5935
|
+
hash: approvalResult.hash,
|
|
5936
|
+
confirmations
|
|
5937
|
+
});
|
|
5938
|
+
allowance = await publicClient.readContract({
|
|
5939
|
+
address: addresses.usdcToken,
|
|
5940
|
+
abi: ERC20_ABI,
|
|
5941
|
+
functionName: "allowance",
|
|
5942
|
+
args: [address, addresses.entry]
|
|
5943
|
+
});
|
|
5944
|
+
}
|
|
5945
|
+
if (allowance < grossWei) {
|
|
5946
|
+
throw new CLIError(
|
|
5947
|
+
ErrorCode.CONTRACT_ERROR,
|
|
5948
|
+
`USDC approval is not yet visible on RPC after confirmation. Allowance ${allowance.toString()} < required ${grossWei.toString()}.`
|
|
5949
|
+
);
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
progress("Sending deposit transaction...");
|
|
5954
|
+
const result = await sendTransaction(config, tx);
|
|
5955
|
+
progress("Confirming...");
|
|
5956
|
+
clearProgress();
|
|
5957
|
+
const output = {
|
|
5958
|
+
success: result.receipt.status === "success",
|
|
5959
|
+
hash: result.hash,
|
|
5960
|
+
asset: assetUpper,
|
|
5961
|
+
amount,
|
|
5962
|
+
fee: feeStr,
|
|
5963
|
+
totalSent: grossStr,
|
|
5964
|
+
blockNumber: result.receipt.blockNumber.toString()
|
|
5965
|
+
};
|
|
5966
|
+
if (options.json) {
|
|
5967
|
+
printJson(output);
|
|
5968
|
+
return;
|
|
5969
|
+
}
|
|
5970
|
+
const feeLabel = `${feeStr} ${assetUpper} (0.3%)`;
|
|
5971
|
+
printHeader("Deposit Submitted");
|
|
5972
|
+
printFields([
|
|
5973
|
+
{ label: "Asset", value: assetUpper },
|
|
5974
|
+
{ label: "Amount", value: `${amount} ${assetUpper}` },
|
|
5975
|
+
{ label: "Fee", value: feeLabel },
|
|
5976
|
+
{ label: "Total sent", value: `${grossStr} ${assetUpper}` },
|
|
5977
|
+
{ label: "From", value: address },
|
|
5978
|
+
{ label: "Transaction", value: txUrl(result.hash) },
|
|
5979
|
+
{ label: "Block", value: result.receipt.blockNumber }
|
|
5980
|
+
]);
|
|
5981
|
+
printLine();
|
|
5982
|
+
} catch (error) {
|
|
5983
|
+
clearProgress();
|
|
5984
|
+
handleCLIError(error);
|
|
5985
|
+
}
|
|
5986
|
+
});
|
|
5987
|
+
return deposit;
|
|
5988
|
+
}
|
|
5806
5989
|
var Utxo = class _Utxo {
|
|
5807
5990
|
amount;
|
|
5808
5991
|
blinding;
|
|
@@ -5933,25 +6116,6 @@ async function getQueueBalance(options) {
|
|
|
5933
6116
|
pendingCount: pendingDeposits.length
|
|
5934
6117
|
};
|
|
5935
6118
|
}
|
|
5936
|
-
async function getDailyFreeRemaining(options) {
|
|
5937
|
-
const { address, pool = "eth", rpcUrl } = options;
|
|
5938
|
-
const queueAddress = getQueueAddress(pool);
|
|
5939
|
-
const publicClient = viem.createPublicClient({
|
|
5940
|
-
chain: chains.base,
|
|
5941
|
-
transport: viem.http(rpcUrl)
|
|
5942
|
-
});
|
|
5943
|
-
try {
|
|
5944
|
-
const remaining = await publicClient.readContract({
|
|
5945
|
-
address: queueAddress,
|
|
5946
|
-
abi: QUEUE_ABI,
|
|
5947
|
-
functionName: "getDailyFreeRemaining",
|
|
5948
|
-
args: [address]
|
|
5949
|
-
});
|
|
5950
|
-
return Number(remaining);
|
|
5951
|
-
} catch {
|
|
5952
|
-
return 0;
|
|
5953
|
-
}
|
|
5954
|
-
}
|
|
5955
6119
|
async function getPrivateBalance(options) {
|
|
5956
6120
|
const { keypair, pool = "eth", rpcUrl, onProgress } = options;
|
|
5957
6121
|
const poolAddress = getPoolAddress(pool);
|
|
@@ -6045,206 +6209,6 @@ async function getPrivateBalance(options) {
|
|
|
6045
6209
|
utxos: utxoInfos
|
|
6046
6210
|
};
|
|
6047
6211
|
}
|
|
6048
|
-
var MINIMUM_NET = {
|
|
6049
|
-
ETH: 0.01,
|
|
6050
|
-
USDC: 10
|
|
6051
|
-
};
|
|
6052
|
-
async function getGrossAmount(netWei, depositor, pool, rpcUrl) {
|
|
6053
|
-
const freeRemaining = await getDailyFreeRemaining({ address: depositor, pool, rpcUrl });
|
|
6054
|
-
if (freeRemaining > 0) {
|
|
6055
|
-
return { grossWei: netWei, feeWei: 0n, dailyFreeUsed: true, dailyFreeRemaining: freeRemaining - 1 };
|
|
6056
|
-
}
|
|
6057
|
-
const publicClient = viem.createPublicClient({
|
|
6058
|
-
chain: chains.base,
|
|
6059
|
-
transport: viem.http(rpcUrl)
|
|
6060
|
-
});
|
|
6061
|
-
const grossWei = await publicClient.readContract({
|
|
6062
|
-
address: getAddresses().entry,
|
|
6063
|
-
abi: ENTRY_ABI,
|
|
6064
|
-
functionName: "getDepositAmountWithFee",
|
|
6065
|
-
args: [netWei]
|
|
6066
|
-
});
|
|
6067
|
-
return { grossWei, feeWei: grossWei - netWei, dailyFreeUsed: false, dailyFreeRemaining: 0 };
|
|
6068
|
-
}
|
|
6069
|
-
var SUPPORTED_ASSETS = ["ETH", "USDC"];
|
|
6070
|
-
function createDepositCommand() {
|
|
6071
|
-
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", `
|
|
6072
|
-
The amount you specify is the net amount that lands in your Veil balance.
|
|
6073
|
-
A 0.3% protocol fee is normally added on top, but each address gets
|
|
6074
|
-
free daily deposits (fee waived). The CLI checks automatically.
|
|
6075
|
-
|
|
6076
|
-
Examples:
|
|
6077
|
-
veil deposit ETH 0.1 # deposits 0.1 ETH (free or ~0.1003 ETH)
|
|
6078
|
-
veil deposit USDC 100 # deposits 100 USDC (free or ~100.30 USDC)
|
|
6079
|
-
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
6080
|
-
SIGNER_ADDRESS=0x... veil deposit ETH 0.1 --unsigned
|
|
6081
|
-
veil deposit ETH 0.1 --json
|
|
6082
|
-
`).action(async (asset, amount, options) => {
|
|
6083
|
-
try {
|
|
6084
|
-
const assetUpper = asset.toUpperCase();
|
|
6085
|
-
if (!SUPPORTED_ASSETS.includes(assetUpper)) {
|
|
6086
|
-
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(", ")}`);
|
|
6087
|
-
}
|
|
6088
|
-
const amountNum = parseFloat(amount);
|
|
6089
|
-
const minimumNet = MINIMUM_NET[assetUpper];
|
|
6090
|
-
if (amountNum < minimumNet) {
|
|
6091
|
-
throw new CLIError(
|
|
6092
|
-
ErrorCode.INVALID_AMOUNT,
|
|
6093
|
-
`Minimum deposit is ${minimumNet} ${assetUpper}.`
|
|
6094
|
-
);
|
|
6095
|
-
}
|
|
6096
|
-
const rpcUrl = process.env.RPC_URL;
|
|
6097
|
-
const pool = assetUpper.toLowerCase();
|
|
6098
|
-
const poolConfig = POOL_CONFIG[pool];
|
|
6099
|
-
const netWei = assetUpper === "ETH" ? viem.parseEther(amount) : viem.parseUnits(amount, poolConfig.decimals);
|
|
6100
|
-
const progress = createProgressReporter();
|
|
6101
|
-
let config = null;
|
|
6102
|
-
let address;
|
|
6103
|
-
let feeRpcUrl = rpcUrl;
|
|
6104
|
-
if (options.unsigned) {
|
|
6105
|
-
const resolved = resolveAddress({ address: options.address }, { required: true });
|
|
6106
|
-
if (!resolved) {
|
|
6107
|
-
throw new CLIError(
|
|
6108
|
-
ErrorCode.WALLET_KEY_MISSING,
|
|
6109
|
-
"Must provide --address, set SIGNER_ADDRESS, or set WALLET_KEY env."
|
|
6110
|
-
);
|
|
6111
|
-
}
|
|
6112
|
-
address = resolved.address;
|
|
6113
|
-
} else {
|
|
6114
|
-
config = getConfig(options);
|
|
6115
|
-
address = getAddress(config.privateKey);
|
|
6116
|
-
feeRpcUrl = config.rpcUrl;
|
|
6117
|
-
}
|
|
6118
|
-
progress("Checking deposit fee...");
|
|
6119
|
-
const { grossWei, feeWei, dailyFreeUsed, dailyFreeRemaining } = await getGrossAmount(
|
|
6120
|
-
netWei,
|
|
6121
|
-
address,
|
|
6122
|
-
pool,
|
|
6123
|
-
feeRpcUrl
|
|
6124
|
-
);
|
|
6125
|
-
const grossStr = assetUpper === "ETH" ? viem.formatEther(grossWei) : viem.formatUnits(grossWei, poolConfig.decimals);
|
|
6126
|
-
const feeStr = assetUpper === "ETH" ? viem.formatEther(feeWei) : viem.formatUnits(feeWei, poolConfig.decimals);
|
|
6127
|
-
const depositKey = process.env.DEPOSIT_KEY;
|
|
6128
|
-
if (!depositKey) {
|
|
6129
|
-
throw new CLIError(ErrorCode.DEPOSIT_KEY_MISSING, 'DEPOSIT_KEY not set. Run "veil init" first.');
|
|
6130
|
-
}
|
|
6131
|
-
progress("Building transaction...");
|
|
6132
|
-
let tx;
|
|
6133
|
-
let approveTx = null;
|
|
6134
|
-
if (assetUpper === "USDC") {
|
|
6135
|
-
approveTx = buildApproveUSDCTx({ amount: grossStr });
|
|
6136
|
-
tx = buildDepositUSDCTx({ depositKey, amount: grossStr });
|
|
6137
|
-
} else {
|
|
6138
|
-
tx = buildDepositETHTx({ depositKey, amount: grossStr });
|
|
6139
|
-
}
|
|
6140
|
-
if (options.unsigned) {
|
|
6141
|
-
clearProgress();
|
|
6142
|
-
const payloads = [];
|
|
6143
|
-
if (approveTx) {
|
|
6144
|
-
payloads.push({
|
|
6145
|
-
step: "approve",
|
|
6146
|
-
to: approveTx.to,
|
|
6147
|
-
data: approveTx.data,
|
|
6148
|
-
value: "0",
|
|
6149
|
-
chainId: 8453
|
|
6150
|
-
});
|
|
6151
|
-
}
|
|
6152
|
-
payloads.push({
|
|
6153
|
-
step: "deposit",
|
|
6154
|
-
to: tx.to,
|
|
6155
|
-
data: tx.data,
|
|
6156
|
-
value: tx.value ? tx.value.toString() : "0",
|
|
6157
|
-
chainId: 8453
|
|
6158
|
-
});
|
|
6159
|
-
printJson(payloads.length === 1 ? payloads[0] : payloads);
|
|
6160
|
-
return;
|
|
6161
|
-
}
|
|
6162
|
-
if (!config) {
|
|
6163
|
-
throw new CLIError(ErrorCode.WALLET_KEY_MISSING, "WALLET_KEY env var required. Set it before running this command.");
|
|
6164
|
-
}
|
|
6165
|
-
if (assetUpper === "ETH") {
|
|
6166
|
-
progress("Checking balance...");
|
|
6167
|
-
const balance = await getBalance(address, config.rpcUrl);
|
|
6168
|
-
if (balance < grossWei) {
|
|
6169
|
-
clearProgress();
|
|
6170
|
-
throw new CLIError(
|
|
6171
|
-
ErrorCode.INSUFFICIENT_BALANCE,
|
|
6172
|
-
`Insufficient ETH balance. Have: ${viem.formatEther(balance)} ETH, Need: ${grossStr} ETH (${amount} + fee)`
|
|
6173
|
-
);
|
|
6174
|
-
}
|
|
6175
|
-
}
|
|
6176
|
-
if (approveTx) {
|
|
6177
|
-
progress(`Approving ${assetUpper}...`);
|
|
6178
|
-
const approvalResult = await sendTransaction(config, approveTx);
|
|
6179
|
-
if (assetUpper === "USDC") {
|
|
6180
|
-
const publicClient = viem.createPublicClient({
|
|
6181
|
-
chain: chains.base,
|
|
6182
|
-
transport: viem.http(config.rpcUrl)
|
|
6183
|
-
});
|
|
6184
|
-
const addresses = getAddresses();
|
|
6185
|
-
let allowance = await publicClient.readContract({
|
|
6186
|
-
address: getAddresses().usdcToken,
|
|
6187
|
-
abi: ERC20_ABI,
|
|
6188
|
-
functionName: "allowance",
|
|
6189
|
-
args: [address, addresses.entry]
|
|
6190
|
-
});
|
|
6191
|
-
for (let confirmations = 2; allowance < grossWei && confirmations <= 3; confirmations++) {
|
|
6192
|
-
await publicClient.waitForTransactionReceipt({
|
|
6193
|
-
hash: approvalResult.hash,
|
|
6194
|
-
confirmations
|
|
6195
|
-
});
|
|
6196
|
-
allowance = await publicClient.readContract({
|
|
6197
|
-
address: addresses.usdcToken,
|
|
6198
|
-
abi: ERC20_ABI,
|
|
6199
|
-
functionName: "allowance",
|
|
6200
|
-
args: [address, addresses.entry]
|
|
6201
|
-
});
|
|
6202
|
-
}
|
|
6203
|
-
if (allowance < grossWei) {
|
|
6204
|
-
throw new CLIError(
|
|
6205
|
-
ErrorCode.CONTRACT_ERROR,
|
|
6206
|
-
`USDC approval is not yet visible on RPC after confirmation. Allowance ${allowance.toString()} < required ${grossWei.toString()}.`
|
|
6207
|
-
);
|
|
6208
|
-
}
|
|
6209
|
-
}
|
|
6210
|
-
}
|
|
6211
|
-
progress("Sending deposit transaction...");
|
|
6212
|
-
const result = await sendTransaction(config, tx);
|
|
6213
|
-
progress("Confirming...");
|
|
6214
|
-
clearProgress();
|
|
6215
|
-
const output = {
|
|
6216
|
-
success: result.receipt.status === "success",
|
|
6217
|
-
hash: result.hash,
|
|
6218
|
-
asset: assetUpper,
|
|
6219
|
-
amount,
|
|
6220
|
-
fee: feeStr,
|
|
6221
|
-
dailyFreeUsed,
|
|
6222
|
-
totalSent: grossStr,
|
|
6223
|
-
blockNumber: result.receipt.blockNumber.toString()
|
|
6224
|
-
};
|
|
6225
|
-
if (options.json) {
|
|
6226
|
-
printJson(output);
|
|
6227
|
-
return;
|
|
6228
|
-
}
|
|
6229
|
-
const feeLabel = dailyFreeUsed ? `0 ${assetUpper} (free \u2014 ${dailyFreeRemaining} remaining today)` : `${feeStr} ${assetUpper} (0.3%)`;
|
|
6230
|
-
printHeader("Deposit Submitted");
|
|
6231
|
-
printFields([
|
|
6232
|
-
{ label: "Asset", value: assetUpper },
|
|
6233
|
-
{ label: "Amount", value: `${amount} ${assetUpper}` },
|
|
6234
|
-
{ label: "Fee", value: feeLabel },
|
|
6235
|
-
{ label: "Total sent", value: `${grossStr} ${assetUpper}` },
|
|
6236
|
-
{ label: "From", value: address },
|
|
6237
|
-
{ label: "Transaction", value: txUrl(result.hash) },
|
|
6238
|
-
{ label: "Block", value: result.receipt.blockNumber }
|
|
6239
|
-
]);
|
|
6240
|
-
printLine();
|
|
6241
|
-
} catch (error) {
|
|
6242
|
-
clearProgress();
|
|
6243
|
-
handleCLIError(error);
|
|
6244
|
-
}
|
|
6245
|
-
});
|
|
6246
|
-
return deposit;
|
|
6247
|
-
}
|
|
6248
6212
|
|
|
6249
6213
|
// src/cli/commands/private-balance.ts
|
|
6250
6214
|
var SUPPORTED_POOLS = ["eth", "usdc"];
|
|
@@ -6994,12 +6958,13 @@ async function buildWithdrawProof(options) {
|
|
|
6994
6958
|
};
|
|
6995
6959
|
}
|
|
6996
6960
|
async function withdraw(options) {
|
|
6997
|
-
const { amount, recipient, pool = "eth", onProgress } = options;
|
|
6961
|
+
const { amount, recipient, pool = "eth", onProgress, relayUrl } = options;
|
|
6998
6962
|
const proof = await buildWithdrawProof(options);
|
|
6999
6963
|
onProgress?.("Submitting to relay...");
|
|
7000
6964
|
const relayResult = await submitRelay({
|
|
7001
6965
|
type: "withdraw",
|
|
7002
6966
|
pool,
|
|
6967
|
+
relayUrl,
|
|
7003
6968
|
proofArgs: proof.proofArgs,
|
|
7004
6969
|
extData: proof.extData,
|
|
7005
6970
|
metadata: {
|