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