@veil-cash/sdk 0.5.0 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veil-cash/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "SDK and CLI for interacting with Veil Cash privacy pools - keypair generation, deposits, and status checking",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,10 +1,11 @@
1
1
  ---
2
2
  name: veil
3
- version: 0.5.0
3
+ version: 0.6.0
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, or build unsigned transaction payloads
7
+ manage Veil keypairs, register on-chain, manage deterministic subaccounts
8
+ (forwarder deploy, sweep, recover), or build unsigned transaction payloads
8
9
  for an external signer (e.g. Bankr). All operations target Base (chain ID 8453).
9
10
  author: veildotcash
10
11
  metadata:
@@ -27,11 +28,15 @@ triggers:
27
28
  - pattern: veil withdraw
28
29
  - pattern: veil transfer
29
30
  - pattern: veil merge
31
+ - pattern: veil subaccount
30
32
  - pattern: unsigned payload
31
33
  - pattern: privacy pool
32
34
  - pattern: deposit privately
33
35
  - pattern: withdraw privately
34
36
  - pattern: private transfer
37
+ - pattern: subaccount
38
+ - pattern: forwarder
39
+ - pattern: stealth deposit
35
40
  ---
36
41
 
37
42
  # Veil CLI
@@ -164,6 +169,8 @@ What do you want to do?
164
169
  |
165
170
  +-- Withdraw / transfer / merge → Section 5
166
171
  |
172
+ +-- Subaccounts (forwarders) → Section 5B
173
+ |
167
174
  +-- Inspect or rotate keypair → veil keypair / veil init --force
168
175
  ```
169
176
 
@@ -188,6 +195,12 @@ What do you want to do?
188
195
  | Withdraw | `veil withdraw ETH 0.05 0xRecipient` |
189
196
  | Transfer privately | `veil transfer ETH 0.02 0xRecipient` |
190
197
  | Merge UTXOs | `veil merge ETH 0.1` |
198
+ | Derive subaccount | `veil subaccount derive --slot 0` |
199
+ | Subaccount status | `veil subaccount status --slot 0` |
200
+ | Subaccount address | `veil subaccount address --slot 0` |
201
+ | Deploy forwarder | `veil subaccount deploy --slot 0` |
202
+ | Sweep forwarder | `veil subaccount sweep --slot 0 --asset eth` |
203
+ | Recover from forwarder | `veil subaccount recover --slot 0 --asset usdc --to 0xAddr --amount 25` |
191
204
 
192
205
  ---
193
206
 
@@ -315,6 +328,8 @@ Important:
315
328
 
316
329
  Deposits treat the CLI amount as the **net** amount that lands in the pool.
317
330
  The `0.3%` protocol fee is calculated on-chain and added automatically.
331
+ After submission, deposits go through screening / queue processing before they
332
+ are accepted into the private pool. This typically takes around `10-15 minutes`.
318
333
 
319
334
  ```bash
320
335
  veil deposit ETH 0.1
@@ -369,6 +384,9 @@ Human-readable balance output includes:
369
384
  - wallet public balances (`ETH`, `USDC`)
370
385
  - queue and private balances
371
386
 
387
+ If a recent deposit still appears in queue balance, screening / queue processing
388
+ may still be in progress. Typical processing time is around `10-15 minutes`.
389
+
372
390
  ---
373
391
 
374
392
  ## 5. Private Actions
@@ -403,6 +421,59 @@ Note: withdraw proof generation is single-threaded for reliable CLI exit after s
403
421
 
404
422
  ---
405
423
 
424
+ ## 5B. Subaccounts
425
+
426
+ Subaccounts are deterministic child slots derived from your main `VEIL_KEY`:
427
+
428
+ `root key → slot → child key → child deposit key → forwarder`
429
+
430
+ Base mainnet only. Slots are `0`–`2` (max 3 subaccounts). Deploy and sweep are
431
+ relay-backed (no `WALLET_KEY` needed). Recovery submits a direct on-chain
432
+ transaction and **requires `WALLET_KEY`** as a gas payer.
433
+
434
+ Status reports the forwarder wallet balances and queue state only, not private
435
+ pool attribution after queued funds are accepted.
436
+
437
+ ### Derive and inspect
438
+
439
+ ```bash
440
+ veil subaccount derive --slot 0 # Full slot metadata
441
+ veil subaccount derive --slot 0 --json
442
+ veil subaccount address --slot 0 # Just the forwarder address
443
+ veil subaccount status --slot 0 # Deployment, balances, queue state
444
+ veil subaccount status --slot 0 --json
445
+ ```
446
+
447
+ ### Deploy and sweep (relay-backed)
448
+
449
+ ```bash
450
+ veil subaccount deploy --slot 0 # Deploy the forwarder contract
451
+ veil subaccount deploy --slot 0 --json
452
+ veil subaccount sweep --slot 0 --asset eth # Sweep ETH into the pool
453
+ veil subaccount sweep --slot 0 --asset usdc # Sweep USDC into the pool
454
+ veil subaccount sweep --slot 0 --asset eth --json
455
+ ```
456
+
457
+ ### Recover (direct on-chain — requires WALLET_KEY)
458
+
459
+ Recovery is for assets still sitting on the forwarder after refund or rejection.
460
+ It signs a forwarder withdraw with the child key and submits the transaction
461
+ using `WALLET_KEY` as the gas payer.
462
+
463
+ ```bash
464
+ veil subaccount recover --slot 0 --asset usdc --to 0xRecipient --amount 25
465
+ veil subaccount recover --slot 0 --asset eth --to 0xRecipient --amount 0.05 --json
466
+ ```
467
+
468
+ Important:
469
+
470
+ - `--asset` is `eth` or `usdc` (case-insensitive in the CLI)
471
+ - `--slot` is `0`–`2`
472
+ - Deploy and sweep only need `VEIL_KEY`
473
+ - Recover needs both `VEIL_KEY` and `WALLET_KEY`
474
+
475
+ ---
476
+
406
477
  ## 6. Unsigned Payloads
407
478
 
408
479
  `--unsigned` is for external signer workflows. The CLI emits a signer-compatible
@@ -504,6 +575,7 @@ All CLI errors output JSON with a standardised `errorCode`:
504
575
  | `DEPOSIT_KEY_MISSING` | `DEPOSIT_KEY` missing from `.env.veil` | Re-run `veil init` to regenerate |
505
576
  | `USER_NOT_REGISTERED` | Transfer recipient has no deposit key registered on-chain | Recipient must run `veil register` first |
506
577
  | `INVALID_AMOUNT` | Amount below minimum or invalid format | ETH min: `0.01`, USDC min: `10` |
578
+ | `INVALID_SLOT` | Invalid subaccount slot | Slot must be `0`–`2` (non-negative integer) |
507
579
  | `INSUFFICIENT_BALANCE` | Not enough ETH for gas | Top up Base ETH balance |
508
580
  | `RPC_ERROR` | Network or RPC failure | Check `RPC_URL` env var or retry |
509
581
  | `RELAY_ERROR` | Relayer rejected the proof | Check relay health with `veil status`; retry |
@@ -143,6 +143,63 @@ const priv = await getPrivateBalance({
143
143
  });
144
144
  ```
145
145
 
146
+ ### Subaccounts
147
+
148
+ ```typescript
149
+ import {
150
+ deriveSubaccountSlot,
151
+ getSubaccountStatus,
152
+ deploySubaccountForwarder,
153
+ sweepSubaccountForwarder,
154
+ buildSubaccountRecoveryTx,
155
+ isSubaccountForwarderDeployed,
156
+ MAX_SUBACCOUNT_SLOTS,
157
+ } from '@veil-cash/sdk';
158
+
159
+ // Derive slot metadata (child key, salt, predicted forwarder address)
160
+ const slot = await deriveSubaccountSlot({
161
+ rootPrivateKey: '0xVEIL_KEY',
162
+ slot: 0, // 0–2
163
+ });
164
+ // slot.forwarderAddress, slot.childOwner, slot.childDepositKey, slot.salt
165
+
166
+ // Check deployment status
167
+ const deployed = await isSubaccountForwarderDeployed({
168
+ forwarderAddress: slot.forwarderAddress,
169
+ });
170
+
171
+ // Full status (deployment, balances, queue state)
172
+ const status = await getSubaccountStatus({
173
+ rootPrivateKey: '0xVEIL_KEY',
174
+ slot: 0,
175
+ });
176
+ // status.deployed, status.balances.eth, status.balances.usdc, status.queues
177
+
178
+ // Deploy forwarder (relay-backed, no WALLET_KEY needed)
179
+ const deployResult = await deploySubaccountForwarder({
180
+ rootPrivateKey: '0xVEIL_KEY',
181
+ slot: 0,
182
+ });
183
+ // deployResult.transactionHash, deployResult.slot.forwarderAddress
184
+
185
+ // Sweep assets into pool (relay-backed)
186
+ const sweepResult = await sweepSubaccountForwarder({
187
+ forwarderAddress: slot.forwarderAddress,
188
+ asset: 'eth', // 'eth' | 'usdc'
189
+ });
190
+
191
+ // Build recovery transaction (for assets stuck on forwarder)
192
+ const recovery = await buildSubaccountRecoveryTx({
193
+ rootPrivateKey: '0xVEIL_KEY',
194
+ slot: 0,
195
+ asset: 'usdc',
196
+ to: '0xRecipient',
197
+ amount: '25',
198
+ });
199
+ // recovery.transaction — submit with your wallet client
200
+ // recovery.forwarderAddress, recovery.signature, recovery.nonce, recovery.deadline
201
+ ```
202
+
146
203
  ---
147
204
 
148
205
  ## CLI quick reference
@@ -158,7 +215,7 @@ Install globally: `npm install -g @veil-cash/sdk`
158
215
  | `WALLET_KEY` | Ethereum wallet private key (for signing) |
159
216
  | `SIGNER_ADDRESS` | Ethereum address for unsigned/query flows when signing is external |
160
217
  | `RPC_URL` | Base RPC URL (optional, defaults to public RPC) |
161
- | `RELAY_URL` | Override relay base URL for relayed CLI operations |
218
+ | `RELAY_URL` | Override relay base URL for relayed CLI operations, subaccount deploy/sweep, and status checks |
162
219
 
163
220
  `WALLET_KEY` and `SIGNER_ADDRESS` are mutually exclusive. Use `SIGNER_ADDRESS` only for address-only CLI flows.
164
221
 
@@ -195,6 +252,18 @@ veil balance queue --pool eth # Queue-only balance
195
252
  veil balance queue --address 0x... --json # Queue balance for explicit address
196
253
  veil balance private --pool eth # Private-only balance
197
254
  veil balance private --json # Private balance as JSON
255
+
256
+ veil subaccount derive --slot 0 # Derive slot metadata
257
+ veil subaccount derive --slot 0 --json # Derive as JSON
258
+ veil subaccount address --slot 0 # Print forwarder address
259
+ veil subaccount status --slot 0 # Deployment, balances, queue state
260
+ veil subaccount status --slot 0 --json # Status as JSON
261
+ veil subaccount deploy --slot 0 # Deploy forwarder (relay-backed)
262
+ veil subaccount deploy --slot 0 --json # Deploy as JSON
263
+ veil subaccount sweep --slot 0 --asset eth # Sweep ETH into pool (relay-backed)
264
+ veil subaccount sweep --slot 0 --asset usdc --json # Sweep USDC as JSON
265
+ veil subaccount recover --slot 0 --asset usdc --to 0x... --amount 25 # Recover assets (needs WALLET_KEY)
266
+ veil subaccount recover --slot 0 --asset eth --to 0x... --amount 0.05 --json
198
267
  ```
199
268
 
200
269
  ### Error format
@@ -210,7 +279,7 @@ All CLI errors output JSON with a standardized `errorCode`:
210
279
  ```
211
280
 
212
281
  Common codes: `VEIL_KEY_MISSING`, `WALLET_KEY_MISSING`, `DEPOSIT_KEY_MISSING`,
213
- `CONFIG_CONFLICT`, `INVALID_AMOUNT`, `INSUFFICIENT_BALANCE`, `CONTRACT_ERROR`, `RPC_ERROR`.
282
+ `CONFIG_CONFLICT`, `INVALID_AMOUNT`, `INVALID_SLOT`, `INSUFFICIENT_BALANCE`, `CONTRACT_ERROR`, `RPC_ERROR`.
214
283
 
215
284
  ---
216
285
 
package/src/abi.ts CHANGED
@@ -647,3 +647,175 @@ export const ERC20_ABI = [
647
647
  type: 'function',
648
648
  },
649
649
  ] as const;
650
+
651
+ /**
652
+ * Veil Forwarder Factory ABI
653
+ */
654
+ export const FORWARDER_FACTORY_ABI = [
655
+ {
656
+ inputs: [],
657
+ name: 'CONTRACT_VERSION',
658
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
659
+ stateMutability: 'view',
660
+ type: 'function',
661
+ },
662
+ {
663
+ inputs: [
664
+ { internalType: 'bytes32', name: '_salt', type: 'bytes32' },
665
+ { internalType: 'bytes', name: '_childDepositKey', type: 'bytes' },
666
+ { internalType: 'address', name: '_owner', type: 'address' },
667
+ ],
668
+ name: 'computeAddress',
669
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
670
+ stateMutability: 'view',
671
+ type: 'function',
672
+ },
673
+ {
674
+ inputs: [
675
+ { internalType: 'bytes32', name: '_salt', type: 'bytes32' },
676
+ { internalType: 'bytes', name: '_childDepositKey', type: 'bytes' },
677
+ { internalType: 'address', name: '_owner', type: 'address' },
678
+ ],
679
+ name: 'deploy',
680
+ outputs: [{ internalType: 'address', name: 'forwarder', type: 'address' }],
681
+ stateMutability: 'nonpayable',
682
+ type: 'function',
683
+ },
684
+ {
685
+ inputs: [],
686
+ name: 'relayer',
687
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
688
+ stateMutability: 'view',
689
+ type: 'function',
690
+ },
691
+ {
692
+ inputs: [],
693
+ name: 'veilEntry',
694
+ outputs: [{ internalType: 'address payable', name: '', type: 'address' }],
695
+ stateMutability: 'view',
696
+ type: 'function',
697
+ },
698
+ {
699
+ inputs: [],
700
+ name: 'usdc',
701
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
702
+ stateMutability: 'view',
703
+ type: 'function',
704
+ },
705
+ {
706
+ inputs: [],
707
+ name: 'owner',
708
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
709
+ stateMutability: 'view',
710
+ type: 'function',
711
+ },
712
+ ] as const;
713
+
714
+ /**
715
+ * Veil Forwarder ABI
716
+ */
717
+ export const FORWARDER_ABI = [
718
+ {
719
+ inputs: [],
720
+ name: 'CONTRACT_VERSION',
721
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
722
+ stateMutability: 'view',
723
+ type: 'function',
724
+ },
725
+ {
726
+ inputs: [
727
+ { internalType: 'address', name: '_token', type: 'address' },
728
+ { internalType: 'address', name: '_to', type: 'address' },
729
+ { internalType: 'uint256', name: '_amount', type: 'uint256' },
730
+ { internalType: 'uint256', name: '_nonce', type: 'uint256' },
731
+ { internalType: 'uint256', name: '_deadline', type: 'uint256' },
732
+ { internalType: 'bytes', name: '_signature', type: 'bytes' },
733
+ ],
734
+ name: 'withdraw',
735
+ outputs: [],
736
+ stateMutability: 'nonpayable',
737
+ type: 'function',
738
+ },
739
+ {
740
+ inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
741
+ name: 'usedNonces',
742
+ outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
743
+ stateMutability: 'view',
744
+ type: 'function',
745
+ },
746
+ {
747
+ inputs: [],
748
+ name: 'owner',
749
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
750
+ stateMutability: 'view',
751
+ type: 'function',
752
+ },
753
+ {
754
+ inputs: [],
755
+ name: 'factory',
756
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
757
+ stateMutability: 'view',
758
+ type: 'function',
759
+ },
760
+ {
761
+ inputs: [],
762
+ name: 'childDepositKey',
763
+ outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }],
764
+ stateMutability: 'view',
765
+ type: 'function',
766
+ },
767
+ {
768
+ inputs: [],
769
+ name: 'entry',
770
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
771
+ stateMutability: 'view',
772
+ type: 'function',
773
+ },
774
+ {
775
+ inputs: [],
776
+ name: 'usdc',
777
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
778
+ stateMutability: 'view',
779
+ type: 'function',
780
+ },
781
+ {
782
+ inputs: [],
783
+ name: 'sweepETH',
784
+ outputs: [],
785
+ stateMutability: 'nonpayable',
786
+ type: 'function',
787
+ },
788
+ {
789
+ inputs: [],
790
+ name: 'sweepUSDC',
791
+ outputs: [],
792
+ stateMutability: 'nonpayable',
793
+ type: 'function',
794
+ },
795
+ {
796
+ inputs: [],
797
+ name: 'eip712Domain',
798
+ outputs: [
799
+ { internalType: 'bytes1', name: 'fields', type: 'bytes1' },
800
+ { internalType: 'string', name: 'name', type: 'string' },
801
+ { internalType: 'string', name: 'version', type: 'string' },
802
+ { internalType: 'uint256', name: 'chainId', type: 'uint256' },
803
+ { internalType: 'address', name: 'verifyingContract', type: 'address' },
804
+ { internalType: 'bytes32', name: 'salt', type: 'bytes32' },
805
+ { internalType: 'uint256[]', name: 'extensions', type: 'uint256[]' },
806
+ ],
807
+ stateMutability: 'view',
808
+ type: 'function',
809
+ },
810
+ { type: 'error', name: 'ZeroAddress', inputs: [] },
811
+ { type: 'error', name: 'ZeroAmount', inputs: [] },
812
+ { type: 'error', name: 'InvalidDepositKey', inputs: [] },
813
+ { type: 'error', name: 'NotRelayer', inputs: [] },
814
+ { type: 'error', name: 'NoETHBalance', inputs: [] },
815
+ { type: 'error', name: 'NoTokenBalance', inputs: [] },
816
+ { type: 'error', name: 'TokenApproveFailed', inputs: [] },
817
+ { type: 'error', name: 'ETHTransferFailed', inputs: [] },
818
+ { type: 'error', name: 'NonceUsed', inputs: [] },
819
+ { type: 'error', name: 'Unauthorized', inputs: [] },
820
+ { type: 'error', name: 'DeadlineExpired', inputs: [] },
821
+ ] as const;
package/src/addresses.ts CHANGED
@@ -14,10 +14,16 @@ export const ADDRESSES: NetworkAddresses = {
14
14
  usdcPool: '0x5c50d58E49C59d112680c187De2Bf989d2a91242',
15
15
  usdcQueue: '0x5530241b24504bF05C9a22e95A1F5458888e6a9B',
16
16
  usdcToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
17
+ forwarderFactory: '0x2848Fd62293A1ff3b4a897E9FcD0e5962dcc8101',
17
18
  chainId: 8453,
18
19
  relayUrl: 'https://veil-relay.up.railway.app',
19
20
  } as const;
20
21
 
22
+ /**
23
+ * Veil forwarder EIP-712 contract version
24
+ */
25
+ export const FORWARDER_CONTRACT_VERSION = '1' as const;
26
+
21
27
  /**
22
28
  * Pool configuration (decimals, symbols, etc.)
23
29
  */
@@ -76,6 +82,14 @@ export function getQueueAddress(
76
82
  }
77
83
  }
78
84
 
85
+ /**
86
+ * Get the forwarder factory contract address
87
+ * @returns Forwarder factory address for Base mainnet
88
+ */
89
+ export function getForwarderFactoryAddress(): `0x${string}` {
90
+ return getAddresses().forwarderFactory;
91
+ }
92
+
79
93
  /**
80
94
  * Get Relay URL
81
95
  * @returns Relay URL for Base mainnet