@veil-cash/sdk 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -38
- package/SDK.md +20 -2
- package/dist/cli/index.cjs +257 -13
- package/dist/index.cjs +185 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -2
- package/dist/index.d.ts +84 -2
- package/dist/index.js +184 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/veil/SKILL.md +93 -3
- package/skills/veil/reference.md +91 -2
- package/src/cli/commands/subaccount.ts +90 -9
- package/src/cli/index.ts +1 -1
- package/src/index.ts +6 -0
- package/src/relay.ts +10 -1
- package/src/subaccount.ts +252 -8
- package/src/types.ts +56 -0
package/package.json
CHANGED
package/skills/veil/SKILL.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: veil
|
|
3
|
-
version: 0.
|
|
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
|
-
manage Veil keypairs, register on-chain,
|
|
8
|
-
|
|
7
|
+
manage Veil keypairs, register on-chain, manage deterministic subaccounts
|
|
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).
|
|
9
11
|
author: veildotcash
|
|
10
12
|
metadata:
|
|
11
13
|
homepage: https://veil.cash
|
|
@@ -27,11 +29,16 @@ triggers:
|
|
|
27
29
|
- pattern: veil withdraw
|
|
28
30
|
- pattern: veil transfer
|
|
29
31
|
- pattern: veil merge
|
|
32
|
+
- pattern: veil subaccount
|
|
30
33
|
- pattern: unsigned payload
|
|
31
34
|
- pattern: privacy pool
|
|
32
35
|
- pattern: deposit privately
|
|
33
36
|
- pattern: withdraw privately
|
|
34
37
|
- pattern: private transfer
|
|
38
|
+
- pattern: subaccount
|
|
39
|
+
- pattern: subaccount merge
|
|
40
|
+
- pattern: forwarder
|
|
41
|
+
- pattern: stealth deposit
|
|
35
42
|
---
|
|
36
43
|
|
|
37
44
|
# Veil CLI
|
|
@@ -164,6 +171,8 @@ What do you want to do?
|
|
|
164
171
|
|
|
|
165
172
|
+-- Withdraw / transfer / merge → Section 5
|
|
166
173
|
|
|
|
174
|
+
+-- Subaccounts (forwarders) → Section 5B
|
|
175
|
+
|
|
|
167
176
|
+-- Inspect or rotate keypair → veil keypair / veil init --force
|
|
168
177
|
```
|
|
169
178
|
|
|
@@ -188,6 +197,13 @@ What do you want to do?
|
|
|
188
197
|
| Withdraw | `veil withdraw ETH 0.05 0xRecipient` |
|
|
189
198
|
| Transfer privately | `veil transfer ETH 0.02 0xRecipient` |
|
|
190
199
|
| Merge UTXOs | `veil merge ETH 0.1` |
|
|
200
|
+
| Derive subaccount | `veil subaccount derive --slot 0` |
|
|
201
|
+
| Subaccount status | `veil subaccount status --slot 0` |
|
|
202
|
+
| Subaccount address | `veil subaccount address --slot 0` |
|
|
203
|
+
| Deploy forwarder | `veil subaccount deploy --slot 0` |
|
|
204
|
+
| Sweep forwarder | `veil subaccount sweep --slot 0 --asset eth` |
|
|
205
|
+
| Merge subaccount to main | `veil subaccount merge --slot 0 --pool eth` |
|
|
206
|
+
| Recover from forwarder | `veil subaccount recover --slot 0 --asset usdc --to 0xAddr --amount 25` |
|
|
191
207
|
|
|
192
208
|
---
|
|
193
209
|
|
|
@@ -315,6 +331,8 @@ Important:
|
|
|
315
331
|
|
|
316
332
|
Deposits treat the CLI amount as the **net** amount that lands in the pool.
|
|
317
333
|
The `0.3%` protocol fee is calculated on-chain and added automatically.
|
|
334
|
+
After submission, deposits go through screening / queue processing before they
|
|
335
|
+
are accepted into the private pool. This typically takes around `10-15 minutes`.
|
|
318
336
|
|
|
319
337
|
```bash
|
|
320
338
|
veil deposit ETH 0.1
|
|
@@ -369,6 +387,9 @@ Human-readable balance output includes:
|
|
|
369
387
|
- wallet public balances (`ETH`, `USDC`)
|
|
370
388
|
- queue and private balances
|
|
371
389
|
|
|
390
|
+
If a recent deposit still appears in queue balance, screening / queue processing
|
|
391
|
+
may still be in progress. Typical processing time is around `10-15 minutes`.
|
|
392
|
+
|
|
372
393
|
---
|
|
373
394
|
|
|
374
395
|
## 5. Private Actions
|
|
@@ -403,6 +424,74 @@ Note: withdraw proof generation is single-threaded for reliable CLI exit after s
|
|
|
403
424
|
|
|
404
425
|
---
|
|
405
426
|
|
|
427
|
+
## 5B. Subaccounts
|
|
428
|
+
|
|
429
|
+
Subaccounts are deterministic child slots derived from your main `VEIL_KEY`:
|
|
430
|
+
|
|
431
|
+
`root key → slot → child key → child deposit key → forwarder`
|
|
432
|
+
|
|
433
|
+
Base mainnet only. Slots are `0`–`2` (max 3 subaccounts). Deploy and sweep are
|
|
434
|
+
relay-backed (no `WALLET_KEY` needed). Merge transfers the subaccount's private
|
|
435
|
+
pool balance back to the main wallet via a ZK proof (relay-backed, no `WALLET_KEY`
|
|
436
|
+
needed). Recovery submits a direct on-chain transaction and **requires `WALLET_KEY`**
|
|
437
|
+
as a gas payer.
|
|
438
|
+
|
|
439
|
+
Status reports the child slot's forwarder wallet balances, private pool
|
|
440
|
+
balances, and queue state.
|
|
441
|
+
|
|
442
|
+
### Derive and inspect
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
veil subaccount derive --slot 0 # Full slot metadata
|
|
446
|
+
veil subaccount derive --slot 0 --json
|
|
447
|
+
veil subaccount address --slot 0 # Just the forwarder address
|
|
448
|
+
veil subaccount status --slot 0 # Deployment, forwarder balances, private balances, queue state
|
|
449
|
+
veil subaccount status --slot 0 --json
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Deploy and sweep (relay-backed)
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
veil subaccount deploy --slot 0 # Deploy the forwarder contract
|
|
456
|
+
veil subaccount deploy --slot 0 --json
|
|
457
|
+
veil subaccount sweep --slot 0 --asset eth # Sweep ETH into the pool
|
|
458
|
+
veil subaccount sweep --slot 0 --asset usdc # Sweep USDC into the pool
|
|
459
|
+
veil subaccount sweep --slot 0 --asset eth --json
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Merge subaccount to main wallet (relay-backed)
|
|
463
|
+
|
|
464
|
+
Merge transfers the subaccount's entire private pool balance back to the main
|
|
465
|
+
wallet. It builds a ZK proof transferring child UTXOs to the parent keypair and
|
|
466
|
+
submits via the relay. Only needs `VEIL_KEY`.
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
veil subaccount merge --slot 0 --pool eth
|
|
470
|
+
veil subaccount merge --slot 0 --pool usdc
|
|
471
|
+
veil subaccount merge --slot 0 --pool eth --json
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Recover (direct on-chain — requires WALLET_KEY)
|
|
475
|
+
|
|
476
|
+
Recovery is for assets still sitting on the forwarder after refund or rejection.
|
|
477
|
+
It signs a forwarder withdraw with the child key and submits the transaction
|
|
478
|
+
using `WALLET_KEY` as the gas payer.
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
veil subaccount recover --slot 0 --asset usdc --to 0xRecipient --amount 25
|
|
482
|
+
veil subaccount recover --slot 0 --asset eth --to 0xRecipient --amount 0.05 --json
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Important:
|
|
486
|
+
|
|
487
|
+
- `--asset` is `eth` or `usdc` (case-insensitive in the CLI)
|
|
488
|
+
- `--slot` is `0`–`2`
|
|
489
|
+
- Deploy and sweep only need `VEIL_KEY`
|
|
490
|
+
- Merge only needs `VEIL_KEY`
|
|
491
|
+
- Recover needs both `VEIL_KEY` and `WALLET_KEY`
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
406
495
|
## 6. Unsigned Payloads
|
|
407
496
|
|
|
408
497
|
`--unsigned` is for external signer workflows. The CLI emits a signer-compatible
|
|
@@ -504,6 +593,7 @@ All CLI errors output JSON with a standardised `errorCode`:
|
|
|
504
593
|
| `DEPOSIT_KEY_MISSING` | `DEPOSIT_KEY` missing from `.env.veil` | Re-run `veil init` to regenerate |
|
|
505
594
|
| `USER_NOT_REGISTERED` | Transfer recipient has no deposit key registered on-chain | Recipient must run `veil register` first |
|
|
506
595
|
| `INVALID_AMOUNT` | Amount below minimum or invalid format | ETH min: `0.01`, USDC min: `10` |
|
|
596
|
+
| `INVALID_SLOT` | Invalid subaccount slot | Slot must be `0`–`2` (non-negative integer) |
|
|
507
597
|
| `INSUFFICIENT_BALANCE` | Not enough ETH for gas | Top up Base ETH balance |
|
|
508
598
|
| `RPC_ERROR` | Network or RPC failure | Check `RPC_URL` env var or retry |
|
|
509
599
|
| `RELAY_ERROR` | Relayer rejected the proof | Check relay health with `veil status`; retry |
|
package/skills/veil/reference.md
CHANGED
|
@@ -143,6 +143,81 @@ const priv = await getPrivateBalance({
|
|
|
143
143
|
});
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
### Subaccounts
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import {
|
|
150
|
+
deriveSubaccountSlot,
|
|
151
|
+
getSubaccountPrivateBalance,
|
|
152
|
+
getSubaccountStatus,
|
|
153
|
+
deploySubaccountForwarder,
|
|
154
|
+
sweepSubaccountForwarder,
|
|
155
|
+
mergeSubaccount,
|
|
156
|
+
buildSubaccountRecoveryTx,
|
|
157
|
+
isSubaccountForwarderDeployed,
|
|
158
|
+
MAX_SUBACCOUNT_SLOTS,
|
|
159
|
+
} from '@veil-cash/sdk';
|
|
160
|
+
|
|
161
|
+
// Derive slot metadata (child key, salt, predicted forwarder address)
|
|
162
|
+
const slot = await deriveSubaccountSlot({
|
|
163
|
+
rootPrivateKey: '0xVEIL_KEY',
|
|
164
|
+
slot: 0, // 0–2
|
|
165
|
+
});
|
|
166
|
+
// slot.forwarderAddress, slot.childOwner, slot.childDepositKey, slot.salt
|
|
167
|
+
|
|
168
|
+
// Check deployment status
|
|
169
|
+
const deployed = await isSubaccountForwarderDeployed({
|
|
170
|
+
forwarderAddress: slot.forwarderAddress,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Full status (deployment, forwarder balances, private balances, queue state)
|
|
174
|
+
const status = await getSubaccountStatus({
|
|
175
|
+
rootPrivateKey: '0xVEIL_KEY',
|
|
176
|
+
slot: 0,
|
|
177
|
+
});
|
|
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
|
|
187
|
+
|
|
188
|
+
// Deploy forwarder (relay-backed, no WALLET_KEY needed)
|
|
189
|
+
const deployResult = await deploySubaccountForwarder({
|
|
190
|
+
rootPrivateKey: '0xVEIL_KEY',
|
|
191
|
+
slot: 0,
|
|
192
|
+
});
|
|
193
|
+
// deployResult.transactionHash, deployResult.slot.forwarderAddress
|
|
194
|
+
|
|
195
|
+
// Sweep assets into pool (relay-backed)
|
|
196
|
+
const sweepResult = await sweepSubaccountForwarder({
|
|
197
|
+
forwarderAddress: slot.forwarderAddress,
|
|
198
|
+
asset: 'eth', // 'eth' | 'usdc'
|
|
199
|
+
});
|
|
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
|
+
|
|
209
|
+
// Build recovery transaction (for assets stuck on forwarder)
|
|
210
|
+
const recovery = await buildSubaccountRecoveryTx({
|
|
211
|
+
rootPrivateKey: '0xVEIL_KEY',
|
|
212
|
+
slot: 0,
|
|
213
|
+
asset: 'usdc',
|
|
214
|
+
to: '0xRecipient',
|
|
215
|
+
amount: '25',
|
|
216
|
+
});
|
|
217
|
+
// recovery.transaction — submit with your wallet client
|
|
218
|
+
// recovery.forwarderAddress, recovery.signature, recovery.nonce, recovery.deadline
|
|
219
|
+
```
|
|
220
|
+
|
|
146
221
|
---
|
|
147
222
|
|
|
148
223
|
## CLI quick reference
|
|
@@ -158,7 +233,7 @@ Install globally: `npm install -g @veil-cash/sdk`
|
|
|
158
233
|
| `WALLET_KEY` | Ethereum wallet private key (for signing) |
|
|
159
234
|
| `SIGNER_ADDRESS` | Ethereum address for unsigned/query flows when signing is external |
|
|
160
235
|
| `RPC_URL` | Base RPC URL (optional, defaults to public RPC) |
|
|
161
|
-
| `RELAY_URL` | Override relay base URL for relayed CLI operations |
|
|
236
|
+
| `RELAY_URL` | Override relay base URL for relayed CLI operations, subaccount deploy/sweep, and status checks |
|
|
162
237
|
|
|
163
238
|
`WALLET_KEY` and `SIGNER_ADDRESS` are mutually exclusive. Use `SIGNER_ADDRESS` only for address-only CLI flows.
|
|
164
239
|
|
|
@@ -195,6 +270,20 @@ veil balance queue --pool eth # Queue-only balance
|
|
|
195
270
|
veil balance queue --address 0x... --json # Queue balance for explicit address
|
|
196
271
|
veil balance private --pool eth # Private-only balance
|
|
197
272
|
veil balance private --json # Private balance as JSON
|
|
273
|
+
|
|
274
|
+
veil subaccount derive --slot 0 # Derive slot metadata
|
|
275
|
+
veil subaccount derive --slot 0 --json # Derive as JSON
|
|
276
|
+
veil subaccount address --slot 0 # Print forwarder address
|
|
277
|
+
veil subaccount status --slot 0 # Deployment, forwarder balances, private balances, queue state
|
|
278
|
+
veil subaccount status --slot 0 --json # Status as JSON
|
|
279
|
+
veil subaccount deploy --slot 0 # Deploy forwarder (relay-backed)
|
|
280
|
+
veil subaccount deploy --slot 0 --json # Deploy as JSON
|
|
281
|
+
veil subaccount sweep --slot 0 --asset eth # Sweep ETH into pool (relay-backed)
|
|
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
|
|
285
|
+
veil subaccount recover --slot 0 --asset usdc --to 0x... --amount 25 # Recover assets (needs WALLET_KEY)
|
|
286
|
+
veil subaccount recover --slot 0 --asset eth --to 0x... --amount 0.05 --json
|
|
198
287
|
```
|
|
199
288
|
|
|
200
289
|
### Error format
|
|
@@ -210,7 +299,7 @@ All CLI errors output JSON with a standardized `errorCode`:
|
|
|
210
299
|
```
|
|
211
300
|
|
|
212
301
|
Common codes: `VEIL_KEY_MISSING`, `WALLET_KEY_MISSING`, `DEPOSIT_KEY_MISSING`,
|
|
213
|
-
`CONFIG_CONFLICT`, `INVALID_AMOUNT`, `INSUFFICIENT_BALANCE`, `CONTRACT_ERROR`, `RPC_ERROR`.
|
|
302
|
+
`CONFIG_CONFLICT`, `INVALID_AMOUNT`, `INVALID_SLOT`, `INSUFFICIENT_BALANCE`, `CONTRACT_ERROR`, `RPC_ERROR`.
|
|
214
303
|
|
|
215
304
|
---
|
|
216
305
|
|
|
@@ -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();
|
|
@@ -178,11 +200,6 @@ Examples:
|
|
|
178
200
|
.action(async (options) => {
|
|
179
201
|
try {
|
|
180
202
|
const rootPrivateKey = getRequiredVeilKey();
|
|
181
|
-
const slot = await deriveSubaccountSlot({
|
|
182
|
-
rootPrivateKey,
|
|
183
|
-
slot: options.slot,
|
|
184
|
-
rpcUrl: process.env.RPC_URL,
|
|
185
|
-
});
|
|
186
203
|
const result = await deploySubaccountForwarder({
|
|
187
204
|
rootPrivateKey,
|
|
188
205
|
slot: options.slot,
|
|
@@ -193,7 +210,7 @@ Examples:
|
|
|
193
210
|
const output = {
|
|
194
211
|
...result,
|
|
195
212
|
slot: options.slot,
|
|
196
|
-
forwarderAddress: slot.forwarderAddress,
|
|
213
|
+
forwarderAddress: result.slot.forwarderAddress,
|
|
197
214
|
};
|
|
198
215
|
|
|
199
216
|
if (options.json) {
|
|
@@ -204,7 +221,7 @@ Examples:
|
|
|
204
221
|
printHeader('Subaccount Deploy Submitted');
|
|
205
222
|
printFields([
|
|
206
223
|
{ label: 'Slot', value: options.slot },
|
|
207
|
-
{ label: 'Forwarder', value: slot.forwarderAddress },
|
|
224
|
+
{ label: 'Forwarder', value: result.slot.forwarderAddress },
|
|
208
225
|
{ label: 'Transaction', value: txUrl(result.transactionHash) },
|
|
209
226
|
{ label: 'Block', value: result.blockNumber },
|
|
210
227
|
]);
|
|
@@ -260,6 +277,64 @@ Examples:
|
|
|
260
277
|
}
|
|
261
278
|
});
|
|
262
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
|
+
|
|
263
338
|
subaccount
|
|
264
339
|
.command('recover')
|
|
265
340
|
.description('Recover assets sitting on the subaccount forwarder with a direct withdraw transaction')
|
|
@@ -274,6 +349,12 @@ Examples:
|
|
|
274
349
|
if (!isAddress(options.to)) {
|
|
275
350
|
throw new CLIError(ErrorCode.INVALID_ADDRESS, `Invalid recipient address: ${options.to}`);
|
|
276
351
|
}
|
|
352
|
+
if (!process.env.WALLET_KEY) {
|
|
353
|
+
throw new CLIError(
|
|
354
|
+
ErrorCode.WALLET_KEY_MISSING,
|
|
355
|
+
'WALLET_KEY required for recovery. Recovery submits a transaction on-chain and needs a gas payer.',
|
|
356
|
+
);
|
|
357
|
+
}
|
|
277
358
|
const config = getConfig({});
|
|
278
359
|
const recovery = await buildSubaccountRecoveryTx({
|
|
279
360
|
rootPrivateKey,
|
package/src/cli/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -139,12 +139,14 @@ export {
|
|
|
139
139
|
isSubaccountForwarderDeployed,
|
|
140
140
|
deploySubaccountForwarder,
|
|
141
141
|
sweepSubaccountForwarder,
|
|
142
|
+
getSubaccountPrivateBalance,
|
|
142
143
|
getSubaccountStatus,
|
|
143
144
|
buildSubaccountWithdrawTypedData,
|
|
144
145
|
signSubaccountWithdraw,
|
|
145
146
|
isSubaccountWithdrawNonceUsed,
|
|
146
147
|
findNextSubaccountWithdrawNonce,
|
|
147
148
|
buildSubaccountRecoveryTx,
|
|
149
|
+
mergeSubaccount,
|
|
148
150
|
} from './subaccount.js';
|
|
149
151
|
|
|
150
152
|
// Utilities
|
|
@@ -197,8 +199,12 @@ export type {
|
|
|
197
199
|
SubaccountRelayResult,
|
|
198
200
|
SubaccountAssetBalance,
|
|
199
201
|
SubaccountBalances,
|
|
202
|
+
SubaccountPrivateBalanceStatus,
|
|
203
|
+
SubaccountPrivateBalances,
|
|
200
204
|
SubaccountQueueStatus,
|
|
201
205
|
SubaccountStatusResult,
|
|
202
206
|
SubaccountWithdrawTypedData,
|
|
203
207
|
SubaccountRecoveryResult,
|
|
208
|
+
SubaccountMergeOptions,
|
|
209
|
+
SubaccountMergeResult,
|
|
204
210
|
} from './types.js';
|
package/src/relay.ts
CHANGED
|
@@ -64,7 +64,16 @@ export async function postRelayJson<T>(
|
|
|
64
64
|
body: JSON.stringify(body),
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
const
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
let data: unknown;
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(text);
|
|
71
|
+
} catch {
|
|
72
|
+
throw new RelayError(
|
|
73
|
+
`Relay returned non-JSON response (HTTP ${response.status})`,
|
|
74
|
+
response.status,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
68
77
|
|
|
69
78
|
if (!response.ok) {
|
|
70
79
|
const errorData = data as RelayErrorResponse;
|