@veil-cash/sdk 0.1.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 +446 -0
- package/dist/cli/index.cjs +6431 -0
- package/dist/index.cjs +1912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2099 -0
- package/dist/index.d.ts +2099 -0
- package/dist/index.js +1840 -0
- package/dist/index.js.map +1 -0
- package/keys/transaction16.wasm +0 -0
- package/keys/transaction16.zkey +0 -0
- package/keys/transaction2.wasm +0 -0
- package/keys/transaction2.zkey +0 -0
- package/package.json +70 -0
- package/src/abi.ts +631 -0
- package/src/addresses.ts +53 -0
- package/src/balance.ts +266 -0
- package/src/cli/commands/balance.ts +118 -0
- package/src/cli/commands/deposit.ts +115 -0
- package/src/cli/commands/init.ts +147 -0
- package/src/cli/commands/keypair.ts +31 -0
- package/src/cli/commands/private-balance.ts +68 -0
- package/src/cli/commands/queue-balance.ts +58 -0
- package/src/cli/commands/register.ts +119 -0
- package/src/cli/commands/transfer.ts +137 -0
- package/src/cli/commands/withdraw.ts +79 -0
- package/src/cli/config.ts +58 -0
- package/src/cli/errors.ts +114 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/wallet.ts +228 -0
- package/src/deposit.ts +183 -0
- package/src/index.ts +160 -0
- package/src/keypair.ts +170 -0
- package/src/merkle.ts +71 -0
- package/src/prover.ts +176 -0
- package/src/relay.ts +216 -0
- package/src/transaction.ts +260 -0
- package/src/transfer.ts +462 -0
- package/src/types.ts +306 -0
- package/src/utils.ts +151 -0
- package/src/utxo.ts +119 -0
- package/src/withdraw.ts +299 -0
package/src/withdraw.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Withdrawal functions for Veil SDK
|
|
3
|
+
* Build ZK proofs to withdraw funds from the pool to a public address
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createPublicClient, http, parseUnits, formatUnits } from 'viem';
|
|
7
|
+
import { base } from 'viem/chains';
|
|
8
|
+
import { getAddresses, POOL_CONFIG } from './addresses.js';
|
|
9
|
+
import { POOL_ABI } from './abi.js';
|
|
10
|
+
import { Keypair } from './keypair.js';
|
|
11
|
+
import { Utxo } from './utxo.js';
|
|
12
|
+
import { getPrivateBalance } from './balance.js';
|
|
13
|
+
import { prepareTransaction } from './transaction.js';
|
|
14
|
+
import { submitRelay } from './relay.js';
|
|
15
|
+
import type {
|
|
16
|
+
BuildWithdrawProofOptions,
|
|
17
|
+
ProofBuildResult,
|
|
18
|
+
WithdrawResult,
|
|
19
|
+
UtxoSelectionResult,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Select UTXOs for withdrawal using largest-first algorithm
|
|
24
|
+
*
|
|
25
|
+
* @param utxos - Available unspent UTXOs
|
|
26
|
+
* @param amount - Amount to withdraw (human readable)
|
|
27
|
+
* @param decimals - Token decimals (default: 18 for ETH)
|
|
28
|
+
* @returns Selected UTXOs and change amount
|
|
29
|
+
*/
|
|
30
|
+
export function selectUtxosForWithdraw(
|
|
31
|
+
utxos: Utxo[],
|
|
32
|
+
amount: string,
|
|
33
|
+
decimals: number = 18
|
|
34
|
+
): UtxoSelectionResult {
|
|
35
|
+
const withdrawWei = parseUnits(amount, decimals);
|
|
36
|
+
|
|
37
|
+
// Sort UTXOs by amount (largest first)
|
|
38
|
+
const sortedUtxos = [...utxos].sort((a, b) => Number(b.amount - a.amount));
|
|
39
|
+
|
|
40
|
+
let totalSelected = 0n;
|
|
41
|
+
const selectedUtxos: Utxo[] = [];
|
|
42
|
+
|
|
43
|
+
for (const utxo of sortedUtxos) {
|
|
44
|
+
selectedUtxos.push(utxo);
|
|
45
|
+
totalSelected += utxo.amount;
|
|
46
|
+
|
|
47
|
+
if (totalSelected >= withdrawWei) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (totalSelected < withdrawWei) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Insufficient balance. Need ${amount}, have ${formatUnits(totalSelected, decimals)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const changeAmount = totalSelected - withdrawWei;
|
|
59
|
+
|
|
60
|
+
return { selectedUtxos, totalSelected, changeAmount };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fetch all commitments from the pool contract
|
|
65
|
+
*
|
|
66
|
+
* @param rpcUrl - RPC URL
|
|
67
|
+
* @param poolAddress - Pool contract address
|
|
68
|
+
* @param onProgress - Progress callback
|
|
69
|
+
* @returns Array of commitment hashes
|
|
70
|
+
*/
|
|
71
|
+
async function fetchCommitments(
|
|
72
|
+
rpcUrl: string | undefined,
|
|
73
|
+
poolAddress: `0x${string}`,
|
|
74
|
+
onProgress?: (stage: string, detail?: string) => void
|
|
75
|
+
): Promise<string[]> {
|
|
76
|
+
const publicClient = createPublicClient({
|
|
77
|
+
chain: base,
|
|
78
|
+
transport: http(rpcUrl),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Get total count
|
|
82
|
+
onProgress?.('Fetching commitment count...');
|
|
83
|
+
const nextIndex = await publicClient.readContract({
|
|
84
|
+
address: poolAddress,
|
|
85
|
+
abi: POOL_ABI,
|
|
86
|
+
functionName: 'nextIndex',
|
|
87
|
+
}) as number;
|
|
88
|
+
|
|
89
|
+
if (nextIndex === 0) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Fetch commitments in batches
|
|
94
|
+
const BATCH_SIZE = 5000;
|
|
95
|
+
const commitments: string[] = [];
|
|
96
|
+
const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
|
|
97
|
+
|
|
98
|
+
for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
|
|
99
|
+
const end = Math.min(start + BATCH_SIZE, nextIndex);
|
|
100
|
+
const batchNum = Math.floor(start / BATCH_SIZE) + 1;
|
|
101
|
+
onProgress?.('Fetching commitments', `batch ${batchNum}/${totalBatches}`);
|
|
102
|
+
|
|
103
|
+
const batch = await publicClient.readContract({
|
|
104
|
+
address: poolAddress,
|
|
105
|
+
abi: POOL_ABI,
|
|
106
|
+
functionName: 'getCommitments',
|
|
107
|
+
args: [BigInt(start), BigInt(end)],
|
|
108
|
+
}) as `0x${string}`[];
|
|
109
|
+
|
|
110
|
+
commitments.push(...batch.map(c => c.toString()));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return commitments;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build a withdrawal proof
|
|
118
|
+
*
|
|
119
|
+
* This function:
|
|
120
|
+
* 1. Fetches the user's unspent UTXOs
|
|
121
|
+
* 2. Selects UTXOs to cover the withdrawal amount
|
|
122
|
+
* 3. Fetches all commitments from the pool
|
|
123
|
+
* 4. Builds the ZK proof
|
|
124
|
+
*
|
|
125
|
+
* @param options - Withdrawal options
|
|
126
|
+
* @returns Proof data ready for relay submission
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const keypair = new Keypair(process.env.VEIL_KEY);
|
|
131
|
+
* const proof = await buildWithdrawProof({
|
|
132
|
+
* amount: '0.1',
|
|
133
|
+
* recipient: '0x1234...',
|
|
134
|
+
* keypair,
|
|
135
|
+
* onProgress: (stage, detail) => console.log(stage, detail),
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export async function buildWithdrawProof(
|
|
140
|
+
options: BuildWithdrawProofOptions
|
|
141
|
+
): Promise<ProofBuildResult> {
|
|
142
|
+
const {
|
|
143
|
+
amount,
|
|
144
|
+
recipient,
|
|
145
|
+
keypair,
|
|
146
|
+
rpcUrl,
|
|
147
|
+
onProgress,
|
|
148
|
+
} = options;
|
|
149
|
+
|
|
150
|
+
const addresses = getAddresses();
|
|
151
|
+
const poolConfig = POOL_CONFIG.eth;
|
|
152
|
+
const poolAddress = addresses.ethPool;
|
|
153
|
+
|
|
154
|
+
// 1. Get user's unspent UTXOs
|
|
155
|
+
onProgress?.('Fetching your UTXOs...');
|
|
156
|
+
const balanceResult = await getPrivateBalance({
|
|
157
|
+
keypair,
|
|
158
|
+
rpcUrl,
|
|
159
|
+
onProgress,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Filter to only unspent UTXOs and recreate Utxo objects with keypair
|
|
163
|
+
const unspentUtxoInfos = balanceResult.utxos.filter(u => !u.isSpent);
|
|
164
|
+
if (unspentUtxoInfos.length === 0) {
|
|
165
|
+
throw new Error('No unspent UTXOs available for withdrawal');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// We need to re-decrypt the UTXOs to get full Utxo objects
|
|
169
|
+
// For now, we'll fetch encrypted outputs and decrypt again
|
|
170
|
+
onProgress?.('Preparing UTXOs...');
|
|
171
|
+
|
|
172
|
+
const publicClient = createPublicClient({
|
|
173
|
+
chain: base,
|
|
174
|
+
transport: http(rpcUrl),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Fetch encrypted outputs for our unspent UTXOs
|
|
178
|
+
const utxos: Utxo[] = [];
|
|
179
|
+
for (const utxoInfo of unspentUtxoInfos) {
|
|
180
|
+
const encryptedOutputs = await publicClient.readContract({
|
|
181
|
+
address: poolAddress,
|
|
182
|
+
abi: POOL_ABI,
|
|
183
|
+
functionName: 'getEncryptedOutputs',
|
|
184
|
+
args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)],
|
|
185
|
+
}) as string[];
|
|
186
|
+
|
|
187
|
+
if (encryptedOutputs.length > 0) {
|
|
188
|
+
try {
|
|
189
|
+
const utxo = Utxo.decrypt(encryptedOutputs[0], keypair);
|
|
190
|
+
utxo.index = utxoInfo.index;
|
|
191
|
+
utxos.push(utxo);
|
|
192
|
+
} catch {
|
|
193
|
+
// Skip if decryption fails
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (utxos.length === 0) {
|
|
199
|
+
throw new Error('Failed to decrypt UTXOs');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 2. Select UTXOs for withdrawal
|
|
203
|
+
onProgress?.('Selecting UTXOs...');
|
|
204
|
+
const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
|
|
205
|
+
utxos,
|
|
206
|
+
amount,
|
|
207
|
+
poolConfig.decimals
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// 3. Create output UTXOs (just change, since withdrawal goes to external address)
|
|
211
|
+
const outputs: Utxo[] = [];
|
|
212
|
+
if (changeAmount > 0n) {
|
|
213
|
+
const changeUtxo = new Utxo({
|
|
214
|
+
amount: changeAmount,
|
|
215
|
+
keypair: keypair,
|
|
216
|
+
});
|
|
217
|
+
outputs.push(changeUtxo);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 4. Fetch all commitments from pool
|
|
221
|
+
const commitments = await fetchCommitments(rpcUrl, poolAddress, onProgress);
|
|
222
|
+
|
|
223
|
+
// 5. Build the ZK proof
|
|
224
|
+
onProgress?.('Building ZK proof...');
|
|
225
|
+
const result = await prepareTransaction({
|
|
226
|
+
commitments,
|
|
227
|
+
inputs: selectedUtxos,
|
|
228
|
+
outputs,
|
|
229
|
+
fee: 0,
|
|
230
|
+
recipient,
|
|
231
|
+
relayer: '0x0000000000000000000000000000000000000000',
|
|
232
|
+
onProgress,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
proofArgs: {
|
|
237
|
+
proof: result.args.proof,
|
|
238
|
+
root: result.args.root,
|
|
239
|
+
inputNullifiers: result.args.inputNullifiers,
|
|
240
|
+
outputCommitments: result.args.outputCommitments as [string, string],
|
|
241
|
+
publicAmount: result.args.publicAmount,
|
|
242
|
+
extDataHash: result.args.extDataHash,
|
|
243
|
+
},
|
|
244
|
+
extData: result.extData,
|
|
245
|
+
inputCount: selectedUtxos.length,
|
|
246
|
+
outputCount: outputs.length,
|
|
247
|
+
amount,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Execute a withdrawal by building proof and submitting to relay
|
|
253
|
+
*
|
|
254
|
+
* @param options - Withdrawal options
|
|
255
|
+
* @returns Withdrawal result with transaction hash
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const keypair = new Keypair(process.env.VEIL_KEY);
|
|
260
|
+
* const result = await withdraw({
|
|
261
|
+
* amount: '0.1',
|
|
262
|
+
* recipient: '0x1234...',
|
|
263
|
+
* keypair,
|
|
264
|
+
* });
|
|
265
|
+
*
|
|
266
|
+
* console.log(`Withdrawal tx: ${result.transactionHash}`);
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
export async function withdraw(
|
|
270
|
+
options: BuildWithdrawProofOptions
|
|
271
|
+
): Promise<WithdrawResult> {
|
|
272
|
+
const { amount, recipient, onProgress } = options;
|
|
273
|
+
|
|
274
|
+
// Build the proof
|
|
275
|
+
const proof = await buildWithdrawProof(options);
|
|
276
|
+
|
|
277
|
+
// Submit to relay
|
|
278
|
+
onProgress?.('Submitting to relay...');
|
|
279
|
+
const relayResult = await submitRelay({
|
|
280
|
+
type: 'withdraw',
|
|
281
|
+
pool: 'eth',
|
|
282
|
+
proofArgs: proof.proofArgs,
|
|
283
|
+
extData: proof.extData,
|
|
284
|
+
metadata: {
|
|
285
|
+
amount,
|
|
286
|
+
recipient,
|
|
287
|
+
inputUtxoCount: proof.inputCount,
|
|
288
|
+
outputUtxoCount: proof.outputCount,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
success: relayResult.success,
|
|
294
|
+
transactionHash: relayResult.transactionHash,
|
|
295
|
+
blockNumber: relayResult.blockNumber,
|
|
296
|
+
amount,
|
|
297
|
+
recipient,
|
|
298
|
+
};
|
|
299
|
+
}
|