@veil-cash/sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -41
- package/dist/cli/index.cjs +507 -130
- package/dist/index.cjs +214 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +172 -4
- package/dist/index.d.ts +172 -4
- package/dist/index.js +210 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/abi.ts +30 -0
- package/src/addresses.ts +40 -1
- package/src/balance.ts +22 -15
- package/src/cli/commands/balance.ts +114 -54
- package/src/cli/commands/deposit.ts +76 -33
- package/src/cli/commands/init.ts +45 -8
- package/src/cli/commands/private-balance.ts +6 -1
- package/src/cli/commands/queue-balance.ts +5 -1
- package/src/cli/commands/register.ts +47 -12
- package/src/cli/commands/transfer.ts +23 -10
- package/src/cli/commands/withdraw.ts +13 -5
- package/src/cli/index.ts +1 -1
- package/src/deposit.ts +106 -1
- package/src/index.ts +8 -2
- package/src/keypair.ts +79 -0
- package/src/relay.ts +2 -2
- package/src/transfer.ts +14 -11
- package/src/types.ts +9 -2
- package/src/withdraw.ts +7 -6
package/package.json
CHANGED
package/src/abi.ts
CHANGED
|
@@ -62,6 +62,24 @@ export const ENTRY_ABI = [
|
|
|
62
62
|
type: 'function',
|
|
63
63
|
},
|
|
64
64
|
|
|
65
|
+
// Change deposit key (must already be registered)
|
|
66
|
+
{
|
|
67
|
+
inputs: [
|
|
68
|
+
{
|
|
69
|
+
components: [
|
|
70
|
+
{ name: 'owner', type: 'address' },
|
|
71
|
+
{ name: 'depositKey', type: 'bytes' },
|
|
72
|
+
],
|
|
73
|
+
name: '_account',
|
|
74
|
+
type: 'tuple',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
name: 'changeDepositKey',
|
|
78
|
+
outputs: [],
|
|
79
|
+
stateMutability: 'nonpayable',
|
|
80
|
+
type: 'function',
|
|
81
|
+
},
|
|
82
|
+
|
|
65
83
|
// Queue ETH deposit
|
|
66
84
|
{
|
|
67
85
|
inputs: [{ name: '_depositKey', type: 'bytes' }],
|
|
@@ -83,6 +101,18 @@ export const ENTRY_ABI = [
|
|
|
83
101
|
type: 'function',
|
|
84
102
|
},
|
|
85
103
|
|
|
104
|
+
// Queue cbBTC deposit
|
|
105
|
+
{
|
|
106
|
+
inputs: [
|
|
107
|
+
{ name: '_amount', type: 'uint256' },
|
|
108
|
+
{ name: '_depositKey', type: 'bytes' },
|
|
109
|
+
],
|
|
110
|
+
name: 'queueBTC',
|
|
111
|
+
outputs: [],
|
|
112
|
+
stateMutability: 'nonpayable',
|
|
113
|
+
type: 'function',
|
|
114
|
+
},
|
|
115
|
+
|
|
86
116
|
// Read deposit keys
|
|
87
117
|
{
|
|
88
118
|
inputs: [{ name: '', type: 'address' }],
|
package/src/addresses.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Contract addresses for Veil on Base
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { NetworkAddresses } from './types.js';
|
|
5
|
+
import type { NetworkAddresses, RelayPool } from './types.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Contract addresses for Base mainnet
|
|
@@ -14,6 +14,9 @@ export const ADDRESSES: NetworkAddresses = {
|
|
|
14
14
|
usdcPool: '0x5c50d58E49C59d112680c187De2Bf989d2a91242',
|
|
15
15
|
usdcQueue: '0x5530241b24504bF05C9a22e95A1F5458888e6a9B',
|
|
16
16
|
usdcToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
17
|
+
cbbtcPool: '0x51A021da774b4bBB59B47f7CB4ccd631337680BA',
|
|
18
|
+
cbbtcQueue: '0x977741CaDF8D1431c4816C0993D32b02094cD35C',
|
|
19
|
+
cbbtcToken: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf',
|
|
17
20
|
chainId: 8453,
|
|
18
21
|
relayUrl: 'https://veil-relay.up.railway.app',
|
|
19
22
|
} as const;
|
|
@@ -34,6 +37,12 @@ export const POOL_CONFIG = {
|
|
|
34
37
|
symbol: 'USDC',
|
|
35
38
|
name: 'USD Coin',
|
|
36
39
|
},
|
|
40
|
+
cbbtc: {
|
|
41
|
+
decimals: 8,
|
|
42
|
+
displayDecimals: 6,
|
|
43
|
+
symbol: 'cbBTC',
|
|
44
|
+
name: 'Coinbase Bitcoin',
|
|
45
|
+
},
|
|
37
46
|
} as const;
|
|
38
47
|
|
|
39
48
|
/**
|
|
@@ -44,6 +53,36 @@ export function getAddresses(): NetworkAddresses {
|
|
|
44
53
|
return ADDRESSES;
|
|
45
54
|
}
|
|
46
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Get the pool contract address for a given pool
|
|
58
|
+
* @param pool - Pool identifier ('eth', 'usdc', or 'cbbtc')
|
|
59
|
+
* @returns Pool contract address
|
|
60
|
+
*/
|
|
61
|
+
export function getPoolAddress(pool: RelayPool): `0x${string}` {
|
|
62
|
+
const addresses = getAddresses();
|
|
63
|
+
switch (pool) {
|
|
64
|
+
case 'eth': return addresses.ethPool;
|
|
65
|
+
case 'usdc': return addresses.usdcPool;
|
|
66
|
+
case 'cbbtc': return addresses.cbbtcPool;
|
|
67
|
+
default: throw new Error(`Unknown pool: ${pool}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the queue contract address for a given pool
|
|
73
|
+
* @param pool - Pool identifier ('eth', 'usdc', or 'cbbtc')
|
|
74
|
+
* @returns Queue contract address
|
|
75
|
+
*/
|
|
76
|
+
export function getQueueAddress(pool: RelayPool): `0x${string}` {
|
|
77
|
+
const addresses = getAddresses();
|
|
78
|
+
switch (pool) {
|
|
79
|
+
case 'eth': return addresses.ethQueue;
|
|
80
|
+
case 'usdc': return addresses.usdcQueue;
|
|
81
|
+
case 'cbbtc': return addresses.cbbtcQueue;
|
|
82
|
+
default: throw new Error(`Unknown pool: ${pool}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
47
86
|
/**
|
|
48
87
|
* Get Relay URL
|
|
49
88
|
* @returns Relay URL for Base mainnet
|
package/src/balance.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Query queue and private balances directly from blockchain
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createPublicClient, http,
|
|
6
|
+
import { createPublicClient, http, formatUnits } from 'viem';
|
|
7
7
|
import { base } from 'viem/chains';
|
|
8
|
-
import {
|
|
8
|
+
import { getPoolAddress, getQueueAddress, POOL_CONFIG } from './addresses.js';
|
|
9
9
|
import { QUEUE_ABI, POOL_ABI } from './abi.js';
|
|
10
10
|
import { Keypair } from './keypair.js';
|
|
11
11
|
import { Utxo } from './utxo.js';
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
PendingDeposit,
|
|
16
16
|
PrivateBalanceResult,
|
|
17
17
|
UtxoInfo,
|
|
18
|
+
RelayPool,
|
|
18
19
|
} from './types.js';
|
|
19
20
|
|
|
20
21
|
// Deposit status enum from Queue contract
|
|
@@ -44,6 +45,7 @@ export type ProgressCallback = (stage: string, detail?: string) => void;
|
|
|
44
45
|
* ```typescript
|
|
45
46
|
* const result = await getQueueBalance({
|
|
46
47
|
* address: '0x...',
|
|
48
|
+
* pool: 'eth',
|
|
47
49
|
* onProgress: (stage, detail) => console.log(stage, detail),
|
|
48
50
|
* });
|
|
49
51
|
*
|
|
@@ -53,11 +55,13 @@ export type ProgressCallback = (stage: string, detail?: string) => void;
|
|
|
53
55
|
*/
|
|
54
56
|
export async function getQueueBalance(options: {
|
|
55
57
|
address: `0x${string}`;
|
|
58
|
+
pool?: RelayPool;
|
|
56
59
|
rpcUrl?: string;
|
|
57
60
|
onProgress?: ProgressCallback;
|
|
58
61
|
}): Promise<QueueBalanceResult> {
|
|
59
|
-
const { address, rpcUrl, onProgress } = options;
|
|
60
|
-
const
|
|
62
|
+
const { address, pool = 'eth', rpcUrl, onProgress } = options;
|
|
63
|
+
const queueAddress = getQueueAddress(pool);
|
|
64
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
61
65
|
|
|
62
66
|
// Create public client
|
|
63
67
|
const publicClient = createPublicClient({
|
|
@@ -68,7 +72,7 @@ export async function getQueueBalance(options: {
|
|
|
68
72
|
// Get all pending deposit nonces
|
|
69
73
|
onProgress?.('Fetching pending deposits...');
|
|
70
74
|
const pendingNonces = await publicClient.readContract({
|
|
71
|
-
address:
|
|
75
|
+
address: queueAddress,
|
|
72
76
|
abi: QUEUE_ABI,
|
|
73
77
|
functionName: 'getPendingDeposits',
|
|
74
78
|
}) as bigint[];
|
|
@@ -83,7 +87,7 @@ export async function getQueueBalance(options: {
|
|
|
83
87
|
onProgress?.('Checking deposit', `${i + 1}/${pendingNonces.length}`);
|
|
84
88
|
|
|
85
89
|
const deposit = await publicClient.readContract({
|
|
86
|
-
address:
|
|
90
|
+
address: queueAddress,
|
|
87
91
|
abi: QUEUE_ABI,
|
|
88
92
|
functionName: 'getDeposit',
|
|
89
93
|
args: [nonce],
|
|
@@ -103,7 +107,7 @@ export async function getQueueBalance(options: {
|
|
|
103
107
|
pendingDeposits.push({
|
|
104
108
|
nonce: nonce.toString(),
|
|
105
109
|
status: DEPOSIT_STATUS_MAP[deposit.status as keyof typeof DEPOSIT_STATUS_MAP] || 'pending',
|
|
106
|
-
amount:
|
|
110
|
+
amount: formatUnits(deposit.amountIn, poolConfig.decimals),
|
|
107
111
|
amountWei: deposit.amountIn.toString(),
|
|
108
112
|
timestamp: new Date(Number(deposit.timestamp) * 1000).toISOString(),
|
|
109
113
|
});
|
|
@@ -116,7 +120,7 @@ export async function getQueueBalance(options: {
|
|
|
116
120
|
|
|
117
121
|
return {
|
|
118
122
|
address,
|
|
119
|
-
queueBalance:
|
|
123
|
+
queueBalance: formatUnits(totalQueueBalance, poolConfig.decimals),
|
|
120
124
|
queueBalanceWei: totalQueueBalance.toString(),
|
|
121
125
|
pendingDeposits,
|
|
122
126
|
pendingCount: pendingDeposits.length,
|
|
@@ -138,6 +142,7 @@ export async function getQueueBalance(options: {
|
|
|
138
142
|
* const keypair = new Keypair(process.env.VEIL_KEY);
|
|
139
143
|
* const result = await getPrivateBalance({
|
|
140
144
|
* keypair,
|
|
145
|
+
* pool: 'eth',
|
|
141
146
|
* onProgress: (stage, detail) => console.log(stage, detail),
|
|
142
147
|
* });
|
|
143
148
|
*
|
|
@@ -147,11 +152,13 @@ export async function getQueueBalance(options: {
|
|
|
147
152
|
*/
|
|
148
153
|
export async function getPrivateBalance(options: {
|
|
149
154
|
keypair: Keypair;
|
|
155
|
+
pool?: RelayPool;
|
|
150
156
|
rpcUrl?: string;
|
|
151
157
|
onProgress?: ProgressCallback;
|
|
152
158
|
}): Promise<PrivateBalanceResult> {
|
|
153
|
-
const { keypair, rpcUrl, onProgress } = options;
|
|
154
|
-
const
|
|
159
|
+
const { keypair, pool = 'eth', rpcUrl, onProgress } = options;
|
|
160
|
+
const poolAddress = getPoolAddress(pool);
|
|
161
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
155
162
|
|
|
156
163
|
if (!keypair.privkey) {
|
|
157
164
|
throw new Error('Keypair must have a private key to calculate private balance');
|
|
@@ -166,7 +173,7 @@ export async function getPrivateBalance(options: {
|
|
|
166
173
|
// 1. Get total count of encrypted outputs
|
|
167
174
|
onProgress?.('Fetching pool index...');
|
|
168
175
|
const nextIndex = await publicClient.readContract({
|
|
169
|
-
address:
|
|
176
|
+
address: poolAddress,
|
|
170
177
|
abi: POOL_ABI,
|
|
171
178
|
functionName: 'nextIndex',
|
|
172
179
|
}) as number;
|
|
@@ -194,7 +201,7 @@ export async function getPrivateBalance(options: {
|
|
|
194
201
|
onProgress?.('Fetching encrypted outputs', `batch ${batchNum}/${totalBatches} (${start}-${end})`);
|
|
195
202
|
|
|
196
203
|
const batch = await publicClient.readContract({
|
|
197
|
-
address:
|
|
204
|
+
address: poolAddress,
|
|
198
205
|
abi: POOL_ABI,
|
|
199
206
|
functionName: 'getEncryptedOutputs',
|
|
200
207
|
args: [BigInt(start), BigInt(end)],
|
|
@@ -234,7 +241,7 @@ export async function getPrivateBalance(options: {
|
|
|
234
241
|
const nullifierHex = toFixedHex(nullifier) as `0x${string}`;
|
|
235
242
|
|
|
236
243
|
const isSpent = await publicClient.readContract({
|
|
237
|
-
address:
|
|
244
|
+
address: poolAddress,
|
|
238
245
|
abi: POOL_ABI,
|
|
239
246
|
functionName: 'isSpent',
|
|
240
247
|
args: [nullifierHex],
|
|
@@ -242,7 +249,7 @@ export async function getPrivateBalance(options: {
|
|
|
242
249
|
|
|
243
250
|
utxoInfos.push({
|
|
244
251
|
index,
|
|
245
|
-
amount:
|
|
252
|
+
amount: formatUnits(utxo.amount, poolConfig.decimals),
|
|
246
253
|
amountWei: utxo.amount.toString(),
|
|
247
254
|
isSpent,
|
|
248
255
|
});
|
|
@@ -256,7 +263,7 @@ export async function getPrivateBalance(options: {
|
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
return {
|
|
259
|
-
privateBalance:
|
|
266
|
+
privateBalance: formatUnits(totalBalance, poolConfig.decimals),
|
|
260
267
|
privateBalanceWei: totalBalance.toString(),
|
|
261
268
|
utxoCount: decryptedUtxos.length,
|
|
262
269
|
spentCount,
|
|
@@ -4,14 +4,91 @@
|
|
|
4
4
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { getQueueBalance, getPrivateBalance } from '../../balance.js';
|
|
7
|
+
import { POOL_CONFIG } from '../../addresses.js';
|
|
7
8
|
import { Keypair } from '../../keypair.js';
|
|
8
9
|
import { getAddress } from '../wallet.js';
|
|
9
|
-
import {
|
|
10
|
+
import { formatUnits } from 'viem';
|
|
10
11
|
import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
|
|
12
|
+
import type { RelayPool } from '../../types.js';
|
|
13
|
+
|
|
14
|
+
const SUPPORTED_POOLS: RelayPool[] = ['eth', 'usdc', 'cbbtc'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch balance for a single pool and return structured output
|
|
18
|
+
*/
|
|
19
|
+
async function fetchPoolBalance(
|
|
20
|
+
pool: RelayPool,
|
|
21
|
+
address: `0x${string}`,
|
|
22
|
+
keypair: Keypair | null,
|
|
23
|
+
rpcUrl: string | undefined,
|
|
24
|
+
onProgress: ((stage: string, detail?: string) => void) | undefined,
|
|
25
|
+
): Promise<Record<string, unknown>> {
|
|
26
|
+
const poolConfig = POOL_CONFIG[pool];
|
|
27
|
+
|
|
28
|
+
// Get queue balance
|
|
29
|
+
const poolProgress = onProgress
|
|
30
|
+
? (stage: string, detail?: string) => onProgress(`[${pool.toUpperCase()}] ${stage}`, detail)
|
|
31
|
+
: undefined;
|
|
32
|
+
|
|
33
|
+
const queueResult = await getQueueBalance({ address, pool, rpcUrl, onProgress: poolProgress });
|
|
34
|
+
|
|
35
|
+
// Get private balance if keypair available
|
|
36
|
+
let privateResult = null;
|
|
37
|
+
if (keypair) {
|
|
38
|
+
privateResult = await getPrivateBalance({ keypair, pool, rpcUrl, onProgress: poolProgress });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Calculate total balance
|
|
42
|
+
const queueBalanceWei = BigInt(queueResult.queueBalanceWei);
|
|
43
|
+
const privateBalanceWei = privateResult ? BigInt(privateResult.privateBalanceWei) : 0n;
|
|
44
|
+
const totalBalanceWei = queueBalanceWei + privateBalanceWei;
|
|
45
|
+
|
|
46
|
+
const result: Record<string, unknown> = {
|
|
47
|
+
pool: pool.toUpperCase(),
|
|
48
|
+
symbol: poolConfig.symbol,
|
|
49
|
+
totalBalance: formatUnits(totalBalanceWei, poolConfig.decimals),
|
|
50
|
+
totalBalanceWei: totalBalanceWei.toString(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Private balance
|
|
54
|
+
if (privateResult) {
|
|
55
|
+
const unspentUtxos = privateResult.utxos.filter(u => !u.isSpent);
|
|
56
|
+
result.private = {
|
|
57
|
+
balance: privateResult.privateBalance,
|
|
58
|
+
balanceWei: privateResult.privateBalanceWei,
|
|
59
|
+
utxoCount: privateResult.unspentCount,
|
|
60
|
+
utxos: unspentUtxos.map(u => ({
|
|
61
|
+
index: u.index,
|
|
62
|
+
amount: u.amount,
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
} else {
|
|
66
|
+
result.private = {
|
|
67
|
+
balance: null,
|
|
68
|
+
note: 'Set VEIL_KEY to see private balance',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Queue details
|
|
73
|
+
result.queue = {
|
|
74
|
+
balance: queueResult.queueBalance,
|
|
75
|
+
balanceWei: queueResult.queueBalanceWei,
|
|
76
|
+
count: queueResult.pendingCount,
|
|
77
|
+
deposits: queueResult.pendingDeposits.map(d => ({
|
|
78
|
+
nonce: d.nonce,
|
|
79
|
+
amount: d.amount,
|
|
80
|
+
status: d.status,
|
|
81
|
+
timestamp: d.timestamp,
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
11
87
|
|
|
12
88
|
export function createBalanceCommand(): Command {
|
|
13
89
|
const balance = new Command('balance')
|
|
14
|
-
.description('Show queue and private balances')
|
|
90
|
+
.description('Show queue and private balances (all pools by default)')
|
|
91
|
+
.option('--pool <pool>', 'Pool to check (eth, usdc, cbbtc, or all)', 'all')
|
|
15
92
|
.option('--wallet-key <key>', 'Ethereum wallet key (or set WALLET_KEY env)')
|
|
16
93
|
.option('--address <address>', 'Address to check (or derived from wallet key)')
|
|
17
94
|
.option('--veil-key <key>', 'Veil private key (or set VEIL_KEY env)')
|
|
@@ -19,6 +96,15 @@ export function createBalanceCommand(): Command {
|
|
|
19
96
|
.option('--quiet', 'Suppress progress output')
|
|
20
97
|
.action(async (options) => {
|
|
21
98
|
try {
|
|
99
|
+
const poolArg = (options.pool || 'all').toLowerCase();
|
|
100
|
+
|
|
101
|
+
// Validate pool
|
|
102
|
+
if (poolArg !== 'all' && !SUPPORTED_POOLS.includes(poolArg as RelayPool)) {
|
|
103
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported pool: ${options.pool}. Supported: ${SUPPORTED_POOLS.join(', ')}, all`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const poolsToQuery: RelayPool[] = poolArg === 'all' ? [...SUPPORTED_POOLS] : [poolArg as RelayPool];
|
|
107
|
+
|
|
22
108
|
// Get address
|
|
23
109
|
let address: `0x${string}`;
|
|
24
110
|
if (options.address) {
|
|
@@ -45,66 +131,40 @@ export function createBalanceCommand(): Command {
|
|
|
45
131
|
process.stderr.write(`\r\x1b[K${msg}`);
|
|
46
132
|
};
|
|
47
133
|
|
|
48
|
-
// Get
|
|
49
|
-
const
|
|
134
|
+
// Get deposit key if available
|
|
135
|
+
const depositKey = process.env.DEPOSIT_KEY || (keypair ? keypair.depositKey() : null);
|
|
50
136
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
privateResult = await getPrivateBalance({ keypair, rpcUrl, onProgress });
|
|
55
|
-
}
|
|
137
|
+
// Single pool mode -- flat output (backwards compatible)
|
|
138
|
+
if (poolsToQuery.length === 1) {
|
|
139
|
+
const poolResult = await fetchPoolBalance(poolsToQuery[0], address, keypair, rpcUrl, onProgress);
|
|
56
140
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
141
|
+
// Clear progress line
|
|
142
|
+
if (!options.quiet) process.stderr.write('\r\x1b[K');
|
|
143
|
+
|
|
144
|
+
const output = {
|
|
145
|
+
address,
|
|
146
|
+
depositKey: depositKey || null,
|
|
147
|
+
...poolResult,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
console.log(JSON.stringify(output, null, 2));
|
|
151
|
+
return;
|
|
60
152
|
}
|
|
61
153
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
154
|
+
// All pools mode -- nested output
|
|
155
|
+
const pools: Record<string, unknown>[] = [];
|
|
156
|
+
for (const pool of poolsToQuery) {
|
|
157
|
+
const poolResult = await fetchPoolBalance(pool, address, keypair, rpcUrl, onProgress);
|
|
158
|
+
pools.push(poolResult);
|
|
159
|
+
}
|
|
66
160
|
|
|
67
|
-
//
|
|
68
|
-
|
|
161
|
+
// Clear progress line
|
|
162
|
+
if (!options.quiet) process.stderr.write('\r\x1b[K');
|
|
69
163
|
|
|
70
|
-
|
|
71
|
-
const output: Record<string, unknown> = {
|
|
164
|
+
const output = {
|
|
72
165
|
address,
|
|
73
166
|
depositKey: depositKey || null,
|
|
74
|
-
|
|
75
|
-
totalBalanceWei: totalBalanceWei.toString(),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Private balance first
|
|
79
|
-
if (privateResult) {
|
|
80
|
-
const unspentUtxos = privateResult.utxos.filter(u => !u.isSpent);
|
|
81
|
-
output.private = {
|
|
82
|
-
balance: privateResult.privateBalance,
|
|
83
|
-
balanceWei: privateResult.privateBalanceWei,
|
|
84
|
-
utxoCount: privateResult.unspentCount,
|
|
85
|
-
utxos: unspentUtxos.map(u => ({
|
|
86
|
-
index: u.index,
|
|
87
|
-
amount: u.amount,
|
|
88
|
-
})),
|
|
89
|
-
};
|
|
90
|
-
} else {
|
|
91
|
-
output.private = {
|
|
92
|
-
balance: null,
|
|
93
|
-
note: 'Set VEIL_KEY to see private balance',
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Queue details second
|
|
98
|
-
output.queue = {
|
|
99
|
-
balance: queueResult.queueBalance,
|
|
100
|
-
balanceWei: queueResult.queueBalanceWei,
|
|
101
|
-
count: queueResult.pendingCount,
|
|
102
|
-
deposits: queueResult.pendingDeposits.map(d => ({
|
|
103
|
-
nonce: d.nonce,
|
|
104
|
-
amount: d.amount,
|
|
105
|
-
status: d.status,
|
|
106
|
-
timestamp: d.timestamp,
|
|
107
|
-
})),
|
|
167
|
+
pools,
|
|
108
168
|
};
|
|
109
169
|
|
|
110
170
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
|
-
import { buildDepositETHTx } from '../../deposit.js';
|
|
6
|
+
import { buildDepositETHTx, buildDepositUSDCTx, buildApproveUSDCTx, buildDepositCBBTCTx, buildApproveCBBTCTx } from '../../deposit.js';
|
|
7
7
|
import { sendTransaction, getAddress, getBalance } from '../wallet.js';
|
|
8
8
|
import { getConfig } from '../config.js';
|
|
9
9
|
import { parseEther, formatEther } from 'viem';
|
|
10
10
|
import { handleCLIError, CLIError, ErrorCode } from '../errors.js';
|
|
11
|
+
import type { TransactionData } from '../../types.js';
|
|
11
12
|
|
|
12
|
-
// Minimum
|
|
13
|
-
// To deposit 0.01 ETH net, you need to send: 0.01 / (1 - 0.003) ≈ 0.01003 ETH
|
|
14
|
-
const MINIMUM_DEPOSIT_ETH = 0.01;
|
|
13
|
+
// Minimum deposits per asset (net after 0.3% fee)
|
|
15
14
|
const DEPOSIT_FEE_PERCENT = 0.3;
|
|
16
|
-
const
|
|
15
|
+
const MINIMUM_DEPOSITS: Record<string, number> = {
|
|
16
|
+
ETH: 0.01,
|
|
17
|
+
USDC: 10,
|
|
18
|
+
CBBTC: 0.0001,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getMinimumWithFee(asset: string): number {
|
|
22
|
+
const min = MINIMUM_DEPOSITS[asset] || 0;
|
|
23
|
+
return min / (1 - DEPOSIT_FEE_PERCENT / 100);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SUPPORTED_ASSETS = ['ETH', 'USDC', 'CBBTC'];
|
|
17
27
|
|
|
18
28
|
// Progress helper - writes to stderr so JSON output stays clean
|
|
19
29
|
function progress(msg: string, quiet?: boolean) {
|
|
@@ -24,8 +34,8 @@ function progress(msg: string, quiet?: boolean) {
|
|
|
24
34
|
|
|
25
35
|
export function createDepositCommand(): Command {
|
|
26
36
|
const deposit = new Command('deposit')
|
|
27
|
-
.description('Deposit ETH into Veil')
|
|
28
|
-
.argument('<asset>', 'Asset to deposit (ETH)')
|
|
37
|
+
.description('Deposit ETH, USDC, or cbBTC into Veil')
|
|
38
|
+
.argument('<asset>', 'Asset to deposit (ETH, USDC, or CBBTC)')
|
|
29
39
|
.argument('<amount>', 'Amount to deposit (e.g., 0.1)')
|
|
30
40
|
.option('--deposit-key <key>', 'Your Veil deposit key (or set DEPOSIT_KEY env)')
|
|
31
41
|
.option('--wallet-key <key>', 'Ethereum wallet key for signing (or set WALLET_KEY env)')
|
|
@@ -34,19 +44,23 @@ export function createDepositCommand(): Command {
|
|
|
34
44
|
.option('--quiet', 'Suppress progress output')
|
|
35
45
|
.action(async (asset: string, amount: string, options) => {
|
|
36
46
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
const assetUpper = asset.toUpperCase();
|
|
48
|
+
|
|
49
|
+
// Validate asset
|
|
50
|
+
if (!SUPPORTED_ASSETS.includes(assetUpper)) {
|
|
51
|
+
throw new CLIError(ErrorCode.INVALID_AMOUNT, `Unsupported asset: ${asset}. Supported: ${SUPPORTED_ASSETS.join(', ')}`);
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
const amountNum = parseFloat(amount);
|
|
55
|
+
const minimumWithFee = getMinimumWithFee(assetUpper);
|
|
56
|
+
const minimumNet = MINIMUM_DEPOSITS[assetUpper];
|
|
43
57
|
|
|
44
58
|
// Check minimum deposit
|
|
45
|
-
if (amountNum <
|
|
59
|
+
if (amountNum < minimumWithFee) {
|
|
46
60
|
throw new CLIError(
|
|
47
61
|
ErrorCode.INVALID_AMOUNT,
|
|
48
|
-
`Minimum deposit is ${
|
|
49
|
-
`With ${DEPOSIT_FEE_PERCENT}% fee, send at least ${
|
|
62
|
+
`Minimum deposit is ${minimumNet} ${assetUpper} (net). ` +
|
|
63
|
+
`With ${DEPOSIT_FEE_PERCENT}% fee, send at least ${minimumWithFee.toFixed(assetUpper === 'ETH' ? 5 : 8)} ${assetUpper}.`
|
|
50
64
|
);
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -58,24 +72,45 @@ export function createDepositCommand(): Command {
|
|
|
58
72
|
|
|
59
73
|
progress('Building transaction...', options.quiet);
|
|
60
74
|
|
|
61
|
-
// Build the transaction
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
// Build the deposit transaction
|
|
76
|
+
let tx: TransactionData;
|
|
77
|
+
let approveTx: TransactionData | null = null;
|
|
78
|
+
|
|
79
|
+
if (assetUpper === 'USDC') {
|
|
80
|
+
approveTx = buildApproveUSDCTx({ amount });
|
|
81
|
+
tx = buildDepositUSDCTx({ depositKey, amount });
|
|
82
|
+
} else if (assetUpper === 'CBBTC') {
|
|
83
|
+
approveTx = buildApproveCBBTCTx({ amount });
|
|
84
|
+
tx = buildDepositCBBTCTx({ depositKey, amount });
|
|
85
|
+
} else {
|
|
86
|
+
tx = buildDepositETHTx({ depositKey, amount });
|
|
87
|
+
}
|
|
66
88
|
|
|
67
89
|
// Handle --unsigned mode (no wallet required, just build payload)
|
|
68
90
|
if (options.unsigned) {
|
|
69
91
|
progress('', options.quiet); // Clear line
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
const payloads: Record<string, unknown>[] = [];
|
|
93
|
+
|
|
94
|
+
// Include approval tx for ERC20 tokens
|
|
95
|
+
if (approveTx) {
|
|
96
|
+
payloads.push({
|
|
97
|
+
step: 'approve',
|
|
98
|
+
to: approveTx.to,
|
|
99
|
+
data: approveTx.data,
|
|
100
|
+
value: '0',
|
|
101
|
+
chainId: 8453,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
payloads.push({
|
|
106
|
+
step: 'deposit',
|
|
72
107
|
to: tx.to,
|
|
73
108
|
data: tx.data,
|
|
74
109
|
value: tx.value ? tx.value.toString() : '0',
|
|
75
|
-
chainId: 8453,
|
|
76
|
-
};
|
|
110
|
+
chainId: 8453,
|
|
111
|
+
});
|
|
77
112
|
|
|
78
|
-
console.log(JSON.stringify(
|
|
113
|
+
console.log(JSON.stringify(payloads.length === 1 ? payloads[0] : payloads, null, 2));
|
|
79
114
|
return;
|
|
80
115
|
}
|
|
81
116
|
|
|
@@ -83,20 +118,27 @@ export function createDepositCommand(): Command {
|
|
|
83
118
|
const config = getConfig(options);
|
|
84
119
|
const address = getAddress(config.privateKey);
|
|
85
120
|
|
|
86
|
-
|
|
121
|
+
// For ETH deposits, check ETH balance
|
|
122
|
+
if (assetUpper === 'ETH') {
|
|
123
|
+
progress('Checking balance...', options.quiet);
|
|
124
|
+
const balance = await getBalance(address, config.rpcUrl);
|
|
125
|
+
const amountWei = parseEther(amount);
|
|
126
|
+
|
|
127
|
+
if (balance < amountWei) {
|
|
128
|
+
progress('', options.quiet);
|
|
129
|
+
throw new CLIError(ErrorCode.INSUFFICIENT_BALANCE, `Insufficient ETH balance. Have: ${formatEther(balance)} ETH, Need: ${amount} ETH`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
87
132
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (balance < amountWei) {
|
|
93
|
-
progress('', options.quiet);
|
|
94
|
-
throw new CLIError(ErrorCode.INSUFFICIENT_BALANCE, `Insufficient ETH balance. Have: ${formatEther(balance)} ETH, Need: ${amount} ETH`);
|
|
133
|
+
// Send approval transaction for ERC20 tokens
|
|
134
|
+
if (approveTx) {
|
|
135
|
+
progress(`Approving ${assetUpper}...`, options.quiet);
|
|
136
|
+
await sendTransaction(config, approveTx);
|
|
95
137
|
}
|
|
96
138
|
|
|
97
|
-
progress('Sending transaction...', options.quiet);
|
|
139
|
+
progress('Sending deposit transaction...', options.quiet);
|
|
98
140
|
|
|
99
|
-
// Send the transaction
|
|
141
|
+
// Send the deposit transaction
|
|
100
142
|
const result = await sendTransaction(config, tx);
|
|
101
143
|
|
|
102
144
|
progress('Confirming...', options.quiet);
|
|
@@ -107,6 +149,7 @@ export function createDepositCommand(): Command {
|
|
|
107
149
|
console.log(JSON.stringify({
|
|
108
150
|
success: result.receipt.status === 'success',
|
|
109
151
|
hash: result.hash,
|
|
152
|
+
asset: assetUpper,
|
|
110
153
|
amount,
|
|
111
154
|
blockNumber: result.receipt.blockNumber.toString(),
|
|
112
155
|
gasUsed: result.receipt.gasUsed.toString(),
|