@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/README.md +75 -37
- package/SDK.md +55 -1
- package/dist/cli/index.cjs +806 -22
- package/dist/index.cjs +554 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +486 -1
- package/dist/index.d.ts +486 -1
- package/dist/index.js +537 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/veil/SKILL.md +74 -2
- package/skills/veil/reference.md +71 -2
- package/src/abi.ts +172 -0
- package/src/addresses.ts +14 -0
- package/src/cli/commands/subaccount.ts +355 -0
- package/src/cli/errors.ts +4 -0
- package/src/cli/index.ts +5 -1
- package/src/cli/wallet.ts +2 -2
- package/src/index.ts +35 -0
- package/src/relay.ts +45 -24
- package/src/subaccount.ts +481 -0
- package/src/types.ts +134 -0
package/dist/cli/index.cjs
CHANGED
|
@@ -3917,6 +3917,7 @@ var ErrorCode = {
|
|
|
3917
3917
|
DEPOSIT_KEY_MISSING: "DEPOSIT_KEY_MISSING",
|
|
3918
3918
|
CONFIG_CONFLICT: "CONFIG_CONFLICT",
|
|
3919
3919
|
INVALID_ADDRESS: "INVALID_ADDRESS",
|
|
3920
|
+
INVALID_SLOT: "INVALID_SLOT",
|
|
3920
3921
|
INVALID_AMOUNT: "INVALID_AMOUNT",
|
|
3921
3922
|
INSUFFICIENT_BALANCE: "INSUFFICIENT_BALANCE",
|
|
3922
3923
|
USER_NOT_REGISTERED: "USER_NOT_REGISTERED",
|
|
@@ -3952,6 +3953,9 @@ function inferErrorCode(message) {
|
|
|
3952
3953
|
if (msg.includes("invalid") && msg.includes("address")) {
|
|
3953
3954
|
return ErrorCode.INVALID_ADDRESS;
|
|
3954
3955
|
}
|
|
3956
|
+
if (msg.includes("invalid") && msg.includes("slot")) {
|
|
3957
|
+
return ErrorCode.INVALID_SLOT;
|
|
3958
|
+
}
|
|
3955
3959
|
if (msg.includes("insufficient balance") || msg.includes("not enough")) {
|
|
3956
3960
|
return ErrorCode.INSUFFICIENT_BALANCE;
|
|
3957
3961
|
}
|
|
@@ -4616,6 +4620,170 @@ var ERC20_ABI = [
|
|
|
4616
4620
|
type: "function"
|
|
4617
4621
|
}
|
|
4618
4622
|
];
|
|
4623
|
+
var FORWARDER_FACTORY_ABI = [
|
|
4624
|
+
{
|
|
4625
|
+
inputs: [],
|
|
4626
|
+
name: "CONTRACT_VERSION",
|
|
4627
|
+
outputs: [{ internalType: "string", name: "", type: "string" }],
|
|
4628
|
+
stateMutability: "view",
|
|
4629
|
+
type: "function"
|
|
4630
|
+
},
|
|
4631
|
+
{
|
|
4632
|
+
inputs: [
|
|
4633
|
+
{ internalType: "bytes32", name: "_salt", type: "bytes32" },
|
|
4634
|
+
{ internalType: "bytes", name: "_childDepositKey", type: "bytes" },
|
|
4635
|
+
{ internalType: "address", name: "_owner", type: "address" }
|
|
4636
|
+
],
|
|
4637
|
+
name: "computeAddress",
|
|
4638
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4639
|
+
stateMutability: "view",
|
|
4640
|
+
type: "function"
|
|
4641
|
+
},
|
|
4642
|
+
{
|
|
4643
|
+
inputs: [
|
|
4644
|
+
{ internalType: "bytes32", name: "_salt", type: "bytes32" },
|
|
4645
|
+
{ internalType: "bytes", name: "_childDepositKey", type: "bytes" },
|
|
4646
|
+
{ internalType: "address", name: "_owner", type: "address" }
|
|
4647
|
+
],
|
|
4648
|
+
name: "deploy",
|
|
4649
|
+
outputs: [{ internalType: "address", name: "forwarder", type: "address" }],
|
|
4650
|
+
stateMutability: "nonpayable",
|
|
4651
|
+
type: "function"
|
|
4652
|
+
},
|
|
4653
|
+
{
|
|
4654
|
+
inputs: [],
|
|
4655
|
+
name: "relayer",
|
|
4656
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4657
|
+
stateMutability: "view",
|
|
4658
|
+
type: "function"
|
|
4659
|
+
},
|
|
4660
|
+
{
|
|
4661
|
+
inputs: [],
|
|
4662
|
+
name: "veilEntry",
|
|
4663
|
+
outputs: [{ internalType: "address payable", name: "", type: "address" }],
|
|
4664
|
+
stateMutability: "view",
|
|
4665
|
+
type: "function"
|
|
4666
|
+
},
|
|
4667
|
+
{
|
|
4668
|
+
inputs: [],
|
|
4669
|
+
name: "usdc",
|
|
4670
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4671
|
+
stateMutability: "view",
|
|
4672
|
+
type: "function"
|
|
4673
|
+
},
|
|
4674
|
+
{
|
|
4675
|
+
inputs: [],
|
|
4676
|
+
name: "owner",
|
|
4677
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4678
|
+
stateMutability: "view",
|
|
4679
|
+
type: "function"
|
|
4680
|
+
}
|
|
4681
|
+
];
|
|
4682
|
+
var FORWARDER_ABI = [
|
|
4683
|
+
{
|
|
4684
|
+
inputs: [],
|
|
4685
|
+
name: "CONTRACT_VERSION",
|
|
4686
|
+
outputs: [{ internalType: "string", name: "", type: "string" }],
|
|
4687
|
+
stateMutability: "view",
|
|
4688
|
+
type: "function"
|
|
4689
|
+
},
|
|
4690
|
+
{
|
|
4691
|
+
inputs: [
|
|
4692
|
+
{ internalType: "address", name: "_token", type: "address" },
|
|
4693
|
+
{ internalType: "address", name: "_to", type: "address" },
|
|
4694
|
+
{ internalType: "uint256", name: "_amount", type: "uint256" },
|
|
4695
|
+
{ internalType: "uint256", name: "_nonce", type: "uint256" },
|
|
4696
|
+
{ internalType: "uint256", name: "_deadline", type: "uint256" },
|
|
4697
|
+
{ internalType: "bytes", name: "_signature", type: "bytes" }
|
|
4698
|
+
],
|
|
4699
|
+
name: "withdraw",
|
|
4700
|
+
outputs: [],
|
|
4701
|
+
stateMutability: "nonpayable",
|
|
4702
|
+
type: "function"
|
|
4703
|
+
},
|
|
4704
|
+
{
|
|
4705
|
+
inputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
|
4706
|
+
name: "usedNonces",
|
|
4707
|
+
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
|
4708
|
+
stateMutability: "view",
|
|
4709
|
+
type: "function"
|
|
4710
|
+
},
|
|
4711
|
+
{
|
|
4712
|
+
inputs: [],
|
|
4713
|
+
name: "owner",
|
|
4714
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4715
|
+
stateMutability: "view",
|
|
4716
|
+
type: "function"
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
inputs: [],
|
|
4720
|
+
name: "factory",
|
|
4721
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4722
|
+
stateMutability: "view",
|
|
4723
|
+
type: "function"
|
|
4724
|
+
},
|
|
4725
|
+
{
|
|
4726
|
+
inputs: [],
|
|
4727
|
+
name: "childDepositKey",
|
|
4728
|
+
outputs: [{ internalType: "bytes", name: "", type: "bytes" }],
|
|
4729
|
+
stateMutability: "view",
|
|
4730
|
+
type: "function"
|
|
4731
|
+
},
|
|
4732
|
+
{
|
|
4733
|
+
inputs: [],
|
|
4734
|
+
name: "entry",
|
|
4735
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4736
|
+
stateMutability: "view",
|
|
4737
|
+
type: "function"
|
|
4738
|
+
},
|
|
4739
|
+
{
|
|
4740
|
+
inputs: [],
|
|
4741
|
+
name: "usdc",
|
|
4742
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
4743
|
+
stateMutability: "view",
|
|
4744
|
+
type: "function"
|
|
4745
|
+
},
|
|
4746
|
+
{
|
|
4747
|
+
inputs: [],
|
|
4748
|
+
name: "sweepETH",
|
|
4749
|
+
outputs: [],
|
|
4750
|
+
stateMutability: "nonpayable",
|
|
4751
|
+
type: "function"
|
|
4752
|
+
},
|
|
4753
|
+
{
|
|
4754
|
+
inputs: [],
|
|
4755
|
+
name: "sweepUSDC",
|
|
4756
|
+
outputs: [],
|
|
4757
|
+
stateMutability: "nonpayable",
|
|
4758
|
+
type: "function"
|
|
4759
|
+
},
|
|
4760
|
+
{
|
|
4761
|
+
inputs: [],
|
|
4762
|
+
name: "eip712Domain",
|
|
4763
|
+
outputs: [
|
|
4764
|
+
{ internalType: "bytes1", name: "fields", type: "bytes1" },
|
|
4765
|
+
{ internalType: "string", name: "name", type: "string" },
|
|
4766
|
+
{ internalType: "string", name: "version", type: "string" },
|
|
4767
|
+
{ internalType: "uint256", name: "chainId", type: "uint256" },
|
|
4768
|
+
{ internalType: "address", name: "verifyingContract", type: "address" },
|
|
4769
|
+
{ internalType: "bytes32", name: "salt", type: "bytes32" },
|
|
4770
|
+
{ internalType: "uint256[]", name: "extensions", type: "uint256[]" }
|
|
4771
|
+
],
|
|
4772
|
+
stateMutability: "view",
|
|
4773
|
+
type: "function"
|
|
4774
|
+
},
|
|
4775
|
+
{ type: "error", name: "ZeroAddress", inputs: [] },
|
|
4776
|
+
{ type: "error", name: "ZeroAmount", inputs: [] },
|
|
4777
|
+
{ type: "error", name: "InvalidDepositKey", inputs: [] },
|
|
4778
|
+
{ type: "error", name: "NotRelayer", inputs: [] },
|
|
4779
|
+
{ type: "error", name: "NoETHBalance", inputs: [] },
|
|
4780
|
+
{ type: "error", name: "NoTokenBalance", inputs: [] },
|
|
4781
|
+
{ type: "error", name: "TokenApproveFailed", inputs: [] },
|
|
4782
|
+
{ type: "error", name: "ETHTransferFailed", inputs: [] },
|
|
4783
|
+
{ type: "error", name: "NonceUsed", inputs: [] },
|
|
4784
|
+
{ type: "error", name: "Unauthorized", inputs: [] },
|
|
4785
|
+
{ type: "error", name: "DeadlineExpired", inputs: [] }
|
|
4786
|
+
];
|
|
4619
4787
|
|
|
4620
4788
|
// src/addresses.ts
|
|
4621
4789
|
var ADDRESSES = {
|
|
@@ -4625,9 +4793,11 @@ var ADDRESSES = {
|
|
|
4625
4793
|
usdcPool: "0x5c50d58E49C59d112680c187De2Bf989d2a91242",
|
|
4626
4794
|
usdcQueue: "0x5530241b24504bF05C9a22e95A1F5458888e6a9B",
|
|
4627
4795
|
usdcToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
4796
|
+
forwarderFactory: "0x2848Fd62293A1ff3b4a897E9FcD0e5962dcc8101",
|
|
4628
4797
|
chainId: 8453,
|
|
4629
4798
|
relayUrl: "https://veil-relay.up.railway.app"
|
|
4630
4799
|
};
|
|
4800
|
+
var FORWARDER_CONTRACT_VERSION = "1";
|
|
4631
4801
|
var POOL_CONFIG = {
|
|
4632
4802
|
eth: {
|
|
4633
4803
|
decimals: 18,
|
|
@@ -4667,6 +4837,9 @@ function getQueueAddress(pool) {
|
|
|
4667
4837
|
throw new Error(`Unknown pool: ${pool}`);
|
|
4668
4838
|
}
|
|
4669
4839
|
}
|
|
4840
|
+
function getForwarderFactoryAddress() {
|
|
4841
|
+
return getAddresses().forwarderFactory;
|
|
4842
|
+
}
|
|
4670
4843
|
function getRelayUrl() {
|
|
4671
4844
|
return ADDRESSES.relayUrl;
|
|
4672
4845
|
}
|
|
@@ -4731,7 +4904,7 @@ function decodeCustomError(error) {
|
|
|
4731
4904
|
const possibleData = anyError.data || anyError.cause?.data || anyError.cause?.cause?.data;
|
|
4732
4905
|
if (possibleData && typeof possibleData === "string" && possibleData.startsWith("0x")) {
|
|
4733
4906
|
try {
|
|
4734
|
-
for (const abi of [ENTRY_ABI]) {
|
|
4907
|
+
for (const abi of [ENTRY_ABI, FORWARDER_ABI]) {
|
|
4735
4908
|
try {
|
|
4736
4909
|
const decoded = viem.decodeErrorResult({
|
|
4737
4910
|
abi,
|
|
@@ -6500,6 +6673,27 @@ var RelayError = class extends Error {
|
|
|
6500
6673
|
this.network = network;
|
|
6501
6674
|
}
|
|
6502
6675
|
};
|
|
6676
|
+
async function postRelayJson(endpoint, body, relayUrl) {
|
|
6677
|
+
const url = relayUrl || getRelayUrl();
|
|
6678
|
+
const response = await fetch(`${url}${endpoint}`, {
|
|
6679
|
+
method: "POST",
|
|
6680
|
+
headers: {
|
|
6681
|
+
"Content-Type": "application/json"
|
|
6682
|
+
},
|
|
6683
|
+
body: JSON.stringify(body)
|
|
6684
|
+
});
|
|
6685
|
+
const data = await response.json();
|
|
6686
|
+
if (!response.ok) {
|
|
6687
|
+
const errorData = data;
|
|
6688
|
+
throw new RelayError(
|
|
6689
|
+
errorData.error || errorData.message || "Relay request failed",
|
|
6690
|
+
response.status,
|
|
6691
|
+
errorData.retryAfter,
|
|
6692
|
+
errorData.network
|
|
6693
|
+
);
|
|
6694
|
+
}
|
|
6695
|
+
return data;
|
|
6696
|
+
}
|
|
6503
6697
|
async function submitRelay(options) {
|
|
6504
6698
|
const {
|
|
6505
6699
|
type,
|
|
@@ -6519,30 +6713,17 @@ async function submitRelay(options) {
|
|
|
6519
6713
|
throw new RelayError("Missing proofArgs or extData", 400);
|
|
6520
6714
|
}
|
|
6521
6715
|
const relayUrl = customRelayUrl || getRelayUrl();
|
|
6522
|
-
const endpoint =
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
"Content-Type": "application/json"
|
|
6527
|
-
},
|
|
6528
|
-
body: JSON.stringify({
|
|
6716
|
+
const endpoint = `/relay/${pool}`;
|
|
6717
|
+
return postRelayJson(
|
|
6718
|
+
endpoint,
|
|
6719
|
+
{
|
|
6529
6720
|
type,
|
|
6530
6721
|
proofArgs,
|
|
6531
6722
|
extData,
|
|
6532
6723
|
metadata
|
|
6533
|
-
}
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
if (!response.ok) {
|
|
6537
|
-
const errorData = data;
|
|
6538
|
-
throw new RelayError(
|
|
6539
|
-
errorData.error || errorData.message || "Relay request failed",
|
|
6540
|
-
response.status,
|
|
6541
|
-
errorData.retryAfter,
|
|
6542
|
-
errorData.network
|
|
6543
|
-
);
|
|
6544
|
-
}
|
|
6545
|
-
return data;
|
|
6724
|
+
},
|
|
6725
|
+
relayUrl
|
|
6726
|
+
);
|
|
6546
6727
|
}
|
|
6547
6728
|
async function checkRelayHealth(relayUrl) {
|
|
6548
6729
|
const url = relayUrl || getRelayUrl();
|
|
@@ -7332,16 +7513,618 @@ function maskUrl(url) {
|
|
|
7332
7513
|
return maskValue(url);
|
|
7333
7514
|
}
|
|
7334
7515
|
}
|
|
7516
|
+
var SUBACCOUNT_CHILD_DOMAIN = "veil-sua-child";
|
|
7517
|
+
var SUBACCOUNT_SALT_DOMAIN = "veil-sua-salt";
|
|
7518
|
+
var ETH_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
7519
|
+
var DEFAULT_WITHDRAW_DEADLINE_SECONDS = 3600n;
|
|
7520
|
+
var DEFAULT_MAX_NONCE_SCAN = 100n;
|
|
7521
|
+
var MAX_SUBACCOUNT_SLOTS = 3;
|
|
7522
|
+
function createBaseClient(rpcUrl) {
|
|
7523
|
+
return viem.createPublicClient({
|
|
7524
|
+
chain: chains.base,
|
|
7525
|
+
transport: viem.http(rpcUrl)
|
|
7526
|
+
});
|
|
7527
|
+
}
|
|
7528
|
+
function assertPrivateKey(value, label) {
|
|
7529
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(value)) {
|
|
7530
|
+
throw new Error(`${label} must be a 0x-prefixed 32-byte hex string`);
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
function normalizeSlot(slot) {
|
|
7534
|
+
if (!Number.isInteger(slot) || slot < 0) {
|
|
7535
|
+
throw new Error("slot must be a non-negative integer");
|
|
7536
|
+
}
|
|
7537
|
+
if (slot >= MAX_SUBACCOUNT_SLOTS) {
|
|
7538
|
+
throw new Error(`slot must be less than ${MAX_SUBACCOUNT_SLOTS} (supported slots: 0-${MAX_SUBACCOUNT_SLOTS - 1})`);
|
|
7539
|
+
}
|
|
7540
|
+
return slot;
|
|
7541
|
+
}
|
|
7542
|
+
function normalizeAsset(asset) {
|
|
7543
|
+
if (asset !== "eth" && asset !== "usdc") {
|
|
7544
|
+
throw new Error('asset must be "eth" or "usdc"');
|
|
7545
|
+
}
|
|
7546
|
+
return asset;
|
|
7547
|
+
}
|
|
7548
|
+
function normalizeNonce(value) {
|
|
7549
|
+
const nonce = typeof value === "bigint" ? value : BigInt(value);
|
|
7550
|
+
if (nonce < 0n) {
|
|
7551
|
+
throw new Error("nonce must be non-negative");
|
|
7552
|
+
}
|
|
7553
|
+
return nonce;
|
|
7554
|
+
}
|
|
7555
|
+
function normalizeDeadline(deadline) {
|
|
7556
|
+
const nextDeadline = deadline === void 0 ? BigInt(Math.floor(Date.now() / 1e3)) + DEFAULT_WITHDRAW_DEADLINE_SECONDS : typeof deadline === "bigint" ? deadline : BigInt(deadline);
|
|
7557
|
+
if (nextDeadline <= 0n) {
|
|
7558
|
+
throw new Error("deadline must be greater than 0");
|
|
7559
|
+
}
|
|
7560
|
+
return nextDeadline;
|
|
7561
|
+
}
|
|
7562
|
+
function deriveSubaccountChildPrivateKey(rootPrivateKey, slot) {
|
|
7563
|
+
assertPrivateKey(rootPrivateKey, "rootPrivateKey");
|
|
7564
|
+
const normalizedSlot = normalizeSlot(slot);
|
|
7565
|
+
return viem.keccak256(
|
|
7566
|
+
viem.encodePacked(
|
|
7567
|
+
["bytes32", "string", "uint256"],
|
|
7568
|
+
[rootPrivateKey, SUBACCOUNT_CHILD_DOMAIN, BigInt(normalizedSlot)]
|
|
7569
|
+
)
|
|
7570
|
+
);
|
|
7571
|
+
}
|
|
7572
|
+
function deriveSubaccountSalt(rootPrivateKey, slot) {
|
|
7573
|
+
assertPrivateKey(rootPrivateKey, "rootPrivateKey");
|
|
7574
|
+
const normalizedSlot = normalizeSlot(slot);
|
|
7575
|
+
return viem.keccak256(
|
|
7576
|
+
viem.encodePacked(
|
|
7577
|
+
["bytes32", "string", "uint256"],
|
|
7578
|
+
[rootPrivateKey, SUBACCOUNT_SALT_DOMAIN, BigInt(normalizedSlot)]
|
|
7579
|
+
)
|
|
7580
|
+
);
|
|
7581
|
+
}
|
|
7582
|
+
function deriveSubaccountChildOwner(childPrivateKey) {
|
|
7583
|
+
assertPrivateKey(childPrivateKey, "childPrivateKey");
|
|
7584
|
+
return accounts.privateKeyToAddress(childPrivateKey);
|
|
7585
|
+
}
|
|
7586
|
+
function deriveSubaccountChildDepositKey(childPrivateKey) {
|
|
7587
|
+
assertPrivateKey(childPrivateKey, "childPrivateKey");
|
|
7588
|
+
return new Keypair(childPrivateKey).depositKey();
|
|
7589
|
+
}
|
|
7590
|
+
async function predictSubaccountForwarder(options) {
|
|
7591
|
+
const publicClient = createBaseClient(options.rpcUrl);
|
|
7592
|
+
return publicClient.readContract({
|
|
7593
|
+
abi: FORWARDER_FACTORY_ABI,
|
|
7594
|
+
address: getForwarderFactoryAddress(),
|
|
7595
|
+
functionName: "computeAddress",
|
|
7596
|
+
args: [options.salt, options.childDepositKey, options.childOwner]
|
|
7597
|
+
});
|
|
7598
|
+
}
|
|
7599
|
+
async function deriveSubaccountSlot(options) {
|
|
7600
|
+
const normalizedSlot = normalizeSlot(options.slot);
|
|
7601
|
+
const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, normalizedSlot);
|
|
7602
|
+
const salt = deriveSubaccountSalt(options.rootPrivateKey, normalizedSlot);
|
|
7603
|
+
const childOwner = deriveSubaccountChildOwner(childPrivateKey);
|
|
7604
|
+
const childDepositKey = deriveSubaccountChildDepositKey(childPrivateKey);
|
|
7605
|
+
const forwarderAddress = await predictSubaccountForwarder({
|
|
7606
|
+
salt,
|
|
7607
|
+
childDepositKey,
|
|
7608
|
+
childOwner,
|
|
7609
|
+
rpcUrl: options.rpcUrl
|
|
7610
|
+
});
|
|
7611
|
+
return {
|
|
7612
|
+
slot: normalizedSlot,
|
|
7613
|
+
childOwner,
|
|
7614
|
+
childDepositKey,
|
|
7615
|
+
salt,
|
|
7616
|
+
forwarderAddress
|
|
7617
|
+
};
|
|
7618
|
+
}
|
|
7619
|
+
async function isSubaccountForwarderDeployed(options) {
|
|
7620
|
+
if (!viem.isAddress(options.forwarderAddress)) {
|
|
7621
|
+
throw new Error("forwarderAddress must be a valid Ethereum address");
|
|
7622
|
+
}
|
|
7623
|
+
const publicClient = createBaseClient(options.rpcUrl);
|
|
7624
|
+
const code = await publicClient.getCode({ address: options.forwarderAddress });
|
|
7625
|
+
return !!code && code !== "0x";
|
|
7626
|
+
}
|
|
7627
|
+
async function deploySubaccountForwarder(options) {
|
|
7628
|
+
const slot = await deriveSubaccountSlot({
|
|
7629
|
+
rootPrivateKey: options.rootPrivateKey,
|
|
7630
|
+
slot: options.slot,
|
|
7631
|
+
rpcUrl: options.rpcUrl
|
|
7632
|
+
});
|
|
7633
|
+
return postRelayJson(
|
|
7634
|
+
"/stealth/deploy",
|
|
7635
|
+
{
|
|
7636
|
+
salt: slot.salt,
|
|
7637
|
+
childDepositKey: slot.childDepositKey,
|
|
7638
|
+
childOwner: slot.childOwner,
|
|
7639
|
+
expectedForwarder: slot.forwarderAddress
|
|
7640
|
+
},
|
|
7641
|
+
options.relayUrl
|
|
7642
|
+
);
|
|
7643
|
+
}
|
|
7644
|
+
async function sweepSubaccountForwarder(options) {
|
|
7645
|
+
const asset = normalizeAsset(options.asset);
|
|
7646
|
+
if (!viem.isAddress(options.forwarderAddress)) {
|
|
7647
|
+
throw new Error("forwarderAddress must be a valid Ethereum address");
|
|
7648
|
+
}
|
|
7649
|
+
return postRelayJson(
|
|
7650
|
+
"/stealth/sweep",
|
|
7651
|
+
{
|
|
7652
|
+
forwarder: options.forwarderAddress,
|
|
7653
|
+
asset
|
|
7654
|
+
},
|
|
7655
|
+
options.relayUrl
|
|
7656
|
+
);
|
|
7657
|
+
}
|
|
7658
|
+
function toQueueStatus(asset, result) {
|
|
7659
|
+
return {
|
|
7660
|
+
asset,
|
|
7661
|
+
queueBalance: result.queueBalance,
|
|
7662
|
+
queueBalanceWei: result.queueBalanceWei,
|
|
7663
|
+
pendingCount: result.pendingCount,
|
|
7664
|
+
pendingDeposits: result.pendingDeposits
|
|
7665
|
+
};
|
|
7666
|
+
}
|
|
7667
|
+
async function getSubaccountStatus(options) {
|
|
7668
|
+
const slot = await deriveSubaccountSlot(options);
|
|
7669
|
+
const publicClient = createBaseClient(options.rpcUrl);
|
|
7670
|
+
const addresses = getAddresses();
|
|
7671
|
+
const [deployed, ethWei, usdcWei, ethQueue, usdcQueue] = await Promise.all([
|
|
7672
|
+
isSubaccountForwarderDeployed({
|
|
7673
|
+
forwarderAddress: slot.forwarderAddress,
|
|
7674
|
+
rpcUrl: options.rpcUrl
|
|
7675
|
+
}),
|
|
7676
|
+
publicClient.getBalance({ address: slot.forwarderAddress }),
|
|
7677
|
+
publicClient.readContract({
|
|
7678
|
+
address: addresses.usdcToken,
|
|
7679
|
+
abi: ERC20_ABI,
|
|
7680
|
+
functionName: "balanceOf",
|
|
7681
|
+
args: [slot.forwarderAddress]
|
|
7682
|
+
}),
|
|
7683
|
+
getQueueBalance({
|
|
7684
|
+
address: slot.forwarderAddress,
|
|
7685
|
+
pool: "eth",
|
|
7686
|
+
rpcUrl: options.rpcUrl
|
|
7687
|
+
}),
|
|
7688
|
+
getQueueBalance({
|
|
7689
|
+
address: slot.forwarderAddress,
|
|
7690
|
+
pool: "usdc",
|
|
7691
|
+
rpcUrl: options.rpcUrl
|
|
7692
|
+
})
|
|
7693
|
+
]);
|
|
7694
|
+
return {
|
|
7695
|
+
slot,
|
|
7696
|
+
deployed,
|
|
7697
|
+
balances: {
|
|
7698
|
+
eth: {
|
|
7699
|
+
balance: viem.formatEther(ethWei),
|
|
7700
|
+
balanceWei: ethWei.toString()
|
|
7701
|
+
},
|
|
7702
|
+
usdc: {
|
|
7703
|
+
balance: viem.formatUnits(usdcWei, 6),
|
|
7704
|
+
balanceWei: usdcWei.toString()
|
|
7705
|
+
}
|
|
7706
|
+
},
|
|
7707
|
+
queues: {
|
|
7708
|
+
eth: toQueueStatus("eth", ethQueue),
|
|
7709
|
+
usdc: toQueueStatus("usdc", usdcQueue)
|
|
7710
|
+
}
|
|
7711
|
+
};
|
|
7712
|
+
}
|
|
7713
|
+
var WITHDRAW_TYPES = {
|
|
7714
|
+
Withdraw: [
|
|
7715
|
+
{ name: "token", type: "address" },
|
|
7716
|
+
{ name: "to", type: "address" },
|
|
7717
|
+
{ name: "amount", type: "uint256" },
|
|
7718
|
+
{ name: "nonce", type: "uint256" },
|
|
7719
|
+
{ name: "deadline", type: "uint256" }
|
|
7720
|
+
]
|
|
7721
|
+
};
|
|
7722
|
+
function buildSubaccountWithdrawTypedData(options) {
|
|
7723
|
+
if (!viem.isAddress(options.forwarderAddress)) {
|
|
7724
|
+
throw new Error("forwarderAddress must be a valid Ethereum address");
|
|
7725
|
+
}
|
|
7726
|
+
if (!viem.isAddress(options.token)) {
|
|
7727
|
+
throw new Error("token must be a valid Ethereum address");
|
|
7728
|
+
}
|
|
7729
|
+
if (!viem.isAddress(options.to)) {
|
|
7730
|
+
throw new Error("to must be a valid Ethereum address");
|
|
7731
|
+
}
|
|
7732
|
+
return {
|
|
7733
|
+
domain: {
|
|
7734
|
+
name: "VeilForwarder",
|
|
7735
|
+
version: FORWARDER_CONTRACT_VERSION,
|
|
7736
|
+
chainId: getAddresses().chainId,
|
|
7737
|
+
verifyingContract: options.forwarderAddress
|
|
7738
|
+
},
|
|
7739
|
+
types: WITHDRAW_TYPES,
|
|
7740
|
+
primaryType: "Withdraw",
|
|
7741
|
+
message: {
|
|
7742
|
+
token: options.token,
|
|
7743
|
+
to: options.to,
|
|
7744
|
+
amount: options.amount,
|
|
7745
|
+
nonce: options.nonce,
|
|
7746
|
+
deadline: options.deadline
|
|
7747
|
+
}
|
|
7748
|
+
};
|
|
7749
|
+
}
|
|
7750
|
+
async function signSubaccountWithdraw(options) {
|
|
7751
|
+
assertPrivateKey(options.childPrivateKey, "childPrivateKey");
|
|
7752
|
+
const account = accounts.privateKeyToAccount(options.childPrivateKey);
|
|
7753
|
+
return account.signTypedData({
|
|
7754
|
+
domain: options.typedData.domain,
|
|
7755
|
+
types: options.typedData.types,
|
|
7756
|
+
primaryType: options.typedData.primaryType,
|
|
7757
|
+
message: options.typedData.message
|
|
7758
|
+
});
|
|
7759
|
+
}
|
|
7760
|
+
async function isSubaccountWithdrawNonceUsed(options) {
|
|
7761
|
+
if (!viem.isAddress(options.forwarderAddress)) {
|
|
7762
|
+
throw new Error("forwarderAddress must be a valid Ethereum address");
|
|
7763
|
+
}
|
|
7764
|
+
const publicClient = createBaseClient(options.rpcUrl);
|
|
7765
|
+
try {
|
|
7766
|
+
return await publicClient.readContract({
|
|
7767
|
+
abi: FORWARDER_ABI,
|
|
7768
|
+
address: options.forwarderAddress,
|
|
7769
|
+
functionName: "usedNonces",
|
|
7770
|
+
args: [normalizeNonce(options.nonce)]
|
|
7771
|
+
});
|
|
7772
|
+
} catch (error) {
|
|
7773
|
+
if (String(error).includes("returned no data")) {
|
|
7774
|
+
throw new Error("Subaccount forwarder is not deployed");
|
|
7775
|
+
}
|
|
7776
|
+
throw error;
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7779
|
+
async function findNextSubaccountWithdrawNonce(options) {
|
|
7780
|
+
const startNonce = normalizeNonce(options.startNonce ?? 0n);
|
|
7781
|
+
const maxScan = normalizeNonce(options.maxScan ?? DEFAULT_MAX_NONCE_SCAN);
|
|
7782
|
+
const limit = startNonce + maxScan;
|
|
7783
|
+
let nonce = startNonce;
|
|
7784
|
+
while (await isSubaccountWithdrawNonceUsed({
|
|
7785
|
+
forwarderAddress: options.forwarderAddress,
|
|
7786
|
+
nonce,
|
|
7787
|
+
rpcUrl: options.rpcUrl
|
|
7788
|
+
})) {
|
|
7789
|
+
nonce += 1n;
|
|
7790
|
+
if (nonce > limit) {
|
|
7791
|
+
throw new Error("Unable to find an unused withdraw nonce within the scan limit");
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
return nonce;
|
|
7795
|
+
}
|
|
7796
|
+
async function buildSubaccountRecoveryTx(options) {
|
|
7797
|
+
if (!viem.isAddress(options.to)) {
|
|
7798
|
+
throw new Error("to must be a valid Ethereum address");
|
|
7799
|
+
}
|
|
7800
|
+
const asset = normalizeAsset(options.asset);
|
|
7801
|
+
const slot = await deriveSubaccountSlot({
|
|
7802
|
+
rootPrivateKey: options.rootPrivateKey,
|
|
7803
|
+
slot: options.slot,
|
|
7804
|
+
rpcUrl: options.rpcUrl
|
|
7805
|
+
});
|
|
7806
|
+
const deployed = await isSubaccountForwarderDeployed({
|
|
7807
|
+
forwarderAddress: slot.forwarderAddress,
|
|
7808
|
+
rpcUrl: options.rpcUrl
|
|
7809
|
+
});
|
|
7810
|
+
if (!deployed) {
|
|
7811
|
+
throw new Error("Subaccount forwarder is not deployed");
|
|
7812
|
+
}
|
|
7813
|
+
const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, options.slot);
|
|
7814
|
+
const tokenAddress = asset === "eth" ? ETH_ADDRESS : getAddresses().usdcToken;
|
|
7815
|
+
const amountWei = asset === "eth" ? viem.parseEther(options.amount) : viem.parseUnits(options.amount, 6);
|
|
7816
|
+
const nonce = options.nonce === void 0 ? await findNextSubaccountWithdrawNonce({
|
|
7817
|
+
forwarderAddress: slot.forwarderAddress,
|
|
7818
|
+
rpcUrl: options.rpcUrl
|
|
7819
|
+
}) : normalizeNonce(options.nonce);
|
|
7820
|
+
const deadline = normalizeDeadline(options.deadline);
|
|
7821
|
+
const typedData = buildSubaccountWithdrawTypedData({
|
|
7822
|
+
forwarderAddress: slot.forwarderAddress,
|
|
7823
|
+
token: tokenAddress,
|
|
7824
|
+
to: options.to,
|
|
7825
|
+
amount: amountWei,
|
|
7826
|
+
nonce,
|
|
7827
|
+
deadline
|
|
7828
|
+
});
|
|
7829
|
+
const signature = await signSubaccountWithdraw({
|
|
7830
|
+
childPrivateKey,
|
|
7831
|
+
typedData
|
|
7832
|
+
});
|
|
7833
|
+
return {
|
|
7834
|
+
transaction: {
|
|
7835
|
+
to: slot.forwarderAddress,
|
|
7836
|
+
data: viem.encodeFunctionData({
|
|
7837
|
+
abi: FORWARDER_ABI,
|
|
7838
|
+
functionName: "withdraw",
|
|
7839
|
+
args: [tokenAddress, options.to, amountWei, nonce, deadline, signature]
|
|
7840
|
+
})
|
|
7841
|
+
},
|
|
7842
|
+
forwarderAddress: slot.forwarderAddress,
|
|
7843
|
+
asset,
|
|
7844
|
+
amount: options.amount,
|
|
7845
|
+
amountWei: amountWei.toString(),
|
|
7846
|
+
nonce: nonce.toString(),
|
|
7847
|
+
deadline: deadline.toString(),
|
|
7848
|
+
recipient: options.to,
|
|
7849
|
+
tokenAddress,
|
|
7850
|
+
signature
|
|
7851
|
+
};
|
|
7852
|
+
}
|
|
7853
|
+
|
|
7854
|
+
// src/cli/commands/subaccount.ts
|
|
7855
|
+
function parseSlotValue(raw) {
|
|
7856
|
+
const normalized = raw.trim();
|
|
7857
|
+
if (!/^\d+$/.test(normalized)) {
|
|
7858
|
+
throw new CLIError(ErrorCode.INVALID_SLOT, "--slot must be a non-negative integer");
|
|
7859
|
+
}
|
|
7860
|
+
const slot = Number(normalized);
|
|
7861
|
+
if (slot >= MAX_SUBACCOUNT_SLOTS) {
|
|
7862
|
+
throw new CLIError(
|
|
7863
|
+
ErrorCode.INVALID_SLOT,
|
|
7864
|
+
`--slot must be 0-${MAX_SUBACCOUNT_SLOTS - 1} (max ${MAX_SUBACCOUNT_SLOTS} subaccounts supported)`
|
|
7865
|
+
);
|
|
7866
|
+
}
|
|
7867
|
+
return slot;
|
|
7868
|
+
}
|
|
7869
|
+
function getRequiredVeilKey() {
|
|
7870
|
+
const veilKey = process.env.VEIL_KEY;
|
|
7871
|
+
if (!veilKey) {
|
|
7872
|
+
throw new CLIError(ErrorCode.VEIL_KEY_MISSING, "VEIL_KEY required. Set VEIL_KEY env");
|
|
7873
|
+
}
|
|
7874
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(veilKey)) {
|
|
7875
|
+
throw new CLIError(ErrorCode.VEIL_KEY_MISSING, "VEIL_KEY must be a 0x-prefixed 32-byte hex string");
|
|
7876
|
+
}
|
|
7877
|
+
return veilKey;
|
|
7878
|
+
}
|
|
7879
|
+
function parseAsset(raw) {
|
|
7880
|
+
const asset = raw.toLowerCase();
|
|
7881
|
+
if (asset !== "eth" && asset !== "usdc") {
|
|
7882
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${raw}. Supported: eth, usdc`);
|
|
7883
|
+
}
|
|
7884
|
+
return asset;
|
|
7885
|
+
}
|
|
7886
|
+
function printQueueHuman(title, queue) {
|
|
7887
|
+
printSection(title);
|
|
7888
|
+
printFields([
|
|
7889
|
+
{ label: "Queue balance", value: queue.queueBalance },
|
|
7890
|
+
{ label: "Pending", value: queue.pendingCount }
|
|
7891
|
+
]);
|
|
7892
|
+
if (queue.pendingDeposits.length > 0) {
|
|
7893
|
+
printList(
|
|
7894
|
+
queue.pendingDeposits.map((deposit) => `nonce ${deposit.nonce}: ${deposit.amount} (${deposit.status})`)
|
|
7895
|
+
);
|
|
7896
|
+
}
|
|
7897
|
+
}
|
|
7898
|
+
function createSubaccountCommand() {
|
|
7899
|
+
const subaccount = new Command("subaccount").description("Manage Veil subaccounts").addHelpText("after", `
|
|
7900
|
+
Examples:
|
|
7901
|
+
veil subaccount derive --slot 0
|
|
7902
|
+
veil subaccount status --slot 0
|
|
7903
|
+
veil subaccount deploy --slot 0
|
|
7904
|
+
veil subaccount sweep --slot 0 --asset eth
|
|
7905
|
+
veil subaccount recover --slot 0 --asset usdc --to 0xRecipientAddress --amount 25
|
|
7906
|
+
veil subaccount address --slot 0
|
|
7907
|
+
`);
|
|
7908
|
+
subaccount.command("derive").description("Derive subaccount metadata for a slot").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
|
|
7909
|
+
try {
|
|
7910
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
7911
|
+
const rpcUrl = process.env.RPC_URL;
|
|
7912
|
+
const slot = await deriveSubaccountSlot({
|
|
7913
|
+
rootPrivateKey,
|
|
7914
|
+
slot: options.slot,
|
|
7915
|
+
rpcUrl
|
|
7916
|
+
});
|
|
7917
|
+
const deployed = await isSubaccountForwarderDeployed({
|
|
7918
|
+
forwarderAddress: slot.forwarderAddress,
|
|
7919
|
+
rpcUrl
|
|
7920
|
+
});
|
|
7921
|
+
const output = {
|
|
7922
|
+
...slot,
|
|
7923
|
+
deployed
|
|
7924
|
+
};
|
|
7925
|
+
if (options.json) {
|
|
7926
|
+
printJson(output);
|
|
7927
|
+
return;
|
|
7928
|
+
}
|
|
7929
|
+
printHeader(`Subaccount Slot ${slot.slot}`);
|
|
7930
|
+
printFields([
|
|
7931
|
+
{ label: "Child owner", value: slot.childOwner },
|
|
7932
|
+
{ label: "Deposit key", value: slot.childDepositKey },
|
|
7933
|
+
{ label: "Salt", value: slot.salt },
|
|
7934
|
+
{ label: "Forwarder", value: slot.forwarderAddress },
|
|
7935
|
+
{ label: "Deployed", value: deployed }
|
|
7936
|
+
]);
|
|
7937
|
+
printLine();
|
|
7938
|
+
} catch (error) {
|
|
7939
|
+
handleCLIError(error);
|
|
7940
|
+
}
|
|
7941
|
+
});
|
|
7942
|
+
subaccount.command("status").description("Show subaccount deployment, balances, and queue state").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
|
|
7943
|
+
try {
|
|
7944
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
7945
|
+
const status = await getSubaccountStatus({
|
|
7946
|
+
rootPrivateKey,
|
|
7947
|
+
slot: options.slot,
|
|
7948
|
+
rpcUrl: process.env.RPC_URL
|
|
7949
|
+
});
|
|
7950
|
+
if (options.json) {
|
|
7951
|
+
printJson(status);
|
|
7952
|
+
return;
|
|
7953
|
+
}
|
|
7954
|
+
printHeader(`Subaccount Slot ${status.slot.slot}`);
|
|
7955
|
+
printFields([
|
|
7956
|
+
{ label: "Forwarder", value: status.slot.forwarderAddress },
|
|
7957
|
+
{ label: "Child owner", value: status.slot.childOwner },
|
|
7958
|
+
{ label: "Deposit key", value: status.slot.childDepositKey },
|
|
7959
|
+
{ label: "Salt", value: status.slot.salt },
|
|
7960
|
+
{ label: "Deployed", value: status.deployed }
|
|
7961
|
+
]);
|
|
7962
|
+
printSection("Forwarder Balances");
|
|
7963
|
+
printFields([
|
|
7964
|
+
{ label: "ETH", value: `${status.balances.eth.balance} ETH` },
|
|
7965
|
+
{ label: "USDC", value: `${status.balances.usdc.balance} USDC` }
|
|
7966
|
+
]);
|
|
7967
|
+
printQueueHuman("ETH Queue", status.queues.eth);
|
|
7968
|
+
printQueueHuman("USDC Queue", status.queues.usdc);
|
|
7969
|
+
printLine();
|
|
7970
|
+
} catch (error) {
|
|
7971
|
+
handleCLIError(error);
|
|
7972
|
+
}
|
|
7973
|
+
});
|
|
7974
|
+
subaccount.command("deploy").description("Deploy a subaccount forwarder through the relay").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
|
|
7975
|
+
try {
|
|
7976
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
7977
|
+
const slot = await deriveSubaccountSlot({
|
|
7978
|
+
rootPrivateKey,
|
|
7979
|
+
slot: options.slot,
|
|
7980
|
+
rpcUrl: process.env.RPC_URL
|
|
7981
|
+
});
|
|
7982
|
+
const result = await deploySubaccountForwarder({
|
|
7983
|
+
rootPrivateKey,
|
|
7984
|
+
slot: options.slot,
|
|
7985
|
+
rpcUrl: process.env.RPC_URL,
|
|
7986
|
+
relayUrl: process.env.RELAY_URL
|
|
7987
|
+
});
|
|
7988
|
+
const output = {
|
|
7989
|
+
...result,
|
|
7990
|
+
slot: options.slot,
|
|
7991
|
+
forwarderAddress: slot.forwarderAddress
|
|
7992
|
+
};
|
|
7993
|
+
if (options.json) {
|
|
7994
|
+
printJson(output);
|
|
7995
|
+
return;
|
|
7996
|
+
}
|
|
7997
|
+
printHeader("Subaccount Deploy Submitted");
|
|
7998
|
+
printFields([
|
|
7999
|
+
{ label: "Slot", value: options.slot },
|
|
8000
|
+
{ label: "Forwarder", value: slot.forwarderAddress },
|
|
8001
|
+
{ label: "Transaction", value: txUrl(result.transactionHash) },
|
|
8002
|
+
{ label: "Block", value: result.blockNumber }
|
|
8003
|
+
]);
|
|
8004
|
+
printLine();
|
|
8005
|
+
} catch (error) {
|
|
8006
|
+
handleCLIError(error);
|
|
8007
|
+
}
|
|
8008
|
+
});
|
|
8009
|
+
subaccount.command("sweep").description("Sweep ETH or USDC from a subaccount forwarder through the relay").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).requiredOption("--asset <asset>", "Asset to sweep (eth or usdc)", parseAsset).option("--json", "Output as JSON").action(async (options) => {
|
|
8010
|
+
try {
|
|
8011
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
8012
|
+
const slot = await deriveSubaccountSlot({
|
|
8013
|
+
rootPrivateKey,
|
|
8014
|
+
slot: options.slot,
|
|
8015
|
+
rpcUrl: process.env.RPC_URL
|
|
8016
|
+
});
|
|
8017
|
+
const result = await sweepSubaccountForwarder({
|
|
8018
|
+
forwarderAddress: slot.forwarderAddress,
|
|
8019
|
+
asset: options.asset,
|
|
8020
|
+
relayUrl: process.env.RELAY_URL
|
|
8021
|
+
});
|
|
8022
|
+
const output = {
|
|
8023
|
+
...result,
|
|
8024
|
+
slot: options.slot,
|
|
8025
|
+
asset: options.asset,
|
|
8026
|
+
forwarderAddress: slot.forwarderAddress
|
|
8027
|
+
};
|
|
8028
|
+
if (options.json) {
|
|
8029
|
+
printJson(output);
|
|
8030
|
+
return;
|
|
8031
|
+
}
|
|
8032
|
+
printHeader("Subaccount Sweep Submitted");
|
|
8033
|
+
printFields([
|
|
8034
|
+
{ label: "Slot", value: options.slot },
|
|
8035
|
+
{ label: "Asset", value: options.asset.toUpperCase() },
|
|
8036
|
+
{ label: "Forwarder", value: slot.forwarderAddress },
|
|
8037
|
+
{ label: "Transaction", value: txUrl(result.transactionHash) },
|
|
8038
|
+
{ label: "Block", value: result.blockNumber }
|
|
8039
|
+
]);
|
|
8040
|
+
printLine();
|
|
8041
|
+
} catch (error) {
|
|
8042
|
+
handleCLIError(error);
|
|
8043
|
+
}
|
|
8044
|
+
});
|
|
8045
|
+
subaccount.command("recover").description("Recover assets sitting on the subaccount forwarder with a direct withdraw transaction").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).requiredOption("--asset <asset>", "Asset to recover (eth or usdc)", parseAsset).requiredOption("--to <address>", "Recipient address").requiredOption("--amount <value>", "Amount to recover").option("--json", "Output as JSON").action(async (options) => {
|
|
8046
|
+
try {
|
|
8047
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
8048
|
+
if (!viem.isAddress(options.to)) {
|
|
8049
|
+
throw new CLIError(ErrorCode.INVALID_ADDRESS, `Invalid recipient address: ${options.to}`);
|
|
8050
|
+
}
|
|
8051
|
+
const config = getConfig({});
|
|
8052
|
+
const recovery = await buildSubaccountRecoveryTx({
|
|
8053
|
+
rootPrivateKey,
|
|
8054
|
+
slot: options.slot,
|
|
8055
|
+
asset: options.asset,
|
|
8056
|
+
to: options.to,
|
|
8057
|
+
amount: options.amount,
|
|
8058
|
+
rpcUrl: process.env.RPC_URL
|
|
8059
|
+
});
|
|
8060
|
+
const result = await sendTransaction(config, recovery.transaction);
|
|
8061
|
+
const output = {
|
|
8062
|
+
success: result.receipt.status === "success",
|
|
8063
|
+
slot: options.slot,
|
|
8064
|
+
asset: recovery.asset,
|
|
8065
|
+
amount: recovery.amount,
|
|
8066
|
+
amountWei: recovery.amountWei,
|
|
8067
|
+
forwarderAddress: recovery.forwarderAddress,
|
|
8068
|
+
recipient: recovery.recipient,
|
|
8069
|
+
nonce: recovery.nonce,
|
|
8070
|
+
deadline: recovery.deadline,
|
|
8071
|
+
signature: recovery.signature,
|
|
8072
|
+
transactionHash: result.hash,
|
|
8073
|
+
blockNumber: result.receipt.blockNumber.toString()
|
|
8074
|
+
};
|
|
8075
|
+
if (options.json) {
|
|
8076
|
+
printJson(output);
|
|
8077
|
+
return;
|
|
8078
|
+
}
|
|
8079
|
+
printHeader("Subaccount Recovery Submitted");
|
|
8080
|
+
printFields([
|
|
8081
|
+
{ label: "Slot", value: options.slot },
|
|
8082
|
+
{ label: "Asset", value: recovery.asset.toUpperCase() },
|
|
8083
|
+
{ label: "Amount", value: recovery.amount },
|
|
8084
|
+
{ label: "Recipient", value: recovery.recipient },
|
|
8085
|
+
{ label: "Forwarder", value: recovery.forwarderAddress },
|
|
8086
|
+
{ label: "Nonce", value: recovery.nonce },
|
|
8087
|
+
{ label: "Transaction", value: txUrl(result.hash) },
|
|
8088
|
+
{ label: "Block", value: result.receipt.blockNumber }
|
|
8089
|
+
]);
|
|
8090
|
+
printLine();
|
|
8091
|
+
} catch (error) {
|
|
8092
|
+
handleCLIError(error);
|
|
8093
|
+
}
|
|
8094
|
+
});
|
|
8095
|
+
subaccount.command("address").description("Print the predicted forwarder address for a subaccount slot").requiredOption("--slot <n>", "Subaccount slot", parseSlotValue).option("--json", "Output as JSON").action(async (options) => {
|
|
8096
|
+
try {
|
|
8097
|
+
const rootPrivateKey = getRequiredVeilKey();
|
|
8098
|
+
const slot = await deriveSubaccountSlot({
|
|
8099
|
+
rootPrivateKey,
|
|
8100
|
+
slot: options.slot,
|
|
8101
|
+
rpcUrl: process.env.RPC_URL
|
|
8102
|
+
});
|
|
8103
|
+
if (options.json) {
|
|
8104
|
+
printJson({
|
|
8105
|
+
slot: options.slot,
|
|
8106
|
+
forwarderAddress: slot.forwarderAddress
|
|
8107
|
+
});
|
|
8108
|
+
return;
|
|
8109
|
+
}
|
|
8110
|
+
printLine(slot.forwarderAddress);
|
|
8111
|
+
} catch (error) {
|
|
8112
|
+
handleCLIError(error);
|
|
8113
|
+
}
|
|
8114
|
+
});
|
|
8115
|
+
return subaccount;
|
|
8116
|
+
}
|
|
7335
8117
|
|
|
7336
8118
|
// src/cli/index.ts
|
|
7337
8119
|
loadEnv();
|
|
7338
8120
|
var program2 = new Command();
|
|
7339
|
-
program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.
|
|
8121
|
+
program2.name("veil").description("CLI for Veil Cash privacy pools on Base").version("0.6.0").addHelpText("after", `
|
|
7340
8122
|
Getting started:
|
|
7341
8123
|
veil init
|
|
7342
8124
|
veil register
|
|
7343
8125
|
veil deposit ETH 0.1
|
|
7344
8126
|
veil balance
|
|
8127
|
+
veil subaccount status --slot 0
|
|
7345
8128
|
`);
|
|
7346
8129
|
program2.addCommand(createInitCommand());
|
|
7347
8130
|
program2.addCommand(createKeypairCommand());
|
|
@@ -7354,6 +8137,7 @@ program2.addCommand(createWithdrawCommand());
|
|
|
7354
8137
|
program2.addCommand(createTransferCommand());
|
|
7355
8138
|
program2.addCommand(createMergeCommand());
|
|
7356
8139
|
program2.addCommand(createStatusCommand());
|
|
8140
|
+
program2.addCommand(createSubaccountCommand());
|
|
7357
8141
|
var knownTopLevelCommands = /* @__PURE__ */ new Set([
|
|
7358
8142
|
...program2.commands.map((command) => command.name()),
|
|
7359
8143
|
"help"
|