@veilo/sdk-core 0.1.17 → 0.3.3
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 +1257 -272
- package/dist/cjs/client.d.ts +407 -0
- package/dist/cjs/client.js +914 -0
- package/dist/cjs/config.d.ts +82 -0
- package/dist/cjs/config.js +57 -0
- package/dist/cjs/events.d.ts +77 -0
- package/dist/cjs/events.js +167 -0
- package/dist/cjs/idl/privacy_pool.json +10313 -0
- package/dist/cjs/index.d.ts +13 -0
- package/dist/cjs/index.js +57 -0
- package/dist/cjs/merkle.d.ts +64 -0
- package/dist/cjs/merkle.js +133 -0
- package/dist/cjs/poseidon.d.ts +29 -0
- package/dist/cjs/poseidon.js +100 -0
- package/dist/cjs/program.d.ts +26 -0
- package/dist/cjs/program.js +38 -0
- package/dist/cjs/proof.d.ts +183 -0
- package/dist/cjs/proof.js +292 -0
- package/dist/cjs/prover.d.ts +54 -0
- package/dist/cjs/prover.js +112 -0
- package/dist/cjs/relayer.d.ts +295 -0
- package/dist/cjs/relayer.js +246 -0
- package/dist/cjs/retry.d.ts +32 -0
- package/dist/cjs/retry.js +75 -0
- package/dist/cjs/utxo.d.ts +215 -0
- package/dist/cjs/utxo.js +394 -0
- package/dist/esm/client.js +887 -0
- package/dist/esm/config.js +51 -0
- package/dist/esm/events.js +129 -0
- package/dist/esm/idl/privacy_pool.json +10313 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/merkle.js +129 -0
- package/dist/esm/poseidon.js +87 -0
- package/dist/esm/program.js +31 -0
- package/dist/esm/proof.js +281 -0
- package/dist/esm/prover.js +75 -0
- package/dist/esm/relayer.js +238 -0
- package/dist/esm/retry.js +71 -0
- package/dist/esm/utxo.js +372 -0
- package/package.json +47 -11
- package/src/client.ts +0 -352
- package/src/config.ts +0 -13
- package/src/index.ts +0 -6
- package/src/merkle.ts +0 -178
- package/src/note.ts +0 -193
- package/src/poseidon.ts +0 -62
- package/src/proof.ts +0 -170
- package/test/script.js +0 -0
- package/test-tsconfig.json +0 -19
- package/tests/note.test.ts +0 -50
- package/tests/sdk.integration.test.ts +0 -210
- package/tsconfig.json +0 -18
package/README.md
CHANGED
|
@@ -1,35 +1,797 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @veilo/sdk-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for the Veilo Privacy Pool Anchor program.
|
|
4
4
|
|
|
5
|
-
This package
|
|
5
|
+
This package provides a complete UTXO-based privacy protocol implementation on Solana with:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- **UTXO Model**: Full support for unspent transaction outputs with Poseidon commitments
|
|
8
|
+
- **Multi-Tree Support**: Multiple concurrent Merkle trees for improved scalability
|
|
9
|
+
- **ZK Proofs**: Integration with Circom circuits for private transactions
|
|
10
|
+
- **Private Swaps**: Atomic cross-pool swaps via Jupiter (native SOL + SPL tokens)
|
|
11
|
+
- **Note Encryption**: NaCl-based encrypted UTXO notes and blind mailbox delivery
|
|
12
|
+
- **Event Scanning**: Reconstruct Merkle trees from on-chain events
|
|
13
|
+
- **Poseidon Hashing**: BN254-curve compatible hashing using circomlibjs
|
|
14
|
+
- **Relayer Support**: Built-in relayer infrastructure for private withdrawals and swaps
|
|
13
15
|
|
|
14
|
-
> **Status:**
|
|
15
|
-
>
|
|
16
|
-
> On-chain, `proof: Vec<u8>` is treated as opaque bytes (hook for a future verifier).
|
|
16
|
+
> **Status:** Active development. The SDK supports full transaction privacy with ZK-SNARK proofs.
|
|
17
|
+
> Proofs are generated off-chain and verified on-chain using Groth16.
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
20
|
-
## 1.
|
|
21
|
+
## 1. Installation
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
```bash
|
|
24
|
+
npm install @veilo/sdk-core
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Prerequisites
|
|
30
|
+
|
|
31
|
+
- A running Solana validator (localnet/devnet/mainnet)
|
|
32
|
+
- The `privacy-pool` program deployed to the network
|
|
33
|
+
- A funded keypair
|
|
23
34
|
|
|
24
35
|
```bash
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
|
|
37
|
+
export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Build
|
|
30
43
|
|
|
31
44
|
```bash
|
|
32
|
-
npm
|
|
45
|
+
npm run build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 4. SDK API
|
|
51
|
+
|
|
52
|
+
### 4.1 PDA Helpers
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {
|
|
56
|
+
getPoolPdas,
|
|
57
|
+
getNoteTreePda,
|
|
58
|
+
getGlobalConfigPda,
|
|
59
|
+
getNullifierMarkerPda,
|
|
60
|
+
getSwapExecutorPda,
|
|
61
|
+
} from "@veilo/sdk-core";
|
|
62
|
+
import { PublicKey } from "@solana/web3.js";
|
|
63
|
+
|
|
64
|
+
const programId = new PublicKey(
|
|
65
|
+
"YourProgram1111111111111111111111111111111111",
|
|
66
|
+
);
|
|
67
|
+
const mintAddress = new PublicKey(
|
|
68
|
+
"So11111111111111111111111111111111111111112",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Pool PDAs (config, vault, nullifiers)
|
|
72
|
+
const { config, vault, nullifiers } = getPoolPdas(programId, mintAddress);
|
|
73
|
+
|
|
74
|
+
// Note tree PDA for tree ID 0
|
|
75
|
+
const noteTree = getNoteTreePda(programId, mintAddress, 0);
|
|
76
|
+
|
|
77
|
+
// Global config PDA (one per program)
|
|
78
|
+
const globalConfig = getGlobalConfigPda(programId);
|
|
79
|
+
|
|
80
|
+
// Nullifier marker PDA (prevents double-spend)
|
|
81
|
+
const nullifier = new Uint8Array(32);
|
|
82
|
+
const marker = getNullifierMarkerPda(programId, mintAddress, nullifier);
|
|
83
|
+
|
|
84
|
+
// Swap executor PDA (for cross-pool swaps)
|
|
85
|
+
const relayerPubkey = new PublicKey("...");
|
|
86
|
+
const executor = getSwapExecutorPda(
|
|
87
|
+
programId,
|
|
88
|
+
sourceMint,
|
|
89
|
+
destMint,
|
|
90
|
+
inputNullifier0, // Uint8Array[32]
|
|
91
|
+
relayerPubkey,
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**PDA seeds (v3):**
|
|
96
|
+
|
|
97
|
+
| Account | Seeds |
|
|
98
|
+
| ---------------- | ------------------------------------------------------------------- |
|
|
99
|
+
| Config | `["privacy_config_v3", mint]` |
|
|
100
|
+
| Vault | `["privacy_vault_v3", mint]` |
|
|
101
|
+
| Note Tree | `["privacy_note_tree_v3", mint, tree_id]` |
|
|
102
|
+
| Nullifiers | `["privacy_nullifiers_v3", mint]` |
|
|
103
|
+
| Nullifier Marker | `["nullifier_v3", mint, nullifier]` |
|
|
104
|
+
| Global Config | `["global_config_v1"]` |
|
|
105
|
+
| Swap Executor | `["swap_executor_v1", source_mint, dest_mint, nullifier0, relayer]` |
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 4.2 Pool Initialization
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
initializeGlobalConfig,
|
|
114
|
+
initializePool,
|
|
115
|
+
updatePoolConfig,
|
|
116
|
+
addMerkleTree,
|
|
117
|
+
getPoolConfig,
|
|
118
|
+
updateGlobalConfig,
|
|
119
|
+
} from "@veilo/sdk-core";
|
|
120
|
+
import { NATIVE_SOL_MINT, sol } from "@veilo/sdk-core/config";
|
|
121
|
+
|
|
122
|
+
// Initialize global config (once per program)
|
|
123
|
+
await initializeGlobalConfig({ program, admin: adminKeypair });
|
|
124
|
+
|
|
125
|
+
// Update global config
|
|
126
|
+
await updateGlobalConfig({
|
|
127
|
+
program,
|
|
128
|
+
admin: adminKeypair,
|
|
129
|
+
newAdmin: newAdminPubkey, // optional
|
|
130
|
+
paused: false, // optional
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Initialize a pool for native SOL
|
|
134
|
+
await initializePool({
|
|
135
|
+
program,
|
|
136
|
+
payer: adminKeypair,
|
|
137
|
+
admin: adminKeypair,
|
|
138
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
139
|
+
minDepositAmount: sol(0.1),
|
|
140
|
+
maxDepositAmount: sol(100),
|
|
141
|
+
minWithdrawAmount: sol(0.1),
|
|
142
|
+
maxWithdrawAmount: sol(100),
|
|
143
|
+
feeBps: 50, // 0.5%
|
|
144
|
+
feeErrorMarginBps: 10, // 0.1% margin
|
|
145
|
+
minWithdrawalFee: 1_000_000n,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Add Merkle tree (tree ID 0)
|
|
149
|
+
await addMerkleTree({
|
|
150
|
+
program,
|
|
151
|
+
payer: adminKeypair,
|
|
152
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
153
|
+
treeId: 0,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Update pool fee
|
|
157
|
+
await updatePoolConfig({
|
|
158
|
+
program,
|
|
159
|
+
admin: adminKeypair,
|
|
160
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
161
|
+
feeBps: 30,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Read pool configuration
|
|
165
|
+
const poolConfig = await getPoolConfig(program, NATIVE_SOL_MINT);
|
|
166
|
+
console.log("TVL:", poolConfig.totalTvl, " Fee:", poolConfig.feeBps, "bps");
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 4.3 On-Chain Account Queries
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import {
|
|
175
|
+
fetchPoolConfig,
|
|
176
|
+
checkNullifierSpent,
|
|
177
|
+
getTreeInfo,
|
|
178
|
+
getAllTreeInfo,
|
|
179
|
+
getBestTreeForDeposit,
|
|
180
|
+
} from "@veilo/sdk-core";
|
|
181
|
+
|
|
182
|
+
// Fetch decoded PrivacyConfig account
|
|
183
|
+
const config = await fetchPoolConfig(program, NATIVE_SOL_MINT);
|
|
184
|
+
|
|
185
|
+
// Check if a nullifier has been spent
|
|
186
|
+
const spent = await checkNullifierSpent(program, NATIVE_SOL_MINT, nullifier);
|
|
187
|
+
|
|
188
|
+
// Info for a single tree
|
|
189
|
+
const info = await getTreeInfo(program, NATIVE_SOL_MINT, 0);
|
|
190
|
+
// info = { treeId, leafCount, root, isFull }
|
|
191
|
+
|
|
192
|
+
// Info for all trees in a pool
|
|
193
|
+
const allInfo = await getAllTreeInfo(program, NATIVE_SOL_MINT);
|
|
194
|
+
|
|
195
|
+
// Pick the best tree for depositing (least full with capacity)
|
|
196
|
+
const best = await getBestTreeForDeposit(program, NATIVE_SOL_MINT);
|
|
197
|
+
// best = { treeId, leafCount, root }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### 4.4 UTXO Management
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import {
|
|
206
|
+
generateKeypair,
|
|
207
|
+
keypairFromPrivateKey,
|
|
208
|
+
createUTXO,
|
|
209
|
+
createOwnedUTXO,
|
|
210
|
+
createOwnedZeroUTXO,
|
|
211
|
+
deriveNullifier,
|
|
212
|
+
type Keypair,
|
|
213
|
+
type UTXO,
|
|
214
|
+
type SerializedUTXO,
|
|
215
|
+
type InputUTXO,
|
|
216
|
+
} from "@veilo/sdk-core";
|
|
217
|
+
import { NATIVE_SOL_MINT } from "@veilo/sdk-core/config";
|
|
218
|
+
import { pubkeyToField } from "@veilo/sdk-core";
|
|
219
|
+
|
|
220
|
+
// Generate a random UTXO keypair
|
|
221
|
+
const keypair: Keypair = generateKeypair();
|
|
222
|
+
// keypair = { privateKey: bigint, publicKey: bigint }
|
|
223
|
+
|
|
224
|
+
// Restore from private key
|
|
225
|
+
const restored = keypairFromPrivateKey(privateKeyBigInt);
|
|
226
|
+
|
|
227
|
+
// Create an owned UTXO (includes private key)
|
|
228
|
+
const ownedUtxo: SerializedUTXO = createOwnedUTXO({
|
|
229
|
+
amount: 1_000_000_000n,
|
|
230
|
+
privateKey: keypair.privateKey,
|
|
231
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Create a zero-value UTXO (for unused inputs/outputs)
|
|
235
|
+
const zeroUtxo = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), keypair);
|
|
236
|
+
|
|
237
|
+
// Derive nullifier for spending
|
|
238
|
+
const pathIndex = 0; // leaf index in Merkle tree
|
|
239
|
+
const treeId = 0;
|
|
240
|
+
const nullifier = deriveNullifier(
|
|
241
|
+
ownedUtxo.privateKey,
|
|
242
|
+
ownedUtxo.commitment,
|
|
243
|
+
pathIndex,
|
|
244
|
+
treeId,
|
|
245
|
+
);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**UTXO commitment:** `Poseidon(amount, pubkey, blinding, mintAddress)`
|
|
249
|
+
|
|
250
|
+
**Nullifier:** `Poseidon(privateKey, commitment, pathIndex, treeId)`
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### 4.5 UTXO Encryption
|
|
255
|
+
|
|
256
|
+
The SDK provides NaCl-based encryption for UTXO notes (relayer storage) and blind mailbox delivery (wallet-to-wallet).
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import {
|
|
260
|
+
deriveEncryptionKeypair,
|
|
261
|
+
encryptUTXONote,
|
|
262
|
+
decryptUTXONote,
|
|
263
|
+
encryptBlindMailboxNote,
|
|
264
|
+
decryptBlindMailboxNote,
|
|
265
|
+
fetchAndDecryptNotes,
|
|
266
|
+
computeSignature,
|
|
267
|
+
inputUTXOToCircuitFormat,
|
|
268
|
+
type EncryptedNote,
|
|
269
|
+
type BlindMailboxNote,
|
|
270
|
+
type BlindMailboxNoteData,
|
|
271
|
+
type DecryptedNote,
|
|
272
|
+
} from "@veilo/sdk-core";
|
|
273
|
+
import { Keypair as SolanaKeypair } from "@solana/web3.js";
|
|
274
|
+
|
|
275
|
+
// Derive a NaCl encryption keypair from a UTXO private key
|
|
276
|
+
const encKeypair = deriveEncryptionKeypair(utxoKeypair.privateKey);
|
|
277
|
+
// encKeypair = { publicKey: Uint8Array[32], secretKey: Uint8Array[32] }
|
|
278
|
+
|
|
279
|
+
// Encrypt a UTXO note for relay storage
|
|
280
|
+
const encryptedNote: EncryptedNote = encryptUTXONote(
|
|
281
|
+
serializedUtxo, // SerializedUTXO
|
|
282
|
+
encKeypair.publicKey, // NaCl public key (Uint8Array[32])
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Decrypt a UTXO note
|
|
286
|
+
const decrypted: SerializedUTXO | null = decryptUTXONote(
|
|
287
|
+
encryptedNote,
|
|
288
|
+
encKeypair.secretKey, // NaCl secret key (Uint8Array[32])
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Blind mailbox: encrypt for a Solana wallet keypair
|
|
292
|
+
const recipientKeypair = SolanaKeypair.generate();
|
|
293
|
+
|
|
294
|
+
const mailboxNote: BlindMailboxNote = encryptBlindMailboxNote(
|
|
295
|
+
serializedUtxo,
|
|
296
|
+
recipientKeypair.publicKey.toBytes(), // ed25519 public key → X25519 DH
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Recipient decrypts using their Solana secret key (64-byte)
|
|
300
|
+
const mailboxDecrypted: DecryptedNote | null = decryptBlindMailboxNote(
|
|
301
|
+
mailboxNote,
|
|
302
|
+
recipientKeypair.secretKey, // 64-byte Solana secret key
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Compute UTXO signature (used in circuit)
|
|
306
|
+
const sig = computeSignature(utxoKeypair.privateKey, utxo.commitment);
|
|
307
|
+
|
|
308
|
+
// Format an InputUTXO for snarkjs circuit
|
|
309
|
+
const circuitFormat = inputUTXOToCircuitFormat(inputUtxo);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### 4.6 Merkle Tree Operations
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { MerkleTree } from "@veilo/sdk-core";
|
|
318
|
+
|
|
319
|
+
// Create a new Merkle tree (default depth: 22)
|
|
320
|
+
const tree = new MerkleTree();
|
|
321
|
+
|
|
322
|
+
// Insert commitments
|
|
323
|
+
const index = tree.insert(ownedUtxo.commitment);
|
|
324
|
+
|
|
325
|
+
// Current root
|
|
326
|
+
const root = tree.root();
|
|
327
|
+
|
|
328
|
+
// Merkle path for proof generation
|
|
329
|
+
const path = tree.path(index);
|
|
330
|
+
// path = { pathElements: Uint8Array[], pathIndices: number[] }
|
|
331
|
+
|
|
332
|
+
// Total leaves inserted
|
|
333
|
+
const count = tree.totalLeaves;
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### 4.7 Transaction Operations
|
|
339
|
+
|
|
340
|
+
All transaction functions return `Promise<string>` (the transaction signature).
|
|
341
|
+
|
|
342
|
+
#### Deposits
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import { deposit, type DepositResult } from "@veilo/sdk-core";
|
|
346
|
+
|
|
347
|
+
const result: DepositResult = await deposit({
|
|
348
|
+
program,
|
|
349
|
+
depositor: depositorKeypair,
|
|
350
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
351
|
+
amount: 1_000_000_000n, // 1 SOL
|
|
352
|
+
recipientPubkey: pubkeyToField(depositorKeypair.publicKey), // bigint
|
|
353
|
+
tree,
|
|
354
|
+
proofBuilder,
|
|
355
|
+
treeId: 0,
|
|
356
|
+
});
|
|
357
|
+
// result = { outputUTXOs, leafIndices, root }
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### Withdrawals
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import { withdraw, type WithdrawResult } from "@veilo/sdk-core";
|
|
364
|
+
|
|
365
|
+
const result: WithdrawResult = await withdraw({
|
|
366
|
+
program,
|
|
367
|
+
relayer: relayerKeypair,
|
|
368
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
369
|
+
amount: 900_000_000n, // 0.9 SOL
|
|
370
|
+
fee: 100_000_000n, // 0.1 SOL to relayer
|
|
371
|
+
inputs: [inputUtxo1, zeroInputUtxo],
|
|
372
|
+
outputs: [changeUtxo, zeroOutputUtxo],
|
|
373
|
+
recipient: recipientKeypair.publicKey,
|
|
374
|
+
tree,
|
|
375
|
+
proofBuilder,
|
|
376
|
+
treeId: 0,
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Private Transfers
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { privateTransfer, type TransferResult } from "@veilo/sdk-core";
|
|
384
|
+
|
|
385
|
+
const result: TransferResult = await privateTransfer({
|
|
386
|
+
program,
|
|
387
|
+
relayer: relayerKeypair,
|
|
388
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
389
|
+
inputs: [inputUtxo1, zeroInputUtxo],
|
|
390
|
+
outputs: [recipientUtxo, changeUtxo],
|
|
391
|
+
recipient: relayerKeypair.publicKey,
|
|
392
|
+
tree,
|
|
393
|
+
proofBuilder,
|
|
394
|
+
treeId: 0,
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### Low-Level `transact`
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import { transact } from "@veilo/sdk-core";
|
|
402
|
+
|
|
403
|
+
const signature: string = await transact({
|
|
404
|
+
program,
|
|
405
|
+
relayer: relayerKeypair,
|
|
406
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
407
|
+
inputTreeId: 0,
|
|
408
|
+
outputTreeId: 0,
|
|
409
|
+
root: tree.root(),
|
|
410
|
+
publicAmount: 0n,
|
|
411
|
+
inputNullifiers: [nullifier1, nullifier2],
|
|
412
|
+
outputCommitments: [output1.commitment, output2.commitment],
|
|
413
|
+
extData: { recipient, relayer: relayerPubkey, fee: 0n, refund: 0n },
|
|
414
|
+
proof,
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
### 4.8 Private Swaps
|
|
421
|
+
|
|
422
|
+
Cross-pool private swaps via Jupiter/Raydium. For native SOL source pools the SDK automatically composes the required `fund_native_source` + `transact_swap` instructions into a single atomic transaction.
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import {
|
|
426
|
+
transactSwap,
|
|
427
|
+
getSwapExecutorPda,
|
|
428
|
+
fundNativeSource,
|
|
429
|
+
type SwapParams,
|
|
430
|
+
type SwapProofStruct,
|
|
431
|
+
} from "@veilo/sdk-core";
|
|
432
|
+
|
|
433
|
+
// Execute a private swap (returns transaction signature)
|
|
434
|
+
const signature: string = await transactSwap({
|
|
435
|
+
program,
|
|
436
|
+
relayer: relayerKeypair,
|
|
437
|
+
sourceMint, // NATIVE_SOL_MINT or SPL token mint
|
|
438
|
+
destMint,
|
|
439
|
+
sourceRoot: tree.root(),
|
|
440
|
+
sourceTreeId: 0,
|
|
441
|
+
destTreeId: 0,
|
|
442
|
+
inputNullifiers: [nullifier0, nullifier1],
|
|
443
|
+
outputCommitments: [changeCommitment, destCommitment],
|
|
444
|
+
proof, // SwapProofStruct
|
|
445
|
+
swapParams: {
|
|
446
|
+
minAmountOut: 950_000_000n,
|
|
447
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 60),
|
|
448
|
+
sourceMint,
|
|
449
|
+
destMint,
|
|
450
|
+
destAmount: 950_000_000n,
|
|
451
|
+
swapDataHash: new Uint8Array(32), // SHA-256 of DEX ix bytes, or zeros
|
|
452
|
+
},
|
|
453
|
+
swapAmount: 1_000_000_000n,
|
|
454
|
+
swapData: jupiterInstructionBytes, // Buffer
|
|
455
|
+
extData: { recipient, relayer: relayerPubkey, fee, refund: 0n },
|
|
456
|
+
sourceVaultTokenAccount,
|
|
457
|
+
sourceMintAccount,
|
|
458
|
+
destVaultTokenAccount,
|
|
459
|
+
destMintAccount,
|
|
460
|
+
relayerTokenAccount,
|
|
461
|
+
swapProgram: JUPITER_PROGRAM_ID,
|
|
462
|
+
jupiterEventAuthority,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Build the fund_native_source instruction standalone (advanced)
|
|
466
|
+
const fundIx = await fundNativeSource({
|
|
467
|
+
program,
|
|
468
|
+
relayer: relayerKeypair,
|
|
469
|
+
sourceMint: NATIVE_SOL_MINT,
|
|
470
|
+
destMint,
|
|
471
|
+
inputNullifier0: nullifier0,
|
|
472
|
+
swapAmount: 1_000_000_000n,
|
|
473
|
+
});
|
|
474
|
+
// Returns TransactionInstruction — must be first ix in same tx as transact_swap
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
> **Note:** `transactSwap` handles the atomicity requirement automatically. Only use `fundNativeSource` directly if you are building transactions manually.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
### 4.9 Event Scanning & Tree Reconstruction
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
import {
|
|
485
|
+
scanCommitmentEvents,
|
|
486
|
+
scanNullifierEvents,
|
|
487
|
+
buildTreeFromEvents,
|
|
488
|
+
type CommitmentEvent,
|
|
489
|
+
type NullifierSpentEvent,
|
|
490
|
+
} from "@veilo/sdk-core";
|
|
491
|
+
|
|
492
|
+
// Scan all commitment events for a mint (paginated by signature)
|
|
493
|
+
const { events, latestSignature } = await scanCommitmentEvents({
|
|
494
|
+
program,
|
|
495
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
496
|
+
treeId: 0,
|
|
497
|
+
beforeSignature: undefined, // or last known signature for pagination
|
|
498
|
+
limit: 1000,
|
|
499
|
+
});
|
|
500
|
+
// events: CommitmentEvent[]
|
|
501
|
+
// CommitmentEvent = { commitment, leafIndex, newRoot, timestamp, mintAddress, treeId }
|
|
502
|
+
|
|
503
|
+
// Scan spent nullifier events
|
|
504
|
+
const { events: nullEvents } = await scanNullifierEvents({
|
|
505
|
+
program,
|
|
506
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
507
|
+
beforeSignature: undefined,
|
|
508
|
+
limit: 1000,
|
|
509
|
+
});
|
|
510
|
+
// NullifierSpentEvent = { nullifier, mintAddress, treeId }
|
|
511
|
+
|
|
512
|
+
// Reconstruct a Merkle tree from on-chain events
|
|
513
|
+
const {
|
|
514
|
+
tree,
|
|
515
|
+
events: allEvents,
|
|
516
|
+
latestSignature: sig,
|
|
517
|
+
} = await buildTreeFromEvents({
|
|
518
|
+
program,
|
|
519
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
520
|
+
treeId: 0,
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
### 4.10 Proof Generation
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
import {
|
|
530
|
+
prepareTransactionInputs,
|
|
531
|
+
formatInputsForSnarkjs,
|
|
532
|
+
computeExtDataHash,
|
|
533
|
+
encodeSnarkjsProofToTransactionProof,
|
|
534
|
+
packProofToBytes,
|
|
535
|
+
computeSwapParamsHash,
|
|
536
|
+
computeSwapDataHash,
|
|
537
|
+
type ExtData,
|
|
538
|
+
type TransactionCircuitInputs,
|
|
539
|
+
} from "@veilo/sdk-core";
|
|
540
|
+
|
|
541
|
+
// Prepare inputs for the transaction circuit
|
|
542
|
+
const circuitInputs: TransactionCircuitInputs = prepareTransactionInputs({
|
|
543
|
+
root: tree.root(),
|
|
544
|
+
publicAmount: 1_000_000_000n,
|
|
545
|
+
extData: { recipient, relayer: relayerPubkey, fee: 0n, refund: 0n },
|
|
546
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
547
|
+
inputs: [input1, input2],
|
|
548
|
+
outputs: [output1, output2],
|
|
549
|
+
inputTreeId: 0,
|
|
550
|
+
outputTreeId: 0,
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Format for snarkjs (converts bigints / Uint8Arrays to strings)
|
|
554
|
+
const snarkjsInputs = formatInputsForSnarkjs(circuitInputs);
|
|
555
|
+
|
|
556
|
+
// After proof generation:
|
|
557
|
+
// const { proof } = await snarkjs.groth16.fullProve(snarkjsInputs, wasmPath, zkeyPath);
|
|
558
|
+
// const transactionProof = encodeSnarkjsProofToTransactionProof(proof);
|
|
559
|
+
|
|
560
|
+
// Compute ext data hash (matches on-chain computation)
|
|
561
|
+
const extDataHash = computeExtDataHash({
|
|
562
|
+
recipient,
|
|
563
|
+
relayer,
|
|
564
|
+
fee: 0n,
|
|
565
|
+
refund: 0n,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Compute swap param/data hashes (for swap circuit inputs)
|
|
569
|
+
const swapParamsHash = computeSwapParamsHash(swapParams);
|
|
570
|
+
const swapDataHash = computeSwapDataHash(jupiterInstructionBytes);
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
### 4.11 Fee Utilities
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
import {
|
|
579
|
+
computeWithdrawalFee,
|
|
580
|
+
computeSwapFee,
|
|
581
|
+
DEFAULT_FEE_BPS,
|
|
582
|
+
} from "@veilo/sdk-core/config";
|
|
583
|
+
|
|
584
|
+
// Compute protocol fee for a withdrawal
|
|
585
|
+
const fee = computeWithdrawalFee(amount, feeBps, minWithdrawalFee);
|
|
586
|
+
|
|
587
|
+
// Compute protocol fee for a swap
|
|
588
|
+
const swapFee = computeSwapFee(swapAmount, feeBps);
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
### 4.12 Relayer & Admin Management
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
import { addRelayer, setPaused } from "@veilo/sdk-core";
|
|
597
|
+
|
|
598
|
+
// Authorise a new relayer for a pool
|
|
599
|
+
await addRelayer({
|
|
600
|
+
program,
|
|
601
|
+
admin: adminKeypair,
|
|
602
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
603
|
+
newRelayer: relayerPubkey,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// Pause or unpause a pool
|
|
607
|
+
await setPaused({
|
|
608
|
+
program,
|
|
609
|
+
admin: adminKeypair,
|
|
610
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
611
|
+
paused: true,
|
|
612
|
+
});
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
### 4.13 Poseidon Utilities
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
import {
|
|
621
|
+
initPoseidon,
|
|
622
|
+
poseidon1,
|
|
623
|
+
poseidon2,
|
|
624
|
+
poseidon3,
|
|
625
|
+
poseidon4,
|
|
626
|
+
pubkeyToField,
|
|
627
|
+
bytesToBigIntBE,
|
|
628
|
+
bigIntToBytesBE,
|
|
629
|
+
BN254_FR_MODULUS,
|
|
630
|
+
} from "@veilo/sdk-core";
|
|
631
|
+
|
|
632
|
+
// Must be called once before using hash functions
|
|
633
|
+
await initPoseidon();
|
|
634
|
+
|
|
635
|
+
const h1 = poseidon1(12345n);
|
|
636
|
+
const h2 = poseidon2(12345n, 67890n);
|
|
637
|
+
const h3 = poseidon3(12345n, 67890n, 11111n);
|
|
638
|
+
const h4 = poseidon4(12345n, 67890n, 11111n, 22222n);
|
|
639
|
+
|
|
640
|
+
// Convert a Solana PublicKey to a BN254 field element
|
|
641
|
+
const field = pubkeyToField(NATIVE_SOL_MINT);
|
|
642
|
+
|
|
643
|
+
// Byte ↔ bigint helpers (big-endian)
|
|
644
|
+
const n = bytesToBigIntBE(bytes32);
|
|
645
|
+
const b = bigIntToBytesBE(someField, 32);
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
### 4.14 Error Utilities
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
import { parseOnChainError } from "@veilo/sdk-core";
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
await transactSwap({ ... });
|
|
657
|
+
} catch (err) {
|
|
658
|
+
// Returns a human-readable error string from Anchor/program errors
|
|
659
|
+
const msg = parseOnChainError(err);
|
|
660
|
+
console.error("Swap failed:", msg);
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## 5. Architecture
|
|
667
|
+
|
|
668
|
+
### Transaction Model
|
|
669
|
+
|
|
670
|
+
Veilo uses a UTXO privacy model inspired by Zcash and Tornado Cash Nova:
|
|
671
|
+
|
|
672
|
+
- **Inputs**: 2 UTXOs (zero-value for deposits)
|
|
673
|
+
- **Outputs**: 2 UTXOs (zero-value for withdrawals)
|
|
674
|
+
- **Public Amount**: net pool change — positive = deposit, negative = withdrawal, zero = private transfer or swap
|
|
675
|
+
|
|
676
|
+
Each transaction consumes 2 input UTXOs (Merkle proofs), creates 2 output UTXOs (commitments inserted to tree), and generates 2 nullifiers to prevent double-spend.
|
|
677
|
+
|
|
678
|
+
### Swap Architecture
|
|
679
|
+
|
|
680
|
+
Private swaps require two instructions in a **single atomic transaction**:
|
|
681
|
+
|
|
682
|
+
1. `fund_native_source` — pre-funds the swap executor with SOL from the vault (native SOL pools only)
|
|
683
|
+
2. `transact_swap` — verifies the ZK proof, spends input UTXOs, creates output UTXOs, and executes the DEX swap
|
|
684
|
+
|
|
685
|
+
`transactSwap()` handles this automatically. The on-chain program validates atomicity via the instructions sysvar.
|
|
686
|
+
|
|
687
|
+
### Privacy Guarantees
|
|
688
|
+
|
|
689
|
+
- **Commitment hiding**: amount, owner, blinding are hidden via Poseidon
|
|
690
|
+
- **Nullifier uniqueness**: each UTXO can only be spent once
|
|
691
|
+
- **Unlinkability**: no public link between inputs and outputs
|
|
692
|
+
- **ZK proofs**: Groth16 verified on-chain
|
|
693
|
+
|
|
694
|
+
### Constants
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
import {
|
|
698
|
+
NATIVE_SOL_MINT, // PublicKey.default — native SOL pools
|
|
699
|
+
MERKLE_TREE_DEPTH, // 22
|
|
700
|
+
ROOT_HISTORY_SIZE, // 256
|
|
701
|
+
DEFAULT_FEE_BPS, // 50 (0.5%)
|
|
702
|
+
sol, // sol(1) === 1_000_000_000n
|
|
703
|
+
} from "@veilo/sdk-core/config";
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Type Exports
|
|
707
|
+
|
|
708
|
+
```ts
|
|
709
|
+
import type {
|
|
710
|
+
// UTXO
|
|
711
|
+
Keypair,
|
|
712
|
+
UTXO,
|
|
713
|
+
SerializedUTXO,
|
|
714
|
+
InputUTXO,
|
|
715
|
+
// Encryption
|
|
716
|
+
EncryptedNote,
|
|
717
|
+
BlindMailboxNote,
|
|
718
|
+
BlindMailboxNoteData,
|
|
719
|
+
DecryptedNote,
|
|
720
|
+
// Proof
|
|
721
|
+
ExtData,
|
|
722
|
+
TransactionCircuitInputs,
|
|
723
|
+
TransactionProofStruct,
|
|
724
|
+
RawProof,
|
|
725
|
+
TransactionProofBuilder,
|
|
726
|
+
// Swap
|
|
727
|
+
SwapProofStruct,
|
|
728
|
+
SwapParams,
|
|
729
|
+
// Tree / events
|
|
730
|
+
MerklePath,
|
|
731
|
+
CircuitMerklePath,
|
|
732
|
+
TreeInfo,
|
|
733
|
+
CommitmentEvent,
|
|
734
|
+
NullifierSpentEvent,
|
|
735
|
+
// Results
|
|
736
|
+
DepositResult,
|
|
737
|
+
WithdrawResult,
|
|
738
|
+
TransferResult,
|
|
739
|
+
// Config
|
|
740
|
+
PoolInitConfig,
|
|
741
|
+
PrivacyConfigAccount,
|
|
742
|
+
GlobalConfigAccount,
|
|
743
|
+
} from "@veilo/sdk-core";
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
---
|
|
747
|
+
|
|
748
|
+
## 6. Development
|
|
749
|
+
|
|
750
|
+
```bash
|
|
751
|
+
# Run tests (requires devnet or local validator)
|
|
752
|
+
npm test
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## 7. Resources
|
|
758
|
+
|
|
759
|
+
- **Repository**: https://github.com/VeiloSolana/veilo-sdk
|
|
760
|
+
- **Circomlibjs**: https://github.com/iden3/circomlibjs
|
|
761
|
+
- **Poseidon Hash**: https://www.poseidon-hash.info/
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## License
|
|
766
|
+
|
|
767
|
+
ISC
|
|
768
|
+
|
|
769
|
+
This package provides a complete UTXO-based privacy protocol implementation on Solana with:
|
|
770
|
+
|
|
771
|
+
- **UTXO Model**: Full support for unspent transaction outputs with Poseidon commitments
|
|
772
|
+
- **Multi-Tree Support**: Multiple concurrent Merkle trees for improved scalability
|
|
773
|
+
- **ZK Proofs**: Integration with Circom circuits for private transactions
|
|
774
|
+
- **Flexible Operations**: Deposits, withdrawals, and private transfers
|
|
775
|
+
- **Poseidon Hashing**: BN254-curve compatible hashing using circomlibjs
|
|
776
|
+
- **Merkle Trees**: Off-chain Merkle tree management with proof generation
|
|
777
|
+
- **Relayer Support**: Built-in relayer infrastructure for private withdrawals
|
|
778
|
+
|
|
779
|
+
> **Status:** Active development. The SDK supports full transaction privacy with ZK-SNARK proofs.
|
|
780
|
+
> Proofs are generated off-chain and verified on-chain using Groth16.
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
## 1. Installation
|
|
785
|
+
|
|
786
|
+
```bash
|
|
787
|
+
npm install @veilo/sdk-core
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
Or from source:
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
cd core-sdk
|
|
794
|
+
npm install
|
|
33
795
|
```
|
|
34
796
|
|
|
35
797
|
---
|
|
@@ -38,20 +800,20 @@ npm install @zkprivacysol/sdk-core
|
|
|
38
800
|
|
|
39
801
|
You need:
|
|
40
802
|
|
|
41
|
-
- A running Solana validator (localnet
|
|
803
|
+
- A running Solana validator (localnet/devnet/mainnet):
|
|
42
804
|
|
|
43
805
|
```bash
|
|
44
806
|
solana-test-validator
|
|
45
807
|
```
|
|
46
808
|
|
|
47
|
-
- The `privacy-pool` program
|
|
48
|
-
- The `privacy-pool` Anchor IDL available
|
|
809
|
+
- The `privacy-pool` program deployed to the network
|
|
810
|
+
- The `privacy-pool` Anchor IDL available:
|
|
49
811
|
|
|
50
812
|
```text
|
|
51
|
-
|
|
813
|
+
idl/idl/privacy_pool.json
|
|
52
814
|
```
|
|
53
815
|
|
|
54
|
-
- A funded keypair
|
|
816
|
+
- A funded keypair:
|
|
55
817
|
|
|
56
818
|
```bash
|
|
57
819
|
solana config set --url http://127.0.0.1:8899
|
|
@@ -59,7 +821,7 @@ You need:
|
|
|
59
821
|
solana airdrop 10
|
|
60
822
|
```
|
|
61
823
|
|
|
62
|
-
Environment variables
|
|
824
|
+
Environment variables:
|
|
63
825
|
|
|
64
826
|
```bash
|
|
65
827
|
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
|
|
@@ -68,372 +830,595 @@ export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
|
68
830
|
|
|
69
831
|
---
|
|
70
832
|
|
|
71
|
-
## 3. Build
|
|
72
|
-
|
|
73
|
-
From `packages/sdk-core`:
|
|
833
|
+
## 3. Build
|
|
74
834
|
|
|
75
835
|
```bash
|
|
76
|
-
# Typecheck & build to dist/
|
|
77
836
|
npm run build
|
|
78
|
-
|
|
79
|
-
# Run unit + integration tests
|
|
80
|
-
npm test
|
|
81
837
|
```
|
|
82
838
|
|
|
83
|
-
|
|
839
|
+
---
|
|
84
840
|
|
|
85
|
-
|
|
86
|
-
- `createRandomNote` / `encodeNoteToBytes` / `commitNote` / `createNoteWithCommitment`
|
|
87
|
-
- Ensures 32-byte commitments, deterministic encoding, etc.
|
|
841
|
+
## 4. SDK API
|
|
88
842
|
|
|
89
|
-
|
|
90
|
-
- Loads the `privacy-pool` IDL from `privacy-pool/target/idl/privacy_pool.json`
|
|
91
|
-
- Constructs an Anchor `Program` with a provider from `ANCHOR_PROVIDER_URL` / `ANCHOR_WALLET`
|
|
92
|
-
- Runs end-to-end flow:
|
|
93
|
-
1. `initializePool` (configures denoms + fee)
|
|
94
|
-
2. `createNoteAndDeposit` (creates note, commits it, calls on-chain `depositFixed` and updates the Merkle root)
|
|
95
|
-
3. `addRelayer`
|
|
96
|
-
4. `withdrawViaRelayer` (using a Merkle root that actually contains the note + a demo nullifier, empty proof)
|
|
97
|
-
5. Asserts vault TVL decreased, recipient gained funds, relayer received fee.
|
|
843
|
+
### 4.1 PDA Helpers
|
|
98
844
|
|
|
99
|
-
|
|
845
|
+
```ts
|
|
846
|
+
import {
|
|
847
|
+
getPoolPdas,
|
|
848
|
+
getNoteTreePda,
|
|
849
|
+
getGlobalConfigPda,
|
|
850
|
+
getNullifierMarkerPda,
|
|
851
|
+
} from "@veilo/sdk-core";
|
|
852
|
+
import { PublicKey } from "@solana/web3.js";
|
|
100
853
|
|
|
101
|
-
|
|
854
|
+
const programId = new PublicKey(
|
|
855
|
+
"YourProgram1111111111111111111111111111111111",
|
|
856
|
+
);
|
|
857
|
+
const mintAddress = new PublicKey(
|
|
858
|
+
"So11111111111111111111111111111111111111112",
|
|
859
|
+
); // Native SOL
|
|
102
860
|
|
|
103
|
-
|
|
861
|
+
// Get pool PDAs
|
|
862
|
+
const { config, vault, nullifiers } = getPoolPdas(programId, mintAddress);
|
|
104
863
|
|
|
105
|
-
|
|
864
|
+
// Get note tree PDA for tree ID 0
|
|
865
|
+
const noteTree = getNoteTreePda(programId, mintAddress, 0);
|
|
106
866
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
import { PublicKey } from "@solana/web3.js";
|
|
867
|
+
// Get global config
|
|
868
|
+
const globalConfig = getGlobalConfigPda(programId);
|
|
110
869
|
|
|
111
|
-
|
|
112
|
-
const
|
|
870
|
+
// Get nullifier marker PDA
|
|
871
|
+
const nullifier = new Uint8Array(32);
|
|
872
|
+
const marker = getNullifierMarkerPda(programId, mintAddress, 0, nullifier);
|
|
113
873
|
```
|
|
114
874
|
|
|
115
|
-
|
|
875
|
+
**PDA seeds (v3):**
|
|
116
876
|
|
|
117
|
-
- `["privacy_config_v3"]`
|
|
118
|
-
- `["privacy_vault_v3"]`
|
|
119
|
-
- `["privacy_note_tree_v3"]`
|
|
120
|
-
- `["privacy_nullifiers_v3"]`
|
|
877
|
+
- Config: `["privacy_config_v3", mint_address]`
|
|
878
|
+
- Vault: `["privacy_vault_v3", mint_address]`
|
|
879
|
+
- Note Tree: `["privacy_note_tree_v3", mint_address, tree_id]`
|
|
880
|
+
- Nullifiers: `["privacy_nullifiers_v3", mint_address]`
|
|
881
|
+
- Nullifier Marker: `["privacy_nullifier_v3", mint_address, tree_id, nullifier]`
|
|
882
|
+
- Global Config: `["global_config_v1"]`
|
|
121
883
|
|
|
122
884
|
---
|
|
123
885
|
|
|
124
|
-
### 4.2 Pool
|
|
886
|
+
### 4.2 Pool Initialization
|
|
125
887
|
|
|
126
888
|
```ts
|
|
127
889
|
import * as anchor from "@coral-xyz/anchor";
|
|
128
|
-
import {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
890
|
+
import {
|
|
891
|
+
initializeGlobalConfig,
|
|
892
|
+
initializePool,
|
|
893
|
+
updatePoolConfig,
|
|
894
|
+
addMerkleTree,
|
|
895
|
+
getPoolConfig,
|
|
896
|
+
} from "@veilo/sdk-core";
|
|
897
|
+
import { NATIVE_SOL_MINT, sol } from "@veilo/sdk-core/config";
|
|
898
|
+
|
|
899
|
+
// Initialize global config (once per program)
|
|
900
|
+
await initializeGlobalConfig({
|
|
901
|
+
program,
|
|
902
|
+
admin: adminKeypair,
|
|
903
|
+
});
|
|
141
904
|
|
|
142
|
-
|
|
905
|
+
// Initialize a pool for native SOL
|
|
906
|
+
await initializePool({
|
|
907
|
+
program,
|
|
908
|
+
payer: adminKeypair,
|
|
909
|
+
admin: adminKeypair,
|
|
910
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
911
|
+
minDepositAmount: sol(0.1), // 0.1 SOL
|
|
912
|
+
maxDepositAmount: sol(100), // 100 SOL
|
|
913
|
+
minWithdrawAmount: sol(0.1),
|
|
914
|
+
maxWithdrawAmount: sol(100),
|
|
915
|
+
feeBps: 50, // 0.5%
|
|
916
|
+
feeErrorMarginBps: 10, // 0.1% margin
|
|
917
|
+
minWithdrawalFee: 1_000_000n, // 0.001 SOL minimum
|
|
918
|
+
});
|
|
143
919
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
920
|
+
// Add first Merkle tree (tree ID 0)
|
|
921
|
+
await addMerkleTree({
|
|
922
|
+
program,
|
|
923
|
+
payer: adminKeypair,
|
|
924
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
925
|
+
treeId: 0,
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
// Update pool configuration
|
|
929
|
+
await updatePoolConfig({
|
|
930
|
+
program,
|
|
931
|
+
admin: adminKeypair,
|
|
932
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
933
|
+
feeBps: 30, // Change to 0.3%
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
// Read pool configuration
|
|
937
|
+
const poolConfig = await getPoolConfig(program, NATIVE_SOL_MINT);
|
|
938
|
+
console.log("TVL:", poolConfig.totalTvl);
|
|
939
|
+
console.log("Fee:", poolConfig.feeBps, "bps");
|
|
940
|
+
console.log("Num Trees:", poolConfig.numTrees);
|
|
941
|
+
```
|
|
148
942
|
|
|
149
943
|
---
|
|
150
944
|
|
|
151
|
-
### 4.3
|
|
945
|
+
### 4.3 UTXO Management
|
|
152
946
|
|
|
153
|
-
|
|
947
|
+
The SDK uses a UTXO (Unspent Transaction Output) model with Poseidon commitments:
|
|
154
948
|
|
|
155
949
|
```ts
|
|
156
950
|
import {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
951
|
+
generateKeypair,
|
|
952
|
+
keypairFromPrivateKey,
|
|
953
|
+
createUTXO,
|
|
954
|
+
createOwnedUTXO,
|
|
955
|
+
createOwnedZeroUTXO,
|
|
956
|
+
deriveNullifier,
|
|
957
|
+
type Keypair,
|
|
958
|
+
type UTXO,
|
|
959
|
+
type SerializedUTXO,
|
|
960
|
+
type InputUTXO,
|
|
961
|
+
} from "@veilo/sdk-core";
|
|
962
|
+
import { NATIVE_SOL_MINT } from "@veilo/sdk-core/config";
|
|
963
|
+
import { pubkeyToField } from "@veilo/sdk-core";
|
|
964
|
+
|
|
965
|
+
// Generate a random keypair
|
|
966
|
+
const keypair: Keypair = generateKeypair();
|
|
967
|
+
// keypair = { privateKey: bigint, publicKey: bigint }
|
|
968
|
+
|
|
969
|
+
// Restore keypair from private key
|
|
970
|
+
const restored = keypairFromPrivateKey(privateKeyBigInt);
|
|
971
|
+
|
|
972
|
+
// Create an owned UTXO (includes private key)
|
|
973
|
+
const ownedUtxo: SerializedUTXO = createOwnedUTXO({
|
|
974
|
+
amount: 1_000_000_000n,
|
|
975
|
+
privateKey: keypair.privateKey,
|
|
976
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
170
977
|
});
|
|
171
978
|
|
|
172
|
-
//
|
|
173
|
-
const
|
|
979
|
+
// Create a zero UTXO (for unused inputs/outputs)
|
|
980
|
+
const zeroUtxo = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), keypair);
|
|
174
981
|
|
|
175
|
-
//
|
|
176
|
-
const
|
|
982
|
+
// Derive nullifier for spending
|
|
983
|
+
const nullifier = deriveNullifier(
|
|
984
|
+
ownedUtxo.privateKey,
|
|
985
|
+
ownedUtxo.commitment,
|
|
986
|
+
0, // pathIndex in Merkle tree
|
|
987
|
+
0, // treeId
|
|
988
|
+
);
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**UTXO commitment formula:**
|
|
177
992
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
value: 1_000_000n,
|
|
181
|
-
owner,
|
|
182
|
-
});
|
|
183
|
-
// full.commitment is 32 bytes
|
|
993
|
+
```
|
|
994
|
+
commitment = Poseidon(amount, pubkey, blinding, mintAddress)
|
|
184
995
|
```
|
|
185
996
|
|
|
186
|
-
|
|
997
|
+
**Nullifier formula:**
|
|
187
998
|
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
|| owner pubkey (32 bytes)
|
|
191
|
-
|| rho (32 bytes random)
|
|
192
|
-
|| r (32 bytes random)
|
|
999
|
+
```
|
|
1000
|
+
nullifier = Poseidon(privateKey, commitment, pathIndex, treeId)
|
|
193
1001
|
```
|
|
194
1002
|
|
|
195
|
-
|
|
1003
|
+
---
|
|
1004
|
+
|
|
1005
|
+
### 4.4 Merkle Tree Operations
|
|
196
1006
|
|
|
197
1007
|
```ts
|
|
198
|
-
|
|
199
|
-
```
|
|
1008
|
+
import { MerkleTree } from "@veilo/sdk-core";
|
|
200
1009
|
|
|
201
|
-
|
|
1010
|
+
// Create a new Merkle tree (default depth: 20)
|
|
1011
|
+
const tree = new MerkleTree();
|
|
202
1012
|
|
|
203
|
-
|
|
1013
|
+
// Insert commitments
|
|
1014
|
+
const index1 = tree.insert(ownedUtxo1.commitment);
|
|
1015
|
+
const index2 = tree.insert(ownedUtxo2.commitment);
|
|
204
1016
|
|
|
205
|
-
|
|
1017
|
+
// Get current root
|
|
1018
|
+
const root = tree.root();
|
|
206
1019
|
|
|
207
|
-
|
|
1020
|
+
// Get Merkle path for proof generation
|
|
1021
|
+
const path = tree.path(index1);
|
|
1022
|
+
// path = { pathElements: Uint8Array[], pathIndices: number[] }
|
|
208
1023
|
|
|
209
|
-
|
|
1024
|
+
// Get number of leaves
|
|
1025
|
+
const numLeaves = tree.totalLeaves;
|
|
210
1026
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
merkleLeafFromCommitment,
|
|
214
|
-
merkleHashPair,
|
|
215
|
-
merkleRootFromLeaves,
|
|
216
|
-
MerkleTree,
|
|
217
|
-
} from "@zkprivacysol/sdk-core/merkle";
|
|
218
|
-
|
|
219
|
-
// Stateless helpers
|
|
220
|
-
const leaf = merkleLeafFromCommitment(commitment);
|
|
221
|
-
const parent = merkleHashPair(left, right);
|
|
222
|
-
const root = merkleRootFromLeaves([leaf1, leaf2, leaf3]);
|
|
223
|
-
|
|
224
|
-
// Simple incremental tree (toy)
|
|
225
|
-
const tree = new MerkleTree();
|
|
226
|
-
const { index, root: newRoot } = tree.insert(commitment);
|
|
227
|
-
const path = tree.getPath(index); // Merkle path for proofs
|
|
1027
|
+
// Custom tree depth
|
|
1028
|
+
const deepTree = new MerkleTree(25); // 25 levels
|
|
228
1029
|
```
|
|
229
1030
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
- Uses SHA-256 under the hood, returning `Uint8Array` of length 32.
|
|
233
|
-
- Pads with a “zero node” derived from hashing the all-zero leaf repeatedly up the tree.
|
|
234
|
-
- This is intentionally “toy” to keep the SDK usable while the real circuit/Merkle design is still in flux.
|
|
235
|
-
- On-chain, the program only stores the **latest Merkle root** in the `NoteTree` account (v3 layout exposes a `currentRoot` field).
|
|
1031
|
+
The Merkle tree uses Poseidon hash for all internal nodes.
|
|
236
1032
|
|
|
237
1033
|
---
|
|
238
1034
|
|
|
239
|
-
### 4.5
|
|
1035
|
+
### 4.5 Transaction Operations
|
|
240
1036
|
|
|
241
|
-
|
|
1037
|
+
The SDK supports three types of transactions:
|
|
1038
|
+
|
|
1039
|
+
#### Deposits (publicAmount > 0)
|
|
242
1040
|
|
|
243
1041
|
```ts
|
|
244
|
-
import
|
|
245
|
-
import { depositFixedSol } from "@zkprivacysol/sdk-core";
|
|
1042
|
+
import { deposit } from "@veilo/sdk-core";
|
|
246
1043
|
|
|
247
|
-
|
|
248
|
-
const
|
|
1044
|
+
// Create output UTXO
|
|
1045
|
+
const outputUtxo = createOwnedUTXO({
|
|
1046
|
+
amount: 1_000_000_000n, // 1 SOL
|
|
1047
|
+
mintAddress: pubkeyToField(NATIVE_SOL_MINT),
|
|
1048
|
+
keypair: utxoKeypair,
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// Zero inputs for deposit
|
|
1052
|
+
const input1 = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair);
|
|
1053
|
+
const input2 = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair);
|
|
1054
|
+
const output2 = createOwnedZeroUTXO(
|
|
1055
|
+
pubkeyToField(NATIVE_SOL_MINT),
|
|
1056
|
+
utxoKeypair,
|
|
1057
|
+
);
|
|
249
1058
|
|
|
250
|
-
await
|
|
1059
|
+
await deposit({
|
|
251
1060
|
program,
|
|
252
|
-
depositor:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
1061
|
+
depositor: depositorKeypair,
|
|
1062
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1063
|
+
inputTreeId: 0,
|
|
1064
|
+
outputTreeId: 0,
|
|
1065
|
+
root: tree.root(),
|
|
1066
|
+
publicAmount: 1_000_000_000n, // Positive = deposit
|
|
1067
|
+
inputs: [input1, input2],
|
|
1068
|
+
outputs: [outputUtxo, output2],
|
|
1069
|
+
recipient: depositorKeypair.publicKey,
|
|
1070
|
+
fee: 0n,
|
|
1071
|
+
refund: 0n,
|
|
1072
|
+
proof: mockProof,
|
|
256
1073
|
});
|
|
1074
|
+
|
|
1075
|
+
// Insert outputs into tree
|
|
1076
|
+
tree.insert(outputUtxo.commitment);
|
|
1077
|
+
tree.insert(output2.commitment);
|
|
257
1078
|
```
|
|
258
1079
|
|
|
259
|
-
|
|
1080
|
+
#### Withdrawals (publicAmount < 0)
|
|
260
1081
|
|
|
261
1082
|
```ts
|
|
262
|
-
import
|
|
263
|
-
|
|
264
|
-
|
|
1083
|
+
import { withdraw } from "@veilo/sdk-core";
|
|
1084
|
+
|
|
1085
|
+
// Prepare input with Merkle path
|
|
1086
|
+
const input1: InputUTXO = {
|
|
1087
|
+
...ownedUtxo1,
|
|
1088
|
+
pathIndex: 0,
|
|
1089
|
+
pathElements: tree.path(0).pathElements,
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
const zeroInput2: InputUTXO = {
|
|
1093
|
+
...createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair),
|
|
1094
|
+
pathIndex: 0,
|
|
1095
|
+
pathElements: tree.path(0).pathElements,
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
// Zero outputs
|
|
1099
|
+
const output1 = createOwnedZeroUTXO(
|
|
1100
|
+
pubkeyToField(NATIVE_SOL_MINT),
|
|
1101
|
+
utxoKeypair,
|
|
1102
|
+
);
|
|
1103
|
+
const output2 = createOwnedZeroUTXO(
|
|
1104
|
+
pubkeyToField(NATIVE_SOL_MINT),
|
|
1105
|
+
utxoKeypair,
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
await withdraw({
|
|
1109
|
+
program,
|
|
1110
|
+
relayer: relayerKeypair,
|
|
1111
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1112
|
+
inputTreeId: 0,
|
|
1113
|
+
outputTreeId: 0,
|
|
1114
|
+
root: tree.root(),
|
|
1115
|
+
publicAmount: -900_000_000n, // Negative = withdrawal (0.9 SOL)
|
|
1116
|
+
inputs: [input1, zeroInput2],
|
|
1117
|
+
outputs: [output1, output2],
|
|
1118
|
+
recipient: recipientKeypair.publicKey,
|
|
1119
|
+
fee: 100_000_000n, // 0.1 SOL to relayer
|
|
1120
|
+
refund: 0n,
|
|
1121
|
+
proof,
|
|
1122
|
+
});
|
|
1123
|
+
```
|
|
265
1124
|
|
|
266
|
-
|
|
267
|
-
const wallet = provider.wallet as anchor.Wallet;
|
|
1125
|
+
#### Private Transfers (publicAmount = 0)
|
|
268
1126
|
|
|
269
|
-
|
|
1127
|
+
```ts
|
|
1128
|
+
import {
|
|
1129
|
+
privateTransfer,
|
|
1130
|
+
generateKeypair,
|
|
1131
|
+
createOwnedUTXO,
|
|
1132
|
+
} from "@veilo/sdk-core";
|
|
1133
|
+
|
|
1134
|
+
// Create new outputs for recipient
|
|
1135
|
+
const recipientKeypair = generateKeypair();
|
|
1136
|
+
const output1 = createOwnedUTXO({
|
|
1137
|
+
amount: 1_000_000_000n,
|
|
1138
|
+
privateKey: recipientKeypair.privateKey,
|
|
1139
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1140
|
+
});
|
|
270
1141
|
|
|
271
|
-
|
|
1142
|
+
await privateTransfer({
|
|
272
1143
|
program,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
1144
|
+
relayer: relayerKeypair,
|
|
1145
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1146
|
+
inputTreeId: 0,
|
|
1147
|
+
outputTreeId: 0,
|
|
1148
|
+
root: tree.root(),
|
|
1149
|
+
publicAmount: 0n, // Zero = private transfer
|
|
1150
|
+
inputs: [input1, zeroInput2],
|
|
1151
|
+
outputs: [output1, output2],
|
|
1152
|
+
recipient: relayerKeypair.publicKey,
|
|
1153
|
+
fee: 0n,
|
|
1154
|
+
refund: 0n,
|
|
1155
|
+
proof,
|
|
277
1156
|
});
|
|
278
1157
|
|
|
279
|
-
//
|
|
1158
|
+
// Insert new outputs
|
|
1159
|
+
tree.insert(output1.commitment);
|
|
1160
|
+
tree.insert(output2.commitment);
|
|
280
1161
|
```
|
|
281
1162
|
|
|
282
|
-
|
|
1163
|
+
#### Low-Level Transaction Function
|
|
1164
|
+
|
|
1165
|
+
For advanced use cases, use the unified `transact` function directly:
|
|
283
1166
|
|
|
284
1167
|
```ts
|
|
285
|
-
import {
|
|
286
|
-
import { MerkleTree } from "@zkprivacysol/sdk-core/merkle";
|
|
1168
|
+
import { transact } from "@veilo/sdk-core";
|
|
287
1169
|
|
|
288
|
-
|
|
1170
|
+
await transact({
|
|
1171
|
+
program,
|
|
1172
|
+
relayer: relayerKeypair,
|
|
1173
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1174
|
+
inputTreeId: 0,
|
|
1175
|
+
outputTreeId: 0,
|
|
1176
|
+
root: tree.root(),
|
|
1177
|
+
publicAmount: 0n,
|
|
1178
|
+
inputNullifiers: [nullifier1, nullifier2],
|
|
1179
|
+
outputCommitments: [output1.commitment, output2.commitment],
|
|
1180
|
+
extData: {
|
|
1181
|
+
recipient: recipientPubkey,
|
|
1182
|
+
relayer: relayerPubkey,
|
|
1183
|
+
fee: 0n,
|
|
1184
|
+
refund: 0n,
|
|
1185
|
+
},
|
|
1186
|
+
proof,
|
|
1187
|
+
});
|
|
1188
|
+
```
|
|
289
1189
|
|
|
290
|
-
|
|
291
|
-
await createNoteDepositWithMerkle({
|
|
292
|
-
program,
|
|
293
|
-
depositor: wallet,
|
|
294
|
-
denomIndex: 0,
|
|
295
|
-
valueLamports: sol(1),
|
|
296
|
-
tree,
|
|
297
|
-
});
|
|
1190
|
+
---
|
|
298
1191
|
|
|
299
|
-
|
|
300
|
-
// `merklePath` can be used as witness for the zk circuit
|
|
301
|
-
```
|
|
1192
|
+
### 4.6 Proof Generation
|
|
302
1193
|
|
|
303
|
-
|
|
1194
|
+
The SDK provides utilities for preparing circuit inputs:
|
|
304
1195
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1196
|
+
```ts
|
|
1197
|
+
import {
|
|
1198
|
+
prepareTransactionInputs,
|
|
1199
|
+
formatInputsForSnarkjs,
|
|
1200
|
+
computeExtDataHash,
|
|
1201
|
+
encodeSnarkjsProofToTransactionProof,
|
|
1202
|
+
packProofToBytes,
|
|
1203
|
+
type ExtData,
|
|
1204
|
+
type TransactionCircuitInputs,
|
|
1205
|
+
} from "@veilo/sdk-core";
|
|
1206
|
+
|
|
1207
|
+
// Prepare inputs for the circuit
|
|
1208
|
+
const circuitInputs: TransactionCircuitInputs = prepareTransactionInputs({
|
|
1209
|
+
root: tree.root(),
|
|
1210
|
+
publicAmount: 1_000_000_000n,
|
|
1211
|
+
extData: {
|
|
1212
|
+
recipient: recipientPubkey,
|
|
1213
|
+
relayer: relayerPubkey,
|
|
1214
|
+
fee: 0n,
|
|
1215
|
+
refund: 0n,
|
|
1216
|
+
},
|
|
1217
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1218
|
+
inputs: [input1, input2],
|
|
1219
|
+
outputs: [output1, output2],
|
|
1220
|
+
inputTreeId: 0,
|
|
1221
|
+
outputTreeId: 0,
|
|
1222
|
+
});
|
|
308
1223
|
|
|
309
|
-
|
|
1224
|
+
// Format for snarkjs (converts Uint8Array to string representations)
|
|
1225
|
+
const snarkjsInputs = formatInputsForSnarkjs(circuitInputs);
|
|
310
1226
|
|
|
311
|
-
|
|
1227
|
+
// Use with snarkjs to generate proof
|
|
1228
|
+
// const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
1229
|
+
// snarkjsInputs,
|
|
1230
|
+
// wasmPath,
|
|
1231
|
+
// zkeyPath
|
|
1232
|
+
// );
|
|
312
1233
|
|
|
313
|
-
|
|
1234
|
+
// Convert snarkjs proof to on-chain format
|
|
1235
|
+
// const transactionProof = encodeSnarkjsProofToTransactionProof(proof);
|
|
1236
|
+
```
|
|
314
1237
|
|
|
315
|
-
|
|
316
|
-
import * as anchor from "@coral-xyz/anchor";
|
|
317
|
-
import { addRelayer } from "@zkprivacysol/sdk-core";
|
|
318
|
-
import { Keypair } from "@solana/web3.js";
|
|
1238
|
+
---
|
|
319
1239
|
|
|
320
|
-
|
|
321
|
-
const wallet = provider.wallet as anchor.Wallet;
|
|
1240
|
+
### 4.7 Relayer Management
|
|
322
1241
|
|
|
323
|
-
|
|
1242
|
+
```ts
|
|
1243
|
+
import { addRelayer, setPaused } from "@veilo/sdk-core";
|
|
324
1244
|
|
|
1245
|
+
// Add a relayer
|
|
325
1246
|
await addRelayer({
|
|
326
1247
|
program,
|
|
327
|
-
admin:
|
|
328
|
-
|
|
1248
|
+
admin: adminKeypair,
|
|
1249
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1250
|
+
newRelayer: relayerPubkey,
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
// Pause/unpause the pool
|
|
1254
|
+
await setPaused({
|
|
1255
|
+
program,
|
|
1256
|
+
admin: adminKeypair,
|
|
1257
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1258
|
+
paused: true,
|
|
329
1259
|
});
|
|
330
1260
|
```
|
|
331
1261
|
|
|
332
|
-
|
|
1262
|
+
---
|
|
1263
|
+
|
|
1264
|
+
### 4.8 Poseidon Utilities
|
|
333
1265
|
|
|
334
1266
|
```ts
|
|
335
|
-
import {
|
|
336
|
-
|
|
1267
|
+
import {
|
|
1268
|
+
initPoseidon,
|
|
1269
|
+
poseidon1,
|
|
1270
|
+
poseidon2,
|
|
1271
|
+
poseidon3,
|
|
1272
|
+
poseidon4,
|
|
1273
|
+
pubkeyToField,
|
|
1274
|
+
} from "@veilo/sdk-core";
|
|
1275
|
+
|
|
1276
|
+
// Initialize Poseidon (required once before using hash functions)
|
|
1277
|
+
await initPoseidon();
|
|
1278
|
+
|
|
1279
|
+
// Hash functions
|
|
1280
|
+
const hash1 = poseidon1(12345n);
|
|
1281
|
+
const hash2 = poseidon2(12345n, 67890n);
|
|
1282
|
+
const hash3 = poseidon3(12345n, 67890n, 11111n);
|
|
1283
|
+
const hash4 = poseidon4(12345n, 67890n, 11111n, 22222n);
|
|
1284
|
+
|
|
1285
|
+
// Convert Solana PublicKey to field element
|
|
1286
|
+
const fieldElement = pubkeyToField(NATIVE_SOL_MINT);
|
|
1287
|
+
```
|
|
337
1288
|
|
|
338
|
-
|
|
339
|
-
const recipient = Keypair.generate();
|
|
1289
|
+
---
|
|
340
1290
|
|
|
341
|
-
|
|
342
|
-
const nullifier = new Uint8Array(32).fill(3); // demo only
|
|
1291
|
+
## 5. Architecture
|
|
343
1292
|
|
|
344
|
-
|
|
345
|
-
const proofBytes = new Uint8Array([]); // currently ignored by on-chain program
|
|
1293
|
+
### Transaction Model
|
|
346
1294
|
|
|
347
|
-
|
|
348
|
-
program,
|
|
349
|
-
relayer,
|
|
350
|
-
recipient: recipient.publicKey,
|
|
351
|
-
denomIndex: 0,
|
|
352
|
-
root,
|
|
353
|
-
nullifier,
|
|
354
|
-
proof: proofBytes,
|
|
355
|
-
});
|
|
356
|
-
```
|
|
1295
|
+
Veilo uses a UTXO-based privacy model inspired by Zcash and Tornado Cash Nova:
|
|
357
1296
|
|
|
358
|
-
|
|
1297
|
+
1. **Inputs**: 2 UTXOs (can be zero for deposits)
|
|
1298
|
+
2. **Outputs**: 2 UTXOs (can be zero for withdrawals)
|
|
1299
|
+
3. **Public Amount**: Net change (positive = deposit, negative = withdrawal, zero = private transfer)
|
|
359
1300
|
|
|
360
|
-
|
|
361
|
-
- `merklePath`,
|
|
362
|
-
- `feeBps`,
|
|
363
|
-
- a `builder: ProofBuilder` callback
|
|
1301
|
+
Each transaction:
|
|
364
1302
|
|
|
365
|
-
|
|
1303
|
+
- Consumes 2 input UTXOs (proven via Merkle paths)
|
|
1304
|
+
- Creates 2 output UTXOs (commitments added to tree)
|
|
1305
|
+
- Generates 2 nullifiers (prevents double-spending)
|
|
1306
|
+
- Optionally transfers funds in/out of the pool
|
|
366
1307
|
|
|
367
|
-
|
|
1308
|
+
### Privacy Guarantees
|
|
368
1309
|
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
- The front-end sends a withdraw request to that service:
|
|
374
|
-
- `root`, `nullifier`, `denomIndex`, `recipient`, plus any private witness data.
|
|
375
|
-
- The relayer:
|
|
376
|
-
1. Builds & verifies the zk proof off-chain.
|
|
377
|
-
2. Packs it into bytes (e.g. via a `packProofToBytes` helper).
|
|
378
|
-
3. Calls the on-chain `withdraw` via Anchor, using the same program/PDAs as the SDK.
|
|
1310
|
+
- **Commitment hiding**: Amount, owner, and blinding factor are hidden via Poseidon hash
|
|
1311
|
+
- **Nullifier uniqueness**: Each UTXO can only be spent once
|
|
1312
|
+
- **Unlinkability**: No public link between inputs and outputs
|
|
1313
|
+
- **Zero-knowledge proofs**: Transactions proven valid without revealing private data
|
|
379
1314
|
|
|
380
|
-
|
|
1315
|
+
### Multi-Tree Support
|
|
381
1316
|
|
|
382
|
-
|
|
1317
|
+
The protocol supports multiple concurrent Merkle trees per pool:
|
|
1318
|
+
|
|
1319
|
+
- Improves scalability by reducing tree depth
|
|
1320
|
+
- Allows parallel insertions
|
|
1321
|
+
- Each tree has independent state
|
|
1322
|
+
|
|
1323
|
+
### Constants
|
|
1324
|
+
|
|
1325
|
+
```ts
|
|
1326
|
+
import {
|
|
1327
|
+
NATIVE_SOL_MINT, // PublicKey.default (all zeros) for native SOL
|
|
1328
|
+
MERKLE_TREE_DEPTH, // 22 levels
|
|
1329
|
+
ROOT_HISTORY_SIZE, // 256 historical roots
|
|
1330
|
+
DEFAULT_FEE_BPS, // 50 (0.5%)
|
|
1331
|
+
sol, // Helper: sol(1) = 1_000_000_000n lamports
|
|
1332
|
+
} from "@veilo/sdk-core/config";
|
|
1333
|
+
|
|
1334
|
+
import { BN254_FR_MODULUS } from "@veilo/sdk-core";
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
### Type Exports
|
|
1338
|
+
|
|
1339
|
+
The SDK exports the following types for TypeScript users:
|
|
383
1340
|
|
|
384
|
-
|
|
1341
|
+
```ts
|
|
1342
|
+
import type {
|
|
1343
|
+
// UTXO types
|
|
1344
|
+
Keypair,
|
|
1345
|
+
UTXO,
|
|
1346
|
+
SerializedUTXO,
|
|
1347
|
+
InputUTXO,
|
|
1348
|
+
|
|
1349
|
+
// Proof types
|
|
1350
|
+
ExtData,
|
|
1351
|
+
TransactionCircuitInputs,
|
|
1352
|
+
TransactionProofStruct,
|
|
1353
|
+
RawProof,
|
|
1354
|
+
TransactionProofBuilder,
|
|
1355
|
+
|
|
1356
|
+
// Merkle types
|
|
1357
|
+
MerklePath,
|
|
1358
|
+
CircuitMerklePath,
|
|
1359
|
+
|
|
1360
|
+
// Config types
|
|
1361
|
+
PoolInitConfig,
|
|
1362
|
+
PrivacyConfigAccount,
|
|
1363
|
+
GlobalConfigAccount,
|
|
1364
|
+
} from "@veilo/sdk-core";
|
|
1365
|
+
```
|
|
385
1366
|
|
|
386
|
-
|
|
1367
|
+
### SPL Token Support
|
|
387
1368
|
|
|
388
|
-
|
|
1369
|
+
The SDK supports both native SOL and SPL tokens:
|
|
389
1370
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
1371
|
+
```ts
|
|
1372
|
+
import { PublicKey } from "@solana/web3.js";
|
|
1373
|
+
|
|
1374
|
+
// For native SOL
|
|
1375
|
+
const solMint = NATIVE_SOL_MINT; // PublicKey.default
|
|
1376
|
+
|
|
1377
|
+
// For SPL tokens
|
|
1378
|
+
const usdcMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
1379
|
+
|
|
1380
|
+
// Initialize pool for SPL token
|
|
1381
|
+
await initializePool({
|
|
1382
|
+
program,
|
|
1383
|
+
payer: adminKeypair,
|
|
1384
|
+
admin: adminKeypair,
|
|
1385
|
+
mintAddress: usdcMint, // Use SPL token mint
|
|
1386
|
+
minDepositAmount: 1_000_000n, // 1 USDC (6 decimals)
|
|
1387
|
+
// ... other params
|
|
1388
|
+
});
|
|
1389
|
+
```
|
|
393
1390
|
|
|
394
|
-
|
|
1391
|
+
When using SPL tokens, the SDK automatically handles associated token accounts.
|
|
395
1392
|
|
|
396
|
-
|
|
397
|
-
cd packages/privacy-pool
|
|
398
|
-
anchor build
|
|
399
|
-
anchor deploy
|
|
400
|
-
```
|
|
1393
|
+
---
|
|
401
1394
|
|
|
402
|
-
|
|
1395
|
+
## 6. Development
|
|
403
1396
|
|
|
404
|
-
|
|
405
|
-
solana config set --url http://127.0.0.1:8899
|
|
406
|
-
solana-keygen new --outfile ~/.config/solana/id.json
|
|
407
|
-
solana airdrop 10
|
|
408
|
-
```
|
|
1397
|
+
### Environment Variables
|
|
409
1398
|
|
|
410
|
-
|
|
1399
|
+
```bash
|
|
1400
|
+
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
|
|
1401
|
+
export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
1402
|
+
```
|
|
411
1403
|
|
|
412
|
-
|
|
413
|
-
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
|
|
414
|
-
export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
415
|
-
```
|
|
1404
|
+
---
|
|
416
1405
|
|
|
417
|
-
|
|
1406
|
+
## 7. Limitations
|
|
418
1407
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
```
|
|
1408
|
+
- **Off-chain tree management**: Merkle trees must be maintained by relayers/clients
|
|
1409
|
+
- **Proof generation not included**: You must integrate your own circuit/prover
|
|
1410
|
+
- **Development status**: Active development, APIs may change
|
|
423
1411
|
|
|
424
1412
|
---
|
|
425
1413
|
|
|
426
|
-
##
|
|
1414
|
+
## 8. Resources
|
|
1415
|
+
|
|
1416
|
+
- **Repository**: https://github.com/VeiloSolana/veilo-sdk
|
|
1417
|
+
- **Circomlibjs**: https://github.com/iden3/circomlibjs
|
|
1418
|
+
- **Poseidon Hash**: https://www.poseidon-hash.info/
|
|
1419
|
+
|
|
1420
|
+
---
|
|
427
1421
|
|
|
428
|
-
|
|
429
|
-
- Toy implementation, primarily for demos/tests.
|
|
430
|
-
- No persisted tree; you’re expected to maintain state in your own service.
|
|
431
|
-
- **On-chain NoteTree only stores the latest root.**
|
|
432
|
-
- Historical roots/nullifiers must be mirrored off-chain.
|
|
433
|
-
- **Proofs are relayer-only.**
|
|
434
|
-
- SDK does **not** generate Groth16/Plonk proofs.
|
|
435
|
-
- On-chain program currently only sees `Vec<u8>` and does not verify it yet.
|
|
436
|
-
- **API is still evolving.**
|
|
437
|
-
- Types, exports, and function signatures may change as the circuit + relayer design solidifies.
|
|
1422
|
+
## License
|
|
438
1423
|
|
|
439
|
-
|
|
1424
|
+
ISC
|