@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/package.json
CHANGED
package/skills/veil/SKILL.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: veil
|
|
3
|
-
version: 0.6.
|
|
3
|
+
version: 0.6.2
|
|
4
4
|
description: >
|
|
5
5
|
Veil CLI for private ETH and USDC transactions on Base. Use when the user wants
|
|
6
6
|
to deposit, withdraw, or transfer assets privately, check private balances,
|
|
7
7
|
manage Veil keypairs, register on-chain, manage deterministic subaccounts
|
|
8
|
-
(forwarder deploy, sweep, recover), or build unsigned
|
|
9
|
-
for an external signer (e.g. Bankr). All operations
|
|
8
|
+
(forwarder deploy, sweep, merge to main wallet, recover), or build unsigned
|
|
9
|
+
transaction payloads for an external signer (e.g. Bankr). All operations
|
|
10
|
+
target Base (chain ID 8453).
|
|
10
11
|
author: veildotcash
|
|
11
12
|
metadata:
|
|
12
13
|
homepage: https://veil.cash
|
|
@@ -35,6 +36,7 @@ triggers:
|
|
|
35
36
|
- pattern: withdraw privately
|
|
36
37
|
- pattern: private transfer
|
|
37
38
|
- pattern: subaccount
|
|
39
|
+
- pattern: subaccount merge
|
|
38
40
|
- pattern: forwarder
|
|
39
41
|
- pattern: stealth deposit
|
|
40
42
|
---
|
|
@@ -163,7 +165,7 @@ What do you want to do?
|
|
|
163
165
|
|
|
|
164
166
|
+-- Register deposit key on-chain → veil register [--unsigned]
|
|
165
167
|
|
|
|
166
|
-
+-- Deposit ETH or USDC → veil deposit <asset> <amount> [--unsigned]
|
|
168
|
+
+-- Deposit ETH or USDC → veil deposit <asset> <amount> [--unsigned --address 0x...]
|
|
167
169
|
|
|
|
168
170
|
+-- Check balances → veil balance [queue|private] [--pool eth|usdc]
|
|
169
171
|
|
|
|
@@ -200,6 +202,7 @@ What do you want to do?
|
|
|
200
202
|
| Subaccount address | `veil subaccount address --slot 0` |
|
|
201
203
|
| Deploy forwarder | `veil subaccount deploy --slot 0` |
|
|
202
204
|
| Sweep forwarder | `veil subaccount sweep --slot 0 --asset eth` |
|
|
205
|
+
| Merge subaccount to main | `veil subaccount merge --slot 0 --pool eth` |
|
|
203
206
|
| Recover from forwarder | `veil subaccount recover --slot 0 --asset usdc --to 0xAddr --amount 25` |
|
|
204
207
|
|
|
205
208
|
---
|
|
@@ -327,7 +330,9 @@ Important:
|
|
|
327
330
|
- If not yet registered, returns a normal `register` payload.
|
|
328
331
|
|
|
329
332
|
Deposits treat the CLI amount as the **net** amount that lands in the pool.
|
|
330
|
-
|
|
333
|
+
Each address gets a configurable number of fee-free deposits per UTC day.
|
|
334
|
+
The CLI checks automatically — if free slots remain the fee is waived;
|
|
335
|
+
otherwise the `0.3%` protocol fee is calculated on-chain and added.
|
|
331
336
|
After submission, deposits go through screening / queue processing before they
|
|
332
337
|
are accepted into the private pool. This typically takes around `10-15 minutes`.
|
|
333
338
|
|
|
@@ -335,8 +340,8 @@ are accepted into the private pool. This typically takes around `10-15 minutes`.
|
|
|
335
340
|
veil deposit ETH 0.1
|
|
336
341
|
veil deposit USDC 100
|
|
337
342
|
veil deposit ETH 0.1 --json
|
|
338
|
-
veil deposit ETH 0.1 --unsigned
|
|
339
|
-
veil deposit USDC 100 --unsigned
|
|
343
|
+
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
344
|
+
SIGNER_ADDRESS=0x... veil deposit USDC 100 --unsigned
|
|
340
345
|
```
|
|
341
346
|
|
|
342
347
|
Minimums:
|
|
@@ -428,11 +433,13 @@ Subaccounts are deterministic child slots derived from your main `VEIL_KEY`:
|
|
|
428
433
|
`root key → slot → child key → child deposit key → forwarder`
|
|
429
434
|
|
|
430
435
|
Base mainnet only. Slots are `0`–`2` (max 3 subaccounts). Deploy and sweep are
|
|
431
|
-
relay-backed (no `WALLET_KEY` needed).
|
|
432
|
-
|
|
436
|
+
relay-backed (no `WALLET_KEY` needed). Merge transfers the subaccount's private
|
|
437
|
+
pool balance back to the main wallet via a ZK proof (relay-backed, no `WALLET_KEY`
|
|
438
|
+
needed). Recovery submits a direct on-chain transaction and **requires `WALLET_KEY`**
|
|
439
|
+
as a gas payer.
|
|
433
440
|
|
|
434
|
-
Status reports the forwarder wallet balances
|
|
435
|
-
|
|
441
|
+
Status reports the child slot's forwarder wallet balances, private pool
|
|
442
|
+
balances, and queue state.
|
|
436
443
|
|
|
437
444
|
### Derive and inspect
|
|
438
445
|
|
|
@@ -440,7 +447,7 @@ pool attribution after queued funds are accepted.
|
|
|
440
447
|
veil subaccount derive --slot 0 # Full slot metadata
|
|
441
448
|
veil subaccount derive --slot 0 --json
|
|
442
449
|
veil subaccount address --slot 0 # Just the forwarder address
|
|
443
|
-
veil subaccount status --slot 0 # Deployment, balances, queue state
|
|
450
|
+
veil subaccount status --slot 0 # Deployment, forwarder balances, private balances, queue state
|
|
444
451
|
veil subaccount status --slot 0 --json
|
|
445
452
|
```
|
|
446
453
|
|
|
@@ -454,6 +461,18 @@ veil subaccount sweep --slot 0 --asset usdc # Sweep USDC into the pool
|
|
|
454
461
|
veil subaccount sweep --slot 0 --asset eth --json
|
|
455
462
|
```
|
|
456
463
|
|
|
464
|
+
### Merge subaccount to main wallet (relay-backed)
|
|
465
|
+
|
|
466
|
+
Merge transfers the subaccount's entire private pool balance back to the main
|
|
467
|
+
wallet. It builds a ZK proof transferring child UTXOs to the parent keypair and
|
|
468
|
+
submits via the relay. Only needs `VEIL_KEY`.
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
veil subaccount merge --slot 0 --pool eth
|
|
472
|
+
veil subaccount merge --slot 0 --pool usdc
|
|
473
|
+
veil subaccount merge --slot 0 --pool eth --json
|
|
474
|
+
```
|
|
475
|
+
|
|
457
476
|
### Recover (direct on-chain — requires WALLET_KEY)
|
|
458
477
|
|
|
459
478
|
Recovery is for assets still sitting on the forwarder after refund or rejection.
|
|
@@ -470,6 +489,7 @@ Important:
|
|
|
470
489
|
- `--asset` is `eth` or `usdc` (case-insensitive in the CLI)
|
|
471
490
|
- `--slot` is `0`–`2`
|
|
472
491
|
- Deploy and sweep only need `VEIL_KEY`
|
|
492
|
+
- Merge only needs `VEIL_KEY`
|
|
473
493
|
- Recover needs both `VEIL_KEY` and `WALLET_KEY`
|
|
474
494
|
|
|
475
495
|
---
|
package/skills/veil/reference.md
CHANGED
|
@@ -148,9 +148,11 @@ const priv = await getPrivateBalance({
|
|
|
148
148
|
```typescript
|
|
149
149
|
import {
|
|
150
150
|
deriveSubaccountSlot,
|
|
151
|
+
getSubaccountPrivateBalance,
|
|
151
152
|
getSubaccountStatus,
|
|
152
153
|
deploySubaccountForwarder,
|
|
153
154
|
sweepSubaccountForwarder,
|
|
155
|
+
mergeSubaccount,
|
|
154
156
|
buildSubaccountRecoveryTx,
|
|
155
157
|
isSubaccountForwarderDeployed,
|
|
156
158
|
MAX_SUBACCOUNT_SLOTS,
|
|
@@ -168,12 +170,20 @@ const deployed = await isSubaccountForwarderDeployed({
|
|
|
168
170
|
forwarderAddress: slot.forwarderAddress,
|
|
169
171
|
});
|
|
170
172
|
|
|
171
|
-
// Full status (deployment, balances, queue state)
|
|
173
|
+
// Full status (deployment, forwarder balances, private balances, queue state)
|
|
172
174
|
const status = await getSubaccountStatus({
|
|
173
175
|
rootPrivateKey: '0xVEIL_KEY',
|
|
174
176
|
slot: 0,
|
|
175
177
|
});
|
|
176
|
-
// status.deployed, status.balances
|
|
178
|
+
// status.deployed, status.balances, status.privateBalances, status.queues
|
|
179
|
+
|
|
180
|
+
// Private pool balance for a single slot + pool
|
|
181
|
+
const privateBalance = await getSubaccountPrivateBalance({
|
|
182
|
+
rootPrivateKey: '0xVEIL_KEY',
|
|
183
|
+
slot: 0,
|
|
184
|
+
pool: 'eth',
|
|
185
|
+
});
|
|
186
|
+
// privateBalance.privateBalance, privateBalance.unspentCount, privateBalance.utxos
|
|
177
187
|
|
|
178
188
|
// Deploy forwarder (relay-backed, no WALLET_KEY needed)
|
|
179
189
|
const deployResult = await deploySubaccountForwarder({
|
|
@@ -188,6 +198,14 @@ const sweepResult = await sweepSubaccountForwarder({
|
|
|
188
198
|
asset: 'eth', // 'eth' | 'usdc'
|
|
189
199
|
});
|
|
190
200
|
|
|
201
|
+
// Merge subaccount's private balance back to main wallet (relay-backed)
|
|
202
|
+
const mergeResult = await mergeSubaccount({
|
|
203
|
+
rootPrivateKey: '0xVEIL_KEY',
|
|
204
|
+
slot: 0,
|
|
205
|
+
pool: 'eth', // 'eth' | 'usdc' (default: 'eth')
|
|
206
|
+
});
|
|
207
|
+
// mergeResult.success, mergeResult.transactionHash, mergeResult.amount, mergeResult.slot, mergeResult.pool
|
|
208
|
+
|
|
191
209
|
// Build recovery transaction (for assets stuck on forwarder)
|
|
192
210
|
const recovery = await buildSubaccountRecoveryTx({
|
|
193
211
|
rootPrivateKey: '0xVEIL_KEY',
|
|
@@ -240,8 +258,8 @@ SIGNER_ADDRESS=0x... veil register --unsigned --force # Unsigned register/change
|
|
|
240
258
|
veil register --unsigned --address 0x... # Unsigned register payload (explicit address)
|
|
241
259
|
veil register --json # Register and output result as JSON
|
|
242
260
|
|
|
243
|
-
veil deposit ETH 0.1 --unsigned
|
|
244
|
-
veil deposit USDC 100 --unsigned
|
|
261
|
+
veil deposit ETH 0.1 --unsigned --address 0x... # Unsigned ETH deposit payload
|
|
262
|
+
SIGNER_ADDRESS=0x... veil deposit USDC 100 --unsigned # Unsigned USDC deposit payload(s)
|
|
245
263
|
veil deposit ETH 0.1 --json # Deposit and output result as JSON
|
|
246
264
|
|
|
247
265
|
veil balance # All pool balances
|
|
@@ -256,12 +274,14 @@ veil balance private --json # Private balance as JSON
|
|
|
256
274
|
veil subaccount derive --slot 0 # Derive slot metadata
|
|
257
275
|
veil subaccount derive --slot 0 --json # Derive as JSON
|
|
258
276
|
veil subaccount address --slot 0 # Print forwarder address
|
|
259
|
-
veil subaccount status --slot 0 # Deployment, balances, queue state
|
|
277
|
+
veil subaccount status --slot 0 # Deployment, forwarder balances, private balances, queue state
|
|
260
278
|
veil subaccount status --slot 0 --json # Status as JSON
|
|
261
279
|
veil subaccount deploy --slot 0 # Deploy forwarder (relay-backed)
|
|
262
280
|
veil subaccount deploy --slot 0 --json # Deploy as JSON
|
|
263
281
|
veil subaccount sweep --slot 0 --asset eth # Sweep ETH into pool (relay-backed)
|
|
264
282
|
veil subaccount sweep --slot 0 --asset usdc --json # Sweep USDC as JSON
|
|
283
|
+
veil subaccount merge --slot 0 --pool eth # Merge subaccount balance to main wallet
|
|
284
|
+
veil subaccount merge --slot 0 --pool usdc --json # Merge USDC as JSON
|
|
265
285
|
veil subaccount recover --slot 0 --asset usdc --to 0x... --amount 25 # Recover assets (needs WALLET_KEY)
|
|
266
286
|
veil subaccount recover --slot 0 --asset eth --to 0x... --amount 0.05 --json
|
|
267
287
|
```
|
|
@@ -287,10 +307,10 @@ Common codes: `VEIL_KEY_MISSING`, `WALLET_KEY_MISSING`, `DEPOSIT_KEY_MISSING`,
|
|
|
287
307
|
|
|
288
308
|
| Asset | Minimum (net) | Notes |
|
|
289
309
|
|-------|--------------|-------|
|
|
290
|
-
| ETH | 0.01 | Fee (0.3%) added automatically
|
|
291
|
-
| USDC | 10 | Fee (0.3%) added automatically
|
|
310
|
+
| ETH | 0.01 | Fee (0.3%) added automatically, or waived if daily free deposits remain |
|
|
311
|
+
| USDC | 10 | Fee (0.3%) added automatically, or waived if daily free deposits remain |
|
|
292
312
|
|
|
293
|
-
The CLI amount is the **net** amount that lands in the pool. The fee is calculated on-chain and added to the transaction automatically
|
|
313
|
+
The CLI amount is the **net** amount that lands in the pool. The CLI checks `getDailyFreeRemaining` on the queue contract — if the user has free slots left today the fee is skipped; otherwise the 0.3% fee is calculated on-chain and added to the transaction automatically.
|
|
294
314
|
|
|
295
315
|
---
|
|
296
316
|
|
package/src/abi.ts
CHANGED
|
@@ -218,6 +218,14 @@ export const QUEUE_ABI = [
|
|
|
218
218
|
stateMutability: 'view',
|
|
219
219
|
type: 'function',
|
|
220
220
|
},
|
|
221
|
+
// Get remaining daily free deposits for an address (V3+)
|
|
222
|
+
{
|
|
223
|
+
inputs: [{ name: '_depositor', type: 'address' }],
|
|
224
|
+
name: 'getDailyFreeRemaining',
|
|
225
|
+
outputs: [{ name: 'remaining', type: 'uint256' }],
|
|
226
|
+
stateMutability: 'view',
|
|
227
|
+
type: 'function',
|
|
228
|
+
},
|
|
221
229
|
] as const;
|
|
222
230
|
|
|
223
231
|
/**
|
package/src/balance.ts
CHANGED
|
@@ -127,6 +127,54 @@ export async function getQueueBalance(options: {
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Get remaining daily free deposits for an address.
|
|
132
|
+
* Returns 0 if the queue contract has not been upgraded to V3 yet
|
|
133
|
+
* or if the daily free feature is disabled.
|
|
134
|
+
*
|
|
135
|
+
* @param options - Query options
|
|
136
|
+
* @param options.address - Depositor address to check
|
|
137
|
+
* @param options.pool - Pool identifier ('eth' or 'usdc', default: 'eth')
|
|
138
|
+
* @param options.rpcUrl - Optional RPC URL
|
|
139
|
+
* @returns Number of free deposits remaining today
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* const remaining = await getDailyFreeRemaining({
|
|
144
|
+
* address: '0x...',
|
|
145
|
+
* pool: 'eth',
|
|
146
|
+
* });
|
|
147
|
+
* console.log(`Free deposits left today: ${remaining}`);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export async function getDailyFreeRemaining(options: {
|
|
151
|
+
address: `0x${string}`;
|
|
152
|
+
pool?: RelayPool;
|
|
153
|
+
rpcUrl?: string;
|
|
154
|
+
}): Promise<number> {
|
|
155
|
+
const { address, pool = 'eth', rpcUrl } = options;
|
|
156
|
+
const queueAddress = getQueueAddress(pool);
|
|
157
|
+
|
|
158
|
+
const publicClient = createPublicClient({
|
|
159
|
+
chain: base,
|
|
160
|
+
transport: http(rpcUrl),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const remaining = await publicClient.readContract({
|
|
165
|
+
address: queueAddress,
|
|
166
|
+
abi: QUEUE_ABI,
|
|
167
|
+
functionName: 'getDailyFreeRemaining',
|
|
168
|
+
args: [address],
|
|
169
|
+
}) as bigint;
|
|
170
|
+
|
|
171
|
+
return Number(remaining);
|
|
172
|
+
} catch {
|
|
173
|
+
// V2 contracts don't have this function — treat as 0 remaining
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
130
178
|
/**
|
|
131
179
|
* Get private balance from the Pool contract
|
|
132
180
|
* Decrypts all encrypted outputs, calculates nullifiers, and checks spent status
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { buildDepositETHTx, buildDepositUSDCTx, buildApproveUSDCTx } from '../../deposit.js';
|
|
7
|
+
import { getDailyFreeRemaining } from '../../balance.js';
|
|
7
8
|
import { sendTransaction, getAddress, getBalance } from '../wallet.js';
|
|
8
|
-
import { getConfig } from '../config.js';
|
|
9
|
+
import { getConfig, resolveAddress } from '../config.js';
|
|
9
10
|
import { createPublicClient, http, parseEther, parseUnits, formatEther, formatUnits } from 'viem';
|
|
10
11
|
import { base } from 'viem/chains';
|
|
11
12
|
import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
|
|
@@ -13,6 +14,7 @@ import { clearProgress, createProgressReporter, printFields, printHeader, printJ
|
|
|
13
14
|
import { POOL_CONFIG, getAddresses } from '../../addresses.js';
|
|
14
15
|
import { ENTRY_ABI, ERC20_ABI } from '../../abi.js';
|
|
15
16
|
import type { TransactionData } from '../../types.js';
|
|
17
|
+
import type { WalletConfig } from '../wallet.js';
|
|
16
18
|
|
|
17
19
|
const MINIMUM_NET: Record<string, number> = {
|
|
18
20
|
ETH: 0.01,
|
|
@@ -20,13 +22,22 @@ const MINIMUM_NET: Record<string, number> = {
|
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
* Compute the gross amount and fee for a deposit.
|
|
26
|
+
* Checks daily free deposit availability first — if the user has
|
|
27
|
+
* free slots remaining the fee is waived and gross === net.
|
|
25
28
|
*/
|
|
26
29
|
async function getGrossAmount(
|
|
27
30
|
netWei: bigint,
|
|
31
|
+
depositor: `0x${string}`,
|
|
32
|
+
pool: 'eth' | 'usdc',
|
|
28
33
|
rpcUrl: string | undefined,
|
|
29
|
-
): Promise<{ grossWei: bigint; feeWei: bigint }> {
|
|
34
|
+
): Promise<{ grossWei: bigint; feeWei: bigint; dailyFreeUsed: boolean; dailyFreeRemaining: number }> {
|
|
35
|
+
const freeRemaining = await getDailyFreeRemaining({ address: depositor, pool, rpcUrl });
|
|
36
|
+
|
|
37
|
+
if (freeRemaining > 0) {
|
|
38
|
+
return { grossWei: netWei, feeWei: 0n, dailyFreeUsed: true, dailyFreeRemaining: freeRemaining - 1 };
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
const publicClient = createPublicClient({
|
|
31
42
|
chain: base,
|
|
32
43
|
transport: http(rpcUrl),
|
|
@@ -39,7 +50,7 @@ async function getGrossAmount(
|
|
|
39
50
|
args: [netWei],
|
|
40
51
|
}) as bigint;
|
|
41
52
|
|
|
42
|
-
return { grossWei, feeWei: grossWei - netWei };
|
|
53
|
+
return { grossWei, feeWei: grossWei - netWei, dailyFreeUsed: false, dailyFreeRemaining: 0 };
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
const SUPPORTED_ASSETS = ['ETH', 'USDC'];
|
|
@@ -49,16 +60,19 @@ export function createDepositCommand(): Command {
|
|
|
49
60
|
.description('Deposit ETH or USDC into Veil')
|
|
50
61
|
.argument('<asset>', 'Asset to deposit (ETH or USDC)')
|
|
51
62
|
.argument('<amount>', 'Amount to deposit — this is what arrives in your Veil balance')
|
|
63
|
+
.option('--address <address>', 'Signer address (required in --unsigned mode unless SIGNER_ADDRESS or WALLET_KEY is set)')
|
|
52
64
|
.option('--unsigned', 'Output unsigned transaction payload instead of sending')
|
|
53
65
|
.option('--json', 'Output as JSON')
|
|
54
66
|
.addHelpText('after', `
|
|
55
67
|
The amount you specify is the net amount that lands in your Veil balance.
|
|
56
|
-
|
|
68
|
+
A 0.3% protocol fee is normally added on top, but each address gets
|
|
69
|
+
free daily deposits (fee waived). The CLI checks automatically.
|
|
57
70
|
|
|
58
71
|
Examples:
|
|
59
|
-
veil deposit ETH 0.1 # deposits 0.1 ETH (
|
|
60
|
-
veil deposit USDC 100 # deposits 100 USDC (
|
|
61
|
-
veil deposit ETH 0.1 --unsigned
|
|
72
|
+
veil deposit ETH 0.1 # deposits 0.1 ETH (free or ~0.1003 ETH)
|
|
73
|
+
veil deposit USDC 100 # deposits 100 USDC (free or ~100.30 USDC)
|
|
74
|
+
veil deposit ETH 0.1 --unsigned --address 0x...
|
|
75
|
+
SIGNER_ADDRESS=0x... veil deposit ETH 0.1 --unsigned
|
|
62
76
|
veil deposit ETH 0.1 --json
|
|
63
77
|
`)
|
|
64
78
|
.action(async (asset: string, amount: string, options) => {
|
|
@@ -80,15 +94,41 @@ Examples:
|
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
const rpcUrl = process.env.RPC_URL;
|
|
83
|
-
const
|
|
97
|
+
const pool = assetUpper.toLowerCase() as 'eth' | 'usdc';
|
|
98
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
84
99
|
const netWei = assetUpper === 'ETH'
|
|
85
100
|
? parseEther(amount)
|
|
86
101
|
: parseUnits(amount, poolConfig.decimals);
|
|
87
102
|
|
|
88
103
|
const progress = createProgressReporter();
|
|
89
|
-
progress('Calculating fee...');
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
let config: WalletConfig | null = null;
|
|
106
|
+
let address: `0x${string}`;
|
|
107
|
+
let feeRpcUrl = rpcUrl;
|
|
108
|
+
|
|
109
|
+
if (options.unsigned) {
|
|
110
|
+
const resolved = resolveAddress({ address: options.address }, { required: true });
|
|
111
|
+
if (!resolved) {
|
|
112
|
+
throw new CLIError(
|
|
113
|
+
ErrorCode.WALLET_KEY_MISSING,
|
|
114
|
+
'Must provide --address, set SIGNER_ADDRESS, or set WALLET_KEY env.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
address = resolved.address;
|
|
118
|
+
} else {
|
|
119
|
+
config = getConfig(options);
|
|
120
|
+
address = getAddress(config.privateKey);
|
|
121
|
+
feeRpcUrl = config.rpcUrl;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
progress('Checking deposit fee...');
|
|
125
|
+
|
|
126
|
+
const { grossWei, feeWei, dailyFreeUsed, dailyFreeRemaining } = await getGrossAmount(
|
|
127
|
+
netWei,
|
|
128
|
+
address,
|
|
129
|
+
pool,
|
|
130
|
+
feeRpcUrl,
|
|
131
|
+
);
|
|
92
132
|
const grossStr = assetUpper === 'ETH'
|
|
93
133
|
? formatEther(grossWei)
|
|
94
134
|
: formatUnits(grossWei, poolConfig.decimals);
|
|
@@ -140,8 +180,9 @@ Examples:
|
|
|
140
180
|
return;
|
|
141
181
|
}
|
|
142
182
|
|
|
143
|
-
|
|
144
|
-
|
|
183
|
+
if (!config) {
|
|
184
|
+
throw new CLIError(ErrorCode.WALLET_KEY_MISSING, 'WALLET_KEY env var required. Set it before running this command.');
|
|
185
|
+
}
|
|
145
186
|
|
|
146
187
|
if (assetUpper === 'ETH') {
|
|
147
188
|
progress('Checking balance...');
|
|
@@ -203,6 +244,7 @@ Examples:
|
|
|
203
244
|
asset: assetUpper,
|
|
204
245
|
amount,
|
|
205
246
|
fee: feeStr,
|
|
247
|
+
dailyFreeUsed,
|
|
206
248
|
totalSent: grossStr,
|
|
207
249
|
blockNumber: result.receipt.blockNumber.toString(),
|
|
208
250
|
};
|
|
@@ -212,11 +254,15 @@ Examples:
|
|
|
212
254
|
return;
|
|
213
255
|
}
|
|
214
256
|
|
|
257
|
+
const feeLabel = dailyFreeUsed
|
|
258
|
+
? `0 ${assetUpper} (free — ${dailyFreeRemaining} remaining today)`
|
|
259
|
+
: `${feeStr} ${assetUpper} (0.3%)`;
|
|
260
|
+
|
|
215
261
|
printHeader('Deposit Submitted');
|
|
216
262
|
printFields([
|
|
217
263
|
{ label: 'Asset', value: assetUpper },
|
|
218
264
|
{ label: 'Amount', value: `${amount} ${assetUpper}` },
|
|
219
|
-
{ label: 'Fee', value:
|
|
265
|
+
{ label: 'Fee', value: feeLabel },
|
|
220
266
|
{ label: 'Total sent', value: `${grossStr} ${assetUpper}` },
|
|
221
267
|
{ label: 'From', value: address },
|
|
222
268
|
{ label: 'Transaction', value: txUrl(result.hash) },
|
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
getSubaccountStatus,
|
|
8
8
|
isSubaccountForwarderDeployed,
|
|
9
9
|
MAX_SUBACCOUNT_SLOTS,
|
|
10
|
+
mergeSubaccount,
|
|
10
11
|
sweepSubaccountForwarder,
|
|
11
12
|
} from '../../subaccount.js';
|
|
12
13
|
import { getConfig } from '../config.js';
|
|
13
14
|
import { CLIError, ErrorCode, handleCLIError } from '../errors.js';
|
|
14
15
|
import { printFields, printHeader, printJson, printLine, printList, printSection, txUrl } from '../output.js';
|
|
15
16
|
import { sendTransaction } from '../wallet.js';
|
|
16
|
-
import type { SubaccountAsset } from '../../types.js';
|
|
17
|
+
import type { SubaccountAsset, RelayPool } from '../../types.js';
|
|
17
18
|
|
|
18
19
|
function parseSlotValue(raw: string): number {
|
|
19
20
|
const normalized = raw.trim();
|
|
@@ -51,6 +52,14 @@ function parseAsset(raw: string): SubaccountAsset {
|
|
|
51
52
|
return asset;
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
function parsePool(raw: string): RelayPool {
|
|
56
|
+
const pool = raw.toLowerCase();
|
|
57
|
+
if (pool !== 'eth' && pool !== 'usdc') {
|
|
58
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported pool: ${raw}. Supported: eth, usdc`);
|
|
59
|
+
}
|
|
60
|
+
return pool;
|
|
61
|
+
}
|
|
62
|
+
|
|
54
63
|
function printQueueHuman(
|
|
55
64
|
title: string,
|
|
56
65
|
queue: {
|
|
@@ -81,6 +90,7 @@ Examples:
|
|
|
81
90
|
veil subaccount status --slot 0
|
|
82
91
|
veil subaccount deploy --slot 0
|
|
83
92
|
veil subaccount sweep --slot 0 --asset eth
|
|
93
|
+
veil subaccount merge --slot 0 --pool eth
|
|
84
94
|
veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
|
|
85
95
|
veil subaccount address --slot 0
|
|
86
96
|
`);
|
|
@@ -130,7 +140,7 @@ Examples:
|
|
|
130
140
|
|
|
131
141
|
subaccount
|
|
132
142
|
.command('status')
|
|
133
|
-
.description('Show subaccount deployment, balances, and queue state')
|
|
143
|
+
.description('Show subaccount deployment, forwarder balances, private balances, and queue state')
|
|
134
144
|
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
135
145
|
.option('--json', 'Output as JSON')
|
|
136
146
|
.action(async (options) => {
|
|
@@ -162,6 +172,18 @@ Examples:
|
|
|
162
172
|
{ label: 'USDC', value: `${status.balances.usdc.balance} USDC` },
|
|
163
173
|
]);
|
|
164
174
|
|
|
175
|
+
printSection('Private Pool Balances');
|
|
176
|
+
printFields([
|
|
177
|
+
{
|
|
178
|
+
label: 'ETH',
|
|
179
|
+
value: `${status.privateBalances.eth.privateBalance} ETH (${status.privateBalances.eth.unspentCount} unspent / ${status.privateBalances.eth.spentCount} spent / ${status.privateBalances.eth.utxoCount} total UTXOs)`,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
label: 'USDC',
|
|
183
|
+
value: `${status.privateBalances.usdc.privateBalance} USDC (${status.privateBalances.usdc.unspentCount} unspent / ${status.privateBalances.usdc.spentCount} spent / ${status.privateBalances.usdc.utxoCount} total UTXOs)`,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
165
187
|
printQueueHuman('ETH Queue', status.queues.eth);
|
|
166
188
|
printQueueHuman('USDC Queue', status.queues.usdc);
|
|
167
189
|
printLine();
|
|
@@ -255,6 +277,64 @@ Examples:
|
|
|
255
277
|
}
|
|
256
278
|
});
|
|
257
279
|
|
|
280
|
+
subaccount
|
|
281
|
+
.command('merge')
|
|
282
|
+
.description('Merge a subaccount\'s private pool balance back to the main wallet')
|
|
283
|
+
.requiredOption('--slot <n>', 'Subaccount slot', parseSlotValue)
|
|
284
|
+
.option('--pool <pool>', 'Pool to merge (eth or usdc)', parsePool, 'eth' as RelayPool)
|
|
285
|
+
.option('--json', 'Output as JSON')
|
|
286
|
+
.action(async (options) => {
|
|
287
|
+
try {
|
|
288
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
289
|
+
const result = await mergeSubaccount({
|
|
290
|
+
rootPrivateKey,
|
|
291
|
+
slot: options.slot,
|
|
292
|
+
pool: options.pool,
|
|
293
|
+
rpcUrl: process.env.RPC_URL,
|
|
294
|
+
relayUrl: process.env.RELAY_URL,
|
|
295
|
+
onProgress: options.json
|
|
296
|
+
? undefined
|
|
297
|
+
: (stage, detail) => {
|
|
298
|
+
const msg = detail ? `${stage} ${detail}` : stage;
|
|
299
|
+
process.stderr.write(`\r\x1b[K${msg}`);
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (!options.json) {
|
|
304
|
+
process.stderr.write('\r\x1b[K');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const output = {
|
|
308
|
+
success: result.success,
|
|
309
|
+
slot: result.slot,
|
|
310
|
+
pool: result.pool,
|
|
311
|
+
amount: result.amount,
|
|
312
|
+
transactionHash: result.transactionHash,
|
|
313
|
+
blockNumber: result.blockNumber,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
if (options.json) {
|
|
317
|
+
printJson(output);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
printHeader('Subaccount Merge Submitted');
|
|
322
|
+
printFields([
|
|
323
|
+
{ label: 'Slot', value: result.slot },
|
|
324
|
+
{ label: 'Pool', value: result.pool.toUpperCase() },
|
|
325
|
+
{ label: 'Amount', value: result.amount },
|
|
326
|
+
{ label: 'Transaction', value: txUrl(result.transactionHash) },
|
|
327
|
+
{ label: 'Block', value: result.blockNumber },
|
|
328
|
+
]);
|
|
329
|
+
printLine();
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (!options.json) {
|
|
332
|
+
process.stderr.write('\r\x1b[K');
|
|
333
|
+
}
|
|
334
|
+
handleCLIError(error);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
258
338
|
subaccount
|
|
259
339
|
.command('recover')
|
|
260
340
|
.description('Recover assets sitting on the subaccount forwarder with a direct withdraw transaction')
|
package/src/cli/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ export {
|
|
|
53
53
|
export {
|
|
54
54
|
getQueueBalance,
|
|
55
55
|
getPrivateBalance,
|
|
56
|
+
getDailyFreeRemaining,
|
|
56
57
|
} from './balance.js';
|
|
57
58
|
export type { ProgressCallback } from './balance.js';
|
|
58
59
|
|
|
@@ -139,12 +140,14 @@ export {
|
|
|
139
140
|
isSubaccountForwarderDeployed,
|
|
140
141
|
deploySubaccountForwarder,
|
|
141
142
|
sweepSubaccountForwarder,
|
|
143
|
+
getSubaccountPrivateBalance,
|
|
142
144
|
getSubaccountStatus,
|
|
143
145
|
buildSubaccountWithdrawTypedData,
|
|
144
146
|
signSubaccountWithdraw,
|
|
145
147
|
isSubaccountWithdrawNonceUsed,
|
|
146
148
|
findNextSubaccountWithdrawNonce,
|
|
147
149
|
buildSubaccountRecoveryTx,
|
|
150
|
+
mergeSubaccount,
|
|
148
151
|
} from './subaccount.js';
|
|
149
152
|
|
|
150
153
|
// Utilities
|
|
@@ -197,8 +200,12 @@ export type {
|
|
|
197
200
|
SubaccountRelayResult,
|
|
198
201
|
SubaccountAssetBalance,
|
|
199
202
|
SubaccountBalances,
|
|
203
|
+
SubaccountPrivateBalanceStatus,
|
|
204
|
+
SubaccountPrivateBalances,
|
|
200
205
|
SubaccountQueueStatus,
|
|
201
206
|
SubaccountStatusResult,
|
|
202
207
|
SubaccountWithdrawTypedData,
|
|
203
208
|
SubaccountRecoveryResult,
|
|
209
|
+
SubaccountMergeOptions,
|
|
210
|
+
SubaccountMergeResult,
|
|
204
211
|
} from './types.js';
|